Skip to content

Latest commit

 

History

History
470 lines (309 loc) · 27.1 KB

File metadata and controls

470 lines (309 loc) · 27.1 KB

二、Keras 默认集成和急切执行

本章涵盖了两个高级 TensorFlow 2.0TF 2.0)API,即 Keras 和估计器。 本章重点关注惰性求值和急切执行的概念,重点介绍如何在 TensorFlow 1.xTF 1.x)和 TF 2.0 中求值基础计算图之间的差异 。 本章还提供了有关使用诸如 Keras 之类的高级 API 构建自定义模型(使用自定义低级操作)的详细指南。

本章将涵盖以下主题:

  • TF 2.0 中的新抽象
  • 深入了解 Keras API
  • 估计器
  • 求值 TensorFlow 图

技术要求

为了运行本章中给出的代码摘录,您将需要以下硬件和软件:

  • TF 2.0 或更高版本(足够使用 CPU 或 GPU 版本)
  • Python 3.4+(目前,TensorFlow 支持的最高 Python 版本是 3.6)
  • NumPy(如果不是由 TensorFlow 自动安装)

可在这个页面中获得本章的代码文件。

TF 2.0 中的新抽象

抽象是在编程和软件开发过程中使用的非常流行的工具。 从非常高级的意义上讲,抽象指的是隔离和描述特定任务或一组任务的中心思想而不必指定物理,空间或时间细节的过程。 正确完成后,抽象可以显着减少针对特定任务需要编写的代码量。 它还提高了现有代码的可重用性,并使其与 TF 2.0 兼容。

在使用机器学习系统时,有一些常见的高级任务,例如训练数据,建模,模型评估,预测,模型存储和模型加载,这是常见的...

深入了解 Keras API

TF 2.0 与 Keras 的结合比以前紧密,特别是对于高级 API。 如果您刚开始在 TensorFlow 中构建基于神经网络的模型,则建议您从 Keras 开始。 简而言之,Keras 公开了用户友好的 API,用于执行常见任务,例如加载数据,构建模型,训练模型,评估模型,运行模型以及加载和保存以前的模型。 影响其灵活性的一个重要因素是,它允许您在不同的抽象级别上无缝运行。

什么是 Keras?

Keras 是用于构建和训练深度学习模型的流行的高级 API。 Keras 的核心是高级神经网络 API 规范。 它在机器学习社区中被研究人员,爱好者和软件工程师广泛使用。 它的开发着眼于实现快速实验。 它具有多种机器学习平台和编程语言的实现,例如 TensorFlow,MXNet,TypeScript,JavaScript,CNTK,Theano,PlaidML,Python,Scala 和 CoreML。 TF 2.0 包含 Keras API 规范的完整实现以及 TensorFlow 特定的增强功能和优化功能。 在tf.keras模块中可用。

Keras 是用明确的...

构建模型

机器学习从根本上讲是一系列统计计算,这些统计计算可以实现最终目的。 这些核心统计组件可以封装为模型。 此外,一些标准计算可被视为与此核心的交互。 从程序员的角度来看,将模型看成一个包含大量数学方程的黑匣子可能会很有用。 然后,其他动作可以描述为与此黑匣子的一组交互。

例如,给定一组输入记录,可以将训练模型理解为计算模型参数(或权重)的过程。 推理可以看作是一个过程,使用数学核心和学习到的参数来生成给定输入集的预测。

Keras 大致采用了我们刚刚讨论的抽象范式,以帮助用户使用基于神经网络的模型轻松地构建,训练和预测。 在随后的小节中,我们将详细介绍 Keras 为上述任务中的每一项提供的选项。 我们还将探讨使 Keras 成为不可忽视的强大力量的其他辅助功能。

在 Keras 中,模型是通过组合层来构建的。 每个 Keras 层大致对应于神经网络架构中的层。 模型也可以看作是层的组合。 Keras 提供了多种选择来组合这些层以形成基于神经网络的模型。 接下来的两个小节重点介绍 Keras 为构建模型而公开的两种最流行的 API,也称为数学统计核心

Keras 层 API

