【深度学习-CS231n】线性分类器和神经网络

文章目录

  • 神经网络静态部分
    • 基本概念和小细节
    • 算法设计选项
  • 神经网络动态部分
    • 学习过程
      • [梯度检查](https://zhuanlan.zhihu.com/p/21741716?refer=intelligentunit)
      • 学习之前:合理性检查的提示与技巧
      • 检查整个学习过程
        • 1. 损失函数
        • 2. 训练集和验证集准确率
        • 3. 权重更新比例
        • 4. 每层的激活数据及梯度分布
      • 参数更新
      • 学习率退火
      • 逐参数适应学习率方法
      • 超参数调优
      • 模型集成
      • 总结

神经网络静态部分

如何创建网络的连接、数据和损失函数

基本概念和小细节

1. 学习到的权重 W W W相当于图像模板【深度学习-CS231n】线性分类器和神经网络_第1张图片
2. 评分函数:原始图像数据到类别分值的映射
损失函数:量化预测值和真实值的差距,越小越好。利用梯度最优化,求出损失函数值最小时的参数(权重W)
损失函数包含两个部分:数据损失和正则化损失
数据损失:数据损失是对所有样本的数据损失求平均
在这里插入图片描述
3. 正则化
目的:向某些特定的权重 W W W添加一些偏好,对其他权重不添加,以此来消除模糊性(即避免不同的权重均使损失函数最小,求出了多个W图像模板);
note:效果是让所有权重w在参数更新过程中逐渐向着0变化,变得小而均匀化
实现方式:是向损失函数增加一个正则化惩罚(regularization penalty)R(W)部分。最常用的正则化惩罚是L2范式,L2范式通过对W所有参数进行逐元素的平方惩罚来抑制大数值的权重(遇到大数值,求得的损失函数变大了)
4. 最优化
目的:使损失函数最小,并记录下此时的权重,即为训练结果
损失函数可视化:直观感受损失函数的值
5. 小批量数据梯度下降简化最优化问题
训练集中的数据都是相关的,小批量数据的梯度就是对整个数据集梯度的一个近似,一个典型的小批量包含256个例子
6. 反向传播
利用链式求导法则,简化求梯度计算,也是为了简化最优化问题
对前向传播变量进行缓存:在计算反向传播时,前向传播过程中得到的一些中间变量非常有用。在实际操作中,最好代码实现对于这些中间变量的缓存,这样在反向传播的时候也能用上它们。如果这样做过于困难,也可以(但是浪费计算资源)重新计算它们。
【深度学习-CS231n】线性分类器和神经网络_第2张图片
加法门单元:把输出的梯度相等地分发给它所有的输入;
取最大值门单元:对梯度做路由;
乘法门单元:相对不容易解释。它的局部梯度就是输入值,但是是相互交换之后的,然后根据链式法则乘以输出值的梯度。
note:注意一种比较特殊的情况,如果乘法门单元的其中一个输入非常小,而另一个输入非常大,那么乘法门的操作将会不是那么直观:它将会把大的梯度分配给小的输入,把小的梯度分配给大的输入;
输入数据的大小对于权重梯度的大小有影响,这就是为什么数据预处理关系重大,它即使只是有微小变化,也会产生巨大影响!!!
7. 神经网络算法和线性分类算法不同(非线性函数即激活函数的作用)
神经网络算法: s = W 2 m a x ( 0 , W 1 x ) s=W_2max(0,W_1x)\quad s=W2max(0,W1x),定义了评分函数的新形式
其中 W 1 W_1 W1的含义是这样的:举个例子来说,它可以是一个[100x3072]的矩阵,其作用是将图像转化为一个100维的过渡向量。函数max(0,-)即ReLu函数是非线性的,它会作用到每个元素。最终,矩阵 W 2 W_2 W2的尺寸是[10x100],因此将得到10个数字,这10个数字可以解释为是分类的评分。
注意非线性函数在计算上是至关重要的,如果略去这一步,那么两个矩阵将会合二为一,对于分类的评分计算将重新变成关于输入的线性函数。这个非线性函数就是改变的关键点(与线性分类不同的点)。
参数 W 1 , W 2 W_1,W_2 W1,W2将通过随机梯度下降来学习到,他们的梯度在反向传播过程中,通过链式法则来求导计算得出。
8. 激活函数(ReLu函数)
每个神经元都对它的输入和权重进行点积,然后加上偏差,最后使用非线性函数(或称为激活函数)
常用激活函数:
ReLu:ReLu神经元会出现死亡,再也不能被激活,尤其当学习率设置过大时,40%都会死亡
Leaky ReLU:解决ReLu的死亡问题;ReLU中当x<0时,函数值为0。而Leaky ReLU则是给出一个很小的负数梯度值,比如0.01
9. 神经网络结构
层数:不包含输入层;
全连接层:后一层神经元与前一层所有神经元皆有连接;
输出层:不需要激活函数,因为表示分类评分,没必要加;
尺寸标准:神经元个数和参数个数;
10. 防止神经网络过拟合
L2正则化、dropout、输入噪音;
不要使用减少神经元数目来解决正则化,相反应该增大网络和加大正则化强度:小网络更难使用梯度下降等局部方法来进行训练,虽然小型网络的损失函数的局部极小值更少,也比较容易收敛到这些局部极小值,但是这些最小值一般都很差,损失值很高,一不小心就略过了。。。
note:正则化强度是控制神经网络过拟合的好方法,增大λ会减小过拟合层度【深度学习-CS231n】线性分类器和神经网络_第3张图片

算法设计选项

包括数据预处理,权重初始化和损失函数

  1. 数据预处理
    1. 均值减法(零中心化):最常用。它对数据中每个独立特征减去平均值,从几何上可以理解为在每个维度上都将数据云的中心都迁移到原点。对于图像可以用X -= np.mean(X)实现,也可以在3个颜色通道上分别操作。最常用均值减法,它对数据中每个独立特征减去平均值,从几何上可以理解为在每个维度上都将数据云的中心都迁移到原点。对于图像可以用X -= np.mean(X)实现,也可以在3个颜色通道上分别操作。
    2. 归一化(Normalization):第一种是先对数据做零中心化(zero-centered)处理即均值减法,然后每个维度都除以其标准差,实现代码为X /= np.std(X, axis=0)。第二种方法是对每个维度都做归一化,使得每个维度的最大和最小值是1和-1。这个预处理操作只有在确信不同的输入特征有不同的数值范围(或计量单位)时才有意义
    3. PCA:先对数据进行零中心化处理,然后计算协方差矩阵,它展示了数据中的相关性结构。
      # 假设输入数据矩阵X的尺寸为[N x D]
      X -= np.mean(X, axis = 0) # 对数据进行零中心化(重要)
      cov = np.dot(X.T, X) / X.shape[0] # 得到数据的协方差矩阵
      
      数据协方差矩阵的第(i, j)个元素是数据第i个和第j个维度的协方差。具体来说,该矩阵的对角线上的元素是方差。还有,协方差矩阵是对称和半正定的。我们可以对数据协方差矩阵进行SVD(奇异值分解)运算:U,S,V = np.linalg.svd(cov)
      U的列是特征向量,S是装有奇异值的1维数组(因为cov是对称且半正定的,所以S中元素是特征值的平方)。为了去除数据相关性,将已经零中心化处理过的原始数据投影到特征基准上Xrot = np.dot(X,U) # 对数据去相关性
      注意U的列是标准正交向量的集合(范式为1,列之间标准正交),所以可以把它们看做标准正交基向量。因此,投影对应x中的数据的一个旋转,旋转产生的结果就是新的特征向量。如果计算Xrot的协方差矩阵,将会看到它是对角对称的。np.linalg.svd的一个良好性质是在它的返回值U中,特征向量是按照特征值的大小排列的。我们可以利用这个性质来对数据降维,只要使用前面的小部分特征向量,丢弃掉那些包含的数据没有方差的维度。 这个操作也被称为主成分分析( Principal Component Analysis 简称PCA)降维:
      Xrot_reduced = np.dot(X, U[:,:100]) # Xrot_reduced 变成 [N x 100]
      经过上面的操作,将原始的数据集的大小由[N x D]降到了[N x 100],留下了数据中包含最大方差的100个维度。通常使用PCA降维过的数据训练线性分类器和神经网络会达到非常好的性能效果,同时还能节省时间和存储器空间。
    4. 白化:
      白化操作的输入是特征基准上的数据,然后对每个维度除以其特征值来对数值范围进行归一化。该变换的几何解释是:如果数据服从多变量的高斯分布,那么经过白化后,数据的分布将会是一个均值为零,且协方差相等的矩阵。该操作的代码如下:
      # 对数据进行白化操作:
      # 除以特征值 
      Xwhite = Xrot / np.sqrt(S + 1e-5)
      
    警告:夸大的噪声。注意分母中添加了1e-5(或一个更小的常量)来防止分母为0。该变换的一个缺陷是在变换的过程中可能会夸大数据中的噪声,这是因为它将所有维度都拉伸到相同的数值范围,这些维度中也包含了那些只有极少差异性(方差小)而大多是噪声的维度。在实际操作中,这个问题可以用更强的平滑来解决(例如:采用比1e-5更大的值)。【深度学习-CS231n】线性分类器和神经网络_第4张图片
    note:
    在卷积神经网络中,一般不PCA和白化,只进行零中心化,有时会归一化;
    只能在训练集上进行数据预处理,先将数据分为训练集、验证集、测试集;
  2. 权重初始化
    一般:w = np.random.randn(n) / sqrt(n),其中randn函数是基于零均值和标准差的一个高斯分布;
    专门针对ReLu:w = np.random.randn(n) * sqrt(2.0/n)。这个形式是神经网络算法使用ReLU神经元时的当前最佳推荐。
    偏置(biases)的初始化:全为0或者0.01;
    实践:当前的推荐是使用ReLU激活函数,并且使用w = np.random.randn(n) * sqrt(2.0/n)来进行权重初始化
    批量归一化(Batch Normalization):
    让激活数据在训练开始前通过一个网络,网络处理数据使其服从标准高斯分布;
    在实现层面,应用这个技巧通常意味着全连接层(或者是卷积层,后续会讲)与激活函数之间添加一个BatchNorm层,批量归一化可以理解为在网络的每一层之前都做预处理,只是这种操作以另一种方式与网络集成在了一起。搞定!
  3. 正则化 Regularization(防止过拟合)
    L2正则化;
    随机失活:在训练的时候,是让神经元以超参数p的概率被激活或者被设置为0,与L2正则化互补;

神经网络动态部分

神经网络学习参数和搜索最优超参数的过程

学习过程

梯度检查

理论上将进行梯度检查很简单,就是简单地把解析梯度和数值计算梯度进行比较。然而从实际操作层面上来说,这个过程更加复杂且容易出错。下面是一些提示、技巧和需要仔细注意的事情:

  1. 使用中心化公式
  2. 使用相对误差来比较
  3. 使用双精度。
  4. 保持在浮点数的有效范围
  5. 目标函数的不可导点(kinks)
  6. 使用少量数据点。谨慎设置步长h。
  7. 在操作的特性模式中梯度检查。
  8. 不要让正则化吞没数据。
  9. 记得关闭随机失活(dropout)和数据扩张(augmentation)。
  10. 检查少量的维度。
  11. 在操作的特性模式中梯度检查。

学习之前:合理性检查的提示与技巧

在进行费时费力的最优化之前,最好进行一些合理性检查:

  1. 寻找特定情况的正确损失值。在使用小参数进行初始化时,确保得到的损失值与期望一致。最好先单独检查数据损失(让正则化强度为0)。例如,对于一个跑CIFAR-10的Softmax分类器,一般期望它的初始损失值是2.302,这是因为初始时预计每个类别的概率是0.1(因为有10个类别),然后Softmax损失值正确分类的负对数概率:-ln(0.1)=2.302。对于Weston Watkins SVM,假设所有的边界都被越过(因为所有的分值都近似为零),所以损失值是9(因为对于每个错误分类,边界值是1)。如果没看到这些损失值,那么初始化中就可能有问题。
  2. 第二个合理性检查:提高正则化强度时要导致损失值变大,否则可能有误
  3. 对小数据子集过拟合:最后也是最重要的一步,在整个数据集进行训练之前,尝试在一个很小的数据集上进行训练(比如20个数据),然后确保能到达0的损失值(过拟合)。进行这个实验的时候,最好让正则化强度为0,不然它会阻止得到0的损失。除非能通过这一个正常性检查,不然进行整个数据集训练是没有意义的。但是注意,能对小数据集进行过拟合并不代表万事大吉,依然有可能存在不正确的实现。比如,因为某些错误,数据点的特征是随机的,这样算法也可能对小数据进行过拟合,但是在整个数据集上跑算法的时候,就没有任何泛化能力。

检查整个学习过程

周期(epochs):x轴通常都是表示周期(epochs)单位,该单位衡量了在训练中每个样本数据都被观察过次数的期望(一个周期意味着每个样本数据都被观察过了一次)。相较于迭代次数(iterations),一般更倾向跟踪周期,这是因为迭代次数与数据的批尺寸(batchsize)有关,而批尺寸的设置又可以是任意的。

1. 损失函数

训练期间第一个要跟踪的数值就是损失值,它在前向传播时对每个独立的批数据进行计算。下图展示的是随着损失值随时间的变化,尤其是曲线形状会给出关于学习率设置的情况:
【深度学习-CS231n】线性分类器和神经网络_第5张图片
左图展示了不同的学习率的效果。过低的学习率导致算法的改善是线性的。高一些的学习率会看起来呈几何指数下降,更高的学习率会让损失值很快下降,但是接着就停在一个不好的损失值上(绿线)。这是因为最优化的“能量”太大,参数在混沌中随机震荡,不能最优化到一个很好的点上。
右图显示了一个典型的随时间变化的损失函数值,在CIFAR-10数据集上面训练了一个小的网络,这个损失函数值曲线看起来比较合理(虽然可能学习率有点小,但是很难说),而且指出了批数据的数量可能有点太小(因为损失值的噪音很大)。
损失函数震荡:损失值的震荡程度和批尺寸(batch size)有关,当批尺寸为1,震荡会相对较大。当批尺寸就是整个数据集时震荡就会最小,因为每个梯度更新都是单调地优化损失函数(除非学习率设置得过高)。

2. 训练集和验证集准确率

在训练分类器的时候,需要跟踪的第二重要的数值是验证集和训练集的准确率。这个图表能够展现知道模型过拟合的程度:【深度学习-CS231n】线性分类器和神经网络_第6张图片
在训练集准确率和验证集准确率中间的空隙指明了模型过拟合的程度。在图中,蓝色的验证集曲线显示相较于训练集,验证集的准确率低了很多,这就说明模型有很强的过拟合。遇到这种情况,就应该增大正则化强度(更强的L2权重惩罚,更多的随机失活等)或收集更多的数据。另一种可能就是验证集曲线和训练集曲线如影随形,这种情况说明你的模型容量还不够大:应该通过增加参数数量让模型容量更大些。

3. 权重更新比例

最后一个应该跟踪的量是权重中更新值的数量和全部值的数量之间的比例。注意:是更新的,而不是原始梯度(比如,在普通sgd中就是梯度乘以学习率)。需要对每个参数集的更新比例进行单独的计算和跟踪。一个经验性的结论是这个比例应该在1e-3左右。如果更低,说明学习率可能太小,如果更高,说明学习率可能太高。下面是具体例子:

#假设参数向量为W,其梯度向量为dW
param_scale = np.linalg.norm(W.ravel())
update = -learning_rate*dW # 简单SGD更新
update_scale = np.linalg.norm(update.ravel())
W += update # 实际更新
print update_scale / param_scale # 要得到1e-3左右

相较于跟踪最大和最小值,有研究者更喜欢计算和跟踪梯度的范式及其更新。这些矩阵通常是相关的,也能得到近似的结果。

4. 每层的激活数据及梯度分布

一个不正确的初始化可能让学习过程变慢,甚至彻底停止。还好,这个问题可以比较简单地诊断出来。其中一个方法是输出网络中所有层的激活数据和梯度分布的柱状图。直观地说,就是如果看到任何奇怪的分布情况,那都不是好兆头。比如,对于使用tanh的神经元,我们应该看到激活数据的值在整个[-1,1]区间中都有分布。如果看到神经元的输出全部是0,或者全都饱和了往-1和1上跑,那肯定就是有问题了。
第一层可视化:
最后,如果数据是图像像素数据,那么把第一层特征可视化会有帮助:
将神经网络第一层的权重可视化的例子。左图中的特征充满了噪音,这暗示了网络可能出现了问题:网络没有收敛,学习率设置不恰当,正则化惩罚的权重过低。右图的特征不错,平滑,干净而且种类繁多,说明训练过程进行良好。

参数更新

  1. 普通更新
    梯度直接影响位置x += -learning_rate * dx
  2. 动量(momentum)更新
    利用物理观点:梯度影响速度,然后速度影响位置;在深度网络上,能得到更好的收敛速度。
v = mu * v - learning_rate * dx #与速度融合
x += v # 与位置融合

v 0 v_0 v0=0;
mu:设为[0.5,0.9,0.95,0.99]中的一个,和学习率随着时间退火(下文有讨论)类似,动量随时间变化的设置有时能略微改善最优化的效果,其中动量在学习过程的后阶段会上升。一个典型的设置是刚开始将动量设为0.5而在后面的多个周期(epoch)中慢慢提升到0.99。
3. Nesterov动量
当参数向量位于某个位置x时,观察上面的动量更新公式可以发现,动量部分(忽视带梯度的第二个部分)会通过mu * v稍微改变参数向量。因此,如果要计算梯度,那么可以将未来的近似位置x + mu * v看做是“向前看”,这个点在我们一会儿要停止的位置附近。因此,计算x + mu * v的梯度而不是“旧”位置x的梯度就有意义了。
【深度学习-CS231n】线性分类器和神经网络_第7张图片
Nesterov动量。既然我们知道动量将会把我们带到绿色箭头指向的点,我们就不要在原点(红色点)那里计算梯度了。使用Nesterov动量,我们就在这个“向前看”的地方计算梯度。
代码:

x_ahead = x + mu * v
# 计算dx_ahead(在x_ahead处的梯度,而不是在x处的梯度)
v = mu * v - learning_rate * dx_ahead
x += v

然而在实践中,人们更喜欢和普通SGD或上面的动量方法一样简单的表达式。通过对x_ahead = x + mu * v使用变量变换进行改写是可以做到的,然后用x_ahead而不是x来表示上面的更新。也就是说,实际存储的参数向量总是向前一步的那个版本。x_ahead的公式(将其重新命名为x)就变成了:

v_prev = v # 存储备份
v = mu * v - learning_rate * dx # 速度更新保持不变
x += -mu * v_prev + (1 + mu) * v # 位置更新变了形式

学习率退火

  1. 随步数衰减
    每进行几个周期就根据一些因素降低学习率。典型的值是每过5个周期就将学习率减少一半,或者每20个周期减少到之前的0.1。这些数值的设定是严重依赖具体问题和模型的选择的。在实践中可能看见这么一种经验做法:使用一个固定的学习率来进行训练的同时观察验证集错误率,每当验证集错误率停止下降,就乘以一个常数(比如0.5)来降低学习率。
  2. 指数衰减
    数学公式是 α = α 0 e − k t \alpha=\alpha_0e^{-kt} α=α0ekt,其中 α 0 , k \alpha_0,k α0,k是超参数, t t t是迭代次数(也可以使用周期作为单位)。
  3. 1/t衰减
    α = α 0 / ( 1 + k t ) \alpha = \alpha_0/(1+kt) α=α0/(1+kt),其中 α 0 , k \alpha_0,k α0,k是超参数, t t t是迭代次数
    note:在实践中,我们发现随步数衰减的随机失活(dropout)更受欢迎因为它使用的超参数(衰减系数和以周期为时间单位的步数)比k更有解释性。最后,如果你有足够的计算资源,可以让衰减更加缓慢一些,让训练时间更长些。

逐参数适应学习率方法

前面讨论的所有方法都是对学习率进行全局地操作,并且对所有的参数都是一样的。学习率调参是很耗费计算资源的过程,所以很多工作投入到发明能够适应性地对学习率调参的方法,甚至是逐个参数适应学习率调参。

  1. Adagrad适应性学习率算法
# 假设有梯度和参数向量x
cache += dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
  1. RMSprop
cache =  decay_rate * cache + (1 - decay_rate) * dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
  1. Adam
m = beta1*m + (1-beta1)*dx
v = beta2*v + (1-beta2)*(dx**2)
x += - learning_rate * m / (np.sqrt(v) + eps)

超参数调优

训练一个神经网络会遇到很多超参数设置。神经网络最常用的设置有:
1. 初始学习率
2. 学习率衰减方式(例如一个衰减常量)
3. 正则化强度(L2惩罚,随机失活强度)

一些额外的调参技巧:
1. 利用辅助程序记录调参过程:持续地随机设置参数然后进行最优化,对每个周期后验证集的准确率进行监控,然后向文件系统写下一个模型的记录点(记录点中有各种各样的训练统计数据,比如随着时间的损失值变化等),这个文件系统最好是可共享的。在文件名中最好包含验证集的算法表现,这样就能方便地查找和排序了。
2. 使用一个合理的验证集而不是交叉验证方式
3. 超参数范围:在对数尺度上进行超参数搜索。例如,一个典型的学习率应该看起来是这样:learning_rate = 10 ** uniform(-6, 1)。也就是说,我们从标准分布中随机生成了一个数字,然后让它成为10的阶数。对于正则化强度,可以采用同样的策略。直观地说,这是因为学习率和正则化强度都对于训练的动态进程有乘的效果。例如:当学习率是0.001的时候,如果对其固定地增加0.01,那么对于学习进程会有很大影响。然而当学习率是10的时候,影响就微乎其微了。这就是因为学习率乘以了计算出的梯度。因此,比起加上或者减少某些值,思考学习率的范围是乘以或者除以某些值更加自然。但是有一些参数(比如随机失活)还是在原始尺度上进行搜索(例如:dropout=uniform(0,1))。
4. 对于边界上的最优值要小心
5. 从粗到细地分阶段搜索
6. 贝叶斯超参数最优化

模型集成

在实践的时候,有一个总是能提升神经网络几个百分点准确率的办法,就是在训练的时候训练几个独立的模型,然后在测试的时候平均它们预测结果。集成的模型数量增加,算法的结果也单调提升(但提升效果越来越少)。还有模型之间的差异度越大,提升效果可能越好。进行集成有以下几种方法:

  1. 同一个模型,不同的初始化
  2. 在交叉验证中实现最好的模型
  3. 一个模型设置多个记录点
  4. 在训练的时候跑参数的平均值

总结

训练一个神经网络需要:

  1. 利用小批量数据对实现进行梯度检查,还要注意各种错误。
  2. 进行合理性检查,确认初始损失值是合理的,在小数据集上能得到100%的准确率。
  3. 在训练时,跟踪损失函数值,训练集和验证集准确率,如果愿意,还可以跟踪更新的参数量相对于总参数量的比例(一般在1e-3左右),然后如果是对于卷积神经网络,可以将第一层的权重可视化。
  4. 推荐的两个更新方法是SGD+Nesterov动量方法,或者Adam方法。
  5. 随着训练进行学习率衰减。比如,在固定多少个周期后让学习率减半,或者当验证集准确率下降的时候。
  6. 使用随机搜索(不要用网格搜索)来搜索最优的超参数。分阶段从粗(比较宽的超参数范围训练1-5个周期)到细(窄范围训练很多个周期)地来搜索。
  7. 进行模型集成来获得额外的性能提高。

你可能感兴趣的:(深度学习,深度学习)