Machine Learning Mastery 深度学习表现教程(二)
更好的深度学习框架
最后更新于 2019 年 8 月 6 日
现代深度学习库(如 Keras)允许您在几分钟内用几行代码定义并开始拟合各种神经网络模型。
然而,配置神经网络以在新的预测建模问题上获得良好的表现仍然是一个挑战。
取得好表现的挑战可以分为三个主要方面:学习问题、概括问题和预测问题。
一旦您诊断出网络中存在的特定类型的问题,就可以选择一套经典和现代技术来解决问题并提高表现。
在这篇文章中,您将发现一个框架,用于使用深度学习模型和技术来诊断表现问题,您可以使用这些模型和技术来定位和改进每个特定的表现问题。
看完这篇文章,你会知道:
- 定义和拟合神经网络从未如此简单,尽管在新问题上获得良好的表现仍然具有挑战性。
- 神经网络建模表现问题可以分解为学习型、泛化型和预测型问题。
- 有几十年的技术以及现代方法可以用来针对每种类型的模型表现问题。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
更好的深度学习框架 图片由 Anupam_ts 提供,保留部分权利。
概观
本教程分为七个部分;它们是:
- 神经网络复兴
- 配置神经网络的挑战
- 系统更好的深度学习框架
- 更好的学习技巧
- 更好的概括技巧
- 更好的预测技术
- 如何使用框架
神经网络复兴
历史上,神经网络模型必须从零开始编码。
你可能会花上几天或几周的时间将描述不佳的数学翻译成代码,花上几天或几周的时间调试你的代码,只是为了运行一个简单的神经网络模型。
那些日子已经过去了。
如今,您只需几行代码就可以在几分钟内定义并开始拟合大多数类型的神经网络,这要归功于像 Keras 这样的开源库,它建立在像 TensorFlow 这样复杂的数学库之上。
这意味着可以快速开发和评估标准模型(如多层感知器)以及更复杂的模型(如卷积神经网络和递归神经网络,如长短期记忆网络),这些模型以前可能超出了大多数从业者的实现能力。
作为深度学习的实践者,我们生活在一个令人惊叹和富有成效的时代。
然而,即使可以快速定义和评估新的神经网络模型,也很少有人指导如何实际配置神经网络模型以充分利用它们。
配置神经网络的挑战
配置神经网络模型通常被称为“黑暗艺术”
这是因为没有针对给定问题配置网络的硬性规则。我们无法分析计算给定数据集的最佳模型类型或模型配置。
相反,有几十年的技术、启发、技巧、诀窍和其他隐性知识在代码、论文、博客文章和人们的头脑中传播。
在一个问题上配置神经网络的捷径是复制另一个类似问题的网络配置。但是这种策略很少导致好的结果,因为模型配置不能跨问题转移。也有可能你处理的是预测建模问题,这些问题与文献中描述的其他问题非常不同。
幸运的是,在配置和训练神经网络时,有一些已知的技术可以解决特定的问题,这些技术可以在像 Keras 这样的现代深度学习库中获得。
此外,在过去的 5 到 10 年中,在诸如激活函数、自适应学习率、正则化方法和集成技术等领域已经有了发现,这些发现已经被证明可以显著提高神经网络模型的表现,而不管它们的具体类型如何。
技术是可用的;你只需要知道它们是什么以及何时使用它们。
系统更好的深度学习框架
不幸的是,您不能简单地对用于提高深度学习表现的技术进行网格搜索。
几乎普遍的是,它们独特地改变了训练数据、学习过程、模型架构等方面。相反,您必须诊断您的模型存在的表现问题的类型,然后仔细选择和评估针对该诊断问题定制的给定干预。
就深度学习神经网络模型的不良表现而言,有三种类型的问题很容易诊断;它们是:
- 学习上的问题。学习中的问题表现在模型不能有效地学习训练数据集,或者在学习训练数据集时表现出缓慢的进度或糟糕的表现。
- 泛化的问题。泛化的问题表现在一个模型中,该模型过度扩展了训练数据集,并且在保持数据集上表现不佳。
- 预测问题。预测的问题表现在随机训练算法中,对最终模型有很大影响,导致行为和表现的高度变化。
这种细分为思考深度学习模型的表现提供了一种系统的方法。
这些关注领域之间有一些自然的重叠和互动。例如,学习方面的问题会影响模型的泛化能力以及最终模型预测的差异。
提议的细分中三个领域之间的顺序关系允许深度学习模型表现的问题首先被隔离,然后用特定的技术或方法作为目标。
我们可以将有助于解决这些问题的技术总结如下:
- 更好的学习。响应于训练数据集改进或加速神经网络模型权重自适应的技术。
- 更好的概括。提高神经网络模型在保持数据集上的表现的技术。
- 更好的预测。降低最终模型表现差异的技术。
现在,我们已经有了一个使用深度学习神经网络系统诊断表现问题的框架,让我们来看一些可能用于这三个关注领域的技术示例。
更好的学习技巧
更好的学习技术是对神经网络模型或学习算法的那些改变,这些改变响应于训练数据集来改善或加速模型权重的自适应。
在本节中,我们将回顾用于改进模型权重自适应的技术。
这从仔细配置与使用随机梯度下降算法优化神经网络模型和使用误差反向传播算法更新权重相关的超参数开始;例如:
- 配置批量。包括探索批量、随机(在线)或小批量梯度下降等变量是否更合适。
- 配置学习率。包括了解不同学习率对你的问题的影响,以及像亚当这样的现代适应性学习率方法是否合适。
- 配置损耗功能。包括理解不同损失函数的解释方式,以及替代损失函数是否适合您的问题。
这还包括简单的数据准备和更深层输入的自动重新缩放。
- 数据缩放技术。包括小网络权重对输入变量规模的敏感性以及目标变量中的大误差对权重更新的影响。
- 批量归一化。包括对网络模型深层层输入分布变化的敏感性,以及标准化层输入以增加输入一致性和学习过程稳定性的好处。
随机梯度下降是一种通用的优化算法,可以应用于广泛的问题。然而,优化过程(或学习过程)可能变得不稳定,需要具体的干预措施;例如:
- 【梯度消失】 。防止深层多层网络的训练导致靠近输入层的层没有更新它们的权重;这可以使用现代激活函数来解决,例如整流线性激活函数。
- 爆炸梯度。大的权重更新导致数字上溢或下溢,使得网络权重采用 NaN 或 Inf 值;可以使用梯度缩放或梯度裁剪来解决。
某些预测建模问题的数据限制会妨碍有效的学习。可以使用专门的技术来启动优化过程,提供一组有用的初始权重,甚至可以用于特征提取的整个模型;例如:
- 【贪婪逐层预训练】 。在模型中一次添加一个层,学习解释先前层的输出,并允许开发更深层次的模型:深度学习领域的里程碑技术。
- 转学 。其中模型是在不同的、但以某种方式相关的预测建模问题上训练的,然后用于播种权重或者作为特征提取模型被大量使用,以向在感兴趣的问题上训练的模型提供输入。
你有没有使用其他技术来提高学习? 在下面的评论里告诉我。
更好的概括技巧
更好的泛化技术是改变神经网络模型或学习算法,以减少模型过拟合训练数据集的影响,并提高模型在保持验证或测试数据集上的表现。
在本节中,我们将回顾在训练过程中减少模型泛化的技术。
旨在减少泛化误差的技术通常被称为正则化技术。几乎普遍而言,正则化是通过某种方式降低或限制模型复杂性来实现的。
也许最广为人知的模型复杂性度量是模型权重的大小。具有大权重的模型是一种迹象,表明它可能过度专用于训练数据中的输入,使得它在对新的未知数据进行预测时不稳定。通过权重正则化保持权重小是一种强大且广泛使用的技术。
- 权重正则化。对损失函数的改变,按照模型权重的标准(大小)来惩罚模型,鼓励更小的权重,从而降低模型的复杂性。
不是简单地通过更新的损失函数来鼓励权重保持较小,而是可以使用约束来强制权重较小。
- 重量约束。当权重的向量范数超过阈值时,更新模型以重新缩放权重。
神经网络层的输出,无论该层在层堆栈中的什么位置,都可以被认为是关于输入的内部表示或一组提取的特征。更简单的内部表示可以对模型产生正则化效果,并且可以通过鼓励稀疏性(零值)的约束来鼓励。
- 【活动正规化】 。损失函数的一种变化,它按照层激活的范数(大小)比例惩罚模型,鼓励更小或更稀疏的内部表示。
噪声可以被添加到模型中,以鼓励在训练期间关于来自先前层的原始输入或输出的鲁棒性;例如:
- 输入噪声。在输入层或隐藏层之间添加统计变化或噪声,以减少模型对特定输入值的依赖。
- 丢弃。在训练网络以打破跨层节点之间的紧密耦合时,从概率上移除连接(权重)。
通常,过拟合可能仅仅是由于在训练数据集上训练模型的时间过长。一个简单的解决办法就是早点停止训练。
- 提前停机。在训练过程中监控等待验证数据集的模型表现,并在验证集的表现开始下降时停止训练过程。
您是否使用了其他技术来提高概括能力? 在下面的评论里告诉我。
更好的预测技术
更好的预测技术是补充模型训练过程的技术,以减少最终模型预期表现的差异。
在本节中,我们将回顾降低最终深度学习神经网络模型的预期方差的技术。
最终模型表现的差异可以通过增加偏差来减少。将偏差引入最终模型的最常见方式是将多个模型的预测结合起来。这被称为集成学习。
集成学习不仅可以减少最终模型的表现差异,还可以带来更好的预测表现。
有效的集成学习方法要求每个有贡献的模型都有技能,这意味着模型做出的预测比随机的更好,但是模型之间的预测误差具有低相关性。这意味着,文工团成员模特应该有技巧,但方式不同。
这可以通过改变整体的一个方面来实现;例如:
- 改变用于适应每个成员的培训数据。
- 改变对集合预测有贡献的成员。
- 改变集合成员的预测组合方式。
可以通过在数据集的不同子样本上拟合模型来改变训练数据。
这可能涉及在训练数据集的不同随机选择的子集上拟合和保留模型,在 k-fold 交叉验证中保留每个折叠的模型,或者使用 bootstrap 方法(例如 bootstrap 聚合)替换保留不同样本的模型。总的来说,我们可以把这些方法看作是重采样集合。
- 重采样集合。模型集合适合训练数据集的不同样本。
也许改变集合成员的最简单方法包括在训练数据集上从多次运行的学习算法中收集模型。随机学习算法将在每次运行中产生稍微不同的拟合,这又会产生稍微不同的拟合。在多次运行中平均模型将确保表现保持一致。
- 模型平均集合。在同一数据集上的同一学习算法的多次运行中重新训练模型。
这种方法的变化可能涉及具有不同超参数配置的训练模型。
训练多个最终深度学习模型可能会很昂贵,尤其是当一个模型可能需要几天或几周的时间来适应时。
另一种方法是收集模型,在一次训练中用作贡献的集成成员;例如:
- 水平集成。在一次训练结束时,集合成员从一个连续的训练时期块中收集。
- 快照集成。使用积极循环学习率的训练运行,其中在学习率的每个循环的低谷收集集成成员。
组合来自多个集合成员的预测的最简单方法是在回归的情况下计算预测的平均值,或者在分类的情况下计算统计模式或最频繁的预测。
或者,可以学习组合来自多个模型的预测的最佳方式;例如:
- 加权平均集合(混合)。使用指示每个模型中的信任度的学习系数来加权每个集成成员对集成预测的贡献。
- 堆叠概括(堆叠 ) 。一个新的模型被训练来学习如何最好地组合来自集合成员的预测。
作为组合来自集合成员的预测的替代方案,模型本身可以被组合;例如:
- 平均模型权重集合。来自多个神经网络模型的权重被平均成用于进行预测的单个模型。
您是否使用了其他技术来减少最终模型的差异? 在下面的评论里告诉我。
如何使用框架
我们可以将技术组织成更好的学习、泛化和预测三个领域,作为一个系统框架来提高你的神经网络模型的表现。
有太多的技术来合理地调查和评估你的项目中的每一个。
相反,你需要有条不紊,有针对性地使用技术来解决一个明确的问题。
步骤 1:诊断表现问题
使用这个框架的第一步是诊断模型的表现问题。
一个强大的诊断工具是在给定数量的训练时期内,在训练和验证数据集上计算损失的学习曲线和特定问题的度量(如回归的 RMSE 或分类的准确性)。
- 如果在训练数据集上的损失很差,停滞不前,或者没有改善,也许你有一个学习问题。
- 如果训练数据集中的损失或特定于问题的度量在验证数据集中持续改进并变得更糟,那么您可能会遇到泛化问题。
- 如果验证数据集中的损失或特定于问题的指标在接近运行结束时显示出较高的方差,则可能存在预测问题。
步骤 2:选择和评估一种技术
回顾旨在解决您的问题的技术。
选择一种看起来很适合您的模型和问题的技术。这可能需要一些先前的技术经验,对于初学者来说可能具有挑战性。
值得庆幸的是,有一些启发式和最佳实践可以很好地解决大多数问题。
例如:
- 学习问题:拨入学习算法的超参数;具体来说,学习率提供了最大的杠杆作用。
- 泛化问题:使用权重正则化和提前停止在大多数问题最多的模型上效果很好,或者尝试提前停止的脱落。
- 预测问题:对从多次运行或一次运行的多个时期收集的模型中的预测进行平均,以增加足够的偏差。
选择一个干预,然后读一点点,包括它是如何工作的,为什么它工作,重要的是,在你使用它之前,为从业者找到如何使用它来解决你的问题的例子。
步骤 3:转到步骤 1
一旦您确定了一个问题并通过干预解决了它,请重复该过程。
开发一个更好的模型是一个迭代的过程,可能需要在多个层次上的多种干预,这些干预是相辅相成的。
这是一个经验过程。这意味着您依赖于测试工具的健壮性来为您提供干预前后的可靠表现总结。花时间来确保您的测试工具是健壮的,保证训练、测试和验证数据集是干净的,并从您的问题域中提供适当的代表性观察样本。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
书
- 深度学习,2016 年。
- 模式识别与机器学习,2006。
- 神经锻造:前馈人工神经网络中的监督学习,1999。
报纸
- 深度架构基于梯度的训练实用建议,2012。
邮件
文章
- 神经网络常见问题
- 必须知道深层神经网络的技巧/诀窍
摘要
在这篇文章中,您发现了一个使用深度学习模型和技术诊断表现问题的框架,您可以使用该框架来定位和改进每个特定的表现问题。
具体来说,您了解到:
- 定义和拟合神经网络从未如此简单,尽管在新问题上获得良好的表现仍然具有挑战性。
- 神经网络建模表现问题可以分解为学习型、泛化型和预测型问题。
- 有几十年的技术以及现代方法可以用来针对每种类型的模型表现问题。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
如何在深度学习神经网络中使用贪婪逐层预训练
最后更新于 2020 年 8 月 25 日
训练深度神经网络在传统上是具有挑战性的,因为梯度消失意味着接近输入层的层中的权重不会响应于在训练数据集上计算的误差而更新。
深度学习领域的一个创新和重要里程碑是贪婪逐层预训练,它允许非常深的神经网络被成功地训练,实现当时最先进的表现。
在本教程中,您将发现贪婪逐层预处理是一种开发深层多层神经网络模型的技术。
完成本教程后,您将知道:
- 贪婪逐层预处理提供了一种开发深层多层神经网络的方法,同时只训练浅层网络。
- 预处理可用于迭代深化监督模型或可重新用作监督模型的非监督模型。
- 对于带有少量标记数据和大量未标记数据的问题,预处理可能很有用。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2019 年 9 月更新:修正了将按键转换为列表的剧情(感谢马库斯)
- 2019 年 10 月更新:针对 Keras 2.3 和 TensorFlow 2.0 更新。
- 2020 年 1 月更新:针对 Sklearn v0.22 API 的变化进行了更新。
如何使用贪婪逐层预处理开发深度神经网络,版权所有。
教程概述
本教程分为四个部分;它们是:
- 贪婪逐层预处理
- 多类分类问题
- 监督贪婪逐层预处理
- 无监督贪婪逐层预处理
贪婪逐层预处理
传统上,训练多层深度神经网络具有挑战性。
随着隐藏层数量的增加,传播回更早层的错误信息量会显著减少。这意味着靠近输出层的隐藏层中的权重被正常更新,而靠近输入层的隐藏层中的权重被最低限度地更新或者根本不更新。一般来说,这个问题阻碍了非常深的神经网络的训练,被称为 梯度消失问题 。
神经网络复兴的一个重要里程碑,最初允许开发更深层次的神经网络模型,是贪婪的逐层预训练技术,通常简称为“预训练”
2006 年的深度学习复兴始于发现这种贪婪的学习过程可以用来为所有层上的联合学习过程找到良好的初始化,并且这种方法可以用来成功地训练甚至完全连接的架构。
—第 528 页,深度学习,2016。
预处理包括向模型中连续添加新的隐藏层并重新调整,允许新添加的模型从现有的隐藏层中学习输入,通常同时保持现有隐藏层的权重固定。由于模型是一次训练一层的,因此这种技术被命名为“T0”层。
这种技术被称为“贪婪的”,因为分段或分层的方法解决了训练深层网络的难题。作为一个优化过程,将训练过程分成一系列逐层的训练过程被视为贪婪的捷径,可能导致局部最优解的聚集,这是一个足够好的全局解的捷径。
*> 贪婪算法将一个问题分解成许多部分,然后孤立地求解每个部分的最优版本。不幸的是,组合单独的最优组件不能保证产生最优的完整解决方案。
—第 323 页,深度学习,2016。
预训练基于这样的假设,即训练浅网络比训练深网络更容易,并设计了一个分层训练过程,我们总是只适合浅模型。
……建立在训练浅层网络比训练深层网络更容易的前提之上,这一点似乎已经在多个环境中得到了验证。
—第 529 页,深度学习,2016。
预处理的主要好处是:
- 简化培训流程。
- 促进更深层次网络的发展。
- 用作权重初始化方案。
- 也许是更低的泛化误差。
总的来说,预处理对优化和推广都有帮助。
—第 325 页,深度学习,2016。
有两种主要的预处理方法;它们是:
- 监督贪婪逐层预处理。
- 无监督贪婪逐层预处理。
广义而言,监督预处理包括向在监督学习任务上训练的模型连续添加隐藏层。无监督预训练包括使用贪婪逐层过程来建立无监督自动编码器模型,随后向该模型添加有监督的输出层。
通常使用“预训练”一词不仅指预训练阶段本身,还指结合了预训练阶段和监督学习阶段的整个两阶段协议。监督学习阶段可以包括在预训练阶段学习的特征的基础上训练简单的分类器,或者可以包括在预训练阶段学习的整个网络的监督微调。
—第 529 页,深度学习,2016。
当您有大量未标记的示例可用于初始化模型时,非监督预处理可能是合适的,然后再使用数量少得多的示例来微调监督任务的模型权重。
….当标记的例子数量很少时,我们可以预期无监督的预处理是最有帮助的。因为无监督预处理增加的信息源是未标记的数据,所以当未标记的例子数量非常大时,我们也可以期望无监督预处理表现最好。
—第 532 页,深度学习,2016。
尽管先前层中的权重保持不变,但在添加最终层后,通常会对网络末端的所有权重进行微调。因此,这允许预处理被认为是一种权重初始化方法。
……它利用了这样一种思想,即深度神经网络初始参数的选择可以对模型产生显著的正则化效果(在较小程度上,它可以改善优化)。
—第 530-531 页,深度学习,2016。
贪婪逐层预训练是深度学习历史上的一个重要里程碑,它允许早期开发比以前可能的具有更多隐藏层的网络。这种方法在某些问题上是有用的;例如,最佳实践是对文本数据使用无监督的预处理,以便通过 word2vec 提供更丰富的单词及其相互关系的分布式表示。
如今,除了在自然语言处理领域之外,无监督的预处理已经基本上被放弃了[……]预处理的优点是,人们可以在一个巨大的未标记集(例如,使用包含数十亿个单词的语料库)上预处理一次,学习一个良好的表示(通常是单词的表示,但也包括句子的表示),然后使用该表示或对其进行微调,用于训练集包含的示例少得多的监督任务。
—第 535 页,深度学习,2016。
然而,使用现代方法,例如更好的激活函数、权重初始化、梯度下降的变体和正则化方法,可能会获得更好的表现。
如今,我们知道训练完全连接的深度架构不需要贪婪逐层预训练,但是无监督预训练方法是第一个成功的方法。
—第 528 页,深度学习,2016。
多类分类问题
我们将使用一个小的多类分类问题作为基础来演示贪婪逐层预处理对模型表现的影响。
Sklearn 类提供了 make_blobs()函数,该函数可用于创建具有规定数量的样本、输入变量、类和类内样本方差的多类分类问题。
该问题将配置两个输入变量(代表点的 x 和 y 坐标)和每组内点的标准偏差 2.0。我们将使用相同的随机状态(伪随机数发生器的种子)来确保我们总是获得相同的数据点。
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
结果是我们可以建模的数据集的输入和输出元素。
为了了解问题的复杂性,我们可以在二维散点图上绘制每个点,并按类值给每个点着色。
下面列出了完整的示例。
# scatter plot of blobs dataset
from sklearn.datasets import make_blobs
from matplotlib import pyplot
from numpy import where
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
# scatter plot for each class value
for class_value in range(3):
# select indices of points with the class label
row_ix = where(y == class_value)
# scatter plot for points with a different color
pyplot.scatter(X[row_ix, 0], X[row_ix, 1])
# show plot
pyplot.show()
运行该示例会创建整个数据集的散点图。我们可以看到,2.0 的标准差意味着类不是线性可分的(用一条线可分),造成了很多模棱两可的点。
这是可取的,因为这意味着问题不是微不足道的,并将允许神经网络模型找到许多不同的“足够好”的候选解决方案。
具有三个类和按类值着色的点的斑点数据集的散点图
监督贪婪逐层预处理
在本节中,我们将使用贪婪逐层监督学习来为斑点监督学习多类分类问题建立一个深度多层感知器(MLP)模型。
解决这个简单的预测建模问题不需要预处理。相反,这是一个如何执行有监督的贪婪逐层预训练的演示,可用作更大和更具挑战性的有监督学习问题的模板。
作为第一步,我们可以开发一个函数,从问题中创建 1000 个样本,并将它们平均分成训练和测试数据集。下面的 prepare_data() 函数实现了这一点,并根据输入和输出组件返回训练和测试集。
# prepare the dataset
def prepare_data():
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
# one hot encode output variable
y = to_categorical(y)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
return trainX, testX, trainy, testy
我们可以调用这个函数来准备数据。
# prepare data
trainX, testX, trainy, testy = prepare_data()
接下来,我们可以训练和拟合一个基本模型。
这将是一个 MLP,期望数据集中的两个输入变量有两个输入,并且有一个包含 10 个节点的隐藏层,并使用校正的线性激活函数。输出层有三个节点,以便预测三个类别中每个类别的概率,并使用 softmax 激活函数。
# define model
model = Sequential()
model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(3, activation='softmax'))
该模型使用随机梯度下降进行拟合,合理默认学习率为 0.01,高动量值为 0.9。利用交叉熵损失对模型进行优化。
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
然后,该模型适用于 100 个时期的训练数据集,默认批次大小为 32 个示例。
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
下面的 get_base_model() 函数将这些元素联系在一起,将训练数据集作为参数并返回拟合基线模型。
# define and fit the base model
def get_base_model(trainX, trainy):
# define model
model = Sequential()
model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(3, activation='softmax'))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
return model
我们可以调用这个函数来准备基础模型,之后我们可以一次向其中添加一个层。
# get the base model
model = get_base_model(trainX, trainy)
我们需要能够轻松评估模型在列车和测试集上的表现。
下面的 evaluate_model() 函数将训练集和测试集作为参数和模型,并返回两个数据集的准确率。
# evaluate a fit model
def evaluate_model(model, trainX, testX, trainy, testy):
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
return train_acc, test_acc
我们可以调用这个函数来计算和报告基础模型的准确性,并将分数与模型中的层数(目前是两层,一个隐藏层和一个输出层)存储在字典中,这样我们以后就可以绘制层和准确性之间的关系。
# evaluate the base model
scores = dict()
train_acc, test_acc = evaluate_model(model, trainX, testX, trainy, testy)
print('> layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
我们现在可以概述贪婪逐层预处理的过程。
需要一个函数,可以添加新的隐藏层并重新训练模型,但只能更新新添加的层和输出层中的权重。
这需要首先存储当前输出层,包括其配置和当前权重集。
# remember the current output layer
output_layer = model.layers[-1]
然后从模型的层堆栈中移除输出层。
# remove the output layer
model.pop()
然后,模型中的所有剩余层都可以标记为不可训练,这意味着当再次调用 fit() 函数时,它们的权重不能更新。
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
然后,我们可以添加一个新的隐藏层,在这种情况下,其配置与基础模型中添加的第一个隐藏层相同。
# add a new hidden layer
model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))
最后,可以将输出层添加回来,并在训练数据集上重新调整模型。
# re-add the output layer
model.add(output_layer)
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
我们可以将所有这些元素绑定到一个名为 add_layer() 的函数中,该函数将模型和训练数据集作为参数。
# add one new layer and re-train only the new layer
def add_layer(model, trainX, trainy):
# remember the current output layer
output_layer = model.layers[-1]
# remove the output layer
model.pop()
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
# add a new hidden layer
model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))
# re-add the output layer
model.add(output_layer)
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
然后可以根据我们希望添加到模型中的层数重复调用这个函数。
在这种情况下,我们将添加 10 层,一次添加一层,并在添加每一层后评估模型的表现,以了解它如何影响表现。
训练和测试准确度分数根据模型中的层数存储在字典中。
# add layers and evaluate the updated model
n_layers = 10
for i in range(n_layers):
# add layer
add_layer(model, trainX, trainy)
# evaluate model
train_acc, test_acc = evaluate_model(model, trainX, testX, trainy, testy)
print('> layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
# store scores for plotting
scores[len(model.layers)] = (train_acc, test_acc)
在运行结束时,会创建一个线图,显示模型中的层数(x 轴)与列车和测试数据集上的模型准确率数的比较。
我们希望增加层可以提高模型在训练数据集上甚至测试数据集上的表现。
# plot number of added layers vs accuracy
pyplot.plot(list(scores.keys()), [scores[k][0] for k in scores.keys()], label='train', marker='.')
pyplot.plot(list(scores.keys()), [scores[k][1] for k in scores.keys()], label='test', marker='.')
pyplot.legend()
pyplot.show()
将所有这些元素结合在一起,下面列出了完整的示例。
# supervised greedy layer-wise pretraining for blobs classification problem
from sklearn.datasets import make_blobs
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import to_categorical
from matplotlib import pyplot
# prepare the dataset
def prepare_data():
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
# one hot encode output variable
y = to_categorical(y)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
return trainX, testX, trainy, testy
# define and fit the base model
def get_base_model(trainX, trainy):
# define model
model = Sequential()
model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(3, activation='softmax'))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
return model
# evaluate a fit model
def evaluate_model(model, trainX, testX, trainy, testy):
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
return train_acc, test_acc
# add one new layer and re-train only the new layer
def add_layer(model, trainX, trainy):
# remember the current output layer
output_layer = model.layers[-1]
# remove the output layer
model.pop()
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
# add a new hidden layer
model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))
# re-add the output layer
model.add(output_layer)
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
# prepare data
trainX, testX, trainy, testy = prepare_data()
# get the base model
model = get_base_model(trainX, trainy)
# evaluate the base model
scores = dict()
train_acc, test_acc = evaluate_model(model, trainX, testX, trainy, testy)
print('> layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
scores[len(model.layers)] = (train_acc, test_acc)
# add layers and evaluate the updated model
n_layers = 10
for i in range(n_layers):
# add layer
add_layer(model, trainX, trainy)
# evaluate model
train_acc, test_acc = evaluate_model(model, trainX, testX, trainy, testy)
print('> layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
# store scores for plotting
scores[len(model.layers)] = (train_acc, test_acc)
# plot number of added layers vs accuracy
pyplot.plot(list(scores.keys()), [scores[k][0] for k in scores.keys()], label='train', marker='.')
pyplot.plot(list(scores.keys()), [scores[k][1] for k in scores.keys()], label='test', marker='.')
pyplot.legend()
pyplot.show()
运行该示例会报告基本模型(两层)在列车和测试集上的分类准确率,然后添加每个附加层(从 3 层到 12 层)。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到基线模型在这个问题上做得相当好。随着层的增加,我们可以大致看到模型在训练数据集上的准确性有所提高,这可能是因为它开始过度填充数据。我们在测试数据集上看到分类准确率的粗略下降,可能是因为过拟合。
> layers=2, train=0.816, test=0.830
> layers=3, train=0.834, test=0.830
> layers=4, train=0.836, test=0.824
> layers=5, train=0.830, test=0.824
> layers=6, train=0.848, test=0.820
> layers=7, train=0.830, test=0.826
> layers=8, train=0.850, test=0.824
> layers=9, train=0.840, test=0.838
> layers=10, train=0.842, test=0.830
> layers=11, train=0.850, test=0.830
> layers=12, train=0.850, test=0.826
还会创建一个线图,显示每个附加层添加到模型时的列车(蓝色)和测试集(橙色)准确率。
在这种情况下,该图表明训练数据集略有过拟合,但在添加七个层后,测试集表现可能会更好。
有监督贪婪逐层预处理的线图显示模型层与训练和测试集在斑点分类问题上的分类准确率
这个例子的一个有趣的扩展是允许对模型中的所有权重进行微调,对于大量的训练时期使用较小的学习率,看看这是否能进一步减少泛化误差。
无监督贪婪逐层预处理
在这一节中,我们将探索在无监督模型中使用贪婪逐层预处理。
具体来说,我们将开发一个自动编码器模型,该模型将被训练来重建输入数据。为了使用这种无监督模型进行分类,我们将移除输出层,添加并拟合一个新的输出层进行分类。
这比之前有监督的贪婪逐层预训练稍微复杂一些,但是我们可以重用前面部分中的许多相同的想法和代码。
第一步是定义、拟合和评估自动编码器模型。我们将使用与上一节相同的两层基本模型,除了修改它以预测作为输出的输入,并使用均方误差来评估模型在重建给定输入样本方面的表现。
下面的 base_autoencoder() 函数实现了这一点,以训练集和测试集为自变量,然后定义、拟合和评估基本无监督自动编码器模型,在训练集和测试集上打印重构误差并返回模型。
# define, fit and evaluate the base autoencoder
def base_autoencoder(trainX, testX):
# define model
model = Sequential()
model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(2, activation='linear'))
# compile model
model.compile(loss='mse', optimizer=SGD(lr=0.01, momentum=0.9))
# fit model
model.fit(trainX, trainX, epochs=100, verbose=0)
# evaluate reconstruction loss
train_mse = model.evaluate(trainX, trainX, verbose=0)
test_mse = model.evaluate(testX, testX, verbose=0)
print('> reconstruction error train=%.3f, test=%.3f' % (train_mse, test_mse))
return model
我们可以调用这个函数来准备我们的基本自动编码器,我们可以添加并贪婪地训练层。
# get the base autoencoder
model = base_autoencoder(trainX, testX)
评估 blobs 多类分类问题的自动编码器模型需要几个步骤。
隐藏层将被用作分类器的基础,该分类器具有新的输出层,该输出层必须经过训练,然后用于在添加回原始输出层之前进行预测,以便我们可以继续向自动编码器添加层。
第一步是引用,然后移除自动编码器模型的输出层。
# remember the current output layer
output_layer = model.layers[-1]
# remove the output layer
model.pop()
自动编码器中所有剩余的隐藏层必须标记为不可训练,以便在我们训练新的输出层时权重不会改变。
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
我们现在可以添加一个新的输出层,它预测一个示例属于三个类的可能性。还必须使用适用于多类分类的新损失函数重新编译模型。
# add new output layer
model.add(Dense(3, activation='softmax'))
# compile model
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01, momentum=0.9), metrics=['accuracy'])
然后,可以在训练数据集上重新拟合模型,特别是训练输出层如何使用从自动编码器学习的特征作为输入来进行类预测。
然后可以在训练和测试数据集上评估拟合模型的分类准确率。
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
# evaluate model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
最后,我们可以将自动编码器放回一起,但移除分类输出层,将原始自动编码器输出层添加回来,并使用适当的损失函数重新编译模型以进行重建。
# put the model back together
model.pop()
model.add(output_layer)
model.compile(loss='mse', optimizer=SGD(lr=0.01, momentum=0.9))
我们可以将它绑定到一个*evaluate _ auto encoder _ as _ classifier()*函数中,该函数获取模型以及训练集和测试集,然后返回训练集和测试集的分类准确率。
# evaluate the autoencoder as a classifier
def evaluate_autoencoder_as_classifier(model, trainX, trainy, testX, testy):
# remember the current output layer
output_layer = model.layers[-1]
# remove the output layer
model.pop()
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
# add new output layer
model.add(Dense(3, activation='softmax'))
# compile model
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01, momentum=0.9), metrics=['accuracy'])
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
# evaluate model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
# put the model back together
model.pop()
model.add(output_layer)
model.compile(loss='mse', optimizer=SGD(lr=0.01, momentum=0.9))
return train_acc, test_acc
可以调用该函数来评估基线自动编码器模型,然后根据模型中的层数(在本例中为两层)将准确率分数存储在字典中。
# evaluate the base model
scores = dict()
train_acc, test_acc = evaluate_autoencoder_as_classifier(model, trainX, trainy, testX, testy)
print('> classifier accuracy layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
scores[len(model.layers)] = (train_acc, test_acc)
我们现在准备定义向模型添加和预处理层的过程。
添加层的过程与上一节中的监督情况非常相似,只是我们优化的是重建损失,而不是新层的分类准确率。
下面的*add _ layer _ to _ autoencoder()函数向 auto encoder 模型中添加一个新的隐藏层,更新新层和隐藏层的权重,然后报告列车上的重建错误并测试输入数据的集合。该函数确实将所有先前的层重新标记为不可训练的,这是多余的,因为我们已经在evaluate _ auto encoder _ as _ classifier()*函数中这样做了,但我将其留在了中,以防您决定在自己的项目中重用该函数。
# add one new layer and re-train only the new layer
def add_layer_to_autoencoder(model, trainX, testX):
# remember the current output layer
output_layer = model.layers[-1]
# remove the output layer
model.pop()
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
# add a new hidden layer
model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))
# re-add the output layer
model.add(output_layer)
# fit model
model.fit(trainX, trainX, epochs=100, verbose=0)
# evaluate reconstruction loss
train_mse = model.evaluate(trainX, trainX, verbose=0)
test_mse = model.evaluate(testX, testX, verbose=0)
print('> reconstruction error train=%.3f, test=%.3f' % (train_mse, test_mse))
我们现在可以重复调用这个函数,添加层,并通过使用自动编码器作为评估新分类器的基础来评估效果。
# add layers and evaluate the updated model
n_layers = 5
for _ in range(n_layers):
# add layer
add_layer_to_autoencoder(model, trainX, testX)
# evaluate model
train_acc, test_acc = evaluate_autoencoder_as_classifier(model, trainX, trainy, testX, testy)
print('> classifier accuracy layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
# store scores for plotting
scores[len(model.layers)] = (train_acc, test_acc)
像以前一样,收集所有的准确性分数,我们可以使用它们来创建模型层数与训练和测试集准确性的折线图。
# plot number of added layers vs accuracy
keys = list(scores.keys())
pyplot.plot(keys, [scores[k][0] for k in keys], label='train', marker='.')
pyplot.plot(keys, [scores[k][1] for k in keys], label='test', marker='.')
pyplot.legend()
pyplot.show()
将所有这些结合在一起,下面列出了针对斑点多类分类问题的无监督贪婪逐层预处理的完整示例。
# unsupervised greedy layer-wise pretraining for blobs classification problem
from sklearn.datasets import make_blobs
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import to_categorical
from matplotlib import pyplot
# prepare the dataset
def prepare_data():
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
# one hot encode output variable
y = to_categorical(y)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
return trainX, testX, trainy, testy
# define, fit and evaluate the base autoencoder
def base_autoencoder(trainX, testX):
# define model
model = Sequential()
model.add(Dense(10, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(2, activation='linear'))
# compile model
model.compile(loss='mse', optimizer=SGD(lr=0.01, momentum=0.9))
# fit model
model.fit(trainX, trainX, epochs=100, verbose=0)
# evaluate reconstruction loss
train_mse = model.evaluate(trainX, trainX, verbose=0)
test_mse = model.evaluate(testX, testX, verbose=0)
print('> reconstruction error train=%.3f, test=%.3f' % (train_mse, test_mse))
return model
# evaluate the autoencoder as a classifier
def evaluate_autoencoder_as_classifier(model, trainX, trainy, testX, testy):
# remember the current output layer
output_layer = model.layers[-1]
# remove the output layer
model.pop()
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
# add new output layer
model.add(Dense(3, activation='softmax'))
# compile model
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01, momentum=0.9), metrics=['accuracy'])
# fit model
model.fit(trainX, trainy, epochs=100, verbose=0)
# evaluate model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
# put the model back together
model.pop()
model.add(output_layer)
model.compile(loss='mse', optimizer=SGD(lr=0.01, momentum=0.9))
return train_acc, test_acc
# add one new layer and re-train only the new layer
def add_layer_to_autoencoder(model, trainX, testX):
# remember the current output layer
output_layer = model.layers[-1]
# remove the output layer
model.pop()
# mark all remaining layers as non-trainable
for layer in model.layers:
layer.trainable = False
# add a new hidden layer
model.add(Dense(10, activation='relu', kernel_initializer='he_uniform'))
# re-add the output layer
model.add(output_layer)
# fit model
model.fit(trainX, trainX, epochs=100, verbose=0)
# evaluate reconstruction loss
train_mse = model.evaluate(trainX, trainX, verbose=0)
test_mse = model.evaluate(testX, testX, verbose=0)
print('> reconstruction error train=%.3f, test=%.3f' % (train_mse, test_mse))
# prepare data
trainX, testX, trainy, testy = prepare_data()
# get the base autoencoder
model = base_autoencoder(trainX, testX)
# evaluate the base model
scores = dict()
train_acc, test_acc = evaluate_autoencoder_as_classifier(model, trainX, trainy, testX, testy)
print('> classifier accuracy layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
scores[len(model.layers)] = (train_acc, test_acc)
# add layers and evaluate the updated model
n_layers = 5
for _ in range(n_layers):
# add layer
add_layer_to_autoencoder(model, trainX, testX)
# evaluate model
train_acc, test_acc = evaluate_autoencoder_as_classifier(model, trainX, trainy, testX, testy)
print('> classifier accuracy layers=%d, train=%.3f, test=%.3f' % (len(model.layers), train_acc, test_acc))
# store scores for plotting
scores[len(model.layers)] = (train_acc, test_acc)
# plot number of added layers vs accuracy
keys = list(scores.keys())
pyplot.plot(keys, [scores[k][0] for k in keys], label='train', marker='.')
pyplot.plot(keys, [scores[k][1] for k in keys], label='test', marker='.')
pyplot.legend()
pyplot.show()
运行该示例会报告基本模型(两层)的训练集和测试集的重建误差和分类准确率,然后添加每个附加层(从三层到十二层)。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到重建误差从低开始,实际上接近完美,然后在训练过程中慢慢增加。随着层被添加到编码器中,训练数据集上的准确率似乎会降低,尽管准确率测试似乎会随着层的添加而提高,至少在模型有五层之前是这样,之后表现似乎会崩溃。
> reconstruction error train=0.000, test=0.000
> classifier accuracy layers=2, train=0.830, test=0.832
> reconstruction error train=0.001, test=0.002
> classifier accuracy layers=3, train=0.826, test=0.842
> reconstruction error train=0.002, test=0.002
> classifier accuracy layers=4, train=0.820, test=0.838
> reconstruction error train=0.016, test=0.028
> classifier accuracy layers=5, train=0.828, test=0.834
> reconstruction error train=2.311, test=2.694
> classifier accuracy layers=6, train=0.764, test=0.762
> reconstruction error train=2.192, test=2.526
> classifier accuracy layers=7, train=0.764, test=0.760
还会创建一个线图,显示每个附加层添加到模型时的列车(蓝色)和测试集(橙色)准确率。
在这种情况下,该图表明,无监督的贪婪逐层预训练可能有一些小的好处,但也许超过五层,模型变得不稳定。
无监督贪婪逐层预处理的线图显示模型层与训练和测试集在斑点分类问题上的分类准确率
一个有趣的扩展是探索在拟合分类器输出层之前或之后微调模型中的所有权重是否会提高表现。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 深度网络的贪婪逐层训练,2007。
- 为什么无监督预训练有助于深度学习,2010。
书
摘要
在本教程中,您发现贪婪逐层预处理是一种开发深层多层神经网络模型的技术。
具体来说,您了解到:
- 贪婪逐层预处理提供了一种开发深层多层神经网络的方法,同时只训练浅层网络。
- 预处理可用于迭代深化监督模型或可重新用作监督模型的非监督模型。
- 对于带有少量标记数据和大量未标记数据的问题,预处理可能很有用。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。*
如何开发水平投票深度学习集成来减少方差
最后更新于 2020 年 8 月 25 日
相对于未标记示例的数量,训练数据集较小的预测建模问题具有挑战性。
神经网络可以在这些类型的问题上表现良好,尽管它们在训练或保持验证数据集上测量的模型表现会有很大差异。这使得选择哪种模型作为最终模型存在风险,因为在训练结束时,没有明确的信号表明哪种模型比另一种模型更好。
水平投票集成是解决这个问题的一种简单方法,在接近训练运行结束的连续训练时期上保存的模型集合被保存并用作集成,这比随机选择单个最终模型产生更稳定和更好的平均表现。
在本教程中,您将发现如何使用水平投票集成来降低最终深度学习神经网络模型的方差。
完成本教程后,您将知道:
- 选择在训练数据集上具有高方差的最终神经网络模型是具有挑战性的。
- 水平投票集成为使用单次训练运行的高方差模型提供了一种减少方差和提高平均模型表现的方法。
- 如何使用 Keras 在 Python 中开发水平投票集成,以提高最终多层感知器模型的表现,从而进行多类分类。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 更新 2019 年 2 月:删除了对完整代码示例中未使用的验证数据集的引用。
- 2019 年 10 月更新:针对 Keras 2.3 和 TensorFlow 2.0 更新。
- 2020 年 1 月更新:针对 Sklearn v0.22 API 的变化进行了更新。
如何通过水平投票集成减少最终深度学习模型中的差异法蒂玛·弗洛雷斯摄,保留部分权利。
教程概述
本教程分为五个部分;它们是:
- 水平投票组合
- 多类分类问题
- 多层感知器模型
- 保存水平模型
- 进行水平集合预测
水平投票组合
集成学习结合了来自多个模型的预测。
当使用深度学习方法时,使用集成学习的一个挑战是,给定使用非常大的数据集和大模型,给定的训练运行可能需要几天、几周甚至几个月。训练多个模型可能不可行。
可能有助于集合的模型的另一个来源是训练期间不同点的单个模型的状态。
水平投票是谢晶晶等人在 2013 年的论文《深度表示分类的水平和垂直集成》中提出的集成方法
该方法包括在集成训练结束之前,使用来自连续时期块末端的多个模型进行预测。
该方法是专门为那些预测建模问题开发的,在这些问题中,与模型所需的预测数量相比,训练数据集相对较小。这导致模型在训练期间具有高的表现差异。在这种情况下,考虑到表现的差异,在训练过程的最后使用最终模型或任何给定的模型都是有风险的。
……随着训练周期的增长,分类错误率会先下降,然后趋于稳定。但是,当标记训练集的规模太小时,错误率会振荡[…],因此很难选择一个“神奇”的时期来获得可靠的输出。
——深度表征分类的水平和垂直集合,2013。
相反,作者建议在训练过程中使用一个集合中来自一个连续时期块的所有模型,例如来自最近 200 个时期的模型。结果是集合的预测与集合中的任何单个模型一样好或更好。
为了减少不稳定性,我们提出了一种称为水平投票的方法。首先,选择为相对稳定的历元范围训练的网络。每个标签概率的预测由具有所选时期的顶层表示的标准分类器产生,然后被平均。
——深度表征分类的水平和垂直集合,2013。
因此,水平投票集成方法为给定模型需要大量计算资源来训练的情况和/或由于使用相对较小的训练数据集而导致训练的高方差而使得最终模型选择具有挑战性的情况提供了理想的方法。
既然我们已经熟悉了横向投票,我们就可以实现这个程序了。
多类分类问题
我们将使用一个小的多类分类问题作为基础来演示水平投票集成。
Sklearn 类提供了 make_blobs()函数,该函数可用于创建具有规定数量的样本、输入变量、类和类内样本方差的多类分类问题。
该问题有两个输入变量(表示点的 x 和 y 坐标),每个组中的点的标准偏差为 2.0。我们将使用相同的随机状态(伪随机数发生器的种子)来确保我们总是获得相同的数据点。
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
结果是我们可以建模的数据集的输入和输出元素。
为了了解问题的复杂性,我们可以在二维散点图上绘制每个点,并按类值给每个点着色。
下面列出了完整的示例。
# scatter plot of blobs dataset
from sklearn.datasets import make_blobs
from matplotlib import pyplot
from pandas import DataFrame
# generate 2d classification dataset
X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=2)
# scatter plot, dots colored by class value
df = DataFrame(dict(x=X[:,0], y=X[:,1], label=y))
colors = {0:'red', 1:'blue', 2:'green'}
fig, ax = pyplot.subplots()
grouped = df.groupby('label')
for key, group in grouped:
group.plot(ax=ax, kind='scatter', x='x', y='y', label=key, color=colors[key])
pyplot.show()
运行该示例会创建整个数据集的散点图。我们可以看到,2.0 的标准差意味着类不是线性可分的(可以用一条线分开),导致很多不明确的点。
这是可取的,因为这意味着问题不是微不足道的,并将允许神经网络模型找到许多不同的“足够好”的候选解决方案,从而导致高方差。
具有三个类和按类值着色的点的斑点数据集的散点图
多层感知器模型
在我们定义一个模型之前,我们需要设计一个适合水平投票集合的问题。
在我们的问题中,训练数据集相对较小。具体来说,训练数据集中的示例与保持数据集中的示例的比例为 10:1。这模拟了一种情况,即我们可能有大量未标记的示例和少量已标记的示例来训练模型。
我们将从斑点问题中创建 1100 个数据点。模型将在前 100 个点上进行训练,剩余的 1000 个点将保留在测试数据集中,模型无法使用。
# generate 2d classification dataset
X, y = make_blobs(n_samples=1100, centers=3, n_features=2, cluster_std=2, random_state=2)
# split into train and test
n_train = 100
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
print(trainX.shape, testX.shape)
该问题是一个多类分类问题,我们将在输出层使用 softmax 激活函数对其进行建模。
这意味着模型将以样本属于三类中每一类的概率来预测具有三个元素的向量。因此,我们必须对类值进行热编码,最好是在我们将行分割成训练、测试和验证数据集之前,这样它就是一个函数调用。
y = to_categorical(y)
接下来,我们可以定义并组合模型。
该模型将预期具有两个输入变量的样本。然后,该模型有一个具有 25 个节点的单个隐藏层和一个校正的线性激活函数,然后有一个具有三个节点的输出层来预测三个类中每一个的概率,还有一个 softmax 激活函数。
由于问题是多类的,我们将使用分类交叉熵损失函数来优化模型和有效的 Adam 味道的随机梯度下降。
# define model
model = Sequential()
model.add(Dense(25, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
该模型适用于 1,000 个训练时期,我们将使用测试集作为验证集来评估训练集中每个时期的模型。
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=1000, verbose=0)
在运行结束时,我们将评估模型在列车和测试集上的表现。
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
最后,我们将在训练和验证数据集上绘制每个训练时期的模型准确率的学习曲线。
# learning curves of model accuracy
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()
将所有这些结合在一起,下面列出了完整的示例。
# develop an mlp for blobs dataset
from sklearn.datasets import make_blobs
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_blobs(n_samples=1100, centers=3, n_features=2, cluster_std=2, random_state=2)
# one hot encode output variable
y = to_categorical(y)
# split into train and test
n_train = 100
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
print(trainX.shape, testX.shape)
# define model
model = Sequential()
model.add(Dense(25, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=1000, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# learning curves of model accuracy
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()
运行该示例首先打印每个数据集的形状以供确认,然后打印最终模型在训练和测试数据集上的表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到,该模型在我们知道是乐观的训练数据集上达到了大约 85%的准确率,在测试数据集上达到了大约 80%,我们预计这将更加真实。
(100, 2) (1000, 2)
Train: 0.850, Test: 0.804
还创建了一个线图,显示了在每个训练周期内,训练和测试集上模型准确率的学习曲线。
我们可以看到,在整个跑步过程中,训练的准确性更加乐观,我们也注意到了最终得分。我们可以看到,与测试集相比,模型的准确性在训练数据集上具有较高的方差,这是我们所期望的。
模型中的差异突出了这样一个事实,即在运行结束时选择模型或从大约纪元 800 开始的任何模型都是具有挑战性的,因为训练数据集上的准确率具有很高的差异。我们还在测试数据集上看到了方差的静音版本。
每个训练时期训练和测试数据集上模型准确率的线图学习曲线
既然我们已经确定该模型是横向投票集成的良好候选,我们就可以开始实现该技术了。
保存水平模型
可能有许多方法来实现水平投票集合。
也许最简单的方法是手动驱动训练过程,一次一个纪元,然后在纪元结束时保存模型,如果我们已经超过了纪元数量的上限。
例如,对于我们的测试问题,我们将针对 1,000 个纪元训练模型,并且可能保存从纪元 950 开始的模型(例如,在纪元 950 和 999 之间并且包括纪元 950 和 999)。
# fit model
n_epochs, n_save_after = 1000, 950
for i in range(n_epochs):
# fit model for a single epoch
model.fit(trainX, trainy, epochs=1, verbose=0)
# check if we should save the model
if i >= n_save_after:
model.save('models/model_' + str(i) + '.h5')
使用模型上的 save() 功能并指定包含纪元号的文件名,可以将模型保存到文件中。
为了避免与我们的源文件混淆,我们将所有模型保存在当前工作目录中新的“ models/ ”文件夹下。
# create directory for models
makedirs('models')
注意,在 Keras 中保存和加载神经网络模型需要安装 h5py 库。
您可以使用 pip 安装此库,如下所示:
pip install h5py
将所有这些结合在一起,下面列出了在训练数据集上拟合模型并保存最近 50 个时代的所有模型的完整示例。
# save horizontal voting ensemble members during training
from sklearn.datasets import make_blobs
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot
from os import makedirs
# generate 2d classification dataset
X, y = make_blobs(n_samples=1100, centers=3, n_features=2, cluster_std=2, random_state=2)
# one hot encode output variable
y = to_categorical(y)
# split into train and test
n_train = 100
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
print(trainX.shape, testX.shape)
# define model
model = Sequential()
model.add(Dense(25, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# create directory for models
makedirs('models')
# fit model
n_epochs, n_save_after = 1000, 950
for i in range(n_epochs):
# fit model for a single epoch
model.fit(trainX, trainy, epochs=1, verbose=0)
# check if we should save the model
if i >= n_save_after:
model.save('models/model_' + str(i) + '.h5')
运行该示例会创建“模型/ ”文件夹,并将 50 个模型保存到目录中。
注意,要重新运行这个示例,您必须删除“ models/ ”目录,以便脚本可以重新创建它。
进行水平集合预测
现在我们已经创建了模型,我们可以在水平投票集合中使用它们。
首先,我们需要将模型加载到内存中。这是合理的,因为模型很小。如果您试图开发一个具有非常大的模型的水平投票集成,可能更容易一次加载一个模型,进行预测,然后加载下一个模型并重复该过程。
下面的功能 load_all_models() 将从“ models/ 目录加载模型。它以开始和结束时代作为参数,这样您就可以试验在连续时代中保存的不同组的模型。
# load models from file
def load_all_models(n_start, n_end):
all_models = list()
for epoch in range(n_start, n_end):
# define filename for this ensemble
filename = 'models/model_' + str(epoch) + '.h5'
# load model from file
model = load_model(filename)
# add to list of members
all_models.append(model)
print('>loaded %s' % filename)
return all_models
我们可以调用函数来加载所有的模型。
然后,我们可以反转模型列表,以便运行结束时的模型位于列表的开头。这将有助于我们以后测试不同大小的投票集合,包括从跑步结束后向后到训练时期的模型,以防最好的模型真的在跑步结束时。
# load models in order
members = load_all_models(950, 1000)
print('Loaded %d models' % len(members))
# reverse loaded models so we build the ensemble with the last models first
members = list(reversed(members))
接下来,我们可以评估测试数据集中每个保存的模型,以及来自训练的最后 n 个连续模型的投票集合。
我们想知道每个模型在测试数据集上的实际表现如何,更重要的是,模型表现在测试数据集上的分布,这样我们就知道从运行结束时选择的平均模型在实践中的表现如何(或差)。
我们不知道横向投票组合中包括多少成员。因此,我们可以测试不同数量的连续成员,从最终模型向后工作。
首先,我们需要一个函数来用集合成员的列表进行预测。每个成员预测三个输出类的概率。将概率相加,我们使用 argmax 来选择支持度最高的类。下面的*集成预测()*函数实现了这种基于投票的预测方案。
# make an ensemble prediction for multi-class classification
def ensemble_predictions(members, testX):
# make predictions
yhats = [model.predict(testX) for model in members]
yhats = array(yhats)
# sum across ensemble members
summed = numpy.sum(yhats, axis=0)
# argmax across classes
result = argmax(summed, axis=1)
return result
接下来,我们需要一个函数来评估给定大小的集合成员的子集。
需要选择子集,做出预测,并通过将预测与期望值进行比较来估计集合的表现。下面的 evaluate_n_members() 函数实现了这个集合大小的评估。
# evaluate a specific number of members in an ensemble
def evaluate_n_members(members, n_members, testX, testy):
# select a subset of members
subset = members[:n_members]
# make prediction
yhat = ensemble_predictions(subset, testX)
# calculate accuracy
return accuracy_score(testy, yhat)
我们现在可以列举从 1 到 50 的不同大小的横向投票组合。每个成员都单独评估,然后评估该规模的整体,并记录分数。
# evaluate different numbers of ensembles on hold out set
single_scores, ensemble_scores = list(), list()
for i in range(1, len(members)+1):
# evaluate model with i members
ensemble_score = evaluate_n_members(members, i, testX, testy)
# evaluate the i'th model standalone
testy_enc = to_categorical(testy)
_, single_score = members[i-1].evaluate(testX, testy_enc, verbose=0)
# summarize this step
print('> %d: single=%.3f, ensemble=%.3f' % (i, single_score, ensemble_score))
ensemble_scores.append(ensemble_score)
single_scores.append(single_score)
在评估结束时,我们报告测试数据集中单个模型的分数分布。如果我们选择任何一个保存的模型作为最终模型,平均分数就是我们平均期望的分数。
# summarize average accuracy of a single final model
print('Accuracy %.3f (%.3f)' % (mean(single_scores), std(single_scores)))
最后,我们可以绘制分数。每个独立模型的分数都绘制为蓝点,并为每个连续模型的集合(橙色)创建线图。
# plot score vs number of ensemble members
x_axis = [i for i in range(1, len(members)+1)]
pyplot.plot(x_axis, single_scores, marker='o', linestyle='None')
pyplot.plot(x_axis, ensemble_scores, marker='o')
pyplot.show()
我们的预期是,一个合理规模的集合将优于随机选择的模型,并且在选择集合规模时存在收益递减点。
下面列出了完整的示例。
# load models and make predictions using a horizontal voting ensemble
from sklearn.datasets import make_blobs
from sklearn.metrics import accuracy_score
from keras.utils import to_categorical
from keras.models import load_model
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot
from numpy import mean
from numpy import std
import numpy
from numpy import array
from numpy import argmax
# load models from file
def load_all_models(n_start, n_end):
all_models = list()
for epoch in range(n_start, n_end):
# define filename for this ensemble
filename = 'models/model_' + str(epoch) + '.h5'
# load model from file
model = load_model(filename)
# add to list of members
all_models.append(model)
print('>loaded %s' % filename)
return all_models
# make an ensemble prediction for multi-class classification
def ensemble_predictions(members, testX):
# make predictions
yhats = [model.predict(testX) for model in members]
yhats = array(yhats)
# sum across ensemble members
summed = numpy.sum(yhats, axis=0)
# argmax across classes
result = argmax(summed, axis=1)
return result
# evaluate a specific number of members in an ensemble
def evaluate_n_members(members, n_members, testX, testy):
# select a subset of members
subset = members[:n_members]
# make prediction
yhat = ensemble_predictions(subset, testX)
# calculate accuracy
return accuracy_score(testy, yhat)
# generate 2d classification dataset
X, y = make_blobs(n_samples=1100, centers=3, n_features=2, cluster_std=2, random_state=2)
# split into train and test
n_train = 100
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
print(trainX.shape, testX.shape)
# load models in order
members = load_all_models(950, 1000)
print('Loaded %d models' % len(members))
# reverse loaded models so we build the ensemble with the last models first
members = list(reversed(members))
# evaluate different numbers of ensembles on hold out set
single_scores, ensemble_scores = list(), list()
for i in range(1, len(members)+1):
# evaluate model with i members
ensemble_score = evaluate_n_members(members, i, testX, testy)
# evaluate the i'th model standalone
testy_enc = to_categorical(testy)
_, single_score = members[i-1].evaluate(testX, testy_enc, verbose=0)
# summarize this step
print('> %d: single=%.3f, ensemble=%.3f' % (i, single_score, ensemble_score))
ensemble_scores.append(ensemble_score)
single_scores.append(single_score)
# summarize average accuracy of a single final model
print('Accuracy %.3f (%.3f)' % (mean(single_scores), std(single_scores)))
# plot score vs number of ensemble members
x_axis = [i for i in range(1, len(members)+1)]
pyplot.plot(x_axis, single_scores, marker='o', linestyle='None')
pyplot.plot(x_axis, ensemble_scores, marker='o')
pyplot.show()
首先,将 50 个保存的模型加载到内存中。
...
>loaded models/model_990.h5
>loaded models/model_991.h5
>loaded models/model_992.h5
>loaded models/model_993.h5
>loaded models/model_994.h5
>loaded models/model_995.h5
>loaded models/model_996.h5
>loaded models/model_997.h5
>loaded models/model_998.h5
>loaded models/model_999.h5
接下来,在保持测试数据集和该大小的集合(1、2、3 等)上评估每个单个模型的表现。)在保持测试数据集上创建和评估。
> 1: single=0.814, ensemble=0.814
> 2: single=0.816, ensemble=0.816
> 3: single=0.812, ensemble=0.816
> 4: single=0.812, ensemble=0.815
> 5: single=0.811, ensemble=0.815
> 6: single=0.812, ensemble=0.812
> 7: single=0.812, ensemble=0.813
> 8: single=0.817, ensemble=0.814
> 9: single=0.819, ensemble=0.814
> 10: single=0.822, ensemble=0.816
> 11: single=0.822, ensemble=0.817
> 12: single=0.821, ensemble=0.818
> 13: single=0.822, ensemble=0.821
> 14: single=0.820, ensemble=0.821
> 15: single=0.817, ensemble=0.820
> 16: single=0.819, ensemble=0.820
> 17: single=0.816, ensemble=0.819
> 18: single=0.815, ensemble=0.819
> 19: single=0.813, ensemble=0.819
> 20: single=0.812, ensemble=0.818
> 21: single=0.812, ensemble=0.818
> 22: single=0.810, ensemble=0.818
> 23: single=0.812, ensemble=0.819
> 24: single=0.815, ensemble=0.819
> 25: single=0.816, ensemble=0.819
> 26: single=0.817, ensemble=0.819
> 27: single=0.819, ensemble=0.819
> 28: single=0.816, ensemble=0.819
> 29: single=0.817, ensemble=0.819
> 30: single=0.819, ensemble=0.820
> 31: single=0.817, ensemble=0.820
> 32: single=0.819, ensemble=0.820
> 33: single=0.817, ensemble=0.819
> 34: single=0.816, ensemble=0.819
> 35: single=0.815, ensemble=0.818
> 36: single=0.816, ensemble=0.818
> 37: single=0.816, ensemble=0.818
> 38: single=0.819, ensemble=0.818
> 39: single=0.817, ensemble=0.817
> 40: single=0.816, ensemble=0.816
> 41: single=0.816, ensemble=0.816
> 42: single=0.816, ensemble=0.817
> 43: single=0.817, ensemble=0.817
> 44: single=0.816, ensemble=0.817
> 45: single=0.816, ensemble=0.818
> 46: single=0.817, ensemble=0.818
> 47: single=0.812, ensemble=0.818
> 48: single=0.811, ensemble=0.818
> 49: single=0.810, ensemble=0.818
> 50: single=0.811, ensemble=0.818
大致上,我们可以看到,该集成似乎优于大多数单一模型,始终达到 81.8%左右的准确率。
接下来,报告单个模型的准确率分布。我们可以看到,随机选择任何一个保存的模型都会得到一个平均准确率为 81.6%的模型,其相对严格的标准偏差为 0.3%。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
为了有用,我们需要水平集成超过这个平均值。
Accuracy 0.816 (0.003)
最后,创建一个图表,总结每个单个模型(蓝点)的表现,以及从 1 到 50 个成员的每个大小的集合。
我们可以从蓝点看出,各个时期的模型没有结构,例如,如果训练期间的最后一个模型更好,那么从左到右的准确性将呈下降趋势。
我们可以看到,随着我们添加更多的集成成员,橙色线中的水平投票集成的表现越好。我们可以看到,在这个问题上的表现可能在 23 至 33 个时代之间趋于平缓;那可能是个不错的选择。
显示单个模型准确率(蓝点)与水平投票集合中不同大小集合的准确率的线图
扩展ˌ扩张
本节列出了一些您可能希望探索的扩展教程的想法。
- 数据集大小。使用较小或较大的数据集重复实验,训练与测试示例的比率相似。
- 更大的集成。用数百个最终模型重新运行该示例,并报告大规模集成对测试集准确性的影响。
- 随机采样模型。重新运行该示例,将相同大小的集成的表现与连续时期保存的模型和随机选择的保存模型进行比较。
如果你探索这些扩展,我很想知道。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 深度表征分类的水平和垂直集合,2013。
应用程序接口
- 开始使用 Keras 顺序模型
- 硬核层 API
- scipy . stat . mode API的缩写形式
- num py . argmax API
- sklearn . dataset . make _ blobs API
- 如何保存一个 Keras 模型?
- 硬回调接口
摘要
在本教程中,您发现了如何使用水平投票集成来降低最终深度学习神经网络模型的方差。
具体来说,您了解到:
- 选择在训练数据集上具有高方差的最终神经网络模型是具有挑战性的。
- 水平投票集成为使用单次训练运行的高方差模型提供了一种减少方差和提高平均模型表现的方法。
- 如何使用 Keras 在 Python 中开发水平投票集成,以提高最终多层感知器模型的表现,从而进行多类分类。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
如何利用批量归一化加速深度神经网络的学习
最后更新于 2020 年 8 月 25 日
批处理标准化是一种技术,旨在自动标准化深度学习神经网络中某一层的输入。
一旦实现,批处理规范化具有显著加速神经网络训练过程的效果,并且在某些情况下,通过适度的规范化效果提高了模型的表现。
在本教程中,您将发现如何使用批处理规范化来加速使用 Keras 在 Python 中训练深度学习神经网络。
完成本教程后,您将知道:
- 如何使用 Keras API 创建和配置 BatchNormalization 层。
- 如何在深度学习神经网络模型中加入 BatchNormalization 层?
- 如何更新 MLP 模型以使用批处理规范化来加速二分类问题的训练。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2019 年 10 月更新:针对 Keras 2.3 和 TensorFlow 2.0 更新。
如何利用批处理归一化加速深度神经网络的学习 图片由安吉拉和安德鲁提供,版权所有。
教程概述
本教程分为三个部分;它们是:
- Keras 的 BatchNormalization
- 模型中的批处理规范化
- 批量化案例研究
Keras 的 BatchNormalization
Keras 通过 batch normalization 层提供对批处理规范化的支持。
例如:
bn = BatchNormalization()
该层将转换输入,使其标准化,这意味着它们的平均值为零,标准偏差为 1。
在训练期间,该层将跟踪每个输入变量的统计数据,并使用它们来标准化数据。
此外,可以使用定义变换输出的新平均值和标准偏差的β和γ的学习参数来缩放标准化输出。该层可以配置为分别通过“中心”和“比例属性控制是否使用这些附加参数。默认情况下,它们是启用的。
用于执行标准化的统计数据,例如每个变量的平均值和标准偏差,针对每个小批量进行更新,并保持运行平均值。
一个“动量”参数允许你控制在计算更新时包含多少来自前一个小批量的统计数据。默认情况下,该值保持较高,为 0.99。这可以设置为 0.0,以便只使用当前小批量的统计数据,如原论文所述。
bn = BatchNormalization(momentum=0.0)
在训练结束时,当使用模型进行预测时,将使用当时层中的均值和标准差统计来标准化输入。
所有小批次的默认配置估计平均值和标准偏差可能是合理的。
模型中的批处理规范化
批处理规范化可以用于模型中的大多数点,也可以用于大多数类型的深度学习神经网络。
输入和隐藏层输入
BatchNormalization 层可以添加到您的模型中,以标准化原始输入变量或隐藏层的输出。
不建议将批处理规范化作为模型适当数据准备的替代方法。
然而,当用于标准化原始输入变量时,层必须指定 input_shape 参数;例如:
...
model = Sequential
model.add(BatchNormalization(input_shape=(2,)))
...
当用于标准化隐藏层的输出时,该层可以像任何其他层一样添加到模型中。
...
model = Sequential
...
model.add(BatchNormalization())
...
在激活功能之前或之后使用
BatchNormalization 规范化层可用于标准化前一层激活功能之前或之后的输入。
介绍该方法的原纸建议在前一层的激活函数之前加入批量归一化,例如:
...
model = Sequential
model.add(Dense(32))
model.add(BatchNormalization())
model.add(Activation('relu'))
...
有报道实验提示在前一层的激活功能后加入批量归一化层时表现更好;例如:
...
model = Sequential
model.add(Dense(32, activation='relu'))
model.add(BatchNormalization())
...
如果时间和资源允许,可能值得在您的模型上测试这两种方法,并使用产生最佳表现的方法。
让我们来看看批处理规范化如何与一些常见的网络类型一起使用。
MLP 批量标准化
下面的示例在密集隐藏层之间的激活函数之后添加批处理规范化。
# example of batch normalization for an mlp
from keras.layers import Dense
from keras.layers import BatchNormalization
...
model.add(Dense(32, activation='relu'))
model.add(BatchNormalization())
model.add(Dense(1))
...
美国有线电视新闻网批量标准化
下面的示例在卷积层和最大池层之间的激活函数之后添加了批处理规范化。
# example of batch normalization for an cnn
from keras.layers import Dense
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import BatchNormalization
...
model.add(Conv2D(32, (3,3), activation='relu'))
model.add(Conv2D(32, (3,3), activation='relu'))
model.add(BatchNormalization())
model.add(MaxPooling2D())
model.add(Dense(1))
...
RNN 批量标准化
下面的示例在 LSTM 和密集隐藏层之间的激活函数之后添加批处理规范化。
# example of batch normalization for a lstm
from keras.layers import Dense
from keras.layers import LSTM
from keras.layers import BatchNormalization
...
model.add(LSTM(32))
model.add(BatchNormalization())
model.add(Dense(1))
...
批量化案例研究
在本节中,我们将演示如何使用批处理规范化来加速 MLP 在简单二进制分类问题上的训练。
此示例提供了一个模板,用于将批处理规范化应用于您自己的神经网络,以解决分类和回归问题。
二分类问题
我们将使用一个标准的二分类问题,它定义了两个观察值的二维同心圆,每个类一个圆。
每个观察都有两个相同规模的输入变量和一个 0 或 1 的类输出值。该数据集被称为“圆”数据集,因为绘制时每个类中的观测值的形状不同。
我们可以使用 make_circles()函数从这个问题中生成观察值。我们将向数据中添加噪声,并为随机数生成器播种,这样每次运行代码时都会生成相同的样本。
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
我们可以绘制数据集,其中两个变量作为图形上的 x 和 y 坐标,类值作为观察的颜色。
下面列出了生成数据集并绘制它的完整示例。
# scatter plot of the circles dataset with points colored by class
from sklearn.datasets import make_circles
from numpy import where
from matplotlib import pyplot
# generate circles
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# select indices of points with each class label
for i in range(2):
samples_ix = where(y == i)
pyplot.scatter(X[samples_ix, 0], X[samples_ix, 1], label=str(i))
pyplot.legend()
pyplot.show()
运行该示例会创建一个散点图,显示每个类中观察值的同心圆形状。
我们可以看到点扩散的噪音使得圆圈不那么明显。
带有显示每个样本类别值的颜色的圆形数据集散点图
这是一个很好的测试问题,因为类不能用一条线分开,例如不能线性分开,需要一个非线性的方法,如神经网络来解决。
多层感知器模型
我们可以开发一个多层感知器模型,或 MLP,作为这个问题的基线。
首先,我们将把 1000 个生成的样本分成一个训练和测试数据集,每个数据集有 500 个例子。这将为模型提供足够大的学习样本和同等规模的(公平的)表现评估。
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
我们将定义一个简单的 MLP 模型。对于数据集中的两个变量,网络在可见层中必须有两个输入。
该模型将有 50 个节点的单个隐藏层,任意选择,并使用校正线性激活函数(ReLU) 和 he 随机权重初始化方法。输出层将是具有 sigmoid 激活函数的单个节点,能够预测问题的外圈为 0,内圈为 1。
该模型将使用随机梯度下降进行训练,学习率为 0.01,动量为 0.9,优化将使用二元交叉熵损失函数进行。
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
一旦定义,模型就可以适合训练数据集。
我们将使用保持测试数据集作为验证数据集,并在每个训练周期结束时评估其表现。该模型将适合 100 个时代,经过一点点尝试和错误选择。
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
在运行结束时,在列车和测试数据集上评估模型,并报告准确率。
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
最后,创建线图,显示每个训练时期结束时列车和测试集的模型准确率,提供学习曲线。
这个学习曲线图很有用,因为它给出了模型学习问题的速度和效果的概念。
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()
将这些元素结合在一起,下面列出了完整的示例。
# mlp for the two circles problem
from sklearn.datasets import make_circles
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import SGD
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='sigmoid'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()
运行该示例符合模型,并在列车和测试集上对其进行评估。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到该模型在保持数据集上实现了大约 84%的准确率,并且在给定两个数据集的相同大小和相似组成的情况下,在训练集和测试集上都实现了相当的表现。
Train: 0.838, Test: 0.846
创建一个图表,显示列车(蓝色)和测试(橙色)数据集上分类准确率的线图。
该图显示了训练过程中模型在两个数据集上的可比表现。我们可以看到,在最初的 30 到 40 个时期,表现跃升至 80%以上,然后准确率慢慢提高。
训练和测试数据集上训练时期的 MLP 分类准确率线图
这个结果,特别是训练过程中模型的动态,提供了一个基线,可以与添加批处理规范化的相同模型进行比较。
批量标准化的 MLP
上一节中介绍的模型可以更新,以添加批处理规范化。
预期批量标准化的增加将加速训练过程,在更少的训练时期提供相似或更好的模型分类准确率。据报道,批处理规范化还提供了一种适度的规范化形式,这意味着它还可以通过保持测试数据集上分类准确率的小幅提高来提供泛化误差的小幅降低。
在输出层之前的隐藏层之后,可以向模型添加新的 BatchNormalization 层。具体来说,在前一隐藏层的激活功能之后。
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Dense(1, activation='sigmoid'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
下面列出了这种修改的完整示例。
# mlp for the two circles problem with batchnorm after activation function
from sklearn.datasets import make_circles
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import BatchNormalization
from keras.optimizers import SGD
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, activation='relu', kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Dense(1, activation='sigmoid'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()
运行该示例首先打印模型在训练和测试数据集上的分类准确率。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到模型在列车和测试集上的可比表现,准确率约为 84%,与我们在上一节中看到的非常相似,如果不是更好一点的话。
Train: 0.846, Test: 0.848
还创建了学习曲线的图,示出了每个训练时期的训练集和测试集的分类准确率。
在这种情况下,我们可以看到,在没有批处理规范化的情况下,模型比上一节中的模型学习问题的速度更快。具体来说,我们可以看到,在前 20 个时期内,训练和测试数据集上的分类准确率跃升至 80%以上,而在没有批量标准化的模型中,这一比例为 30-40 个时期。
该图还显示了训练期间批次标准化的效果。我们可以看到训练数据集的表现低于测试数据集:在训练运行结束时,训练数据集的得分低于模型的表现。这可能是每个小批量收集和更新的输入的效果。
训练和测试数据集上激活函数后批量归一化的 MLP 线路图分类准确率
我们也可以尝试模型的一种变体,在隐藏层的激活函数之前应用批处理规范化,而不是在激活函数之后。
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dense(1, activation='sigmoid'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
下面列出了对模型进行此更改的完整代码列表。
# mlp for the two circles problem with batchnorm before activation function
from sklearn.datasets import make_circles
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Activation
from keras.layers import BatchNormalization
from keras.optimizers import SGD
from matplotlib import pyplot
# generate 2d classification dataset
X, y = make_circles(n_samples=1000, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, kernel_initializer='he_uniform'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dense(1, activation='sigmoid'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
_, train_acc = model.evaluate(trainX, trainy, verbose=0)
_, test_acc = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_acc, test_acc))
# plot history
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()
运行该示例首先打印模型在训练和测试数据集上的分类准确率。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到该模型在训练和测试数据集上的表现相当,但比没有批处理归一化的模型稍差。
Train: 0.826, Test: 0.830
火车和测试集上学习曲线的线图也讲述了一个不同的故事。
该图显示,模型学习的速度可能与没有批处理标准化的模型相同,但模型在训练数据集上的表现要差得多,准确率徘徊在 70%到 75%左右,这很可能是收集和使用的统计数据对每个小批处理的影响。
至少对于这个特定数据集上的这个模型配置,在校正线性激活函数之后,批处理规范化似乎更有效。
训练和测试数据集上激活函数前批量归一化的 MLP 线路图分类准确率
扩展ˌ扩张
本节列出了一些您可能希望探索的扩展教程的想法。
- 无β和γ。更新示例,使其不使用批处理标准化层中的 beta 和 gamma 参数,并比较结果。
- 无动量。更新示例,以便在训练和比较结果期间不在批处理规范化层中使用动量。
- 输入层。更新示例,以便在模型输入后使用批处理规范化并比较结果。
如果你探索这些扩展,我很想知道。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
应用程序接口
文章
- Keras 批量归一化层断裂,Vasilis Vryniotis ,2018。
- 【ReLU 之前还是之后批量归一化?,Reddit 。
- 激活功能前后批次标准化的研究。
摘要
在本教程中,您发现了如何使用批处理规范化来加速使用 Keras 在 Python 中训练深度学习神经网络。
具体来说,您了解到:
- 如何使用 Keras API 创建和配置 BatchNormalization 层。
- 如何在深度学习神经网络模型中加入 BatchNormalization 层?
- 如何更新 MLP 模型以使用批处理规范化来加速二分类问题的训练。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
如何避免梯度裁剪带来的梯度爆炸
最后更新于 2020 年 8 月 28 日
给定误差函数、学习率甚至目标变量的规模的选择,训练神经网络会变得不稳定。
训练期间权重的大量更新会导致数值溢出或下溢,通常称为“梯度爆炸”
梯度爆炸的问题在递归神经网络中更常见,例如给定在数百个输入时间步长上展开的梯度累积的 LSTMs。
梯度爆炸问题的一个常见且相对简单的解决方案是,在通过网络反向传播误差并使用它来更新权重之前,改变误差的导数。两种方法包括给定选定的向量范数重新缩放梯度,以及剪切超过优选范围的梯度值。这些方法统称为“梯度裁剪”
在本教程中,您将发现梯度爆炸问题,以及如何使用梯度裁剪来提高神经网络训练的稳定性。
完成本教程后,您将知道:
- 训练神经网络会变得不稳定,导致被称为梯度爆炸的数值上溢或下溢。
- 通过缩放向量范数或者将梯度值裁剪到一定范围来改变误差梯度,可以使训练过程稳定。
- 如何使用梯度裁剪方法更新具有梯度爆炸的回归预测建模问题的 MLP 模型,以获得稳定的训练过程。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
如何用梯度裁剪避免神经网络中的梯度爆炸 图片由伊恩·利弗西提供,版权所有。
教程概述
本教程分为六个部分;它们是:
- 分解梯度和剪辑
- Keras 的梯度裁剪
- 回归预测建模问题
- 具有梯度爆炸的多层感知器
- 具有梯度范数标度的 MLP
- 具有梯度值削波的 MLP
分解梯度和剪辑
使用随机梯度下降优化算法训练神经网络。
这首先需要对一个或多个训练示例的损失进行估计,然后计算损失的导数,该导数通过网络向后传播,以便更新权重。使用由学习率控制的反向传播误差的一部分来更新权重。
权重的更新可能太大,以至于权重要么溢出,要么下溢其数值准确率。在实践中,当权重上溢或下溢时,权重可以采用“ NaN ”或“ Inf ”的值,并且出于实际目的,网络从该点开始将是无用的,当信号流过无效权重时,永远预测 NaN 值。
出现的困难是,当参数梯度非常大时,梯度下降参数更新可能会将参数抛得很远,进入目标函数更大的区域,从而撤销为达到当前解所做的大量工作。
—第 413 页,深度学习,2016。
权重的下溢或上溢通常被称为网络训练过程的不稳定性,并被称为“梯度爆炸”,因为不稳定的训练过程导致网络无法以模型基本无用的方式进行训练。
在给定的神经网络(如卷积神经网络或多层感知器)中,这可能是由于配置选择不当造成的。一些例子包括:
- 学习率选择不当,导致权重更新过大。
- 数据准备选择不当,导致目标变量差异较大。
- 损失函数选择不当,允许计算较大的误差值。
考虑到在展开的递归结构中误差梯度的累积,在诸如长短期记忆网络的递归神经网络中,梯度爆炸也是一个问题。
一般来说,通过仔细配置网络模型,例如选择小的学习率、缩放的目标变量和标准损失函数,可以避免梯度爆炸。然而,对于具有大量输入时间步长的递归网络,梯度爆炸可能仍然是一个问题。
用全梯度训练 LSTM 的一个困难是导数有时变得过大,导致数值问题。为了防止这种情况,[我们]将损耗相对于 LSTM 层网络输入的导数(在应用 sigmoid 和 tanh 函数之前)限制在预定范围内。
——用递归神经网络生成序列,2013。
分解梯度的一个常见解决方案是在通过网络反向传播误差导数并使用它更新权重之前改变它。通过重新调整误差导数,权重的更新也将被重新调整,显著降低上溢或下溢的可能性。
更新误差导数有两种主要方法;它们是:
- 梯度缩放。
- 梯度剪辑。
梯度缩放包括归一化误差梯度向量,使得向量范数(幅度)等于定义的值,例如 1.0。
……处理梯度范数突然增加的一个简单机制是,每当它们超过阈值时,重新调整它们的比例
——关于递归神经网络的训练难度,2013。
如果梯度超出了预期范围,梯度裁剪会强制梯度值(按元素)达到特定的最小值或最大值。
这些方法合在一起,通常简称为“梯度裁剪”
当传统的梯度下降算法提出进行非常大的步长时,梯度裁剪启发式介入以将步长减小到足够小,使得它不太可能超出梯度指示近似最陡下降方向的区域。
—第 289 页,深度学习,2016。
这种方法只解决了训练深度神经网络模型的数值稳定性问题,并没有提供任何表现上的全面改善。
梯度向量范数或优选范围的值可以通过反复试验、使用文献中使用的公共值或通过首先通过实验观察公共向量范数或范围,然后选择合理的值来配置。
在我们的实验中,我们注意到,对于给定的任务和模型大小,训练对这个[梯度范数]超参数不是很敏感,并且即使对于相当小的阈值,算法也表现良好。
——关于递归神经网络的训练难度,2013。
网络中的所有层通常使用相同的梯度裁剪配置。然而,与隐藏层相比,输出层允许更大范围的误差梯度。
输出导数[…]被限幅在范围[-100,100]内,LSTM 导数被限幅在范围[-10,10]内。削波输出梯度被证明对数值稳定性至关重要;即便如此,在训练后期,当网络开始过拟合训练数据后,有时也会出现数字问题。
——用递归神经网络生成序列,2013。
Keras 的梯度裁剪
Keras 支持对每个优化算法进行梯度裁剪,对模型中的所有层应用相同的方案
通过在配置优化算法时包含一个附加参数,梯度裁剪可以与优化算法一起使用,例如随机梯度下降。
可以使用两种类型的梯度裁剪:梯度范数缩放和梯度值裁剪。
梯度范数标度
梯度范数缩放涉及当梯度向量的 L2 向量范数(平方值的和)超过阈值时,改变损失函数的导数以具有给定的向量范数。
例如,我们可以指定一个 1.0 的范数,这意味着如果一个梯度的向量范数超过 1.0,那么向量中的值将被重新缩放,使得向量的范数等于 1.0。
这可以在 Keras 中通过在优化器上指定“ clipnorm ”参数来使用;例如:
....
# configure sgd with gradient norm clipping
opt = SGD(lr=0.01, momentum=0.9, clipnorm=1.0)
梯度值削波
如果梯度值小于负阈值或大于正阈值,梯度值削波包括将损失函数的导数削波为给定值。
例如,我们可以指定 0.5 的范数,这意味着如果梯度值小于-0.5,它将被设置为-0.5,如果它大于 0.5,那么它将被设置为 0.5。
这可以在 Keras 中通过在优化器上指定“clipvalue”参数来使用,例如:
...
# configure sgd with gradient value clipping
opt = SGD(lr=0.01, momentum=0.9, clipvalue=0.5)
回归预测建模问题
回归预测建模问题涉及预测实值量。
我们可以在make _ revolution()函数中使用 Sklearn 库提供的标准回归问题生成器。该函数将从具有给定数量的输入变量、统计噪声和其他属性的简单回归问题中生成示例。
我们将使用这个函数来定义一个有 20 个输入特征的问题;其中 10 个功能将是有意义的,10 个将不相关。总共将随机生成 1000 个示例。伪随机数发生器将被固定,以确保我们每次运行代码时都能得到相同的 1000 个例子。
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
每个输入变量都具有高斯分布,目标变量也是如此。
我们可以创建显示分布和扩散的目标变量图。下面列出了完整的示例。
# regression predictive modeling problem
from sklearn.datasets import make_regression
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# histogram of target variable
pyplot.subplot(121)
pyplot.hist(y)
# boxplot of target variable
pyplot.subplot(122)
pyplot.boxplot(y)
pyplot.show()
运行该示例会创建一个图形,该图形有两个曲线图,分别显示目标变量的直方图和方框图。
直方图显示目标变量的高斯分布。方框图和晶须图显示样品的范围在约-400 至 400 之间变化,平均值约为 0.0。
回归问题目标变量的直方图和盒须图
具有梯度爆炸的多层感知器
我们可以为回归问题开发一个多层感知器(MLP)模型。
将在原始数据上演示一个模型,不需要对输入或输出变量进行任何缩放。这是一个很好的例子来演示梯度爆炸,因为被训练来预测未缩放目标变量的模型将导致误差梯度,其值在数百甚至数千,这取决于训练期间使用的批次大小。如此大的梯度值很可能导致学习不稳定或权重值溢出。
第一步是将数据分成训练集和测试集,这样我们就可以拟合和评估模型。我们将从域中生成 1,000 个示例,并将数据集分成两半,使用 500 个示例作为训练集和测试集。
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
接下来,我们可以定义一个 MLP 模型。
该模型将在问题的 20 个输入变量中有 20 个输入。单个隐藏层将使用 25 个节点和一个校正的线性激活函数。输出层有一个单目标变量节点和一个线性激活函数来直接预测真实值。
# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
均方误差损失函数将用于优化模型,随机梯度下降优化算法将用于学习率为 0.01、动量为 0.9 的合理默认配置。
# compile model
model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))
该模型将适用于 100 个训练时期,测试集将用作验证集,在每个训练时期结束时进行评估。
训练结束时,在训练和测试数据集上计算均方误差,以了解模型学习问题的程度。
# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
最后,在每个训练周期结束时,训练集和测试集的均方误差的学习曲线用线图表示,在学习问题的同时,提供学习曲线以获得模型的动力学概念。
# plot loss during training
pyplot.title('Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()
将这些元素结合在一起,下面列出了完整的示例。
# mlp with unscaled data for the regression problem
from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
# compile model
model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))
# plot loss during training
pyplot.title('Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()
运行该示例符合模型,并计算列车和测试集的均方误差。
在这种情况下,模型无法学习问题,导致对 NaN 值的预测。给定非常大的误差,模型权重在训练期间爆炸,并且反过来为权重更新计算误差梯度。
Train: nan, Test: nan
这表明需要对模型的目标变量进行一些干预来学习这个问题。
创建训练历史的线图,但不显示任何内容,因为模型几乎立即导致 NaN 均方误差。
一个传统的解决方案是使用标准化或规范化来重新调整目标变量,这种方法被推荐用于 MLP。然而,在这种情况下,我们将研究的另一种方法是使用梯度裁剪。
具有梯度范数标度的 MLP
我们可以更新上一节中模型的训练,添加梯度范数缩放。
这可以通过在优化器上设置“ clipnorm ”参数来实现。
例如,可以将梯度重新缩放为向量范数(大小或长度)为 1.0,如下所示:
# compile model
opt = SGD(lr=0.01, momentum=0.9, clipnorm=1.0)
model.compile(loss='mean_squared_error', optimizer=opt)
下面列出了此更改的完整示例。
# mlp with unscaled data for the regression problem with gradient norm scaling
from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
# compile model
opt = SGD(lr=0.01, momentum=0.9, clipnorm=1.0)
model.compile(loss='mean_squared_error', optimizer=opt)
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))
# plot loss during training
pyplot.title('Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()
运行该示例符合模型,并在列车和测试集上对其进行评估,打印均方误差。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到,用 1.0 的向量范数缩放梯度已经产生了能够学习问题并收敛到解决方案的稳定模型。
Train: 5.082, Test: 27.433
还创建了一个线图,显示训练和测试数据集在训练时期的均方误差损失。
该图显示了在 20 个时期内,损失是如何从 20,000 以上的大值迅速下降到 100 以下的小值的。
具有梯度范数缩放的训练时期内训练(蓝色)和测试(橙色)数据集的均方误差损失线图
向量范数值 1.0 没有什么特别之处,可以评估其他值,并比较所得模型的表现。
具有梯度值削波的 MLP
爆炸梯度问题的另一个解决方案是在梯度变得太大或太小时对其进行裁剪。
我们可以通过将“ clipvalue ”参数添加到优化算法配置中来更新 MLP 的训练以使用梯度裁剪。例如,下面的代码将梯度剪辑到[-5 到 5]的范围。
# compile model
opt = SGD(lr=0.01, momentum=0.9, clipvalue=5.0)
model.compile(loss='mean_squared_error', optimizer=opt)
下面列出了使用梯度剪辑训练 MLP 的完整示例。
# mlp with unscaled data for the regression problem with gradient clipping
from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
# compile model
opt = SGD(lr=0.01, momentum=0.9, clipvalue=5.0)
model.compile(loss='mean_squared_error', optimizer=opt)
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))
# plot loss during training
pyplot.title('Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()
运行此示例符合模型,并在列车和测试集上对其进行评估,打印均方误差。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
我们可以看到,在这种情况下,模型能够在不分解梯度的情况下学习问题,在训练集和测试集中均达到小于 10 的均方误差。
Train: 9.487, Test: 9.985
还创建了一个线图,显示训练和测试数据集在训练时期的均方误差损失。
该图显示,该模型学习问题的速度很快,仅在几个训练时期内就实现了亚 100 毫秒的损失。
具有梯度值削波的训练时期内训练(蓝色)和测试(橙色)数据集的均方误差损失线图
任意选择了[-5,5]的剪裁范围;您可以尝试不同大小的范围,并比较学习速度和最终模型表现的表现。
扩展ˌ扩张
本节列出了一些您可能希望探索的扩展教程的想法。
- 向量范数值。更新示例以评估不同的梯度向量范数值并比较表现。
- 矢量剪辑值。更新示例以评估不同的梯度值范围并比较表现。
- 向量范数和剪辑。更新示例,在同一训练运行中使用向量范数缩放和向量值裁剪的组合,并比较表现。
如果你探索这些扩展,我很想知道。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
邮件
书
报纸
- 关于递归神经网络的训练难度,2013。
- 用递归神经网络生成序列,2013。
应用程序接口
摘要
在本教程中,您发现了梯度爆炸问题,以及如何使用梯度裁剪来提高神经网络训练的稳定性。
具体来说,您了解到:
- 训练神经网络会变得不稳定,导致被称为梯度爆炸的数值上溢或下溢。
- 通过改变误差梯度,可以使训练过程稳定,或者通过缩放向量范数,或者将梯度值裁剪到一定范围。
- 如何使用梯度裁剪方法更新具有梯度爆炸的回归预测建模问题的 MLP 模型,以获得稳定的训练过程。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。