在用于模型构建的高级 Keras API 中,Keras 层是基本构建块。 模型通常定义为这些层的某种图形。 这些层也可以被编程为彼此交互。 由于这些是基本的构建块,因此我们可以在训练和推理阶段定义和自定义层的行为。 换句话说,我们具有在前进和后退过程中定义层行为的能力(如果适用)。 从程序员的角度来看,可以将一层视为封装状态和逻辑的数据结构,以从给定的一组输入生成特定的输出。

层...

使用顺序 API 建立简单模型

Sequential API 是 Keras 为构建模型公开的非常简单但功能强大的抽象。 如果刚开始使用 Keras,建议您使用此功能。 如果要使用单输入级模型,这也是推荐的选择。

该 API 的主要组件是tf.keras.Sequential

这对于简单,连续的层组合很有用。 假设您有一个n层神经网络。 假设这些层定义为[layer_1, layer_2, …. , layer_n]

请注意,这些层中的每一层都是 Keras 层,如前所述。 对于我们的实现,这意味着该层对象将是tf.keras.layers中公开的层之一,或者是对基础 Keras 层实现进行子类化的用户定义层。

可以使用tf.keras.Sequential实例的add()方法合并组成层。

按顺序组合它们的一般形式如下:

my_model = tf.keras.Sequential()
my_model.add(layer_1)
.
.
my_model.add(layer_n)

假设您要建立一个描述全连接神经网络的模型(也称为多层感知器MLP)),以对具有五个属性的一维记录进行二分类。 我们的模型包括四个全连接层。 纯粹出于说明目的,我们假设每个全连接层包含 10 个节点或神经元。 这些层中的每一层都使用整流线性单元ReLU)激活函数。 最终输出通过softmax层获取。 可以在相应层的构造器中定义特定于层的自定义。 实现此模型的代码如下:

model = tf.keras.Sequential()

# Adds a densely-connected layer with 10 units and rectified linear unit activations
# Accepts multiple input tensors of size 5 from user
model.add(layers.Dense(10, activation='relu', input_shape=(5,)))

# Add layer 2 with 10 units and relu activations:
model.add(layers.Dense(10, activation='relu'))

# Add layer 3 with 10 units and relu activations:
model.add(layers.Dense(10, activation='relu'))

# Add layer 4 with 10 units and relu activations:
model.add(layers.Dense(10, activation='relu'))

# Add a softmax layer with 2 output units:
model.add(layers.Dense(2, activation='softmax'))

使用Sequential API 的另一种方法是提供列表中的所有层,或者通常提供某种迭代器。 这些可以在初始化模型对象时传递给Sequential()构造器。 这在分隔层描述和模型创建任务时特别有用。 让我们看下面的示例,以更好地理解这一点。

考虑一下尝试从以下这些层的列表中生成模型的示例:layer_list =[layer_1, layer_2, …. , layer_n]。 现在可以通过将layer_list对象直接传递给构造器来创建模型,如下所示:

new_model = tf.keras.Sequential(layer_list)

值得注意的是,前面的语句等同于下面的语句:

new_model = tf.keras.Sequential(layers=layer_list)

这也可以用其他方式使用。 一个示例是将层规范和模型创建过程分开。 让我们进一步探讨这个想法。 假设您有一个用例,其中模型需要多个仅在运行时可用的层。

一种简单的方法是编写一个用于创建层的函数。 让我们编写一个示例函数get_layers(n),它使用整数值n并一个接一个地返回许多Dense层。 为了说明 API 的灵活性,让我们使用 Python 生成器实现该函数:

def get_layers(n):
    while n > 0:
        yield tf.keras.Dense(10, activation='relu')
        n -= 1

如果您不熟悉 Python 生成器,请在继续操作之前参阅这里

前一个代码块中定义的函数接受n的正整数值并返回generator对象。 此生成器生成的每个元素都是一个层。 以下代码段显示了如何使用此函数创建模型:

model_using_generator = tf.keras.Sequential(layers=get_layers(10))

使用函数式 API 建立高级模型

随着机器学习任务的日益成熟,具有多阶段输入和输出的模型变得越来越普遍。 大量实际使用案例涉及具有多阶段输入和输出的模型。 具有多个输入的真实世界模型的一个示例是文本分类模型,该模型可以查看输入文本中的单词和字符序列。

尽管Sequential API 在以串行方式组合层方面做得非常好,但是它不能用于描述基础层的并行组成。 通常,它不能用于构建不具有线性拓扑的层图。 在需要利用特定层的情况下,其实用性也受到限制。

