| 深度神经网络,特别是卷积神经网络(CNN),使得由许多处理层构成的计算模型能够完成对数据的多层抽象表示。它们已经非常显著地提高了计算机视觉诸多领域的现有水平,如物体识别,物体检测,文本识别等。并且还包括许多其他领域,如药物发现和基因研究。 此外,在计算机视觉领域,目前已有许多优秀的论文发表,而且诞生了不少高质量的CNN开源软件和学习框架。同时也出现了许多不错的CNN教程和手册。然而,关于如何从零开始实现一个出色的深度卷积神经网络,目前可能还是缺少一个较新的、较全面的总结。因此,我们收集并总结了许多DCNNs的实现细节。这里我们将介绍大量关于如何构建和训练神经网络的实现细节,也可以说是_技巧或秘诀_。 —|—
我们假设你已经掌握了基本的深度学习知识,并且我们这里介绍的实现细节(技巧)是关于深度神经网络的,尤指用于图像相关任务的卷积神经网络(CNN),主要包括以下8个部分:1)数据扩增;2)数据预处理;3)网络初始化;4)训练过程中的一些技巧和注意事项;5)激活函数的选择;6)多种正则化方法;7)深刻理解训练图;8)组合多个网络。 另外,与本文对应的PPT在这里[slide]。如果关于这篇文章和PPT你有任何的疑问,或者发现了错误,或者还有其他重要的、有意思的东西你觉得应该加上,可以随时联系我。
因为深度网络需要使用大量的图片数据进行训练以达到满意的效果,如果原始的训练数据集数量较少,则应该对这些数据做一些扩增(Augmentation)来提高训练效果。其实,数据扩充已经成为了训练深度网络一个必不可少的步骤。
),叠加上:
,其中
和
分别是第i个特征向量和RGB像素值3x3协方差矩阵的特征值,
是服从高斯分布(均值为0,标准差为0.1)的随机数。每个
对于一张训练图像的所有像素点只初始化一次,直到这张图像再次用于训练。也就是说,当训练模型再次遇到同一张图像时,它会随机产生一个新的
用于数据扩增。在[1]中指出,fancy PCA可以近似地提取出图像中的重要信息,也就是说对色彩饱和度变化以及光照具有不变性。在分类性能上,这种方法在ImageNet 2012比赛中将top1错误降低了1%。
现在我们手上已经有了大量的训练数据了,不过可千万别着急!我们需要对这些数据做一下预处理。在这个章节我们将介绍几种预处理的方法。
第一种简单的处理方法就是对数据进行零中心化(zero-center),然后进行归一化(normalize)。使用Python代码表示如下:
>>> X -= np.mean(X, axis = 0) # zero-center
>>> X /= np.std(X, axis = 0) # normalize
其中X是输入数据。另一种方式是对每一个维度的数据进行归一化,使得最小值和最大值分别为- 1和1。如果不同的输入特征具有不同的尺度,但它们对学习算法的影响效果大致相等,这样进行数据预处理才有意义。对于图像而言,像素的相对尺度是差不多相同的(在0~255之间),所以预处理操作也不是十分必须的。
另外一种方法跟第一种方法差不多,叫做PCA白化(PCA Whitening)。首先同样是对数据进行零中心化,然后计算协方差,它展示了数据中的相关性结构:
>>> X -= np.mean(X, axis = 0) # zero-center
>>> cov = np.dot(X.T, X) / X.shape[0] # compute the covariance matrix
之后,把原始数据(已经零中心化)投射(projecting)到特征向量上进行去相关操作:
>>> U,S,V = np.linalg.svd(cov) # compute the SVD factorization of the data covariance matrix
>>> Xrot = np.dot(X, U) # decorrelate the data
最后一步就是白化操作,将上一个步骤得到的结果除以特征值:
>>> Xwhite = Xrot / np.sqrt(S + 1e-5) # divide by the eigenvalues (which are square roots of the singular values)
这里加上1e-5(一个很小的常量)是为了防止除0。这一步变换有个缺点是它会极度放大数据中的噪声,因为输入数据的所有维度(包括方差很小的不相关维度,绝大部分是噪声)变成了同等大小。可以通过增大平滑参数来减小噪声影响 (比如把1e-5再增大一点)。
值得注意的是,这里提到预处理操作是为了介绍的完整性。在卷积神经网络中,一般不采用预处理操作。但是零中心化和归一化还是十分必要的。
现在数据已经准备好了。但是在开始训练网络之前,必须对网络的参数进行初始化。
全零初始化
对数据进行适当的归一化后,理想的情况是希望权值(weights)初始化约为一半正数一半负数。一个听起来蛮合理的方案就是将权值全部初始化为0,因为从期望上来讲,0是最佳猜测。但事实上证明这样做是错误的,因为如果每个神经元具有同样的输出,那么在反向传播 (back- propagation)时就会计算得到同样的梯度值,参数的更新也就完全一样。换句话说,若权值初始化时都为同样的值,那么网络的各个神经元之间就缺少了不对称性。
随机初始化为较小的值
权值初始化为接近于0的值,但不完全等于0。每个权值都随机初始化为非常接近于0的值,这样就打破了网络的对称性(symmetry breaking)。在训练的开始阶段,所有神经元都是随机且唯一的,然后它们将进行不同的更新,成为整个网络不同的一部分。这种初始化方式很简单:
,其中
是均值为0,标准差为1的高斯分布。也可以使用服从均匀分布的较小值,但实际训练中发现其对最终训练结果的影响要小一些。
校正方差
上述随机初始化权值的方法存在一个问题,随着输入数据的增加,输出的方差也会增加。可以用随机初始化权值除以输入数据大小的开方,使得每个神经元输出的方差变为1,如下所示:
>>> w = np.random.randn(n) / sqrt(n) # calibrating the variances with 1/sqrt(n)
其中“randn”是产生高斯分布的函数,“n”表示输入数据的大小。这样使得网络中所有神经元在训练开始的时候具有大致相同的输出分布,实验也证明这会提高收敛速度。详细的推导过程可见PPT的18~23页,要说明的是,这个推导并没有考虑ReLU(修正线性单元,见第5章节)的影响。
推荐方法
上述校正方差的方法并未考虑ReLU的影响。最近He等人[4]提出了一种专为ReLU设计的初始化权值方法,得出的结论是:网络中神经元的方差应为
。
>>> w = np.random.randn(n) * sqrt(2.0/n) # current recommendation
如[4]中的讨论,这也是当前推荐在实际中使用的方法。
现在所有的准备工作都做好了,让我们开始训练网络吧!
| 在自己的数据集上调优(fine-tune)预训练模型。不同情况下采用不同的调优策略。对于数据集,_Caltech- 101_与_ImageNet_比较相似,都是关于物体的数据集;而_Place Database_就与_ImageNet_不同,它是关于场景的数据集。 —|—
激活函数(activation function)是深度网络几个重要特点之一,它使得网络具有非线性表达能力。这里将介绍几种常用的激活函数,然后提出一些使用建议。
| 图片来自Stanford CS231n —|—
Sigmoid
| sigmoid非线性函数:
它将实数“压扁”在0~1之间,正无穷趋向于1,负无穷趋向于0。sigmoid函数过去经常被使用,因为它很好地模拟了神经元的激活状态:从根本不被激活(0)到完全被激活(1)。 —|—
在实际使用中,已经很少采用sigmoid函数,因为它有两个主要的缺点:
中每个
),那么权值
的梯度在反向传播的过程中,要么全部为正数,要么全部为负数(具体依整个表达式
的梯度而定)。这样在权值梯度更新的时候就会出现“Z字形”锯齿。然而,当一个批大小(batch)里面数据的梯度叠加起来以后,一定程度上会缓解这个问题,因为一个批大小里面的梯度值有正有负。因此,这个缺点相比于上面的饱和梯度问题来说,只是个小麻烦,没有那么严重。
tanh(x)
| tanh非线性函数将实数“压扁”在[-1,1]之间,和sigmoid函数一样,它也存在饱和梯度的问题,但不同的是它的输出是零中心化的。因此,在实际使用中,tanh非线性函数比sigmoid非线性函数要用得多一些。 —|—
Rectified Linear Unit
| 修正线性单元(Rectified Linear Unit,ReLU)近年来非常受欢迎。其函数公式:
—|—
ReLU有以下优缺点: