Python 深度学习第二版(GPT 重译)(二)(1)https://developer.aliyun.com/article/1485262
5.1.2 深度学习中泛化性质的本质
深度学习模型的一个显著特点是,只要具有足够的表征能力,它们就可以被训练来拟合任何东西。
不相信?试着洗牌 MNIST 标签并在此基础上训练一个模型。尽管输入和洗牌标签之间没有任何关系,训练损失仍然可以很好地下降,即使是使用相对较小的模型。当然,由于在这种情况下没有泛化的可能性,验证损失不会随时间改善。
列表 5.4 使用随机洗牌标签拟合 MNIST 模型
(train_images, train_labels), _ = mnist.load_data() train_images = train_images.reshape((60000, 28 * 28)) train_images = train_images.astype("float32") / 255 random_train_labels = train_labels[:] np.random.shuffle(random_train_labels) model = keras.Sequential([ layers.Dense(512, activation="relu"), layers.Dense(10, activation="softmax") ]) model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) model.fit(train_images, random_train_labels, epochs=100, batch_size=128, validation_split=0.2)
实际上,你甚至不需要用 MNIST 数据来做这个实验——你可以只生成白噪声输入和随机标签。只要模型有足够的参数,你也可以在这上面拟合一个模型。它最终只会记住特定的输入,就像一个 Python 字典一样。
如果是这样的话,那么深度学习模型到底是如何泛化的呢?它们难道不应该只是学习训练输入和目标之间的临时映射,就像一个高级dict
一样吗?我们有什么期望这种映射会适用于新的输入呢?
事实证明,深度学习中的泛化性质与深度学习模型本身关系不大,而与现实世界中信息的结构有很大关系。让我们看看这里到底发生了什么。
流形假设
MNIST 分类器的输入(预处理之前)是一个 28×28 的整数数组,取值范围在 0 到 255 之间。因此,可能的输入值总数是 256 的 784 次方——远远大于宇宙中的原子数量。然而,这些输入中很少有看起来像有效 MNIST 样本的:实际手写数字只占据了所有可能的 28×28 uint8
数组父空间中的一个微小子空间。而且,这个子空间不仅仅是在父空间中随机分布的一组点:它具有高度结构化。
首先,有效手写数字的子空间是连续的:如果你拿一个样本并稍微修改它,它仍然可以被识别为相同的手写数字。此外,所有有效子空间中的样本都通过平滑路径连接在一起。这意味着如果你拿两个随机的 MNIST 数字 A 和 B,存在一个“中间”图像序列,将 A 变形为 B,使得两个连续数字非常接近彼此(见图 5.7)。也许在两个类之间边界附近会有一些模糊的形状,但即使这些形状看起来仍然非常像数字。
[外链图片转存中…(img-licfhwXU-1710946537843)]
图 5.7 不同的 MNIST 数字逐渐变形成彼此,显示手写数字空间形成了一个“流形”。此图像是使用第十二章的代码生成的。
从技术角度来说,你会说手写数字形成了一个流形,位于可能的 28×28 uint8
数组空间中。这是一个大词,但概念相当直观。一个“流形”是某个父空间中的低维子空间,局部类似于线性(欧几里得)空间。例如,在平面上的平滑曲线是 2D 空间中的 1D 流形,因为对于曲线的每个点,你都可以画出一个切线(曲线可以在每个点处用一条直线来近似)。在 3D 空间中的平滑曲面是 2D 流形。依此类推。
更一般地,流形假设认为所有自然数据都位于编码它的高维空间中的低维流形上。这是关于宇宙中信息结构的一个非常强烈的陈述。据我们所知,这是准确的,也是深度学习有效的原因。这对于 MNIST 数字是正确的,但也适用于人脸、树形态、人声和甚至自然语言。
流形假设意味着
- 机器学习模型只需要适应潜在输入空间(潜在流形)中相对简单、低维、高度结构化的子空间。
- 在这些流形中的一个中,总是可以在两个输入之间插值,也就是说,通过一个连续路径将一个变形为另一个,路径上的所有点都落在流形上。
在深度学习中,插值样本之间的能力是理解泛化的关键。
插值作为泛化的来源
如果你处理可以插值的数据点,你可以通过将它们与流形上靠近的其他点联系起来,开始理解以前从未见过的点。换句话说,你可以通过插值来填补空白,从而理解空间的整体。
请注意,潜在流形上的插值与父空间中的线性插值是不同的,如图 5.8 所示。例如,在两个 MNIST 数字之间的像素的平均值通常不是一个有效的数字。
[外链图片转存中…(img-HklXaYOZ-1710946537843)]
图 5.8 线性插值和潜在流形上的插值之间的差异。数字的潜在流形上的每个点都是一个有效的数字,但两个数字的平均值通常不是。
至关重要的是,虽然深度学习通过在学习的数据流形上进行插值来实现泛化,但假设插值就是泛化的全部是错误的。这只是冰山一角。插值只能帮助你理解与之前看到的非常接近的事物:它实现了局部泛化。但值得注意的是,人类总是处理极端的新颖性,并且做得很好。你不需要事先在无数例子上接受训练,以便应对你将要遇到的每种情况。你每一天都与以往任何一天都不同,也与自人类诞生以来的任何一天都不同。你可以在纽约待一周,上海待一周,班加罗尔待一周,而无需为每个城市进行数千次的学习和排练。
人类能够进行极端泛化,这是由于插值之外的认知机制所实现的:抽象、世界的符号模型、推理、逻辑、常识、对世界的内在先验——我们通常称之为理性,与直觉和模式识别相对。后者在很大程度上是插值性质的,但前者不是。这两者对智能都是至关重要的。我们将在第十四章中更多地讨论这个问题。
为什么深度学习有效
还记得第二章中的揉皱纸球的比喻吗?一张纸代表了 3D 空间中的 2D 流形(见图 5.9)。深度学习模型是一种展开纸球的工具,也就是说,是为了解开潜在流形。
[外链图片转存中…(img-0O5k4SQH-1710946537844)]
图 5.9 展开复杂的数据流形
一个深度学习模型基本上是一个非常高维的曲线—一条平滑连续的曲线(受模型架构先验的额外约束),因为它需要是可微的。这条曲线通过梯度下降逐渐和增量地拟合到数据点。深度学习的本质是关于取一个大的、复杂的曲线—一个流形—并逐渐调整其参数,直到它拟合一些训练数据点。
曲线涉及足够多的参数,可以拟合任何东西——实际上,如果你让你的模型训练足够长的时间,它最终将纯粹地记忆其训练数据,根本无法泛化。然而,你要拟合的数据并不是由稀疏分布在基础空间中的孤立点组成。你的数据在输入空间内形成了一个高度结构化的低维流形—这就是流形假设。由于随着梯度下降的进行,将模型曲线拟合到这些数据中是逐渐平稳进行的,因此在训练过程中会有一个中间点,此时模型大致近似于数据的自然流形,正如你在图 5.10 中所看到的。
[外链图片转存中…(img-LpBm5HIJ-1710946537844)]
图 5.10 从随机模型到过拟合模型,以及作为中间状态实现稳健拟合
沿着模型在那一点学习的曲线移动将接近沿着数据的实际潜在流形移动—因此,模型将能够通过在训练输入之间进行插值来理解以前从未见过的输入。
除了它们具有足够的表征能力这一显而易见的事实外,深度学习模型具有一些特性使它们特别适合学习潜在流形:
- 深度学习模型实现了从输入到输出的平滑连续映射。它必须是平滑和连续的,因为它必须是可微的,这是必然的(否则你无法进行梯度下降)。这种平滑性有助于近似潜在流形,这些流形具有相同的特性。
- 深度学习模型往往以与其训练数据中信息“形状”相似的方式进行结构化(通过架构先验)。这特别适用于图像处理模型(在第八章和第九章讨论)和序列处理模型(第十章)。更一般地说,深度神经网络以分层和模块化的方式构建其学习表示,这与自然数据组织方式相呼应。
训练数据至关重要
虽然深度学习确实非常适合流形学习,但泛化的能力更多地是由数据的自然结构而不是模型的任何属性决定的。只有当你的数据形成一个可以进行插值的流形时,你才能进行泛化。你的特征越具信息性,噪声越少,你就越能进行泛化,因为你的输入空间将更简单、更有结构。数据筛选和特征工程对泛化至关重要。
此外,由于深度学习是曲线拟合,为了使模型表现良好,它需要在其输入空间上进行密集采样训练。在这种情况下,“密集采样”意味着训练数据应该密集覆盖整个输入数据流形(参见图 5.11)。这在决策边界附近尤为重要。通过足够密集的采样,可以通过在过去的训练输入之间进行插值来理解新的输入,而无需使用常识、抽象推理或关于世界的外部知识—这些是机器学习模型无法访问的东西。
[外链图片转存中…(img-St7XNyUO-1710946537844)]
图 5.11 为了学习一个能够准确泛化的模型,需要对输入空间进行密集采样。
因此,您应始终牢记改进深度学习模型的最佳方法是在更多或更好的数据上训练它(当然,添加过于嘈杂或不准确的数据将损害泛化能力)。输入数据流形的更密集覆盖将产生更好泛化的模型。您永远不应期望深度学习模型执行比其训练样本之间的粗略插值更多的操作,因此您应尽一切可能使插值变得更容易。您在深度学习模型中找到的唯一东西就是您放入其中的东西:编码在其架构中的先验和训练数据。
当无法获取更多数据时,下一个最佳解决方案是调节模型允许存储的信息量,或者对模型曲线的平滑性添加约束。如果一个网络只能记住少量模式,或者非常规律的模式,优化过程将迫使其专注于最突出的模式,这些模式更有可能泛化良好。这种通过这种方式对抗过拟合的过程称为正则化。我们将在第 5.4.4 节深入讨论正则化技术。
在开始调整模型以帮助其更好地泛化之前,您需要一种评估当前模型表现的方法。在接下来的部分中,您将学习如何在模型开发过程中监控泛化:模型评估。
5.2 评估机器学习模型
您只能控制您能观察到的内容。由于您的目标是开发能够成功泛化到新数据的模型,因此能够可靠地衡量模型泛化能力至关重要。在本节中,我将正式介绍您可以评估机器学习模型的不同方法。您在上一章中已经看到了其中大部分的应用。
5.2.1 训练、验证和测试集
评估模型总是归结为将可用数据分为三组:训练、验证和测试。您在训练数据上训练模型,并在验证数据上评估模型。一旦您的模型准备投入实际使用,您将最后一次在测试数据上测试它,这些数据应尽可能与生产数据相似。然后您可以将模型部署到生产环境中。
您可能会问,为什么它只有两组:一个训练集和一个测试集?您可以在训练数据上训练,并在测试数据上评估。简单得多!
原因在于,开发模型总是涉及调整其配置:例如,选择层数或层的大小(称为模型的超参数,以区别于参数,即网络的权重)。您通过使用模型在验证数据上的性能作为反馈信号来进行这种调整。本质上,这种调整是一种学习:在某个参数空间中寻找良好配置。因此,基于模型在验证集上的性能调整模型的配置可能很快导致过拟合验证集,即使您的模型从未直接在其上进行训练。
这种现象的核心是信息泄漏的概念。每当您根据模型在验证集上的性能调整模型的超参数时,一些关于验证数据的信息就会泄漏到模型中。如果您只这样做一次,针对一个参数,那么泄漏的信息量将很少,您的验证集将保持可靠,用于评估模型。但是,如果您多次重复这个过程——运行一个实验,在验证集上评估,并根据结果修改模型——那么您将泄漏越来越多关于验证集的信息到模型中。
最终,您将得到一个在验证数据上表现良好的模型,因为这是您优化的目标。您关心的是在全新数据上的表现,而不是在验证数据上的表现,因此您需要使用一个完全不同的、以前从未见过的数据集来评估模型:测试数据集。您的模型不应该有关于测试集的任何信息,甚至间接的。如果模型的任何部分基于测试集的性能进行调整,那么您的泛化度量将是有缺陷的。
将数据分成训练、验证和测试集可能看起来很简单,但在数据有限时,有一些高级方法可以派上用场。让我们回顾三种经典的评估方法:简单留出验证、K 折验证和具有洗牌功能的迭代 K 折验证。我们还将讨论使用常识基线来检查您的训练是否有所进展。
简单留出验证
将一部分数据作为测试集。在剩余数据上进行训练,并在测试集上进行评估。正如您在前面的部分中看到的,为了防止信息泄漏,您不应该根据测试集调整模型,因此您还应该保留一个验证集。
从示意图 5.12 的示意图上看,留出验证看起来像是。列表 5.5 显示了一个简单的实现。
[外链图片转存中…(img-FY0qTnrF-1710946537844)]
图 5.12 简单留出验证分割
列表 5.5 留出验证(为简单起见省略了标签)
num_validation_samples = 10000 np.random.shuffle(data) # ❶ validation_data = data[:num_validation_samples] # ❷ training_data = data[num_validation_samples:] # ❸ model = get_model() # ❹ model.fit(training_data, ...) # ❹ validation_score = model.evaluate(validation_data, ...) # ❹ ... # ❺ model = get_model() # ❻ model.fit(np.concatenate([training_data, # ❻ validation_data]), ...) # ❻ test_score = model.evaluate(test_data, ...) # ❻
❶ 通常适合对数据进行洗牌。
❷ 定义验证集
❸ 定义训练集
❹ 在训练数据上训练模型,并在验证数据上评估
❺ 在这一点上,您可以调整您的模型,重新训练它,评估它,再次调整它。
❻ 一旦调整了超参数,通常会从头开始在所有非测试数据上训练最终模型。
这是最简单的评估协议,但存在一个缺陷:如果可用的数据很少,那么您的验证和测试集可能包含的样本太少,无法统计代表手头的数据。这很容易识别:如果在分割之前对数据进行不同的随机洗牌轮次导致模型性能的度量值非常不同,那么您就会遇到这个问题。K 折验证和具有洗牌功能的迭代 K 折验证是解决这个问题的两种方法,接下来将讨论。
K 折验证
使用这种方法,将数据分成K
个大小相等的分区。对于每个分区i
,在剩余的K - 1
个分区上训练模型,并在分区i
上评估。然后,您的最终得分是获得的 K 个分数的平均值。当您的模型的性能根据训练-测试分割显示出显著变化时,这种方法是有帮助的。与留出验证一样,这种方法并不免除您使用一个不同的验证集进行模型校准。
从图 5.13 的示意图上看,K 折交叉验证看起来像是。列表 5.6 显示了一个简单的实现。
[外链图片转存中…(img-x8hgiGI5-1710946537844)]
图 5.13 K 折交叉验证,K=3
列表 5.6 K 折交叉验证(为简单起见省略了标签)
k = 3 num_validation_samples = len(data) // k np.random.shuffle(data) validation_scores = [] for fold in range(k): validation_data = data[num_validation_samples * fold: # ❶ num_validation_samples * (fold + 1)] # ❶ training_data = np.concatenate( # ❷ data[:num_validation_samples * fold], # ❷ data[num_validation_samples * (fold + 1):]) # ❷ model = get_model() # ❸ model.fit(training_data, ...) validation_score = model.evaluate(validation_data, ...) validation_scores.append(validation_score) validation_score = np.average(validation_scores) # ❹ model = get_model() # ❺ model.fit(data, ...) # ❺ test_score = model.evaluate(test_data, ...) # ❺
❶ 选择验证数据分区
❷ 使用剩余的数据作为训练数据。请注意,+ 运算符表示列表连接,而不是求和。
❸ 创建一个全新的模型实例(未经训练)
❹ 验证分数:k 个折叠的验证分数的平均值
❺ 在所有非测试数据上训练最终模型
具有洗牌功能的迭代 K 折验证
这个是用于在可用数据相对较少且需要尽可能精确评估模型的情况下。我发现在 Kaggle 竞赛中非常有帮助。它包括多次应用 K 折验证,在每次将数据随机洗牌后将其分成K
份。最终得分是在每次 K 折验证运行中获得的得分的平均值。请注意,你最终会训练和评估P * K
个模型(其中P
是你使用的迭代次数),这可能非常昂贵。
5.2.2 打败常识基线
除了你可以使用的不同评估协议之外,你还应该了解的最后一件事是使用常识基线。
训练深度学习模型有点像按下一个按钮,在另一个平行世界中发射火箭。你听不到也看不到。你无法观察到流形学习过程—它发生在一个有数千维度的空间中,即使你将其投影到 3D,你也无法解释它。你唯一的反馈是你的验证指标—就像你看不见的火箭上的高度计。
能够判断你是否有所进展特别重要。你开始时的高度是多少?你的模型似乎有 15%的准确率—这算好吗?在开始处理数据集之前,你应该始终选择一个微不足道的基线来尝试超越。如果你超过了这个阈值,你就知道你做对了:你的模型实际上正在利用输入数据中的信息进行泛化预测,你可以继续前进。这个基线可以是随机分类器的性能,或者你能想象到的最简单的非机器学习技术的性能。
例如,在 MNIST 数字分类示例中,一个简单的基准是验证准确率大于 0.1(随机分类器);在 IMDB 示例中,它将是验证准确率大于 0.5。在 Reuters 示例中,由于类别不平衡,它将在 0.18-0.19 左右。如果你有一个二元分类问题,其中 90%的样本属于 A 类,10%属于 B 类,那么总是预测 A 的分类器在验证准确率方面已经达到 0.9,你需要做得比这更好。
在开始解决以前没有人解决过的问题时,拥有一个可以参考的常识基线是至关重要的。如果你无法击败一个微不足道的解决方案,你的模型是毫无价值的—也许你使用的是错误的模型,或者你正在处理的问题根本无法用机器学习方法解决。是时候重新审视问题了。
5.2.3 关于模型评估需要记住的事情
在选择评估协议时,请注意以下事项:
- 数据代表性—你希望你的训练集和测试集都能代表手头的数据。例如,如果你试图对数字图像进行分类,并且从一个按类别排序的样本数组开始,将数组的前 80%作为训练集,剩下的 20%作为测试集,将导致你的训练集只包含类别 0-7,而测试集只包含类别 8-9。这似乎是一个荒谬的错误,但这种情况出奇地常见。因此,你通常应该在将数据拆分为训练集和测试集之前随机洗牌你的数据。
- 时间的箭头—如果你试图根据过去来预测未来(例如,明天的天气、股票走势等),在将数据拆分之前不要随机洗牌,因为这样做会造成时间泄漏:你的模型实际上是在未来的数据上进行训练的。在这种情况下,你应该始终确保测试集中的所有数据都晚于训练集中的数据。
- 数据中的冗余——如果你的数据中有一些数据点出现两次(在真实世界数据中很常见),那么对数据进行洗牌并将其分成训练集和验证集将导致训练集和验证集之间存在冗余。实际上,你将在部分训练数据上进行测试,这是你能做的最糟糕的事情!确保你的训练集和验证集是不相交的。
有一个可靠的方法来评估模型性能是你如何能够监控机器学习中的核心张力——在优化和泛化、欠拟合和过拟合之间。
5.3 改善模型拟合
要实现完美拟合,你必须首先过拟合。由于你事先不知道边界在哪里,你必须越过它找到它。因此,你在开始解决问题时的初始目标是获得一个显示一定泛化能力并能够过拟合的模型。一旦你有了这样的模型,你将专注于通过对抗过拟合来完善泛化。
在这个阶段你会遇到三个常见问题:
- 训练无法开始:你的训练损失不会随时间降低。
- 训练开始得很顺利,但你的模型并没有有意义地泛化:你无法击败你设定的常识基线。
- 随着时间的推移,训练和验证损失都在下降,你可以击败你的基线,但似乎无法过拟合,这表明你仍然欠拟合。
让我们看看如何解决这些问题,以实现机器学习项目的第一个重要里程碑:获得具有一定泛化能力的模型(能够击败一个简单的基线)并且能够过拟合。
5.3.1 调整关键梯度下降参数
有时训练无法开始,或者过早停滞。你的损失停滞不前。这总是可以克服的:记住你可以将模型拟合到随机数据上。即使你的问题毫无意义,你仍然应该能够训练出一些东西——即使只是通过记忆训练数据。
当这种情况发生时,通常是由于梯度下降过程的配置问题:你选择的优化器、模型权重的初始值分布、学习率或批量大小。所有这些参数是相互依赖的,因此通常只需调整学习率和批量大小,同时保持其他参数不变即可。
让我们看一个具体的例子:让我们用值为 1 的不合适大学习率训练第二章的 MNIST 模型。
列表 5.7 使用不正确高学习率训练 MNIST 模型
(train_images, train_labels), _ = mnist.load_data() train_images = train_images.reshape((60000, 28 * 28)) train_images = train_images.astype("float32") / 255 model = keras.Sequential([ layers.Dense(512, activation="relu"), layers.Dense(10, activation="softmax") ]) model.compile(optimizer=keras.optimizers.RMSprop(1.), loss="sparse_categorical_crossentropy", metrics=["accuracy"]) model.fit(train_images, train_labels, epochs=10, batch_size=128, validation_split=0.2)
该模型很快达到了 30%–40% 的训练和验证准确率,但无法超越这一范围。让我们尝试将学习率降低到一个更合理的值1e-2
。
列表 5.8 具有更合适学习率的相同模型
model = keras.Sequential([ layers.Dense(512, activation="relu"), layers.Dense(10, activation="softmax") ]) model.compile(optimizer=keras.optimizers.RMSprop(1e-2), loss="sparse_categorical_crossentropy", metrics=["accuracy"]) model.fit(train_images, train_labels, epochs=10, batch_size=128, validation_split=0.2)
现在模型能够训练。
如果你发现自己处于类似情况,尝试
- 降低或增加学习率。学习率过高可能导致更新远超适当拟合,就像前面的例子中一样,而学习率过低可能使训练过于缓慢,看起来停滞不前。
- 增加批量大小。具有更多样本的批次将导致更具信息性和更少噪声的梯度(方差更低)。
最终,你会找到一个能够开始训练的配置。
5.3.2 利用更好的架构先验知识
你有一个适合的模型,但由于某种原因你的验证指标根本没有改善。它们仍然不比随机分类器获得的好:你的模型训练了,但泛化能力不强。发生了什么?
这可能是你会遇到的最糟糕的机器学习情况。这表明你的方法在根本上有问题,而且可能不容易判断。以下是一些建议。
首先,可能是你使用的输入数据根本不包含足够的信息来预测目标:问题的表述是不可解的。这就是之前当我们尝试拟合一个 MNIST 模型时发生的情况,其中标签被洗牌:模型训练得很好,但验证准确率停留在 10%,因为用这样的数据集明显不可能泛化。
也可能是你使用的模型类型不适合当前的问题。例如,在第十章中,你会看到一个时间序列预测问题的例子,其中一个密集连接的架构无法击败一个微不足道的基准线,而一个更合适的循环架构确实能够很好地泛化。使用对问题做出正确假设的模型对于实现泛化是至关重要的:你应该利用正确的架构先验知识。
在接下来的章节中,你将学习到用于各种数据模态(图像、文本、时间序列等)的最佳架构。一般来说,你应该始终确保阅读关于你正在攻击的任务类型的架构最佳实践——很可能你不是第一个尝试的人。
5.3.3 增加模型容量
如果你成功得到一个适合的模型,其中验证指标在下降,并且似乎至少达到了一定程度的泛化能力,恭喜你:你已经接近成功了。接下来,你需要让你的模型开始过拟合。
考虑以下的小模型——一个简单的逻辑回归——在 MNIST 像素上训练。
列表 5.9 在 MNIST 上的简单逻辑回归
model = keras.Sequential([layers.Dense(10, activation="softmax")]) model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) history_small_model = model.fit( train_images, train_labels, epochs=20, batch_size=128, validation_split=0.2)
你会得到类似于图 5.14 的损失曲线:
import matplotlib.pyplot as plt val_loss = history_small_model.history["val_loss"] epochs = range(1, 21) plt.plot(epochs, val_loss, "b--", label="Validation loss") plt.title("Effect of insufficient model capacity on validation loss") plt.xlabel("Epochs") plt.ylabel("Loss") plt.legend()
[外链图片转存中…(img-pbgxiNbc-1710946537845)]
图 5.14 不足模型容量对损失曲线的影响
验证指标似乎停滞不前,或者改善非常缓慢,而不是达到峰值然后逆转。验证损失降至 0.26,然后停在那里。你可以拟合,但你无法明显过拟合,即使在对训练数据进行多次迭代后。你在职业生涯中很可能经常遇到类似的曲线。
记住,总是可以过拟合的。就像训练损失不下降的问题一样,这是一个总是可以解决的问题。如果你似乎无法过拟合,很可能是你的模型的表征能力的问题:你需要一个更大的模型,一个具有更多容量的模型,也就是说,能够存储更多信息的模型。你可以通过添加更多层、使用更大的层(具有更多参数的层)或使用更适合问题的层来增加表征能力(更好的架构先验)。
让我们尝试训练一个更大的模型,一个具有两个中间层,每个层有 96 个单元:
model = keras.Sequential([ layers.Dense(96, activation="relu"), layers.Dense(96, activation="relu"), layers.Dense(10, activation="softmax"), ]) model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) history_large_model = model.fit( train_images, train_labels, epochs=20, batch_size=128, validation_split=0.2)
现在验证曲线看起来正是应该的:模型快速拟合,并在 8 个时期后开始过拟合(见图 5.15)。
[外链图片转存中…(img-MyYWoFFZ-1710946537845)]
图 5.15 具有适当容量的模型的验证损失
5.4 改善泛化能力
一旦你的模型展现出一定的泛化能力并且能够过拟合,就是时候将注意力转移到最大化泛化上了。
5.4.1 数据集整理
你已经学到了深度学习中泛化是源自数据的潜在结构。如果你的数据使得在样本之间平滑插值成为可能,你将能够训练一个泛化的深度学习模型。如果你的问题过于嘈杂或基本上是离散的,比如说,列表排序,深度学习将无法帮助你。深度学习是曲线拟合,而不是魔术。
因此,确保你正在使用一个合适的数据集是至关重要的。在数据收集上投入更多的精力和金钱几乎总是比在开发更好的模型上投入相同的精力和金钱产生更大的回报。
- 确保你有足够的数据。记住你需要对输入-输出空间进行密集采样。更多的数据将产生更好的模型。有时,一开始看起来不可能的问题会随着更大的数据集而变得可解。
- 最小化标记错误——可视化你的输入以检查异常,并校对你的标签。
- 清洁你的数据并处理缺失值(我们将在下一章中介绍)。
- 如果你有很多特征,而不确定哪些是真正有用的,那就进行特征选择。
提高数据泛化潜力的一个特别重要的方法是特征工程。对于大多数机器学习问题,特征工程是成功的关键因素。让我们来看看。
5.4.2 特征工程
特征工程是利用你对数据和手头的机器学习算法(在本例中是神经网络)的知识,通过在数据进入模型之前应用硬编码(非学习的)转换来使算法更好地工作的过程。在许多情况下,期望机器学习模型能够从完全任意的数据中学习是不合理的。数据需要以一种使模型工作更轻松的方式呈现给模型。
让我们看一个直观的例子。假设你正在开发一个模型,可以接受时钟的图像作为输入,并输出一天中的时间(见图 5.16)。
[外链图片转存中…(img-S5RJVWF9-1710946537845)]
图 5.16 读取时钟上时间的特征工程
如果你选择将图像的原始像素作为输入数据,那么你将面临一个困难的机器学习问题。你将需要一个卷积神经网络来解决它,并且需要耗费相当多的计算资源来训练网络。
但是如果你已经在高层次上理解了问题(你了解人类如何读取时钟面上的时间),你可以为机器学习算法想出更好的输入特征:例如,编写一个五行的 Python 脚本来跟踪时钟指针的黑色像素,并输出每个指针尖端的(x, y)
坐标。然后一个简单的机器学习算法可以学会将这些坐标与适当的时间关联起来。
你甚至可以更进一步:进行坐标变换,将(x, y)
坐标表示为相对于图像中心的极坐标。你的输入将变为每个时钟指针的角度theta
。此时,你的特征使问题变得如此简单,以至于不需要机器学习;一个简单的四舍五入操作和字典查找就足以恢复大致的时间。
这就是特征工程的本质:通过以更简单的方式表达问题来使问题变得更容易。使潜在流形更加平滑、简单、更有组织。通常这需要深入理解问题。
在深度学习之前,特征工程曾经是机器学习工作流程中最重要的部分,因为经典的浅层算法没有足够丰富的假设空间来自动学习有用的特征。你向算法呈现数据的方式对其成功至关重要。例如,在卷积神经网络在 MNIST 数字分类问题上取得成功之前,解决方案通常基于硬编码的特征,如数字图像中的循环次数、图像中每个数字的高度、像素值的直方图等。
幸运的是,现代深度学习消除了大部分特征工程的需求,因为神经网络能够自动从原始数据中提取有用的特征。这是否意味着只要使用深度神经网络,你就不必担心特征工程了?不,有两个原因:
- 优秀的特征仍然可以让您更优雅地解决问题,同时使用更少的资源。例如,使用卷积神经网络解决读取时钟面的问题是荒谬的。
- 优秀的特征让您可以用更少的数据解决问题。深度学习模型学习特征的能力依赖于有大量的训练数据可用;如果只有少量样本,那么它们的特征中的信息价值就变得至关重要。
5.4.3 使用早停法
在深度学习中,我们总是使用远远超参数化的模型:它们的自由度远远超过拟合数据的潜在流形所需的最小自由度。这种过度参数化并不是问题,因为你永远不会完全拟合一个深度学习模型。这样的拟合根本不会泛化。你总是会在达到最小可能的训练损失之前中断训练。
找到训练过程中达到最具泛化性拟合的确切点——欠拟合曲线和过拟合曲线之间的确切边界——是改善泛化的最有效的事情之一。
在上一章的例子中,我们会先训练我们的模型比需要的时间更长,以找出产生最佳验证指标的时期数量,然后我们会重新训练一个新模型,确切地达到那个时期数量。这是相当标准的,但它要求你做冗余的工作,有时可能很昂贵。当然,你可以在每个时期结束时保存你的模型,一旦找到最佳时期,就重用你最接近的已保存模型。在 Keras 中,通常使用EarlyStopping
回调来实现这一点,它会在验证指标停止改善时立即中断训练,同时记住已知的最佳模型状态。你将在第七章学习如何使用回调。
5.4.4 正则化您的模型
正则化技术是一组最佳实践,积极阻碍模型完美拟合训练数据的能力,目的是使模型在验证期间表现更好。这被称为“正则化”模型,因为它倾向于使模型更简单,更“规则”,其曲线更平滑,更“通用”;因此,它对训练集不那么特定,更能够通过更接近地逼近数据的潜在流形来泛化。
请记住,正则化模型是一个应该始终由准确的评估程序指导的过程。只有通过测量,您才能实现泛化。
让我们回顾一些最常见的正则化技术,并在实践中应用它们来改进第四章的电影分类模型。
减小网络的大小
您已经学到,一个太小的模型不会过拟合。缓解过拟合的最简单方法是减小模型的大小(模型中可学习参数的数量,由层数和每层单元的数量确定)。如果模型的记忆资源有限,它将无法简单地记住其训练数据;因此,为了最小化损失,它将不得不求助于学习具有关于目标的预测能力的压缩表示——这正是我们感兴趣的表示类型。同时,请记住,您应该使用具有足够参数的模型,以便它们不会欠拟合:您的模型不应该缺乏记忆资源。在容量过大和容量不足之间需要找到一个折衷。
不幸的是,没有一个神奇的公式可以确定正确的层数或每个层的正确大小。你必须评估一系列不同的架构(当然是在验证集上,而不是在测试集上)以找到适合你数据的正确模型大小。找到合适模型大小的一般工作流程是从相对较少的层和参数开始,并增加层的大小或添加新层,直到看到验证损失的收益递减。
让我们尝试在电影评论分类模型上进行这个操作。以下列表显示了我们的原始模型。
列表 5.10 原始模型
from tensorflow.keras.datasets import imdb (train_data, train_labels), _ = imdb.load_data(num_words=10000) def vectorize_sequences(sequences, dimension=10000): results = np.zeros((len(sequences), dimension)) for i, sequence in enumerate(sequences): results[i, sequence] = 1. return results train_data = vectorize_sequences(train_data) model = keras.Sequential([ layers.Dense(16, activation="relu"), layers.Dense(16, activation="relu"), layers.Dense(1, activation="sigmoid") ]) model.compile(optimizer="rmsprop", loss="binary_crossentropy", metrics=["accuracy"]) history_original = model.fit(train_data, train_labels, epochs=20, batch_size=512, validation_split=0.4)
现在让我们尝试用这个较小的模型替换它。
列表 5.11 具有较低容量的模型版本
model = keras.Sequential([ layers.Dense(4, activation="relu"), layers.Dense(4, activation="relu"), layers.Dense(1, activation="sigmoid") ]) model.compile(optimizer="rmsprop", loss="binary_crossentropy", metrics=["accuracy"]) history_smaller_model = model.fit( train_data, train_labels, epochs=20, batch_size=512, validation_split=0.4)
图 5.17 显示了原始模型和较小模型的验证损失的比较。
[外链图片转存中…(img-DR9yPI1H-1710946537845)]
图 5.17 IMDB 评论分类中原始模型与较小模型的比较
正如你所看到的,较小的模型开始过拟合的时间比参考模型晚(在六个周期而不是四个周期之后),一旦开始过拟合,其性能下降速度更慢。
现在,让我们在基准模型中添加一个容量更大的模型——远远超出问题所需的容量。虽然通常使用远超参数化的模型来学习是标准做法,但确实存在记忆容量过大的情况。如果你的模型开始过拟合并且其验证损失曲线看起来波动较大(尽管波动的验证指标也可能是使用不可靠的验证过程的症状,比如验证分割太小),那么你会知道你的模型太大了。
列表 5.12 具有更高容量的模型版本
model = keras.Sequential([ layers.Dense(512, activation="relu"), layers.Dense(512, activation="relu"), layers.Dense(1, activation="sigmoid") ]) model.compile(optimizer="rmsprop", loss="binary_crossentropy", metrics=["accuracy"]) history_larger_model = model.fit( train_data, train_labels, epochs=20, batch_size=512, validation_split=0.4)
图 5.18 显示了较大模型与参考模型的比较。
[外链图片转存中…(img-jN2k8PVM-1710946537846)]
图 5.18 原始模型与 IMDB 评论分类中更大模型的比较
较大模型几乎立即开始过拟合,仅经过一个周期,过拟合程度更严重。其验证损失也更加嘈杂。它非常快地将训练损失降至接近零。模型容量越大,就越能快速对训练数据进行建模(导致训练损失较低),但也越容易过拟合(导致训练和验证损失之间的差异较大)。
添加权重正则化
你可能熟悉奥卡姆剃刀原理:对于某事的两种解释,最有可能正确的解释是最简单的解释——即做出更少假设的解释。这个想法也适用于神经网络学习的模型:在给定一些训练数据和网络架构的情况下,多组权重值(多个模型)可以解释数据。简单模型比复杂模型更不容易过拟合。
在这种情况下,简单模型是指参数值分布熵较低的模型(或者是参数较少的模型,正如你在前一节中看到的)。因此,减轻过拟合的常见方法是通过对模型的复杂性施加约束,强制其权重只取小值,这使得权重值的分布更规则。这被称为权重正则化,通过向模型的损失函数添加与权重较大相关的成本来实现。这个成本有两种形式:
- L1 正则化—添加的成本与权重系数的绝对值成比例(权重的L1 范数)。
- L2 正则化—添加的成本与权重系数的平方值成比例(权重的L2 范数)。在神经网络的背景下,L2 正则化也被称为权重衰减。不要让不同的名称使你困惑:数学上,权重衰减与 L2 正则化是相同的。
在 Keras 中,通过将权重正则化器实例作为关键字参数传递给层来添加权重正则化。让我们向我们最初的电影评论分类模型添加 L2 权重正则化。
清单 5.13 向模型添加 L2 权重正则化
from tensorflow.keras import regularizers model = keras.Sequential([ layers.Dense(16, kernel_regularizer=regularizers.l2(0.002), activation="relu"), layers.Dense(16, kernel_regularizer=regularizers.l2(0.002), activation="relu"), layers.Dense(1, activation="sigmoid") ]) model.compile(optimizer="rmsprop", loss="binary_crossentropy", metrics=["accuracy"]) history_l2_reg = model.fit( train_data, train_labels, epochs=20, batch_size=512, validation_split=0.4)
在前面的清单中,l2(0.002)
表示层的权重矩阵中的每个系数将会为模型的总损失增加 0.002 * weight_coefficient_value ** 2
。请注意,因为这种惩罚仅在训练时添加,所以该模型的损失在训练时会比在测试时高得多。
图 5.19 显示了 L2 正则化惩罚的影响。正如您所看到的,具有 L2 正则化的模型比参考模型更加抵抗过拟合,尽管两个模型具有相同数量的参数。
[外链图片转存中…(img-8FSyoNIx-1710946537846)]
图 5.19 L2 权重正则化对验证损失的影响
作为 L2 正则化的替代,您可以使用以下 Keras 权重正则化器之一。
清单 5.14 Keras 中可用的不同权重正则化器
from tensorflow.keras import regularizers regularizers.l1(0.001) # ❶ regularizers.l1_l2(l1=0.001, l2=0.001) # ❷
❶ L1 正则化
❷ 同时使用 L1 和 L2 正则化
请注意,权重正则化更常用于较小的深度学习模型。大型深度学习模型往往过于参数化,对权重值施加约束对模型容量和泛化能力的影响不大。在这些情况下,更倾向于使用不同的正则化技术:丢弃。
添加丢弃
Dropout 是神经网络中最有效和最常用的正则化技术之一;它是由杰夫·辛顿及其多伦多大学的学生开发的。应用于层的 Dropout 在训练期间会随机丢弃(设置为零)层的一些输出特征。假设给定层在训练期间对于给定输入样本会返回一个向量 [0.2, 0.5, 1.3, 0.8, 1.1]
。应用 Dropout 后,这个向量将随机分布一些零条目:例如,[0, 0.5, 1.3, 0, 1.1]
。丢弃率是被置零的特征的分数;通常设置在 0.2 和 0.5 之间。在测试时,没有单位被丢弃;相反,层的输出值会按照与丢弃率相等的因子进行缩放,以平衡训练时更多单位处于活动状态的事实。
考虑一个包含层输出 layer_output
的 NumPy 矩阵,形状为 (batch_size, features)
。在训练时,我们随机将矩阵中的一部分值置零:
layer_output *= np.random.randint(0, high=2, size=layer_output.shape) # ❶
❶ 在训练时,输出中的单位有 50% 被丢弃
在测试时,我们通过丢弃率缩小输出。在这里,我们缩小了 0.5(因为之前我们丢弃了一半的单位):
layer_output *= 0.5 # ❶
❶ 在测试时
请注意,这个过程可以通过在训练时执行这两个操作并在测试时保持输出不变来实现,这通常是实践中的实现方式(参见图 5.20):
layer_output *= np.random.randint(0, high=2, size=layer_output.shape) # ❶ layer_output /= 0.5 # ❷
❶ 在训练时
❷ 请注意,在这种情况下我们是放大而不是缩小。
[外链图片转存中…(img-SB9MOjut-1710946537846)]
图 5.20 在训练时应用丢弃到激活矩阵,训练期间进行重新缩放。在测试时,激活矩阵保持不变。
这种技术可能看起来奇怪而武断。为什么这有助于减少过拟合?辛顿说,他受到了银行使用的防欺诈机制的启发,其中包括其他事物。他自己的话是:“我去了我的银行。出纳员经常变动,我问其中一个原因。他说他不知道,但他们经常换岗。我想这一定是因为需要员工之间的合作才能成功欺诈银行。这让我意识到,随机地在每个示例中删除不同的神经元子集将防止阴谋,从而减少过拟合。” 核心思想是在层的输出值中引入噪声可以打破不重要的偶然模式(辛顿称之为阴谋),如果没有噪声,模型将开始记忆。
在 Keras 中,您可以通过Dropout
层在模型中引入辍学,该层应用于其前一层的输出。让我们在 IMDB 模型中添加两个Dropout
层,看看它们在减少过拟合方面的效果如何。
列表 5.15 向 IMDB 模型添加辍学
model = keras.Sequential([ layers.Dense(16, activation="relu"), layers.Dropout(0.5), layers.Dense(16, activation="relu"), layers.Dropout(0.5), layers.Dense(1, activation="sigmoid") ]) model.compile(optimizer="rmsprop", loss="binary_crossentropy", metrics=["accuracy"]) history_dropout = model.fit( train_data, train_labels, epochs=20, batch_size=512, validation_split=0.4)
图 5.21 显示了结果的图表。这明显比参考模型有所改进——它似乎也比 L2 正则化效果更好,因为达到的最低验证损失有所改善。
[外链图片转存中…(img-RxG2hQMH-1710946537846)]
图 5.21 辍学对验证损失的影响
总结一下,以下是在神经网络中最大化泛化并防止过拟合的最常见方法:
- 获取更多训练数据,或更好的训练数据。
- 开发更好的特征。
- 减少模型的容量。
- 添加权重正则化(适用于较小的模型)。
- 添加辍学。
总结
- 机器学习模型的目的是泛化:在以前未见过的输入上准确执行。这比看起来更难。
- 深度神经网络通过学习一个能够成功插值训练样本之间的参数模型来实现泛化——这样的模型可以说已经学会了训练数据的“潜在流形”。这就是为什么深度学习模型只能理解与训练时非常接近的输入。
- 机器学习中的基本问题是优化和泛化之间的紧张关系:要实现泛化,您必须首先对训练数据进行良好拟合,但随着时间的推移,改进模型对训练数据的拟合将不可避免地开始损害泛化。每一个深度学习最佳实践都涉及管理这种紧张关系。
- 深度学习模型泛化的能力来自于它们设法学习逼近其数据的潜在流形,因此可以通过插值理解新的输入。
- 在开发模型时,准确评估模型的泛化能力至关重要。您可以使用各种评估方法,从简单的留出验证到 K 折交叉验证和带有洗牌的迭代 K 折交叉验证。请记住,始终保留一个完全独立的测试集用于最终模型评估,因为您的验证数据可能会泄漏到模型中。
- 当您开始处理模型时,您的目标首先是实现具有一定泛化能力且可能过拟合的模型。实现此目标的最佳实践包括调整学习率和批量大小,利用更好的架构先验,增加模型容量,或者简单地延长训练时间。
- 当您的模型开始过拟合时,您的目标转为通过模型正则化来改善泛化。您可以减少模型的容量,添加辍学或权重正则化,并使用提前停止。自然地,更大或更好的数据集始终是帮助模型泛化的首要方法。
¹ 马克·吐温甚至称之为“人类所知最美味的水果”。
六、机器学习的通用工作流程
本章涵盖
- 制定机器学习问题的步骤
- 开发一个可工作模型的步骤
- 部署模型到生产环境并进行维护的步骤
我们先前的示例假设我们已经有一个标记的数据集可以开始训练模型。在现实世界中,这通常不是这样。您不是从数据集开始,而是从问题开始。
想象一下,您正在开设自己的机器学习咨询公司。您注册公司,建立一个漂亮的网站,通知您的网络。项目开始涌入:
- 为图片分享社交网络设计的个性化照片搜索引擎——输入“婚礼”并检索您在婚礼上拍摄的所有照片,无需任何手动标记。
- 在新兴聊天应用的帖子中标记垃圾邮件和冒犯性文本内容。
- 为在线广播用户构建音乐推荐系统。
- 为电子商务网站检测信用卡欺诈。
- 预测显示广告的点击率,以决定在特定时间向特定用户提供哪个广告。
- 在饼干制造线上的传送带上标记异常的饼干。
- 使用卫星图像预测尚未知晓的考古遗址的位置。
伦理注意事项
有时您可能会被提供道德上可疑的项目,比如“构建一个从某人面部照片中评估其信任度的 AI”。首先,该项目的有效性存疑:不清楚为什么信任度会反映在某人的脸上。其次,这样的任务打开了各种道德问题的大门。为这个任务收集数据集将等同于记录标记图片的人的偏见和成见。您将在这些数据上训练的模型只会将这些偏见编码到一个黑盒算法中,给予它们一层薄薄的合法性外衣。在我们这样一个技术素养较低的社会中,“AI 算法说这个人不可信”似乎比“约翰·史密斯说这个人不可信”更具权威性和客观性,尽管前者只是对后者的学习近似。您的模型将在规模上洗钱和操作化人类判断的最糟糕的方面,对真实人们的生活产生负面影响。
技术从来都不是中立的。如果您的工作对世界产生任何影响,这种影响具有道德方向:技术选择也是伦理选择。始终要谨慎选择您希望您的工作支持的价值观。
如果您可以从keras.datasets
导入正确的数据集并开始拟合一些深度学习模型将会非常方便。不幸的是,在现实世界中,您将不得不从头开始。
在本章中,您将学习一个通用的逐步蓝图,您可以使用它来处理和解决任何机器学习问题,就像前面列表中的问题一样。这个模板将汇集和巩固您在第四章和第五章中学到的一切,并为您提供更广泛的背景,以便扎实您将在接下来的章节中学到的内容。
机器学习的通用工作流程大致分为三个部分:
- 定义任务—理解问题领域和客户要求背后的业务逻辑。收集数据集,了解数据代表什么,并选择如何衡量任务的成功。
- 开发模型—准备数据以便机器学习模型处理,选择一个模型评估协议和一个简单的基准来超越,训练一个具有泛化能力且可以过拟合的第一个模型,然后正则化和调整您的模型,直到实现最佳的泛化性能。
- 部署模型—向利益相关者展示您的工作,将模型部署到 Web 服务器、移动应用程序、网页或嵌入式设备,监控模型在实际环境中的性能,并开始收集构建下一代模型所需的数据。
让我们深入了解。
6.1 定义任务
没有对您正在做的事情的背景有深刻的理解,您无法做出好的工作。您的客户为什么要解决这个特定的问题?他们将从解决方案中获得什么价值——您的模型将如何使用,它将如何融入客户的业务流程?有哪些可用的数据,或者可以收集哪些数据?什么样的机器学习任务可以映射到业务问题?
6.1.1 界定问题
构建机器学习问题通常需要与利益相关者进行许多详细的讨论。以下是您应该牢记的问题:
- 您的输入数据将是什么?您试图预测什么?只有在有训练数据可用的情况下,您才能学会预测某些东西:例如,只有在有电影评论和情感注释可用时,您才能学会对电影评论的情感进行分类。因此,在这个阶段,数据可用性通常是限制因素。在许多情况下,您将不得不自己收集和注释新的数据集(我们将在下一节中介绍)。
- 您面临的是什么类型的机器学习任务?是二元分类?多类分类?标量回归?向量回归?多类别、多标签分类?图像分割?排名?还是其他类型,如聚类、生成或强化学习?在某些情况下,可能机器学习甚至不是理解数据的最佳方式,您应该使用其他方法,比如传统的统计分析。
- 照片搜索引擎项目是一个多类别、多标签分类任务。
- 垃圾邮件检测项目是一个二元分类任务。如果将“具有攻击性内容”设置为单独的类别,则它是一个三分类任务。
- 音乐推荐引擎最终最好不是通过深度学习来处理,而是通过矩阵分解(协同过滤)。
- 信用卡欺诈检测项目是一个二元分类任务。
- 点击率预测项目是一个标量回归任务。
- 异常饼干检测是一个二元分类任务,但它还需要一个对象检测模型作为第一阶段,以正确裁剪原始图像中的饼干。请注意,被称为“异常检测”的一组机器学习技术在这种情况下并不适用!
- 从卫星图像中寻找新的考古遗址项目是一个图像相似度排名任务:您需要检索看起来最像已知考古遗址的新图像。
- 现有解决方案是什么样的?也许您的客户已经有一个手工制作的算法来处理垃圾邮件过滤或信用卡欺诈检测,其中有很多嵌套的
if
语句。也许目前是一个人手动处理正在考虑的过程——监控饼干工厂的传送带并手动移除坏饼干,或者制作歌曲推荐播放列表以发送给喜欢特定艺术家的用户。您应该确保了解已经存在的系统以及它们是如何工作的。 - 您是否需要处理特定的约束?例如,您可能会发现您正在为其构建垃圾邮件检测系统的应用程序严格端到端加密,因此垃圾邮件检测模型将不得不存在于最终用户的手机上,并且必须在外部数据集上进行训练。也许饼干过滤模型有延迟约束,因此它将需要在工厂的嵌入式设备上运行,而不是在远程服务器上运行。您应该了解您的工作将适用的完整背景。
一旦你完成了研究,你应该知道你的输入是什么,你的目标是什么,以及问题映射到什么类型的机器学习任务。在这个阶段要注意你所做的假设:
- 你假设可以根据输入来预测目标。
- 你假设可用的数据(或者你即将收集的数据)足够信息丰富,可以学习输入和目标之间的关系。
在你有一个可用的模型之前,这些只是等待验证或无效的假设。并不是所有问题都可以通过机器学习解决;仅仅因为你已经收集了输入 X 和目标 Y 的示例并不意味着 X 包含足够的信息来预测 Y。例如,如果你试图根据股票市场的最近价格历史来预测股票的走势,你很可能不会成功,因为价格历史并不包含太多预测信息。
6.1.2 收集数据集
一旦你了解了任务的性质,知道你的输入和目标将是什么,就是数据收集的时候了——大多数机器学习项目中最费力、耗时和昂贵的部分。
- 图片搜索引擎项目要求你首先选择你想要分类的标签集——你选择了 1 万个常见的图像类别。然后你需要手动为数十万张过去用户上传的图片打上这个标签集中的标签。
- 对于聊天应用的垃圾信息检测项目,由于用户聊天是端到端加密的,你无法使用其内容来训练模型。你需要获得一个包含数万条未经过滤的社交媒体帖子的独立数据集,并手动将其标记为垃圾信息、冒犯性内容或可接受内容。
- 对于音乐推荐引擎,你可以只使用用户的“喜欢”。不需要收集新数据。同样对于点击率预测项目:你有过去多年广告的点击率记录。
- 对于饼干标记模型,你需要在传送带上方安装摄像头,收集数万张图片,然后需要有人手动标记这些图片。目前知道如何做这件事的人在饼干工厂工作,但似乎并不太困难。你应该能够训练人员来做这件事。
- 卫星图像项目将需要一个考古学家团队收集一个现有感兴趣地点的数据库,对于每个地点,你需要找到在不同天气条件下拍摄的现有卫星图像。为了得到一个好的模型,你需要成千上万个不同的地点。
你在第五章学到,模型的泛化能力几乎完全来自于它所训练的数据的属性——你拥有的数据点数量,标签的可靠性,特征的质量。一个好的数据集是值得关注和投资的资产。如果你有额外的 50 个小时用于项目,最有效的分配方式很可能是收集更多数据,而不是寻找渐进的建模改进。
数据比算法更重要的观点最著名的是由谷歌研究人员在 2009 年发表的一篇名为“数据的不合理有效性”的论文中提出的(标题是对尤金·维格纳 1960 年文章“数学在自然科学中的不合理有效性”进行的改编)。这是在深度学习流行之前,但令人惊讶的是,深度学习的兴起只使数据的重要性更加突出。
如果你正在进行监督学习,那么一旦你收集了输入(如图片),你将需要为它们提供注释(如这些图片的标签)—你将训练模型来预测的目标。有时,注释可以自动获取,比如音乐推荐任务或点击率预测任务的注释。但通常你需要手动为数据进行注释。这是一个劳动密集型的过程。
投资于数据标注基础设施
你的数据标注流程将决定你的目标的质量,进而决定你的模型的质量。仔细考虑你可用的选项:
- 你应该自己标注数据吗?
- 你应该使用类似 Mechanical Turk 这样的众包平台来收集标签吗?
- 是否应该使用专门的数据标注公司的服务?
外包可能会节省你时间和金钱,但会带走控制权。使用类似 Mechanical Turk 这样的东西可能会便宜且扩展性好,但你的标注可能会变得很嘈杂。
要选择最佳选项,考虑你正在处理的限制条件:
- 数据标注员是否需要是专业领域专家,还是任何人都可以标注数据?猫狗图像分类问题的标签可以由任何人选择,但狗品种分类任务的标签需要专业知识。同时,标注骨折的 CT 扫描几乎需要医学学位。
- 如果标注数据需要专业知识,你能培训人员来做吗?如果不能,你如何获得相关专家的帮助?
- 你自己是否了解专家如何进行标注?如果不了解,你将不得不将数据集视为黑匣子,你将无法进行手动特征工程—这并非关键,但可能会有限制。
如果决定在内部标注数据,问问自己你将使用什么软件记录标注。你可能需要自己开发这个软件。高效的数据标注软件会为你节省大量时间,因此在项目早期投资于此是值得的。
警惕非代表性数据
机器学习模型只能理解与之前看到的输入类似的内容。因此,训练使用的数据应该代表生产数据。这个问题应该是所有数据收集工作的基础。
假设你正在开发一个应用,用户可以拍摄一盘食物的照片以找出菜名。你使用来自食客常用的图片分享社交网络的图片来训练模型。在部署时,愤怒用户的反馈开始涌入:你的应用在 10 次中有 8 次答错!怎么回事?你在测试集上的准确率远远超过 90%!快速查看用户上传的数据发现,随机餐馆的随机菜品用随机手机拍摄的移动图片看起来与你训练模型的专业质量、光线充足、诱人的图片完全不同:你的训练数据不代表生产数据。这是一个致命的错误—欢迎来到机器学习地狱。
如果可能的话,直接从模型将要使用的环境中收集数据。电影评论情感分类模型应该用于新的 IMDB 评论,而不是 Yelp 餐厅评论,也不是 Twitter 状态更新。如果你想评价一条推文的情感,首先收集和标注来自与你预期在生产中的用户类似的一组用户的实际推文。如果无法在生产数据上进行训练,那么确保你充分了解你的训练和生产数据的差异,并积极纠正这些差异。
你应该注意的一个相关现象是概念漂移。你几乎会在所有涉及用户生成数据的真实世界问题中遇到概念漂移。概念漂移发生在生产数据的属性随时间变化,导致模型准确性逐渐下降的情况下。2013 年训练的音乐推荐引擎可能在今天效果不佳。同样,你使用的 IMDB 数据集是在 2011 年收集的,基于它训练的模型可能在处理 2020 年的评论时表现不如处理 2012 年的评论,因为词汇、表达和电影类型随时间演变。概念漂移在像信用卡欺诈检测这样的对抗性环境中尤为严重,那里的欺诈模式几乎每天都在变化。处理快速概念漂移需要不断进行数据收集、标注和模型重新训练。
请记住,机器学习只能用于记忆训练数据中存在的模式。你只能识别以前见过的内容。使用过去数据训练的机器学习来预测未来是假设未来会像过去一样。这通常并非如此。
抽样偏差问题
一个特别阴险且常见的非代表性数据案例是抽样偏差。当你的数据收集过程与你试图预测的内容相互作用,导致测量结果出现偏差时,就会发生抽样偏差。一个著名的历史例子发生在 1948 年的美国总统选举中。选举当晚,芝加哥论坛报刊登了“杜威击败杜鲁门”的头条新闻。第二天早上,杜鲁门被宣布为胜者。论坛报的编辑信任了一项电话调查的结果——但 1948 年的电话用户并不是选民群体的随机代表样本。他们更有可能是富裕、保守派的人,更有可能投票给共和党候选人杜威。
[外链图片转存中…(img-ZlcagxVJ-1710946537846)]
“杜威击败杜鲁门”:抽样偏差的一个著名例子
如今,每次电话调查都会考虑到抽样偏差。这并不意味着在政治民意调查中抽样偏差已经成为过去的事情——相反,与 1948 年不同,民意调查员现在已经意识到这一点并采取措施加以纠正。
6.1.3 理解你的数据
将数据集视为黑匣子是相当糟糕的做法。在开始训练模型之前,你应该探索和可视化数据,以获取关于数据预测性的见解,这将为特征工程提供信息,并筛选潜在问题。
- 如果你的数据包含图像或自然语言文本,请直接查看一些样本(及其标签)。
- 如果你的数据包含数值特征,绘制特征值的直方图是个好主意,以了解取值范围和不同值的频率。
- 如果你的数据包含位置信息,请在地图上绘制出来。是否出现了明显的模式?
- 一些样本是否缺少某些特征的值?如果是,你在准备数据时需要处理这个问题(我们将在下一节介绍如何处理)。
- 如果你的任务是一个分类问题,请打印出数据中每个类别的实例数。这些类别是否大致平均表示?如果不是,你将需要考虑这种不平衡。
- 检查目标泄漏:数据中存在的特征提供了关于目标的信息,这些信息在生产中可能不可用。如果你正在训练一个模型来预测未来某人是否会接受癌症治疗,并且记录包括“这个人被诊断患有癌症”这个特征,那么你的目标就会人为地泄漏到你的数据中。始终要问自己,你的数据中的每个特征是否都是在生产中以相同形式可用的?
6.1.4 选择成功的衡量标准
要控制某物,你需要能够观察它。要在项目上取得成功,你必须首先定义你所理解的成功。准确率?精确率和召回率?客户保留率?你对成功的度量标准将指导你在整个项目中做出的所有技术选择。它应直接与你的更高级别目标(例如客户的业务成功)保持一致。
对于平衡分类问题,每个类别等可能出现的情况下,准确率和受试者工作特征曲线(ROC)下的面积,简称为 ROC AUC,是常见的度量标准。对于类别不平衡的问题、排名问题或多标签分类问题,可以使用精确率和召回率,以及加权准确率或 ROC AUC 的形式。而且,定义自己的成功度量标准也并不罕见。要了解机器学习成功度量标准的多样性以及它们与不同问题领域的关系,浏览 Kaggle(kaggle.com
)上的数据科学竞赛是很有帮助的;它们展示了各种问题和评估指标。
6.2 开发模型
一旦你知道如何衡量你的进展,就可以开始模型开发。大多数教程和研究项目假设这是唯一的步骤——跳过问题定义和数据集收集,这些被假定已经完成,并跳过模型部署和维护,这些被假定由其他人处理。事实上,模型开发只是机器学习工作流程中的一个步骤,如果你问我,这并不是最困难的步骤。机器学习中最困难的事情是明确问题并收集、注释和清理数据。所以振作起来——接下来的事情相对容易!
6.2.1 准备数据
正如你之前学到的,深度学习模型通常不会直接处理原始数据。数据预处理旨在使手头的原始数据更适合神经网络。这包括向量化、归一化或处理缺失值。许多预处理技术是领域特定的(例如,特定于文本数据或图像数据);在接下来的章节中,当我们在实际示例中遇到它们时,我们将介绍这些技术。目前,我们将回顾所有数据领域通用的基础知识。
向量化
神经网络中的所有输入和目标通常必须是浮点数据张量(或在特定情况下是整数或字符串张量)。无论你需要处理的数据是声音、图像、文本,你都必须首先将其转换为张量,这一步称为数据向量化。例如,在第四章的两个文本分类示例中,我们开始时将文本表示为整数列表(代表单词序列),然后使用独热编码将其转换为float32
数据张量。在分类数字和预测房价的示例中,数据以向量化形式呈现,因此我们可以跳过这一步。
值归一化
在第二章的 MNIST 数字分类示例中,我们开始时将图像数据编码为 0-255 范围内的整数,表示灰度值。在将这些数据馈送到网络之前,我们必须将其转换为float32
并除以 255,以便最终得到 0-1 范围内的浮点值。类似地,在预测房价时,我们开始时的特征具有各种范围——一些特征具有较小的浮点值,而其他特征具有相当大的整数值。在将这些数据馈送到网络之前,我们必须独立地对每个特征进行归一化,使其具有标准差为 1 和均值为 0。
一般来说,将相对较大的值(例如,多位整数,远大于网络权重初始值的值)或异构数据(例如,一个特征在 0-1 范围内,另一个在 100-200 范围内)输入神经网络是不安全的。这样做可能会触发大的梯度更新,阻止网络收敛。为了让你的网络学习更容易,你的数据应具有以下特征:
- 取小值—通常,大多数值应该在 0-1 范围内。
- 保持同质性—所有特征的取值应该在大致相同的范围内。
此外,以下更严格的归一化实践是常见的,可以帮助,尽管并不总是必要的(例如,在数字分类示例中我们没有这样做):
- 将每个特征独立归一化为均值为 0。
- 将每个特征独立归一化为标准差为 1。
这在 NumPy 数组中很容易实现:
x -= x.mean(axis=0) # ❶ x /= x.std(axis=0)
❶ 假设 x 是一个形状为(样本数,特征数)的 2D 数据矩阵
处理缺失值
有时你的数据中可能会有缺失值。例如,在房价示例中,第一个特征(数据中索引为 0 的列)是人均犯罪率。如果这个特征并非所有样本都有呢?那么你的训练或测试数据中就会有缺失值。
你可以选择完全丢弃该特征,但不一定必须这样做。
- 如果特征是分类的,创建一个新类别表示“该值缺失”是安全的。模型将自动学习这对目标意味着什么。
- 如果特征是数值的,避免输入像
"0"
这样的任意值,因为它可能会在特征形成的潜在空间中创建不连续,使得模型更难泛化。相反,考虑用数据集中该特征的平均值或中位数替换缺失值。你也可以训练一个模型,根据其他特征的值来预测该特征的值。
请注意,如果你预期测试数据中会有缺失的分类特征,但网络是在没有任何缺失值的数据上训练的,那么网络就不会学会忽略缺失值!在这种情况下,你应该人为生成带有缺失条目的训练样本:多次复制一些训练样本,并丢弃你预计在测试数据中可能缺失的一些分类特征。
6.2.2 选择评估协议
正如你在前一章中学到的,模型的目的是实现泛化,你在整个模型开发过程中做出的每个建模决策都将由寻求衡量泛化性能的验证指标指导。你的验证协议的目标是准确估计你选择的成功指标(如准确率)在实际生产数据上的表现。这个过程的可靠性对于构建一个有用的模型至关重要。
在第五章中,我们回顾了三种常见的评估协议:
- 保持留出验证集—当你有大量数据时,这是一个好方法。
- 进行 K 折交叉验证—当你的样本量太少,无法依靠留出验证来进行可靠评估时,这是正确的选择。
- 进行迭代的 K 折验证—这是在数据有限的情况下进行高精度模型评估的方法。
选择其中之一。在大多数情况下,第一个就足够好了。但是要记住,始终要注意你的验证集的代表性,并小心不要在训练集和验证集之间有重复的样本。
6.2.3 击败基准
当你开始着手模型本身时,你的初始目标是实现统计功效,就像你在第五章看到的那样:也就是说,开发一个能够击败简单基准的小模型。
在这个阶段,你应该专注于以下三件最重要的事情:
- 特征工程—过滤掉无信息的特征(特征选择)并利用你对问题的了解开发可能有用的新特征。
- 选择正确的架构先验—你将使用什么类型的模型架构?密集连接网络、卷积网络、循环神经网络、Transformer?深度学习是否是解决任务的好方法,还是应该使用其他方法?
- 选择足够好的训练配置—你应该使用什么损失函数?什么批量大小和学习率?
选择正确的损失函数
往往不可能直接优化衡量问题成功的指标。有时没有简单的方法将指标转化为损失函数;毕竟,损失函数需要仅通过一个小批量数据就能计算(理想情况下,损失函数应该能够计算一个数据点)并且必须可微分(否则,你无法使用反向传播来训练你的网络)。例如,广泛使用的分类指标 ROC AUC 不能直接优化。因此,在分类任务中,通常会优化 ROC AUC 的代理指标,如交叉熵。一般来说,你可以希望交叉熵越低,ROC AUC 就越高。
以下表格可以帮助你为几种常见问题类型选择最后一层激活函数和损失函数。
为你的模型选择正确的最后一层激活函数和损失函数
问题类型 | 最后一层激活函数 | 损失函数 |
二元分类 | sigmoid | binary_crossentropy |
多类单标签分类 | softmax | categorical_crossentropy |
多类多标签分类 | sigmoid | binary_crossentropy |
对于大多数问题,你可以从现有的模板开始。你不是第一个尝试构建垃圾邮件检测器、音乐推荐引擎或图像分类器的人。确保你研究先前的技术以识别在你的任务上表现良好的特征工程技术和模型架构。
注意,并非总是能够达到统计功效。如果在尝试多个合理的架构后仍无法超越简单的基准线,可能是你所问的问题在输入数据中没有答案。记住,你正在提出两个假设:
- 你假设可以根据输入预测输出。
- 你假设可用数据足够信息丰富,可以学习输入和输出之间的关系。
很可能这些假设是错误的,如果是这样,你必须回到起点。
6.2.4 扩展规模:开发一个过拟合的模型
一旦你获得了具有统计功效的模型,问题就变成了,你的模型是否足够强大?它是否有足够的层和参数来正确地建模手头的问题?例如,逻辑回归模型在 MNIST 上具有统计功效,但不足以很好地解决问题。记住,机器学习中的普遍张力在于优化和泛化之间。理想的模型是一个恰好处于欠拟合和过拟合、欠容量和过容量之间的模型。要弄清楚这个边界在哪里,首先你必须跨越它。
要弄清楚你需要多大的模型,你必须开发一个过拟合的模型。这相当容易,正如你在第五章学到的那样:
- 添加层。
- 让层变得更大。
- 训练更多的 epochs。
始终监控训练损失和验证损失,以及你关心的任何指标的训练和验证值。当你看到模型在验证数据上的表现开始下降时,你已经过拟合了。
6.2.5 正则化和调整你的模型
一旦您达到了统计功效并且能够过度拟合,您就知道自己走在正确的道路上。此时,您的目标是最大化泛化性能。
这个阶段将花费最多的时间:您将反复修改您的模型,训练它,在验证数据上评估(此时不是测试数据),再次修改它,并重复,直到模型达到最佳状态。以下是您应该尝试的一些事项:
- 尝试不同的架构;添加或删除层。
- 添加丢弃(dropout)。
- 如果您的模型很小,添加 L1 或 L2 正则化。
- 尝试不同的超参数(例如每层的单元数或优化器的学习率)以找到最佳配置。
- 可选地,迭代数据整理或特征工程:收集和注释更多数据,开发更好的特征,或删除看起来不具信息量的特征。
可以通过使用自动化超参数调整软件(如 KerasTuner)来自动化大部分工作。我们将在第十三章中介绍这个内容。
要注意以下事项:每次您使用验证过程的反馈来调整模型时,都会泄漏有关验证过程的信息到模型中。这样做几次是无害的;但如果在许多迭代中系统地重复这样做,最终会导致您的模型过度拟合验证过程(即使没有任何模型直接在任何验证数据上训练)。这会使评估过程变得不太可靠。
一旦您开发出令人满意的模型配置,您可以在所有可用数据(训练和验证)上训练最终的生产模型,并最后一次在测试集上评估它。如果测试集上的性能明显低于在验证数据上测量的性能,这可能意味着您的验证程序毕竟不可靠,或者在调整模型参数时开始过拟合验证数据。在这种情况下,您可能需要切换到更可靠的评估协议(如迭代 K 折验证)。
6.3 部署模型
您的模型已成功通过了测试集上的最终评估,它已准备好部署并开始其生产生活。
6.3.1 向利益相关者解释您的工作并设定期望
成功和客户信任在于始终满足或超越人们的期望。您交付的实际系统只是这个画面的一半;另一半是在推出前设定适当的期望。
非专业人士对人工智能系统的期望往往是不切实际的。例如,他们可能期望系统“理解”其任务,并能在任务背景下行使类似人类常识的能力。为了解决这个问题,您应该考虑展示一些模型的失败模式的示例(例如,展示被错误分类的样本是什么样子,特别是那些误分类看起来令人惊讶的样本)。
他们可能还期望人类水平的表现,特别是对以前由人类处理的流程。大多数机器学习模型,因为它们(不完美地)训练来逼近人类生成的标签,并没有真正达到那里。您应该清楚地传达模型表现的期望。避免使用抽象的陈述,如“模型的准确率为 98%”(大多数人心理上会四舍五入到 100%),而更倾向于谈论假阴性率和假阳性率。您可以说,“使用这些设置,欺诈检测模型将有 5%的假阴性率和 2.5%的假阳性率。每天,平均有 200 笔有效交易会被标记为欺诈并送去人工审核,平均会漏掉 14 笔欺诈交易。平均会正确捕捉 266 笔欺诈交易。”清楚地将模型的性能指标与业务目标联系起来。
你还应该确保与利益相关者讨论关键启动参数的选择——例如,应该在哪个概率阈值下标记交易(不同的阈值会产生不同的假阴性和假阳性率)。这些决策涉及权衡,只有深刻理解业务背景才能处理。
6.3.2 发布推理模型
当你到达一个可以保存训练模型的 Colab 笔记本时,一个机器学习项目并没有结束。你很少会将在训练过程中操作的完全相同的 Python 模型对象投入生产。
首先,你可能希望将模型导出为除 Python 之外的其他形式:
- 你的生产环境可能根本不支持 Python——例如,如果是移动应用程序或嵌入式系统。
- 如果应用程序的其余部分不是用 Python 编写的(可能是 JavaScript、C++ 等),使用 Python 来提供模型可能会引入显著的开销。
其次,由于你的生产模型只用于输出预测(一个称为推理的阶段),而不是用于训练,你有机会进行各种优化,使模型更快速并减少其内存占用。
让我们快速看一下你可以使用的不同模型部署选项。
将模型部署为 REST API
这可能是将模型转化为产品的常见方式:在服务器或云实例上安装 TensorFlow,并通过 REST API 查询模型的预测。你可以使用类似 Flask(或任何其他 Python Web 开发库)的东西构建自己的服务应用程序,或者使用 TensorFlow 的自己的库来将模型作为 API 进行部署,称为TensorFlow Serving(www.tensorflow.org/tfx/guide/serving)。使用 TensorFlow Serving,你可以在几分钟内部署一个 Keras 模型。
当你需要使用这种部署设置时
- 将消费模型预测的应用程序将可靠地访问互联网(显然)。例如,如果你的应用程序是一个移动应用程序,从远程 API 提供预测意味着应用程序在飞行模式或低连接环境下无法使用。
- 应用程序没有严格的延迟要求:请求、推理和答复往返通常需要大约 500 毫秒。
- 发送用于推理的输入数据并不高度敏感:数据需要以解密形式在服务器上可用,因为模型需要查看它(但请注意,应该使用 SSL 加密进行 HTTP 请求和答复)。
例如,图像搜索引擎项目、音乐推荐系统、信用卡欺诈检测项目和卫星图像项目都非常适合通过 REST API 进行服务。
将模型部署为 REST API 时一个重要的问题是,你是想自己托管代码,还是想使用完全托管的第三方云服务。例如,Google 的产品 Cloud AI Platform 允许你简单地将你的 TensorFlow 模型上传到 Google Cloud Storage(GCS),并提供一个 API 端点来查询它。它会处理许多实际细节,如批处理预测、负载平衡和扩展。
在设备上部署模型
有时,你可能需要让你的模型与使用它的应用程序运行在同一设备上——也许是智能手机、机器人上的嵌入式 ARM CPU,或者微型设备上的微控制器。你可能见过一款相机,能够自动检测你对准的场景中的人和面孔:那很可能是一款直接在相机上运行的小型深度学习模型。
当你需要使用这种设置时
- 你的模型具有严格的延迟约束或需要在低连接环境中运行。如果你正在构建一款沉浸式增强现实应用程序,查询远程服务器不是一个可行的选择。
- 您的模型可以足够小,以便在目标设备的内存和功率约束下运行。您可以使用 TensorFlow 模型优化工具包来帮助实现此目标(www.tensorflow.org/model_optimization)。
- 获得最高可能的准确性对您的任务并非至关重要。运行时效率和准确性之间总是存在权衡,因此内存和功率约束通常要求您部署一个不如在大型 GPU 上运行的最佳模型好的模型。
- 输入数据非常敏感,因此不应在远程服务器上解密。
我们的垃圾邮件检测模型将需要在最终用户的智能手机上运行,作为聊天应用程序的一部分,因为消息是端到端加密的,因此无法由远程托管模型读取。同样,坏 cookie 检测模型具有严格的延迟约束,并且需要在工厂中运行。幸运的是,在这种情况下,我们没有任何功率或空间约束,因此我们实际上可以在 GPU 上运行模型。
要在智能手机或嵌入式设备上部署 Keras 模型,您的首选解决方案是 TensorFlow Lite(www.tensorflow.org/lite)。这是一个用于在设备上高效进行深度学习推断的框架,可在 Android 和 iOS 智能手机以及基于 ARM64 的计算机、树莓派或某些微控制器上运行。它包括一个转换器,可以直接将您的 Keras 模型转换为 TensorFlow Lite 格式。
在浏览器中部署模型
深度学习经常用于基于浏览器或桌面的 JavaScript 应用程序。虽然通常可以让应用程序通过 REST API 查询远程模型,但在浏览器中直接运行模型(利用 GPU 资源(如果可用))可能具有关键优势。
当以下情况发生时,请使用此设置
- 您希望将计算卸载到最终用户,这可以大大降低服务器成本。
- 输入数据需要保留在最终用户的计算机或手机上。例如,在我们的垃圾邮件检测项目中,Web 版本和桌面版聊天应用程序(作为用 JavaScript 编写的跨平台应用程序实现)应使用本地运行的模型。
- 您的应用程序具有严格的延迟约束。虽然在最终用户的笔记本电脑或智能手机上运行的模型可能比在您自己服务器上的大型 GPU 上运行的模型慢,但您没有额外的 100 毫秒的网络往返时间。
- 在模型已被下载并缓存后,您需要确保您的应用程序在没有连接的情况下继续工作。
只有在您的模型足够小,不会占用用户笔记本电脑或智能手机的 CPU、GPU 或 RAM 时,才应选择此选项。此外,由于整个模型将被下载到用户设备上,因此您应确保模型的任何内容都不需要保密。请注意,鉴于训练过的深度学习模型,通常可以恢复有关训练数据的一些信息:如果模型是在敏感数据上训练的,则最好不要将训练过的模型公开。
要在 JavaScript 中部署模型,TensorFlow 生态系统包括 TensorFlow.js(www.tensorflow.org/js),这是一个用于深度学习的 JavaScript 库,几乎实现了所有 Keras API(最初以 WebKeras 为工作名称开发)以及许多较低级别的 TensorFlow API。您可以轻松地将保存的 Keras 模型导入到 TensorFlow.js 中,以便在基于浏览器的 JavaScript 应用程序或桌面 Electron 应用程序中查询它。
推断模型优化
在部署在具有严格可用功率和内存约束(智能手机和嵌入式设备)或具有低延迟要求的环境中时,优化您的模型以进行推断尤为重要。在将模型导入到 TensorFlow.js 或导出到 TensorFlow Lite 之前,您应始终寻求优化您的模型。
有两种流行的优化技术可以应用:
- 权重剪枝—权重张量中的每个系数并不都对预测产生相同的贡献。通过仅保留最重要的系数,可以显著降低模型层中的参数数量。这减少了模型的内存和计算占用,但在性能指标上略有损失。通过决定要应用多少剪枝,你可以控制大小和准确性之间的权衡。
- 权重量化—深度学习模型是使用单精度浮点(
float32
)权重训练的。然而,可以将权重量化为 8 位有符号整数(int8
),以获得一个仅用于推断的模型,其大小是原始模型的四分之一,但保持接近原始模型的准确性。
TensorFlow 生态系统包括一个权重剪枝和量化工具包(www.tensorflow.org/model_optimization),它与 Keras API 深度集成。
6.3.3 在实际环境中监控你的模型
你已经导出了一个推断模型,将其集成到你的应用程序中,并在生产数据上进行了干扰测试—模型的行为与你预期的完全一致。你已经编写了单元测试以及日志记录和状态监控代码—完美。现在是时候按下大红按钮,部署到生产环境了。
即使这还不是结束。一旦部署了模型,你需要继续监控其行为,对新数据的性能,与应用程序其他部分的交互以及对业务指标的最终影响。
- 在部署新的音乐推荐系统后,你的在线广播的用户参与度是上升还是下降?切换到新的点击率预测模型后,平均广告点击率是否增加?考虑使用随机 A/B 测试来分离模型本身的影响和其他变化:一部分案例应该经过新模型,而另一部分控制案例应该继续使用旧流程。一旦处理了足够多的案例,两者之间的结果差异很可能归因于模型。
- 如果可能的话,定期对模型在生产数据上的预测进行手动审核。通常可以重复使用与数据标注相同的基础设施:将一部分生产数据发送到手动标注,然后将模型的预测与新的标注进行比较。例如,你应该绝对为图像搜索引擎和恶意 cookie 标记系统做这个工作。
- 当无法进行手动审核时,考虑替代的评估途径,比如用户调查(例如,在垃圾邮件和有害内容标记系统的情况下)。
6.3.4 维护你的模型
最后,没有模型能永远持续。你已经了解了概念漂移:随着时间的推移,你的生产数据的特征将发生变化,逐渐降低模型的性能和相关性。你的音乐推荐系统的寿命将以周计算。对于信用卡欺诈检测系统,将以天计算。最好情况下,图像搜索引擎的寿命为几年。
一旦你的模型启动,你应该准备好训练下一个将取代它的新一代。因此,
- 注意生产数据的变化。是否有新特征可用?是否应该扩展或编辑标签集?
- 继续收集和标注数据,并随着时间的推移改进你的标注流水线。特别要注意收集那些对当前模型分类困难的样本—这些样本最有可能帮助提高性能。
这就结束了机器学习的通用工作流程——需要记住的事情很多。成为专家需要时间和经验,但不要担心,您现在比几章前聪明多了。您现在熟悉了整体情况——机器学习项目所涉及的整个范围。虽然本书的大部分内容将集中在模型开发上,但您现在意识到这只是整个工作流程的一部分。始终牢记整体情况!
摘要
- 当您开始新的机器学习项目时,首先定义手头的问题:
- 理解您要做的事情的更广泛背景——最终目标是什么,有哪些约束条件?
- 收集和注释数据集;确保深入了解数据。
- 选择如何衡量问题的成功——您将在验证数据上监视哪些指标?
- 一旦您理解了问题并拥有适当的数据集,就开发一个模型:
- 准备您的数据。
- 选择评估协议:留出验证?K 折验证?应该使用数据的哪一部分进行验证?
- 实现统计功效:击败一个简单的基准。
- 扩展:开发一个可以过拟合的模型。
- 正则化您的模型并调整其超参数,基于验证数据的性能。许多机器学习研究往往只关注这一步,但要牢记整体情况。
- 当您的模型准备就绪并在测试数据上表现良好时,就是部署的时候了:
- 首先,确保与利益相关者设定适当的期望。
- 优化最终模型以进行推断,并将模型部署到选择的部署环境——Web 服务器、移动设备、浏览器、嵌入式设备等。
- 监控模型在生产中的性能,并继续收集数据,以便开发下一代模型。
Python 深度学习第二版(GPT 重译)(二)(3)https://developer.aliyun.com/article/1485264