训练模型

训练模型指的是为不同网络组件学习权重的过程,这些过程在给定的一组示例中将损失函数降至最低。 简而言之,训练神经网络意味着找到网络值的最佳组合。 如您所知,训练过程也与评估和预测过程紧密相关。 借助抽象的强大功能,Keras 提供了强大的高级接口来实现和管理端到端的训练过程。 让我们看一下它为使用顺序和函数式 API 创建的模型提供的训练 API。 它为此阶段提供的一些函数如下:

  • model.compile():此函数用于配置训练过程。 用户指定详细信息,例如优化器的类型(以及超参数(如果有的话)),损失函数的类型以及要评估的指标。 这些也是可以使用 TensorBoard 可视化的指标。 下面的示例代码片段描述了一个带有随机梯度下降SGD)优化器,CategoricalCrossentropy损失函数和记录Accuracy指标的样本训练配置:
model.compile(
             # Optimizer
             optimizer=tf.keras.optimizers.SGD(),

            # Loss function to minimize
            loss=keras.losses.CategoricalCrossentropy(),

            # List of metrics to monitor
            metrics=[
                    keras.metrics.SparseCategoricalAccuracy()
            ]
)
  • model.fit():此方法用于提供训练数据并控制实际训练过程。 此方法中的一些重要参数和参数是训练记录,训练标签,训练周期数和训练批量大小。 以下样本片段描述了一个样本训练过程,该过程用于在训练记录(train_x)和训练标签(train_y)上以批号32训练30周期的预定义模型:
model.fit(
      x=train_x,
      y=train_y,
      epochs=30,
      batch_size=32
)
  • model.evaluate():如前所述,训练和评估过程是相互联系的,并且紧密相连。 训练神经网络需要经常更新权重,以找到最佳的权重集。 为此,有必要在当前阶段计算某种类型的网络状态。 此过程称为评估。 更具体地说,评估是针对给定数据集在当前阶段计算网络的损失和其他指标的过程。 请记住,此方法执行的计算是分批执行的。 该函数返回与损失函数相对应的标量。 它还返回与model.compile()阶段中提供的任何度量对应的值。 以下代码段描述了一个评估函数,该函数以批量大小32来计算记录(test_x)和标签(test_y)上的度量:
results = model.evaluate(
      test_x,
      test_y,
      batch_size=32
     )

保存和加载模型

训练后,保存模型以备后用可能非常有用。 在许多用例中,将训练和推理管道分离是一个好主意。 从开发人员的角度来看,模型可以抽象为一个黑匣子,该黑匣子接受一组输入并返回一些输出。 这样,保存模型只不过是导出表示该黑匣子的工件。 然后,还原或加载模型成为使用此黑匣子执行一些实际工作的过程。 这也可以理解为序列化反序列化模型黑匣子的过程。

TF 2.0 支持以多种模式保存和恢复模型:

  • 仅模型架构(Keras)
  • 仅模型权重(Keras)
  • 整个模型:...

分别加载和保存架构和权重

在某些用例中,将模型创建和模型初始化步骤分离是有意义的。 在这种情况下,模型序列化将需要使用单独的过程来加载和保存架构和模型权重。 Keras 为用户提供支持,以独立使用架构和权重。

加载和保存架构

tf.Keras Python API 中,架构交换的基本单元是 Python dict。 Keras 模型使用get_config()方法从现有模型生成此dict。 然后可以使用标准的 Python 序列化和反序列化方法(例如 Pickle 或 HD5)将此dict保存到磁盘或任何其他存储介质中。 您也可以将 Python dict直接写入磁盘上的文件。

假设您要将 Keras 模型的架构my_model保存到磁盘。 以下代码段说明了如何执行此操作:

my_model_architecture = my_model.get_config()

现在,您可以使用选择的方法将此 Python dict保存到磁盘。

对于从配置对象生成模型的逆用例,...

加载和保存权重

在 Python API 中,tensorflow.keras使用 NumPy 数组作为权重交换的单元。 这与用于加载和保存架构的 API 非常相似。 这些 NumPy 数组也可以使用原生 Python 技术保存到磁盘中。 get_weights()set_weights()方法大致类似于get_config()from_config()。 前者返回对应于模型中不同层的 NumPy 数组列表。 后者接受 NumPy 数组列表并更新内存中的模型。

