Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(四)(4)

简介: Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(四)

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(四)(3)https://developer.aliyun.com/article/1482423


AdamW

AdamW是 Adam 的一个变体,它集成了一种称为权重衰减的正则化技术。权重衰减通过将模型的权重在每次训练迭代中乘以一个衰减因子,如 0.99,来减小权重的大小。这可能让您想起ℓ[2]正则化(在第四章介绍),它也旨在保持权重较小,事实上,可以在数学上证明,当使用 SGD 时,ℓ[2]正则化等效于权重衰减。然而,当使用 Adam 或其变体时,ℓ[2]正则化和权重衰减等效:实际上,将 Adam 与ℓ[2]正则化结合使用会导致模型通常不如 SGD 产生的模型泛化能力好。AdamW 通过正确地将 Adam 与权重衰减结合来解决这个问题。

警告

自适应优化方法(包括 RMSProp、Adam、AdaMax、Nadam 和 AdamW 优化)通常很好,快速收敛到一个好的解决方案。然而,阿希亚·C·威尔逊等人在一篇2017 年的论文中表明,它们可能导致在某些数据集上泛化能力较差的解决方案。因此,当您对模型的性能感到失望时,请尝试使用 NAG:您的数据集可能只是对自适应梯度过敏。还要关注最新的研究,因为它发展迅速。

要在 Keras 中使用 Nadam、AdaMax 或 AdamW,请将tf.keras.optimizers.Adam替换为tf.keras.optimizers.Nadamtf.keras.optimizers.Adamaxtf.keras.optimizers.experimental.AdamW。对于 AdamW,您可能需要调整weight_decay超参数。

到目前为止讨论的所有优化技术只依赖于一阶偏导数雅可比)。优化文献中还包含基于二阶偏导数海森,即雅可比的偏导数)的惊人算法。不幸的是,这些算法很难应用于深度神经网络,因为每个输出有n²个海森(其中n是参数的数量),而不是每个输出只有n个雅可比。由于 DNN 通常具有成千上万个参数甚至更多,第二阶优化算法通常甚至无法适应内存,即使能够适应,计算海森也太慢。

表 11-2 比较了到目前为止我们讨论过的所有优化器(*是不好的,**是平均的,***是好的)。

表 11-2。优化器比较

收敛速度 收敛质量
SGD * ***
SGD(momentum=...) ** ***
SGD(momentum=..., nesterov=True) ** ***
Adagrad *** *(过早停止)
RMSprop *** ** or ***
Adam *** ** or ***
AdaMax *** ** or ***
Nadam *** ** or ***
AdamW *** ** or ***

学习率调度

找到一个好的学习率非常重要。如果设置得太高,训练可能会发散(如“梯度下降”中讨论的)。如果设置得太低,训练最终会收敛到最优解,但需要很长时间。如果设置得稍微偏高,它会在一开始就非常快地取得进展,但最终会围绕最优解打转,从未真正稳定下来。如果你的计算预算有限,你可能需要在训练收敛之前中断训练,得到一个次优解(参见图 11-9)。


图 11-9。不同学习率η的学习曲线

如第十章中讨论的,您可以通过训练模型几百次,将学习率从一个非常小的值指数增加到一个非常大的值,然后查看学习曲线并选择一个略低于学习曲线开始迅速上升的学习率来找到一个好的学习率。然后,您可以重新初始化您的模型,并使用该学习率进行训练。

但是你可以比恒定学习率做得更好:如果你从一个较大的学习率开始,然后在训练停止快速取得进展时降低它,你可以比使用最佳恒定学习率更快地达到一个好的解。有许多不同的策略可以在训练过程中降低学习率。从一个低学习率开始,增加它,然后再次降低它也可能是有益的。这些策略被称为学习计划(我在第四章中简要介绍了这个概念)。这些是最常用的学习计划:

幂调度

将学习率设置为迭代次数t的函数:η(t) = η[0] / (1 + t/s)^(c)。初始学习率η[0],幂c(通常设置为 1)和步长s是超参数。学习率在每一步下降。经过s步,学习率降至η[0]的一半。再经过s步,它降至η[0]的 1/3,然后降至η[0]的 1/4,然后η[0]的 1/5,依此类推。正如您所看到的,这个调度首先快速下降,然后变得越来越慢。当然,幂调度需要调整η[0]和s(可能还有c)。

指数调度

将学习率设置为η(t) = η[0] 0.1^(t/s)。学习率将每s步逐渐降低 10 倍。虽然幂调度使学习率降低得越来越慢,指数调度则每s步将其降低 10 倍。

分段常数调度

在一些时期内使用恒定的学习率(例如,η[0] = 0.1,持续 5 个时期),然后在另一些时期内使用较小的学习率(例如,η[1] = 0.001,持续 50 个时期),依此类推。尽管这种解决方案可能效果很好,但需要调整以找出正确的学习率序列以及每个学习率使用的时间长度。

性能调度

N步测量验证错误(就像提前停止一样),当错误停止下降时,将学习率降低λ倍。

1cycle 调度

