Lecture4,5,6是把神经网络的一些东西分开讲了,个人在笔记的顺序上没有按照原文来,我内容重新编排后整理融合成了这一篇文章
Lecture4,5,6:Neural Networks
1. 神经网络抽象
1.1 抽象生物神经元
我们在高中就学过神经元是什么样的了。就是他前面有突触,突触可以传递递质,兴奋性递质到达的时候,神经元电位上升,抑制性递质到达,电位下降。总的来说就是接受前面的很多输入,经过一定处理后输出。这是不是跟之前的 Linear Classification一样?也就是说,我们可以将人脑单个神经元的操作视为一个线性分类器。记住,不同突触的能力不一样,有的很厉害,一个突触就可以影响整个神经元的电位,因此,我们还需要一个权重。还有一点,我们知道神经元还有一个“兴奋”的过程,在电位达到一定程度的时候神经元会达到兴奋状态,向后发送脉冲,没达到时是不会发送脉冲的,为了模拟这一点,我们设定一个“激活函数”。总的来说,一个神经元的运行可以理解为,执行操作
1.2 人工神经网络(Artificial Neural Network)
什么叫做神经网络?很多个神经元在一起连接成网就叫神经网络【滑稽.jpg】。最简单的神经网络可以看成是这样的。
我们将不同神经元之间分层,这样做有方便计算的考虑。在后面的反向传播中我们会提到这一点。
2. 神经网络基本计算
2.1 前向传播 Forward Propogation
其实很容易发现,神经网络的前向计算方式跟线性分类器其实是一样的,实际上,线性分类器可以看作是一个只有输入和输出的神经网络。
不过由于激活函数的存在对每一层需要进行两次运算即:
2.2 反向传播 Backward Propogation
如果我没记错的话,在前面一片文章里,我就提到了一句求导好像就没有下文了,这里比较详细的说说。我们仍然需要简化问题……毕竟……太复杂算得太麻烦,而如果平时只使用框架也不需要频繁进行BP推导。
我们先从最简单的开始。我们假设我们的网络输出只有一个数字。也就是说,网络是下面这样的,其中是一个n维行向量,
是一个数字,
是一个n维列向量,
是正确预测结果。我们先看下这是种什么情况。
向前传播是
那么我们从最右边开始倒推我们首先计算
接着是
其中展开之后是
因此有
最后相乘就得到了参数更新所需的梯度。到现在为止还是简单的链式法则。
这个还比较简单,我们下面看看当输出有很多个的时候的反向传播。我们先前进一小步,把上面的改成一个
的矩阵,这样
就是一个列向量了,
是输入还不变,还是
维列向量,当然相对应的
,也变成向量了,
维。
向前传播还是
现在我们开始从右面倒推,下面的表达式不是严格的数学表达式,理解意思即可
首先还是注意,这个式子里面的都是向量
接着是这个向量相乘,是逐元素相乘
我们观察发现于是有
因此
现在我们已经完成了多输入多输出的反向推导。这不就是神经网络中的一层吗?我们接下来给出更通用的形式。
2.3 神经网络中一层的计算
2.3.1 参数
我们用表示现在的层数,
表示这一层从上一层接受的输入向量,
表示这一层的权重,
表示这一层加入的线性偏置,
为这一层的中间变量,
表示这一层的输出值。
2.3.2 Forward Propogation(整个网络的前向传播也称“推理”inference)
2.3.3 Backward Propogation
注意:
- 这里写的是
但是上面举的例子里面是
这里的
就是求导结果,不是写错了(真写错了记得留个言)。
- 关于
的推导,和上面
的推导一样,写出式子,整理就行了。
2.3.4 trian
接下来根据我们获得的来更新参数就可以了,更新方式并不止一种,在本文3.5节有介绍。
2.4 激活函数 Activation Function
激活函数在之前不太好提,就是因为反向传播说的不很详细,不好讲激活函数的优缺点,下面是几种激活函数的介绍。
2.4.1 Sigmoid函数
- 我们看到,这个函数两侧,太平了,所以如果处理不当,会导致“梯度消失”的问题。我们看出这个函数输出是
的,于是,我们可以想象,其实一层层下去,这个函数会慢慢右移,最后,就会无法学习,初始化权重比较大也会导致同样的问题。
- 同样是因为函数输出是
的。如果输入神经元的数据总是正数,那么梯度在反向传播的过程中,将会要么全部是正数,要么全部是负数(具体依整个表达式而定)。这将会导致梯度下降权重更新时出现z字型的下降。
2.4.2 Tanh函数
和sigmoid一样,它也存在饱和问题,但是和sigmoid不同的是,它的输出是零中心的。因此,在实际操作中,tanh比sigmoid更受欢迎。注意,tanh是一个简单放大的sigmoid.
2.4.3 ReLU函数
线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元
- 优点:相较于sigmoid和tanh函数,ReLU对于随机梯度下降的收敛有巨大的加速作用( Krizhevsky 的论文指出有6倍之多)。这是由它的线性,非饱和的公式导致的。
- 优点:sigmoid和tanh含有指数运算等耗费计算资源的操作,而ReLU可以简单地通过对一个矩阵进行阈值计算得到。
- 缺点:在训练的时候,ReLU单元比较脆弱并且可能“死掉”。举例来说,当一个很大的梯度流过ReLU的神经元的时候,可能会导致梯度更新到一种特别的状态,在这种状态下神经元将无法被其他任何数据点再次激活。如果这种情况发生,那么从此所以流过这个神经元的梯度将都变成0。也就是说,这个ReLU单元在训练中将不可逆转的死亡,因为这导致了数据多样化的丢失。(负数没梯度导致的?)
- 其实我个人一直对ReLU感觉很奇怪……毕竟这看上去……就是个线性单元嘛……谁知道他为啥有用,……不过确实有用
2.4.4 Leaky ReLU
Leaky ReLU是为解决“ReLU死亡”问题的尝试。其中为一个比较小的数比如0.01.
3. 训练神经网络
3.1 数据预处理
在数据预处理中我们有三个常用记号:数据矩阵,数据大小
,其中
代表样本数量,
代表数据维度
3.1.1 均值减法(Mean subtraction)
均值减法(Mean subtraction) 是预处理最常用的形式。它对数据中每个独立特征减去平均值,从几何上可以理解为在每个维度上都将数据云的中心都迁移到原点。
3.1.2 归一化(Normalization)
归一化(Normalization) 是指将数据的所有维度都归一化,使其数值范围都近似相等。有两种常用方法可以实现归一化。第一种是先对数据做零中心化(zero-centered)(其实就是上面的均值减法)处理,然后每个维度都除以其标准差。第二种方法是对每个维度都做归一化,使得每个维度的最大和最小值是1和-1。但是这个预处理操作只有在确信不同的输入特征有不同的数值范围(或计量单位)时才有意义。在图像处理中,由于像素的数值范围几乎是一致的(都在0-255之间),所以进行这个额外的预处理步骤并不是很必要。
3.2 权值初始化
3.2.1 一种错误的方案:全零初始化
如果我们的权重是全零的,那么他们的输出也相同,梯度也相同,什么都是一样的,这样用很多神经元就没有意义了。所以,全零初始化是一种错误的初始化。
3.2.2 随机初始化
我们使用很多神经元的目的就是为了让他们各自不一样,以此来学习出不同的特征,加大训练的准确度,那怎么着就不一样了呢?干脆随机初始化吧。就这样。但是这样还会有一个不很容易注意到的小问题,在实践中会遇到,我们生成的随机数越多,他们的方差就越大,以常用的Python中的numpy为例,numpy默认生成的是高斯分布,他的均值是指定好的,数字越多,方差自然越大,我们可以这样做
w = np.random.randn(n) / sqrt(n)
这样做会使得生成的方差都为1,使得神经网络在一开始的时候每一层生成相似的结果。实践证明,这可以提高收敛速度。
3.3 批量归一化 Batch Normalization
我们在数据预处理中介绍了归一化,Batch Normalization说简单了就是在神经网络内部进行归一化处理,比如说
在实践中,使用了批量归一化的网络对于不好的初始值有更强的鲁棒性。
3.4 正则化
我们在之前介绍过L2正则化,现在,介绍另一种正则化Dropout。
Dropout就是在训练的时候随机废掉一些节点,相当于把网络搞小了。用图片来表示就是这样的:
核心思路:在训练过程中,随机失活可以被认为是对完整的神经网络抽样出一些子集,每次基于输入数据只更新子网络的参数(然而,数量巨大的子网络们并不是相互独立的,因为它们都共享参数)。在测试过程中不使用随机失活,可以理解为是对数量巨大的子网络们做了模型集成(model ensemble),以此来计算出一个平均的预测。
需要注意的是,我们在训练的时候会规定一个参数
Dropout的理解。我一开始看到这个的时候,感觉这简直就是个智障级别的操作,明明那么多神经元,非要废掉一点……真是日了狗了。但是注意,Dropout仅在数据量大,网络大的情况下适用。想想,去掉一部分确实就是减少过拟合了(雾)。专业的解释可以参考 https://arxiv.org/abs/1506.08700 。
3.5 参数更新
3.5.1 普通更新
我们都知道,在我们计算出梯度之后,我们需要更新权重,权重更新多少,靠一个“学习率”控制。一般情况下,我们的更新是:
其中learning_rate是一个超参数,它是一个固定的常量。当在整个数据集上进行计算时,只要学习率足够低,总是能在损失函数上得到非负的进展。
3.5.2 动量更新
动量(Momentum)更新是另一个方法,这个方法在实践上几乎总能得到更好的收敛速度。该方法可以看成是从物理上得到的启发。损失值可以理解为是山的高度。用随机数初始化参数等同于在某个位置给质点设定初始速度为0。这样最优化过程可以看做是模拟参数(即质点)在山上滚动的过程。
因为作用于质点的力与梯度的潜在能量有关,质点所受的力就是损失函数的负梯度。还有,因为,所以在这个观点下负梯度与质点的加速度是成比例的。注意这个理解和上面的随机梯度下降(SDG)是不同的,在普通版本中,梯度直接影响位置。而在这个版本的更新中,物理观点建议梯度先影响速度,然后速度再影响位置:
在这里引入了一个初始化为0的变量v和一个超参数mu。说得不恰当一点,这个变量(mu)在最优化的过程中被看做动量(一般值设为0.9),但其物理意义与摩擦系数更一致。这个变量有效地抑制了速度,降低了系统的动能,不然质点在山底永远不会停下来。通过交叉验证,这个参数通常设为[0.5,0.9,0.95,0.99]中的一个。和学习率随着时间退火(下文有)类似,动量随时间变化有时能略微改善优化的效果,其中动量在学习过程的后阶段会上升。一个典型的设置是刚开始将动量设为0.5而在后面的多个训练轮次中慢慢提升到0.99。
3.5.3 Nesterov动量更新
Nesterov动量更新说实话我没怎么看明白他到底想讲什么意思……有时间看论文原文再回来填坑吧。
3.5.4 学习率退火
我们在之前已经提到过,学习率过大会导致在训练后期网络难以收敛,在最优解附近跳来跳去,为了解决这个问题,我们将使学习率不断下降,常用的有两种。
- 指数衰减:
其中k是超参数,t是迭代次数
- 1/t衰减:
其中k是超参数,t是迭代次数
3.5.5 逐参数适应学习率方法
我们前面讨论的方案都是对全局学习率进行操控,但是,我们知道,神经网络每一点都是不同的,如果我们对每个参数都用不同的学习率怎么样呢?
下面为了方便(懒得打公式,下面的式子写成写程序都简单,写公式麻烦的一比)
下面的结论都是没有推导的(如果我看视频没走神的话),想看可以找原论文看
3.5.5.1 Adagrad
cache += dx**2
x += ‐ learning_rate * dx / (np.sqrt(cache) + eps)
其中eqs一般设置为防止除0。
3.5.5.2 RMSprop
cache = decay_rate * cache + (1 ‐ decay_rate) * dx**2
x += ‐ learning_rate * dx / (np.sqrt(cache) + eps)
decay_rate是一个超参数,常用的值是[0.9,0.99,0.999]。
3.5.5.3 Adam
m = beta1*m + (1‐beta1)*dx
v = beta2*v + (1‐beta2)*(dx**2)
x += ‐ learning_rate * m / (np.sqrt(v) + eps)
RMSProp的动量版【滑稽.jpg】推荐的参数值