Python深度学习(Keras)
对于张量运算所操作的张量,其元素可以被解释为某种几何空间内点的坐标.
神经网络完全由一系列张量运算组成,而这些张量运算都只是输入数据的几何变换。
因此,你可以将神经网络解释为高维空间中非常复杂的几何变换,这种变换可以通过许多简单的步骤来实现。
训练循环过程:
(1) 抽取训练样本x 和对应目标y 组成的数据批量。
(2) 在x 上运行网络[这一步叫作前向传播(forward pass)],得到预测值y_pred。
(3) 计算网络在这批数据上的损失,用于衡量y_pred 和y 之间的距离。
(4) 更新网络的所有权重,使网络在这批数据上的损失略微下降。
最终得到的网络在训练数据上的损失非常小,即预测值y_pred 和预期目标y 之间的距离非常小。网络“学会”将输入映射到正确的目标。
梯度(gradient)是张量运算的导数。
小批量随机梯度下降算法步骤
(1) 抽取训练样本x 和对应目标y 组成的数据批量。
(2) 在x 上运行网络,得到预测值y_pred。
(3) 计算网络在这批数据上的损失,用于衡量y_pred 和y 之间的距离。
(4) 计算损失相对于网络参数的梯度[一次反向传播(backward pass)]。
(5) 将参数沿着梯度的反方向移动一点,比如W -= step * gradient,从而使这批数据上的损失减小一点。
术语随机(stochastic)是指每批数据都是随机抽取的。
注意,小批量SGD 算法的一个变体是每次迭代时只抽取一个样本和目标,而不是抽取一批数据。这叫作真SGD(有别于小批量SGD)。还有另一种极端,每一次迭代都在所有数据上运行,这叫作批量SGD。这样做的话,每次更新都更加准确,但计算代价也高得多。这两个极端之间的有效折中则是选择合理的批量大小。
SGD 还有多种变体,其区别在于计算下一次权重更新时还要考虑上一次权重更新,而不是仅仅考虑当前梯度值,比如带动量的SGD、Adagrad、RMSProp 等变体。这些变体被称为优化方法(optimization method)或优化器(optimizer)。其中动量的概念尤其值得关注,它在许多变体中都有应用。动量解决了SGD 的两个问题:收敛速度和局部极小点。
在某个参数值附近,有一个局部极小点(local minimum):在这个点附近,向左移动和向右移动都会导致损失值增大。如果使用小学习率的SGD 进行优化,那么优化过程可能会陷入局部极小点,导致无法找到全局最小点。
使用动量方法可以避免这样的问题,这一方法的灵感来源于物理学。有一种有用的思维图像,就是将优化过程想象成一个小球从损失函数曲线上滚下来。如果小球的动量足够大,那么它不会卡在峡谷里,最终会到达全局最小点。动量方法的实现过程是每一步都移动小球,不仅要考虑当前的斜率值(当前的加速度),还要考虑当前的速度(来自于之前的加速度)。这在实践中的是指,更新参数w 不仅要考虑当前的梯度值,还要考虑上一次的参数更新
将链式法则应用于神经网络梯度值的计算,得到的算法叫作反向传播(backpropagation,有时也叫反式微分,reverse-mode differentiation)。反向传播从最终损失值开始,从最顶层反向作用至最底层,利用链式法则计算每个参数对损失值的贡献大小。现在以及未来数年,人们将使用能够进行符号微分(symbolic differentiation)的现代框架来实现神经网络,比如TensorFlow。也就是说,给定一个运算链,并且已知每个运算的导数,这些框架就可以利用链式法则来计算这个运算链的梯度函数,将网络参数值映射为梯度值。对于这样的函数,反向传播就简化为调用这个梯度函数。由于符号微分的出现,你无须手动实现反向传播算法。因此,我们不会在本节浪费你的时间和精力来推导反向传播的具体公式。你只需充分理解基于梯度的优化方法的工作原理。
(1)学习是指找到一组模型参数,使得在给定的训练数据样本和对应目标值上的损失函数最小化。
(2) 学习的过程:随机选取包含数据样本及其目标值的批量,并计算批量损失相对于网络参数的梯度。随后将网络参数沿着梯度的反方向稍稍移动(移动距离由学习率指定)。
(3) 整个学习过程之所以能够实现,是因为神经网络是一系列可微分的张量运算,因此可以利用求导的链式法则来得到梯度函数,这个函数将当前参数和当前数据批量映射为一个梯度值。
(4) 后续几章你会经常遇到两个关键的概念:损失和优化器。将数据输入网络之前,你需要先定义这二者。
(5) 损失是在训练过程中需要最小化的量,因此,它应该能够衡量当前任务是否已成功解决。
(6) 优化器是使用损失梯度更新参数的具体方式,比如 RMSProp 优化器、带动量的随机梯度下降(SGD)等。
前面几章介绍过,训练神经网络主要围绕以下四个方面。
1、层,多个层组合成网络(或模型)。
2、输入数据和相应的目标。
3、 损失函数,即用于学习的反馈信号。
4、 优化器,决定学习过程如何进行。
四者的关系如下:多个层链接在一起组成了网络,将输入数据映射为预测值。然后损失函数将这些预测值与目标进行比较,得到损失值,用于衡量网络预测值与预期结果的匹配程度。优化器使用这个损失值来更新网络的权重。
神经网络的基本数据结构是层。层是一个数据处理模块,将一个或多个输入张量转换为一个或多个输出张量。有些层是无状态的,但大多数的层是有状态的,即层的权重。权重是利用随机梯度下降学到的一个或多个张量,其中包含网络的知识。不同的张量格式与不同的数据处理类型需要用到不同的层。例如,简单的向量数据保存在形状为 的2D 张量中,通常用密集连接层[也叫全连接层或密集层,对应于Keras的Dense类]来处理。
序列数据保存在形状为的3D 张量中,通常用循环层(比如Keras 的LSTM 层)来处理。图像数据保存在4D 张量中,通常用二维卷积层(Keras 的Conv2D)来处理。你可以将层看作深度学习的乐高积木,Keras等框架则将这种比喻具体化。
在Keras 中,构建深度学习模型就是将相互兼容的多个层拼接在一起,以建立有用的数据变换流程。这里层兼容性具体指的是每一层只接受特定形状的输入张量,并返回特定形状的输出张量。
深度学习模型是层构成的有向无环图。最常见的例子就是层的线性堆叠,将单一输入映射为单一输出。但随着深入学习,你会接触到更多类型的网络拓扑结构。一些常见的网络拓扑结构如下。
1 双分支网络
2 多头网络
3 Inception模块
网络的拓扑结构定义了一个假设空间。选定了网络拓扑结构,意味着将可能性空间(假设空间)限定为一系列特定的张量运算,将输入数据映射为输出数据。然后,你需要为这些张量运算的权重张量找到一组合适的值。选择正确的网络架构更像是一门艺术而不是科学。
一旦确定了网络架构,你还需要选择以下两个参数。
1 损失函数(也就是目标函数)——在训练过程中需要将其最小化。它能够衡量当前任务是否已成功完成。
2 优化器——决定如何基于损失函数对网络进行更新。它执行的是随机梯度下降(SGD)的某个变体。具有多个输出的神经网络可能具有多个损失函数(每个输出对应一个损失函数)。
但是,梯度下降过程必须基于单个标量损失值。因此,对于具有多个损失函数的网络,需要将所有损失函数取平均,变为一个标量值。
选择正确的目标函数对解决问题是非常重要的。网络的目的是使损失尽可能最小化,因此,如果目标函数与成功完成当前任务不完全相关,那么网络最终得到的结果可能会不符合你的预期。一定要明智地选择目标函数,否则你将会遇到意想不到的副作用。
幸运的是,对于分类、回归、序列预测等常见问题,你可以遵循一些简单的指导原则来选择正确的损失函数。例如,对于二分类问题,你可以使用二元交叉熵损失函数;对于多分类问题,可以用分类交叉熵损失函数;对于回归问题,可以用均方误差损失函数;对于序列学习问题,可以用联结主义时序分类损失函数。只有在面对真正全新的研究问题时,你才需要自主开发目标函数。
Keras 具有以下重要特性。
1、相同的代码可以在 CPU或 GPU 上无缝切换运行。
2、具有用户友好的 API,便于快速开发深度学习模型的原型。
3、内置支持卷积网络(用于计算机视觉)、循环网络(用于序列处理)以及二者的任意组合。
4、支持任意网络架构:多输入或多输出模型、层共享、模型共享等。这也就是说,Keras能够构建任意深度学习模型,无论是生成式对抗网络还是神经图灵机。
Keras 是一个模型级的库,为开发深度学习模型提供了高层次的构建模块。
它不处理张量操作、求微分等低层次的运算。相反,它依赖于一个专门的、高度优化的张量库来完成这些运算,这个张量库就是Keras 的后端引擎。Keras 没有选择单个张量库并将Keras 实现与这个库绑定,而是以模块化的方式处理这个问题。因此,几个不同的后端引擎都可以无缝嵌入到Keras中。目前,Keras 有三个后端实现:TensorFlow 后端、Theano 后端和微软认知工具包(CNTK)后端。
TensorFlow、CNTK 和Theano 是当今深度学习的几个主要平台。
你用Keras 写的每一段代码都可以在这三个后端上运行,无须任何修改。也就是说,你在开发过程中可以在两个后端之间无缝切换,这通常是很有用的。通过TensorFlow(或Theano、CNTK),Keras 可以在CPU 和GPU 上无缝运行。在CPU 上运行时,TensorFlow 本身封装了一个低层次的张量运算库,叫作Eigen;在GPU 上运行时,TensorFlow封装了一个高度优化的深度学习运算库,叫作NVIDIA CUDA 深度神经网络库(cuDNN)。
Keras开发流程
(1) 定义训练数据:输入张量和目标张量。
(2) 定义层组成的网络(或模型),将输入映射到目标。
(3) 配置学习过程:选择损失函数、优化器和需要监控的指标。
(4) 调用模型的fit 方法在训练数据上进行迭代。
定义模型有两种方法:一种是使用Sequential 类(仅用于层的线性堆叠,这是目前最常见的网络架构),另一种是函数式API(functional API,用于层组成的有向无环图,让你可以构建任意形式的架构)。一旦定义好了模型架构,使用Sequential 模型还是函数式API 就不重要了。接下来的步骤都是相同的。配置学习过程是在编译这一步,你需要指定模型使用的优化器和损失函数,以及训练过程中想要监控的指标。
最后,学习过程就是通过fit() 方法将输入数据的Numpy 数组(和对应的目标数据)传入模型,这一做法与Scikit-Learn 及其他机器学习库类似。
不能将整数序列直接输入神经网络。你需要将列表转换为张量。转换方法有以下两种。第一种为填充列表,使其具有相同的长度,再将列表转换成形状为 (samples, word_indices)的整数张量,然后网络第一层使用能处理这种整数张量的层(即Embedding 层)。第二种对列表进行one-hot 编码,将其转换为 0 和 1 组成的向量。
对于这种Dense 层的堆叠,你需要确定以下两个关键架构:
1、网络有多少层;
2、每层有多少个隐藏单元。
relu(整流线性单元)函数将所有负值归零。
而sigmoid 函数则将任意值“压缩”到[0,1] 区间内,其输出值可以看作概率值。
什么是激活函数?为什么要使用激活函数?
如果没有relu 等激活函数(也叫非线性),Dense 层将只包含两个线性运算——点积和加法。这样Dense 层就只能学习输入数据的线性变换(仿射变换):该层的假设空间是从输入数据到16 位空间所有可能的线性变换集合。这种假设空间非常有限,无法利用多个表示层的优势,因为多个线性层堆叠实现的仍是线性运算,添加层数并不会扩展假设空间。为了得到更丰富的假设空间,从而充分利用多层表示的优势,你需要添加非线性或激活函数。relu 是深度学习中最常用的激活函数,但还有许多其他函数可选,它们都有类似的奇怪名称,比如prelu、elu 等。
注意:
1、通常需要对原始数据进行大量预处理,以便将其转换为张量输入到神经网络中。单词序列可以编码为二进制向量,但也有其他编码方式。
2、带有relu激活的 Dense 层堆叠,可以解决很多种问题(包括情感分类),你可能会经常用到这种模型。
3、对于二分类问题(两个输出类别),网络的最后一层应该是只有一个单元并使用sigmoid激活的Dense 层,网络输出应该是0~1 范围内的标量,表示概率值。
4、对于二分类问题的 sigmoid标量输出,你应该使用binary_crossentropy损失函数。
5、无论你的问题是什么,rmsprop优化器通常都是足够好的选择。这一点你无须担心。
6、随着神经网络在训练数据上的表现越来越好,模型最终会过拟合,并在前所未见的数据上得到越来越差的结果。一定要一直监控模型在训练集之外的数据上的性能。
选择损失函数和优化器时:由于你面对的是一个二分类问题,网络输出是一个概率值(网络最后一层使用sigmoid 激活函数,仅包含一个单元),那么最好使用binary_crossentropy(二元交叉熵)损失。这并不是唯一可行的选择,比如你还可以使用mean_squared_error(均方误差)。但对于输出概率值的模型,交叉熵(crossentropy)往往是最好的选择。交叉熵是来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离。
将标签向量化有两种方法:你可以将标签列表转换为整数张量,或者使用one-hot 编码。one-hot 编码是分类数据广泛使用的一种格式,也叫分类编码。另一种编码标签的方法,就是将其转换为整数张量,对于整数标签,你应该使用sparse_categorical_crossentropy。
Dense 层的堆叠,每层只能访问上一层输出的信息。如果某一层丢失了与分类问题相关的一些信息,那么这些信息无法被后面的层找回,也就是说,每一层都可能成为信息瓶颈。
注意:
1、对于多分类问题,最好的损失函数是categorical_crossentropy(分类交叉熵)。它用于衡量两个概率分布之间的距离,这里两个概率分布分别是网络输出的概率分布和标签的真实分布。通过将这两个分布的距离最小化,训练网络可使输出结果尽可能接近真实标签。
2、如果要对 N个类别的数据点进行分类,网络的最后一层应该是大小为N的Dense层。
3、对于单标签、多分类问题,网络的最后一层应该使用softmax 激活,这样可以输出在N个输出类别上的概率分布。
4、这种问题的损失函数几乎总是应该使用分类交叉熵。它将网络输出的概率分布与目标的真实分布之间的距离最小化。
5、处理多分类问题的标签有两种方法。
通过分类编码(也叫 one-hot 编码)对标签进行编码,然后使用 categorical_crossentropy 作为损失函数。 将标签编码为整数,然后使用 sparse_categorical_crossentropy损失函数。
6、如果你需要将数据划分到许多类别中,应该避免使用太小的中间层,以免在网络中造成信息瓶颈。将取值范围差异很大的数据输入到神经网络中,这是有问题的。网络可能会自动适应这种取值范围不同的数据,但学习肯定变得更加困难。对于这种数据,普遍采用的最佳实践是对每个特征做标准化,即对于输入数据的每个特征(输入数据矩阵中的列),减去特征平均值,再除以标准差,这样得到的特征平均值为0,标准差为1。用Numpy 可以很容易实现标准化。
注意,用于测试数据标准化的均值和标准差都是在训练数据上计算得到的。在工作流程中,你不能使用在测试数据上计算得到的任何结果,即使是像数据标准化这么简单的事情也不行。一般来说,训练数据越少,过拟合会越严重,而较小的网络可以降低过拟合。网络的最后一层只有一个单元,没有激活,是一个线性层。这是标量回归(标量回归是预测单一连续值的回归)的典型设置。添加激活函数将会限制输出范围。例如,如果向最后一层添加sigmoid 激活函数,网络只能学会预测0~1 范围内的值。这里最后一层是纯线性的,所以网络可以学会预测任意范围内的值。
注意,编译网络用的是mse 损失函数,即均方误差(MSE),预测值与目标值之差的平方。这是回归问题常用的损失函数。在训练过程中还监控一个新指标:平均绝对误差(MAE)。它是预测值与目标值之差的绝对值。
为了在调节网络参数(比如训练的轮数)的同时对网络进行评估,你可以将数据划分为训练集和验证集,正如前面例子中所做的那样。但由于数据点很少,验证集会非常小(比如大约100个样本)。因此,验证分数可能会有很大波动,这取决于你所选择的验证集和训练集。也就是说,验证集的划分方式可能会造成验证分数上有很大的方差,这样就无法对模型进行可靠的评估。在这种情况下,最佳做法是使用K 折交叉验证。这种方法将可用数据划分为K个分区(K通常取4或5),实例化K个相同的模型,将每个模型在K-1 个分区上训练,并在剩下的一个分区上进行评估。模型的验证分数等于K 个验证分数的平均值。
注意:
1、回归问题使用的损失函数与分类问题不同。回归常用的损失函数是均方误差(MSE)。
2、同样,回归问题使用的评估指标也与分类问题不同。显而易见,精度的概念不适用于回归问题。常见的回归指标是平均绝对误差(MAE)。
3、如果输入数据的特征具有不同的取值范围,应该先进行预处理,对每个特征单独进行缩放。
4、如果可用的数据很少,使用K折验证可以可靠地评估模型。
5、如果可用的训练数据很少,最好使用隐藏层较少(通常只有一到两个)的小型网络,以避免严重的过拟合。
自监督学习是监督学习的一个特例,它与众不同,值得单独归为一类。自监督学习是没有人工标注的标签的监督学习,你可以将它看作没有人类参与的监督学习。标签仍然存在(因为总要有什么东西来监督学习过程),但它们是从输入数据中生成的,通常是使用启发式算法生成的。
举个例子,自编码器是有名的自监督学习的例子,其生成的目标就是未经修改的输入。同样,给定视频中过去的帧来预测下一帧,或者给定文本中前面的词来预测下一个词,都是自监督学习的例子[这两个例子也属于时序监督学习,即用未来的输入数据作为监督]。
注意,监督学习、自监督学习和无监督学习之间的区别有时很模糊,这三个类别更像是没有明确界限的连续体。自监督学习可以被重新解释为监督学习或无监督学习,这取决于你关注的是学习机制还是应用场景。
在强化学习中,智能体(agent)接收有关其环境的信息,并学会选择使某种奖励最大化的行动。例如,神经网络会“观察”视频游戏的屏幕并输出游戏操作,目的是尽可能得高分,这种神经网络可以通过强化学习来训练。
术语:
多分类:一种分类任务,每个输入样本都应被划分到两个以上的类别中,比如手写数字分类。
多标签分类:一种分类任务,每个输入样本都可以分配多个标签。
举个例子,如果一幅图像里可能既有猫又有狗,那么应该同时标注“猫”标签和“狗”标签。每幅图像的标签个数通常是可变的。
标量回归(scalar regression):目标是连续标量值的任务。预测房价就是一个很好的
例子,不同的目标价格形成一个连续的空间。
向量回归(vector regression):目标是一组连续值(比如一个连续向量)的任务。如果对多个值(比如图像边界框的坐标)进行回归,那就是向量回归。
小批量(mini-batch)或批量(batch):模型同时处理的一小部分样本(样本数通常为8~128)。样本数通常取2 的幂,这样便于GPU 上的内存分配。训练时,小批量用来为模型权重计算一次梯度下降更新。
原因在于开发模型时总是需要调节模型配置,比如选择层数或每层大小[这叫作模型的超参数,以便与模型参数(即权重)区分开]。这个调节过程需要使用模型在验证数据上的性能作为反馈信号。这个调节过程本质上就是一种学习:在某个参数空间中寻找良好的模型配置。因此,如果基于模型在验证集上的性能来调节模型配置,会很快导致模型在验证集上过拟合,即使你并没有在验证集上直接训练模型也会如此。
造成这一现象的关键在于信息泄露。每次基于模型在验证集上的性能来调节模型超参数,都会有一些关于验证数据的信息泄露到模型中。如果对每个参数只调节一次,那么泄露的信息很少,验证集仍然可以可靠地评估模型。但如果你多次重复这一过程(运行一次实验,在验证集上评估,然后据此修改模型),那么将会有越来越多的关于验证集的信息泄露到模型中。
最后,你得到的模型在验证集上的性能非常好(人为造成的),因为这正是你优化的目的。你关心的是模型在全新数据上的性能,而不是在验证数据上的性能,因此你需要使用一个完全不同的、前所未见的数据集来评估模型,它就是测试集。你的模型一定不能读取与测试集有关的任何信息,既使间接读取也不行。如果基于测试集性能来调节模型,那么对泛化能力的衡量是不准确的。
数据量较少的时候训练集/测试集/验证集的划分的三种经典的评估方法:
1、简单的留出验证
缺点:如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。
2、K 折验证
3、带有打乱数据的重复K 折验证。
具体做法是多次使用K 折验证,在每次将数据划分为K 个分区之前都先将数据打乱。最终分数是每次K 折验证分数的平均值。注意,这种方法一共要训练和评估P×K 个模型(P是重复次数),计算代价很大。
评估模型的注意事项
选择模型评估方法时,需要注意以下几点。
数据代表性 :你希望训练集和测试集都能够代表当前数据。
例如,你想要对数字图像进行分类,而图像样本是按类别排序的,如果你将前80% 作为训练集,剩余20% 作为测试集,那么会导致训练集中只包含类别0~7,而测试集中只包含类别8~9。这个错误看起来很可笑,却很常见。因此,在将数据划分为训练集和测试集之前,通常应该随机打乱数据。
时间箭头:如果想要根据过去预测未来(比如明天的天气、股票走势等),那么在划分数据前你不应该随机打乱数据,因为这么做会造成时间泄露,你的模型将在未来数据上得到有效训练。在这种情况下,你应该始终确保测试集中所有数据的时间都晚于训练集数据。
数据冗余:如果数据中的某些数据点出现了两次(这在现实中的数据里十分常见),那么打乱数据并划分成训练集和验证集会导致训练集和验证集之间的数据冗余。从效果上来看,你是在部分训练数据上评估模型,这是极其糟糕的!一定要确保训练集和验证集之间没有交集。
数据预处理的目的是使原始数据更适于用神经网络处理,包括向量化、标准化、处理缺失值和特征提取神经网络的所有输入和目标都必须是浮点数张量(在特定情况下可以是整数张量)。无论处理什么数据(声音、图像还是文本),都必须首先将其转换为张量,这一步叫作数据向量化。
一般来说,将取值相对较大的数据(比如多位整数,比网络权重的初始值大很多)或异质数据(比如数据的一个特征在0~1 范围内,另一个特征在100~200 范围内)输入到神经网络中是不安全的。这么做可能导致较大的梯度更新,进而导致网络无法收敛。为了让网络的学习变得更容易,输入数据应该具有以下特征。
1、取值较小:大部分值都应该在 0~1 范围内。
2、同质性:所有特征的取值都应该在大致相同的范围内。
此外,下面这种更严格的标准化方法也很常见,而且很有用,虽然不一定总是必需的(例如,对于数字分类问题就不需要这么做)。
1、将每个特征分别标准化,使其平均值为 0。
2、将每个特征分别标准化,使其标准差为 1。
一般来说,对于神经网络,将缺失值设置为0 是安全的,只要0 不是一个有意义的值。网络能够从数据中学到0 意味着缺失数据,并且会忽略这个值。
注意,如果测试数据中可能有缺失值,而网络是在没有缺失值的数据上训练的,那么网络不可能学会忽略缺失值。在这种情况下,你应该人为生成一些有缺失项的训练样本:多次复制一些训练样本,然后删除测试数据中可能缺失的某些特征。
特征工程是指将数据输入模型之前,利用你自己关于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换(不是模型学到的),以改善模型的效果。多数情况下,一个机器学习模型无法从完全任意的数据中进行学习。呈现给模型的数据应该便于模型进行学习。
,对于现代深度学习,大部分特征工程都是不需要的,因为神经网络能够从原始数据中自动提取有用的特征。这是否意味着,只要使用深度神经网络,就无须担心特征工程呢?
并不是这样,原因有两点。
1、良好的特征仍然可以让你用更少的资源更优雅地解决问题。例如,使用卷积神经网络来读取钟面上的时间是非常可笑的。
2、良好的特征可以让你用更少的数据解决问题。深度学习模型自主学习特征的能力依赖于大量的训练数据。如果只有很少的样本,那么特征的信息价值就变得非常重要。
训练数据上的损失越小,测试数据上的损失也越小。这时的模型是欠拟合的。
为了防止模型从训练数据中学到错误或无关紧要的模式,最优解决方法是获取更多的训练数据。模型的训练数据越多,泛化能力自然也越好。如果无法获取更多数据,次优解决方法是调节模型允许存储的信息量,或对模型允许存储的信息加以约束。如果一个网络只能记住几个模式,那么优化过程会迫使模型集中学习最重要的模式,这样更可能得到良好的泛化。这种降低过拟合的方法叫作正则化。
1、减小网络大小
防止过拟合的最简单的方法就是减小模型大小,即减少模型中可学习参数的个数。在深度学习中,模型中可学习参数的个数通常被称为模型的容量。直观上来看,参数更多的模型拥有更大的记忆容量,因此能够在训练样本和目标之间轻松地学会完美的字典式映射,这种映射没有任何泛化能力。与此相反,如果网络的记忆资源有限,则无法轻松学会这种映射。因此,为了让损失最小化,网络必须学会对目标具有很强预测能力的压缩表示,这也正是我们感兴趣的数据表示。同时请记住,你使用的模型应该具有足够多的参数,以防欠拟合,即模型应避免记忆资源不足。在容量过大与容量不足之间要找到一个折中。
要找到合适的模型大小,一般的工作流程是开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层,直到这种增加对验证损失的影响变得很小。
更大网络的训练损失很快就接近于零,网络的容量越大,它拟合训练数据(即得到很小的训练损失)的速度就越快,但也更容易过拟合(导致训练损失和验证损失有很大差异)。
简单模型是指参数值分布的熵更小的模型(或参数更少的模型)。因此,一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更加规则。这种方法叫作权重正则化,其实现方法是向网络损失函数中添加与较大权重值相关的成本(cost)。
这个成本有两种形式。
1、L1 正则化(L1 regularization):添加的成本与权重系数的绝对值[权重的 L1 范数(norm)]成正比。
2、L2 正则化(L2 regularization):添加的成本与权重系数的平方(权重的L2 范数)成正比。神经网络的L2 正则化也叫权重衰减(weight decay)。不要被不同的名称搞混,权重衰减与L2 正则化在数学上是完全相同的。在Keras 中,添加权重正则化的方法是向层传递权重正则化项实例(weight regularizerinstance)作为关键字参数。
由于这个惩罚项只在训练时添加,所以这个网络的训练损失会比测试损失大很多。
dropout 是神经网络最有效也最常用的正则化方法之一对某一层使用dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为0)。dropout 比率(dropout rate)是被设为0 的特征所占的比例,通常在0.2~0.5范围内。测试时没有单元被舍弃,而该层的输出值需要按dropout 比率缩小,因为这时比训练时有更多的单元被激活,需要加以平衡。
dropout正则化的核心思想是在层的输出值中引入噪声,打破不显著的偶然模式。
防止神经网络过拟合的常用方法包括:
1、获取更多的训练数据
2、减小网络容量
3、添加权重正则化
4、添加 dropout
密集连接层和卷积层的根本区别在于,Dense 层从输入特征空间中学到的是全局模式(比如对于MNIST 数字,全局模式就是涉及所有像素的模式),而卷积层学到的是局部模式这个重要特性使卷积神经网络具有以下两个有趣的性质。
1、卷积神经网络学到的模式具有平移不变性(translation invariant)。卷积神经网络在图像右下角学到某个模式之后,它可以在任何地方识别这个模式,比如左上角。对于密集连接网络来说,如果模式出现在新的位置,它只能重新学习这个模式。这使得卷积神经网络在处理图像时可以高效利用数据(因为视觉世界从根本上具有平移不变性),它只需要更少的训练样本就可以学到具有泛化能力的数据表示。
2、卷积神经网络可以学到模式的空间层次结构(spatial hierarchies of patterns)。
第一个卷积层将学习较小的局部模式(比如边缘),第二个卷积层将学习由第一层特征组成的更大的模式,以此类推。这使得卷积神经网络可以有效地学习越来越复杂、越来越抽象的视觉概念(因为视觉世界从根本上具有空间层次结构)。
对于包含两个空间轴(高度和宽度)和一个深度轴(也叫通道轴)的3D 张量,其卷积也叫特征图(feature map)。对于RGB 图像,深度轴的维度大小等于3,因为图像有3 个颜色通道:红色、绿色和蓝色。对于黑白图像(比如MNIST 数字图像),深度等于1(表示灰度等级)。卷积运算从输入特征图中提取图块,并对所有这些图块应用相同的变换,生成输出特征图(output feature map)。该输出特征图仍是一个3D 张量,具有宽度和高度,其深度可以任意取值,因为输出深度是层的参数,深度轴的不同通道不再像RGB 输入那样代表特定颜色,而是代表过滤器(filter)。过滤器对输入数据的某一方面进行编码,比如,单个过滤器可以从更高层次编码这样一个概念:“输入中包含一张脸。”
卷积由以下两个关键参数所定义。
1、从输入中提取的图块尺寸:这些图块的大小通常是 3×3 或 5×5。本例中为 3×3,这是很常见的选择。
2、输出特征图的深度:卷积所计算的过滤器的数量。本例第一层的深度为32,最后一层的深度是64。
输出的宽度和高度可能与输入的宽度和高度不同。不同的原因可能有两点。
1、边界效应,可以通过对输入特征图进行填充来抵消。
2、使用了步幅(stride),稍后会给出其定义。
如果你希望输出特征图的空间维度与输入相同,那么可以使用填充(padding)。填充是在输入特征图的每一边添加适当数目的行和列,使得每个输入方块都能作为卷积窗口的中心。
对于Conv2D 层,可以通过padding 参数来设置填充,这个参数有两个取值:"valid" 表示不使用填充(只使用有效的窗口位置);"same" 表示“填充后输出的宽度和高度与输入相同”。
padding 参数的默认值为"valid"。
两个连续窗口的距离是卷积的一个参数,叫作步幅,默认值为1。也可以使用步进卷积(strided convolution),即步幅大于1 的卷积。
为了对特征图进行下采样,我们不用步幅,而是通常使用最大池化(max-pooling)运算.最大池化的作用:对特征图进行下采样.
最大池化是从输入特征图中提取窗口,并输出每个通道的最大值。它的概念与卷积类似,但是最大池化使用硬编码的max 张量运算对局部图块进行变换,而不是使用学到的线性变换(卷积核)。最大池化与卷积的最大不同之处在于,最大池化通常使用2×2 的窗口和步幅2,其目的是将特征图下采样2 倍。与此相对的是,卷积通常使用3×3 窗口和步幅1。
使用下采样的原因,一是减少需要处理的特征图的元素个数,二是通过让连续
卷积层的观察窗口越来越大(即窗口覆盖原始输入的比例越来越大),从而引入空间过滤器的层级结构。
注意,最大池化不是实现这种下采样的唯一方法。你已经知道,还可以在前一个卷积层中使用步幅来实现。此外,你还可以使用平均池化来代替最大池化,其方法是将每个局部输入图块变换为取该图块各通道的平均值,而不是最大值。但最大池化的效果往往比这些替代方法更好。
简而言之,原因在于特征中往往编码了某种模式或概念在特征图的不同位置是否存在(因此得名特征图),而观察不同特征的最大值而不是平均值能够给出更多的信息。因此,最合理的子采样策略是首先生成密集的特征图(通过无步进的卷积),然后观察特征每个小图块上的最大激活,而不是查看输入的稀疏窗口(通过步进卷积)或对输入图块取平均,因为后两种方法可能导致错过或淡化特征是否存在的信息。
数据增强(data augmentation),它在计算机视觉领域是一种非常强大的降低过拟合的技术。将深度学习应用于小型数据集的另外两个重要技巧:用预训练的网络做特征提取,对预训练的网络进行微调。
由于卷积神经网络学到的是局部的、平移不变的特征,它对于感知问题可以高效地利用数据。虽然数据相对较少,但在非常小的图像数据集上从头开始训练一个卷积神经网络,仍然可以得到不错的结果,而且无须任何自定义的特征工程。
此外,深度学习模型本质上具有高度的可复用性,比如,已有一个在大规模数据集上训练的图像分类模型或语音转文本模型,你只需做很小的修改就能将其复用于完全不同的问题。特别是在计算机视觉领域,许多预训练的模型(通常都是在ImageNet 数据集上训练得到的)现在都可以公开下载,并可以用于在数据很少的情况下构建强大的视觉模型。
你现在已经知道,将数据输入神经网络之前,应该将数据格式化为经过预处理的浮点数张量。现在,数据以JPEG 文件的形式保存在硬盘中,所以数据预处理步骤大致如下。
(1) 读取图像文件。
(2) 将JPEG 文件解码为RGB 像素网格。
(3) 将这些像素网格转换为浮点数张量。
(4) 将像素值(0~255 范围内)缩放到[0, 1] 区间(正如你所知,神经网络喜欢处理较小的输入值)。
Keras有一个图像处理辅助工具的模块,位于keras.preprocessing.image。特别地,它包含ImageDataGenerator 类,可以快速创建Python 生成器,能够将硬盘上的图像文件自动转换为预处理好的张量批量。
数据增强是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加(augment)样本。其目标是,模型在训练时不会两次查看完全相同的图像。这让模型能够观察到数据的更多内容,从而具有更好的泛化能力。
想要将深度学习应用于小型图像数据集,一种常用且非常高效的方法是使用预训练网络。预训练网络(pretrained network)是一个保存好的网络,之前已在大型数据集(通常是大规模图像分类任务)上训练好。如果这个原始数据集足够大且足够通用,那么预训练网络学到的特征的空间层次结构可以有效地作为视觉世界的通用模型,因此这些特征可用于各种不同的计算机视觉问题,即使这些新问题涉及的类别和原始任务完全不同。举个例子,你在ImageNet 上训练了一个网络(其类别主要是动物和日常用品),然后将这个训练好的网络应用于某个不相干的任务,比如在图像中识别家具。这种学到的特征在不同问题之间的可移植性,是深度学习与许多早期浅层学习方法相比的重要优势,它使得深度学习对小数据问题非常有效。
使用预训练网络有两种方法:特征提取(feature extraction)和微调模型(fine-tuning)。
特征提取是使用之前网络学到的表示来从新样本中提取出有趣的特征。然后将这些特征输入一个新的分类器,从头开始训练。用于图像分类的卷积神经网络包含两部分:首先是一系列池化层和卷积层,最后是一个密集连接分类器。第一部分叫作模型的卷积基(convolutional base)。对于卷积神经网络而言,特征提取就是取出之前训练好的网络的卷积基,在上面运行新数据,然后在输出上面训练一个新的分类器。
为什么仅重复使用卷积基?我们能否也重复使用密集连接分类器?一般来说,应该避免这么做。原因在于卷积基学到的表示可能更加通用,因此更适合重复使用。卷积神经网络的特征图表示通用概念在图像中是否存在,无论面对什么样的计算机视觉问题,这种特征图都可能很有用。但是,分类器学到的表示必然是针对于模型训练的类别,其中仅包含某个类别出现在整张图像中的概率信息。此外,密集连接层的表示不再包含物体在输入图像中的位置信息。密集连接层舍弃了空间的概念,而物体位置信息仍然由卷积特征图所描述。如果物体位置对于问题很重要,那么密集连接层的特征在很大程度上是无用的。
注意,某个卷积层提取的表示的通用性(以及可复用性)取决于该层在模型中的深度。模型中更靠近底部的层提取的是局部的、高度通用的特征图(比如视觉边缘、颜色和纹理),而更靠近顶部的层提取的是更加抽象的概念(比如“猫耳朵”或“狗眼睛”)。因此,如果你的新数据集与原始模型训练的数据集有很大差异,那么最好只使用模型的前几层来做特征提取,而不是使用整个卷积基。
卷积基的使用有两种方法可供选择。
1、在你的数据集上运行卷积基,将输出保存成硬盘中的Numpy 数组,然后用这个数据作为输入,输入到独立的密集连接分类器中(与本书第一部分介绍的分类器类似)。这种方法速度快,计算代价低,因为对于每个输入图像只需运行一次卷积基,而卷积基是目前流程中计算代价最高的。但出于同样的原因,这种方法不允许你使用数据增强。
2、在顶部添加 Dense 层来扩展已有模型(即 conv_base),并在输入数据上端到端地运行整个模型。这样你可以使用数据增强,因为每个输入图像进入模型时都会经过卷积基。但出于同样的原因,这种方法的计算代价比第一种要高很多。
特征提取的第二种方法,它的速度更慢,计算代价更高,但在训练期间可以使用数据增强。这种方法就是:扩展conv_base 模型,然后在输入数据上端到端地运行模型。模型的行为和层类似,所以你可以向Sequential 模型中添加一个模型(比如conv_base),就像添加一个层一样。
(卷积基参数很多所以)在编译和训练模型之前,一定要“冻结”卷积基。冻结(freeze)一个或多个层是指在训练过程中保持其权重不变。如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。因为其上添加的Dense 层是随机初始化的,所以非常大的权重更新将会在网络中传播,对之前学到的表示造成很大破坏。在Keras 中,冻结网络的方法是将其trainable 属性设为False(conv_base.trainable = False)。
注意,为了让这些修改生效,你必须先编译模型。如果在编译之后修改了权重的trainable 属性,那么应该重新编译模型,否则这些修改将被忽略。
另一种广泛使用的模型复用方法是模型微调(fine-tuning),与特征提取互为补充。对于用于特征提取的冻结的模型基,微调是指将其顶部的几层“解冻”,并将这解冻的几层和新增加的部分(本例中是全连接分类器)联合训练。之所以叫作微调,是因为它只是略微调整了所复用模型中更加抽象的表示,以便让这些表示与手头的问题更加相关。
冻结VGG16 的卷积基是为了能够在上面训练一个随机初始化的分类器。同理,只有上面的分类器已经训练好了,才能微调卷积基的顶部几层。如果分类器没有训练好,那么训练期间通过网络传播的误差信号会特别大,微调的几层之前学到的表示都会被破坏。因此,微调网络的步骤如下。
(1) 在已经训练好的基网络(base network)上添加自定义网络。
(2) 冻结基网络。
(3) 训练所添加的部分。
(4) 解冻基网络的一些层。
(5) 联合训练解冻的这些层和添加的部分。
为什么不微调更多层?为什么不微调整个卷积基?你当然可以这么做,但需要考虑以下几点。
. 卷积基中更靠底部的层编码的是更加通用的可复用特征,而更靠顶部的层编码的是更专业化的特征。微调这些更专业化的特征更加有用,因为它们需要在你的新问题上改变用途。微调更靠底部的层,得到的回报会更少。
. 训练的参数越多,过拟合的风险越大。卷积基有 1500 万个参数,所以在你的小型数据集上训练这么多参数是有风险的。因此,在这种情况下,一个好策略是仅微调卷积基最后的两三层。
微调网络时我们将使用学习率非常小的RMSProp 优化器来实现。之所以让学习率很小,是因为对于微调的三层表示,我们希望其变化范围不要太大。太大的权重更新可能会破坏这些表示。
注意,从损失曲线上看不出与之前相比有任何真正的提高(实际上还在变差)。你可能感到奇怪,如果损失没有降低,那么精度怎么能保持稳定或提高呢?答案很简单:图中展示的是逐点(pointwise)损失值的平均值,但影响精度的是损失值的分布,而不是平均值,因为精度是模型预测的类别概率的二进制阈值。
下面是你应该从以上两节的练习中学到的要点。
卷积神经网络是用于计算机视觉任务的最佳机器学习模型。即使在非常小的数据集上也可以从头开始训练一个卷积神经网络,而且得到的结果还不错。
在小型数据集上的主要问题是过拟合。在处理图像数据时,数据增强是一种降低过拟合的强大方法。
利用特征提取,可以很容易将现有的卷积神经网络复用于新的数据集。对于小型图像数据集,这是一种很有价值的方法。
作为特征提取的补充,你还可以使用微调,将现有模型之前学到的一些数据表示应用于新问题。这种方法可以进一步提高模型性能。
可视化卷积神经网络的中间输出(中间激活):有助于理解卷积神经网络连续的层如何对输入进行变换,也有助于初步了解卷积神经网络每个过滤器的含义。
可视化卷积神经网络的过滤器:有助于精确理解卷积神经网络中每个过滤器容易接受的视觉模式或视觉概念。
可视化图像中类激活的热力图:有助于理解图像的哪个部分被识别为属于某个类别,从而可以定位图像中的物体。
可视化中间激活,是指对于给定输入,展示网络中各个卷积层和池化层输出的特征图(层的输出通常被称为该层的激活,即激活函数的输出)。这让我们可以看到输入如何被分解为网络学到的不同过滤器。我们希望在三个维度对特征图进行可视化:宽度、高度和深度(通道)。每个通道都对应相对独立的特征,所以将这些特征图可视化的正确方法是将每个通道的内容分别绘制成二维图像。
为了提取想要查看的特征图,我们需要创建一个Keras 模型,以图像批量作为输入,并输出所有卷积层和池化层的激活。为此,我们需要使用Keras 的Model 类。模型实例化需要两个参数:一个输入张量(或输入张量的列表)和一个输出张量(或输出张量的列表)。得到的类是一个Keras 模型,就像你熟悉的Sequential 模型一样,将特定输入映射为特定输出。Model 类允许模型有多个输出,这一点与Sequential 模型不同
这里需要注意:第一层是各种边缘探测器的集合。在这一阶段,激活几乎保留了原始图像中的所有信息。随着层数的加深,激活变得越来越抽象,并且越来越难以直观地理解。它们开始表示更高层次的概念,比如“猫耳朵”和“猫眼睛”。层数越深,其表示中关于图像视觉内容的信息就越少,而关于类别的信息就越多。激活的稀疏度(sparsity)随着层数的加深而增大。在第一层里,所有过滤器都被输入图像激活,但在后面的层里,越来越多的过滤器是空白的。也就是说,输入图像中找不到这些过滤器所编码的模式。
深度神经网络可以有效地作为信息蒸馏管道,输入原始数据(本例中是RGB 图像),反复对其进行变换,将无关信息过滤掉(比如图像的具体外观),并放大和细化有用的信息(比如图像的类别)。
想要观察卷积神经网络学到的过滤器,另一种简单的方法是显示每个过滤器所响应的视觉模式。这可以通过在输入空间中进行梯度上升来实现:从空白输入图像开始,将梯度下降应用于卷积神经网络输入图像的值,其目的是让某个过滤器的响应最大化。得到的输入图像是选定过滤器具有最大响应的图像。这个过程很简单:我们需要构建一个损失函数,其目的是让某个卷积层的某个过滤器的值最大化;然后,我们要使用随机梯度下降来调节输入图像的值,以便让这个激活值最大化。
为了实现梯度下降,我们需要得到损失相对于模型输入的梯度。为此,我们需要使用Keras的backend模块内置的gradients 函数
为了让梯度下降过程顺利进行,一个非显而易见的技巧是将梯度张量除以其L2 范数(张量中所有值的平方的平均值的平方根)来标准化。这就确保了输入图像的更新大小始终位于相同的范围。
本节将介绍一种可用于解决任何机器学习问题的通用模板。这一模板将你在本章学到的这些概念串在一起:问题定义、评估、特征工程和解决过拟合。
首先,你必须定义所面对的问题。
你的输入数据是什么?你要预测什么?只有拥有可用的训练数据,你才能学习预测某件事情。比如,只有同时拥有电影评论和情感标注,你才能学习对电影评论进行情感分类。因此,数据可用性通常是这一阶段的限制因素(除非你有办法付钱让人帮你收集数据)。 你面对的是什么类型的问题?是二分类问题、多分类问题、标量回归问题、向量回归问题,还是多分类、多标签问题?或者是其他问题,比如聚类、生成或强化学习?确定问题类型有助于你选择模型架构、损失函数等。只有明确了输入、输出以及所使用的数据,你才能进入下一阶段。注意你在这一阶段所做的假设。
假设输出是可以根据输入进行预测的。假设可用数据包含足够多的信息,足以学习输入和输出之间的关系。在开发出工作模型之前,这些只是假设,等待验证真假。并非所有问题都可以解决。你收集了包含输入X 和目标Y 的很多样例,并不意味着X 包含足够多的信息来预测Y。例如,如果你想根据某支股票最近的历史价格来预测其股价走势,那你成功的可能性不大,因为历史价格并没有包含很多可用于预测的信息。
有一类无法解决的问题你应该知道,那就是非平稳问题(nonstationary problem)。假设你想要构建一个服装推荐引擎,并在一个月(八月)的数据上训练,然后在冬天开始生成推荐结果。一个大问题是,人们购买服装的种类是随着季节变化的,即服装购买在几个月的尺度上是一个非平稳现象。你想要建模的对象随着时间推移而改变。在这种情况下,正确的做法是不断地利用最新数据重新训练模型,或者在一个问题是平稳的时间尺度上收集数据。对于服装购买这种周期性问题,几年的数据足以捕捉到季节性变化,但一定要记住,要将一年中的时间作为模型的一个输入。请记住,机器学习只能用来记忆训练数据中存在的模式。你只能识别出曾经见过的东西。在过去的数据上训练机器学习来预测未来,这里存在一个假设,就是未来的规律与过去相同。
但事实往往并非如此。
要控制一件事物,就需要能够观察它。要取得成功,就必须给出成功的定义:精度?准确率(precision)和召回率(recall)?客户保留率?衡量成功的指标将指引你选择损失函数,即模型要优化什么。它应该直接与你的目标(如业务成功)保持一致。
对于平衡分类问题(每个类别的可能性相同),精度和接收者操作特征曲线下面积(area under the receiver operating characteristic curve,ROC AUC)是常用的指标。对于类别不平衡的问题,你可以使用准确率和召回率。对于排序问题或多标签分类,你可以使用平均准确率均值(mean average precision)。自定义衡量成功的指标也很常见。要想了解各种机器学习的成功衡量指标以及这些指标与不同问题域的关系,你可以浏览Kaggle 网站上的数据科学竞赛,上面展示了各种各样的问题和评估指标。
一旦明确了目标,你必须确定如何衡量当前的进展。前面介绍了三种常见的评估方法。 留出验证集。数据量很大时可以采用这种方法。K折交叉验证。如果留出验证的样本量太少,无法保证可靠性,那么应该选择这种方法。重复的 K 折验证。如果可用的数据很少,同时模型评估又需要非常准确,那么应该使用这种方法。只需选择三者之一。大多数情况下,第一种方法足以满足要求。
一旦知道了要训练什么、要优化什么以及评估方法,那么你就几乎已经准备好训练模型了。但首先你应该将数据格式化,使其可以输入到机器学习模型中(这里假设模型为深度神经网络)。
如前所述,应该将数据格式化为张量。
这些张量的取值通常应该缩放为较小的值,比如在 [-1, 1] 区间或 [0, 1] 区间。
如果不同的特征具有不同的取值范围(异质数据),那么应该做数据标准化。
你可能需要做特征工程,尤其是对于小数据问题。
准备好输入数据和目标数据的张量后,你就可以开始训练模型了。
这一阶段的目标是获得统计功效(statistical power),即开发一个小型模型,它能够打败纯随机的基准(dumb baseline)。在MNIST 数字分类的例子中,任何精度大于0.1 的模型都可以说具有统计功效;在IMDB 的例子中,任何精度大于0.5 的模型都可以说具有统计功效。注意,不一定总是能获得统计功效。如果你尝试了多种合理架构之后仍然无法打败随机基准,那么原因可能是问题的答案并不在输入数据中。要记住你所做的两个假设。
假设输出是可以根据输入进行预测的。
假设可用的数据包含足够多的信息,足以学习输入和输出之间的关系。
这些假设很可能是错误的,这样的话你需要从头重新开始。如果一切顺利,你还需要选择三个关键参数来构建第一个工作模型。
1、最后一层的激活。它对网络输出进行有效的限制。例如,IMDB 分类的例子在最后一层使用了sigmoid,回归的例子在最后一层没有使用激活,等等。
2、损失函数。它应该匹配你要解决的问题的类型。例如,IMDB 的例子使用 binary_crossentropy、回归的例子使用mse,等等。
3、优化配置。你要使用哪种优化器?学习率是多少?大多数情况下,使用 rmsprop 及其默认的学习率是稳妥的。
关于损失函数的选择,需要注意,直接优化衡量问题成功的指标不一定总是可行的。有时难以将指标转化为损失函数,要知道,损失函数需要在只有小批量数据时即可计算(理想情况下,只有一个数据点时,损失函数应该也是可计算的),而且还必须是可微的(否则无法用反向传播来训练网络)。例如,广泛使用的分类指标ROC AUC 就不能被直接优化。因此在分类任务中,常见的做法是优化ROC AUC 的替代指标,比如交叉熵。一般来说,你可以认为交叉熵越小,ROC AUC 越大。
表4-1 列出了常见问题类型的最后一层激活和损失函数,可以帮你进行选择。表4-1 为模型选择正确的最后一层激活和损失函数
问题类型 |
最后一层激活 |
损失函数 |
二分类问题 |
sigmoid |
binary_crossentropy |
多分类、单标签问题 |
softmax |
categorical_crossentropy |
多分类、多标签问题 |
sigmoid |
binary_crossentropy |
回归到任意值 |
无 |
mse |
回归到0~1、范围内的值 |
sigmoid |
mse或binary_crossentropy |
一旦得到了具有统计功效的模型,问题就变成了:模型是否足够强大?它是否具有足够多的层和参数来对问题进行建模?例如,只有单个隐藏层且只有两个单元的网络,在MNIST 问题上具有统计功效,但并不足以很好地解决问题。请记住,机器学习中无处不在的对立是优化和泛化的对立,理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上。为了找到这条界线,你必须穿过它。
要搞清楚你需要多大的模型,就必须开发一个过拟合的模型,这很简单。
(1) 添加更多的层。
(2) 让每一层变得更大。
(3) 训练更多的轮次。
要始终监控训练损失和验证损失,以及你所关心的指标的训练值和验证值。如果你发现模型在验证数据上的性能开始下降,那么就出现了过拟合。下一阶段将开始正则化和调节模型,以便尽可能地接近理想模型,既不过拟合也不欠拟合。
这一步是最费时间的:你将不断地调节模型、训练、在验证数据上评估(这里不是测试数据)、再次调节模型,然后重复这一过程,直到模型达到最佳性能。
你应该尝试以下几项:
添加 dropout。
尝试不同的架构:增加或减少层数。
添加 L1 和 / 或 L2 正则化。
尝试不同的超参数(比如每层的单元个数或优化器的学习率),以找到最佳配置。
反复做特征工程:添加新特征或删除没有信息量的特征。
请注意:每次使用验证过程的反馈来调节模型,都会将有关验证过程的信息泄露到模型中。如果只重复几次,那么无关紧要;但如果系统性地迭代许多次,最终会导致模型对验证过程过拟合(即使模型并没有直接在验证数据上训练)。这会降低验证过程的可靠性。
一旦开发出令人满意的模型配置,你就可以在所有可用数据(训练数据+ 验证数据)上训
练最终的生产模型,然后在测试集上最后评估一次。如果测试集上的性能比验证集上差很多,那么这可能意味着你的验证流程不可靠,或者你在调节模型参数时在验证数据上出现了过拟合。在这种情况下,你可能需要换用更加可靠的评估方法,比如重复的K 折验证。
用于处理序列的两种基本的深度学习算法分别是循环神经网络(recurrent neural network)和一维卷积神经网络(1D convnet)。
深度学习用于自然语言处理是将模式识别应用于单词、句子和段落,这与计算机视觉是将模式识别应用于像素大致相同。
与其他所有神经网络一样,深度学习模型不会接收原始文本作为输入,它只能处理数值张量。文本向量化(vectorize)是指将文本转换为数值张量的过程。它有多种实现方法:
将文本分割为单词,并将每个单词转换为一个向量。
将文本分割为字符,并将每个字符转换为一个向量。
提取单词或字符的 n-gram,并将每个 n-gram 转换为一个向量。n-gram 是多个连续单词或字符的集合(n-gram 之间可重叠)。
将文本分解而成的单元(单词、字符或n-gram)叫作标记(token),将文本分解成标记的过程叫作分词(tokenization)。所有文本向量化过程都是应用某种分词方案,然后将数值向量与生成的标记相关联。这些向量组合成序列张量,被输入到深度神经网络中。将向量与标记相关联的方法有很多种。本节将介绍两种主要方法:对标记做one-hot 编码(one-hotencoding)与标记嵌入[token embedding,通常只用于单词,叫作词嵌入(word embedding)]。
n-gram 是从一个句子中提取的N 个(或更少)连续单词的集合。这一概念中的“单词”也可以替换为“字符”。这样的集合分别叫作二元语法袋(bag-of-2-grams)及三元语法袋(bag-of-3-grams)。这里袋(bag)这一术语指的是,我们处理的是标记组成的集合,而不是一个列表或序列,即标记没有特定的顺序。这一系列分词方法叫作词袋(bag-of-words)。
词袋是一种不保存顺序的分词方法(生成的标记组成一个集合,而不是一个序列,舍弃了句子的总体结构),因此它往往被用于浅层的语言处理模型,而不是深度学习模型。提取n-gram 是一种特征工程,深度学习不需要这种死板而又不稳定的方法,并将其替换为分层特征学习。本章后面将介绍的一维卷积神经网络和循环神经网络,都能够通过观察连续的单词序列或字符序列来学习单词组和字符组的数据表示,而无须明确知道这些组的存在。因此,本书不会进一步讨论n-gram。但一定要记住,在使用轻量级的浅层文本处理模型时(比如logistic 回归和随机森林),n-gram 是一种功能强大、不可或缺的特征工程工具。
one-hot 编码是将标记转换为向量的最常用、最基本的方法。它将每个单词与一个唯一的整数索引相关联,然后将这个整数索引i 转换为长度为N 的二进制向量(N 是词表大小),这个向量只有第i 个元素是1,其余元素都为0。当然,也可以进行字符级的one-hot 编码。
Keras 的内置函数可以对原始文本数据进行单词级或字符级的one-hot 编码。你应该使用这些函数,因为它们实现了许多重要的特性,比如从字符串中去除特殊字符、只考虑数据集中前N 个最常见的单词(这是一种常用的限制,以避免处理非常大的输入向量空间)。one-hot 编码的一种变体是所谓的one-hot 散列技巧(one-hot hashing trick),如果词表中唯一标记的数量太大而无法直接处理,就可以使用这种技巧。这种方法没有为每个单词显式分配一个索引并将这些索引保存在一个字典中,而是将单词散列编码为固定长度的向量,通常用一个非常简单的散列函数来实现。这种方法的主要优点在于,它避免了维护一个显式的单词索引,从而节省内存并允许数据的在线编码(在读取完所有数据之前,你就可以立刻生成标记向量)。这种方法有一个缺点,就是可能会出现散列冲突(hash collision),即两个不同的单词可能具有相同的散列值,随后任何机器学习模型观察这些散列值,都无法区分它们所对应的单词。如果散列空间的维度远大于需要散列的唯一标记的个数,散列冲突的可能性会减小。
---------------------------------------------------------------------------
将单词与向量相关联还有另一种常用的强大方法,就是使用密集的词向量(word vector),也叫词嵌入(word embedding)。one-hot 编码得到的向量是二进制的、稀疏的、维度很高的(维度大小等于词表中的单词个数),而词嵌入是低维的浮点数向量(即密集向量,与稀疏向量相对)。与one-hot 编码得到的词向量不同,词嵌入是从数据中学习得到的。常见的词向量维度是256、512 或1024(处理非常大的词表时)。与此相对,onehot编码的词向量维度通常为20 000 或更高(对应包含20 000 个标记的词表)。因此,词向量可以将更多的信息塞入更低的维度中。
one-hot 编码或one-hot 散列得到的词表示是稀疏的、高维的、硬编码的,而词嵌入是密集的、相对低维的,而且是从数据中学习得到的
获取词嵌入有两种方法。
1、在完成主任务(比如文档分类或情感预测)的同时学习词嵌入。在这种情况下,一开始是随机的词向量,然后对这些词向量进行学习,其学习方式与学习神经网络的权重相同。
2、在不同于待解决问题的机器学习任务上预计算好词嵌入,然后将其加载到模型中。这些词嵌入叫作预训练词嵌入(pretrained word embedding)。
合理的做法是对每个新任务都学习一个新的嵌入空间。幸运的是,反向传播让这种学习变得很简单,而Keras 使其变得更简单。我们要做的就是学习一个层的权重,这个层就是Embedding 层。最好将Embedding 层理解为一个字典,将整数索引(表示特定单词)映射为密集向量。它接收整数作为输入,并在内部字典中查找这些整数,然后返回相关联的向量。Embedding 层实际上是一种字典查找。
有时可用的训练数据很少,以至于只用手头数据无法学习适合特定任务的词嵌入。可以使用已有的预计算的词嵌入数据库,如Google的word2vec 算法,其维度抓住了特定的语义属性,比如性别。另一个常用的是GloVe,由斯坦福开发。这种嵌入方法基于对词共现统计矩阵进行因式分解。
前馈网络指没有记忆的神经网络(如密集连接网络和卷积神经网络),它们单独处理每个输入,在输入与输入之间没有保存任何状态。
循环神经网络(RNN)采用同样的原理,不过是一个极其简化的版本:它处理序列的方式是,遍历所有序列元素,并保存一个状态,其中包含与已查看内容相关的信息。实际上,RNN 是一类具有内部环的神经网络。在处理两个不同的独立序列之间,RNN 状态会被重置,因此,你仍可以将一个序列看作单个数据点,即网络的单个输入。真正改变的是,数据点不再是在单个步骤中进行处理,相反,网络内部会对序列元素进行遍历。
RNN 是一个for 循环,它重复使用循环前一次迭代的计算结果。
与Keras 中的所有循环层一样,SimpleRNN 可以在两种不同的模式下运行:一种是返回每个时间步连续输出的完整序列;另一种是只返回每个输入序列的最终输出。为了提高网络的表示能力,将多个循环层逐个堆叠有时也是很有用的。在这种情况下,你需要让所有中间层都返回完整的输出序列。
SimpleRNN 并不是Keras 中唯一可用的循环层,还有另外两个:长短期记忆算法LSTM 和GRU。在实践中总会用到其中之一,因为SimpleRNN 通常过于简化,没有实用价值。SimpleRNN 的最大问题是,在时刻t,理论上来说,它应该能够记住许多时间步之前见过的信息,但实际上它是不可能学到这种长期依赖的。其原因在于梯度消失问题,这一效应类似于在层数较多的非循环网络(即前馈网络)中观察到的效应:随着层数的增加,网络最终变得无法训练。
LSTM 层是SimpleRNN 层的一种变体,它增加了一种携带信息跨越多个时间步的方法。假设有一条传送带,其运行方向平行于你所处理的序列。序列中的信息可以在任意位置跳上传送带,然后被传送到更晚的时间步,并在需要时原封不动地跳回来。这实际上就是LSTM 的原理:它保存信息以便后面使用,从而防止较早期的信号在处理过程中逐渐消失。
1、循环 dropout:这是一种特殊的内置方法,在循环层中使用 dropout来降低过拟合。
2、堆叠循环层。这会提高网络的表示能力(代价是更高的计算负荷)。
3、双向循环层。将相同的信息以不同的方式呈现给循环网络,可以提高精度并缓解遗忘问题。
一种基本的机器学习方法:在尝试机器学习方法之前,建立一个基于常识的基准方法是很有用的;同样,在开始研究复杂且计算代价很高的模型(比如RNN)之前,尝试使用简单且计算代价低的机器学习模型也是很有用的,比如小型的密集连接网络。这可以保证进一步增加问题的复杂度是合理的,并且会带来真正的好处。
我们已经学过降低过拟合的一种经典技术——dropout,即将某一层的输入单元随机设为0,其目的是打破该层训练数据中的偶然相关性。但在循环网络中如何正确地使用dropout,这并不是一个简单的问题。人们早就知道,在循环层前面应用dropout,这种正则化会妨碍学习过程,而不是有所帮助。2015 年,在关于贝叶斯深度学习的博士论文中a,Yarin Gal 确定了在循环网络中使用dropout 的正确方法:对每个时间步应该使用相同的dropout 掩码(dropout mask,相同模式的舍弃单元),而不是让dropout 掩码随着时间步的增加而随机变化。此外,为了对GRU、LSTM 等循环层得到的表示做正则化,应该将不随时间变化的dropout 掩码应用于层的内部循环激活(叫作循环dropout 掩码)。对每个时间步使用相同的dropout 掩码,可以让网络沿着时间正确地传播其学习误差,而随时间随机变化的dropout 掩码则会破坏这个误差信号,并且不利于学习过程。
Yarin Gal 使用Keras 开展这项研究,并帮助将这种机制直接内置到Keras 循环层中。Keras的每个循环层都有两个与dropout 相关的参数:一个是dropout,它是一个浮点数,指定该层输入单元的dropout 比率;另一个是recurrent_dropout,指定循环单元的dropout 比率。我们向GRU 层中添加dropout 和循环dropout,看一下这么做对过拟合的影响。因为使用dropout正则化的网络总是需要更长的时间才能完全收敛,所以网络训练轮次增加为原来的2 倍。
机器学习的通用工作流程:增加网络容量通常是一个好主意,直到过拟合变成主要的障碍(假设你已经采取基本步骤来降低过拟合,比如使用dropout)。只要过拟合不是太严重,那么很可能是容量不足的问题。增加网络容量的通常做法是增加每层单元数或增加层数。循环层堆叠(recurrent layer stacking)是构建更加强大的循环网络的经典方法。
在Keras 中逐个堆叠循环层,所有中间层都应该返回完整的输出序列(一个3D 张量),而不是只返回最后一个时间步的输出。这可以通过指定return_sequences=True 来实现。
双向RNN 是一种常见的RNN 变体,它在某些任务上的性能比普通RNN 更好。它常用于自然语言处理,可谓深度学习对自然语言处理的瑞士军刀。
RNN 特别依赖于顺序或时间,RNN 按顺序处理输入序列的时间步,而打乱时间步或反转时间步会完全改变RNN 从序列中提取的表示。正是由于这个原因,如果顺序对问题很重要,RNN的表现会很好。双向RNN 利用了RNN 的顺序敏感性:它包含两个普通RNN,比如你已经学过的GRU 层和LSTM 层,每个RNN 分别沿一个方向对输入序列进行处理(时间正序和时间逆序),然后将它们的表示合并在一起。通过沿这两个方向处理序列,双向RNN 能够捕捉到可能被单向RNN 忽略的模式。
在Keras 中将一个双向RNN 实例化,我们需要使用Bidirectional 层,它的第一个参数是一个循环层实例。
小结
遇到新问题时,最好首先为你选择的指标建立一个基于常识的基准。如果没有需要打败的基准,那么就无法分辨是否取得了真正的进步。
在尝试计算代价较高的模型之前,先尝试一些简单的模型,以此证明增加计算代价是有意义的。有时简单模型就是你的最佳选择。
如果时间顺序对数据很重要,那么循环网络是一种很适合的方法,与那些先将时间数据展平的模型相比,其性能要更好。
想要在循环网络中使用 dropout,你应该使用一个不随时间变化的 dropout 掩码与循环dropout 掩码。这二者都内置于Keras 的循环层中,所以你只需要使用循环层的dropout和recurrent_dropout 参数即可。
与单个 RNN 层相比,堆叠 RNN 的表示能力更加强大。但它的计算代价也更高,因此不一定总是需要。虽然它在机器翻译等复杂问题上很有效,但在较小、较简单的问题上可能不一定有用。
双向 RNN 从两个方向查看一个序列,它对自然语言处理问题非常有用。但如果在序列数据中最近的数据比序列开头包含更多的信息,那么这种方法的效果就不明显。
循环注意和序列掩码这两个概念通常对自然语言处理特别有用。
卷积神经网络在计算机视觉问题上表现出色,原因在于它能够进行卷积运算,从局部输入图块中提取特征,并能够将表示模块化,同时可以高效地利用数据。
这种一维卷积层可以识别序列中的局部模式。因为对每个序列段执行相同的输入变换,所以在句子中某个位置学到的模式稍后可以在其他位置被识别,这使得一维卷积神经网络具有平移不变性(对于时间平移而言)。字符级的一维卷积神经网络能够学会单词构词法。
你已经学过二维池化运算,比如二维平均池化和二维最大池化,在卷积神经网络中用于对图像张量进行空间下采样。一维也可以做相同的池化运算:从输入中提取一维序列段(即子序列),然后输出其最大值(最大池化)或平均值(平均池化)。与二维卷积神经网络一样,该运算也是用于降低一维输入的长度(子采样)。
一维卷积神经网络的架构与二维卷积神经网络相同,它是Conv1D 层和MaxPooling1D层的堆叠,最后是一个全局池化层或Flatten 层,将三维输出转换为二维输出,让你可以向模型中添加一个或多个Dense 层,用于分类或回归。
不过二者有一点不同:一维卷积神经网络可以使用更大的卷积窗口。对于二维卷积层,3×3 的卷积窗口包含3×3=9 个特征向量;但对于一位卷积层,大小为3 的卷积窗口只包含3个卷积向量。因此,你可以轻松使用大小等于7 或9 的一维卷积窗口。
在单词级的情感分类任务上,一维卷积神经网络可以替代循环网络,并且速度更快、计算代价更低。
一维卷积神经网络分别处理每个输入序列段,所以它对时间步的顺序不敏感(这里所说顺序的范围要大于局部尺度,即大于卷积窗口的大小),这一点与RNN 不同。
要想结合卷积神经网络的速度和轻量与RNN 的顺序敏感性,一种方法是在RNN 前面使用一维卷积神经网络作为预处理步骤。对于那些非常长,以至于RNN 无法处理的序列(比如包含上千个时间步的序列),这种方法尤其有用。卷积神经网络可以将长的输入序列转换为高级特征组成的更短序列(下采样)。然后,提取的特征组成的这些序列成为网络中RNN 的输入。这种方法的速度要快很多。
小结:
二维卷积神经网络在二维空间中处理视觉模式时表现很好,与此相同,一维卷积神经网络在处理时间模式时表现也很好。对于某些问题,特别是自然语言处理任务,它可以替代RNN,并且速度更快。
通常情况下,一维卷积神经网络的架构与计算机视觉领域的二维卷积神经网络很相似,它将Conv1D 层和MaxPooling1D 层堆叠在一起,最后是一个全局池化运算或展平操作。
因为 RNN 在处理非常长的序列时计算代价很大,但一维卷积神经网络的计算代价很小,所以在RNN 之前使用一维卷积神经网络作为预处理步骤是一个好主意,这样可以使序列变短,并提取出有用的表示交给RNN 来处理。
Sequential模型假设网络只有一个输入和一个输出,而且网络是层的线性堆叠。
如果你只有元数据,那么可以使用one-hot 编码,然后用密集连接网络来预测价格。如果你只有文本描述,那么可以使用循环神经网络或一维卷积神经网络。如果你只有图像,那么可以使用二维卷积神经网络。但怎么才能同时使用这三种数据呢?一种朴素的方法是训练三个独立的模型,然后对三者的预测做加权平均。但这种方法可能不是最优的,因为模型提取的信息可能存在冗余。更好的方法是使用一个可以同时查看所有可用的输入模态的模型,从而联合学习一个更加精确的数据模型——这个模型具有三个输入分支。
许多最新开发的神经架构要求非线性的网络拓扑结构,即网络结构为有向无环图。比如,Inception 系列网络依赖于Inception 模块,其输入被多个并行的卷积分支所处理,然后将这些分支的输出合并为单个张量。最近还有一种趋势是向模型中添加残差连接,残差连接是将前面的输出张量与后面的输出张量相加,从而将前面的表示重新注入下游数据流中,这有助于防止信息处理流程中的信息损失。
将Model 对象实例化只用了一个输入张量和一个输出张量。Keras 会在后台检索从input_tensor 到output_tensor 所包含的每一层,并将这些层组合成一个类图的数据结构,即一个Model。当然,这种方法有效的原因在于,output_tensor 是通过对input_tensor 进行多次变换得到的。如果你试图利用不相关的输入和输出来构建一个模型,那么会得到RuntimeError。
函数式API 可用于构建具有多个输入的模型。通常情况下,这种模型会在某一时刻用一个可以组合多个张量的层将不同的输入分支合并,张量组合方式可能是相加、连接等。这通常利用Keras 的合并运算来实现,比如keras.layers.add、keras.layers.concatenate。
使用函数式API 来构建具有多个输出(或多头)的模型。训练这种模型需要能够对网络的各个头指定不同的损失函数,例如,年龄预测是标量回归任务,而性别预测是二分类任务,二者需要不同的训练过程。但是,梯度下降要求将一个标量最小化,所以为了能够训练模型,我们必须将这些损失合并为单个标量。合并不同损失最简单的方法就是对所有损失求和。在Keras 中,你可以在编译时使用损失组成的列表或字典来为不同输出指定不同损失,然后将得到的损失值相加得到一个全局损失,并在训练过程中将这个损失最小化。
注意,严重不平衡的损失贡献会导致模型表示针对单个损失值最大的任务优先进行优化,而不考虑其他任务的优化。为了解决这个问题,我们可以为每个损失值对最终损失的贡献分配不同大小的重要性。如果不同的损失值具有不同的取值范围,那么这一方法尤其有用。比如,用于年龄回归任务的均方误差(MSE)损失值通常在3~5 左右,而用于性别分类任务的交叉熵损失值可能低至0.1。在这种情况下,为了平衡不同损失的贡献,我们可以让交叉熵损失的权重取10,而MSE 损失的权重取0.5。
利用函数式API,我们不仅可以构建多输入和多输出的模型,而且还可以实现具有复杂的内部拓扑结构的网络。Keras 中的神经网络可以是层组成的任意有向无环图(directed acyclic graph)。无环(acyclic)这个限定词很重要,即这些图不能有循环。张量x 不能成为生成x 的某一层的输入。唯一允许的处理循环(即循环连接)是循环层的内部循环。一些常见的神经网络组件都以图的形式实现。两个著名的组件是Inception 模块和残差连接。
卷积能够在输入张量的每一个方块周围提取空间图块,并对所有图块应用相同的变换。极端情况是提取的图块只包含一个方块。这时卷积运算等价于让每个方块向量经过一个Dense 层:它计算得到的特征能够将输入张量通道中的信息混合在一起,但不会将跨空间的信息混合在一起(因为它一次只查看一个方块)。这种1×1 卷积[也叫作逐点卷积]是Inception 模块的特色,它有助于区分开通道特征学习和空间特征学习。如果你假设每个通道在跨越空间时是高度自相关的,但不同的通道之间可能并不高度相关,那么这种做法是很合理的。
残差连接(residual connection)是一种常见的类图网络组件,残差连接解决了困扰所有大规模深度学习模型的两个共性问题:梯度消失和表示瓶颈。通常来说,向任何多于10 层的模型中添加残差连接,都可能会有所帮助。残差连接是让前面某层的输出作为后面某层的输入,从而在序列网络中有效地创造了一条捷径。前面层的输出没有与后面层的激活连接在一起,而是与后面层的激活相加(这里假设两个激活的形状相同)。如果它们的形状不同,我们可以用一个线性变换将前面层的激活改变成目标形状(例如,这个线性变换可以是不带激活的Dense 层;对于卷积特征图,可以是不带激活1×1 卷积)。
如果特征图的尺寸相同,在Keras 中实现残差连接的方法是恒等残差连接。
如果特征图的尺寸不同,实现残差连接的方法是线性残差连接。
如果你对一个层实例调用两次,而不是每次调用都实例化一个新层,那么每次调用可以重复使用相同的权重。
这个LSTM 层的表示(即它的权重)是同时基于两个输入来学习的。我们将其称为连体LSTM或共享LSTM模型。
将模型作为层:在函数式API 中,可以像使用层一样使用模型。实际上,你可以将模型看作“更大的层”。
训练过程中将回调函数作用于模型:回调函数(callback)是在调用fit 时传入模型的一个对象(即实现特定方法的类实例),它在训练过程中的不同时间点都会被模型调用。它可以访问关于模型状态与性能的所有可用数据,还可以采取行动:中断训练、保存模型、加载一组不同的权重或改变模型的状态。
回调函数的一些用法示例如下所示:
1、模型检查点:在训练过程中的不同时间点保存模型的当前权重。
2、提前终止:如果验证损失不再改善,则中断训练(当然,同时保存在训练过程中得到的最佳模型)。
3、在训练过程中动态调节某些参数值:比如优化器的学习率。
4、在训练过程中记录训练指标和验证指标,或将模型学到的表示可视化(这些表示也在不断更新):你熟悉的Keras 进度条就是一个回调函数!
编写你自己的回调函数:实现方式是创建keras.callbacks.Callback 类的子类。
TensorBoard,一个内置于TensorFlow 中的基于浏览器的可视化工具。注意,只有当Keras 使用TensorFlow 后端时,这一方法才能用于Keras 模型。
TensorBoard 的主要用途是,在训练过程中帮助你以可视化的方法监控模型内部发生的一切。如果你监控了除模型最终损失之外的更多信息,那么可以更清楚地了解模型做了什么、没做什么,并且能够更快地取得进展。TensorBoard 具有下列巧妙的功能,都在浏览器中实现:在训练过程中以可视化的方式监控指标;将模型架构可视化;将激活和梯度的直方图可视化;以三维的形式研究嵌入。
EMBEDDINGS(嵌入)标签页让你可以查看输入词表中2000 个单词的嵌入位置和空间关系,它们都是由第一个Embedding 层学到的。因为嵌入空间是128 维的,所以TensorBoard 会使用你选择的降维算法自动将其降至二维或三维,可选的降维算法有主成分分析(PCA)和t-分布随机近邻嵌入(t-SNE)
残差连接、标准化和深度可分离卷积,这些模式在构建高性能深度卷积神经网络时特别重要。
标准化(normalization)是一大类方法,用于让机器学习模型看到的不同样本彼此之间更加相似,这有助于模型的学习与对新数据的泛化。最常见的数据标准化形式:将数据减去其平均值使其中心为0,然后将数据除以其标准差使其标准差为1。实际上,这种做法假设数据服从正态分布(也叫高斯分布),并确保让该分布的中心为0,同时缩放到方差为1。
批标准化即使在训练过程中均值和方差随时间发生变化,它也可以适应性地将数据标准化。批标准化的工作原理是,训练过程中在内部保存已读取每批数据均值和方差的指数移动平均值。批标准化的主要效果是,它有助于梯度传播(这一点和残差连接很像),因此允许更深的网络。对于有些特别深的网络,只有包含多个BatchNormalization 层时才能进行训练。例如,BatchNormalization 广泛用于Keras 内置的许多高级卷积神经网络架构,比如ResNet50、Inception V3 和Xception。
BatchNormalization 层通常在卷积层或密集连接层之后使用。
深度可分离卷积层(SeparableConv2D)的作用。这个层对输入的每个通道分别执行空间卷积,然后通过逐点卷积(1×1 卷积)将输出通道混合,这相当于将空间特征学习和通道特征学习分开,如果你假设输入中的空间位置高度相关,但不同的通道之间相对独立,那么这么做是很有意义的。它需要的参数要少很多,计算量也更小,因此可以得到更小、更快的模型。因为它是一种执行卷积更高效的方法,所以往往能够使用更少的数据学到更好的表示,从而得到性能更好的模型。
对于规模更大的模型,深度可分离卷积是Xception 架构的基础,Xception 是一个高性能的卷积神经网络,内置于Keras 中。
超参数优化,你需要制定一个原则,系统性地自动探索可能的决策空间。你需要搜索架构空间,并根据经验找到性能最佳的架构。这正是超参数自动优化领域的内容。超参数优化的过程通常如下所示。
(1) 选择一组超参数(自动选择)。
(2) 构建相应的模型。
(3) 将模型在训练数据上拟合,并衡量其在验证数据上的最终性能。
(4) 选择要尝试的下一组超参数(自动选择)。
(5) 重复上述过程。
(6) 最后,衡量模型在测试数据上的性能。
这个过程的关键在于,给定许多组超参数,使用验证性能的历史来选择下一组需要评估的超参数的算法。有多种不同的技术可供选择:贝叶斯优化、遗传算法、简单随机搜索等。训练模型权重相对简单:在小批量数据上计算损失函数,然后用反向传播算法让权重向正确的方向移动。与此相反,更新超参数则非常具有挑战性。我们来考虑以下两点。
计算反馈信号(这组超参数在这个任务上是否得到了一个高性能的模型)的计算代价可能非常高,它需要在数据集上创建一个新模型并从头开始训练。
超参数空间通常由许多离散的决定组成,因而既不是连续的,也不是可微的。因此,你通常不能在超参数空间中做梯度下降。相反,你必须依赖不使用梯度的优化方法,而这些方法的效率比梯度下降要低很多。
这些挑战非常困难,而这个领域还很年轻,因此我们目前只能使用非常有限的工具来优化模型。通常情况下,随机搜索(随机选择需要评估的超参数,并重复这一过程)就是最好的解决方案,虽然这也是最简单的解决方案。但我发现有一种工具确实比随机搜索更好,它就是Hyperopt。它是一个用于超参数优化的Python 库,其内部使用Parzen 估计器的树来预测哪组超参数可能会得到好的结果。另一个叫作Hyperas 的库将Hyperopt 与Keras 模型集成在一起。
注意 在进行大规模超参数自动优化时,有一个重要的问题需要牢记,那就是验证集过拟合。因为你是使用验证数据计算出一个信号,然后根据这个信号更新超参数,所以你实际上是在验证数据上训练超参数,很快会对验证数据过拟合。
模型集成是指将一系列不同模型的预测结果汇集到一起,从而得到更好的预测结果。
将分类器集成有一个更聪明的做法,即加权平均,其权重在验证数据上学习得到。通常来说,更好的分类器被赋予更大的权重,而较差的分类器则被赋予较小的权重。为了找到一组好的集成权重,你可以使用随机搜索或简单的优化算法(比如Nelder-Mead 方法)。还有许多其他变体,比如你可以对预测结果先取指数再做平均。一般来说,简单的加权平均,其权重在验证数据上进行最优化,这是一个很强大的基准方法。想要保证集成方法有效,关键在于这组分类器的多样性(diversity)。多样性就是力量。
集成的模型应该尽可能好,同时尽可能不同。这通常意味着使用非常不同的架构,甚至使用不同类型的机器学习方法。有一件事情基本上是不值得做的,就是对相同的网络,使用不同的随机初始化多次独立训练,然后集成。如果模型之间的唯一区别是随机初始化和训练数据的读取顺序,那么集成的多样性很小,与单一模型相比只会有微小的改进。
DeepDream 的过程是反向运行一个卷积神经网络,基于网络学到的表示来生成输入。得到的结果是很有趣的,有些类似于通过迷幻剂扰乱视觉皮层而诱发的视觉伪影。 注意,这个过程并不局限于图像模型,甚至并不局限于卷积神经网络。它可以应用于语音、音乐等更多内容。
神经风格迁移是指将参考图像的风格应用于目标图像,同时保留目标图像的内容。
网络更靠底部的层激活包含关于图像的局部信息,而更靠近顶部的层则包含更加全局、更加抽象的信息。卷积神经网络不同层的激活用另一种方式提供了图像内容在不同空间尺度上的分解。因此,图像的内容是更加全局和抽象的,我们认为它能够被卷积神经网络更靠顶部的层的表示所捕捉到。
神经风格迁移可以用任何预训练卷积神经网络来实现,例如VGG19 网络。VGG19 是第5 章介绍的VGG16 网络的简单变体,增加了三个卷积层。
神经风格迁移的一般过程如下。
(1) 创建一个网络,它能够同时计算风格参考图像、目标图像和生成图像的VGG19 层激活。
(2) 使用这三张图像上计算的层激活来定义之前所述的损失函数,为了实现风格迁移,需要将这个损失函数最小化。
(3) 设置梯度下降过程来将这个损失函数最小化。
风格迁移是指创建一张新图像,保留目标图像的内容的同时还抓住了参考图像的风格。内容可以被卷积神经网络更靠顶部的层激活所捕捉到。风格可以被卷积神经网络不同层激活的内部相互关系所捕捉到。因此,深度学习可以将风格迁移表述为一个最优化过程,并用到了一个用预训练卷积神经网络所定义的损失。