1cycle 是由 Leslie Smith 在2018 年的一篇论文中提出的。与其他方法相反,它从增加初始学习率η[0]开始,线性增长到训练中途的η[1]。然后在训练的第二半部分线性降低学习率至η[0],最后几个时期通过几个数量级的降低率(仍然是线性)来完成。最大学习率η[1]是使用我们用来找到最佳学习率的相同方法选择的,初始学习率η[0]通常低 10 倍。当使用动量时,我们首先使用高动量(例如 0.95),然后在训练的前半部分将其降低到较低的动量(例如 0.85,线性),然后在训练的后半部分将其提高到最大值(例如 0.95),最后几个时期使用该最大值。Smith 进行了许多实验,表明这种方法通常能够显著加快训练速度并达到更好的性能。例如,在流行的 CIFAR10 图像数据集上,这种方法仅在 100 个时期内达到了 91.9%的验证准确率,而通过标准方法(使用相同的神经网络架构)在 800 个时期内仅达到了 90.3%的准确率。这一壮举被称为超级收敛

Andrew Senior 等人在2013 年的一篇论文中比较了使用动量优化训练深度神经网络进行语音识别时一些最流行的学习调度的性能。作者得出结论,在这种情况下,性能调度和指数调度表现良好。他们更青睐指数调度,因为它易于调整,并且收敛到最佳解稍快。他们还提到,它比性能调度更容易实现,但在 Keras 中,这两个选项都很容易。也就是说,1cycle 方法似乎表现得更好。

在 Keras 中实现幂调度是最简单的选择——只需在创建优化器时设置衰减超参数:

optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, decay=1e-4)

衰减s的倒数(将学习率除以一个单位所需的步数),Keras 假设c等于 1。

指数调度和分段调度也很简单。您首先需要定义一个函数,该函数接受当前 epoch 并返回学习率。例如,让我们实现指数调度:

def exponential_decay_fn(epoch):
    return 0.01 * 0.1 ** (epoch / 20)

如果您不想硬编码 η[0] 和 s,您可以创建一个返回配置函数的函数:

def exponential_decay(lr0, s):
    def exponential_decay_fn(epoch):
        return lr0 * 0.1 ** (epoch / s)
    return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)

接下来,创建一个 LearningRateScheduler 回调,将调度函数传递给它,并将此回调传递给 fit() 方法:

lr_scheduler = tf.keras.callbacks.LearningRateScheduler(exponential_decay_fn)
history = model.fit(X_train, y_train, [...], callbacks=[lr_scheduler])

LearningRateScheduler 将在每个 epoch 开始时更新优化器的 learning_rate 属性。通常每个 epoch 更新一次学习率就足够了,但是如果您希望更频繁地更新它,例如在每一步,您可以随时编写自己的回调(请参阅本章笔记本中“指数调度”部分的示例)。在每一步更新学习率可能有助于处理每个 epoch 中的许多步骤。或者,您可以使用 tf.keras.optimiz⁠ers.schedules 方法,稍后会进行描述。

提示

训练后,history.history["lr"] 可以让您访问训练过程中使用的学习率列表。

调度函数可以选择将当前学习率作为第二个参数。例如,以下调度函数将前一个学习率乘以 0.1^(1/20),这将导致相同的指数衰减(除了衰减现在从第 0 个 epoch 开始而不是第 1 个):

def exponential_decay_fn(epoch, lr):
    return lr * 0.1 ** (1 / 20)

这个实现依赖于优化器的初始学习率(与之前的实现相反),所以请确保适当设置它。

当您保存一个模型时,优化器及其学习率也会被保存。这意味着使用这个新的调度函数,您可以加载一个训练好的模型,并继续在离开的地方继续训练,没有问题。然而,如果您的调度函数使用 epoch 参数,情况就不那么简单了:epoch 不会被保存,并且每次调用 fit() 方法时都会被重置为 0。如果您要继续训练一个模型,这可能会导致一个非常大的学习率,这可能会损坏模型的权重。一个解决方案是手动设置 fit() 方法的 initial_epoch 参数,使 epoch 从正确的值开始。

对于分段常数调度,您可以使用以下类似的调度函数(与之前一样,如果您愿意,您可以定义一个更通用的函数;请参阅笔记本中“分段常数调度”部分的示例),然后创建一个带有此函数的 LearningRateScheduler 回调,并将其传递给 fit() 方法,就像对指数调度一样:

def piecewise_constant_fn(epoch):
    if epoch < 5:
        return 0.01
    elif epoch < 15:
        return 0.005
    else:
        return 0.001

对于性能调度,请使用 ReduceLROnPlateau 回调。例如,如果您将以下回调传递给 fit() 方法,每当最佳验证损失连续五个 epoch 没有改善时,它将把学习率乘以 0.5(还有其他选项可用;请查看文档以获取更多详细信息):

lr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5)
history = model.fit(X_train, y_train, [...], callbacks=[lr_scheduler])

最后,Keras 提供了另一种实现学习率调度的方法:您可以使用 tf.keras.opti⁠mizers.schedules 中可用的类之一定义一个调度学习率,然后将其传递给任何优化器。这种方法在每一步而不是每个 epoch 更新学习率。例如,以下是如何实现与我们之前定义的 exponential_decay_fn() 函数相同的指数调度:

import math
batch_size = 32
n_epochs = 25
n_steps = n_epochs * math.ceil(len(X_train) / batch_size)
scheduled_learning_rate = tf.keras.optimizers.schedules.ExponentialDecay(
    initial_learning_rate=0.01, decay_steps=n_steps, decay_rate=0.1)
optimizer = tf.keras.optimizers.SGD(learning_rate=scheduled_learning_rate)

这很简单明了,而且当您保存模型时,学习率及其调度(包括其状态)也会被保存。

至于 1cycle,Keras 不支持它,但是可以通过创建一个自定义回调,在每次迭代时修改学习率来实现它,代码不到 30 行。要从回调的 on_batch_begin() 方法中更新优化器的学习率,您需要调用 tf.keras.back⁠end.set_value(self.model.optimizer.learning_rate, new_learning_rate)。请参阅笔记本中的“1Cycle Scheduling”部分以获取示例。

总之,指数衰减、性能调度和 1cycle 可以显著加快收敛速度,所以试一试吧!

通过正则化避免过拟合

有了四个参数,我可以拟合一只大象,有了五个我可以让它摇动它的鼻子。

约翰·冯·诺伊曼,引用自恩里科·费米在《自然》427 中

拥有成千上万个参数,你可以拟合整个动物园。深度神经网络通常有数万个参数,有时甚至有数百万个。这给予它们极大的自由度,意味着它们可以拟合各种复杂的数据集。但这种极大的灵活性也使得网络容易过拟合训练集。通常需要正则化来防止这种情况发生。

我们已经在第十章中实现了最好的正则化技术之一:提前停止。此外,即使批量归一化是为了解决不稳定梯度问题而设计的,它也像一个相当不错的正则化器。在本节中,我们将研究神经网络的其他流行正则化技术:ℓ[1] 和 ℓ[2] 正则化、dropout 和最大范数正则化。

ℓ[1] 和 ℓ[2] 正则化

就像你在第四章中为简单线性模型所做的那样,你可以使用 ℓ[2] 正则化来约束神经网络的连接权重,和/或者使用 ℓ[1] 正则化如果你想要一个稀疏模型(其中许多权重等于 0)。以下是如何将 ℓ[2] 正则化应用于 Keras 层的连接权重,使用正则化因子为 0.01:

layer = tf.keras.layers.Dense(100, activation="relu",
                              kernel_initializer="he_normal",
                              kernel_regularizer=tf.keras.regularizers.l2(0.01))

l2() 函数返回一个正则化器,在训练过程中的每一步都会调用它来计算正则化损失。然后将其添加到最终损失中。正如你所期望的那样,如果你想要 ℓ[1] 正则化,你可以简单地使用tf.keras.regularizers.l1();如果你想要同时使用 ℓ[1] 和 ℓ[2] 正则化,可以使用tf.keras.regularizers.l1_l2()(指定两个正则化因子)。

由于通常希望在网络的所有层中应用相同的正则化器,以及在所有隐藏层中使用相同的激活函数和相同的初始化策略,你可能会发现自己重复相同的参数。这会使代码变得丑陋且容易出错。为了避免这种情况,你可以尝试重构代码以使用循环。另一个选择是使用 Python 的functools.partial()函数,它允许你为任何可调用对象创建一个薄包装器,并设置一些默认参数值:

from functools import partial
RegularizedDense = partial(tf.keras.layers.Dense,
                           activation="relu",
                           kernel_initializer="he_normal",
                           kernel_regularizer=tf.keras.regularizers.l2(0.01))
model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    RegularizedDense(100),
    RegularizedDense(100),
    RegularizedDense(10, activation="softmax")
])
警告

正如我们之前看到的,当使用 SGD、动量优化和 Nesterov 动量优化时,ℓ[2] 正则化是可以的,但在使用 Adam 及其变种时不行。如果你想要在使用 Adam 时进行权重衰减,那么不要使用 ℓ[2] 正则化:使用 AdamW 替代。

Dropout

Dropout 是深度神经网络中最流行的正则化技术之一。它是由 Geoffrey Hinton 等人在 2012 年的一篇论文中提出的,并在 2014 年由 Nitish Srivastava 等人进一步详细阐述,已被证明非常成功:许多最先进的神经网络使用了 dropout,因为它使它们的准确率提高了 1%–2%。这听起来可能不多,但当一个模型已经有 95%的准确率时,获得 2%的准确率提升意味着将错误率减少了近 40%(从 5%的错误率降至大约 3%)。

这是一个相当简单的算法:在每个训练步骤中,每个神经元(包括输入神经元,但始终不包括输出神经元)都有一个概率p在训练期间暂时“被丢弃”,这意味着在这个训练步骤中它将被完全忽略,但在下一个步骤中可能会活跃(参见图 11-10)。超参数p称为dropout 率,通常设置在 10%到 50%之间:在循环神经网络中更接近 20%-30%(参见第十五章),在卷积神经网络中更接近 40%-50%(参见第十四章)。训练后,神经元不再被丢弃。这就是全部(除了我们将立即讨论的一个技术细节)。

