Logistic 回归(或者叫逻辑回归,logistic regression)是一个用于二分分类(binary classification)的算法。比如输入一张图片,判断是否为猫,结果用表示,表示是猫,表示不是猫。计算机中图片是以红、绿、蓝三种颜色通道的三个矩阵来存储一张图片的,比如图片大小为像素,那么有三个规模为的矩阵,为了计算方便,我们会把这些像素矩阵的值竖着放入一个特征向量中(如下图),这样的话的维度来表示,有时也写做.
Logistic 回归中使用的参数如下:
为将约束在间,引入 Sigmoid函数。从下图可看出,Sigmoid函数的值域为。
Logistic回归可以看作是一个非常小的神经网络。下图是一个典型例子:
损失函数(loss function)用于衡量预测结果与真实值之间的误差。
最简单的损失函数定义方式为平方差损失:
但 Logistic回归中我们并不倾向于使用这样的损失函数,因为之后讨论的优化问题会变成非凸的,最后会得到很多个局部最优解,梯度下降法可能找不到全局最优值。
一般使用
为了更好地理解这个损失函数怎么起作用,我们举两个例子:
- 当时损失函数,如果想要损失函数尽可能得小,那么就要尽可能大,因为sigmoid函数取值,所以会无限接近于1.
- 当时损失函数,如果想要损失函数尽可能得小,那么就要尽可能小,因为sigmoid函数取值,所以会无限接近于0.
损失函数是在单个训练样本中定义的,它衡量了在单个训练样本上的表现。而代价函数(cost function,或者称作成本函数)衡量的是在全体训练样本上的表现,即衡量参数和的效果。
补充:logistic损失函数的解释
回想一下,在逻辑回归中,需要预测的结果可以表示为,是我们熟悉的型函数。我们约定,即算法的输出是给定训练样本条件下等于1的概率,换句话说,如果,在给定训练样本条件下;反过来说,如果,在给定训练样本条件下等于1减去,因此,如果 代表 的概率,那么就是 的概率。接下来,我们就来分析这两个条件概率公式
这两个条件概率公式定义形式为 并且代表了或者这两种情况,我们可以将这两个公式合并成一个公式。需要指出的是,我们讨论的是二分类问题的损失函数,因此,的取值只能是0或者1。上述的两个条件概率公式可以合并成
接下来我会解释为什么可以合并成这种形式的表达式:
因此,又由于函数是严格单调递增的函数,最大化等价于最大化,将代入并化简为,而这就是我们前面提到的损失函数的负数,前面有一个负号的原因是当你训练学习算法时需要算法输出值的概率是最大的(以最大的概率预测这个值),然而在逻辑回归中我们需要最小化损失函数,因此最小化损失函数与最大化条件概率的对数关联起来了,因此这就是单个训练样本的损失函数表达式。
在 个训练样本的整个训练集中又该如何表示呢,让我们一起来探讨一下。
假设所有的训练样本服从同一分布且相互独立,也即独立同分布的,所有这些样本的联合概率就是每个样本概率的乘积:。
如果你想做最大似然估计,需要寻找一组参数,使得给定样本的观测值概率最大,但令这个概率最大化等价于令其对数最大化,在等式两边取对数:
在统计学里面,有一个方法叫做最大似然估计,即求出一组参数,使这个式子取最大值,,可以将负号移到求和符号的外面,,这样我们就推导出了前面给出的logistic回归的成本函数。
由于训练模型时,目标是让成本函数最小化,所以我们不是直接用最大似然概率,要去掉这里的负号,最后为了方便,可以对成本函数进行适当的缩放,我们就在前面加一个额外的常数因子,即:。
总结一下,为了最小化成本函数,我们从logistic回归模型的最大似然估计的角度出发,假设了训练集中的样本都是独立同分布的。
函数的梯度(gradient)指出了函数的最陡增长方向。即是说,按梯度的方向走,函数增长得就越快。那么按梯度的负方向走,函数值自然就降低得最快了。
模型的训练目标即是寻找合适的与以最小化代价函数值。简单起见我们先假设与都是一维实数,那么可以得到如下的关于与的图:
可以看到,成本函数是一个凸函数,与非凸函数的区别在于其不含有多个局部最低点;选择这样的代价函数就保证了无论我们初始化模型参数如何,都能够寻找到合适的最优解。
参数的更新公式为:
其中就是函数对求导(derivative),导数可以理解为斜率,如下图,沿着导数的方向可以达到最小值处;表示学习速率(learning rate),即每次更新的的步伐长度。
当大于最优解时,导数大于,那么就会向更小的方向更新。反之当小于最优解时,导数小于,那么就会向更大的方向更新。迭代直到收敛。
在成本函数中还存在参数,因此也有:
神经网络的计算过程中,通常有一个正向过程(forward pass)或者叫正向传播步骤(forward propagation step),接着会有一个反向过程(backward pass)或者叫反向传播步骤(backward propagation step)。所谓的反向传播即是当我们需要计算最终值相对于某个特征变量的导数时,我们需要利用计算图中上一步的结点定义。
计算图(Computation Graph)
可以说,一个神经网络的计算,都是按照前向或反向传播过程组织的。首先我们计算出一个新的网络的输出(前向过程),紧接着进行一个反向传输操作。后者我们用来计算出对应的梯度或导数。计算图解释了为什么我们用这种方式组织这些计算过程。
让我们举一个不那么正式的神经网络的例子:我们尝试计算函数,计算这个函数实际上有三个不同的步骤,首先是计算乘以,我们把它储存在变量中,因此; 然后计算;最后输出。我们可以把这三步画成像下面的计算图,通过一个从左向右的过程,你可以计算出的值。为了计算导数,从右到左(红色箭头)的过程是用于计算导数最自然的方式。
使用计算图求导数(Derivatives with a Computation Graph)
现在我们清理一下计算图的描述,看看你如何利用它计算出函数的导数。
下面用到的公式:,,
这是一个计算图:
要计算,先定义,现在,如果让增加一点点,比如到11.001,那么,即增加了0.001,最终结果是上升到原来的3倍,所以。可以看到,如果想计算最后输出变量的导数,使用你最关心的变量对的导数,那么就做完了一步反向传播。
再比如,计算,变量,我们让它增加到5.001,那么对的影响就是,之前,现在变成11.001,我们从上面看到现在 就变成33.003了,所以如果让增加0.001,增加0.003。那么增加,我是说如果你把这个5换成某个新值,那么的改变量就会传播到流程图的最右,所以最后是33.003。所以的增量是3乘以的增量,意味着这个导数是3。首先a增加了,也会增加,增加多少取决于,然后的变化导致也在增加,所以这在微积分里实际上叫链式法则。
最后,计算,如果使用链式法则是,理由是,如果你改变一点点,比如增加到3.001,它首先会影响,的定义是,所以时是,现在就变成6.002了,所以这告诉我们,而,所以让这两部分相乘,我们发现。
所以当计算所有这些导数时,最有效率的办法是从右到左计算。一般是正向或者说从左到右的计算来计算成本函数,然后反向从右到左计算导数。
假设输入的特征向量维度为,即输入参数共有这五个。可以推导出如下的计算图:
首先反向求出对于的导数:
然后继续反向求出对于的导数:
其中.
最后计算和变化对代价函数的影响(下式左边),更一般地,如果在整个训练集上考虑(下式右边),还会有:
我们需要将对于单个用例的损失函数扩展到整个训练集的代价函数:
我们可以对于某个权重参数,其导数计算为:
和同理,所以有后面的公式。
依此类推求出最终的损失函数相较于原始参数的导数之后,根据如下公式进行参数更新:
完整的Logistic回归中某次训练的流程如下,这里仅假设特征向量的维度为:
上述过程在计算时有一个缺点:你需要编写两个for循环。外面的for循环遍历m个样本,而里面的for循环遍历所有特征。如果有大量特征,在代码中显式使用for循环会使算法很低效。向量化(Vectorization)可以用于解决显式使用for循环的问题。
一些Python编程的技巧和事项
向量化(Vectorization)
在 Logistic 回归中,需要计算 如果是非向量化的循环方式操作,代码可能如下:
= 0;
z for i in range(n_x):
+= w[i] * x[i]
z += b z
而如果是向量化的操作,代码则会简洁很多,并带来近百倍的性能提升(并行指令):
= np.dot(w, x) + b z
不用显式for循环,实现Logistic回归的梯度下降一次迭代(伪代码,部分是NumPy的语法):
正向和反向传播尽管如此,多次迭代的梯度下降依然需要for循环(上面的伪代码只是一次迭代的过程)。
广播(broadcasting):Numpy的Universal functions中要求输入的数组shape是一致的。当数组的shape不相等的时候,则会使用广播机制,调整数组使得shape一样,满足规则,则可以运算,否则就出错。
四条规则:
NumPy 使用技巧:转置对秩为的数组无效。因此,应该避免使用秩为的数组,用的矩阵代替。例如,用np.random.randn(5,1)
代替np.random.randn(5)
。
如果得到了一个秩为的数组,可以使用reshape
进行转换。
竖向堆叠起来的输入特征、、被称作神经网络的输入层(the input layer)。
中间的是神经网络的隐藏层(a hidden layer),“隐藏”的含义是在训练集中,这些中间节点的真正数值是无法看到的。
输出层(the output layer)负责输出预测值。
如图是一个双层神经网络,也称作单隐层神经网络(a single hidden layer neural network)。当我们计算网络的层数时,通常不考虑输入层,因此图中隐藏层是第一层,输出层是第二层。我们会使用符号表示第层网络中节点相关的数,这些节点的集合被称为第层网络。
符号约定(以上图为例):
输入层的这些写作,表示它们是激活值,“激活”是指网络中不同层的值会传递到它们后面的层中;
同样,隐藏层也会产生一些激活值,记作,隐藏层的第一个单元(或者说节点)就记作,依此类推,输出层同理;
输出层将产生某个数值,它是一个单独的实数,最后的值将取为;
这里的和前面Logistic回归中的很像,也是指输出的结果,只不过这里有多层,需要将前面一步得到的结果作为输入多次计算,所以需要上标。
另外,隐藏层和输出层都是带有参数和的。它们都使用上标来表示是和第一个隐藏层有关,或者上标来表示是和输出层有关;
单样本的情况
实际上,神经网络只不过将Logistic 回归的计算步骤重复很多次。
对于隐藏层的第一个节点,有 和,后面的节点也是一样,于是有:
和
和
和
和
如果令这些分别竖向堆叠起来记作,按行堆叠记作,那么第一个隐藏层的公式是:和,其中可以是一个列向量,也可以将多个列向量横向堆叠起来得到矩阵,如果是后者的话,得到的和也是一个矩阵。
是一个列向量:比如输入层的竖向堆叠起来。
是矩阵:比如多个样本这些列向量横向堆叠,而每个样本又有个特征,就是的矩阵;这时比如第2个样本第1层第3个单元就写成,下标是单元序号、上标中括号是层序号、上标圆括号是样本序号。
同理,对于输出层有:、.
值得注意的是层与层之间参数矩阵的规格大小(以前面的图为例):
多样本的情况
如果有个训练样本(即是多个列向量堆叠而成的矩阵时),那么就需要重复这个过程:
也就是让从1到实现这四个等式: 样本隐藏层:、 隐藏层输出层:、
、等也分别组成列向量然后横向堆叠起来,向量化后就可以写成: 样本隐藏层:、 隐藏层输出层:、
其中的每一列都对应了,如下图所示(这里为了简化运算把设置为0):
有一个问题是神经网络的隐藏层和输出单元用什么激活函数(Activation functions)。之前我们都是选用sigmoid函数,但有时其他函数的效果会好得多。
可供选用的激活函数有:
效果几乎总比 sigmoid 函数好(除开二元分类的输出层,因为我们希望输出的结果介于0到1之间),因为函数输出介于-1和1之间,激活函数的平均值就更接近0,有类似数据中心化的效果。然而,函数存在和sigmoid函数一样的缺点:当趋紧无穷大(或无穷小),导数的梯度(即函数的斜率)就趋紧于0,这使得梯度算法的速度大大减缓。
当时,梯度始终为1,从而提高神经网络基于梯度算法的运算速度,收敛速度远大于sigmoid和tanh。然而当时,梯度一直为0,但是实际的运用中,该缺陷的影响不是很大。
Leaky ReLU保证在的时候,梯度仍然不为0。理论上来说,Leaky ReLU有ReLU的所有优点,但在实际操作中没有证明总是好于ReLU,因此不常用。
激活函数一般记作。在选择激活函数的时候,如果在不知道该选什么的时候就选择ReLU(二元分类的输出层要用sigmoid函数其余层可以用Relu),当然也没有固定答案,要依据实际问题在交叉验证集合中进行验证分析。当然,我们可以在不同层选用不同的激活函数,这时就需要给激活函数添加上标,比如.
使用非线性激活函数的原因:使用线性激活函数与不使用激活函数直接使用Logistic 回归没有区别,无论神经网络有多少层,输出都是输入的线性组合,效果与没有隐藏层相当(其实这就是最原始的感知器)。虽然隐藏层不能用线性激活函数,但是输出层还是可以用的。不过这也不是绝对的,一些特殊问题,比如压缩问题,有时隐藏层也会用线性激活函数。
附:激活函数的导数
激活函数名 | 激活函数 | 导数 |
---|---|---|
sigmoid函数 | ||
tanh函数 | ||
Relu函数 |
正向梯度下降就是计算、、、的过程,而反向梯度下降是指求导(因为求导在计算图中是反向的),之所以要求导是因为要迭代更新和,可以参考前面《使用梯度下降法计算Logistic 回归》相关推导。
正向梯度下降(假设是二元分类,输出层用sigmoid函数)
反向梯度下降
神经网络反向梯度下降公式(左)和其向量化后的代码(右):
np.sum
是python的numpy命令,axis=1
表示水平相加求和,keepdims
是防止python输出那些古怪的秩数。左边的、、是指损失函数分别对、、的导数,这些公式的推导可以参考前面《使用梯度下降法计算Logistic 回归》,向量化后因为是在全体样本上考虑的,所以把损失函数换成了代价函数,因此部分式子多了。这里的损失函数和代价函数和Logistic 回归是一样的。
这里假设是二元分类所以输出层用的是sigmoid函数,其导数可以求出来,但是隐藏层的激活函数未知,所以的形式和有所不同,需要计算激活函数的导数,而且表示的是逐元素乘积。注意这时的链式法则是,可以自己画一个计算图看看,我们会把第一层的预测值代入到第二层的中,所以,结合前面的,而且(也表示激活函数的意思,预测值就是激活函数的值),而且由于公式原本是数乘,换到矩阵上就是逐项相乘。
计算完正向和反向梯度下降后,再通过、、、更新、、、(其中是学习速率,即步长),然后再计算下一层,依此类推,这就是一个完整的神经网络计算过程。
如果在初始时将两个隐藏神经元的参数设置为相同的大小,那么两个隐藏神经元对输出单元的影响也是相同的,通过反向梯度下降去进行计算的时候,会得到同样的梯度大小,所以在经过多次迭代后,两个隐藏层单位仍然是对称的。无论设置多少个隐藏单元,其最终的影响都是相同的,那么多个隐藏神经元就没有了意义。
在初始化的时候,参数要进行随机初始化(Random Initialization),不可以设置为0。而因为不存在对称性的问题,可以设置为0。
以2个输入,2个隐藏神经元为例:
= np.random.rand(2,2)* 0.01
W = np.zeros((2,1)) b
这里将的值乘以 0.01(或者其他的常数值)的原因是为了使得权重初始化为较小的值,这是因为使用sigmoid函数或者tanh函数作为激活函数时,比较小,则所得的值趋近于0,梯度较大,能够提高算法的更新速度。而如果设置的太大的话,得到的梯度较小,训练过程因此会变得很慢。ReLU和Leaky ReLU作为激活函数时不存在这种问题,因为在大于0的时候,梯度均为1。
符号约定(以上图为例):我们用表示神经网络的层数(讨论层数时不包括输入层),如图,用表示第层的单元数量(输入层是第0层),比如、,用表示第层的激活函数,特别地,.
前向传播(Forward propagation)
输入:
输出:,cache()
公式:
反向传播(Backward propagation)
输入:
输出:,,
公式:
推导过程参考《使用梯度下降法计算Logistic 回归》和《神经网络的梯度下降法》,其中表示逐项相乘
算完前向和反向传播后,还要用和更新、,才计算完一层神经网络。
要弄清楚这些矩阵的维度:
在计算反向传播时,、、的维度和、、是一样的。
注意:这里,是没有转置的,和前面几节中定义的不一样,前面几节的行数列数要对应地调换一下,反正就是的系数矩阵维度是。后面如果发现维度不对,可能是定义的方式不同。
以前面的图中的4层神经网络中的第一层为例:
- 向量化之前:(这说明了和的维度是一样的,且的行数就是单元数)、(这说明了和的维度是一样的)、、
- 向量化之后(假设有2个样本):
也就是说,想要下一层有多少个节点,就把和的行数定义成多少。
对于人脸识别,神经网络的第一层从原始图片中提取人脸的轮廓和边缘,每个神经元学习到不同边缘的信息;网络的第二层将第一层学得的边缘信息组合起来,形成人脸的一些局部的特征,例如眼睛、嘴巴等;后面的几层逐步将上一层的特征组合起来,形成人脸的模样。随着神经网络层数的增加,特征也从原来的边缘逐步扩展为人脸的整体,由整体到局部,由简单到复杂。层数越多,那么模型学习的效果也就越精确。
同样的,对于语音识别,第一层神经网络可以学习到语言发音的一些音调,后面更深层次的网络可以检测到基本的音素,再到单词信息,逐渐加深可以学到短语、句子。
通过例子可以看到,随着神经网络的深度加深,模型能学习到更加复杂的问题,功能也更加强大。
神经网络的一步训练(一个梯度下降循环),包含了从(即)经过一系列正向传播计算得到(即)。然后再计算,开始实现反向传播,用链式法则得到所有的导数项,和也会在每一层被更新。
在代码实现时,可以将正向传播过程中计算出来的值缓存下来,待到反向传播计算时使用。
补充一张图,有助于理解整个过程:
参数(parameters)即是我们在过程中想要模型学习到的信息(模型自己能计算出来的),例如,。而超参数(hyper parameters)即为控制参数的输出值的一些网络信息(需要人经验判断)。超参数的改变会导致最终得到的参数,的改变。
典型的超参数有:
当开发新应用时,预先很难准确知道超参数的最优值应该是什么。因此,通常需要尝试很多不同的值。应用深度学习领域是一个很大程度基于经验的过程。
应用深度学习是一个典型的迭代过程。
对于一个需要解决的问题的样本数据,在建立模型的过程中,数据会被划分为以下几个部分:
在小数据量的时代,如100、1000、10000的数据量大小,可以将数据集按照以下比例进行划分:
而在如今的大数据时代,对于一个问题,我们拥有的数据集的规模可能是百万级别的,所以验证集和测试集所占的比重会趋向于变得更小。
验证集的目的是为了验证不同的算法哪种更加有效,所以验证集只要足够大到能够验证大约2-10种算法哪种更好,而不需要使用20%的数据作为验证集。如百万数据中抽取1万的数据作为验证集就可以了。
测试集的主要目的是评估模型的效果,如在单个分类器中,往往在百万级别的数据中,我们选择其中1000条数据足以评估单个模型的效果。
建议:验证集要和训练集来自于同一个分布(数据来源一致),可以使得机器学习算法变得更快并获得更好的效果。
如果不需要用无偏估计来评估模型的性能,则可以不需要测试集。
补充-交叉验证(cross validation):交叉验证的基本思想是重复地使用数据;把给定的数据进行切分,将切分的数据集组合为训练集与测试集,在此基础上反复地进行训练、测试以及模型选择。
偏差-方差分解(bias-variance decomposition)是解释学习算法泛化性能的一种重要工具。
泛化误差可分解为偏差、方差与噪声之和:
偏差-方差分解说明,泛化性能是由学习算法的能力、数据的充分性以及学习任务本身的难度所共同决定的。给定学习任务,为了取得好的泛化性能,则需要使偏差较小,即能够充分拟合数据,并且使方差较小,即使得数据扰动产生的影响小。
在欠拟合(underfitting)的情况下,出现高偏差(high bias)的情况,即不能很好地对数据进行分类。
当模型设置的太复杂时,训练集中的一些噪声没有被排除,使得模型出现过拟合(overfitting)的情况,在验证集上出现高方差(high variance)的现象。
当训练出一个模型以后,如果:
偏差和方差的权衡问题对于模型来说十分重要。最优误差通常也称为贝叶斯误差。
应对方法
存在高偏差:
存在高方差:
不断尝试,直到找到低偏差、低方差的框架。
在深度学习的早期阶段,没有太多方法能做到只减少偏差或方差而不影响到另外一方。而在大数据时代,深度学习对监督式学习大有裨益,使得我们不用像以前一样太过关注如何平衡偏差和方差的权衡问题,通过以上方法可以在不增加某一方的前提下减少另一方的值。
正则化(regularization)是在成本函数中加入一个正则化项,惩罚模型的复杂度。正则化可以用于解决高方差的问题。
对于Logistic回归,加入L2正则化(也称“L2 范数”)的成本函数:
其中,为正则化因子,是超参数,它是非负的。
由于L1正则化最后得到向量中将存在大量的0,使模型变得稀疏化,因此L2正则化更加常用。
之所以只正则化而不加上,是因为是一个高维向量,已经足以表达高方差问题,当然,你加上也是可以的。
对于神经网络,加入正则化的成本函数:
因为的大小为 ,所以,该矩阵范数被称为弗罗贝尼乌斯范数(Frobenius Norm),所以神经网络中的正则化项被称为弗罗贝尼乌斯范数矩阵。
在加入正则化项后,梯度变为(反向传播要按这个计算):
代入梯度更新公式:,可得:
其中,因为,会给原来的一个衰减的参数,因此L2正则化项也被称为权重衰减(Weight Decay)。
直观解释
正则化因子设置的足够大的情况下,为了使成本函数最小化,权重矩阵就会被设置为接近于0的值,直观上相当于消除了很多神经元的影响,那么大的神经网络就会变成一个较小的网络。当然,实际上隐藏层的神经元依然存在,但是其影响减弱了,便不会导致过拟合。
数学解释
假设神经元中使用的激活函数为(sigmoid 同理)。
在加入正则化项后,当增大,导致减小,便会减小。由上图可知,在较小(接近于0)的区域里,函数近似线性,所以每层的函数就近似线性函数,整个网络就成为一个简单的近似线性的网络,因此不会发生过拟合。
其他解释
在权值变小之下,输入样本随机的变化不会对神经网络模造成过大的影响,神经网络受局部噪音的影响的可能性变小。这就是正则化能够降低模型方差的原因。
dropout(随机失活)是在神经网络的隐藏层为每个神经元结点设置一个随机消除的概率,保留下来的神经元形成一个结点较少、规模较小的网络用于训练。dropout 正则化较多地被使用在计算机视觉(Computer Vision)领域。
反向随机失活(Inverted dropout)是实现dropout的一种方法。具体做法是,比如对第层进行dropout:
= 0.8 # 设置神经元保留概率为80%,你也可以改成自己想要的保留概率,且每一层都可以不同
keep_prob = np.random.rand(al.shape[0], al.shape[1]) < keep_prob # shape[0]是矩阵的行数、shape[1]是列数
dl = np.multiply(al, dl) # np.multiply是对应位置的元素相乘
al /= keep_prob al
最后一步al /= keep_prob
是因为中的一部分元素失活(相当于被归零),为了在下一层计算时不影响的期望值,因此除以一个keep_prob
。
关于“不影响期望值”的解释:
部分元素随机失活后,期望,其中是随机失活后的
al
,所以就可以让期望仍然是原来的。其实这样解释并不严谨,因为随机失活后的和原来的是不一样的,后面做的只不过是把留下的那些元素变大了而已,不过这样做至少可以让对期望值的影响减少一些,也是有用的。换个角度,因为有20%的元素被随机归零,也就是影响力只剩下80%,所以再除以80%就又变回了原来的影响力。
注意,在测试阶段不要使用dropout,因为那样会使得预测结果变得随机。
理解dropout
对于单个神经元,其工作是接收输入并产生一些有意义的输出。但是加入了dropout后,输入的特征都存在被随机清除的可能,所以该神经元不会再特别依赖于任何一个输入特征,即不会给任何一个输入特征设置太大的权重。因此,通过传播过程,dropout将产生和L2正则化相同的收缩权重的效果。
对于不同的层,设置的keep_prob
也不同。一般来说,神经元较少的层不用担心过拟合,会设keep_prob
为1.0,即保留所有的神经元;而神经元多的层则会设置比较小的keep_prob
。
dropout的一大缺点是成本函数无法被明确定义。因为每次迭代都会随机消除一些神经元结点的影响,因此无法确保成本函数单调递减。因此,使用dropout时,先将keep_prob
全部设置为1.0后运行代码,确保函数单调递减,再打开dropout。
使用标准化处理输入能够有效加速收敛。
标准化输入(normalizing inputs)分为两步:1. 零均值化;2. 归一化标准差。
如上图,假设样本有两个特征,减去均值后分布在了原点附近,再除以标准差后分布更均匀(本来的方差比大,除以标准差后方差都变成了1)。
标准化公式:
其中,、.
吴恩达讲的是除以方差,也就是,但实际上应该是除以标准差.
使用标准化的原因
有图可知,使用标准化前后,成本函数的形状有较大差别。其实标准化是为了把取值范围相差很大的特征,变成取值范围相似的特征,这样函数就不至于在某个特征上拉伸得很厉害。
在不使用标准化的成本函数中,如果设置一个较小的学习率,可能需要很多次迭代才能到达全局最优解;而如果使用了标准化,那么无论从哪个位置开始迭代,都能以相对较少的迭代次数找到全局最优解。
在梯度函数上出现的以指数级递增或者递减的情况分别称为梯度爆炸(exploding gradients)或者梯度消失(vanishing gradients)。
假定、,对于目标输出有,则:
因此,在计算梯度时,根据不同情况梯度函数会以指数级递增或递减,导致训练导数难度上升,梯度下降算法的步长会变得非常小,需要训练的时间将会非常长。
利用初始化缓解梯度消失和爆炸:以一层单神经元为例,根据可知,当输入的特征数较大时,我们希望每个的值都小一些,这样得到的也较小。
为了得到较小的,可以令的方差为,记作Var(wi)=1/n
,这称为Xavier
initialization。其中是输入的神经元个数(对于第0层则是特征数),即WL.shape[1]
。
# numpy.random模块生成的随机数是标准正态分布,均值为0,方差为1
# 为了让WL的方差变成1/n,根据方差公式,应该乘以根号下1/n
= np.random.randn(WL.shape[0], WL.shape[1]) * np.sqrt(1/n) WL
这样,激活函数的输入近似设置成均值为0,标准差为1,神经元输出的方差就正则化到1了。虽然没有解决梯度消失和爆炸的问题,但其在一定程度上确实减缓了梯度消失和爆炸的速度。
同理,也有He Initialization。它和Xavier
initialization唯一的区别是Var(wi)=2/n
,适用于ReLU作为激活函数时。
当激活函数使用ReLU时,Var(wi)=2/n
;当激活函数使用tanh时,Var(wi)=1/n
。至于其他激活函数,你也可以把方差看作一个可调的超参数,比如某个乘子参数乘以,自行测试效果。
梯度检验(Gradient checking):为了防止代码写错或者其他bug,我们可以用近似计算出的梯度和按照前面的方式推导出的公式计算出的梯度做对比,看看这两个向量的欧氏距离是多少(实际上还会进行归一化处理,防止梯度的数值过大),如果两者相差过大,就说明可能有问题。
使用双边误差的方法去逼近导数,精度要高于单边误差。
,误差:
,误差:
当越小时,结果越接近真实的导数,也就是梯度值。可以使用这种方法来判断反向传播进行梯度下降时,是否出现了错误。
连接参数
将全部连接出来,成为一个巨型向量,即:
同时,对执行同样的操作得到巨型向量,它和有同样的维度。
现在,我们需要找到和代价函数的梯度的关系。
进行梯度检验
求得一个梯度逼近值,它应该
因此,我们用梯度检验值
检验反向传播的实施是否正确。其中表示向量的2-范数(也称“欧几里德范数”)。
如果梯度检验值和的值相近,说明神经网络的实施是正确的,否则要去检查代码是否存在bug。
深度学习难以在大数据领域发挥最大效果的一个原因是,在巨大的数据集基础上进行训练速度很慢。而优化算法(Optimization algorithms)能够帮助快速训练模型,大大提高效率。
batch梯度下降法(批梯度下降法,我们之前一直使用的梯度下降法)是最常用的梯度下降形式,即同时处理整个训练集。其在更新参数时使用所有的样本来进行更新。对整个训练集进行梯度下降法的时候,我们必须处理整个训练数据集,然后才能进行一步梯度下降,即每一步梯度下降法需要对整个训练集进行一次处理,如果训练数据集很大的时候,处理速度就会比较慢。但是如果每次处理训练数据的一部分即进行梯度下降法,则我们的算法速度会执行的更快。而处理的这些一小部分训练子集即称为mini-batch。
Mini-Batch梯度下降法(小批量梯度下降法)把训练集分割成小一点的子训练集,这些子集称为mini-batch,每次同时对单个的mini-batch进行梯度下降,更新同一个和。
使用batch梯度下降法,对整个训练集的一次遍历只能做一个梯度下降;而使用Mini-Batch梯度下降法,对整个训练集的一次遍历(称为一个epoch)能做mini-batch个数个梯度下降。之后,可以一直遍历训练集,直到最后收敛到一个合适的精度。
符号约定:用上标大括号来表示mini-batch的序号,比如、。举个例子,一个训练集分成3个mini-batch,那就是,同理,也要对应分成。
batch梯度下降法和Mini-batch梯度下降法代价函数的变化趋势如下(Mini-batch梯度下降法可以看成是同时在若干个训练集上进行的,不同的训练集误差可能不一样,所以代价函数不总是下降的,但整体趋势仍是下降的):
mini-batch的不同大小(size)带来的影响:
随机梯度下降法和batch梯度下降法的优缺点:
因此,选择一个1 < size < m
的合适的大小进行Mini-batch梯度下降,可以实现快速学习,也应用了向量化带来的好处,且成本函数的下降处于前两者之间。
mini-batch 大小的选择:
mini-batch的大小也是一个重要的超变量,需要根据经验快速尝试,找到能够最有效地减少成本函数的值。
获得mini-batch的步骤:
其中打乱数据集的代码:
= X.shape[1]
m = list(np.random.permutation(m))
permutation = X[:, permutation]
shuffled_X = Y[:, permutation].reshape((1,m)) shuffled_Y
np.random.permutation
与np.random.shuffle
有两处不同:
permutation
一个矩阵,它会返回一个洗牌后的矩阵副本;而shuffle
只是对一个矩阵进行洗牌,没有返回值。arange
。指数加权平均(Exponentially Weight Average)在统计学中又叫“指数加权移动平均”,它是一种常用的序列数据处理方式,计算公式为:
其中 为下的实际值,为下加权平均后的值,为权重值(在0到1之间)。
给定一个时间序列,例如伦敦一年每天的气温值,图中蓝色的点代表真实数据。
计算时可视为天的平均气温(后面有解释)。当取权重值为0.9时,根据求得的值可以得到图中的红色曲线,相当于把过去10天的气温指数加权平均作为当日的气温,它反映了气温变化的大致趋势。当取权重值时,可以得到图中更为平滑的绿色曲线,相当于把过去50天的气温指数加权平均作为当日的气温。而当取权重值时,得到图中噪点更多的黄色曲线,相当于把过去2天的气温指数加权平均作为当日的气温。
也就是说,越大相当于求取平均利用的天数越多,曲线自然就会越平滑而且越滞后。
理解指数平均加权(Understanding exponentially weighted averages)
当为0.9时,我们有
展开后得:
其中指第天的实际数据。所有前面的系数相加起来为1或者接近于1,这些系数被称作偏差修正(Bias Correction)。
前面说在计算时可视为天的平均气温(这里代入就是10天),其实是因为,也就是10天后前面气温的影响下降到约三分之一(或者说),若令,那么根据极限公式,刚好可以用来近似计算天数。
指数平均加权并不是最精准的计算平均数的方法,你可以直接计算过去10天或50天的平均值来得到更好的估计,但缺点是保存数据需要占用更多内存,执行更加复杂,计算成本更加高昂。指数加权平均数公式写成代码只需要不断更新即可,它的好处之一在于它只需要一行代码,且占用极少内存,因此效率极高,且节省成本。
指数平均加权的偏差修正(Bias correction in exponentially weighted averages)
当为0.98时,我们有
因此,仅为第一个实际数据的0.02(或者说),画出来的曲线的起点会非常接近轴,显然不准确,且往后递推同理,前期的数据偏差都比较大。
因此,我们把修正为:
较小时,分母也较小,相当于把前期的数据放大了;随着的增大,的次方趋近于0。因此当很大的时候,偏差修正几乎没有作用,但是在前期学习可以帮助更好的预测数据。
不过在实际过程中,一般会忽略前期偏差的影响。
动量梯度下降(又叫Momentum梯度下降,Gradient Descent with Momentum)是计算梯度的指数加权平均数,并利用该值来更新参数值。具体过程为:
for l = 1, … ,L:
其中,将动量衰减参数设置为0.9是超参数的一个常见且效果不错的选择。当被设置为0时,显然就成了batch梯度下降法。
进行一般的梯度下降(指mini-batch梯度下降)将会得到图中的蓝色曲线,由于存在上下波动,减缓了梯度下降的速度,因此只能使用一个较小的学习率进行迭代。如果用较大的学习率,结果可能会像紫色曲线一样偏离函数的范围。而使用动量梯度下降时,通过累加过去的梯度值来减少抵达最小值路径上的波动,加速了收敛,因此在横轴方向下降得更快,从而得到图中红色的曲线。
当前后梯度方向一致时,动量梯度下降能够加速学习;而前后梯度方向不一致时,动量梯度下降能够抑制震荡。
动量梯度下降法的形象解释:将成本函数想象为一个碗状,从顶部开始运动的小球向下滚,其中,想象成球的加速度;而、相当于速度。小球在向下滚动的过程中,因为加速度的存在速度会变快,但是由于的存在,其值小于1,可以认为是摩擦力,所以球不会无限加速下去。
另外,在10次迭代之后(按前面说的,那么),指数加权平均已经不再是一个具有偏差的预测。因此实际在使用梯度下降法或者动量梯度下降法时,一般不会进行偏差修正。
RMSProp(Root Mean Square Propagation,均方根传播)算法是在对梯度进行指数加权平均的基础上,引入平方和平方根。具体过程为(省略了):
注意:这里的“平方”是指element-wise(同位置的元素相乘),即对应元素的平方。
其中,是一个实际操作时加上的较小数(例如),为了防止分母太小而导致的数值不稳定。
如下图所示,蓝色轨迹代表初始的移动,可以看到在方向上走得比较陡峭(即较大),相比起来较小,这影响了优化速度。因此,在采用RMSProp算法后,由于较小、较大,进而也会较小、也会较大,最终使得较大,而较小。后面的更新就会像绿色轨迹一样,明显好于蓝色的更新曲线。
当然,可能实际情况不一定是大小,但关键点在于,RMSProp可以让大的变小、小的变大,减小某些维度梯度更新波动较大的情况,让它在各个维度上更平均。
RMSProp有助于减少抵达最小值路径上的摆动,并允许使用一个更大的学习率,从而加快算法学习速度。并且,它和Adam优化算法已被证明适用于不同的深度学习网络结构。
注意,也是一个超参数。
Adam优化算法(Adaptive Moment Estimation,自适应矩估计)基本上就是将Momentum和RMSProp算法结合在一起,通常有超越二者单独时的效果。具体过程如下(省略了):
首先进行初始化:
用每一个mini-batch计算、,第次迭代时:
一般使用Adam算法时需要计算偏差修正:
所以,更新、时有:
(可以看到公式中的并没有写到平方根里去,和在RMSProp中写的不太一样。考虑到所起的作用,我感觉影响不大)
超参数的选择
Adam优化算法有很多的超参数,其中:
、、通常不需要调试。
如果设置一个固定的学习率,在最小值点附近,由于不同的batch中存在一定的噪声,因此不会精确收敛,而是始终在最小值周围一个较大的范围内波动。
而如果随着时间慢慢减少学习率的大小,在初期较大时,下降的步长较大,能以较快的速度进行梯度下降;而后期逐步减小的值,即减小步长,有助于算法的收敛,更容易接近最优解。
最常用的学习率衰减(Learning rate decay)方法:
其中,decay_rate
为衰减率(超参数),epoch_num
为将所有的训练样本完整过一遍的次数。
除了以上的方法,还有一些常见的学习率衰减方法:
mini_batch_num
指mini-batch的序号鞍点(saddle)是函数上的导数为零,但不是轴上局部极值的点。当我们建立一个神经网络时,通常梯度为零的点是上图所示的鞍点,而非局部最小值。减少损失的难度也来自误差曲面中的鞍点,而不是局部最低点。因为在一个具有高维度空间的成本函数中,如果梯度为0,那么在不同的方向上,成本函数可能是凸函数,也可能是凹函数,而所有维度均需要是凹函数的概率极小,因此在低维度的局部最优点的情况并不适用于高维度,高维度上更有可能遇到鞍点而不是局部最优点。
不同数学书上定义的凹/凸函数不一样,这里的凹函数指图像类似 的。
高维度上几乎不会遇到局部最优问题(the problem of local optima),但是接近鞍点时(也就是“平稳段”)导数接近0,如上图,会使梯度下降极慢。这时前面的Momentum梯度下降、RMSProp算法、Adam优化算法就可以让你尽早地往下走出平稳段,这也是它们能够加速学习的原因。
目前已经讲到过的超参数中,重要程度排序依次是(仅供参考):
系统地组织超参调试过程的技巧:
选择合适的范围:
r = -4 * np.random.rand(1)
、alpha = 10^r
;上述操作的原因是当接近1时,即使只有微小的改变,所得结果的灵敏度会有较大的变化。例如,从0.9增加到0.9005对结果()几乎没有影响,而从0.999到0.9995对结果的影响巨大(从1000个值中计算平均值变为2000个值中计算平均值)。
一些建议:
因为深层神经网络在做非线性变换前的激活输入值随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或者变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近(比如Sigmoid函数,逐渐变成大的负值或正值),这就导致了反向传播时神经网络梯度的消失。我们希望通过某种方法将该层特征值分布重新拉回标准正态分布,让特征值落在激活函数对于输入较为敏感的区间,输入的小变化可导致损失函数较大的变化,使得梯度变大,避免梯度消失。
批标准化(Batch Normalization,经常简称为BN)会使参数搜索问题变得很容易,使神经网络对超参数的选择更加稳定,超参数的范围会更庞大,工作效果也很好,也会使训练更容易。
之前,我们对输入特征使用了标准化处理。我们也可以用同样的思路处理隐藏层的激活值,以加速和 的训练。在实践中,经常选择标准化:
其中,是单个mini-batch所包含的样本个数,是为了防止分母为零,通常取。
这样,我们使得所有的输入均值为0,方差为1。但我们不想让隐藏层单元总是含有平均值0和方差1,也许隐藏层单元有了不同的分布会更有意义。因此,我们计算
其中,和都是模型的学习参数,所以可以用各种梯度下降算法来更新和的值,如同更新神经网络的权重一样。通过对和的合理设置,可以让的均值和方差为任意值。这样,我们对隐藏层的进行标准化处理,用得到的替代。
注意区分这里的和前面出现过的各种、、,它们是不同的参数。
设置和的原因是,如果各隐藏层的输入均值在靠近0的区域,即处于激活函数的线性区域,不利于训练非线性神经网络,从而得到效果较差的模型。因此,需要用和对标准化后的结果做进一步处理。
对于L层神经网络,经过Batch Normalization的作用,整体流程如下:
实际上,Batch Normalization经常使用在mini-batch上,这也是其名称的由来。
因为Batch Normalization包含减去均值的一步,因此实际上没有起到作用,其数值效果交由来实现。因此,在Batch Normalization中,可以省略或者暂时设置为0。
、是和一样迭代更新的,、,除了传统的梯度下降算法之外,还可以使用之前学过的动量梯度下降、RMSProp或者Adam等优化算法。
Batch Normalization效果很好的原因有以下两点:
关于第二点,如果实际应用样本和训练样本的数据分布不同(例如,训练时使用的是黑猫图片,但是实际应用要识别多种颜色的猫的图片),我们称发生了“Covariate Shift”。这种情况下,一般要对模型进行重新训练。Batch Normalization的作用就是减小Covariate Shift所带来的影响,让模型变得更加健壮,鲁棒性(Robustness)更强。
即使输入的值改变了,由于Batch Normalization的作用,使得均值和方差保持不变(由和决定),限制了在前层的参数更新对数值分布的影响程度,因此后层的学习变得更容易一些。Batch Normalization减少了各层和之间的耦合性,让各层更加独立,实现自我训练学习的效果。
另外,Batch Normalization也起到微弱的正则化(regularization)效果。因为在每个mini-batch而非整个数据集上计算均值和方差,只由这一小部分数据估计得出的均值和方差会有一些噪声,因此最终计算出的也有一定噪声。类似于dropout,这种噪声会使得神经元不会再特别依赖于任何一个输入特征。
因为Batch Normalization只有微弱的正则化效果,因此可以和dropout一起使用,以获得更强大的正则化效果。通过应用更大的mini-batch大小,可以减少噪声,从而减少这种正则化效果。
最后,不要将Batch Normalization作为正则化的手段,而是当作加速学习的方式。正则化只是一种非期望的副作用,Batch Normalization解决的还是反向传播过程中的梯度问题(梯度消失和爆炸)。
Batch Normalization将数据以mini-batch的形式逐一处理,但在测试时,可能需要对每一个样本逐一处理,这样无法得到和。
理论上,我们可以将所有训练集放入最终的神经网络模型中,然后将每个隐藏层计算得到的和直接作为测试过程的和来使用。但是,实际应用中一般不使用这种方法,而是使用之前学习过的指数加权平均的方法来预测测试过程单个样本的和。
具体过程如下:对于第层隐藏层,考虑所有mini-batch在该隐藏层下的和,在不同mini-batch的这些(或)之间用指数加权平均的方式来预测得到测试集中当前单个样本的和。这样就实现了对测试过程单个样本的均值和方差估计。
目前为止,介绍的分类例子都是二分类问题:神经网络输出层只有一个神经元,表示预测输出是正类的概率,则判断为正类,反之判断为负类。
对于多分类问题,用表示种类个数,则神经网络输出层,也就是第层的单元数量。每个神经元的输出依次对应属于该类的概率,即,其中。有一种Logistic回归的一般形式,叫做Softmax回归(Softmax regression),可以处理多分类问题。
对于Softmax回归模型的输出层,即第层,有:
for i in range(L),
输出层每个神经元的输出,对应属于该类的概率,且应满足:
一个直观的计算例子如下:
,,,每个位置的元素都对应一个种类,比如这里预测出属于第一个种类的概率是0.842。
Softmax这个名称的来源是与所谓hardmax对比,hardmax会把向量变成,hardmax函数会观察的元素,把最大的元素的输出为1,其它的输出都为0。与之相反,Softmax所做的从到这些概率的映射更为温和。
定义损失函数(loss function)为:
虽然预测的给出了属于各个类别的概率,但是实际数据的行里面只有一个1,其他都是0,比如,其他的(),那么其实只有,其余的全部,因此,损失函数可以简化为:
所有个样本的成本函数(cost function)为:
多分类的Softmax回归模型与二分类的Logistic回归模型只有输出层上有一点区别:
反向传播过程的其他步骤也和Logistic回归的一致。
推导过程(以下省略上标,并且为简单起见,假设只有一个样本,于是、都只有一列,只写行标就行了):
计算图如下(以矩阵中的某一行为例列出公式):
我们假设第个神经元是正确标签,即,其余的(时).
先求对的导数:对于输出层有,而就是对中的每个元素求导,
再求对的导数:根据链式法则,有,因为只有,其余都等于零,于是可以进一步简化为
关于,要分成两种情况讨论,第一种是,
其中,当时,视作常数求导为零,当时求导得。
第二种情况是,
综上,写出矩阵可以得出
由于只有,其他的都为零,于是可以进一步写成
比较著名的框架有:
选择框架的标准:
Tensorflow框架介绍:
目前最火的深度学习框架大概是Tensorflow了。这里简单的介绍一下。
Tensorflow框架内可以直接调用梯度下降算法,极大地降低了编程人员的工作量。例如以下代码:
import numpy as np
import tensorflow as tf
= np.array([[1.],[-10.],[25.]])
cofficients
= tf.Variable(0,dtype=tf.float32)
w = tf.placeholder(tf.float32,[3,1])
x # Tensorflow 重载了加减乘除符号
# 假设代价函数是 w^2-10w+25,其中系数定义在前面的cofficients里面了,但是要在session.run中传入
# 这一步的目的是让TensorFlow建立计算图,TensorFlow可以自动计算反向传播
= x[0][0]*w**2 + x[1][0]*w + x[2][0]
cost # 改变下面这行代码,可以换用更好的优化算法
= tf.train.GradientDescentOptimizer(0.01).minimize(cost)
train
= tf.global_variables_initializer()
init = tf.Session()
session
session.run(init)for i in range(1000):
={x:coefficients})
session.run(train, feed_dictprint(session.run(w))
打印为4.99999,基本可以认为是我们需要的结果。更改cofficients的值可以得到不同的结果。
上述代码中:
= tf.Session()
session
session.run(init)print(session.run(w))
也可以写作:
with tf.Session() as session:
session.run(init)print(session.run(w))
with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
对于一个已经被构建好且产生初步结果的机器学习系统,为了能使结果更令人满意,往往还要进行大量的改进。鉴于之前的课程介绍了多种改进的方法,例如收集更多数据、调试超参数、调整神经网络的大小或结构、采用不同的优化算法、进行正则化等等,我们有可能浪费大量时间在一条错误的改进路线上。
想要找准改进的方向,使一个机器学习系统更快更有效地工作,就需要学习一些在构建机器学习系统时常用到的策略。
正交化(Orthogonalization)的核心在于每次调整只会影响模型某一方面的性能,而对其他功能没有影响。这种方法有助于更快更有效地进行机器学习模型的调试和优化。
在机器学习(监督学习)系统中,可以划分四个“功能”:
其中,
面对遇到的各种问题,正交化能够帮助我们更为精准有效地解决问题。
一个反例是早停止法(Early Stopping)。如果早期停止,虽然可以改善验证集的拟合表现,但是对训练集的拟合就不太好。因为对两个不同的“功能”都有影响,所以早停止法不具有正交化。虽然也可以使用,但是用其他正交化控制手段来进行优化会更简单有效。
构建机器学习系统时,通过设置一个量化的单值评价指标(single-number evaluation metric),可以使我们根据这一指标比较不同超参数对应的模型的优劣,从而选择最优的那个模型。
例如,对于二分类问题,常用的评价指标是精确率(Precision)和召回率(Recall)。假设我们有A和B两个分类器,其两项指标分别如下:
分类器 | 精确率 | 召回率 |
---|---|---|
A | 95% | 90% |
B | 98% | 85% |
精确率:
召回率:
实际应用中,我们通常使用综合了精确率和召回率的单值评价指标F1 Score来评价模型的好坏。F1 Score其实就是精准率和召回率的调和平均数(Harmonic Mean),比单纯的平均数效果要好。
因此,我们计算出两个分类器的F1 Score;可以看出 A 模型的效果要更好。
分类器 | 精确率 | 召回率 | F1 Score |
---|---|---|---|
A | 95% | 90% | 92.4% |
B | 98% | 85% | 91.0% |
通过引入单值评价指标,我们可以更方便快速地对不同模型进行比较。
如果我们还想要将分类器的运行时间也纳入考虑范围,将其和精确率、召回率组合成一个单值评价指标显然不那么合适。这时,我们可以将某些指标作为优化指标(Optimizing Matric),寻求它们的最优值;而将某些指标作为满足指标(Satisficing Matric),只要在一定阈值以内即可。
在这个例子中,准确率就是一个优化指标,因为我们想要分类器尽可能做到正确分类;而运行时间就是一个满足指标,如果你想要分类器的运行时间不多于某个阈值,那最终选择的分类器就应该是以这个阈值为界里面准确率最高的那个。
动态改变评价指标
对于模型的评价标准优势需要根据实际情况进行动态调整,以让模型在实际应用中获得更好的效果。
例如,有时我们不太能接受某些分类错误,于是改变单纯用错误率作为评价标准,给某些分类错误更高的权重,以从追求最小错误率转为追求最小风险。
我们一般将数据集分为训练集、验证集、测试集。构建机器学习系统时,我们采用不同的学习方法,在训练集上训练出不同的模型,然后使用验证集对模型的好坏进行评估,确信其中某个模型足够好时再用测试集对其进行测试。
因此,训练集、验证集、测试集的设置对于机器学习模型非常重要,合理的设置能够大大提高模型训练效率和模型质量。
验证集和测试集的分布
验证集和测试集的数据来源应该相同(来自同一分布)、和机器学习系统将要在实际应用中面对的数据一致,且必须从所有数据中随机抽取。这样,系统才能做到尽可能不偏离目标。
验证集和测试集的大小
过去数据量较小(小于1万)时,通常将数据集按照以下比例进行划分:
这是为了保证验证集和测试集有足够的数据。现在的机器学习时代数据集规模普遍较大,例如 100 万数据量,这时将相应比例设为 98% / 1% / 1% 或 99% / 1% 就已经能保证验证集和测试集的规模足够。
测试集的大小应该设置得足够提高系统整体性能的可信度,验证集的大小也要设置得足够用于评估几个不同的模型。应该根据实际情况对数据集灵活地进行划分,而不是死板地遵循老旧的经验。
很多机器学习模型的诞生是为了取代人类的工作,因此其表现也会跟人类表现水平作比较。
上图展示了随着时间的推进,机器学习系统和人的表现水平的变化。一般的,当机器学习超过人的表现水平后,它的进步速度逐渐变得缓慢,最终性能无法超过某个理论上限,这个上限被称为贝叶斯最优误差(Bayes Optimal Error)。
贝叶斯最优误差一般认为是理论上可能达到的最优误差,换句话说,其就是理论最优函数,任何从到精确度映射的函数都不可能超过这个值。例如,对于语音识别,某些音频片段嘈杂到基本不可能知道说的是什么,所以完美的识别率不可能达到100%。
因为人类对于一些自然感知问题的表现水平十分接近贝叶斯最优误差,所以当机器学习系统的表现超过人类后,就没有太多继续改善的空间了。
也因此,只要建立的机器学习模型的表现还没达到人类的表现水平时,就可以通过各种手段来提升它。例如采用人工标记过的数据进行训练,通过人工误差分析了解为什么人能够正确识别,或者是进行偏差、方差分析。
当模型的表现超过人类后,这些手段起的作用就微乎其微了。
通过与贝叶斯最优误差,或者说,与人类表现水平的比较,可以表明一个机器学习模型表现的好坏程度,由此判断后续操作应该注重于减小偏差还是减小方差。
模型在训练集上的误差与人类表现水平的差值被称作可避免偏差(Avoidable Bias)。可避免偏差低便意味着模型在训练集上的表现很好,而训练集与验证集之间错误率的差值越小,意味着模型在验证集与测试集上的表现和训练集同样好。
如果可避免偏差大于训练集与验证集之间错误率的差值,之后的工作就应该专注于减小偏差;反之,就应该专注于减小方差。
我们一般用人类水平误差(Human-level Error)来代表贝叶斯最优误差(或者简称贝叶斯误差)。对于不同领域的例子,不同人群由于其经验水平不一,错误率也不同。一般来说,我们将表现最好的作为人类水平误差。但是实际应用中,不同人选择人类水平误差的基准是不同的,这会带来一定的影响。
例如,如果某模型在训练集上的错误率为0.7%,验证集的错误率为0.8%。如果选择的人类水平误差为0.5%,那么偏差(bias)比方差(variance)更加突出;而如果选择的人类水平误差为0.7%,则方差更加突出。也就是说,根据人类水平误差的不同选择,我们可能因此选择不同的优化操作。
这种问题只会发生在模型表现很好,接近人类水平误差的时候才会出现。人类水平误差给了我们一种估计贝叶斯误差的方式,而不是像之前一样将训练的错误率直接对着0%的方向进行优化。
当机器学习模型的表现超过了人类水平误差时,很难再通过人的直觉去判断模型还能够往什么方向优化以提高性能。
想让一个监督学习算法达到使用程度,应该做到以下两点:
根据正交化的思想,我们有一些措施可以独立地优化二者之一。
通过人工检查机器学习模型得出的结果中出现的一些错误,有助于深入了解下一步要进行的工作。这个过程被称作错误分析(Error Analysis)。
例如,你可能会发现一个猫图片识别器错误地将一些看上去像猫的狗误识别为猫。这时,立即盲目地去研究一个能够精确识别出狗的算法不一定是最好的选择,因为我们不知道这样做会对提高分类器的准确率有多大的帮助。
这时,我们可以从分类错误的样本中统计出狗的样本数量。根据狗样本所占的比重来判断这一问题的重要性。假如狗类样本所占比重仅为5%,那么即使花费几个月的时间来提升模型对狗的识别率,改进后的模型错误率并没有显著改善;而如果错误样本中狗类所占比重为50%,那么改进后的模型性能会有较大的提升。因此,花费更多的时间去研究能够精确识别出狗的算法是值得的。
这种人工检查看似简单而愚笨,但却是十分必要的,因为这项工作能够有效避免花费大量的时间与精力去做一些对提高模型性能收效甚微的工作,让我们专注于解决影响模型准确率的主要问题。
在对输出结果中分类错误的样本进行人工分析时,可以建立一个表格来记录每一个分类错误的具体信息,例如某些图像是模糊的,或者是把狗识别成了猫等,并统计属于不同错误类型的错误数量。这样,分类结果会更加清晰。
总结一下,进行错误分析时,你应该观察错误标记的例子,看看假阳性和假阴性,统计属于不同错误类型的错误数量。在这个过程中,你可能会得到启发,归纳出新的错误类型。总之,通过统计不同错误标记类型占总数的百分比,有助于发现哪些问题亟待解决,或者提供构思新优化方向的灵感。
我们用mislabeled examples来表示学习算法输出了错误的 Y 值。而在做误差分析时,有时会注意到数据集中有些样本被人为地错误标记(incorrectly labeled)了,这时该怎么做?
如果是在训练集中,由于机器学习算法对于随机误差的稳健性(Robust)(也称作“鲁棒性”),只要这些出错的样本数量较小,且分布近似随机,就不必花费时间一一修正。
而如果出现在验证集或者测试集,则可以在进行误差分析时,通过统计人为标记错误所占的百分比,来大致分析这种情况对模型的识别准确率的影响,并比较该比例的大小和其他错误类型的比例,以此判断是否值得去将错误的标记一一进行修正,还是可以忽略。
当你决定在验证集和测试集上手动检查标签并进行修正时,有一些额外的方针和原则需要考虑:
对于每个可以改善模型的合理方向,如何选择一个方向集中精力处理成了问题。如果想搭建一个全新的机器学习系统,建议根据以下步骤快速搭建好第一个系统,然后开始迭代:
有时,我们很难得到来自同一个分布的训练集和验证/测试集。还是以猫识别作为例子,我们的训练集可能由网络爬取得到,图片比较清晰,而且规模较大(例如20万);而验证/测试集可能来自用户手机拍摄,图片比较模糊,且数量较少(例如1万),难以满足作为训练集时的规模需要。
虽然验证/测试集的质量不高,但是机器学习模型最终主要应用于识别这些用户上传的模糊图片。考虑到这一点,在划分数据集时,可以将20万张网络爬取的图片和5000张用户上传的图片作为训练集,而将剩下的5000张图片一半作验证集,一半作测试集。比起混合数据集所有样本再随机划分,这种分配方法虽然使训练集分布和验证/测试集的分布并不一样,但是能保证验证/测试集更接近实际应用场景,在长期能带来更好的系统性能。
之前的学习中,我们通过比较人类水平误差、训练集错误率、验证集错误率的相对差值来判断进行偏差/方差分析。但在训练集和验证/测试集分布不一致的情况下,无法根据相对差值来进行偏差/方差分析。这是因为训练集错误率和验证集错误率的差值可能来自于算法本身(归为方差),也可能来自于样本分布不同,和模型关系不大。
在可能存在训练集和验证/测试集分布不一致的情况下,为了解决这个问题,我们可以再定义一个训练-验证集(Training-dev Set)。训练-验证集和训练集的分布相同(或者是训练集分割出的子集),但是不参与训练过程。
现在,我们有了训练集错误率、训练-验证集错误率,以及验证集错误率。其中,训练集错误率和训练-验证集错误率的差值反映了方差;而训练-验证集错误率和验证集错误率的差值反映了样本分布不一致的问题,从而说明模型擅长处理的数据和我们关心的数据来自不同的分布,我们称之为数据不匹配(Data Mismatch)问题。
人类水平误差、训练集错误率、训练-验证集错误率、验证集错误率、测试集错误率之间的差值所反映的问题如下图所示:
处理方法
这里有两条关于如何解决数据不匹配问题的建议:
如果你打算将训练数据调整得更像验证集,可以使用的一种技术是人工合成数据。我们以语音识别问题为例,实际应用场合(验证/测试集)是包含背景噪声的,而作为训练样本的音频很可能是清晰而没有背景噪声的。为了让训练集与验证/测试集分布一致,我们可以给训练集人工添加背景噪声,合成类似实际场景的声音。
人工合成数据能够使数据集匹配,从而提升模型的效果。但需要注意的是,不能给每段语音都增加同一段背景噪声,因为这样模型会对这段背景噪音出现过拟合现象,使得效果不佳。
迁移学习(Tranfer Learning)是通过将已训练好的神经网络模型的一部分网络结构应用到另一模型,将一个神经网络从某个任务中学到的知识和经验运用到另一个任务中,以显著提高学习任务的性能。
例如,我们将为猫识别器构建的神经网络迁移应用到放射科诊断中。因为猫识别器的神经网络已经学习到了有关图像的结构和性质等方面的知识,所以只要先删除神经网络中原有的输出层,加入新的输出层并随机初始化权重系数(、),随后用新的训练集进行训练,就完成了以上的迁移学习。
如果新的数据集很小,可能只需要重新训练输出层前的最后一层的权重,即、,并保持其他参数不变;而如果有足够多的数据,可以只保留网络结构,重新训练神经网络中所有层的系数。这时初始权重由之前的模型训练得到,这个过程称为预训练(Pre-Training),之后的权重更新过程称为微调(Fine-Tuning)。
你也可以不止加入一个新的输出层,而是多向神经网络加几个新层。
在下述场合进行迁移学习是有意义的:
迁移学习中的步骤是串行的;而多任务学习(Multi-Task Learning)使用单个神经网络模型,利用共享表示采用并行训练同时学习多个任务。多任务学习的基本假设是多个任务之间具有相关性,并且任务之间可以利用相关性相互促进。例如,属性分类中,抹口红和戴耳环有一定的相关性,单独训练的时候是无法利用这些信息,多任务学习则可以利用任务相关性联合提高多个属性分类的精度。
以汽车自动驾驶为例,需要实现的多任务是识别行人、车辆、交通标志和信号灯。如果在输入的图像中检测出车辆和交通标志,则输出的为:
多任务学习模型的成本函数为:
其中,代表任务下标,总有个任务。对应的损失函数为:
多任务学习是使用单个神经网络模型来实现多个任务。实际上,也可以分别构建多个神经网络来实现。多任务学习中可能存在训练样本某些标签空白的情况,这不会影响多任务学习模型的训练。
多任务学习和Softmax回归看上去有些类似,容易混淆。它们的区别是,Softmax回归的输出向量中只有一个元素为1;而多任务学习的输出向量中可以有多个元素为1。
在下述场合进行多任务学习是有意义的:
在多任务深度网络中,低层次信息的共享有助于减少计算量,同时共享表示层可以使得几个有共性的任务更好的结合相关性信息,任务特定层则可以单独建模任务特定的信息,实现共享信息和任务特定信息的统一。
在实践中,多任务学习的使用频率要远低于迁移学习。计算机视觉领域中的物体识别是一个多任务学习的例子。
在传统的机器学习分块模型中,每一个模块处理一种输入,然后其输出作为下一个模块的输入,构成一条流水线。而端到端深度学习(End-to-end Deep Learning)只用一个单一的神经网络模型来实现所有的功能。它将所有模块混合在一起,只关心输入和输出。
如果数据量较少,传统机器学习分块模型所构成的流水线效果会很不错。但如果训练样本足够大,并且训练出的神经网络模型足够复杂,那么端到端深度学习模型的性能会比传统机器学习分块模型更好。
而如果数据集规模适中,还是可以使用流水线方法,但是可以混合端到端深度学习,通过神经网络绕过某些模块,直接输出某些特征。
优点与缺点
应用端到端学习的优点:
缺点:
根据以上分析,决定一个问题是否应用端到端学习的关键点是:是否有足够的数据,支持能够直接学习从映射到并且足够复杂的函数?
计算机视觉(Computer Vision)的高速发展标志着新型应用产生的可能,例如自动驾驶、人脸识别、创造新的艺术风格。人们对于计算机视觉的研究也催生了很多机算机视觉与其他领域的交叉成果。一般的计算机视觉问题包括以下几类:
应用计算机视觉时要面临的一个挑战是数据的输入可能会非常大。例如一张1000x1000像素的的图片,由于每个像素都有RGB三个通道,神经网络输入层的维度将高达三百万,使得网络权重非常庞大。这样会造成两个后果:
因此,一般的神经网络很难处理蕴含着大量数据的图像。解决这一问题的方法就是使用卷积神经网络(Convolutional Neural Network,简称CNN)。
神经网络由浅层到深层,分别可以检测出图片的边缘特征、局部特征(例如眼睛、鼻子等),到最后面的一层就可以根据前面检测的特征来识别整体面部轮廓。这些工作都是依托卷积神经网络来实现的。
卷积运算(Convolutional Operation)是卷积神经网络最基本的组成部分。我们以边缘检测为例,来解释卷积是怎样运算的。
边缘检测
图片最常做的边缘检测有两类:垂直边缘(vertical edges)检测和水平边缘(horizontal edges)检测。
图片的边缘检测可以通过与相应滤波器进行卷积来实现。以垂直边缘检测为例,原始图片尺寸为
6x6,中间的矩阵被称作滤波器(filter,又称作卷积核),尺寸为3x3,卷积后得到的图片尺寸为4x4,得到结果如下(数值表示灰度,以左上角和右下角的值为例,*
表示卷积运算):
可以看到,卷积运算的求解过程是从左到右,由上到下,每次在原始图片矩阵中取与滤波器同等大小的一部分,每一部分中的值与滤波器中的值对应相乘后求和,将结果组成一个矩阵。
下图对应一个垂直边缘检测的例子:
如果将最右边的矩阵当作图像,那么中间一段亮一些的区域对应最左边的图像中间的垂直边缘。
这里有另一个卷积运算的动态的例子,方便理解:
图中的*
表示卷积运算符号。在计算机中这个符号表示一般的乘法,而在不同的深度学习框架中,卷积操作的API定义可能不同:
conv_forward()
表示;tf.nn.conv2d()
表示;Conv2D()
表示。更多边缘检测的例子
如果将灰度图左右的颜色进行翻转,再与之前的滤波器进行卷积,得到的结果也有区别。实际应用中,这反映了由明变暗和由暗变明的两种渐变方式。可以对输出图片取绝对值操作,以得到同样的结果。
垂直边缘检测和水平边缘检测的滤波器如下所示:
其他常用的滤波器还有Sobel滤波器和Scharr滤波器。它们增加了中间行的权重,以提高结果的稳健性。
滤波器中的值还可以设置为参数,通过模型训练来得到(用梯度下降来更新)。这样,神经网络使用反向传播算法可以学习到一些低级特征,从而实现对图片所有边缘特征的检测,而不仅限于垂直边缘和水平边缘。
附上卷积后的矩阵中第行第列元素的计算公式(建议看完本章再看这里)::
其中是通道数,是滤波器的大小(宽度或高度,这里假设两者相同),表示滤波器的第个通道第行第列位置的参数(或者叫权重),表示图像(输入的矩阵)的第个通道第行第列像素,是步长,是偏移量,是激活函数,是卷积完成后第行第列的元素。
假设输入图片的大小为,而滤波器的大小为,则卷积后的输出图片大小为。
这样就有两个问题:
为了解决这些问题,可以在进行卷积操作前,对原始图片在边界上进行填充(Padding),以增加矩阵的大小。通常将0作为填充值。
设每个方向扩展像素点数量为 ,则填充后原始图片的大小为,滤波器大小保持不变,则输出图片大小为。
因此,在进行卷积运算时,我们有两种选择:
在计算机视觉领域,通常为奇数。原因包括Same卷积中能得到自然数结果,并且滤波器有一个便于表示其所在位置的中心点。
卷积过程中,有时需要通过填充来避免信息损失,有时也需要通过设置步长来压缩一部分信息。
步长(Stride)表示滤波器在原始图片的水平方向和垂直方向上每次移动的距离。之前,步长被默认为1。而如果我们设置步长为2,则卷积过程如下图所示:
设步长为,填充长度为,输入图片大小为,滤波器大小为,则卷积后图片的尺寸为:
注意公式中有一个向下取整的符号,用于处理商不为整数的情况。向下取整反映着当取原始矩阵的图示蓝框完全包括在图像内部时,才对它进行运算。
目前为止我们学习的“卷积”实际上被称为互相关(cross-correlation),而非数学意义上的卷积。真正的卷积操作在做元素乘积求和之前,要将滤波器沿水平和垂直轴翻转(相当于旋转180度)。因为这种翻转对一般为水平或垂直对称的滤波器影响不大,按照机器学习的惯例,我们通常不进行翻转操作,在简化代码的同时使神经网络能够正常工作。
如果我们想要对三通道的RGB图片进行卷积运算,那么其对应的滤波器组也同样是三通道的。过程是将每个单通(R, G, B)与对应的滤波器进行卷积运算求和,然后再将三个通道的和相加,将27个乘积的和作为输出图片的一个像素值。
不同通道的滤波器可以不相同。例如只检测R通道的垂直边缘,G通道和B通道不进行边缘检测,则G通道和B通道的滤波器全部置零。当输入有特定的高、宽和通道数时,滤波器可以有不同的高和宽,但滤波器的通道数必须和输入一致。
如果想同时检测垂直和水平边缘,或者更多的边缘检测,可以增加更多的滤波器组。例如设置第一个滤波器组实现垂直边缘检测,第二个滤波器组实现水平边缘检测。设输入图片的尺寸为(为通道数),滤波器尺寸为,则卷积后的输出图片尺寸为,为滤波器组的个数。(即滤波器有多少组,输出就有多少个通道)
与之前的卷积过程相比较,卷积神经网络的单层结构多了激活函数和偏移量;而与标准神经网络:
相比,滤波器的数值对应着权重,卷积运算对应着与的乘积运算,所选的激活函数变为ReLU。
对于一个3x3x3的滤波器,包括偏移量在内共有28个参数。不论输入的图片有多大,用这一个滤波器来提取特征时,参数始终都是28个,固定不变。即选定滤波器组后,参数的数目与输入图片的尺寸无关。因此,卷积神经网络的参数相较于标准神经网络来说要少得多。这是CNN的优点之一。
符号约定(总结)
设层为卷积层:
:滤波器的高(或宽)
:填充长度
:步长
:滤波器组的数量
输入维度: 。其中表示输入图片的高,表示输入图片的宽。之前的示例中输入图片的高和宽都相同,但是实际中也可能不同,因此加上下标予以区分。
输出维度: 。其中、
每个滤波器组的维度: 。其中 为输入图片通道数(也称深度)。
权重维度:。其中当前层的输入的通道数是由上一层的滤波器组的数量决定的,而当前层还有组滤波器。
偏置维度:
由于深度学习的相关文献并未对卷积标示法达成一致,因此不同的资料关于高度、宽度和通道数的顺序可能不同。有些作者会将通道数放在首位,需要根据标示自行分辨。
一个简单的CNN模型如下图所示:
其中,的维度为7x7x40,将1960个特征平滑展开成1960个单元的一列,然后连接最后一级的输出层。输出层可以是一个神经元,即二元分类(logistic);也可以是多个神经元,即多元分类(softmax)。最后得到预测输出。
随着神经网络计算深度不断加深,图片的高度和宽度、一般逐渐减小,而在增加。
一个典型的卷积神经网络通常包含有三种层:卷积层(Convolution layer)、池化层(Pooling layer)、全连接层(Fully Connected layer)。仅用卷积层也有可能构建出很好的神经网络,但大部分神经网络还是会添加池化层和全连接层,它们更容易设计。
池化层的作用是缩减模型的大小,提高计算速度,同时减小噪声提高所提取特征的稳健性。
采用较多的一种池化过程叫做最大池化(Max Pooling)。将输入拆分成不同的区域,输出的每个元素都是对应区域中元素的最大值,如下图所示:
池化过程类似于卷积过程,上图所示的池化过程中相当于使用了一个大小的滤波器,且池化步长。卷积过程中的几个计算大小的公式也都适用于池化过程。如果有多个通道,那么就对每个通道分别执行计算过程。
对最大池化的一种直观解释是,元素值较大可能意味着池化过程之前的卷积过程提取到了某些特定的特征,池化过程中的最大化操作使得只要在一个区域内提取到某个特征,它都会保留在最大池化的输出中。但是,没有足够的证据证明这种直观解释的正确性,而最大池化被使用的主要原因是它在很多实验中的效果都很好。
另一种池化过程是平均池化(Average Pooling),就是从取某个区域的最大值改为求这个区域的平均值:
池化过程的特点之一是,它有一组超参数,但是并没有参数需要学习。池化过程的超参数包括滤波器的大小、步长,以及选用最大池化还是平均池化。填充则很少用到。
池化过程的输入维度为:
输出维度为:
卷积时,f=5表示滤波器大小为,滤波器的通道数和输入一致,省略不写;s=1表示步长为1;6 filters表示有6组滤波器。
池化层通常跟在卷积层后面。在计算神经网络的层数时,通常只统计具有权重和参数的层,因为池化层没有权重和参数,只有一些超参数,所以池化层通常和之前的卷积层共同计为一层,比如CONV1
和POOL1
都属于Layer1
。
图中的FC3和FC4为全连接层,与标准的神经网络结构一致。整个神经网络各层的尺寸与参数如下表所示:
Activation shape | Activation Size | #parameters | |
---|---|---|---|
Input: | (32, 32, 3) | 3072 | 0 |
CONV1(f=5, s=1) | (28, 28, 6) | 4704 | 156 |
POOL1 | (14, 14, 6) | 1176 | 0 |
CONV2(f=5, s=1) | (10, 10, 16) | 1600 | 416 |
POOL2 | (5, 5, 16) | 400 | 0 |
FC3 | (120, 1) | 120 | 48120 |
FC4 | (84, 1) | 84 | 10164 |
Softmax | (10, 1) | 10 | 850 |
前面说过,滤波器里面的数可以设置为参数,通过训练得到。
比如CONV1的参数就是,因为滤波器是的,有6个通道就有6个滤波器,并且卷积完后输入到激活函数前还要加偏移量,每个通道都有一个偏移量,也就是加6。
再比如FC3,全连接层就是一个标准的神经网络,,其中的维数是,并且还要加偏移量,的维数是,所以参数个数就是.
相比标准神经网络,对于大量的输入数据,卷积过程有效地减少了CNN的参数数量,原因有以下两点:
池化过程则在卷积后很好地聚合了特征,通过降维来减少运算量。
由于CNN参数数量相对标准神经网络而言较少,所需的训练样本就相对较少,因此在一定程度上不容易发生过拟合现象。并且CNN比较擅长捕捉区域位置偏移。即进行物体检测时,不太受物体在图片中位置的影响,增加检测的准确性和系统的健壮性。
讲到的经典CNN模型包括:
此外还有ResNet(Residual Network,残差网络),以及Inception Neural Network。
特点:
相关论文:LeCun et.al., 1998. Gradient-based learning applied to document recognition。建议精读第二段,泛读第三段。
上图中的same指same卷积,即填充后再卷积,保持输入输出大小不变。(参考前面“填充”的部分)
特点:
相关论文:Krizhevsky et al.,2012. ImageNet classification with deep convolutional neural networks。这是一篇易于理解并且影响巨大的论文,计算机视觉群体自此开始重视深度学习。
特点:
相关论文:Simonvan & Zisserman 2015. Very deep convolutional networks for large-scale image recognition。
因为存在梯度消失和梯度爆炸问题,网络越深,就越难以训练成功。残差网络(Residual Networks,简称为 ResNets)可以有效解决这个问题。
上图的结构被称为残差块(Residual block)。通过捷径(Short cut,或者称跳远连接,Skip connections)可以将添加到第二个ReLU过程中,直接建立与 之间的隔层联系。表达式如下:
构建一个残差网络就是将许多残差块堆积在一起,形成一个深度网络。
为了便于区分,在 ResNets 的论文He et al., 2015. Deep residual networks for image recognition中,非残差网络被称为普通网络(Plain Network)。将它变为残差网络的方法是加上所有的跳远连接。
在理论上,随着网络深度的增加,性能应该越来越好。但实际上,对于一个普通网络,随着神经网络层数增加,训练错误会先减少,然后开始增多。但残差网络的训练效果显示,即使网络再深,其在训练集上的表现也会越来越好。
残差网络有助于解决梯度消失和梯度爆炸问题,使得在训练更深的网络的同时,又能保证良好的性能。
残差网络有效的原因
假设有一个大型神经网络,其输入为,输出为。给这个神经网络额外增加两层,输出为。将这两层看作一个具有跳远连接的残差块。为了方便说明,假设整个网络中都选用ReLU作为激活函数,因此输出的所有激活值都大于等于0。
则有:
当发生梯度消失时,,,则有,也就是说,一旦发生梯度消失,发生梯度消失这一层神经网络就相当于被忽略了,直接跳进后面的神经网络。
因此,这两层额外的残差块不会降低网络性能。而如果没有发生梯度消失时,训练得到的非线性关系会使得表现效果进一步提高。
注意,如果与的维度不同,需要引入矩阵与相乘,使得二者的维度相匹配。参数矩阵既可以通过模型训练得到,也可以作为固定值,仅使截断或者补零。
上图是论文提供的CNN中ResNet的一个典型结构。卷积层通常使用Same卷积以保持维度相同,而不同类型层之间的连接(例如卷积层和池化层),如果维度不同,则需要引入矩阵。
1x1卷积(1x1 convolution,或称为Network in Network)指滤波器的尺寸为 1。当通道数为1时,1x1卷积意味着卷积操作等同于乘积操作。
而当通道数更多时,1x1卷积的作用实际上类似全连接层的神经网络结构,从而降低(或升高,取决于滤波器组数)数据的维度。
池化能压缩数据的高度()及宽度(),而1×1卷积能压缩数据的通道数()。在如下图所示的例子中,用1个大小为1×1×32的滤波器进行卷积,就能使原先数据包含的32个通道压缩为1个。
如果保持通道数不变,也就是用32个1×1×32的滤波器进行卷积,由于卷积完还要代入到激活函数里,那么就相当于添加了一个非线性函数。
虽然论文Lin et al., 2013. Network in network中关于架构的详细内容并没有得到广泛应用,但是1x1卷积的理念十分有影响力,许多神经网络架构(包括 Inception 网络)都受到它的影响。
在之前的卷积网络中,我们只能选择单一尺寸和类型的滤波器。而Inception网络的作用即是代替人工来确定卷积层中的滤波器尺寸与类型,或者确定是否需要创建卷积层或池化层。
如图,Inception网络选用不同尺寸的滤波器进行Same卷积,并将卷积和池化得到的输出组合拼接起来,最终让网络自己去学习需要的参数和采用的滤波器组合。
相关论文:Szegedy et al., 2014, Going Deeper with Convolutions
计算成本问题
在提升性能的同时,Inception网络有着较大的计算成本。下图是一个例子:
图中有32个滤波器,每个滤波器的大小为5x5x192。输出大小为28x28x32,所以需要计算28x28x32个数字,对于每个数,都要执行5x5x192次乘法运算。加法运算次数与乘法运算次数近似相等。因此,可以看作这一层的计算量为28x28x32x5x5x192 = 1.2亿。
为了解决计算量大的问题,可以引入1x1卷积来减少其计算量。
对于同一个例子,我们使用1x1卷积把输入数据从192个通道减少到16个通道,然后对这个较小层运行5x5卷积,得到最终输出。这个1x1的卷积层通常被称作瓶颈层(Bottleneck layer)。
改进后的计算量为28x28x192x16 + 28x28x32x5x5x15 = 1.24千万,减少了约90%。
只要合理构建瓶颈层,就可以既显著缩小计算规模,又不会降低网络性能。
完整的Inception网络
上图是引入1x1卷积后的Inception模块。值得注意的是,为了将所有的输出组合起来,红色的池化层使用Same类型的填充(padding)来池化使得输出的宽高不变,通道数也不变。
多个Inception模块组成一个完整的Inception网络(比如下图红框就是一个Inception模块),如下图所示(被称为GoogLeNet,以向LeNet致敬):
注意黑色椭圆圈出的隐藏层,这些分支都是Softmax的输出层,可以用来参与特征的计算及结果预测,起到调整并防止发生过拟合的效果。
经过研究者们的不断发展,Inception模型的V2、V3、V4以及引入残差网络的版本被提出,这些变体都基于Inception V1版本的基础思想上。顺便一提,Inception模型的名字来自电影《盗梦空间》。
深度可分离卷积(depthwise separable convolution)可以减少卷积的计算量。
深度可分离卷积分为两步:
Depthwise:设输入图片的尺寸为,滤波器的尺寸改为(而不是),滤波器数量为,然后对图片的个通道都应用第一个滤波器,得到第一个通道,然后再对图片的个通道都应用第二个滤波器,得到第二个通道,依此类推,直至得到个通道的图片。
Pointwise:然后对第一步中得到的个通道的图片做1x1卷积,滤波器数量是个,这样就得到了通道为的图片。
之前的卷积是图片的每个通道都对应滤波器的一个通道,因此滤波器有多个通道,然后又有若干个滤波器。这里的第一步把多通道的滤波器改为一个通道,如果假设滤波器的参数是通过训练得到的,那么要更新的参数减少了。
我们把计算成本定义为:计算成本=滤波器参数个数×输出维度(不计通道数)×滤波器数量。
实际上深度可分离卷积的计算量是普通卷积的倍。
MoblieNet v1其实就是把卷积网络中的卷积运算都换成了深度可分离卷积,且一般进行13次深度可分离卷积运算;而MoblieNet v2又在深度可分离卷积的基础上加入了一个Expansion(拓展层),并且加入了残差网络的跳远连接,它一般进行17次瓶颈块运算。
Expansion和深度可分离卷积的Depthwise、Projection(即之前的Pointwise,只是不同叫法)合起来叫做Bottleneck block(瓶颈块)。
Expansion也是一个1×1卷积,通道数一般是输入的通道数的6倍(也可以自行设定),因为它把通道数变多了,所以叫“拓展”。后面的Depthwise、Pointwise和之前深度可分离卷积一样(上图中使用了填充,使输入输出维度保持一致)。由于Pointwise把通道数减少了,相当于降维,所以也叫Projection(投影)。
瓶颈块有两个作用:
EfficientNet
我们希望在计算能力较小的设备上运行规模较小的神经网络,而在计算能力较大的设备上运行规模较大的神经网络。
神经网络的规模有三个方面可以控制,一是输入的大小(比如普通图片和高分辨率图片)、二是网络深度(即隐藏层的数量)、三是网络宽度(即隐藏层的维度大小)。使用EfficientNet可以自动控制网络的规模。
使用开源的实现方案
很多神经网络复杂细致,并充斥着参数调节的细节问题,因而很难仅通过阅读论文来重现他人的成果。想要搭建一个同样的神经网络,查看开源的实现方案会快很多。
迁移学习
在“搭建机器学习项目”课程中,迁移学习已经被提到过。计算机视觉是一个经常用到迁移学习的领域。在搭建计算机视觉的应用时,相比于从头训练权重,下载别人已经训练好的网络结构的权重,用其做预训练,然后转换到自己感兴趣的任务上,有助于加速开发。
对于已训练好的卷积神经网络,可以将所有层都看作是冻结的,只需要训练与你的Softmax层有关的参数即可。大多数深度学习框架都允许用户指定是否训练特定层的权重。而冻结的层由于不需要改变和训练,可以看作一个固定函数。可以将这个固定函数存入硬盘,以便后续使用,而不必每次再使用训练集进行训练了。
你也可以加入残差网络,把输入的数据放到后面的层的激活函数输入里变成。
上述的做法适用于你只有一个较小的数据集。如果你有一个更大的数据集,应该冻结更少的层,然后训练后面的层。越多的数据意味着冻结越少的层,训练更多的层。如果有一个极大的数据集,你可以将开源的网络和它的权重整个当作初始化(代替随机初始化),然后训练整个网络。
数据扩增
计算机视觉领域的应用都需要大量的数据。当数据不够时,数据扩增(Data Augmentation)就有帮助。常用的数据扩增包括镜像翻转、随机裁剪、色彩转换。
其中,色彩转换是对图片的RGB通道数值进行随意增加或者减少,改变图片色调。另外,PCA颜色增强指更有针对性地对图片的RGB通道进行主成分分析(Principles Components Analysis,PCA),对主要的通道颜色进行增加或减少,可以采用高斯扰动做法来增加有效的样本数量。具体的PCA颜色增强做法可以查阅AlexNet的相关论文或者开源代码。
在构建大型神经网络的时候,数据扩增和模型训练可以由两个或多个不同的线程并行来实现。
通常,学习算法有两种知识来源:
手工工程(Hand-engineering,又称hacks)指精心设计的特性、网络体系结构或是系统的其他组件。手工工程是一项非常重要也比较困难的工作。在数据量不多的情况下,手工工程是获得良好表现的最佳方式。正因为数据量不能满足需要,历史上计算机视觉领域更多地依赖于手工工程。近几年数据量急剧增加,因此手工工程量大幅减少。
另外,在模型研究或者竞赛方面,有一些方法能够有助于提升神经网络模型的性能:
但是由于这些方法计算和内存成本较大,一般不适用于构建实际的生产项目。
目标检测是计算机视觉领域中一个新兴的应用方向,其任务是对输入图像进行分类的同时,检测图像中是否包含某些目标,并对他们准确定位并标识。
主要分为两种问题:一种是目标定位(object localization),一般和分类问题一起出现,叫目标分类定位,通常只有一个较大的对象位于图片中间,定位的同时还需判断属于哪种分类;另一种是目标检测(Object detection),图片可以含有多个对象,甚至单张图片中会有多个不同分类的对象。这两种问题可以同时存在,思路可以相互转化。
分类定位问题不仅要求判断出图片中物体的种类,还要在图片中标记出它的具体位置,用边框(Bounding Box,或者称包围盒)把物体圈起来。
符号约定:
?
表示。定义目标标签。如果;如果。
损失函数可以表示为,如果使用平方误差形式,对于不同的有不同的损失函数(注意下标指标签的第个值):
对于,即,有:
对于,即,有:
这里用平方误差简化了描述,但实际上,可以不用对、、计算损失。
除了使用平方误差,也可以使用逻辑回归损失函数,类标签也可以通过softmax输出。相比较而言,平方误差已经能够取得比较好的效果。
神经网络可以像标识目标的中心点位置那样,通过输出图片上的特征点,来实现对目标特征的识别。在标签中,这些特征点以多个二维坐标的形式表示。
通过检测人脸特征点可以进行情绪分类与判断,或者应用于AR领域等等。也可以透过检测姿态特征点来进行人体姿态检测。
想要实现目标检测,可以采用基于滑动窗口的目标检测(Sliding Windows Detection)算法。该算法的步骤如下:
滑动窗口目标检测的优点是原理简单,且不需要人为选定目标区域;缺点是需要人为直观设定滑动窗口的大小和步幅。滑动窗口过小或过大,步幅过大均会降低目标检测的正确率。另外,每次滑动都要进行一次CNN网络计算,如果滑动窗口和步幅较小,计算成本往往很大。
所以,滑动窗口目标检测算法虽然简单,但是性能不佳,效率较低。
相比从较大图片多次截取,在卷积层上应用滑动窗口目标检测算法可以提高运行速度。所要做的仅是将模型的全连接层换成卷积层来训练(即使用适当大小的滤波器进行卷积运算,使得变为卷积层后的全连接层的大小和原来的全连接层一致),然后将整张图片传入到训练好的模型中得到一组输出,这组输出的不同位置的元素就对应着不同的滑动窗口的值。
(这个模型规定最大池化层的宽高和步长相等)
普通的卷积神经网络是,比如对于14×14的RGB三通道图片,使用16组5×5×3的滤波器得到10×10×16的图像,然后通过步长为2的2×2的最大池化将图像压缩到5×5×16,然后将这400个元素组成列向量,放到全连接层上,再经过一个全连接层,最后通过Softmax输出,
这里我们要把全连接层换成卷积层,然后训练得到一个模型。比如对于14x14x3的图片,前面的步骤是一样的,用16组5×5×3的滤波器得到10×10×16的图像,然后通过步长为2的2×2的最大池化将图像压缩到5×5×16,然后不同的是,对这5×5×16的图像,要用400组5×5×16的滤波器得到1×1×400的图像,再经过400组1×1×400的滤波器得到1×1×400的图像,最后通过4组(假设输出4个数据)1×1×400的滤波器得到1×1×4的输出层。
得到了模型之后,假设测试集的图片是16×16的RGB三通道,根据滑动窗口的思想,我们本应先在左上角画一个14×14的“窗口”,然后把这部分的图片输入到模型中,生成一个输出;接着滑动窗口,假设步长为2,向右滑动2个像素得到一部分的图片然后输入到模型中得到第二个输出;依此类推(如上图,中分别是红、绿、橙、紫四个“窗口”)。但是仔细观察,其实很多计算都是重复的,实际上我们只需把整张图片输入到神经网络中,就会得到一个2×2的输出,每个位置分别对应着前面4个窗口的结果。
对于更复杂的图片,比如28x28的RGB三通道,得到的输出层为8x8x4,共64个窗口结果。
运行速度提高的原理:在滑动窗口的过程中,需要重复进行CNN正向计算。因此,不需要将输入图片分割成多个子集,分别执行前向传播,而是将它们作为一张图片输入给卷积网络进行一次CNN正向计算。这样,公共区域的计算可以共享,以降低运算成本。
在上述算法中,边框的位置可能无法完美覆盖目标,或者大小不合适,或者最准确的边框并非正方形,而是长方形。
YOLO算法(You Only Look Once)可以用于得到更精确的边框。YOLO算法将原始图片划分为n×n网格,并将目标定位一节中提到的图像分类和目标定位算法,逐一应用在每个网格中,每个网格都有标签如:
若某个目标的中点落在某个网格,则该网格负责检测该对象。
如上面的示例中,如果将输入的图片划分为3×3的网格、需要检测的目标有3类,则每一网格部分图片的标签会是一个8维的列矩阵,最终输出的就是大小为3×3×8的结果。要得到这个结果,就要训练一个输入大小为100×100×3,输出大小为3×3×8的CNN(用前面把全连接层改为卷积层的CNN)。在实践中,输出可能使用更为精细的19×19网格,则两个目标的中点在同一个网格的概率更小。
YOLO算法的优点:
YOLO算法设、、、的值是相对于网格长或宽的比例。每个小网格的左上角是、右下角是,则、在0到1之间,而、可以大于1。当然,也有其他参数化的形式,且效果可能更好。这里只是给出一个通用的表示方法。
相关论文:Redmon et al., 2015. You Only Look Once: Unified, Real-Time Object Detection。
交互比(IoU, Intersection Over Union)函数用于评价对象检测算法,它计算预测边框和实际边框交集(I)与并集(U)之比:
IoU的值在0~1之间,且越接近1表示目标的定位越准确。IoU大于等于0.5时,一般可以认为预测边框是正确的,当然也可以更加严格地要求一个更高的阈值。
YOLO算法中,可能有很多网格检测到同一目标。非极大值抑制(Non-max Suppression)会通过清理检测结果,找到每个目标中点所位于的网格,确保算法对每个目标只检测一次。
如上图,不同网格中都认为自己识别到了中心点,这些“中心点”分别对应不同的方框,我们观察每个方框的(前面定义的只能取值0和1,这个其实是原来的乘以),先找出最大的那个是0.9,然后逐一审视剩下的方框,把所有和这个最大的方框有很高交并比(即高度重叠)的其他方框去掉,得出一个目标的方框;然后在剩下的方框中找出下一个最大的,依此类推。
进行非极大值抑制的步骤如下:
上述步骤适用于单类别目标检测。进行多个类别目标检测时,对于每个类别,应该单独做一次非极大值抑制。
到目前为止,我们讨论的情况都是一个网格只检测一个对象。如果要将算法运用在多目标检测上,需要用到Anchor Boxes。一个网格的标签中将包含多个Anchor Box,相当于存在多个用以标识不同目标的边框。
在上图示例中,我们希望同时检测人和汽车两种物体。因此,每个网格的的标签中需要含有两个Anchor Box,即每个网络的变为,也就是将输出的标签结果大小从3×3×8变为3×3×16。若两个都大于预设阈值,则说明检测到了两个目标。
在单目标检测中,图像中的目标被分配给了包含该目标中点的那个网格;引入Anchor Box进行多目标检测时,图像中的目标则被分配到了包含该目标中点的那个网格,以及跟目标的实际边界框具有最高IoU值的Anchor Box。
即,每个网格都对应一个,假设分为Anchor Box1和Anchor Box2两部分,以为中心分别画出两个Anchor Box,哪个Anchor Box的形状和目标实际边界框的IoU高,就分给的哪个Anchor Box。
像上图,一个网格中既有车也有人,那么一般人的形状是Anchor Box1,车的形状是Anchor Box2,那么就把人的各项输出绑定到了的Anchor Box1的部分,车的各项输出绑定到了的Anchor Box2的部分。
Anchor Boxes也有局限性,对于同一网格有三个及以上目标,或者两个目标的Anchor Box高度重合的情况处理不好。
Anchor Box的形状一般通过人工选取。高级一点的方法是用k-means将两类对象形状聚类,选择最具代表性的Anchor Box。
前面介绍的滑动窗口目标检测算法对一些明显没有目标的区域也进行了扫描,这降低了算法的运行效率。为了解决这个问题,R-CNN(Region CNN,带区域的 CNN)被提出。通过对输入图片运行图像分割算法,在不同的色块上找出候选区域(Region Proposal),就只需要在这些区域上运行分类器。
R-CNN的缺点是运行速度很慢,所以有一系列后续研究工作改进。例如Fast R-CNN(与基于卷积的滑动窗口实现相似,但得到候选区域的聚类步骤依然很慢)、Faster R-CNN(使用卷积对图片进行分割)。不过大多数时候还是比YOLO算法慢。
相关论文:
转置卷积(Transposed Convolution)又叫反卷积(Deconvolution),它是在输出上放置过滤器,把卷积的过程反过来。
如图,设过输入为、滤器尺寸、填充、步长,根据之前的公式,有,代入、、、的值,可算出最大可以是4,所以画出一个的框,外面再填充格。输入的第一个元素乘以滤波器,放到输出的对应滤波器位置上,其中填充部分不用写;然后输入的第二个元素乘以滤波器,放到输出的对应滤波器位置上,其中和第一次重叠的元素要加起来,依此类推,最后去掉填充的部分,剩下的就是得到的输出。
对于多通道,转置卷积和普通的卷积一样,把得到的多个通道的值加起来,变成一个通道。(因此有多少个滤波器输出就有多少个通道)
像判断马路在哪里,或者从背景中分割出汽车等问题,单单框出物体是不够的,需要按像素判断。
U-Net是一种语义分割(Semantic Segmentation)算法,它可以理解图片中每一个像素是什么意思,分辨出图片中的物体。比如每个数值表示一种物体,在对应的像素位置输出它所属种类的数字(其实应该在不同通道输出属于该种类的概率)。
如上图(图中没有体现出隐藏层的通道数):
U-Net的前半部分使用普通的卷积以及最大池化,将图片压缩得很小,但维度变得很深(即通道数很多),这样可以学习到高维特征。
后半部分使用普通的卷积以及转置卷积相结合,而且每次进行转置卷积后通道数要减少(因为跳远连接传过来的数据需要和这一层数据的通道数一样才能运算,而之前的通道数是不断增加的,现在要对应地不断减少),最后将大小恢复到原始输入图像的大小。
它同时也使用了残差网络的跳远连接,将前面的数据复制到后面的层中,和后面的数据拼在一起,因为在学习到高维特征的同时,图像信息其实是丢失了的,传入前面的图像信息有助于判断图片中的内容。
最后输出的宽高要和原图片一致,但通道数不一定要和原图片相同,有多少个分类就输出多少个通道,一个通道表示一个种类,每个通道里面的值表示该像素属于对应种类的概率。如果每个像素都取概率最大的那个种类,给不同的种类上分配不同的颜色,就可以可视化为一张图片。
人脸验证(Face Verification)和人脸识别(Face Recognition)的区别:
一般来说,由于需要匹配的身份信息更多导致错误率增加,人脸识别比人脸验证更难一些。
活体检测可以通过监督学习解决,这里我们只讨论人脸验证问题。
人脸识别所面临的一个挑战是要求系统只采集某人的一个面部样本,就能快速准确地识别出这个人,即只用一个训练样本来获得准确的预测结果。这被称为One-Shot学习。
有一种方法是假设数据库中存有个人的身份信息,对于每张输入图像,用Softmax输出种标签,分别对应每个人以及都不是。然而这种方法的实际效果很差,因为过小的训练集不足以训练出一个稳健的神经网络;并且如果有新的身份信息入库,需要重新训练神经网络,不够灵活。
因此,我们通过学习一个Similarity函数来实现One-Shot学习过程。Similarity函数定义了输入的两幅图像的差异度,其公式如下:
可以设置一个超参数作为阈值,作为判断两幅图片是否为同一个人的依据。
实现Similarity函数的一种方式是使用Siamese网络,它是一种对两个不同输入运行相同的卷积网络,然后对它们的结果进行比较的神经网络。
如上图示例,将图片、 分别输入两个相同的卷积网络中,经过全连接层后不再进行Softmax,而是得到特征向量、。这时,Similarity函数就被定义为两个特征向量之差的L2范数:
训练神经网络的参数,使得当两张图片是同一个人时上式输出一个很小的值,不是同一个人时输出一个很大的值。
相关论文:Taigman et al., 2014, DeepFace closing the gap to human level performance
Triplet 损失函数用于训练出合适的参数,以获得高质量的人脸图像编码。“Triplet”一词来源于训练这个神经网络需要大量包含Anchor(靶目标)、Positive(正例)、Negative(反例)的图片组,其中Anchor和Positive需要是同一个人的人脸图像。
对于这三张图片,应该有:
其中,被称为间隔(margin),用于确保不会总是输出零向量(或者一个恒定的值)。
如果没有,移项后会得到,这时如果,那么这条等式就恒成立了,但这不是我们想要的。加上可以排除掉这种情况;同时,通过合理地设置,可以让和有显著的区别,让分类更清楚。
Triplet损失函数的定义:
因为我们要让,如果它一旦大于0,那么损失函数就是一个正数,网络的参数会被梯度下降继续优化。当它时会取0,此时网络并不关心它负得有多大,只要与的区别就可以了。
对于大小为的训练集,代价函数为:
通过梯度下降最小化代价函数。
在选择训练样本时,随机选择容易使Anchor和Positive极为接近,而Anchor和Negative相差较大,以致训练出来的模型容易抓不到关键的区别。因此,最好的做法是人为增加Anchor和Positive的区别,缩小Anchor和Negative的区别,促使模型去学习不同人脸之间的关键差异。
相关论文:Schroff et al., 2015, FaceNet: A unified embedding for face recognition and clustering
除了Triplet损失函数,二分类结构也可用于学习参数以解决人脸识别问题。其做法是输入一对图片,将两个Siamese网络产生的特征向量输入至同一个Sigmoid单元,输出1则表示是识别为同一人,输出0则表示识别为不同的人。
Sigmoid单元对应的表达式为:
其中,表示向量的第个元素,和都是通过梯度下降算法迭代训练得到的参数。
上述计算表达式也可以用另一种表达式代替:
其中, 被称为相似度。
无论是对于使用Triplet损失函数的网络,还是二分类结构,为了减少计算量,可以提前计算好编码输出并保存。这样就不必存储原始图片,并且每次进行人脸识别时只需要计算测试图片的编码输出。
神经风格迁移(Neural style transfer)将参考风格图像的风格“迁移”到另外一张内容图像中,生成具有其特色的图像。
想要理解如何实现神经风格转换,首先要理解在输入图像数据后,一个深度卷积网络从中都学到了些什么。我们借助可视化来做到这一点。
我们通过遍历所有的训练样本,找出使各层各个单元/通道的激活函数输出最大的那些图像区域(比如每个通道找9张,然后全部拼起来得到上面的图)。可以看出,不同通道捕获的特征不同,且浅层的隐藏层通常检测出的是原始图像的边缘、颜色、阴影等简单信息。随着层数的增加,隐藏单元能捕捉的区域更大,学习到的特征也由从边缘到纹理再到具体物体,变得更加复杂。
相关论文:Zeiler and Fergus., 2013, Visualizing and understanding convolutional networks
神经风格迁移生成图片G的代价函数如下:
它分为两部分,一部分是内容代价函数,用来衡量内容图片与生成的图片的内容相似度;另一部分是风格代价函数,用来衡量图片与生成的图片的风格相似度。其中,、是用于控制相似度比重的超参数。
神经风格迁移的算法步骤如下:
相关论文:Gatys al., 2015. A neural algorithm of artistic style
的计算过程如下:
和越相似,则越小。其中是用来归一化的,可以改成其他,改成什么影响不大,因为都可以由和调整。
每个通道提取图片的特征不同,比如标为红色的通道提取的是图片的垂直纹理特征,标为黄色的通道提取的是图片的橙色背景特征。计算这两个通道的相关性,那么相关性的大小就表示了原始图片既包含垂直纹理也包含橙色背景的可能性大小。通过CNN,“风格”被定义为同一个隐藏层不同通道之间激活函数值的相关系数,因其反映了原始图片特征间的相互关系。
对于风格图像,网络中第层的相关系数可以以一个gram矩阵的形式表示:
其中,和为第层的高度和宽度;和为选定的通道,其范围为到; 为激活函数值。(上述公式可以表述为:图片在CNN第层的两个不同通道的激活函数值对应位置元素相乘之和)
同理,对于生成图像,有:
因此,第层的风格代价函数为:
如果对各层都使用风格代价函数,效果会更好。因此有:
其中,是用于设置不同层所占权重的超参数。
之前我们处理的都是二维图片,实际上卷积也可以延伸到一维和三维数据。我们举两个示例来说明。
EKG数据(心电图)是由时间序列对应的每个瞬间的电压组成,是一维数据。一般来说我们会用 RNN(循环神经网络)来处理,不过如果用卷积处理,则有:
而对于三维图片的示例,有
自然语言和音频都是前后相互关联的数据,对于这些序列数据需要使用循环神经网络(Recurrent Neural Network,简称RNN)来进行处理。
使用RNN实现的应用包括下图中所示:
符号约定
对于一个序列数据,用符号来表示这个数据中的第个元素,用来表示第个标签,用和来表示输入和输出的长度。对于一段音频,元素可能是其中的几帧;对于一句话,元素可能是一到多个单词。
第个序列数据的第个元素用符号,第个标签即为,对应地,有和.
比如对于一个句子,我们要识别其中的人名,每个单词对应一个数字,0表示不是人名,1表示是人名(其实还有更好的输出形式,这里我们用最简单的那种举例):
这时每个单词按顺序分别用表示,其中表示它们是时序序列(但其实无论是不是时序序列,我们都用表示),输出同理,分别用表示,用表示输入序列的长度,这个例子中是9个单词,所以,相应的,,当然,有时输入输出长度也可以不一样。
想要表示一个词语,需要先建立一个词汇表(Vocabulary),或者叫字典(Dictionary)。将需要表示的所有词语变为一个列向量,可以根据字母顺序排列,然后根据单词在向量中的位置,用one-hot向量(one-hot
vector)来表示该单词的标签:将每个单词编码成一个向量,其中是词汇表中单词的数量。一个单词在词汇表中的索引在该向量对应的元素为1,其余元素均为0。如果遇到了一个不在词汇表里的单词,那就创建一个新的标记,比如UNK
,我们后面会详细讲这个。
例如,“zebra”排在词汇表的最后一位,因此它的词向量表示为:
补充:one-hot向量是最简单的词向量。它的缺点是,由于每个单词被表示为完全独立的个体,因此单词间的相似度无法体现。例如单词hotel和motel意思相近,而与cat不相似,但是.
对于序列数据,使用标准神经网络存在以下问题:
为了解决这些问题,引入循环神经网络(Recurrent Neural Network,简称RNN)。一种循环神经网络的结构如下图所示(最右边的是简化版画法):
当元素输入对应时间步(Time Step)的隐藏层的同时,该隐藏层也会接收来自上一时间步的隐藏层的激活值,其中一般直接初始化为零向量。一个时间步输出一个对应的预测结果。
循环神经网络从左向右扫描数据,同时每个时间步的参数也是共享的,输入、激活、输出的参数对应为、、(即每一层都用相同的这几个参数)。
下图是一个RNN神经元的结构:
前向传播过程的公式如下:
这些的下标表示了,比如,用来算,并且是用来乘以的。同理。
激活函数通常选择tanh,有时也用ReLU;可选sigmoid或softmax,取决于需要的输出类型。
为了进一步简化公式以方便运算,可以将、 水平并列为一个矩阵,同时 和 垂直堆叠成一个矩阵。则有:
为了计算反向传播过程,需要先定义一个损失函数。单个位置上(或者说单个时间步上)某个单词的预测值的损失函数采用交叉熵损失函数(Cross Entropy Loss),如下所示:
将单个位置上的损失函数相加,得到整个序列的损失函数如下:
循环神经网络的反向传播被称为通过时间反向传播(Backpropagation through time),因为从右向左计算的过程就像是时间倒流。计算公式如下:
最后一条公式那里我算出来应该是,而不是。
某些情况下,输入长度和输出长度不一致。根据所需的输入及输出长度,循环神经网络可分为“一对一”、“多对一”、“多对多”等结构:
目前我们看到的模型的问题是,只使用了这个序列中之前的信息来做出预测,即后文没有被使用。可以通过双向循环神经网络(Bidirectional RNN,简称BRNN)来解决这个问题。
语言模型(Language Model)是根据语言客观事实而进行的语言抽象数学建模,能够估计某个序列中各元素出现的可能性,并依此做出更准确的判断。
比如,在语音识别系统中,对于两个读音相近的句子,根据在某个位置上每个单词出现的概率,计算出整个句子出现的概率:
这样,我们就知道语音中更可能说的是第二个句子。
建立语言模型所采用的训练集是一个大型的语料库(Corpus),指数量众多的句子组成的文本。建立过程的第一步是标记化(Tokenize),即建立字典;然后将语料库中的每个词表示为对应的one-hot向量。另外,需要增加一个额外的标记EOS(End of Sentence)来表示一个句子的结尾。标点符号可以忽略,也可以加入字典后用one-hot向量表示。
对于语料库中部分特殊的、不包含在字典中的词汇,例如人名、地名,可以不必针对这些具体的词,而是在句子中把它们替换成一个标记,比如UNK
(unknown
words),并在字典中加入一个UNK
标记来表示所有的特殊词汇。
将标志化后的训练集用于训练RNN,过程如下图所示:
在第一个时间步中,输入的和都是零向量,是通过softmax预测出的字典中每个词作为第一个词出现的概率;在第二个时间步中,输入的是训练样本的标签中的第一个单词(即“cats”)和上一层的激活项,输出的表示的是通过softmax预测出的、单词“cats”后面出现字典中的其他每个词的条件概率。以此类推,最后就可以得到整个句子出现的概率。
定义单个预测的损失函数为(比如用Softmax输出,那么是一个列向量,下标表示列向量的第个元素):
则总体的损失函数为:
在训练好一个语言模型后,可以通过采样(Sample)新的序列来了解这个模型中都学习到了一些什么。
在第一个时间步输入和为零向量,用比如softmax等方式输出预测出的字典中每个词作为第一个词出现的概率,然后进行随机采样(比如Python可以用numpy的np.random.choice
),将采样得到的作为第二个时间步的输入。以此类推,直到采样到EOS,最后模型会自动生成一些句子,从这些句子中可以发现模型通过语料库学习到的知识。
这里建立的是基于词汇构建的语言模型。根据需要也可以构建基于字符的语言模型,其优点是不必担心出现未知标识(UNK),其缺点是得到的序列过多过长,并且训练成本高昂。因此,基于词汇构建的语言模型更为常用。
对于以上两个句子,后面的动词单复数形式由前面的名词的单复数形式决定。但是基本的RNN不擅长捕获这种长期依赖关系。究其原因,由于梯度消失,在反向传播时,后面层的输出误差很难影响到较靠前层的计算,网络很难调整靠前的计算。
在反向传播时,随着层数的增多,梯度不仅可能指数型下降,也有可能指数型上升,即梯度爆炸。不过梯度爆炸比较容易发现,因为参数会急剧膨胀到数值溢出(可能显示为NaN)。这时可以采用梯度修剪(Gradient Clipping)来解决:观察梯度向量,如果它大于某个阈值,则缩放梯度向量以保证其不会太大。相比之下,梯度消失问题更难解决。GRU和LSTM都可以作为缓解梯度消失问题的方案。
GRU(Gated Recurrent Units, 门控循环单元)改善了RNN的隐藏层,使其可以更好地捕捉深层连接,并改善了梯度消失问题。
当我们从左到右读上面这个句子时,GRU单元有一个新的变量称为,代表记忆细胞(Memory Cell),其作用是提供记忆的能力,记住例如前文主语是单数还是复数等信息。在时间,记忆细胞的值等于输出的激活值; 代表下一个的候选值。代表更新门(Update Gate),用于决定什么时候更新记忆细胞的值。以上结构的具体公式为:
当使用sigmoid作为激活函数来得到时,的值在0到1的范围内,且大多数时间非常接近于0或1。当时,被更新为,否则保持为。因为可以很接近0,因此几乎就等于。在经过很长的序列后,的值依然被维持,从而实现“记忆”的功能。
以上实际上是简化过的GRU单元,但是蕴涵了GRU最重要的思想。完整的GRU单元添加了一个新的相关门(Relevance Gate),表示和的相关性。因此,表达式改为如下所示:
这里和本节前面的
*
都是指element-wise乘法,即同位置的元素相乘。
相关论文:
LSTM(Long Short Term Memory,长短期记忆)网络比GRU更加灵活和强大,它额外引入了遗忘门(Forget Gate)和输出门(Output Gate)。其结构图和公式如下:
对比GRU,相当于用替换掉了GRU中的,并且此时不再等于。实际上,LSTM比GRU更早出现,可以把GRU看作是LSTM的简化版本。
同样的,这里的
*
指element-wise乘法,即同位置的元素相乘。
将多个LSTM单元按时间次序连接起来,就得到一个LSTM网络。
以上是简化版的LSTM。在更为常用的版本中,几个门值不仅取决于和,有时也可以偷窥上一个记忆细胞输入的值(也就是变为),这被称为窥视孔连接(Peephole Connection)。这时,和GRU不同,和门值是一对一的(比如的第1个元素只能影响门的第1个元素)。
常被初始化为零向量。
相关论文:Hochreiter & Schmidhuber 1997. Long short-term memory
单向的循环神经网络在某一时刻的预测结果只能使用之前输入的序列信息。双向循环神经网络(Bidirectional RNN,简称BRNN)可以在序列的任意位置使用之前和之后的数据。其工作原理是增加一个反向循环层,结构如下图所示:
上图的从左边开始算,中间各层的值用表示,从右边开始算(比如一句话,从右边开始读),中间各层的值用表示。
因此,有
这个改进的方法不仅能用于基本的RNN,也可以用于GRU或LSTM。缺点是需要完整的序列数据,才能预测任意位置的结果。例如构建语音识别系统,需要等待用户说完并获取整个语音表达,才能处理这段语音并进一步做语音识别。因此,实际应用会有更加复杂的模块。
循环神经网络的每个时间步上也可以包含多个隐藏层,形成深度循环神经网络(Deep RNN)。结构如下图所示:
以 为例,有 。
one-hot向量将每个单词表示为完全独立的个体,不同词向量都是正交的,因此单词间的相似度无法体现。
换用特征化表示方法能够解决这一问题。我们可以通过用语义特征作为维度来表示一个词,因此语义相近的词,其词向量也相近。(如下图,不过实际上这个矩阵是通过训练得到的,每个值代表什么意思无法得知)
词嵌入(Word Embedding)是NLP中语言模型与表征学习技术的统称,概念上而言,它是指把一个维数为所有词的数量的高维空间(one-hot形式表示的词)“嵌入”到一个维数低得多的连续向量空间中,每个单词或词组被映射为实数域上的向量。
将高维的词嵌入“嵌入”到一个二维空间里,就可以进行可视化。常用的一种可视化算法是t-SNE算法。在通过复杂而非线性的方法映射到二维空间后,每个词会根据语义和相关程度聚在一起。
t-SNE 中主要是将“距离的远近关系”转化为一个概率分布,每一个概率分布就对应着一个“样本间距离远近”的关系。而降维前后的数据都各自对应着一个概率分布,我们就希望这两个概率分布足够的接近。
首先随机生成同等数量的低维数据,然后计算出损失函数(该损失函数就度量了两个概率分布之间的差异),用梯度下降的方法来更新这批数据,最终得到满足要求的低维数据。
相关论文:van der Maaten and Hinton., 2008. Visualizing Data using t-SNE
用词嵌入做迁移学习可以降低学习成本,提高效率。其步骤如下:
词嵌入可用于类比推理。例如,给定对应关系“男性(Man)”对“女性(Woman)”,想要类比出“国王(King)”对应的词汇。则可以有,之后的目标就是找到词向量,来找到使相似度最大。
一个最常用的相似度计算函数是余弦相似度(cosine similarity)。公式为:
相关论文:Mikolov et. al., 2013, Linguistic regularities in continuous space word representations
不同的词嵌入方法能够用不同的方式学习到一个嵌入矩阵(Embedding Matrix)。将字典中位置为的词的one-hot向量表示为,词嵌入后生成的词向量用表示,则有:
但在实际情况下一般不这么做,因为one-hot向量维度很高,且几乎所有元素都是0,这样做的效率太低。其实就相当于把的第列取了出来,因此,实践中直接用专门的函数查找矩阵的特定列。(这个过程有点像查表,所以有的地方把嵌入矩阵称为Lookup Table)
神经概率语言模型(Neural Probabilistic Language Model)构建了一个能够通过上下文来预测未知词的神经网络,在训练这个语言模型的同时学习词嵌入。
训练过程中,将语料库中的某些词作为目标词,目标词的部分上下文的词向量竖向堆叠成一个列向量,以该列向量作为输入,放进神经网络中,训练该模型,使Softmax输出的预测结果为目标词。嵌入矩阵和神经网络各层的、为需要通过训练得到的参数。这样,在得到嵌入矩阵后,就可以得到词嵌入后生成的词向量。
相关论文:Bengio et. al., 2003, A neural probabilistic language model
Word2Vec 是一种简单高效的词嵌入学习算法,包括2种模型:
每种语言模型又包含负采样(Negative Sampling)和分级的Softmax(Hierarchical Softmax)两种训练方法。
训练神经网络时候的隐藏层参数即是学习到的词嵌入。
相关论文:
Skip-gram随机抽取一个词作为中心词(即前面说的“目标词”),然后在该词的正负一定词距内随机选取若干个词(即上下文)来训练,然后构造一个监督学习问题,给定中心词,让它预测出正负一定词距内的上下文有哪些。
实际上的训练中,并不是随机抽取中心词的,比如对于句子”I love you so much.”,选定词距为1,先选取”I”作中心词,那么它的上下文就只能是”love”;然后选取”love”作为中心词,那么它的上下文可能是”I”或者”you”,依此类推。
如果用框框住选定的那些词,那么这个框移动的过程就像滑动窗户一样,因此被称为“滑窗”。
如上图,输入的one-hot向量乘以嵌入矩阵得到词向量(又叫中心词向量矩阵,这个过程图中用隐藏层表示,300个神经元表示得到了一个的矩阵,可以把看作是第一层神经网络的参数),然后乘以矩阵变回和总词汇数一样的行数(是上下文词向量矩阵,可以把看作是第二层神经网络的参数),最后取Softmax可得到每个词是上下文的概率,然后与答案对比,计算损失。
注意到输入是一个one-hot向量,乘以嵌入矩阵其实是把的某一列取了出来,因此,反向传播时,每次其实都只更新其中一列。
如图,某个词的词向量的维度是,那么这个词对应的的维度就应该是(乘的时候是)。
设某个词为(指center word,中心词),该词的一定词距内选取一些配对的目标上下文(指target word,即我们要算概率的词),用Softmax输出每个上下文词的条件概率:
其中是总的词汇数;是一个与有关的参数;这里的公式省略了用以纠正偏差的参数。
公式中的可以看作是这个只有一列的矩阵的第个元素。
假设总词汇数是10,000,那么就会产生10,000个概率,每个位置的概率就是词汇表中对应位置的单词是上下文的概率。
损失函数仍选用交叉熵:
在此Softmax分类中,每次计算条件概率时,需要对词典中所有词做求和操作,因此计算量很大。解决方案之一是使用一个分级的Softmax分类器(Hierarchical Softmax Classifier),形如二叉树。在实践中,一般采用霍夫曼树(Huffman Tree)而非平衡二叉树,常用词在顶部。
如果在语料库中随机均匀采样得到选定的词,则’the’, ‘of’, ‘a’, ’and’等出现频繁的词将影响到训练结果。因此,采用了一些策略来平衡选择。
CBOW(Continuous Bag of Words)模型的工作方式与Skip-gram相反,通过采样上下文中的词来预测中心词。
如上图,假设总词汇数是,取个上下文的词汇用one-hot向量表示,按行排列组成一个的矩阵,乘以嵌入矩阵(图中用表示,乘的时候要转置,即)得到一个的矩阵,再每行求平均值得到一个的矩阵,然后乘以矩阵变回和总词汇数一样的行数,最后再用Softmax输出。
吴恩达老师没有深入去讲CBOW。想要更深入了解的话,推荐资料:
为了解决Softmax计算较慢的问题,我们重新定义每个单词的概率的表示方法。如图,把所有的单词排在二叉树最下面(排法随意),记录每个节点往右边走的概率,图中的二叉树有三个节点,可以表示4个单词。
比如要计算选定上下文词(比如context),目标词是name的概率,就是从节点1出发。往左走到节点2,再往右走到单词name,那么概率就是P(name|context)=P(节点1往左走|context)×P(节点2往右走|context)=(1-P(节点1往右走|context))×P(节点2往右走|context)。
每个单词作为输入时都对应这样一颗不同的二叉树。
更新的时候只需要更新经过的每个节点的模型中相关的参数即可。(比如每个节点都用Logistics回归、用sigmoid来计算概率,那就更新经过的节点的,而可以简化不要)
霍夫曼树(Huffman Tree)和Hierarchical Softmax稍有不同,它是根据词频来构建的,比如:
I love data science. I love big data. I am who I am.
这句话中,“I”出现了4次,因此它的freq(词频)是4,“love”出现了2次,所以freq是2,….(上图表中code是路径,total bits是需要更新的总次数,比如”I”,出现了4次,每次都要更新2个节点,那就总共需要更新8次)
上图的树是这样画的:先汇总词频最低的,比如”science”、“big”,它们都只出现了1次,把它们汇总得到了节点2,再看”who”也只出现了一次,那就把它和前面的两个都汇总到节点3,…;而”I”出现了4次,因此它的路径是最短的。
词频越高,路径就越短,每次更新的节点数就越少。
为了解决Softmax计算较慢的问题,Word2Vec的作者后续提出了负采样(Negative Sampling)模型。对于监督学习问题中的分类任务,在训练时同时需要正例和负例。在分级的Softmax中,负例放在二叉树的根节点上;而对于负采样,负例是随机采样得到的。
如上图所示,当输入的词为一对上下文-目标词时,标签设置为1(这里的上下文也是一个词)。另外任意取对非上下文-目标词作为负样本,标签设置为0。(该方法的作者建议在5-20之间,数据集越大,就越小,比如2-5之间)
改用多个Sigmoid输出上下文-目标词为正样本的概率:
其中,、分别代表目标词和上下文的词向量。
这个其实就是在对每个单词对应的节点做Logistics回归,每个节点都对应一个不同的Logistics回归。其中乘出来是一个数。
此时的损失函数为,然后最小化损失函数即可。
之前训练中每次要更新维的多分类Softmax单元(为词典中词的数量)。现在每次只需要更新维的二分类Sigmoid单元,计算量大大降低。
比如选一个词orange,要判断king是不是目标词,那么本来按之前的方法会得到一个的向量(是总词汇数),然后对这个向量作Softmax就需要对个向量分别作再求和,然后再计算。
而现在我们输入(c,t)=(orange, king),算法就直接给出一个0到1之间的值(这个值就是是的目标词的概率),不需要再作Softmax,也就是不需要再对那么多的数进行求和了。
更新的时候,我们有1个正样本和个负样本,也就是只需要更新个Sigmoid单元。
关于计算选择某个词作为负样本的概率,作者推荐采用以下公式(而非经验频率或均匀分布,因为经验频率会导致’the’, ‘of’, ‘a’, ’and’等出现频繁的词影响到训练结果,均匀分布会导致选的词没有代表性;下面的公式其实是两者的结合):
其中,代表语料库中单词出现的频率。上述公式更加平滑,能够增加低频词的选取可能。
相关论文:Mikolov et. al., 2013. Distributed representation of words and phrases and their compositionality
GloVe(Global Vectors)是另一种流行的词嵌入算法。GloVe模型基于语料库统计了词的共现矩阵,中的元素表示单词和单词为“上下文-目标词”的次数(即单词和单词出现位置相近的计数器)。之后,用梯度下降法最小化以下损失函数:
其中,、是单词和单词的词向量;是总的词汇数;是一个用来避免时为负无穷大、并在其他情况下调整权重的函数。时,,并定义。
“上下文-目标词”对可以代表两个词出现在同一个窗口。在这种情况下,和是完全对称的。因此,在训练时可以一致地初始化二者,使用梯度下降法处理完以后取平均值作为二者共同的值。
相关论文:Pennington st. al., 2014. Glove: Global Vectors for Word Representation
最后,使用各种词嵌入算法学到的词向量实际上大多都超出了人类的理解范围,难以从某个值中看出与语义的相关程度。
情感分类是指分析一段文本对某个对象的情感是正面的还是负面的,实际应用包括舆情分析、民意调查、产品意见调查等等。情感分类的问题之一是标记好的训练数据不足。但是有了词嵌入得到的词向量,中等规模的标记训练数据也能构建出一个效果不错的情感分类器。
如上图所示,用词嵌入方法获得嵌入矩阵 后,计算出句中每个单词的词向量并取平均值(对应位置元素),输入一个Softmax单元,输出预测结果。这种方法的优点是适用于任何长度的文本;缺点是没有考虑词的顺序,对于包含了多个正面评价词的负面评价,很容易预测到错误结果。
使用RNN能够实现一个效果更好的情感分类器:
语料库中可能存在性别歧视、种族歧视、性取向歧视等非预期形式偏见(Bias),这种偏见会直接反映到通过词嵌入获得的词向量。例如,使用未除偏的词嵌入结果进行类比推理时,“Man”对 “Computer Programmer” 可能得到 “Woman” 对 “Housemaker” 等带有性别偏见的结果。词嵌入除偏的方法有以下几种:
1. 中和本身与性别无关词汇
对于“医生(doctor)”、“老师(teacher)”、“接待员(receptionist)”等本身与性别无关词汇,可以中和(Neutralize)其中的偏见。首先用“女性(woman)”的词向量减去“男性(man)”的词向量,得到的向量就代表了“性别(gender)”。假设现有的词向量维数为50,那么对某个词向量,将50维空间分成两个部分:与性别相关的方向和与正交的其他49个维度。如下左图:
而除偏的步骤,是将要除偏的词向量(左图中的 )在向量 方向上的值置为 0,变成右图所示的 。
公式如下:
2. 均衡本身与性别有关词汇
对于“男演员(actor)”、“女演员(actress)”、“爷爷(grandfather)”等本身与性别有关词汇,中和“婴儿看护人(babysit)”中存在的性别偏见后,还是无法保证它到“女演员(actress)”与到“男演员(actor)”的距离相等。对这样一对性别有关的词,除偏的过程是均衡(Equalization)它们的性别属性。其核心思想是确保一对词(actor和actress)到的距离相等。
公式:
Seq2Seq(Sequence-to-Sequence)模型能够应用于机器翻译、语音识别等各种序列到序列的转换问题。一个Seq2Seq模型包含编码器(Encoder)和解码器(Decoder)两部分,它们通常是两个不同的RNN。如下图所示,将编码器的输出作为解码器的输入,由解码器负责输出正确的翻译结果。
提出 Seq2Seq 模型的相关论文:
这种编码器-解码器的结构也可以用于图像描述(Image captioning)。将AlexNet作为编码器,最后一层的Softmax换成一个RNN作为解码器,网络的输出序列就是对图像的一个描述。
图像描述的相关论文:
选择最可能的句子
机器翻译用到的模型与语言模型相似,只是用编码器的输出作为解码器第一个时间步的输入(而非0)。因此机器翻译的过程其实相当于建立一个条件语言模型。
由于解码器进行随机采样过程,输出的翻译结果可能有好有坏。因此需要找到能使条件概率最大化的翻译,即
鉴于贪心搜索算法得到的结果显然难以不符合上述要求,解决此问题最常使用的算法是集束搜索(Beam Search)。
集束搜索(Beam Search)会考虑每个时间步多个可能的选择。设定一个集束宽(Beam Width),代表了解码器中每个时间步的预选单词数量。例如,则将第一个时间步最可能的三个预选单词及其概率值 保存到计算机内存,以待后续使用。
第二步中,分别将三个预选词作为第二个时间步的输入,得到。
因为我们需要的其实是第一个和第二个单词对(而非只有第二个单词)有着最大概率,因此根据条件概率公式,有:
设词典中有个词,则当时,有个。仍然取其中概率值最大的3个,作为对应第一个词条件下的第二个词的预选词。以此类推,最后输出一个最优的结果,即结果符合公式:
可以看到,当时,集束搜索就变为贪心搜索。
长度标准化(Length Normalization)是对集束搜索算法的优化方式。对于公式,当多个小于1的概率值相乘后,会造成数值下溢(Numerical Underflow),即得到的结果将会是一个电脑不能精确表示的极小浮点数。因此,我们会取log值,并进行标准化:
其中,是翻译结果的单词数量,是一个需要根据实际情况进行调节的超参数。标准化用于减少对输出长的结果的惩罚(因为翻译结果一般没有长度限制)。
关于集束宽的取值,较大的值意味着可能更好的结果和巨大的计算成本;而较小的值代表较小的计算成本和可能表现较差的结果。通常来说,可以取一个10以下的值。
和BFS、DFS等精确的查找算法相比,集束搜索算法运行速度更快,但是不能保证一定找到准确的最大值。
集束搜索是一种启发式搜索算法,其输出结果不总为最优。当结合Seq2Seq模型和集束搜索算法所构建的系统出错(没有输出最佳翻译结果)时,我们通过误差分析来分析错误出现在RNN模型还是集束搜索算法中。
例如,对于下述两个由人工和算法得到的翻译结果:
将翻译中没有太大差别的前三个单词作为解码器前三个时间步的输入,得到第四个时间步的条件概率和,比较其大小并分析:
建立一个如下图所示的表格,记录对每一个错误的分析,有助于判断错误出现在RNN模型还是集束搜索算法中。如果错误出现在集束搜索算法中,可以考虑增大集束宽;否则,需要进一步分析,看是需要正则化、更多数据或是尝试一个不同的网络结构。
Bleu(Bilingual Evaluation Understudy)得分用于评估机器翻译的质量,其思想是机器翻译的结果越接近于人工翻译,则评分越高。
最原始的Bleu将机器翻译结果中每个单词在人工翻译中出现的次数作为分子,机器翻译结果总词数作为分母得到。但是容易出现错误,例如,机器翻译结果单纯为某个在人工翻译结果中出现的单词的重复,则按照上述方法得到的Bleu为1,显然有误。改进的方法是将每个单词在人工翻译结果中出现的次数作为分子,在机器翻译结果中出现的次数作为分母。
上述方法是以单个词为单位进行统计,以单个词为单位的集合称为unigram(一元组)。而以成对的词为单位的集合称为bigram(二元组)。对每个二元组,可以统计其在机器翻译结果()和人工翻译结果()出现的次数,计算Bleu得分。
以此类推,以个单词为单位的集合称为n-gram(多元组),对应的Blue(即翻译精确度)得分计算公式为:
对N个进行几何加权平均得到:
有一个问题是,当机器翻译结果短于人工翻译结果时,比较容易能得到更大的精确度分值,因为输出的大部分词可能都出现在人工翻译结果中。改进的方法是设置一个最佳匹配长度(Best Match Length),如果机器翻译的结果短于该最佳匹配长度,则需要接受简短惩罚(Brevity Penalty,简称BP):
因此,最后得到的Bleu得分为:
Bleu 得分的贡献是提出了一个表现不错的单一实数评估指标,因此加快了整个机器翻译领域以及其他文本生成领域的进程。
相关论文:Papineni et. al., 2002. A method for automatic evaluation of machine translation
对于一大段文字,人工翻译一般每次阅读并翻译一小部分。因为难以记忆,很难每次将一大段文字一口气翻译完。同理,用Seq2Seq模型建立的翻译系统,对于长句子,Bleu得分会随着输入序列长度的增加而降低。
实际上,我们也并不希望神经网络每次去“记忆”很长一段文字,而是想让它像人工翻译一样工作。因此,注意力模型(Attention Model)被提出。目前,其思想已经成为深度学习领域中最有影响力的思想之一。
注意力模型的一个示例网络结构如上图所示。其中,底层是一个双向循环神经网络(BRNN),该网络中每个时间步的激活都包含前向传播和反向传播产生的激活:
顶层是一个“多对多”结构的循环神经网络,第个时间步的输入包含该网络中前一个时间步的激活、输出以及底层的BRNN中多个时间步的激活,其中有(注意分辨和):
其中,参数即代表着对的“注意力”,总有
我们使用Softmax来确保上式成立,因此有:
而对于,我们通过神经网络学习得到。输入为和,如下图所示:
注意力模型的一个缺点是时间复杂度为。
相关论文:
在语音识别任务中,输入是一段以时间为横轴的音频片段,输出是文本。
音频数据的常见预处理步骤是运行音频片段来生成一个声谱图,并将其作为特征。以前的语音识别系统通过语言学家人工设计的音素(Phonemes)来构建,音素指的是一种语言中能区别两个词的最小语音单位。现在的端到端系统中,用深度学习就可以实现输入音频,直接输出文本。
对于训练基于深度学习的语音识别系统,大规模的数据集是必要的。学术研究中通常使用 3000 小时长度的音频数据,而商业应用则需要超过一万小时的数据。
语音识别系统可以用注意力模型来构建,一个简单的图例如下:
用CTC(Connectionist Temporal Classification)损失函数来做语音识别的效果也不错。由于输入是音频数据,使用RNN所建立的系统含有很多个时间步,且输出数量往往小于输入。因此,不是每一个时间步都有对应的输出。CTC允许RNN生成下图红字所示的输出,并将两个空白符(blank)中重复的字符折叠起来,再将空白符去掉,得到最终的输出文本。
触发词检测(Trigger Word Detection)常用于各种智能设备,通过约定的触发词可以语音唤醒设备。
使用RNN来实现触发词检测时,可以将触发词对应的序列的标签设置为“1”,而将其他的标签设置为“0”。
传统的RNN、GRU、LSTM等都是序贯模型(Sequential Model),只有算出前面所有单元的输出数据,才能得出最后单元的输出;并且这些模型在顺序计算的过程中信息会丢失,尽管LSTM等门机制的结构一定程度上缓解了长期依赖的问题,但仍未彻底解决。
Transformer结合了Attention(注意力机制)和CNN,实现了并行计算。它有两个关键部分:
相关论文:Ashish Vaswani et al., 2017. Attention Is All You Need
参考:https://luweikxy.gitbook.io/machine-learning-notes/self-attention-and-transformer
自注意力机制其实和注意力机制区别不大,注意力机制中的公式为,而自注意力机制的公式是:
其中、、是通过词向量乘以三个不同的权值矩阵、、得到的。(这三个权值矩阵是通过训练得到的,一开始可以随机初始化)
上述公式向量化后是(和之前的定义不同,论文中的词向量和、、都是用行向量表示的,然后比如多个竖向排列起来就变成了,且乘法的顺序和之前的不太一样,比如):
其中除以是为了防止梯度爆炸或者梯度消失的参数,通常把设置为或的长度(和的长度是相同的,而的长度和它们不一定相同),是一个经验值。
下面以一个例子来说明这些符号都代表了什么:
对于机器翻译问题,比如:
我们想要得知是什么意思,那么对于这个单词,就有三个值,分别指query、key和value。其中就好比一个问题,比如“那里发生了什么?”,带着这个问题,先去问,得到了一个表示人名的,然后再问,得到了表示动作的,依此类推。由于第二个单词表示动作,是和我们的问题最相关的,因此的值会是最大的,表示这是最有关联的背景。我们对得到的这些值做softmax,然后再让它们乘以各自的(相当于给每个赋权重),最后把它们加起来,就得到了。因此也可以写成。分别对每个单词重复以上的过程,就得到了,它是一个矩阵。
这样做的好处是,并不是一个固定的词向量,而是可以让自注意力机制意识到它是访问的目的地,让一个词可以用其他词表达出来,从而计算出这个单词更丰富、更有用的表达。这样可以避免像Word2Vex那样只基于这个词左右两边一定词距内的内容的局限性。
每计算一次序列中的自注意力,就算一个“头”。每个“头”都有自己的、、,分别代表不同的“问题”,这样对同一个单词,每个“头”的最有关联背景的单词就可以不同。
和自注意机制不同的是,这里要分别乘以,变成,其余的计算方法和自注意力相同,只是用不同的重复了若干次。最后的是每个“头”的矩阵拼接起来得到的一个大矩阵再乘以.
(如果词向量用行向量表示的话,那么就是多个竖向排列而成的矩阵)
还是用机器翻译问题举例:
如图,左边是个编码器(Encoder)放在一起,右边是个解码器(Decoder)放在一起,通常。
左边句子中的每个词经过嵌入矩阵得到词向量后,再加上起始符<SOS>
和结束符<EOS>
,然后输入到一个具有多头注意力层的编码器中,通过词向量和权重矩阵得到,然后把生成的矩阵传入到一个前馈神经网络(Feed
Forward Neural
Network)中,这层神经网络可以帮助决定句子中有哪些有趣的特征。
右边的解码器的作用是输出译文,每个解码器最下面的多头注意力层传入的是目前为止已经翻译好的内容,由于第一个输出一定是句子的起始符<SOS>
,所以一开始就可以传入<SOS>
,然后根据输入的内容就可以算出这个多头注意力层的,然后经过一系列的计算,把生成的矩阵作为下一个多头注意力层的,而编码器的输出则用于生成和,然后再经过一系列的计算,把生成的矩阵传入到一个前馈神经网络中,最后通过一系列的计算,得出译文的下一个单词。
这些虽然图中的字母相同,但是对于不同的多头注意力层,这些矩阵是不同的。
这样做的一个直观的解释是,根据当前已翻译的内容,提出一个问题query,传入解码器中,然后根据提取自原文句子上下文的和,尝试决定序列中要生成的下一个单词。
其中前馈神经网络分为两层,第一层的激活函数是ReLU,第二层是一个线性激活函数,即:
注意:只有最后一个编码器的输出才是传入到解码器中作为和的。(如下图)
这个网络还需要再做改进:
改进一:位置编码
截止目前为止,我们介绍的Transformer模型并没有捕捉顺序序列的能力,也就是说无论句子的结构怎么打乱,Transformer都会得到类似的结果。为了解决这个问题,需要在编码词向量时引入了位置编码(Position Embedding)的特征。
编码的方式可以使用以下公式:
其中是词向量的维度,是单词的数字位置,表示编码的不同维度(即下标,从0开始算,偶数代入第一条公式,奇数要减1,然后代入第二条公式,不同位置的元素根据公式算出不同的值,组成一个位置编码向量)。
比如,假设词向量是一个有4个元素的向量,那么就是4,而我们要创建一个具有相同维度的位置编码向量,比如对于第一个单词”Jane”的位置编码向量,公式中的就是1,,其中.
最后要将位置编码向量和词向量相加,把得到的向量作为新的词向量输入到Transformer网络中。
改进二:残差网络
可以把残差网络的跳远连接加进来,这样就可以通过整个架构传递位置信息。
在目前架构的每一层后面都加一层Add&Norm,把跳远连接连到这一层中。Add&Norm很像之前的Batch Normalization,只不过指定了均值和方差,它也可以加速学习速度。
顾名思义,Add&Norm分为Add和Norm两步,其中Add表示残差,而Norm表示Layer Normalize。各向量先加上残差的值,再进行Layer Normalize。
Layer Normalize就是对每一层的所有向量进行求均值和方差,然后归一化到正态分布,再学习到合适的均值和方差进行再次缩放,即:
改进三:输出层
输出层其实还有一层线性层,然后再经Softmax输出,使每次只预测一个单词。损失函数使用的是交叉熵损失。
改进四:Masked Multi-Head Attention
只有在用一个正确的翻译数据集来训练Transformer网络时,才能使用掩码多头注意力机制(Masked Multi-Head Attention)。
如果有完整的、正确的译文,那么可以每次盖住一个或几个单词,让网络根据句子中的其他信息,去学习这些单词的意思。比如句子“我爱吃饭”,mask后句子变成了“我爱mask饭”,这时
以前需要得到上一个时间步的输出后,才能计算下一个翻译的词的,比如,而现在可以学习任意位置的词。