深度学习神经网络从业者推荐
原文:https://machinelearningmastery.com/recommendations-for-deep-learning-neural-network-practitioners/
最后更新于 2019 年 8 月 6 日
鉴于开源库的广泛采用,深度学习神经网络的定义和训练相对简单。
然而,神经网络的配置和训练仍然具有挑战性。
在他 2012 年发表的题为《基于梯度的深度架构训练实用建议》》的论文中,作为预印本和 2012 年流行书籍《*神经网络:交易技巧》*的一章,“深度学习领域之父”之一的 Yoshua Bengio 提供了配置和调整神经网络模型的实用建议。
在这篇文章中,你将浏览这篇长而有趣的论文,并为现代深度学习实践者挑选出最相关的技巧和诀窍。
看完这篇文章,你会知道:
- 深度学习复兴的早期基础包括预处理和自动编码器。
- 神经网络超参数范围的初始配置建议。
- 如何有效地调整神经网络超参数以及更有效地调整模型的策略。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
为深度学习神经网络从业者提供的实用建议
图片由 Susanne Nilsson 提供,保留部分权利。
概观
本教程分为五个部分;它们是:
- 从业者必读
- 论文概述
- 深度学习的开始
- 通过梯度下降学习
- 超参数建议
对从业者的建议
2012 年,流行实用书籍《神经网络:交易技巧》第二版出版。
第一版于 1999 年出版,包含 17 章(每一章都由不同的学者和专家撰写),内容是如何最大限度地利用神经网络模型。更新后的第二版增加了 13 章,其中包括由 Yoshua Bengio 撰写的一个重要章节(第十九章),标题为“基于梯度的深度架构培训实用建议”
第二版出版的时间是对神经网络重新产生兴趣的重要时间,也是已经成为“深度学习”的开始 Yoshua Bengio 的章节很重要,因为它为开发神经网络模型提供了建议,包括当时非常现代的深度学习方法的细节。
虽然这一章可以作为第二版的一部分阅读,但本吉奥还在 arXiv 网站上发布了这一章的预印本,可在此访问:
- 深度架构基于梯度的训练实用建议,预印本,2012。
这一章也很重要,因为它为四年后成为事实上的深度学习教科书提供了一个宝贵的基础,书名很简单,叫做《深度学习》,本吉奥是该书的合著者。
这一章(我将从现在开始称之为论文)是所有神经网络从业者的必读。
在这篇文章中,我们将逐步浏览论文的每一部分,并指出一些最突出的建议。
论文概述
论文的目标是为从业者提供开发神经网络模型的实用建议。
有许多类型的神经网络模型和许多类型的从业者,因此目标是广泛的,并且建议不特定于给定类型的神经网络或预测建模问题。这很好,因为我们可以在我们的项目中自由地应用这些建议,但也令人沮丧,因为没有给出文献或案例研究中的具体例子。
这些建议的重点是模型超参数的配置,特别是与随机梯度下降学习算法相关的超参数。
本章旨在作为一个实用指南,为一些最常用的超参数提供建议,特别是在基于反向传播梯度和基于梯度的优化的学习算法的背景下。
这些建议是在深度学习领域出现的背景下提出的,在这个领域,现代方法和快速的图形处理器硬件促进了网络的发展,其深度和能力都超过了以前。Bengio 将这种复兴追溯到 2006 年(在撰写本文之前的六年)和开发贪婪逐层预训练方法,后来(在撰写本文之后)被大量使用 ReLU、Dropout、BatchNorm 和其他有助于开发深度模型的方法所取代。
2006 年的深度学习突破集中在使用无监督学习,通过在特征层次的每一层提供局部训练信号来帮助学习内部表示。
本文分为六个主要部分,第三部分提供了关于配置超参数的建议的主要阅读重点。论文的完整目录如下。
- 摘要
- 1 导言
- 1.1 深度学习和贪婪逐层预处理
- 1.2 去噪和压缩自动编码器
- 1.3 在线学习和泛化误差优化
- 2 个梯度
- 2.1 梯度下降和学习率
- 2.2 梯度计算和自动微分
- 3 个超级参数
- 3.1 神经网络超参数
- 3.1.1 近似优化的超参数
- 3.2 模型的超参数和训练准则
- 3.3 手动搜索和网格搜索
- 3.3.1 探索超参数的一般指南
- 3.3.2 坐标下降和多分辨率搜索
- 3.3.3 自动和半自动网格搜索
- 3.3.4 超参数的逐层优化
- 3.4 超参数的随机采样
- 3.1 神经网络超参数
- 4 调试和分析
- 4.1 坡度检查和受控过拟合
- 4.2 可视化和统计
- 5 其他建议
- 5.1 多核机器、BLAS 和图形处理器
- 5.2 稀疏高维输入
- 5.3 符号变量、嵌入、多任务学习和多关系学习
- 6 个开放式问题
- 6.1 培训更深层架构的额外难度
- 6.2 自适应学习率和二阶方法
- 6.3 结论
我们将不涉及每一部分,而是将重点放在论文的开头,特别是关于超参数和模型调整的建议。
深度学习的开始
引言部分花了一些时间来介绍深度学习的开始,如果把它看作是该领域的历史快照,那将会非常有趣。
当时,深度学习复兴是由神经网络模型的发展推动的,该模型的层数比以前基于贪婪逐层预处理和通过自动编码器进行表示学习等技术使用的层数多得多。
训练深度神经网络最常用的方法之一是基于贪婪逐层预训练。
这种方法不仅很重要,因为它允许开发更深层次的模型,而且无监督的形式允许使用无标记的例子,例如半监督学习,这也是一个突破。
特征学习和深度学习的另一个重要动机是它们可以用未标记的例子来完成…
因此,重用(字面上的重用)是一个主要的主题。
重用的概念解释了分布式表示的力量,也是深度学习背后理论优势的核心。
尽管理论上可以证明一个具有足够容量的单层或两层神经网络可以逼近任何函数,但他温和地提醒人们,深层网络为逼近更复杂的函数提供了一条计算捷径。这是一个重要的提醒,有助于推动深度模型的发展。
理论结果清楚地确定了函数族,在这些函数族中,深度表示可能比深度不够的函数更有效率。
时间花在了两个主要的深度学习突破上:贪婪逐层预训练(有监督和无监督)和自动编码器(去噪和对比)。
第三个突破,成果管理制被留在了本书的另一章中讨论,该章由该方法的开发者辛顿撰写。
- 受限玻尔兹曼机(RBM)。
- 贪婪逐层预处理(无监督和有监督)。
- 自动编码器(去噪和对比)。
尽管具有里程碑意义,但在深度学习的发展过程中,这些技术没有一种是首选的,也没有一种被广泛使用,或许除了自动编码器之外,没有一种技术像以前一样得到了大力研究。
通过梯度下降学习
第二节提供了梯度和梯度学习算法的基础,梯度和梯度学习算法是用于将神经网络权重拟合到训练数据集的主要优化技术。
这包括批量梯度下降和随机梯度下降之间的重要区别,以及通过小批量梯度下降的近似,今天都简称为随机梯度下降。
- 批量梯度下降。使用训练数据集中的所有示例来估计梯度。
- 小批量梯度下降。使用训练数据集中的样本子集来估计梯度。
- 随机(在线)梯度下降。使用训练数据集中的每个单一模式来估计梯度。
小批量变量被提供作为一种方法来实现由随机梯度下降提供的收敛速度和由批量梯度下降提供的误差梯度的改进估计。
批量越大,收敛速度越慢。
另一方面,随着 B[批处理大小]的增加,每次计算完成的更新数量减少,这减慢了收敛速度(就误差与执行的乘加操作数量而言),因为在相同的计算时间内可以完成的更新更少。
由于在梯度估计中引入了统计噪声,较小的批量提供了规则化效果。
……较小的 B 值[批次大小]可能受益于参数空间中的更多探索和一种正则化形式,这两者都是由于梯度估计器中注入的“噪声”,这可以解释有时用较小的 B 观察到的更好的测试结果
这一次也是自动微分在神经网络模型开发中的引入和广泛采用。
梯度可以手动计算,也可以通过自动微分计算。
本吉奥对此特别感兴趣,因为他参与了开发现在已不存在的“安托”Python 数学库“T1”和“T2”派尔恩 2 深度学习库“T3”,或许分别由“T4”TensorFlow“T5”和“T6”Keras“T7”取得了成功。
手动实现神经网络的微分很容易出错,并且错误很难调试并导致次优表现。
当用手动微分实现梯度下降算法时,结果往往是冗长、脆弱的代码,缺乏模块化——所有这些对软件工程来说都是坏事。
自动微分被描绘成一种更稳健的方法,将神经网络开发为数学运算的图形,每个运算都知道如何微分,这可以象征性地定义。
一种更好的方法是用对象来表示流程图,这些对象模块化了如何从输入计算输出以及如何计算梯度下降所需的偏导数。
基于图的方法定义模型的灵活性和计算误差导数时误差可能性的降低意味着这种方法已经成为现代开源神经网络库的标准,至少在底层数学库中是如此。
超参数建议
本文的重点是在随机梯度下降下控制模型收敛和推广的超参数的配置。
使用验证数据集
本节从使用来自训练集和测试集的单独验证数据集来调整模型超参数的重要性开始。
对于任何影响学习器有效能力的超参数,基于样本外数据(在训练集之外)选择其值更有意义,例如验证集表现、在线错误或交叉验证错误。
以及在模型表现评估中不包括验证数据集的重要性。
一旦一些样本外数据被用于选择超参数值,它就不能再用于获得泛化表现的无偏估计量,因此人们通常使用测试集(或者在小数据集的情况下使用双重交叉验证)来估计纯学习算法的泛化误差(超参数选择隐藏在内部)。
交叉验证通常不用于神经网络模型,因为它们可能需要几天、几周甚至几个月的时间来训练。然而,在可以使用交叉验证的较小数据集上,建议使用双重交叉验证技术,其中在每个交叉验证文件夹内执行超参数调整。
双重交叉验证递归地应用交叉验证的思想,使用外部循环交叉验证来评估泛化误差,然后在每个外部循环拆分的训练子集中应用内部循环交叉验证(即,再次将其拆分为训练和验证折叠),以便为该拆分选择超参数。
学习超参数
然后介绍一套学习超参数,并附带一些建议。
套件中的超参数有:
- 初始学习率。权重更新的比例;0.01 是一个好的开始。
- 学习状态计划表。随着时间的推移,学习率下降;1/T 是一个好的开始。
- 小批量。用于估计梯度的样本数量;32 岁是一个好的开始。
- 训练迭代。权重的更新次数;设置大并使用提前停止。
- 动量。使用以前重量更新的历史记录;设置为大(例如 0.9)。
- 层特定超参数。可能,但很少做到。
学习率是最重要的调节参数。尽管 0.01 的值是推荐的起点,但对于特定的数据集和模型,需要拨入该值。
这通常是最重要的一个超参数,人们应该始终确保它已经被调优[……]默认值 0.01 通常适用于标准的多层神经网络,但是仅仅依赖这个默认值是愚蠢的。
他甚至说,如果只有一个参数可以调整,那就是学习率。
如果只有时间来优化一个超参数,并且使用随机梯度下降,那么这就是值得调整的超参数。
批处理大小是作为对学习速度的控制来呈现的,而不是关于调整测试集表现(泛化误差)。
理论上,这个超参数应该影响训练时间,而不是太多的测试表现,因此可以在选择了其他超参数(除了学习率)之后,通过比较训练曲线(训练和验证误差对训练时间量),与其他超参数分开优化。
模型超参数
然后引入模型超参数,再加上一些建议。
它们是:
- 节点数。控制模型的容量;使用更大的正则化模型。
- 权重正则化。处罚权重大的车型;一般来说,试试 L2 或 L1 的稀疏性。
- 活动正规化。对大型激活的模型进行处罚;试试 L1 的稀疏表示。
- 激活功能。用作隐藏层中节点的输出;使用乙状结肠函数(逻辑和唐)或整流器(现在的标准)。
- 重量初始化。优化过程的起点;受前一层的激活功能和尺寸的影响。
- 随机种子。优化过程的随机性质;多次运行的平均模型。
- 预处理。建模前准备数据;至少标准化和消除相关性。
配置一个层中的节点数量很有挑战性,可能也是初学者问得最多的问题之一。他建议,在每个隐藏层中使用相同数量的节点可能是一个很好的起点。
在一项大型比较研究中,我们发现对所有层使用相同的大小通常比使用减小的大小(金字塔状)或增加的大小(倒置的金字塔)效果更好或相同,但当然这可能取决于数据。
他还建议对第一个隐藏层使用过完备配置。
对于我们所处理的大多数任务,我们发现过完备(大于输入向量)的第一隐藏层比欠完备层效果更好。
给定对分层训练和自动编码器的关注,表示的稀疏性(隐藏层的输出)是当时的焦点。因此建议使用活动正则化,这在较大的编码器-解码器模型中可能仍然有用。
稀疏表示可能是有利的,因为它们促进了解开表示的潜在因素的表示。
当时,线性整流器激活功能刚刚开始使用,还没有被广泛采用。今天,使用整流器(ReLU)是标准,因为使用它的模型很容易胜过使用逻辑或双曲正切非线性的模型。
调整超参数
默认配置在大多数问题上对大多数神经网络都很有效。
然而,需要对超参数进行调整,以便在给定数据集上充分利用给定模型。
调整超参数可能具有挑战性,这既是因为需要计算资源,也是因为可能很容易过度填充验证数据集,从而导致误导性的结果。
人们不得不认为超参数选择是一种困难的学习形式:既有优化问题(寻找产生低验证误差的超参数配置)又有泛化问题:优化验证表现后,预期的泛化存在不确定性,在比较许多超参数配置时,有可能过度估计验证误差并获得表现的乐观偏差估计。
为模型调整一个超参数并绘制结果通常会产生一个 U 形曲线,显示表现差、表现好以及表现差的模式(例如,将损失或误差降至最低)。目标是找到“ U 的底部。”
问题是,许多超参数相互作用,并且“*U”*的底部可能会有噪声。
尽管对于第一近似,我们期望一种 U 形曲线(当仅考虑单个超参数时,其他参数是固定的),但该曲线也可能有噪声变化,部分原因是使用了有限的数据集。
为了帮助这一搜索,他提供了三个有价值的提示,在调整模型超参数时通常要考虑这些提示:
- 边界上的最佳值。如果在搜索的区间边缘找到一个好的值,可以考虑扩大搜索范围。
- 考虑的数值范围。考虑在对数标度上搜索,至少在开始时(例如 0.1、0.01、0.001 等)。).
- 计算考虑因素。考虑放弃结果的保真度以加速搜索。
提出了三种系统的超参数搜索策略:
- 坐标下降。每次拨入一个超参数。
- 多分辨率搜索。反复放大搜索间隔。
- 网格搜索。定义一个 n 维的值网格,并依次测试每个值。
这些策略可以单独使用,甚至可以组合使用。
网格搜索可能是最常被理解和广泛使用的模型超参数调整方法。它是详尽的,但可并行化的,这是一个可以利用廉价云计算基础设施的优势。
与许多其他优化策略(如坐标下降)相比,网格搜索的优势在于完全可并行化。
通常,通过迭代网格搜索,结合多分辨率和网格搜索,重复该过程。
通常,单个网格搜索是不够的,从业者倾向于继续进行一系列网格搜索,每次都根据先前获得的结果调整所考虑的值的范围。
他还建议保持一个人在循环中,以密切关注 bug,并使用模式识别来识别趋势和改变搜索空间的形状。
人类可以非常擅长执行超参数搜索,有人类在循环中也有优势,它可以帮助检测学习算法的 bug 或不想要的或意想不到的行为。
然而,重要的是尽可能地自动化,以确保该过程对于未来的新问题和模型是可重复的。
网格搜索是详尽而缓慢的。
寻找好的超参数配置的网格搜索方法的一个严重问题是,它与所考虑的超参数的数量成指数关系。
他建议使用随机采样策略,这已被证明是有效的。每个超参数的区间可以统一搜索。这种分布可能会因包含先验而有所偏差,例如选择合理的违约。
随机采样的思想是用随机(通常是均匀的)采样代替规则网格。通过从先前分布(通常在感兴趣的区间内的对数域中是均匀的)中独立地采样每个超参数来选择每个测试的超参数配置。
文章最后给出了一些更一般性的建议,包括调试学习过程的技术、加速 GPU 硬件的训练以及剩余的未决问题。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
- 神经网络:交易技巧:交易技巧,第一版,1999 年。
- 神经网络:交易的诀窍:交易的诀窍,第二版,2012 年。
- 深度架构基于梯度的训练实用建议,预印本,2012。
- 深度学习,2016 年。
- 自动分化,维基百科。
摘要
在这篇文章中,您发现了 Yoshua Bengio 在 2012 年发表的题为“基于梯度的深度体系结构训练的实用建议”的论文中的突出建议、技巧和诀窍
*你读过这篇论文吗?你有什么想法?
在下面的评论里告诉我。
你有什么问题吗?
在下面的评论中提问,我会尽力回答。*
整流线性单元的温和介绍
最后更新于 2020 年 8 月 20 日
在神经网络中,激活函数负责将来自节点的加权总输入转换为该节点的激活或该输入的输出。
整流线性激活函数或 ReLU 简称为分段线性函数,为正则直接输出输入,否则输出零。它已经成为许多类型神经网络的默认激活函数,因为使用它的模型更容易训练,并且通常会获得更好的表现。
在本教程中,您将发现深度学习神经网络的校正线性激活函数。
完成本教程后,您将知道:
- 由于梯度消失问题,sigmoid 和双曲正切激活函数不能用于具有许多层的网络。
- 修正后的线性激活函数克服了梯度消失问题,使模型学习更快,表现更好。
- 修正后的线性激活是开发多层感知器和卷积神经网络时的默认激活。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2019 年 6 月:修正了 he 权重初始化公式的错误(感谢 Maltev)。
深度学习神经网络校正线性激活函数简介
图片由土地管理局提供,版权所有。
教程概述
本教程分为六个部分;它们是:
- Sigmoid 和 Tanh 激活函数的局限性
- 整流器线性激活函数
- 如何实现整流器线性激活功能
- 整流器线性激活的优点
- 整流器线性激活的使用技巧
- ReLU 的扩展和替代
Sigmoid 和 Tanh 激活函数的局限性
神经网络由多层节点组成,并学习将输入示例映射到输出。
对于给定的节点,输入与节点中的权重相乘并相加。该值被称为节点的总激活。然后,通过激活函数转换相加的激活,并定义节点的特定输出或“激活”。
最简单的激活函数称为线性激活,其中根本不应用任何变换。仅由线性激活函数组成的网络非常容易训练,但是不能学习复杂的映射函数。线性激活函数仍然用于预测数量的网络的输出层(例如回归问题)。
非线性激活函数是优选的,因为它们允许节点学习数据中更复杂的结构。传统上,两个广泛使用的非线性激活函数是 sigmoid 和双曲正切激活函数。
sigmoid 激活函数,也称为逻辑函数,传统上是非常流行的神经网络激活函数。函数的输入被转换为 0.0 到 1.0 之间的值。比 1.0 大得多的输入被转换为值 1.0,类似地,比 0.0 小得多的值被捕捉到 0.0。所有可能输入的函数形状都是从零到 0.5 到 1.0 的 S 形。在很长一段时间里,直到 20 世纪 90 年代初,这是神经网络上使用的默认激活。
双曲正切函数,简称 tanh,是一种类似形状的非线性激活函数,输出值介于-1.0 和 1.0 之间。在 20 世纪 90 年代后期和 21 世纪初,tanh 函数比 sigmoid 激活函数更受青睐,因为使用它的模型更容易训练,通常具有更好的预测表现。
…双曲正切激活函数的表现通常优于逻辑 sigmoid。
—第 195 页,深度学习,2016。
sigmoid 和 tanh 函数的一个普遍问题是它们饱和。这意味着,对于 tanh 和 sigmoid,大值会捕捉到 1.0,小值会捕捉到-1 或 0。此外,这些函数只对输入中点附近的变化非常敏感,例如 sigmoid 为 0.5,tanh 为 0.0。
不管作为输入提供的节点的总激活是否包含有用信息,函数的有限灵敏度和饱和都会发生。一旦饱和,学习算法继续调整权重以提高模型的表现就变得具有挑战性。
……sigmoxic 单位在其大部分域内饱和——当 z 非常正时饱和到高值,当 z 非常负时饱和到低值,并且仅当 z 接近 0 时对其输入非常敏感。
—第 195 页,深度学习,2016。
最后,随着硬件能力的提高,使用 sigmoid 和 tanh 激活函数的 GPU 深度神经网络不容易训练。
使用这些非线性激活函数的大型网络中的深层无法接收有用的梯度信息。误差通过网络反向传播,并用于更新权重。给定所选激活函数的导数,误差量随着传播通过的每个附加层而显著减少。这被称为梯度消失问题,阻碍了深层(多层)网络的有效学习。
梯度消失使得很难知道参数应该向哪个方向移动来改善成本函数
—第 290 页,深度学习,2016。
有关 ReLU 如何修复梯度消失问题的示例,请参见教程:
- 如何使用校正后的线性激活函数固定梯度消失
虽然非线性激活函数的使用允许神经网络学习复杂的映射函数,但它们有效地阻止了学习算法与深度网络一起工作。
在 2000 年代末和 2010 年代初,使用替代网络类型(如玻尔兹曼机器和分层训练或无监督预训练)找到了解决方法。
整流器线性激活函数
为了使用误差反向传播的随机梯度下降来训练深度神经网络,需要一个激活函数,它看起来和行为都像线性函数,但实际上是一个非线性函数,允许学习数据中的复杂关系。
该功能还必须对激活和输入提供更高的灵敏度,并避免容易饱和。
这一解决方案已经在该领域出现了一段时间,尽管直到 2009 年和 2011 年的论文对此有所提及。
解决方案是使用整流线性激活函数,简称 ReL。
实现该激活功能的节点或单元被称为整流线性激活单元,简称 ReLU。通常,对隐藏层使用整流功能的网络称为整流网络。
ReLU 的采用很容易被认为是深度学习革命中为数不多的里程碑之一,例如,现在允许非常深的神经网络的常规开发的技术。
[另一个]大大改善前馈网络表现的主要算法变化是用分段线性隐藏单元替换 sigmoid 隐藏单元,例如整流线性单元。
—第 226 页,深度学习,2016。
整流线性激活函数是一个简单的计算,它直接返回作为输入提供的值,如果输入为 0.0 或更小,则返回值 0.0。
我们可以用一个简单的 if 语句来描述这一点:
if input > 0:
return input
else:
return 0
我们可以使用 0.0 集合上的 max() 函数和输入 z 对该函数 g() 进行数学描述;例如:
g(z) = max{0, z}
对于大于零的值,该函数是线性的,这意味着当使用反向传播训练神经网络时,它具有线性激活函数的许多理想特性。然而,这是一个非线性函数,因为负值总是输出为零。
因为校正后的线性单位几乎是线性的,所以它们保留了许多属性,使得线性模型易于使用基于梯度的方法进行优化。它们还保留了许多使线性模型很好地推广的特性。
—第 175 页,深度学习,2016。
因为整流函数对于输入域的一半是线性的,而对于另一半是非线性的,所以它被称为分段线性函数或铰链函数。
然而,该函数仍然非常接近线性,即具有两个线性部分的分段线性函数。
—第 175 页,深度学习,2016。
现在我们已经熟悉了校正后的线性激活函数,让我们看看如何在 Python 中实现它。
整流器线性激活函数如何编码
我们可以在 Python 中轻松实现校正后的线性激活函数。
或许最简单的实现就是使用 max()函数;例如:
# rectified linear function
def rectified(x):
return max(0.0, x)
我们期望任何正值都不变地返回,而输入值 0.0 或负值将作为值 0.0 返回。
下面是整流线性激活函数的一些输入和输出示例。
# demonstrate the rectified linear function
# rectified linear function
def rectified(x):
return max(0.0, x)
# demonstrate with a positive input
x = 1.0
print('rectified(%.1f) is %.1f' % (x, rectified(x)))
x = 1000.0
print('rectified(%.1f) is %.1f' % (x, rectified(x)))
# demonstrate with a zero input
x = 0.0
print('rectified(%.1f) is %.1f' % (x, rectified(x)))
# demonstrate with a negative input
x = -1.0
print('rectified(%.1f) is %.1f' % (x, rectified(x)))
x = -1000.0
print('rectified(%.1f) is %.1f' % (x, rectified(x)))
运行该示例,我们可以看到,无论正值的大小如何,都会返回正值,而负值会被捕捉到值 0.0。
rectified(1.0) is 1.0
rectified(1000.0) is 1000.0
rectified(0.0) is 0.0
rectified(-1.0) is 0.0
rectified(-1000.0) is 0.0
通过绘制一系列输入和计算的输出,我们可以了解函数的输入和输出之间的关系。
下面的示例生成一系列从-10 到 10 的整数,并计算每个输入的校正线性激活,然后绘制结果。
# plot inputs and outputs
from matplotlib import pyplot
# rectified linear function
def rectified(x):
return max(0.0, x)
# define a series of inputs
series_in = [x for x in range(-10, 11)]
# calculate outputs for our inputs
series_out = [rectified(x) for x in series_in]
# line plot of raw inputs to rectified outputs
pyplot.plot(series_in, series_out)
pyplot.show()
运行该示例会创建一个折线图,显示所有负值和零输入被捕捉到 0.0,而正输出按原样返回,导致斜率线性增加,假设我们创建了一系列线性增加的正值(例如 1 到 10)。
正负输入的整流线性激活线图
整流后的线性函数的导数也很容易计算。回想一下,作为误差反向传播的一部分,当更新节点的权重时,需要激活函数的导数。
函数的导数是斜率。负值的斜率为 0.0,正值的斜率为 1.0。
传统上,神经网络领域避免了任何不完全可微的激活函数,这可能会延迟校正线性函数和其他分段线性函数的采用。技术上,当输入为 0.0 时,我们无法计算导数,因此,我们可以假设它为零。这在实践中不是问题。
例如,整流后的线性函数 g(z) = max{0,z}在 z = 0 时不可微。这看起来像是使 g 在基于梯度的学习算法中无效。在实践中,梯度下降仍然表现得足够好,可以将这些模型用于机器学习任务。
—第 192 页,深度学习,2016。
使用整流线性激活函数有许多优点;让我们在下一节看几个。
整流器线性激活功能的优点
在开发大多数类型的神经网络时,校正的线性激活函数已经迅速成为默认的激活函数。
因此,花点时间回顾一下这种方法的一些好处是很重要的,Xavier Glorot 等人在他们 2012 年关于使用 ReLU 的里程碑式论文中首次强调了这一点,论文标题为“深度稀疏整流神经网络”。
1.计算简单性。
整流器功能实现起来很简单,需要 max() 功能。
这与需要使用指数计算的 tanh 和 sigmoid 激活函数不同。
计算也更便宜:激活时不需要计算指数函数
——深度稀疏整流神经网络,2011。
2.表征稀疏性
整流器功能的一个重要好处是它能够输出真正的零值。
这与 tanh 和 sigmoid 激活函数不同,后者学习逼近零输出,例如非常接近零的值,但不是真正的零值。
这意味着负输入可以输出真零值,允许神经网络中隐藏层的激活包含一个或多个真零值。这被称为稀疏表示,是表示学习中的一个理想特性,因为它可以加速学习并简化模型。
自动编码器是研究和寻找稀疏性等高效表示的一个领域,在该领域中,网络在从紧凑表示重构输入(称为代码层)之前,先学习该输入的紧凑表示,例如图像或序列。
稀疏(和去噪)自动编码器在 h 中实现实际零点的一种方法是……这种想法是使用整流的线性单元来产生代码层。有了实际将表示推至零的先验知识(如绝对值罚分),就可以间接控制表示中零的平均数量。
—第 507 页,深度学习,2016。
3.线性性质
整流器功能看起来和行为都像一个线性激活功能。
一般来说,当神经网络的行为是线性或接近线性时,它更容易优化。
修正的线性单位【…】基于这样的原则,即如果模型的行为更接近线性,则模型更容易优化。
—第 194 页,深度学习,2016。
这个特性的关键是,用这个激活函数训练的网络几乎完全避免了梯度消失的问题,因为梯度保持与节点激活成比例。
由于这种线性,梯度在神经元的活动路径上流动良好(由于 sigmoid 或 tanh 单元的激活非线性,没有梯度消失效应)。
——深度稀疏整流神经网络,2011。
4.训练深层网络
重要的是,整流线性激活函数的(重新)发现和采用意味着有可能利用硬件的改进,并使用反向传播成功地训练具有非线性激活函数的深层多层网络。
反过来,像玻尔兹曼机器这样繁琐的网络以及像分层训练和无标记预训练这样繁琐的训练计划可能会被抛在后面。
……深度整流器网络可以达到最佳表现,而不需要对带有大型标记数据集的纯监督任务进行任何无监督预训练。因此,这些结果可以被视为一个新的里程碑,试图理解训练深度但纯监督的神经网络的难度,并缩小在无监督预训练和有监督预训练下学习的神经网络之间的表现差距。
——深度稀疏整流神经网络,2011。
整流器线性激活的使用技巧
在本节中,我们将了解在您自己的深度学习神经网络中使用校正线性激活函数时的一些技巧。
使用 ReLU 作为默认激活功能
长期以来,默认使用的激活是 sigmoid 激活功能。后来,这是 tanh 激活功能。
对于现代深度学习神经网络,默认激活函数是校正线性激活函数。
在引入整流线性单元之前,大多数神经网络使用逻辑 sigmoid 激活函数或双曲正切激活函数。
—第 195 页,深度学习,2016。
大多数获得最先进结果的论文将描述一个使用 ReLU 的网络。例如,在 Alex Krizhevsky 等人于 2012 年发表的里程碑式论文《使用深度卷积神经网络进行 ImageNet 分类》中,作者开发了一种具有 ReLU 激活的深度卷积神经网络,该网络在 ImageNet 照片类别数据集上取得了最先进的结果。
……我们将具有这种非线性的神经元称为整流线性单位(ReLUs)。带有 ReLUs 的深度卷积神经网络比带有 tanh 单元的同类网络训练速度快几倍。
如果有疑问,从你的神经网络中的 ReLU 开始,然后也许尝试其他分段线性激活函数,看看它们的表现比较如何。
在现代神经网络中,默认的建议是使用校正后的线性单位或 ReLU
—第 174 页,深度学习,2016。
将 ReLU 用于 MLPs、CNNs,但可能不用于 RNNs
ReLU 可以用于大多数类型的神经网络。
建议将其作为多层感知器(MLP)和卷积神经网络(CNN)的默认值。
已对使用氯化萘的 ReLU 进行了彻底调查,几乎普遍的结果是结果有所改善,最初,令人惊讶的是。
……滤波器组之后的非线性如何影响识别准确率。令人惊讶的答案是,使用校正非线性是提高识别系统表现的最重要因素。
——对象识别的最佳多级架构是什么?,2009 年
用氯化萘调查 ReLU 的工作引发了它们在其他网络类型中的使用。
[其他人]已经在卷积网络的环境中探索了各种校正的非线性……,并且已经发现它们改善了鉴别表现。
——整流线性单元改进受限玻尔兹曼机,2010。
当将 ReLU 与 CNNs 一起使用时,它们可以用作过滤器映射本身的激活功能,然后是一个池层。
卷积网络的典型层由三个阶段组成[…]在第二阶段,每个线性激活通过非线性激活函数运行,例如校正的线性激活函数。这个阶段有时被称为探测器阶段。
—第 339 页,深度学习,2016。
传统上,LSTMs 使用 tanh 激活函数激活单元状态,使用 sigmoid 激活函数激活节点输出。考虑到它们的精心设计,ReLU 被认为不适合递归神经网络,例如长短期记忆网络(LSTM)。
乍一看,ReLUs 似乎不适合 rnn,因为它们可以有非常大的输出,所以它们可能比具有有界值的单元更有可能爆炸。
——一种初始化整流线性单元递归网络的简单方法,2015。
尽管如此,已经有一些工作在调查在 LSTMs 中使用 ReLU 作为输出激活,其结果是仔细初始化网络权重,以确保网络在训练之前是稳定的。这在 2015 年的论文《初始化整流线性单元循环网络的简单方法》中有所概述
尝试较小的偏置输入值
偏置是节点上具有固定值的输入。
偏置具有移动激活函数的效果,传统上将偏置输入值设置为 1.0。
在网络中使用 ReLU 时,请考虑将偏差设置为较小的值,例如 0.1。
…将[偏差]的所有元素都设置为一个小的正值(如 0.1)可能是一个很好的做法。这使得整流后的线性单元很有可能最初对训练集中的大多数输入有效,并允许导数通过。
—第 193 页,深度学习,2016。
关于是否需要这样做,有一些相互矛盾的报告,因此将表现与带有 1.0 偏差输入的模型进行比较。
使用“重量初始化”
在训练神经网络之前,网络的权重必须初始化为小的随机值。
当在网络中使用 ReLU 并将权重初始化为以零为中心的小随机值时,默认情况下,网络中一半的单位将输出零值。
例如,在权重统一初始化之后,大约 50%的隐藏单元连续输出值是实零
——深度稀疏整流神经网络,2011。
有许多启发式方法来初始化神经网络的权重,然而除了将权重初始化方案映射到激活函数的选择的一般准则之外,没有最佳的权重初始化方案,并且几乎没有关系。
在 ReLU 被广泛采用之前,Xavier Glorot 和 Yoshua Bengio 在 2010 年发表的题为《理解训练深度前馈神经网络的难度》的论文中提出了一种初始化方案,该方案在使用 sigmoid 和 tanh 激活函数时迅速成为默认值,一般称为“ Xavier 初始化”。权重被设置为从与前一层中的节点数量的大小成比例的范围中均匀采样的随机值(具体地说+/1/sqrt(n),其中 n 是前一层中的节点数量)。
何等人在 2015 年发表的论文《深入研究整流器:在 ImageNet 分类上超越人类水平的表现》中提出,Xavier 初始化和其他方案不适用于 ReLU 和扩展。
格洛特和本吉奥建议采用适当比例的均匀分布进行初始化。这被称为“泽维尔”初始化[…]。其推导基于激活是线性的假设。这个假设对 ReLU 无效
——深入探究整流器:在 ImageNet 分类上超越人类水平的表现,2015。
他们提出了对 Xavier 初始化的一个小修改,使其适合与 ReLU 一起使用,现在通常称为“ He 初始化”(具体来说是+/sqrt(2/n),其中 n 是前一层中的节点数,称为扇入)。在实践中,高斯和均匀版本的方案都可以使用。
缩放输入数据
在使用神经网络之前对输入数据进行缩放是一种良好的做法。
这可能涉及标准化变量,使其均值和单位方差为零,或者将每个值标准化为 0 到 1。
在许多问题上没有数据缩放,神经网络的权重会变大,使网络不稳定并增加泛化误差。
无论您的网络是否使用 ReLU,这种缩放输入的良好做法都适用。
使用重量惩罚
根据设计,ReLU 的输出在正域中是无界的。
这意味着在某些情况下,产量可以继续增长。因此,使用一种形式的权重正则化可能是一个好主意,例如 L1 或 L2 向量范数。
另一个问题可能会由于激活的无界行为而出现;因此,人们可能希望使用正则化来防止潜在的数值问题。因此,我们对激活值使用 L1 惩罚,这也促进了额外的稀疏性
——深度稀疏整流神经网络,2011。
这对于促进稀疏表示(例如,使用 L1 正则化)和降低模型的泛化误差都是一个很好的实践。
ReLU 的扩展和替代
ReLU 确实有一些限制。
ReLU 的局限性之一是,大权重更新可能意味着激活函数的总输入总是负的,而与网络的输入无关。
这意味着有此问题的节点将永远输出 0.0 的激活值。这被称为一个“垂死的 ReLU ”。
每当设备不活动时,梯度为 0。这可能导致单元从不激活的情况,因为基于梯度的优化算法不会调整最初从不激活的单元的权重。此外,就像梯度消失问题一样,当训练具有恒定 0 梯度的 ReL 网络时,我们可能期望学习是缓慢的。
——整流器非线性改善神经网络声学模型,2013 年。
ReLU 的一些流行扩展放松了函数的非线性输出,以某种方式允许小的负值。
当输入小于 0 时,泄漏 ReLU (LReLU 或 LReL)修改函数以允许小负值。
当装置饱和且不活动时,泄漏整流器允许小的非零梯度
——整流器非线性改善神经网络声学模型,2013 年。
指数线性单位,或称 ELU,是 r ELU 的推广,它使用参数化的指数函数从正值过渡到小负值。
elu 具有负值,这使得激活的平均值接近于零。接近于零的平均激活使得学习更快,因为它们使梯度更接近自然梯度
——指数线性单位快速准确的深度网络学习(ELUs) ,2016。
参数 ReLU,或 PReLU,学习控制函数形状和泄漏的参数。
……我们提出了 ReLU 的一个新的推广,我们称之为参数整流线性单元(PReLU)。该激活函数自适应地学习整流器的参数
——深入探究整流器:在 ImageNet 分类上超越人类水平的表现,2015。
Maxout 是一种可选的分段线性函数,返回输入的最大值,设计用于与压差正则化技术结合使用。
我们定义了一个简单的新模型,称为 maxout(之所以这样命名,是因为它的输出是一组输入的最大值,也因为它是 drop 的自然伴侣),旨在通过 drop 促进优化,并提高 drop 的快速近似模型平均技术的准确率。
——Maxout Networks,2013 年。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
邮件
- 如何使用校正后的线性激活函数固定梯度消失
书
- 第 6.3.1 节矫正线性单位及其推广,深度学习,2016。
报纸
- 对象识别的最佳多级架构是什么?,2009 年
- 整流线性单元改善受限玻尔兹曼机器,2010。
- 深度稀疏整流神经网络,2011。
- 整流器非线性改善神经网络声学模型,2013。
- 理解深度前馈神经网络的训练难度,2010。
- 深究整流器:在 ImageNet 分类上超越人类水平的表现,2015。
- Maxout Networks ,2013 年。
应用程序接口
- 最大 API
文章
- 神经网络常见问题
- 激活功能,维基百科。
- 消失梯度问题,维基百科。
- 整流器(神经网络),维基百科。
- 分段线性函数,维基百科。
摘要
在本教程中,您发现了深度学习神经网络的校正线性激活函数。
具体来说,您了解到:
- 由于梯度消失问题,sigmoid 和双曲正切激活函数不能用于具有许多层的网络。
- 修正后的线性激活函数克服了梯度消失问题,使模型学习更快,表现更好。
- 修正后的线性激活是开发多层感知器和卷积神经网络时的默认激活。
你有什么问题吗?
在下面的评论中提问,我会尽力回答。
Python 中深度学习神经网络的快照集成
原文:https://machinelearningmastery.com/snapshot-ensemble-deep-learning-neural-network/
最后更新于 2020 年 8 月 28 日
模型集成可以实现比单个模型更低的泛化误差,但是考虑到训练每个单个模型的计算成本,使用深度学习神经网络进行开发是具有挑战性的。
另一种方法是在一次训练运行中训练多个模型快照,并结合它们的预测进行集成预测。这种方法的一个限制是,保存的模型将是相似的,导致相似的预测和预测误差,并且不能从组合它们的预测中提供太多好处。
有效的集成需要一组不同的熟练集成成员,这些成员具有不同的预测误差分布。一种促进在单次训练运行期间保存的模型多样性的方法是使用积极的学习率计划,该计划迫使模型权重发生大的变化,进而迫使在每次快照时保存的模型的性质发生大的变化。
在本教程中,您将发现如何在一次训练中使用积极的学习率计划来开发保存的模型的快照集合。
完成本教程后,您将知道:
- 快照集成组合了单次训练运行期间保存的多个模型的预测。
- 模型快照的多样性可以通过在单次训练中积极循环使用学习率来实现。
- 如何在单次运行期间保存模型快照并加载快照模型以进行集成预测。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2019 年 10 月更新:针对 Keras 2.3 和 TensorFlow 2.0 更新。
- 2020 年 1 月更新:针对 Sklearn v0.22 API 的变化进行了更新。
如何用 Keras 开发 Python 中的快照集成深度学习神经网络图片作者:杰森·雅各布斯,版权所有。
教程概述
本教程分为五个部分;它们是:
- 快照集
- 多类分类问题
- 多层感知器模型
- 余弦退火学习率
- MLP 快照一起
快照集
使用深度学习方法的集成学习的一个问题是训练多个模型的巨大计算成本。
这是因为使用了非常深的模型和非常大的数据集,导致模型训练时间可能会延长到几天、几周甚至几个月。
尽管集成具有明显的优势,但它在深度网络中的使用远不如在其他算法中广泛。缺乏适应性的一个可能原因可能是学习多个神经网络的成本。训练深度网络可以持续数周,即使是在具有 GPU 加速的高表现硬件上。
——快照合集:1 号列车,免费获得 M,2017。
深度学习神经网络集成学习的一种方法是从单次训练中收集多个模型。这解决了训练多个深度学习模型的计算成本,因为模型可以在训练期间被选择和保存,然后用于进行集成预测。
与单个模型的预测相比,集成学习的一个主要好处是提高了表现。这可以通过选择具有良好技能的成员来实现,但是以不同的方式,提供一组不同的预测来组合。在一次训练中收集多个模型的一个限制是模型可能很好,但过于相似。
这可以通过改变深度神经网络的学习算法来解决,以在单次训练运行期间强制探索不同的网络权重,这将反过来导致具有不同表现的模型。实现这一点的一种方法是积极改变训练中使用的学习率。
一种在训练过程中系统地、积极地改变学习率以产生非常不同的网络权重的方法被称为“带温重启的随机梯度下降”或简称为 SGDR,由 Ilya Loshchilov 和 Frank Hutter 在他们 2017 年的论文“ SGDR:带温重启的随机梯度下降”中描述
他们的方法包括在训练阶段系统地改变学习率,称为余弦退火。这种方法需要指定两个超参数:初始学习率和训练时期的总数。
“余弦退火”方法具有以较大的学习率开始的效果,该学习率在再次急剧增加之前相对快速地降低到最小值。模型权重在训练过程中会发生剧烈变化,其效果是使用“好权重”作为后续学习率周期的起点,但允许学习算法收敛到不同的解。
学习率的重置类似于学习过程的模拟重启,并且重新使用好的权重作为重启的起点被称为“热重启”,而不是“冷重启”,在冷重启中可以使用一组新的小随机数作为起点。
每个循环底部的“好权重”可以保存到文件中,提供模型的快照。这些快照可以在运行结束时一起收集,并在模型平均集成中使用。在积极的学习进度计划中保存和使用这些模型被称为“快照集合”,由黄高等人在他们 2017 年发表的题为“快照集合:火车 1,免费获得 M”的论文中进行了描述,随后也用于更新版本的洛希洛夫和赫特论文中。
……我们让 SGD 沿着它的优化路径收敛 M 次到局部极小值。每次模型收敛时,我们保存权重并将相应的网络添加到我们的集成中。然后,我们以较大的学习率重新开始优化,以避开当前的局部最小值。
——快照合集:1 号列车,免费获得 M,2017。
模式集合是在训练单个模式的过程中创建的,因此,作者声称集合预报是免费提供的。
[该方法允许]学习多个神经网络的集合,而不会产生任何额外的训练成本。
——快照合集:1 号列车,免费获得 M,2017。
虽然余弦退火计划用于学习率,但也可以使用其他积极的学习率计划,例如莱斯利·史密斯在 2017 年题为“训练神经网络的循环学习率”的论文中描述的更简单的循环学习率计划
现在我们已经熟悉了快照集成技术,我们可以看看如何用 Keras 在 Python 中实现它。
多类分类问题
我们将使用一个小的多类分类问题作为基础来演示快照集成。
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 的标准偏差意味着类不是线性可分的(用一条线可分的),导致很多不明确的点。
这是可取的,因为这意味着问题不是微不足道的,并将允许神经网络模型找到许多不同的“足够好”的候选解决方案,从而导致高方差。
具有三个类和按类值着色的点的斑点数据集的散点图
多层感知器模型
在我们定义一个模型之前,我们需要设计一个适合整体的问题。
在我们的问题中,训练数据集相对较小。具体来说,训练数据集中的示例与保持数据集中的示例的比例为 10:1。这模拟了一种情况,即我们可能有大量未标记的示例和少量已标记的示例来训练模型。
我们将从斑点问题中创建 1100 个数据点。模型将在前 100 个点上进行训练,剩余的 1000 个点将保留在测试数据集中,模型无法使用。
该问题是一个多类分类问题,我们将在输出层使用 softmax 激活函数对其进行建模。这意味着模型将以样本属于三类中每一类的概率来预测具有三个元素的向量。因此,在将行分割成训练和测试数据集之前,我们必须对类值进行热编码。我们可以使用 Keras *到 _ classic()*函数来实现这一点。
# 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:]
接下来,我们可以定义和编译模型。
该模型将预期具有两个输入变量的样本。然后,该模型有一个具有 25 个节点的单个隐藏层和一个校正的线性激活函数,然后有一个具有三个节点的输出层来预测三个类中每一个的概率,还有一个 softmax 激活函数。
由于问题是多类的,我们将使用分类交叉熵损失函数来优化模型和具有小学习率和动量的随机梯度下降。
# define model
model = Sequential()
model.add(Dense(25, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
该模型适合 200 个训练时期,我们将在测试集上评估每个时期的模型,使用测试集作为验证集。
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=200, 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 keras.optimizers import SGD
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:]
# define model
model = Sequential()
model.add(Dense(25, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
opt = SGD(lr=0.01, momentum=0.9)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=200, 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()
运行该示例将打印最终模型在列车和测试数据集上的表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到该模型在训练数据集上获得了大约 84%的准确率,我们知道这是乐观的,在测试数据集上获得了大约 79%的准确率,我们预计这将更加真实。
Train: 0.840, Test: 0.796
还创建了一个线图,显示了在每个训练周期内,训练和测试集上模型准确率的学习曲线。
我们可以看到,在大部分跑步过程中,训练的准确性更加乐观,我们也注意到了最终得分。
每个训练时期训练和测试数据集上模型准确率的线图学习曲线
接下来,我们可以看看如何实现积极的学习进度计划。
余弦退火学习率
一个有效的快照集成需要训练一个具有积极学习率计划的神经网络。
余弦退火调度是主动学习率调度的一个例子,其中学习率开始较高,并且在再次增加到最大值之前相对快速地下降到接近零的最小值。
我们可以按照 2017 年论文《快照合集:1 号列车,免费获得 M》中描述的时间表执行该等式需要总训练时期、最大学习率和周期数作为自变量以及当前时期数。然后,该函数返回给定时期的学习率。
余弦退火学习率表的等式
其中 a(t)是在时间点 T 的学习率,a0 是最大学习率,T 是总时间点,M 是周期数,mod 是模运算,方括号表示下限运算。
摘自《快照合集:1 号列车,免费取 M》。
下面的函数*余弦 _ 退火()*实现了这个等式。
# cosine annealing learning rate schedule
def cosine_annealing(epoch, n_epochs, n_cycles, lrate_max):
epochs_per_cycle = floor(n_epochs/n_cycles)
cos_inner = (pi * (epoch % epochs_per_cycle)) / (epochs_per_cycle)
return lrate_max/2 * (cos(cos_inner) + 1)
我们可以通过绘制具有五个周期(例如 20 个时期长)的 100 个时期的学习率和 0.01 的最大学习率来测试这种实现。下面列出了完整的示例。
# example of a cosine annealing learning rate schedule
from matplotlib import pyplot
from math import pi
from math import cos
from math import floor
# cosine annealing learning rate schedule
def cosine_annealing(epoch, n_epochs, n_cycles, lrate_max):
epochs_per_cycle = floor(n_epochs/n_cycles)
cos_inner = (pi * (epoch % epochs_per_cycle)) / (epochs_per_cycle)
return lrate_max/2 * (cos(cos_inner) + 1)
# create learning rate series
n_epochs = 100
n_cycles = 5
lrate_max = 0.01
series = [cosine_annealing(i, n_epochs, n_cycles, lrate_max) for i in range(n_epochs)]
# plot series
pyplot.plot(series)
pyplot.show()
运行该示例会创建一个超过 100 个时期的学习进度线图。
我们可以看到,学习率从历元 0 处的最大值开始,并迅速降低到历元 19,然后在历元 20 处复位,即下一个周期的开始。按照参数中的指定,循环重复五次。
余弦退火学习率表的线图
我们可以在 Keras 中将这个时间表实现为自定义回调。这允许指定时间表的参数并记录学习率,以便我们可以确保它具有预期的效果。
自定义回调可以定义为扩展 Keras 回调类的 Python 类。
在类构造器中,我们可以将所需的配置作为参数保存起来以备使用,具体来说就是训练时期的总数、学习率计划的周期数以及最大学习率。
我们可以使用上面定义的*余弦 _ 退火()*来计算给定训练时期的学习率。
回调类允许在每个训练时期之前调用的 on_epoch_begin() 函数被覆盖。我们可以覆盖这个函数来计算当前时期的学习率,并在优化器中设置它。我们还可以在内部列表中记录学习率。
完整的自定义回调定义如下。
# define custom learning rate schedule
class CosineAnnealingLearningRateSchedule(Callback):
# constructor
def __init__(self, n_epochs, n_cycles, lrate_max, verbose=0):
self.epochs = n_epochs
self.cycles = n_cycles
self.lr_max = lrate_max
self.lrates = list()
# calculate learning rate for an epoch
def cosine_annealing(self, epoch, n_epochs, n_cycles, lrate_max):
epochs_per_cycle = floor(n_epochs/n_cycles)
cos_inner = (pi * (epoch % epochs_per_cycle)) / (epochs_per_cycle)
return lrate_max/2 * (cos(cos_inner) + 1)
# calculate and set learning rate at the start of the epoch
def on_epoch_begin(self, epoch, logs=None):
# calculate learning rate
lr = self.cosine_annealing(epoch, self.epochs, self.cycles, self.lr_max)
# set learning rate
backend.set_value(self.model.optimizer.lr, lr)
# log value
self.lrates.append(lr)
我们可以创建回调的实例并设置参数。我们将针对 400 个时期对模型进行培训,并将周期数设置为 50 个时期长,即 500 / 50,这是一个建议,并在整个快照合集论文中进行了配置。
我们以非常快的速度降低学习率,鼓励模型在短短 50 个时期后收敛到其第一个局部最小值。
——快照合集:1 号列车,免费获得 M,2017。
该文件还建议,可以为每个样本或每个小批量设置学习率,而不是在每个时期之前,以便为更新提供更多的细微差别,但我们将把这作为未来的练习。
……我们在每次迭代而不是每个时期更新学习率。这改善了短周期的收敛性,即使当使用大的初始学习率时。
——快照合集:1 号列车,免费获得 M,2017。
一旦回调被实例化和配置,我们可以将其指定为回调列表的一部分,以调用 fit() 函数来训练模型。
# define learning rate callback
n_epochs = 400
n_cycles = n_epochs / 50
ca = CosineAnnealingLearningRateSchedule(n_epochs, n_cycles, 0.01)
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=n_epochs, verbose=0, callbacks=[ca])
在运行结束时,我们可以通过绘制列表和列表的内容来确认学习率计划的执行。
# plot learning rate
pyplot.plot(ca.lrates)
pyplot.show()
将这些元素结合在一起,下面列出了用余弦退火学习率训练 MLP 的完整例子。
# mlp with cosine annealing learning rate schedule on blobs problem
from sklearn.datasets import make_blobs
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from keras.callbacks import Callback
from keras.optimizers import SGD
from keras import backend
from math import pi
from math import cos
from math import floor
from matplotlib import pyplot
# define custom learning rate schedule
class CosineAnnealingLearningRateSchedule(Callback):
# constructor
def __init__(self, n_epochs, n_cycles, lrate_max, verbose=0):
self.epochs = n_epochs
self.cycles = n_cycles
self.lr_max = lrate_max
self.lrates = list()
# calculate learning rate for an epoch
def cosine_annealing(self, epoch, n_epochs, n_cycles, lrate_max):
epochs_per_cycle = floor(n_epochs/n_cycles)
cos_inner = (pi * (epoch % epochs_per_cycle)) / (epochs_per_cycle)
return lrate_max/2 * (cos(cos_inner) + 1)
# calculate and set learning rate at the start of the epoch
def on_epoch_begin(self, epoch, logs=None):
# calculate learning rate
lr = self.cosine_annealing(epoch, self.epochs, self.cycles, self.lr_max)
# set learning rate
backend.set_value(self.model.optimizer.lr, lr)
# log value
self.lrates.append(lr)
# 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:]
# define model
model = Sequential()
model.add(Dense(25, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
opt = SGD(momentum=0.9)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
# define learning rate callback
n_epochs = 400
n_cycles = n_epochs / 50
ca = CosineAnnealingLearningRateSchedule(n_epochs, n_cycles, 0.01)
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=n_epochs, verbose=0, callbacks=[ca])
# 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 learning rate
pyplot.plot(ca.lrates)
pyplot.show()
# learning curves of model accuracy
pyplot.plot(history.history['accuracy'], label='train')
pyplot.plot(history.history['val_accuracy'], label='test')
pyplot.legend()
pyplot.show()
运行该示例首先报告模型在训练集和测试集上的准确性。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,与前一部分相比,我们看不出最终模型的表现有多大差异。
Train: 0.850, Test: 0.806
创建学习率时间表的线图,显示每个 50 个时代的八个周期。
布洛布问题拟合 MLP 时的余弦退火学习率调度
最后,在每个训练时期,在训练集和测试集上创建模型准确率的线图。
我们可以看到,虽然学习率发生了巨大的变化,但对模型的准确性没有显著的影响,这可能是因为所选的分类问题并不是很难。
余弦退火学习率调度下 Blobs 数据集上训练和测试集准确率的线图
现在我们知道如何实现余弦退火学习调度,我们可以使用它来准备快照集成。
MLP 快照一起
我们可以分两部分开发快照集合。
第一部分包括创建一个自定义回调,将模型保存在每个学习进度计划的底部。第二部分包括加载保存的模型,并使用它们来进行集成预测。
培训期间保存快照模型
可以更新cosineannealinglearningletschedule以覆盖在每个训练周期结束时调用的 on_epoch_end() 函数。在这个函数中,我们可以检查刚刚结束的当前纪元是否是一个周期的结束。如果是这样,我们可以将模型保存到文件中。
下面是更新后的回调,命名为 SnapshotEnsemble 类。
每次保存模型时都会打印一条调试消息,以确认模型保存的时间是正确的。例如,对于 50 个周期的长周期,我们希望在 49、99 等周期保存一个模型。并且在时期 50、100 等重置学习率。
# snapshot ensemble with custom learning rate schedule
class SnapshotEnsemble(Callback):
# constructor
def __init__(self, n_epochs, n_cycles, lrate_max, verbose=0):
self.epochs = n_epochs
self.cycles = n_cycles
self.lr_max = lrate_max
self.lrates = list()
# calculate learning rate for epoch
def cosine_annealing(self, epoch, n_epochs, n_cycles, lrate_max):
epochs_per_cycle = floor(n_epochs/n_cycles)
cos_inner = (pi * (epoch % epochs_per_cycle)) / (epochs_per_cycle)
return lrate_max/2 * (cos(cos_inner) + 1)
# calculate and set learning rate at the start of the epoch
def on_epoch_begin(self, epoch, logs={}):
# calculate learning rate
lr = self.cosine_annealing(epoch, self.epochs, self.cycles, self.lr_max)
# set learning rate
backend.set_value(self.model.optimizer.lr, lr)
# log value
self.lrates.append(lr)
# save models at the end of each cycle
def on_epoch_end(self, epoch, logs={}):
# check if we can save model
epochs_per_cycle = floor(self.epochs / self.cycles)
if epoch != 0 and (epoch + 1) % epochs_per_cycle == 0:
# save model to file
filename = "snapshot_model_%d.h5" % int((epoch + 1) / epochs_per_cycle)
self.model.save(filename)
print('>saved snapshot %s, epoch %d' % (filename, epoch))
我们将为 500 个时期训练模型,以便在以后进行集合预测时给出 10 个模型供选择。
下面列出了使用这种新的快照集成将模型保存到文件中的完整示例。
# example of saving models for a snapshot ensemble
from sklearn.datasets import make_blobs
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from keras.callbacks import Callback
from keras.optimizers import SGD
from keras import backend
from math import pi
from math import cos
from math import floor
# snapshot ensemble with custom learning rate schedule
class SnapshotEnsemble(Callback):
# constructor
def __init__(self, n_epochs, n_cycles, lrate_max, verbose=0):
self.epochs = n_epochs
self.cycles = n_cycles
self.lr_max = lrate_max
self.lrates = list()
# calculate learning rate for epoch
def cosine_annealing(self, epoch, n_epochs, n_cycles, lrate_max):
epochs_per_cycle = floor(n_epochs/n_cycles)
cos_inner = (pi * (epoch % epochs_per_cycle)) / (epochs_per_cycle)
return lrate_max/2 * (cos(cos_inner) + 1)
# calculate and set learning rate at the start of the epoch
def on_epoch_begin(self, epoch, logs={}):
# calculate learning rate
lr = self.cosine_annealing(epoch, self.epochs, self.cycles, self.lr_max)
# set learning rate
backend.set_value(self.model.optimizer.lr, lr)
# log value
self.lrates.append(lr)
# save models at the end of each cycle
def on_epoch_end(self, epoch, logs={}):
# check if we can save model
epochs_per_cycle = floor(self.epochs / self.cycles)
if epoch != 0 and (epoch + 1) % epochs_per_cycle == 0:
# save model to file
filename = "snapshot_model_%d.h5" % int((epoch + 1) / epochs_per_cycle)
self.model.save(filename)
print('>saved snapshot %s, epoch %d' % (filename, epoch))
# 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:]
# define model
model = Sequential()
model.add(Dense(50, input_dim=2, activation='relu'))
model.add(Dense(3, activation='softmax'))
opt = SGD(momentum=0.9)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])
# create snapshot ensemble callback
n_epochs = 500
n_cycles = n_epochs / 50
ca = SnapshotEnsemble(n_epochs, n_cycles, 0.01)
# fit model
model.fit(trainX, trainy, validation_data=(testX, testy), epochs=n_epochs, verbose=0, callbacks=[ca])
运行该示例报告,余弦退火学习率计划的 10 端保存了 10 个模型。
>saved snapshot snapshot_model_1.h5, epoch 49
>saved snapshot snapshot_model_2.h5, epoch 99
>saved snapshot snapshot_model_3.h5, epoch 149
>saved snapshot snapshot_model_4.h5, epoch 199
>saved snapshot snapshot_model_5.h5, epoch 249
>saved snapshot snapshot_model_6.h5, epoch 299
>saved snapshot snapshot_model_7.h5, epoch 349
>saved snapshot snapshot_model_8.h5, epoch 399
>saved snapshot snapshot_model_9.h5, epoch 449
>saved snapshot snapshot_model_10.h5, epoch 499
加载模型并进行集成预测
一旦快照模型被保存到文件中,它们就可以被加载并用于进行集成预测。
第一步是将模型载入内存。对于大型模型,这可以一次一个模型来完成,进行预测,并在组合预测之前继续下一个模型。在这种情况下,模型相对较小,我们可以从文件中以列表的形式加载所有 10 个模型。
# load models from file
def load_all_models(n_models):
all_models = list()
for i in range(n_models):
# define filename for this ensemble
filename = 'snapshot_model_' + str(i + 1) + '.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
我们预计在运行结束时保存的模型可能比运行早期保存的模型具有更好的表现。因此,我们可以颠倒加载模型的列表,以便旧模型是第一个。
# reverse loaded models so we build the ensemble with the last models first
members = list(reversed(members))
我们不知道需要多少快照才能对这个问题做出很好的预测。我们可以通过从 499 时期的最终模型开始创建尺寸不断增加的集成,然后添加 449 时期保存的模型,以此类推,直到包括所有 10 个模型,来探索集成成员的数量对测试集准确性的影响。
首先,我们需要一个函数在给定一系列模型的情况下进行预测。给定每个模型预测每个输出类的概率,我们可以对模型的预测概率求和,并通过 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
然后,我们可以通过从模型列表中选择前 n 个成员来评估给定大小的集成,通过调用*集成 _ 预测()*函数进行预测,然后计算并返回预测的准确率。下面的 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)
每个集合的表现也可以与每个独立模型的表现和所有独立模型的平均表现进行对比。
# 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 a snapshot ensemble prediction
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_models):
all_models = list()
for i in range(n_models):
# define filename for this ensemble
filename = 'snapshot_model_' + str(i + 1) + '.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(10)
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()
运行该示例首先将所有 10 个模型加载到内存中。
>loaded snapshot_model_1.h5
>loaded snapshot_model_2.h5
>loaded snapshot_model_3.h5
>loaded snapshot_model_4.h5
>loaded snapshot_model_5.h5
>loaded snapshot_model_6.h5
>loaded snapshot_model_7.h5
>loaded snapshot_model_8.h5
>loaded snapshot_model_9.h5
>loaded snapshot_model_10.h5
Loaded 10 models
接下来,在测试数据集上评估每个快照模型,并报告准确性。这与快照集成的准确性形成对比,快照集成包括从运行结束后向后工作的所有快照模型,包括单个模型。
结果显示,当我们从运行结束向后工作时,快照模型的表现会变得更差,正如我们可能预期的那样。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
将快照模型组合成一个集合表明,表现提高到包括最后 3 到 5 个模型,达到大约 82%。这可以与大约 80%测试集准确度的快照模型的平均表现相比较。
> 1: single=0.813, ensemble=0.813
> 2: single=0.814, ensemble=0.813
> 3: single=0.822, ensemble=0.822
> 4: single=0.810, ensemble=0.820
> 5: single=0.813, ensemble=0.818
> 6: single=0.811, ensemble=0.815
> 7: single=0.807, ensemble=0.813
> 8: single=0.805, ensemble=0.813
> 9: single=0.805, ensemble=0.813
> 10: single=0.790, ensemble=0.813
Accuracy 0.809 (0.008)
最后,创建一个线图,绘制相同的测试集准确度分数。
我们可以将每个单独快照模型的表现设置为一个蓝点,将大小(成员数量)从 1 增加到 10 的快照集合设置为一条橙色线。
至少在这次运行中,我们可以看到快照集成在表现降级回 81.3%的最终模型之前,以 82.2%的速度快速超越了具有 3 个成员的最终模型和所有其他保存的模型。
单一快照模型(蓝点)与不同大小快照集合(橙色线)的线图
扩展ˌ扩张
本节列出了一些您可能希望探索的扩展教程的想法。
- 改变周期长度。更新示例以使用更短或更长的周期长度并比较结果。
- 改变最大学习率。更新示例以使用更大或更小的最大学习率并比较结果。
- 更新每批学习率。更新示例以计算每批而不是每时期的学习率。
- 重复评估。更新示例以重复模型的评估,从而确认该方法确实在斑点问题上比最终模型有所改进。
- 循环学习率。更新示例以使用循环学习率计划并比较结果。
如果你探索这些扩展,我很想知道。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 快照合集:1 号列车,免费获得 M,2017。
- SGDR:温重启随机梯度下降,2017。
- 训练神经网络的循环学习率,2017。
应用程序接口
文章
- 2017 年深度学习亮点优化。
- GitHubKeras 的循环学习率(CLR)。
- 喀拉拉邦、吉卜赛邦的快照集。
- 用学习率改善我们的工作方式,2017。
- 循环学习率技术,2017。
摘要
在本教程中,您发现了如何在一次训练中使用积极的学习率计划来开发保存的模型的快照集合。
具体来说,您了解到:
- 快照集成组合了单次训练运行期间保存的多个模型的预测。
- 模型快照的多样性可以通过在单次训练中积极循环使用学习率来实现。
- 如何在单次运行期间保存模型快照并加载快照模型以进行集成预测。
你有什么问题吗?
在下面的评论中提问,我会尽力回答。
Python 中深度学习神经网络的堆叠集成
原文:https://machinelearningmastery.com/stacking-ensemble-for-deep-learning-neural-networks/
最后更新于 2020 年 8 月 28 日
模型平均是一种集成技术,其中多个子模型对组合预测的贡献相等。
通过子模型的预期表现加权每个子模型对组合预测的贡献,可以改进模型平均。这可以通过训练一个全新的模型来学习如何最好地组合来自每个子模型的贡献来进一步扩展。这种方法被称为堆叠一般化,简称堆叠,可以比任何单一的贡献模型产生更好的预测表现。
在本教程中,您将发现如何为深度学习神经网络开发堆叠泛化集成。
完成本教程后,您将知道:
- 堆叠概括是一种集成方法,其中新模型学习如何最好地组合来自多个现有模型的预测。
- 如何使用神经网络作为子模型和 Sklearn 分类器作为元学习器开发堆叠模型?
- 如何开发一个叠加模型,将神经网络子模型嵌入到更大的叠加集成模型中进行训练和预测。
用我的新书更好的深度学习启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2019 年 10 月更新:针对 Keras 2.3 和 TensorFlow 2.0 更新。
- 2020 年 1 月更新:针对 Sklearn v0.22 API 的变化进行了更新。
- 2020 年 8 月更新:针对 Keras 2.4.3 和 TensorFlow 2.3 更新
如何用 Keras 开发 Python 中深度学习神经网络的堆叠集成。
教程概述
本教程分为六个部分;它们是:
- 堆叠综合集成
- 多类分类问题
- 多层感知器模型
- 训练和保存子模型
- 独立堆叠模型
- 集成堆叠模型
堆叠综合集成
模型平均集成组合了来自多个训练模型的预测。
这种方法的一个局限性是,无论模型表现如何,每个模型对集合预测的贡献都是相同的。这种方法的一种变体称为加权平均集成,通过模型在保持数据集上的信任或预期表现来衡量每个集成成员的贡献。这允许表现良好的模型贡献更多,表现不佳的模型贡献更少。加权平均集成提供了对模型平均集成的改进。
这种方法的进一步推广是用任何学习算法代替用于组合子模型预测的线性加权和(例如线性回归)模型。这种方法被称为堆叠概括,简称堆叠。
在堆叠中,算法将子模型的输出作为输入,并试图学习如何最好地组合输入预测,以做出更好的输出预测。
将堆叠过程视为具有两个级别可能会有所帮助:级别 0 和级别 1。
- 0 级:0 级数据是训练数据集输入,0 级模型学习根据该数据进行预测。
- 1 级:1 级数据以 0 级模型的输出为输入,单一的 1 级模型,即元学习器,从这个数据中学习做出预测。
堆叠泛化的工作原理是通过推导泛化器相对于所提供的学习集的偏差。这种推导是通过在第二个空间中进行归纳来进行的,当用学习集的一部分进行教学时,第二个空间的输入是(例如)原始归纳者的猜测,并试图猜测其余部分,第二个空间的输出是(例如)正确的猜测。
——堆叠概括,1992。
与加权平均集成不同,堆叠概括集成可以使用预测集作为上下文,并有条件地决定对输入预测进行不同的加权,从而潜在地获得更好的表现。
有趣的是,虽然堆叠被描述为具有两个或多个 0 级模型的集成学习方法,但是它可以用于只有单个 0 级模型的情况。在这种情况下,1 级或元学习器模型学习校正来自 0 级模型的预测。
…尽管它也可以在只有一个概化器时使用,作为改进该概化器的一种技术
——堆叠概括,1992。
重要的是,元学习器在单独的数据集上被训练到用于训练 0 级模型的例子,以避免过拟合。
实现这一点的一个简单方法是将训练数据集拆分为训练集和验证集。然后在列车组上训练 0 级模型。然后使用验证集来训练 1 级模型,其中原始输入首先通过 0 级模型来获得用作 1 级模型输入的预测。
保留验证集方法训练堆叠模型的一个限制是 0 级和 1 级模型不是在完整的数据集上训练的。
训练堆叠模型的更复杂的方法包括使用 k-fold 交叉验证来开发元学习器模型的训练数据集。每一个 0 级模型都使用 k 倍交叉验证(甚至留一个出来交叉验证以获得最大效果)进行训练;然后模型被丢弃,但是预测被保留。这意味着,对于每个模型,都有未在这些示例上训练的模型版本做出的预测,例如,像保持示例,但在这种情况下是针对整个训练数据集。
这些预测然后被用作训练元学习器的输入。然后在整个训练数据集上训练 0 级模型,并且与元学习器一起,堆叠模型可以用于对新数据进行预测。
在实践中,通常使用不同的算法来准备每个 0 级模型,以提供不同的预测集。
……堆叠通常不用于组合相同类型的模型……它应用于由不同学习算法构建的模型。
——实用机器学习工具与技术,第二版,2005。
使用简单的线性模型来组合预测也很常见。因为线性模型的使用很常见,堆叠最近被称为“模型混合”或简称为“T2 混合”,尤其是在机器学习比赛中。
……应采用多响应最小二乘线性回归技术作为高级概括。该技术提供了一种组合 0 级模型置信度的方法
——堆叠概括中的问题,1999。
可以为回归和分类问题开发一个堆叠的综合集成。在分类问题的情况下,当使用类概率的预测代替类标签作为元学习器的输入时,已经看到了更好的结果。
……应该使用类概率而不是单个预测类作为高级学习的输入属性。类别概率用作预测的置信度度量。
——堆叠概括中的问题,1999。
现在我们已经熟悉了堆叠概括,我们可以通过一个开发堆叠深度学习模型的案例研究来工作。
多类分类问题
我们将使用一个小的多类分类问题作为基础来演示堆叠集成。
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 个点将保留在测试数据集中,模型无法使用。
该问题是一个多类分类问题,我们将在输出层使用 softmax 激活函数对其进行建模。这意味着模型将以样本属于三类中每一类的概率来预测具有三个元素的向量。因此,在将行分割成训练和测试数据集之前,我们必须对类值进行热编码。我们可以使用 Keras *到 _ classic()*函数来实现这一点。
# 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)
接下来,我们可以定义并组合模型。
该模型将预期具有两个输入变量的样本。然后,该模型有一个具有 25 个节点的单个隐藏层和一个校正的线性激活函数,然后有一个具有三个节点的输出层来预测三个类中每一个的概率,还有一个 softmax 激活函数。
由于问题是多类的,我们将使用分类交叉熵损失函数来优化模型和随机梯度下降的有效亚当味。
# 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'])
该模型适用于 500 个训练时期,我们将使用测试集作为验证集,在测试集上评估每个时期的模型。
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=500, 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=500, 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.809
还创建了一个线图,显示了在每个训练周期内,训练和测试集上模型准确率的学习曲线。
我们可以看到,在大部分跑步过程中,训练的准确性更加乐观,我们也注意到了最终得分。
每个训练时期训练和测试数据集上模型准确率的线图学习曲线
我们现在可以将这个模型的实例作为堆叠集合的一部分。
训练和保存子模型
为了保持这个例子的简单,我们将使用同一个模型的多个实例作为堆叠集成中的 0 级或子模型。
我们还将使用保持验证数据集来训练集合中的第一级或元学习器。
更高级的例子可以使用不同类型的 MLP 模型(更深、更宽等)。)作为子模型,使用 K 折交叉验证训练元学习器。
在本节中,我们将训练多个子模型,将它们保存到文件中,以便以后在我们的堆叠集成中使用。
第一步是创建一个函数,该函数将在训练数据集上定义和拟合 MLP 模型。
# fit model on dataset
def fit_model(trainX, trainy):
# 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
model.fit(trainX, trainy, epochs=500, verbose=0)
return model
接下来,我们可以创建一个子目录来存储模型。
注意,如果目录已经存在,您可能必须在重新运行此代码时删除它。
# create directory for models
makedirs('models')
最后,我们可以创建 MLP 的多个实例,并用唯一的文件名将每个实例保存到“模型/ ”子目录中。
在这种情况下,我们将创建五个子模型,但是您可以用不同数量的模型进行实验,看看它如何影响模型表现。
# fit and save models
n_members = 5
for i in range(n_members):
# fit model
model = fit_model(trainX, trainy)
# save model
filename = 'models/model_' + str(i + 1) + '.h5'
model.save(filename)
print('>Saved %s' % filename)
我们可以把所有这些元素联系在一起;下面列出了训练子模型并将它们保存到文件中的完整示例。
# example of saving sub-models for later use in a stacking ensemble
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
# fit model on dataset
def fit_model(trainX, trainy):
# 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
model.fit(trainX, trainy, epochs=500, verbose=0)
return model
# 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)
# create directory for models
makedirs('models')
# fit and save models
n_members = 5
for i in range(n_members):
# fit model
model = fit_model(trainX, trainy)
# save model
filename = 'models/model_' + str(i + 1) + '.h5'
model.save(filename)
print('>Saved %s' % filename)
运行该示例会创建“模型/ ”子文件夹,并用唯一的文件名保存五个训练好的模型。
(100, 2) (1000, 2)
>Saved models/model_1.h5
>Saved models/model_2.h5
>Saved models/model_3.h5
>Saved models/model_4.h5
>Saved models/model_5.h5
接下来,我们可以考虑训练一个元学习器来最好地利用这些子模型的预测。
独立堆叠模型
我们现在可以训练一个元学习器,它将最好地组合来自子模型的预测,并且理想地比任何单个子模型表现得更好。
第一步是加载保存的模型。
我们可以使用 load_model() Keras 函数,创建一个 Python 加载模型列表。
# load models from file
def load_all_models(n_models):
all_models = list()
for i in range(n_models):
# define filename for this ensemble
filename = 'models/model_' + str(i + 1) + '.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 all models
n_members = 5
members = load_all_models(n_members)
print('Loaded %d models' % len(members))
了解单个模型在测试数据集上的表现会很有用,因为我们希望堆叠模型表现得更好。
我们可以轻松评估训练数据集中的每个模型,并建立表现基线。
# evaluate standalone models on test dataset
for model in members:
testy_enc = to_categorical(testy)
_, acc = model.evaluate(testX, testy_enc, verbose=0)
print('Model Accuracy: %.3f' % acc)
接下来,我们可以训练我们的元学习器。这需要两个步骤:
- 为元学习器准备一个训练数据集。
- 使用准备好的训练数据集来适应元学习器模型。
我们将为元学习器准备一个训练数据集,将测试集中的例子提供给每个子模型,并收集预测。在这种情况下,每个模型将为每个示例输出三个预测,预测给定示例属于三个类别中的每一个的概率。因此,测试集中的 1000 个示例将产生五个形状为*【1000,3】*的数组。
我们可以使用 dstack() NumPy 函数将这些数组组合成一个三维数组,其形状为*【1000,5,3】*,该函数将堆叠每组新的预测。
作为一个新模型的输入,我们将需要 1000 个具有一些特征的例子。假设我们有五个模型,每个模型对每个示例进行三次预测,那么每个示例将有 15 (3 x 5)个特征提供给子模型。我们可以将子模型中*【1000,5,3】形状的预测转换为【1000,15】*形状的数组,用于使用重塑()NumPy 函数训练元学习器,并展平最终的两个维度。 stacked_dataset() 函数实现这一步。
# create stacked model input dataset as outputs from the ensemble
def stacked_dataset(members, inputX):
stackX = None
for model in members:
# make prediction
yhat = model.predict(inputX, verbose=0)
# stack predictions into [rows, members, probabilities]
if stackX is None:
stackX = yhat
else:
stackX = dstack((stackX, yhat))
# flatten predictions to [rows, members x probabilities]
stackX = stackX.reshape((stackX.shape[0], stackX.shape[1]*stackX.shape[2]))
return stackX
一旦准备好了,我们就可以使用这个输入数据集和输出,或者测试集的 y 部分,来训练一个新的元学习器。
在这种情况下,我们将从 Sklearn 库中训练一个简单的逻辑回归算法。
逻辑回归只支持二进制分类,虽然逻辑回归类在 Sklearn 中的逻辑回归的实现使用一对多的方案支持多类分类(两类以上)。下面的函数 fit_stacked_model() 将通过调用 stacked_dataset() 函数为元学习器准备训练数据集,然后拟合逻辑回归模型,然后返回。
# fit a model based on the outputs from the ensemble members
def fit_stacked_model(members, inputX, inputy):
# create dataset using ensemble
stackedX = stacked_dataset(members, inputX)
# fit standalone model
model = LogisticRegression()
model.fit(stackedX, inputy)
return model
我们可以调用这个函数,并传入加载的模型列表和训练数据集。
# fit stacked model using the ensemble
model = fit_stacked_model(members, testX, testy)
一旦适合,我们可以使用堆叠模型,包括成员和元学习器,对新数据进行预测。
这可以通过首先使用子模型为元学习器制作输入数据集来实现,例如通过调用*stagged _ dataset()函数,然后与元学习器一起进行预测。下面的堆叠预测()*函数实现了这一点。
# make a prediction with the stacked model
def stacked_prediction(members, model, inputX):
# create dataset using ensemble
stackedX = stacked_dataset(members, inputX)
# make a prediction
yhat = model.predict(stackedX)
return yhat
我们可以用这个函数对新数据进行预测;在这种情况下,我们可以通过对测试集进行预测来证明这一点。
# evaluate model on test set
yhat = stacked_prediction(members, model, testX)
acc = accuracy_score(testy, yhat)
print('Stacked Test Accuracy: %.3f' % acc)
将所有这些元素结合在一起,下面列出了为 MLP 子模型的堆叠集合拟合线性元学习器的完整示例。
# stacked generalization with linear meta model on blobs dataset
from sklearn.datasets import make_blobs
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression
from keras.models import load_model
from keras.utils import to_categorical
from numpy import dstack
# load models from file
def load_all_models(n_models):
all_models = list()
for i in range(n_models):
# define filename for this ensemble
filename = 'models/model_' + str(i + 1) + '.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
# create stacked model input dataset as outputs from the ensemble
def stacked_dataset(members, inputX):
stackX = None
for model in members:
# make prediction
yhat = model.predict(inputX, verbose=0)
# stack predictions into [rows, members, probabilities]
if stackX is None:
stackX = yhat
else:
stackX = dstack((stackX, yhat))
# flatten predictions to [rows, members x probabilities]
stackX = stackX.reshape((stackX.shape[0], stackX.shape[1]*stackX.shape[2]))
return stackX
# fit a model based on the outputs from the ensemble members
def fit_stacked_model(members, inputX, inputy):
# create dataset using ensemble
stackedX = stacked_dataset(members, inputX)
# fit standalone model
model = LogisticRegression()
model.fit(stackedX, inputy)
return model
# make a prediction with the stacked model
def stacked_prediction(members, model, inputX):
# create dataset using ensemble
stackedX = stacked_dataset(members, inputX)
# make a prediction
yhat = model.predict(stackedX)
return 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 all models
n_members = 5
members = load_all_models(n_members)
print('Loaded %d models' % len(members))
# evaluate standalone models on test dataset
for model in members:
testy_enc = to_categorical(testy)
_, acc = model.evaluate(testX, testy_enc, verbose=0)
print('Model Accuracy: %.3f' % acc)
# fit stacked model using the ensemble
model = fit_stacked_model(members, testX, testy)
# evaluate model on test set
yhat = stacked_prediction(members, model, testX)
acc = accuracy_score(testy, yhat)
print('Stacked Test Accuracy: %.3f' % acc)
运行该示例首先将子模型加载到列表中,并评估每个子模型的表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
我们可以看到,表现最好的模型是最终模型,准确率约为 81.3%。
(100, 2) (1000, 2)
>loaded models/model_1.h5
>loaded models/model_2.h5
>loaded models/model_3.h5
>loaded models/model_4.h5
>loaded models/model_5.h5
Loaded 5 models
Model Accuracy: 0.805
Model Accuracy: 0.806
Model Accuracy: 0.804
Model Accuracy: 0.809
Model Accuracy: 0.813
接下来,逻辑回归元学习器基于来自测试集上每个子模型的预测概率进行训练,然后在测试集上评估整个堆叠模型。
我们可以看到,在这种情况下,元学习器在测试集上执行了每个子模型,达到了大约 82.4%的准确率。
Stacked Test Accuracy: 0.824
集成堆叠模型
当使用神经网络作为子模型时,可能希望使用神经网络作为元学习器。
具体而言,子网络可以嵌入到更大的多头神经网络中,然后学习如何最好地组合来自每个输入子模型的预测。它允许堆叠集合被视为一个单一的大模型。
这种方法的好处是子模型的输出直接提供给元学习器。此外,如果需要,还可以结合元学习器模型来更新子模型的权重。
这可以使用开发模型的 Keras 功能界面来实现。
在模型作为列表加载之后,可以定义更大的堆叠集合模型,其中每个加载的模型被用作模型的独立输入头。这要求每个加载模型中的所有层都被标记为不可训练,以便在训练新的更大模型时权重不能被更新。Keras 还要求每个层都有一个唯一的名称,因此每个加载模型中每个层的名称都必须更新,以指示它们属于哪个集合成员。
# update all layers in all models to not be trainable
for i in range(len(members)):
model = members[i]
for layer in model.layers:
# make not trainable
layer.trainable = False
# rename to avoid 'unique layer name' issue
layer._name = 'ensemble_' + str(i+1) + '_' + layer.name
一旦准备好子模型,我们就可以定义堆叠集成模型。
每个子模型的输入层将用作这个新模型的独立输入头。这意味着必须向模型提供任何输入数据的 k 副本,其中 k 是输入模型的数量,在本例中为 5。
然后可以合并每个模型的输出。在这种情况下,我们将使用一个简单的串联合并,其中将从 5 个模型中的每一个预测的三个类概率中创建一个 15 元素的向量。
然后,我们将定义一个隐藏层来解释元学习器的输入*,并定义一个输出层来进行自己的概率预测。下面的 define_stacked_model() 函数实现了这一点,并将返回一个给定训练子模型列表的堆叠泛化神经网络模型。*
# define stacked model from multiple member input models
def define_stacked_model(members):
# update all layers in all models to not be trainable
for i in range(len(members)):
model = members[i]
for layer in model.layers:
# make not trainable
layer.trainable = False
# rename to avoid 'unique layer name' issue
layer._name = 'ensemble_' + str(i+1) + '_' + layer.name
# define multi-headed input
ensemble_visible = [model.input for model in members]
# concatenate merge output from each model
ensemble_outputs = [model.output for model in members]
merge = concatenate(ensemble_outputs)
hidden = Dense(10, activation='relu')(merge)
output = Dense(3, activation='softmax')(hidden)
model = Model(inputs=ensemble_visible, outputs=output)
# plot graph of ensemble
plot_model(model, show_shapes=True, to_file='model_graph.png')
# compile
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
当调用这个函数给出集合模型如何配合在一起的想法时,网络图的图被创建。
# define ensemble model
stacked_model = define_stacked_model(members)
创建绘图需要安装 pygraphviz。
如果这在您的工作站上是一个挑战,您可以注释掉对 plot_model() 函数的调用。
神经网络模型层叠泛化集成的可视化
一旦定义了模型,就可以进行拟合。我们可以将其直接放在保持测试数据集上。
因为子模型是不可训练的,所以在训练过程中不会更新它们的权重,只会更新新隐藏层和输出层的权重。下面的 fit_stacked_model() 函数将在 300 个时期内拟合堆叠神经网络模型。
# fit a stacked model
def fit_stacked_model(model, inputX, inputy):
# prepare input data
X = [inputX for _ in range(len(model.input))]
# encode output data
inputy_enc = to_categorical(inputy)
# fit model
model.fit(X, inputy_enc, epochs=300, verbose=0)
我们可以通过提供定义的堆叠模型和测试数据集来调用这个函数。
# fit stacked model on test dataset
fit_stacked_model(stacked_model, testX, testy)
一旦拟合,我们可以使用新的堆叠模型对新数据进行预测。
这就像在模型上调用 predict() 函数一样简单。一个小的变化是,我们要求将列表中的输入数据的 k 副本提供给每个 k 子模型的模型。下面的 predict_stacked_model() 函数简化了使用堆叠模型进行预测的过程。
# make a prediction with a stacked model
def predict_stacked_model(model, inputX):
# prepare input data
X = [inputX for _ in range(len(model.input))]
# make prediction
return model.predict(X, verbose=0)
我们可以调用这个函数对测试数据集进行预测,并报告准确性。
我们期望神经网络学习器的表现优于任何单独的子模型,并且可能与上一节中使用的线性元学习器相竞争。
# make predictions and evaluate
yhat = predict_stacked_model(stacked_model, testX)
yhat = argmax(yhat, axis=1)
acc = accuracy_score(testy, yhat)
print('Stacked Test Accuracy: %.3f' % acc)
将所有这些元素结合在一起,下面列出了完整的示例。
# stacked generalization with neural net meta model on blobs dataset
from sklearn.datasets import make_blobs
from sklearn.metrics import accuracy_score
from keras.models import load_model
from keras.utils import to_categorical
from keras.utils import plot_model
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers.merge import concatenate
from numpy import argmax
# load models from file
def load_all_models(n_models):
all_models = list()
for i in range(n_models):
# define filename for this ensemble
filename = 'models/model_' + str(i + 1) + '.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
# define stacked model from multiple member input models
def define_stacked_model(members):
# update all layers in all models to not be trainable
for i in range(len(members)):
model = members[i]
for layer in model.layers:
# make not trainable
layer.trainable = False
# rename to avoid 'unique layer name' issue
layer._name = 'ensemble_' + str(i+1) + '_' + layer.name
# define multi-headed input
ensemble_visible = [model.input for model in members]
# concatenate merge output from each model
ensemble_outputs = [model.output for model in members]
merge = concatenate(ensemble_outputs)
hidden = Dense(10, activation='relu')(merge)
output = Dense(3, activation='softmax')(hidden)
model = Model(inputs=ensemble_visible, outputs=output)
# plot graph of ensemble
plot_model(model, show_shapes=True, to_file='model_graph.png')
# compile
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# fit a stacked model
def fit_stacked_model(model, inputX, inputy):
# prepare input data
X = [inputX for _ in range(len(model.input))]
# encode output data
inputy_enc = to_categorical(inputy)
# fit model
model.fit(X, inputy_enc, epochs=300, verbose=0)
# make a prediction with a stacked model
def predict_stacked_model(model, inputX):
# prepare input data
X = [inputX for _ in range(len(model.input))]
# make prediction
return model.predict(X, verbose=0)
# 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 all models
n_members = 5
members = load_all_models(n_members)
print('Loaded %d models' % len(members))
# define ensemble model
stacked_model = define_stacked_model(members)
# fit stacked model on test dataset
fit_stacked_model(stacked_model, testX, testy)
# make predictions and evaluate
yhat = predict_stacked_model(stacked_model, testX)
yhat = argmax(yhat, axis=1)
acc = accuracy_score(testy, yhat)
print('Stacked Test Accuracy: %.3f' % acc)
运行该示例首先加载五个子模型。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
定义了一个更大的叠加集成神经网络,并将其拟合到测试数据集上,然后利用新模型对测试数据集进行预测。我们可以看到,在这种情况下,模型达到了大约 83.3%的准确率,超过了前面部分的线性模型。
(100, 2) (1000, 2)
>loaded models/model_1.h5
>loaded models/model_2.h5
>loaded models/model_3.h5
>loaded models/model_4.h5
>loaded models/model_5.h5
Loaded 5 models
Stacked Test Accuracy: 0.833
扩展ˌ扩张
本节列出了一些您可能希望探索的扩展教程的想法。
- 交替元学习器。更新示例,将替代元学习器分类器模型用于逻辑回归模型。
- 单级 0 模型。更新示例以使用单个 0 级模型并比较结果。
- 改变 0 级模型。开展一项研究,证明测试分类准确率和堆叠集成中使用的子模型数量之间的关系。
- 交叉验证堆叠集合。更新示例,使用 k-fold 交叉验证为元学习器模型准备训练数据集。
- 在元学习器中使用原始输入。更新示例,以便元学习器算法获取样本的原始输入数据以及子模型的输出,并比较表现。
如果你探索这些扩展,我很想知道。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
书
- 第 8.8 节模型平均和叠加,统计学习的要素:数据挖掘、推理和预测,第二版,2016。
- 第 7.5 节组合多个模型,数据挖掘:实用机器学习工具和技术,第二版,2005。
- 第 9.8.2 节堆叠概括,模式识别的神经网络,1995。
报纸
应用程序接口
- 开始使用 Keras 顺序模型
- 硬核层 API
- num py . argmax API
- sklearn . dataset . make _ blobs API
- num py . dstack API
- sklearn.linear_model。物流配送应用编程接口
文章
- 堆叠概括(堆叠)书目。
- 一起学习,维基百科。
邮件
摘要
在本教程中,您发现了如何为深度学习神经网络开发堆叠泛化集成。
具体来说,您了解到:
- 堆叠综合是一种集成方法,其中新模型学习如何最好地组合来自多个现有模型的预测。
- 如何使用神经网络作为子模型和 Sklearn 分类器作为元学习器开发堆叠模型?
- 如何开发一个叠加模型,将神经网络子模型嵌入到更大的叠加集成模型中进行训练和预测。
你有什么问题吗?
在下面的评论中提问,我会尽力回答。*