最初令人惊讶的是,这种破坏性技术居然有效。如果一家公司告诉员工每天早上抛硬币决定是否去上班,公司会表现得更好吗?谁知道呢;也许会!公司将被迫调整其组织;它不能依赖任何一个人来操作咖啡机或执行其他关键任务,因此这种专业知识必须分散到几个人身上。员工必须学会与许多同事合作,而不仅仅是少数几个人。公司将变得更具弹性。如果有人离职,这不会有太大影响。目前尚不清楚这种想法是否适用于公司,但对于神经网络来说,它确实有效。使用 dropout 训练的神经元无法与其相邻的神经元共同适应;它们必须尽可能独立地发挥作用。它们也不能过度依赖少数输入神经元;它们必须关注每个输入神经元。它们最终对输入的轻微变化不太敏感。最终,您将获得一个更健壮的网络,具有更好的泛化能力。


图 11-10。使用 dropout 正则化,每次训练迭代中,一个或多个层中的所有神经元的随机子集(除了输出层)会“被丢弃”;这些神经元在这次迭代中输出为 0(由虚线箭头表示)

理解 dropout 的另一种方法是意识到在每个训练步骤中生成了一个独特的神经网络。由于每个神经元可以存在或不存在,因此存在 2^(N)个可能的网络(其中N是可丢弃神经元的总数)。这是一个如此巨大的数字,以至于同一个神经网络被重复抽样几乎是不可能的。一旦您运行了 10,000 个训练步骤,您实际上已经训练了 10,000 个不同的神经网络,每个神经网络只有一个训练实例。这些神经网络显然不是独立的,因为它们共享许多权重,但它们仍然是不同的。最终的神经网络可以看作是所有这些较小神经网络的平均集合。

提示

在实践中,通常只能将 dropout 应用于顶部一到三层的神经元(不包括输出层)。

有一个小但重要的技术细节。假设p=75%:平均每次训练步骤中只有 25%的神经元是活跃的。这意味着在训练后,神经元将连接到四倍于训练期间的输入神经元。为了补偿这一事实,我们需要在训练期间将每个神经元的输入连接权重乘以四。如果不这样做,神经网络在训练期间和训练后将看到不同的数据,表现不佳。更一般地,在训练期间,我们需要将连接权重除以“保留概率”(1-p)。

使用 Keras 实现 dropout,可以使用tf.keras.layers.Dropout层。在训练期间,它会随机丢弃一些输入(将它们设置为 0),并将剩余的输入除以保留概率。训练结束后,它什么也不做;它只是将输入传递给下一层。以下代码在每个密集层之前应用了 dropout 正则化,使用了 0.2 的 dropout 率:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(100, activation="relu",
                          kernel_initializer="he_normal"),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(100, activation="relu",
                          kernel_initializer="he_normal"),
    tf.keras.layers.Dropout(rate=0.2),
    tf.keras.layers.Dense(10, activation="softmax")
])
[...]  # compile and train the model
警告

由于 dropout 只在训练期间激活,比较训练损失和验证损失可能会产生误导。特别是,模型可能会过度拟合训练集,但训练和验证损失却相似。因此,请确保在没有 dropout 的情况下评估训练损失(例如,在训练后)。

如果观察到模型过拟合,可以增加 dropout 率。相反,如果模型对训练集拟合不足,可以尝试减少 dropout 率。对于大型层,增加 dropout 率,对于小型层,减少 dropout 率也有帮助。此外,许多最先进的架构仅在最后一个隐藏层之后使用 dropout,因此如果全局 dropout 太强,您可能想尝试这样做。

Dropout 确实会显著减慢收敛速度,但在适当调整后通常会得到更好的模型。因此,额外的时间和精力通常是值得的,特别是对于大型模型。

提示

如果要对基于 SELU 激活函数的自正则化网络进行正则化(如前面讨论的),应该使用alpha dropout:这是一种保留其输入均值和标准差的 dropout 变体。它是在与 SELU 一起引入的同一篇论文中提出的,因为常规 dropout 会破坏自正则化。

蒙特卡洛(MC)Dropout

2016 年,Yarin Gal 和 Zoubin Ghahramani 的一篇论文建立了使用 dropout 的更多好理由:

  • 首先,该论文建立了 dropout 网络(即包含Dropout层的神经网络)与近似贝叶斯推断之间的深刻联系,为 dropout 提供了坚实的数学理论基础。
  • 其次,作者引入了一种强大的技术称为MC dropout,它可以提升任何经过训练的 dropout 模型的性能,而无需重新训练它甚至修改它。它还提供了模型不确定性的更好度量,并且可以在几行代码中实现。

如果这一切听起来像某种“奇怪的技巧”点击诱饵,那么看看以下代码。这是 MC dropout 的完整实现,增强了我们之前训练的 dropout 模型而无需重新训练它:

import numpy as np
y_probas = np.stack([model(X_test, training=True)
                     for sample in range(100)])
