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】推荐的参数值