本文主要基于各大公司的面试题目,经典论文,学习过程中的常见问题进行总结,以期达到进一步务实深度学习基础部分,和面试复习。
本章主要总结深度学习经典网络发展,以及其作用特点所在
Batch Normalization,BN,批标准化,是2015年提出的,如今在进行深度学习训练时,一般将其作为基础结构。
论文地址:Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift
Internal Covariate Shift,内部协方差转移:深度网络中,每一层的参数更新导致上一层输入数据的分布发生改变,通过层层的累加,高层的输入分布变化会非常剧烈,这就使得高层需要不断去重新适应底层的参数更新。为了训好模型,我们需要非常谨慎地去设定学习率、初始化权重、以及尽可能细致的参数更新策略。
深度网络随着网络深度的增加,出现以下两个问题:
训练复杂的原因
因为ICS的存在,打破了机器学习模型的假设:独立同分布假设,训练数据和测试数据是满足相同分布的。
一旦输入的分布老是变化,不符合独立同分布的假设,因此网络模型很难稳定的去学习。
收敛变慢的原因:
随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或者变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近,所以这导致反向传播时低层神经网络的梯度消失或者梯度爆炸,这是训练深层神经网络收敛越来越慢的本质原因。
BatchNorm就是在深度神经网络训练过程中使得每一层神经网络的输入保持相同分布的。
通过将输入数据的分布映射到均值为0,方差为1的正态化分布,使得激活函数输入值落在非线性函数对输入比较敏感的区间(之前是向两端偏移),这样就可以得到一个较合适的梯度,不会消失也不会爆炸。
BN层一般用在全连接层和卷积层后面,而不是放在非线性单元(激活函数)之后。
原文解释:
因为非线性单元的输出分布形状会在训练过程中变化,归一化无法消除他的方差偏移。
相反的,全连接和卷积层的输出一般是一个对称,非稀疏的一个分布,更加类似高斯分布,对他们进行归一化会产生更加稳定的分布。
例如像relu这样的激活函数,如果你输入的数据是一个高斯分布,经过他变换出来的数据能是一个什么形状?小于0的被抑制了,也就是分布小于0的部分直接变成0了,这样不是很高斯了。
在2012年,Hinton在其论文《Improving neural networks by preventing co-adaptation of feature detectors》中提出Dropout。
在2012年,Alex、Hinton在其论文《ImageNet Classification with Deep Convolutional Neural Networks》中用到了Dropout算法,用于防止过拟合。
在深度网络中,如果模型的寻训练数据不足,而模型的参数很多导致复杂的模型很容易产生过拟合的现象,也就是在训练集效果很好,在测试集一塌糊涂。
对于一个神经网络,输入是x输出是y,正常的流程是:我们首先把x通过网络前向传播,然后把误差反向传播以决定如何更新参数让网络进行学习。
使用Dropout之后,过程变成如下:
(1)首先随机(临时)删掉网络中一部分的隐藏神经元,输入输出神经元保持不变(图3中虚线为部分临时被删除的神经元)
(2) 然后把输入x通过修改后的网络前向传播,然后把得到的损失结果通过修改的网络反向传播。一小批训练样本执行完这个过程后,在没有被删除的神经元上按照随机梯度下降法更新对应的参数(w,b)。
(3)然后继续重复这一过程:
一直重复以上过程
具体怎么让某些神经元以一定的概率停止工作(就是被删除掉)?
(1)每个神经元增加一个概率参数
没有Dropout的网络计算公式:
z i ( l + 1 ) = w i ( l + 1 ) y l + b i ( l + 1 ) , y i ( l + 1 ) = f ( z i ( l + 1 ) ) . \begin{aligned} z_{i}^{(l+1)} &=\mathbf{w}_{i}^{(l+1)} {\mathbf{y}}^{l}+b_{i}^{(l+1)}, \\ y_{i}^{(l+1)} &=f\left(z_{i}^{(l+1)}\right) . \end{aligned} zi(l+1)yi(l+1)=wi(l+1)yl+bi(l+1),=f(zi(l+1)).
采用Dropout的网络计算公式:
r j ( l ) ∼ Bernoulli ( p ) , y ~ ( l ) = r ( l ) ∗ y ( l ) , z i ( l + 1 ) = w i ( l + 1 ) y ~ l + b i ( l + 1 ) , y i ( l + 1 ) = f ( z i ( l + 1 ) ) . \begin{aligned} r_{j}^{(l)} & \sim \operatorname{Bernoulli}(p), \\ \widetilde{\mathbf{y}}^{(l)} &=\mathbf{r}^{(l)} * \mathbf{y}^{(l)}, \\ z_{i}^{(l+1)} &=\mathbf{w}_{i}^{(l+1)} \widetilde{\mathbf{y}}^{l}+b_{i}^{(l+1)}, \\ y_{i}^{(l+1)} &=f\left(z_{i}^{(l+1)}\right) . \end{aligned} rj(l)y (l)zi(l+1)yi(l+1)∼Bernoulli(p),=r(l)∗y(l),=wi(l+1)y l+bi(l+1),=f(zi(l+1)).
上面公式中Bernoulli函数是为了生成概率r向量,也就是随机生成一个0、1的向量。
(1) 从Ensemble learning的角度
随机杀死一部分神经元,使得每次都是一个新的模型,每次的模型结果进行投票平均,也就是另一个random forest模型。
(2)减少神经元之间复杂的共适应性
随机使得两个神经元不一定共同出现在一个神经网络里面,这样权值更新不再依赖于有固定关系的隐含节点的共同作用,阻止了某些特征仅仅在其它特定特征下才有效果的情况 。迫使网络去学习更加鲁棒的特征。
换句话说假如我们的神经网络是在做出某种预测,它不应该对一些特定的线索片段太过敏感,即使丢失特定的线索,它也应该可以从众多其它线索中学习一些共同的特征。从这个角度看dropout就有点像L1,L2正则,减少权重使得网络对丢失特定神经元连接的鲁棒性提高。
基于keras的源码实现
通过以下代码可以单独实现dropout效果:
# coding:utf-8
import numpy as np
# dropout函数的实现
def dropout(x, level):
if level < 0. or level >= 1: #level是概率值,必须在0~1之间
raise ValueError('Dropout level must be in interval [0, 1[.')
retain_prob = 1. - level
# 我们通过binomial函数,生成与x一样的维数向量。binomial函数就像抛硬币一样,我们可以把每个神经元当做抛硬币一样
# 硬币 正面的概率为p,n表示每个神经元试验的次数
# 因为我们每个神经元只需要抛一次就可以了所以n=1,size参数是我们有多少个硬币。
random_tensor = np.random.binomial(n=1, p=retain_prob, size=x.shape) #即将生成一个0、1分布的向量,0表示这个神经元被屏蔽,不工作了,也就是dropout了
print(random_tensor)
x *= random_tensor
print(x)
x /= retain_prob
return x
#对dropout的测试,大家可以跑一下上面的函数,了解一个输入x向量,经过dropout的结果
x=np.asarray([1,2,3,4,5,6,7,8,9,10],dtype=np.float32)
dropout(x,0.4)
[1 1 1 1 1 0 1 1 0 1]
[ 1. 2. 3. 4. 5. 0. 7. 8. 0. 10.]
array([ 1.6666666, 3.3333333, 5. , 6.6666665, 8.333333 ,
0. , 11.666666 , 13.333333 , 0. , 16.666666 ],
dtype=float32)