y_proba = y_probas.mean(axis=0)

请注意,model(X)类似于model.predict(X),只是它返回一个张量而不是 NumPy 数组,并支持training参数。在这个代码示例中,设置training=True确保Dropout层保持活动状态,因此所有预测都会有些不同。我们只对测试集进行 100 次预测,并计算它们的平均值。更具体地说,每次调用模型都会返回一个矩阵,每个实例一行,每个类别一列。因为测试集中有 10,000 个实例和 10 个类别,所以这是一个形状为[10000, 10]的矩阵。我们堆叠了 100 个这样的矩阵,所以y_probas是一个形状为[100, 10000, 10]的 3D 数组。一旦我们在第一个维度上取平均值(axis=0),我们得到y_proba,一个形状为[10000, 10]的数组,就像我们在单次预测中得到的一样。就是这样!在打开 dropout 的情况下对多次预测取平均值会给我们一个通常比关闭 dropout 的单次预测结果更可靠的蒙特卡洛估计。例如,让我们看看模型对 Fashion MNIST 测试集中第一个实例的预测,关闭 dropout:

>>> model.predict(X_test[:1]).round(3)
array([[0\.   , 0\.   , 0\.   , 0\.   , 0\.   , 0.024, 0\.   , 0.132, 0\.   ,
 0.844]], dtype=float32)

模型相当自信(84.4%)这张图片属于第 9 类(踝靴)。与 MC dropout 预测进行比较:

>>> y_proba[0].round(3)
array([0\.   , 0\.   , 0\.   , 0\.   , 0\.   , 0.067, 0\.   , 0.209, 0.001,
 0.723], dtype=float32)

模型似乎仍然更喜欢类别 9,但其置信度降至 72.3%,类别 5(凉鞋)和 7(运动鞋)的估计概率增加,这是有道理的,因为它们也是鞋类。

MC dropout 倾向于提高模型概率估计的可靠性。这意味着它不太可能自信但错误,这可能是危险的:想象一下一个自动驾驶汽车自信地忽略一个停车标志。了解哪些其他类别最有可能也很有用。此外,您可以查看概率估计的标准差

>>> y_std = y_probas.std(axis=0)
>>> y_std[0].round(3)
array([0\.   , 0\.   , 0\.   , 0.001, 0\.   , 0.096, 0\.   , 0.162, 0.001,
 0.183], dtype=float32)

显然,类别 9 的概率估计存在相当大的方差:标准差为 0.183,应与估计的概率 0.723 进行比较:如果您正在构建一个风险敏感的系统(例如医疗或金融系统),您可能会对这种不确定的预测极为谨慎。您绝对不会将其视为 84.4%的自信预测。模型的准确性也从 87.0%略微提高到 87.2%:

>>> y_pred = y_proba.argmax(axis=1)
>>> accuracy = (y_pred == y_test).sum() / len(y_test)
>>> accuracy
0.8717
注意

您使用的蒙特卡洛样本数量(在此示例中为 100)是一个可以调整的超参数。它越高,预测和不确定性估计就越准确。但是,如果您将其加倍,推断时间也将加倍。此外,在一定数量的样本之上,您将注意到改进很小。您的任务是根据您的应用程序找到延迟和准确性之间的正确权衡。

如果您的模型包含在训练期间以特殊方式行为的其他层(例如BatchNormalization层),那么您不应该像我们刚刚做的那样强制训练模式。相反,您应该用以下MCDropout类替换Dropout层:⁠³⁰

class MCDropout(tf.keras.layers.Dropout):
    def call(self, inputs, training=False):
        return super().call(inputs, training=True)

在这里,我们只是子类化Dropout层,并覆盖call()方法以强制其training参数为True(请参阅第十二章)。类似地,您可以通过子类化AlphaDropout来定义一个MCAlphaDropout类。如果您从头开始创建一个模型,只需使用MCDropout而不是Dropout。但是,如果您已经使用Dropout训练了一个模型,您需要创建一个与现有模型相同但使用Dropout而不是MCDropout的新模型,然后将现有模型的权重复制到新模型中。

简而言之,MC dropout 是一种很棒的技术,可以提升 dropout 模型并提供更好的不确定性估计。当然,由于在训练期间只是常规的 dropout,因此它也起到了正则化的作用。

最大范数正则化

神经网络的另一种流行的正则化技术称为最大范数正则化:对于每个神经元,它约束传入连接的权重w,使得∥ w ∥[2] ≤ r,其中r是最大范数超参数,∥ · ∥[2]是ℓ[2]范数。

最大范数正则化不会向整体损失函数添加正则化损失项。相反,通常是在每个训练步骤之后计算∥ w ∥[2],并在需要时重新缩放www r / ∥ w ∥[2])。

减小r会增加正则化的程度,并有助于减少过拟合。最大范数正则化还可以帮助缓解不稳定的梯度问题(如果您没有使用批量归一化)。

在 Keras 中实现最大范数正则化,将每个隐藏层的kernel_constraint参数设置为具有适当最大值的max_norm()约束,如下所示:

dense = tf.keras.layers.Dense(
    100, activation="relu", kernel_initializer="he_normal",
    kernel_constraint=tf.keras.constraints.max_norm(1.))

在每次训练迭代之后,模型的fit()方法将调用max_norm()返回的对象,将该层的权重传递给它,并得到重新缩放的权重,然后替换该层的权重。正如您将在第十二章中看到的,如果需要,您可以定义自己的自定义约束函数,并将其用作kernel_constraint。您还可以通过设置bias_constraint参数来约束偏置项。

max_norm()函数有一个默认为0axis参数。一个Dense层通常具有形状为[输入数量神经元数量]的权重,因此使用axis=0意味着最大范数约束将独立应用于每个神经元的权重向量。如果您想在卷积层中使用最大范数(参见第十四章),请确保适当设置max_norm()约束的axis参数(通常为axis=[0, 1, 2])。

总结和实用指南

在本章中,我们涵盖了各种技术,您可能想知道应该使用哪些技术。这取决于任务,目前还没有明确的共识,但我发现表 11-3 中的配置在大多数情况下都能很好地工作,而不需要太多的超参数调整。尽管如此,请不要将这些默认值视为硬性规则!

表 11-3. 默认 DNN 配置

超参数 默认值
内核初始化器 He 初始化
激活函数 如果是浅层则为 ReLU;如果是深层则为 Swish
归一化 如果是浅层则为无;如果是深层则为批量归一化
正则化 提前停止;如果需要则使用权重衰减
优化器 Nesterov 加速梯度或 AdamW
学习率调度 性能调度或 1cycle

如果网络是简单的密集层堆叠,则它可以自我归一化,您应该使用表 11-4 中的配置。

表 11-4. 自我归一化网络的 DNN 配置

超参数 默认值
内核初始化器 LeCun 初始化
激活函数 SELU
归一化 无(自我归一化)
正则化 如果需要则使用 Alpha dropout
优化器 Nesterov 加速梯度
学习率调度 性能调度或 1cycle

不要忘记对输入特征进行归一化!您还应尝试重用预训练神经网络的部分,如果您可以找到一个解决类似问题的模型,或者如果您有大量未标记数据,则使用无监督预训练,或者如果您有大量类似任务的标记数据,则使用辅助任务的预训练。

虽然前面的指南应该涵盖了大多数情况,但这里有一些例外情况:

  • 如果您需要一个稀疏模型,您可以使用ℓ[1]正则化(并在训练后选择性地将微小权重归零)。如果您需要一个更稀疏的模型,您可以使用 TensorFlow 模型优化工具包。这将破坏自我归一化,因此在这种情况下应使用默认配置。
  • 如果您需要一个低延迟模型(执行闪电般快速预测的模型),您可能需要使用更少的层,使用快速激活函数(如 ReLU 或 leaky ReLU),并在训练后将批量归一化层折叠到前面的层中。拥有一个稀疏模型也会有所帮助。最后,您可能希望将浮点精度从 32 位减少到 16 位甚至 8 位(参见“将模型部署到移动设备或嵌入式设备”)。再次,查看 TF-MOT。
  • 如果您正在构建一个风险敏感的应用程序,或者推断延迟在您的应用程序中并不是非常重要,您可以使用 MC dropout 来提高性能,并获得更可靠的概率估计,以及不确定性估计。

有了这些指导,您现在已经准备好训练非常深的网络了!我希望您现在相信,只使用方便的 Keras API 就可以走很长一段路。然而,可能会有一天,当您需要更多控制时,例如编写自定义损失函数或调整训练算法时。对于这种情况,您将需要使用 TensorFlow 的较低级别 API,您将在下一章中看到。

练习

  1. Glorot 初始化和 He 初始化旨在解决什么问题?
  2. 只要使用 He 初始化随机选择的值将所有权重初始化为相同值,这样做可以吗?
  3. 将偏置项初始化为 0 可以吗?
  4. 在本章讨论的每种激活函数中,您希望在哪些情况下使用?
  5. 当使用SGD优化器时,如果将momentum超参数设置得太接近 1(例如 0.99999),可能会发生什么?
  6. 列出三种可以生成稀疏模型的方法。
  7. Dropout 会减慢训练速度吗?它会减慢推断速度(即对新实例进行预测)吗?MC dropout 呢?
  8. 练习在 CIFAR10 图像数据集上训练深度神经网络:
  1. 构建一个具有 20 个每层 100 个神经元的隐藏层的 DNN(这太多了,但这是这个练习的重点)。使用 He 初始化和 Swish 激活函数。
  2. 使用 Nadam 优化和提前停止,在 CIFAR10 数据集上训练网络。您可以使用tf.keras.datasets.cifar10.load_data()加载数据集。该数据集由 60,000 个 32×32 像素的彩色图像组成(50,000 个用于训练,10,000 个用于测试),具有 10 个类别,因此您需要一个具有 10 个神经元的 softmax 输出层。记得每次更改模型架构或超参数时都要搜索正确的学习率。
  3. 现在尝试添加批量归一化并比较学习曲线:它是否比以前收敛得更快?它是否产生更好的模型?它如何影响训练速度?
  4. 尝试用 SELU 替换批量归一化,并进行必要的调整以确保网络自我归一化(即标准化输入特征,使用 LeCun 正态初始化,确保 DNN 仅包含一系列密集层等)。
  5. 尝试使用 alpha dropout 对模型进行正则化。然后,在不重新训练模型的情况下,看看是否可以通过 MC dropout 获得更好的准确性。
  6. 使用 1cycle 调度重新训练您的模型,看看它是否提高了训练速度和模型准确性。