以下代码段说明了如何获取现有模型的权重:

my_model_weights = my_model.get_weights()

给定一组权重和一个模型副本,可以按以下步骤执行更新内存中模型权重的逆操作:

replica_my_model.set_weights(my_model_weights)

如我们所见,可以使用get_config()get_weights()from_configset_weights()的组合存储整个模型。 但是,此过程的局限性在于它不存储有关训练过程的任何信息。

为了更好地理解这一点,让我们看一个例子。 考虑一个具有一个输入层,一个隐藏层和一个输出层的简单模型。 然后,我们将仅使用上一节中讨论的方法来创建此模型的副本。 步骤如下:

  1. 首先,让我们使用functional API 定义此模型:
# Define layer chain
input_layer = tf.keras.layers.Input(shape=(5,))
hidden_layer = tf.keras.layers.Dense(10)(input_layer)
output_layer = tf.keras.layers.Dense(5, activation='softmax')(hidden_layer)

# Define Model
my_model = tf.keras.Model(inputs=input_layer, outputs=output_layer)
  1. 这里的目标是创建先前模型的副本。 为此,让我们创建模型架构和模型权重的副本:
# Save architecture
my_model_arch = my_model.get_config()

# Save weights
my_model_weights = my_model.get_weights()
  1. 要创建我们的副本模型,我们必须创建一个 Keras 模型,其架构与源模型相同:
# Create replica using saved architecture 
my_model_replica = tf.keras.Model.from_config(my_model_arch)
  1. 接下来,我们将权重从源模型复制到模型副本:
# Copy saved weights
my_model_replica.set_weights(
    my_model_weights
)

如您所见,我们已经使用save API 成功创建了源模型的副本。 此外,我们已经通过使用前面介绍的加载 API 将其重新加载到单独的对象内存中进行了测试。 换句话说,我们已经使用loadsave API 创建了模型的副本。

保存和加载整个模型

上一节中描述的过程的主要限制之一是它不包括训练过程。 这可能是用例中的主要障碍,这些用例涉及训练过程中某个时刻的检查点。 为了克服它,TensorFlow 可以完整保存模型。 这主要可以通过两种方式实现-使用 Keras API 或使用SavedModel API。

在以下各节中,我们将简要讨论方法及其语法。 我们还提供了有关何时使用它们的见解。

使用 Keras

可以将使用Sequential API 或functional API 构建的模型保存在单个文件中。 也可以从此文件中加载此模型,而与构建模型所用的代码无关。

该文件包括以下内容:

  • 模型的架构
  • 模型的权重值(如果适用,还包括训练中获得的权重)
  • 优化器及其状态(如果有的话)(可用于从特定点恢复训练)
  • 模型的训练配置(已传递来编译)(如果有)

使用Sequentialfunctional API 创建的 Keras 模型可以直接保存到磁盘。 使用 Keras 的本机 HDF5 文件格式保存文件。 实现此目的的代码的一般形式如下:

model.save('file_name.h5')

可以使用简单的 Python 单一代码将该模型重新加载到内存中。 通用格式如下:

loaded_model = tf.keras.models.load_model(
                                         'path_to_model.h5'
                                         )

这是一种非常直接的方法,在 Python API 中交换模型时效果很好。

使用SavedModel API

SavedModel是在 TensorFlow 生态系统中存储对象的默认方式。 由于这种标准化的性质,它可以用于在不同的 TensorFlow 实现之间交换模型。 使用SavedModel保存的模型除包含模型架构和权重外,还包含实际的 TensorFlow 代码。 SavedModel文件的确切内容可以列出如下:

  • 一个包含模型权重的 TensorFlow 检查点
  • 包含底层 TensorFlow 图的SavedModel原型:
    • 默认情况下,为预测阶段保存了单独的图(训练和评估阶段也分别在适用时存储)
  • 模型的架构配置(如果有)

在 Python API 中,与SavedModel ...进行交互

其它功能

除了非常强大的 API 规范外,TensorFlow 的tf.keras Keras 实现还附带了许多附加组件。 在以下各节中,我们将简要讨论其中最相关的两个。

keras.applications模块

