卷积神经网络
Convolutional Neural Networks—CNN,其与常规神经网络的构想基本一致,不同的是需要训练卷积层,因为其更能保留输入的空间结构。
前面我们提到了全连接层的概念,将一个32×32×3的照片像素提取并展开,得到一个3072维的向量,将其与10×3072的权值矩阵相乘得到激活值。
至于卷积层和全连接层的主要区别在于卷积层可以保全空间结构,比如上面中的图片不再将它展开成长向量,保持图片的结构,我们的权重W转为一些小的卷积核(也叫权重滤波器),将其在整个图像上滑动计算每一个空间定位时的点积结果,也就是将卷积核每个位置元素和与之对应图像区域的像素值相乘再加上偏置项。在做点积运算时我们会将这个5×5×3的块及对应的输入数据快转开成一个向量,将对应的每个空间位置元素进行相乘然后相加。
我们如何滑动卷积核并遍历所有空间位置呢,将这个卷积核从左上方的边角处开始,并且让其遍历输入的所有像素点,每一次点积运算都会在我们输出激活映射中产生一个值之后继续滑动卷积核,最简单的方式是一个个像素地滑动并相应的填满我们的输出激活映射或者其它滑动方式,比如每次滑动俩个像素点这样得到的是不同尺寸大小的输出。
当我们在处理一个卷积层时往往希望用到多种卷积核,因为每一种卷积核都可以从输入中得到一种特殊的模式或者概念,如果说我们有六个卷积核每个尺寸都是5×5×3,这样我们就会得到一个6层的尺寸大小是28×28×6(每一个的尺寸是28×28×1)的激活映射。
卷积神经网络基本上是由多个卷积层组成的一个序列,一个图片在输入之后输出最终结果之前的这些中间结果就是卷积层,它们依次堆叠,就像之前在神经网络中那样堆叠简单的线性层一样,之后我们将会用激活函数对其逐一处理,得到一些ReLU、Conv和池化层等东西,之后得到一系列的这些层,每一个都有一个输出,该输出又作为下一个卷积层的输入。
下面图中的28×28×6的卷积层是由6个卷积核得到的6个28×28×1的激活映射组成的。
这些层采用多个卷积核,每一个卷积核会产生一个激活映射,最后的结果是你完成了对这组卷积核的学习,前面的卷积核一般代表了一些低阶的图像特征比如边缘特征,而对于中间层你可以得到一些更加复杂的图像特征比如边角和斑点等,对于那些高级特征你可以获得一些比斑点更加丰富的内容。
卷积神经网络总体上来看其实就是一个输入图片通过很多层,第一个是卷积层然后通常是非线性层比如ReLU,然后有了Conv ReLU层然后用到池化层,这些后面都会介绍。这些措施大大降低了激活映射的采样尺寸,经过这些处理最终吧卷积网络最后一层输出,然后就可以用之前见过的全连接层连接所有的卷积输出并用其获得一个最终的分值函数,即一个固定尺寸的矩阵知道其长宽高并拉平,得到一个与朴素神经网络向量的一维向量,把所有内容汇聚到一起根据这些信息来获得一些结论。
为了保持输入大小相同,我们可以在图像外圈填补上0保持全尺寸输出。 之前提到过如果图像有多个层,如果不做零填充或者其他形式的填充,输出图像的尺寸会随着层数的增加迅速减少,这样会损失一些信息,只能用很少的值表示你的原始图像,每次图像变小,关于图像边角的信息也会丢掉更多。
下面来看个例子,当输入32×32×3的图片且填充宽度为2步长为1,使用10个5×5的卷积核,输出图像的大小是多少? —>323210
这一层的参数有多少个?—>因为有10个5×5的卷积核且隐含了3的深度且在实际运用中还有偏差项,所以答案是10×(5×5×3+1)=760
以下是一些卷积层中的参数总结
前面提到了池化层的概念,池化层要做的是让所生成的表示更小且更容易控制,也就是降采样,这是为了最后有更少的参数,且不会在深度方向上做池化处理而只做平面上的。
最常见的方法是最大池化法,在下面的粒子中池化层有一个卷积核的大小,如果使用2×2的滤波器(卷积核或者池化器)并且设定步长为2,让这个滤波器划过整个输入部分只取最大值。池化层的典型设置是2×2的卷积核和2的步长,或者3×3加3。为什么最常用最大值池化而不是均值之类的?
因为最大值池化的意义在于我有一些神经元的激活值在这,每个值都在一定程度上表示了在这个位置某个神经元的激发程度或者是某个卷积核的激发程度, 你可以把最大值池化看成这个卷积核在图像任意区域的受激程度的表示,所以如果要做检测识别之类的任务最大值池化是更直观的,用一个最大值来激活它。
还要注意一般不在池化层做填零,因为池化层只做降采样,这样就不会导致卷积核在扫过边缘部分时有一部分超出了输入的范围。
训练神经网络-激活函数、数据预处理、权重初始化、批量归一化、训练过程监控、超参数优化
激活函数
前面有介绍,任意特定层是如何产生输出的,我们输入数据,在全连接层或者卷积层我们将输入乘上权重值,然后将结果输入一个激活函数或者非线性单元,这里介绍不同的非线性函数并在他们之间权衡。
首先是Sigmoid函数,它将输入转化至0到1,历史上很受欢迎,因为它们可以很好地解释为神经元的饱和“放电速率”。
但是其有三个缺点:1、输入的正数太大或负数太小容易导致饱和的神经元造成梯度消失的问题,经过链式法则后会让梯度流消失,零梯度会传递到下游的节点;2、其是一个非零中心函数。思考计算图中的节点,当输入参数会全部传递到激活函数运算并输出结果,那么求W的梯度的时候,当xi的值全为正数时,局部梯度实际上就是x本身乘以激活函数的导数,Sigmoid函数的导数肯定是正的,而xi全为正数,所以局地梯度全是正值,而W的梯度是局地梯度乘以上游梯度,所以W的梯度只是传递了上游梯度的符号,所以W的梯度要么全正要么全负,这种方法对梯度更新来说是非常低效的,原因可见下图;
3、使用了指数函数,计算代价略高,但这通常不是主要问题。
然后是tanh函数,解决了非零中心函数的问题,但是梯度消失的问题依然存在。
ReLU,在之前的例子中用过,函数很简单fx=max(0,x),其是我们经常使用的函数,其在正数区域不会存在梯度消失的问题,计算成本也不高,计算速度比前俩种大约快6倍,且其更符合生物学。
但它的问题是不再以0为中心,且负半轴还是有梯度消失的问题称为dead ReLU,如果有一个不好的初始化或者学习率太高时容易出现这种情况,实际上网络中10%-20%是挂掉的ReLU单元,实际中人们也喜欢用较小的正偏置来初始化ReLU以增加初始化时其被激活的可能并获得一些更新,但这基本上是让更多的ReLUs在一开始就能放电的偏置项,很多人也认为这也没什么用。
另一种是Leaky ReLU,是ReLU的优化版,唯一区别是在负区间给出一个微小的斜率,这解决了之前提到的问题,且计算仍然高效。
这里还有另一个例子,参数整流器简称PReLU,其与Leaky ReLU很相似,负区间的斜率通过alpha参数确定,将其作为一个可以反向传播和学习的参数,具有更高灵活性。
还有一种指数线性单元,简称ELU,其同样具有ReLU的所有优点,且输出均值接近0这也是个优势(PReLU也可以达到这个效果),但与Leaky ReLU相比ELU没有在负区间倾斜,实际上在建立一个负饱和机制,有些人认为这样使得模型对噪音具有更强的鲁棒性。
还有一种最大输出神经元,作用是泛化ReLU和Leaky ReLU,不会饱和也不会消亡,问题在于把每个神经元的参数翻倍了。
总结以上,最好的经验法则是使用ReLU,Leaky ReLU、Maxout和ELU等实用性还是差一些。
数据预处理
一般我们总想要预处理输入数据,一般的类型是零均值化(中心化)、通过标准差归一化,这样做的意义在于如下面的例子所示,我们要通过二元分类的方法分离这些点,左图中这些点没有被归一化、中心化而离原点很远,虽然仍然可以用一条直线分离它们,但是直线的稍微转动都会破坏分类器,意味着左边的损失函数对权重矩阵中的线性分类器中的小扰动非常敏感,虽可以表示相同的函数但是却会让深度学习变得异常艰难,因为它们的损失对我们的参数向量非常敏感。而右边的情况如果你使用数据集时将数据点移到原点附近,缩小它们的单位方差我们仍可以很好的进行分类,但当我们转动直线,损失函数对参数值中的小扰动就不那么敏感了,这会让优化变得更容易更深入。
TIPS:这种情况不仅仅在线性分类中,因为在神经网络中需要交叉使用线性矩阵相乘或者卷积还有非线性激活函数,如果神经网络中某一层的输入均值不为0或者方差不为1那么该层网络权值矩阵的微小浮动机会造成该层输出的巨大浮动从而导致学习困难。因为我们了解归一化的重要性所以引入下面的批量归一化的概念,即在神经网络中加入额外一层以使得中间的激活值均值为0方差为1。
在深度学习中零均值化是为了防止上面说的非常低效的梯度更新情况出现,在本节课中不会对图像进行过多的归一化像素值,因为一般对图像来说在每个位置已经得到了相对可比较的范围与分布。在机器学习中还有更复杂的PCA、白化等,在图像应用领域用的很少。
一般来说在训练阶段,我们会决定我们的均值图像将其应用到测试数据中去,一般对于图像我们就是做零均值化的预处理,方法是用图片减去整张均值图像的值,其尺寸和每张图相同。对于你要传入网络的每张图减去均值图像这组数的值就达到了零均值化的预处理。在测试组我们也会做一样的事情。
TIPS:零均值化只在第一层解决了Sigmoid函数的非零中心问题,在后面还是会有一样的问题。
权重初始化
对于越深的网络,权重初始化越重要,如何初始化网络权值然后用梯度来更新它们,当我们用0来初始化W会发生什么?
因为整个神经网络有很多神经元用一样的方式连接在一起,所以它们会有相同的更新做一样的事情,这将会是个问题。
所以可以改变为所有权值初始化为一个小随机数比如w=0.01*np.random.randn(D,H),这样的参数在小型网络中适用,打破了参数对称问题,但是在结构深一点的网络中可能存在问题,可能导致网络崩溃(权值和梯度都接近0,学习也就无从谈起),如果权重设置的太大可能导致网络饱和(数值过大或过小,梯度消失或者导致保值性增长而无法学习)。
一个很好的初始化经验是Xavier初始化(Glorot于2010年发表的论文),具体可以看这个论文的解释或者笔记,基本思想是要求输入的方差等于输出的方差。还有一种MSRA初始化法也比较好。
实际上,当前的建议是使用relu单位并使用w = np.random.randn(n) * sqrt(2.0/n)
批量归一化
通过在训练开始时明确地强制整个网络的激活呈现单位高斯分布(想要单位高斯激活的目的是想让归一化的同时还保留到学到的特征),从而通过正确初始化神经网络,缓解了许多令人头痛的问题。
核心观察结果说明这是可能的,因为归一化是一个简单的可微操作。实际应用此技术通常相当于在完全连接的层(或卷积层)之后,非线性之前插入BatchNorm层。我们在这里不扩展这种技术,因为它在链接文件中已经被很好地描述了,但是请注意,在神经网络中使用批量归一化已经成为一种非常普遍的实践。在实践中,使用批量标准化的网络对于不良初始化的鲁棒性明显更强。此外,批量标准化可以解释为在网络的每一层进行预处理,但以可微的方式集成到网络本身。太棒了!
批量归一化的思想是我们提供输入然后计算小批量的均值,对每个输入的小批量都做这个操作,然后计算方差,通过均值和方差对输入数据进行归一化,然后还有额外的缩放和平移因子从而改进了整个网络的梯度流,它还具有更高的鲁棒性能够在更广泛的学习率和不同初始值下工作。另外你也可以把它看作一种正则化的方法,因为是小批量训练出来的,不会对给定的训练样本给特定的值,就给每层的X中加入一些抖动。
最后注意,在测试阶段批归一化,在测试阶段不用重新计算。
训练过程监控
现在讨论如何监视训练,并在训练过程中如何调整这些超参数。一直以来的第一步是数据预处理,将数据进行零均值化处理。
然后选择网络结构,例如这里是个简单的具有50个神经元的隐含层的神经网络,基本上你可以选择任何想要的网络结构。
然后初始化我们的网络,网络进行前向传播,由确定的损失函数确定第一轮损失函数是合理的,比如我们在前面几节课讲的多项逻辑回归损失函数,当我们的W很小且分散,当我们有十个类别时它将会是一个1/10的负对数,大概是2.3左右,这是一个非常好的完整性检查。
接下来我们想做的是加入0正则化项,如果不加入则损失值就是数据的损失值,在2.3左右,启动正则化可以看到我们的损失值上升到了3.07左右。
...
loss,grad=two_layer_net(X_train,y_train,1e3)
print loss
.036859716482
完成了这些,我们开始训练,最好先从小数据集开始,因为能够把他们拟合的非常好来获得非常小的损失,这里我们关闭正则化为了观察能否把训练损失降为0。
我们看到损失正在降低,当我们训练那么多epoch的时候(一个epoch就是将所有训练样本训练一次的过程,表示所有的数据送入网络中,完成了一次前向计算 + 反向传播的过程),我们在每一个epoch计算我们的损失,我们希望损失降为0,另一方面也看到训练集的准确率上升为1,所以如果你有一个小数据集,你应该能够完美地拟合这些数据。
tips:lr是学习率 cost是损失 train、val是训练集和验证集的准确率
如果你完成了全部完整性检查的操作, 你就可以开始真正的训练了,可以拿出你所有的训练数据加一个小的正则化项。让我们确定一下什么才是最优的学习率,其是最重要的参数之一且是你首先需要调整的参数之一,你可以试一些学习率的值,比如这里我们用1e-6。
可以看到损失基本不变,原因是学习率太低了导致梯度和cost更新太小,但是这里即使损失基本不变但训练集和验证机的准确率却迅速来到20%,原因是虽然这里的分布依然分散,因此损失项很接近,但是我们把这些所有的分布都朝着正确的方向再轻微的移动,我们的权重参数在朝着正确的方向改变,现在准确率可能发生突变虽然损失还是相当分散。
当我们尝试令一个极端的学习率1e6,结果是代价值cost是NaNs;正常范围的学习率是1e-3到1e-5,你可以在这个范围里尝试不同的学习率,其取决于你的损失变化,太小或太大,在这个原则上调整它。
超参数优化
我们优化超参的策略是,对任何超参比如学习率执行交叉验证,其是在训练集上训练,然后在验证集验证,观察这些超参的实验效果。首先我们选择比较分散的数值,然后用几个epoch的迭代去学习,经过几个epochs你可以知道哪些超参有效,哪些值好或不好以及是一个NaN或者什么也没有,通常就可以发现一个较好的参数区间,以此进一步搜索更精确的值,这便是第二步,或许会花费比较长的时间。学习率、更新类型、正则化、网络结构、隐藏层数量及深度都可以优化。
学习率是最重要的一个因素,通过损失曲线你可以直观感受哪些学习率是好的哪些是不好的,如果有个很高的激增线(黄色)就是损失爆炸表面学习率过高,如果过于线性和平缓(蓝色)表明学习率过低,如果有一个突变且过后是一个平滑区(绿色)说明也可能是学习率过高,因为步长过大可能不能落入局部最优。一个好的学习率(红色)是一个相对陡峭的曲线然后又连续下降。
PS:如果你的学习率损失曲线在开始的一定时间内很平滑然后突然开始训练并下降,这可能是初试值没有设定好。
tips:如果可视化精度发现训练精度和验证精度间有很大的差值说明训练过拟合了,可以试着增加正则化权重,如果没有过拟合就可以增加模型容量也会提高精度。
举个优化的例子如下图所示,用5个peochs来进行过程搜索,我们去观察这些得到的验证准确度,红框标注了效果较好的结果,这些区间是准备进一步细化的区域。
PS:在训练循环中有一个找到像NaN这样激增的技巧,在每一个迭代或epoch观察你的代价cost,如果出现一个远远大于初始代价的值比如说超过了3倍,就可以知道这不是一个正确的方向,它会迅速变大所以跳出循环停止这个参数的训练。
PS:注意采用对数来优化效果更好,与在1e-0.001到1e100之间均匀采样相比,比如在学习率中,用一些值的乘或除的值更加合理。
另外对于一种网格搜索的方式来说,对一个超参的一组固定值进行采样采用随机采样更好,比如下面有俩个超参需要采样而随机采样更好,看一下图像上方画出的绿色函数标记了表现较好的位置,如果用网格分布就就只能采样到3个值而错过了很好的局域。
最后总体来说,我们可以用已有参数的范数来记录更新值,权重更新和权重幅度的比率从而知道它们有多大什么时候更新尺寸,也可以采用范数描述它的大小,可以让比率在0.001附近,不用非得到具体的某个值,但需要知道哪个值和你要的值相比过大或过小就不用训练了,这样就避免无用功。