这些练习的解决方案可在本章笔记本的末尾找到,网址为https://homl.info/colab3

¹ Xavier Glorot 和 Yoshua Bengio,“理解训练深度前馈神经网络的困难”,第 13 届人工智能和统计国际会议论文集(2010):249-256。

² 这里有一个类比:如果将麦克风放大器的旋钮调得太接近零,人们就听不到您的声音,但如果将其调得太接近最大值,您的声音将被饱和,人们将听不懂您在说什么。现在想象一下这样一系列放大器:它们都需要适当设置,以便您的声音在链的末端响亮清晰地传出。您的声音必须以与进入时相同的幅度从每个放大器中传出。

³ 例如,Kaiming He 等人,“深入研究整流器:在 ImageNet 分类上超越人类水平表现”,2015 年 IEEE 国际计算机视觉大会论文集(2015):1026-1034。

⁴ 如果神经元下面的层中的输入随时间演变并最终返回到 ReLU 激活函数再次获得正输入的范围内,死神经元可能会复活。例如,如果梯度下降调整了死神经元下面的神经元,这种情况可能会发生。

⁵ Bing Xu 等人,“卷积网络中修正激活的实证评估”,arXiv 预印本 arXiv:1505.00853(2015)。

⁶ Djork-Arné Clevert 等人,“指数线性单元(ELUs)快速准确的深度网络学习”,国际学习表示会议论文集,arXiv 预印本(2015 年)。

⁷ Günter Klambauer 等人,“自正则化神经网络”,第 31 届国际神经信息处理系统会议论文集(2017):972–981。

⁸ Dan Hendrycks 和 Kevin Gimpel,“高斯误差线性单元(GELUs)”,arXiv 预印本 arXiv:1606.08415(2016)。

⁹ 如果曲线上任意两点之间的线段永远不会低于曲线,则函数是凸的。单调函数只增加或只减少。

¹⁰ Prajit Ramachandran 等人,“寻找激活函数”,arXiv 预印本 arXiv:1710.05941(2017)。

¹¹ Diganta Misra,“Mish:一种自正则化的非单调激活函数”,arXiv 预印本 arXiv:1908.08681(2019)。

¹² Sergey Ioffe 和 Christian Szegedy,“批量归一化:通过减少内部协变量转移加速深度网络训练”,第 32 届国际机器学习会议论文集(2015):448–456。

¹³ 然而,它们是根据训练数据在训练期间估计的,因此可以说它们是可训练的。在 Keras 中,“不可训练”实际上意味着“不受反向传播影响”。

¹⁴ Razvan Pascanu 等人,“关于训练递归神经网络的困难”,第 30 届国际机器学习会议论文集(2013):1310–1318。

¹⁵ Boris T. Polyak,“加速迭代方法收敛的一些方法”,苏联计算数学和数学物理杂志 4,第 5 期(1964):1–17。

¹⁶ Yurii Nesterov,“一种具有收敛速率O(1/k²)的无约束凸最小化问题方法”,苏联科学院学报 269(1983):543–547。

¹⁷ John Duchi 等人,“用于在线学习和随机优化的自适应次梯度方法”,机器学习研究杂志 12(2011):2121–2159。