keras.applications模块包含具有流行模型权重的预构建架构。 这些可以直接用于进行预测。 用户还可以使用它们来创建其他网络的输入特征。 该包中突出的预建实现包括:

  • densenet module:Keras 的 DenseNet 模型
  • inception_resnet_v2:Keras 的 Inception-ResNet V2 模型
  • inception_v3:适用于 Keras 的 Inception V3 模型
  • mobilenet:Keras 的 MobileNet v1 模型
  • mobilenet_v2:Keras 的 MobileNet v2 模型
  • nasnet:Keras 的 NASNet-A 模型
  • resnet50:用于 Keras 的 ResNet50 模型
  • vgg16:适用于 Keras 的 VGG16 模型
  • vgg19:适用于 Keras 的 VGG19 模型
  • xception:适用于 Keras 的 Xception V1 模型

每个...

keras.datasets模块

keras.datasets模块包括自动化功能,可以从文件中解析某些流行数据集的数据。 如果本地没有这些文件,它还包括自动通过互联网下载这些文件的功能。 这使用户可以更轻松,更快捷地试验和评估不同的模型。 对于某些用例,此模块可以代替整个数据处理阶段! Keras 随附的各种数据集模块包括以下内容:

  • boston_housing:波士顿房屋价格回归数据集
  • cifar10:CIFAR10 小图像分类数据集
  • cifar100:CIFAR100 小图像分类数据集
  • fashion_mnist:Fashion-MNIST 数据集
  • imdb:IMDB 情感分类数据集
  • mnist:MNIST 手写数字数据集
  • reuters:路透社主题分类数据集

列出的每个数据集都是一个 Python 模块。 可在这个页面中找到其组件的详细列表。

端到端顺序示例

现在,让我们使用上一节中讨论的 Keras API 的组件来完成一个小的实际任务。 让我们使用Sequential API 构建神经网络,以对 MNIST 数据集中的手写数字进行分类。 步骤如下:

  1. 在开始编写任何函数代码之前,我们需要将tensorflowkeras导入内存:
import tensorflow as tfimport tensorflow.keras as keras
  1. 然后,让我们开始将数据集加载到内存中。 为此,请使用前面章节中讨论的keras.datasets模块:
# Load Data(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
  1. 在前面的代码段中,数据作为numpy数组加载到内存中。 ...

估计器

从头开始构建机器学习模型时,从业人员通常会经历多个高级阶段。 其中包括训练,评估,预测和装运,以供大规模使用(或导出)。 到目前为止,开发人员必须编写自定义代码才能实现这些步骤中的每个步骤。 在所有应用中,运行这些过程所需的许多样板代码都保持不变。 更糟的是,此代码很容易需要在低抽象级别上进行操作。 这些问题放在一起,可能会在开发过程中造成极大的效率低下。

TensorFlow 团队尝试通过引入 Estimators 来解决此问题,Estimators 是一个高级 API,旨在抽象出在上述阶段执行不同任务时产生的许多复杂性。 具体来说,估计器是用于封装以下类别任务的高级 API:

  • 训练
  • 评价
  • 预测
  • 模型共享(导出和运输模型)

用户可以从一组预先构建的估计器中进行选择,甚至可以实现自己的估计器。 标准库中提供了针对各种常用机器学习和深度学习算法的估计器的实现。

估计器具有以下优点:

  • 基于估计器的模型与硬件和环境无关:
    • 程序员不必担心 Estimator 是在本地计算机上运行还是在远程计算网格上运行。
    • 程序员可以在 CPU,GPU 或 TPU 上运行基于 Estimator 的模型,而无需重新编码他们的模型。
  • 估计器简​​化了团队中不同开发人员之间或使用不同环境或栈的团队之间的共享实现。
  • 程序员可以使用高级直观代码来开发高性能和前沿模型。 换句话说,程序员不必在管理低级 TensorFlow API 的复杂性上浪费时间。
  • 估计器建立在tf.keras.layers本身上,从而简化了自定义。
  • 估计器为您构建图。
  • 估计器提供了一个安全分布的训练循环,该循环控制如何以及何时执行以下操作:
    • 建立图
    • 初始化变量
    • 加载数据
    • 处理异常
    • 创建检查点文件并从故障中恢复
    • 为 TensorBoard 保存摘要
  • 使用 Estimators 编写应用时,程序员可以灵活地将数据输入管道与模型分开。 通过这种分离,可以轻松地尝试使用不同的数据集和不同的数据源。

在 TF 2.0 中,Keras 已经提供了 Estimators 公开的许多功能。 如果您只是入门,那么 Keras 是一个更容易学习的 API。 建议初学者在评估器上使用 Keras API。 一旦用例需要使用 Estimators,就可以查找并了解更多信息。 有关详细指南,请访问这里

求值 TensorFlow 图

TensorFlow 的中心思想是,要求程序员创建计算图以指定需要执行的操作才能获得所需的结果。 然后,程序员指定了硬件和其他环境参数,以针对给定的一组输入来计算此计算图的输出。 这意味着在程序员明确计算图之前,值和变量没有任何值。 当程序员真正想要的只是数量的值时,这增加了程序员创建和管理会话的开销。

TF 2.0 旨在通过更改求值和计算基础计算图的方式来解决此问题。 用一个句子,TF ...

延迟加载与急切执行

延迟加载是一种编程范例,其中直到实际需要数量才计算数量的值。 换句话说,在没有明确请求之前,不会初始化对象。 这样做的主要好处是,当按需计算数量值时,无需使用额外的内存来存储计算结果。 如果正确使用,这将导致非常有效的内存使用并提高速度。

急切执行可以理解为与延迟加载相反。 在此,数量的值一定义就立即计算,而不必等到它被调用。 这意味着当实际请求数量时,该值从内存中返回,而不是从头开始计算。 这有助于最小化返回查询结果所需的时间,因为用户不必等待计算值所花费的时间。

可以通过添加两个常量的简单操作来说明两者之间的区别:ab。 首先,让我们看一下 2.0 之前的 TensorFlow 版本。 这些要求用户定义一个计算图,然后使用会话来运行和求值该图。 这可以理解为延迟加载的示例。 让我们看一下以下代码片段,以获得更好的主意:

# Define constants
a = tf.constant(10)
b = tf.constant(32)

# Define add operation
c = a + b
print(f"Value outside session: {c}”)

这给出以下输出:

Outside session: Tensor("add_1:0", shape=(), dtype=int32)

在此阶段,我们可以看到c的值(即add对两个常量进行运算的结果)实际上是张量,没有实际数值。 因此,我们可以看到该图已构建但尚未求值。 为了获得加法运算的实际数值结果,我们必须定义一个会话来运行和求值基础图:

# Create a session and run graph in it
with tf.Session() as sess:
  print(f"Value inside Session: {c}”)

您将看到以下输出:

Value inside Session: 42

这表明添加操作仅在会话中运行后才进行求值。

现在,让我们尝试使用 TF 2.0 及更高版本的相同示例。 我们用相同的变量名称和值定义两个常量。 我们还定义了第三个变量来保存加法的结果。 然后,我们在紧接之后打印加法的值:

# Define constants
a = tf.constant(10)
b = tf.constant(32)

#Define add operation
c = a + b
print(f"Value outside session: {c}")

结果输出如下:

Value outside session: 42

如我们所见,此阶段的输出在 TensorFlow 2.0+ 和 <2.0 版本之间有所不同。 在这种情况下,c变量已经包含加法运算的值。 无需程序员求值任何计算图即可进行计算。 换句话说,加法操作急切地执行。 这是 2.0 及更高版本与旧版本之间的主要区别。

TF 2.0 与 Python 编程语言紧密集成。 急切的执行使张量可以无缝用作本机 Python 对象,而不必担心求值计算图以及管理会话或基础硬件。 好处不止于此。 急切的执行使程序员能够利用宿主编程语言的强大控制流结构。 TensorFlow 代码现在与平台的其余部分更加直观地集成,这为开发人员带来了巨大的价值,因为它不再需要特殊的流控制结构。 这也为实验,调试和笔记本环境增加了重要价值。

总结

在本章中,我们了解了 TF 2.0 中可用于模型构建,训练,保存和加载的高级抽象。 深入研究 Keras API,我们了解了如何通过使用Sequentialfunctional API 组合层来构建模型。 我们还了解了如何利用 Keras API 的高级抽象来训练模型。 本章还研究了在各种配置和模式下加载和保存模型的复杂性。 我们已经了解了保存模型,架构和权重的不同方法,本章对每种方法进行了深入的说明,并描述了何时应该选择一种方法。

将讨论的所有概念放在一起...