¹⁸ 该算法由 Geoffrey Hinton 和 Tijmen Tieleman 于 2012 年创建,并由 Geoffrey Hinton 在他关于神经网络的 Coursera 课程中介绍(幻灯片:https://homl.info/57;视频:https://homl.info/58)。有趣的是,由于作者没有撰写描述该算法的论文,研究人员经常在其论文中引用“第 6e 讲座的第 29 张幻灯片”。

¹⁹ ρ是希腊字母 rho。

²⁰ Diederik P. Kingma 和 Jimmy Ba,“Adam:一种随机优化方法”,arXiv 预印本 arXiv:1412.6980(2014)。

²¹ Timothy Dozat,“将 Nesterov 动量合并到 Adam 中”(2016)。

²² Ilya Loshchilov 和 Frank Hutter,“解耦权重衰减正则化”,arXiv 预印本 arXiv:1711.05101(2017)。

²³ Ashia C. Wilson 等人,“机器学习中自适应梯度方法的边际价值”,神经信息处理系统进展 30(2017):4148–4158。

Leslie N. Smith,“神经网络超参数的纪律性方法:第 1 部分—学习率、批量大小、动量和权重衰减”,arXiv 预印本 arXiv:1803.09820(2018)。

Andrew Senior 等人,“深度神经网络在语音识别中的学习率的实证研究”,IEEE 国际会议论文集(2013):6724–6728。

Geoffrey E. Hinton 等人,“通过防止特征探测器的共适应来改进神经网络”,arXiv 预印本 arXiv:1207.0580(2012)。

Nitish Srivastava 等人,“Dropout:防止神经网络过拟合的简单方法”,机器学习研究杂志 15(2014):1929–1958。

Yarin Gal 和 Zoubin Ghahramani,“Dropout 作为贝叶斯近似:在深度学习中表示模型不确定性”,第 33 届国际机器学习会议论文集(2016):1050–1059。

具体来说,他们表明训练一个 dropout 网络在数学上等同于在一种特定类型的概率模型中进行近似贝叶斯推断,这种模型被称为深高斯过程

这个MCDropout类将与所有 Keras API 一起工作,包括顺序 API。如果您只关心功能 API 或子类 API,您不必创建一个MCDropout类;您可以创建一个常规的Dropout层,并使用training=True调用它。

相关文章
|
6月前
|
机器学习/深度学习 算法 数据挖掘
PyTabKit:比sklearn更强大的表格数据机器学习框架
PyTabKit是一个专为表格数据设计的新兴机器学习框架,集成了RealMLP等先进深度学习技术与优化的GBDT超参数配置。相比传统Scikit-Learn,PyTabKit通过元级调优的默认参数设置,在无需复杂超参调整的情况下,显著提升中大型数据集的性能表现。其简化API设计、高效训练速度和多模型集成能力,使其成为企业决策与竞赛建模的理想工具。
209 12
PyTabKit:比sklearn更强大的表格数据机器学习框架
|
机器学习/深度学习 TensorFlow 算法框架/工具
深度学习之格式转换笔记(三):keras(.hdf5)模型转TensorFlow(.pb) 转TensorRT(.uff)格式
将Keras训练好的.hdf5模型转换为TensorFlow的.pb模型,然后再转换为TensorRT支持的.uff格式,并提供了转换代码和测试步骤。
277 3
深度学习之格式转换笔记(三):keras(.hdf5)模型转TensorFlow(.pb) 转TensorRT(.uff)格式
|
8月前
|
机器学习/深度学习 PyTorch TensorFlow
深度学习工具和框架详细指南:PyTorch、TensorFlow、Keras
在深度学习的世界中,PyTorch、TensorFlow和Keras是最受欢迎的工具和框架,它们为研究者和开发者提供了强大且易于使用的接口。在本文中,我们将深入探索这三个框架,涵盖如何用它们实现经典深度学习模型,并通过代码实例详细讲解这些工具的使用方法。
|
11月前
|
机器学习/深度学习 人工智能 TensorFlow
基于TensorFlow的深度学习模型训练与优化实战
基于TensorFlow的深度学习模型训练与优化实战
489 3
|
11月前
|
机器学习/深度学习 人工智能 算法
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
手写数字识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Flask框架,开发网页端操作平台,实现用户上传一张图片识别其名称。
412 0
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
|
11月前
|
机器学习/深度学习 TensorFlow API
机器学习实战:TensorFlow在图像识别中的应用探索
【10月更文挑战第28天】随着深度学习技术的发展,图像识别取得了显著进步。TensorFlow作为Google开源的机器学习框架,凭借其强大的功能和灵活的API,在图像识别任务中广泛应用。本文通过实战案例,探讨TensorFlow在图像识别中的优势与挑战,展示如何使用TensorFlow构建和训练卷积神经网络(CNN),并评估模型的性能。尽管面临学习曲线和资源消耗等挑战,TensorFlow仍展现出广阔的应用前景。
324 5
|
机器学习/深度学习 人工智能 算法
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
玉米病害识别系统,本系统使用Python作为主要开发语言,通过收集了8种常见的玉米叶部病害图片数据集('矮花叶病', '健康', '灰斑病一般', '灰斑病严重', '锈病一般', '锈病严重', '叶斑病一般', '叶斑病严重'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。再使用Django搭建Web网页操作平台,实现用户上传一张玉米病害图片识别其名称。
206 0
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
|
机器学习/深度学习 TensorFlow API
使用 TensorFlow 和 Keras 构建图像分类器
【10月更文挑战第2天】使用 TensorFlow 和 Keras 构建图像分类器
|
机器学习/深度学习 移动开发 TensorFlow
深度学习之格式转换笔记(四):Keras(.h5)模型转化为TensorFlow(.pb)模型
本文介绍了如何使用Python脚本将Keras模型转换为TensorFlow的.pb格式模型,包括加载模型、重命名输出节点和量化等步骤,以便在TensorFlow中进行部署和推理。
411 0
|
10月前
|
机器学习/深度学习 人工智能 算法
猫狗宠物识别系统Python+TensorFlow+人工智能+深度学习+卷积网络算法
宠物识别系统使用Python和TensorFlow搭建卷积神经网络,基于37种常见猫狗数据集训练高精度模型,并保存为h5格式。通过Django框架搭建Web平台,用户上传宠物图片即可识别其名称,提供便捷的宠物识别服务。
878 55