PyTorch 学习笔记(四):激活函数对比、权重初始化、防止过拟合的方法

一. 激活函数

如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合。如果使用的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。

1. Sigmoid

Sigmoid非线性函数的数学表达式是 σ ( x ) = 1 1 + e − x \sigma (x)=\frac{1}{1+e^{-x}} σ(x)=1+ex1,其图形如下图所示:
PyTorch 学习笔记(四):激活函数对比、权重初始化、防止过拟合的方法_第1张图片
我们知道Sigmoid函数是将一个实数输入转化到0-1之间的输出,具体来说越小的负数转化到越靠近0,越大的正数越靠近1。

历史上Sigmoid函数频繁的使用,因为其具有良好的可解释性。但最近这些年,Sigmoid函数已经很少被人使用了,主要是因为Sigmoid函数有以下两大缺点。
(1)Sigmoid函数会造成梯度损失。一个非常不好的地方在于Sigmoid在靠近1和0的两端时梯度几乎为0,而反向传播算法的梯度向下传播时,每过一层就会增加一个 g ′ ( z ) g^{'}(z) g(z)项(Sigmoid关于每一层线性组合值的导数),且Sigmoid函数的导数满足 f ′ ( x ) = f ( x ) ( 1 − f ( x ) ) f^{'}(x)=f(x)(1-f(x)) f(x)=f(x)(1f(x)),又f(x)的值在(0, 1)之间,故 f ′ ( x ) f^{'}(x) f(x)的值在(0, 0.25]之间,因此当神经网络层数非常深的时候,较深层的梯度值由于乘了很多值很小的数更变得很小,导致较深层的参数更新不动,这就是“梯度消失”现象。另外,如果使用Sigmoid函数,那么需要在权重初始化的时候非常小心,如果初始化的权重过大,经过线性激活函数也会导致大多数神经元变得饱和,没有办法更新参数。
(2)Sigmoid输出并非以0为均值,这就会导致经过Sigmoid激活函数之后的输出,作为后面一层的输入的时候是非0均值的,这个时候如果输入进入下一层神经元的时候全是正的,那么在更新参数时永远都是正梯度。怎么理解呢?比如下一层神经元的输入是x,参数是w和b,那么输出为f=wx+b,这个时候 ▽ f ( w ) = x \bigtriangledown f(w)=x f(w)=x,所以如果x是0均值的数据,那么梯度就会有正有负,但是这个问题并不是很严重,因为一般神经网络在训练的时候都是按batch进行训练的,这个时候一定程度上可以缓解这个问题。

2. tanh

tanh激活函数是上面Sigmoid函数的变形,其数学表达式为 t a n h ( x ) = 2 σ ( 2 x ) − 1 tanh(x)=2\sigma(2x)-1 tanh(x)=2σ(2x)1,图形如下所示:
PyTorch 学习笔记(四):激活函数对比、权重初始化、防止过拟合的方法_第2张图片
它将输入数据转化到-1到1之间,可以通过图像看出它将输出变成了0均值,在一定程度上解决了Sigmoid函数的第二个问题,但是它仍然存在梯度消失的问题。

3. ReLU

ReLU激活函数近一些年来越来越流行,它的数学表达式为f(x)=max(0, x),换句话说,这个激活函数只是简单地将大于0的部分保留,将小于0的部分变成0,图形如下:
PyTorch 学习笔记(四):激活函数对比、权重初始化、防止过拟合的方法_第3张图片
ReLU的优点:
(1)相比于Sigmoid激活函数和Tanh激活函数,ReLU能够极大地加速随机梯度下降法的收敛速度,这因为它是线性的,且不存在梯度消失的问题。
(2)相比于Sigmoid激活函数和tanh激活函数的复杂计算,ReLU的计算方法更加简单,只需要一个阈值过滤就可以得到结果。

**ReLU的缺点:**训练的时候很脆弱,我们回忆一下反向传播的梯度公式里面有一项w,如果权重初始化的时候不是很好,很多个大的w乘起来会得到一个很大的梯度——梯度爆炸现象,从而导致更新的w很小,进而得到的输出值进入了负半轴,神经元失活。如果发生这种情况之后,经过ReLU的梯度永远都会是0,也就意味着参数无法更新了,因为ReLU激活函数本质上是一个不可逆的过程。不过这个问题可以通过一些比较好的权重初始化方法解决,因此ReLU现在仍然是用的最多的激活函数。

4. Leaky ReLU

Leaky ReLU激活函数是ReLU激活函数的变形,主要是为了解决它在负半轴上梯度一直为0的问题,给它在负半轴上设置了一个很小的斜率,比如0.01,它的数学形式可以表现为: f ( x ) = I ( x < 0 ) ( α x ) + I ( x ≥ 0 ) x f(x)=I(x<0)(\alpha x)+I(x\geq 0)x f(x)=I(x<0)(αx)+I(x0)x

二. 权重初始化

1. 全0初始化

乍一看这种方法貌似可行,然而这种策略不能采用。因为如果神经网络中每个权重都被初始化为相同的值,那么每个神经元的计算结果就相同,在反向传播时也会计算出相同的值,最后导致所有权重都会有相同的更新,那么这样一个具有很多隐藏单元的网络结构就是完全多余的表达,最终该网络只能学到一种特征。换句话说,如果每个权重都被初始化成相同的值,那么权重之间失去了不对称性。

2. 随机初始化

目前知道我们希望权重初始化的时候能够接近0,但是不能全是0,所以可以初始化权重为一些靠近0的随机数,通过这个方式可以打破对称性。这里面的核心想法就是神经元最开始都是随机的、唯一的,所以在更新的时候也是作为独立的部分,最后一起合成在神经网络当中。

一般的随机初始化策略有高斯随机化、均匀随机化等,需注意的是并不是越小的随机化产生的效果越好,因为权重初始化越小,反向传播中关于权重的梯度也越小,因为梯度与参数的大小是成比例的,所以这会极大地减弱梯度流的信号,成为神经网络训练中的隐患。

这个初始化策略还存在一个问题就是网络输出分布的方差会随着输入维度的增加而增大,可以用下面的数学式子来说明(n代表上一层神经元的数量):
在这里插入图片描述

其中假设输入和权重都是0均值的,也就是说 E ( w i ) E(w_{i}) E(wi)= E ( x i ) E(x_{i}) E(xi)=0,但这并不是一般的情况比如经过了ReLU激活函数之后输出就会是一个正的均值,这里做这样的假设是为了计算方便。我们可以看到输出的结果S比输入x的方差增大了 n V a r ( w ) nVar(w) nVar(w)倍,如果网络越来越深,就会导致方差越来越大,所以希望 n V a r ( w ) nVar(w) nVar(w)尽可能接近1,也就是说 n V a r ( w ) = 1 nVar(w)=1 nVar(w)=1,这可以得到 V a r ( w ) = 1 n Var(w)=\frac{1}{n} Var(w)=n1,也就是w初始化之后需要除以 n \sqrt{n} n

至于为什么要让输入输出的方差尽可能一样?可以这样考虑,整个大型前馈神经网络无非就是一个超级大映射,将原始样本稳定的映射成它的类别。也就是将样本空间映射到类别空间。试想,如果样本空间与类别空间的分布差异很大,比如说类别空间特别稠密,样本空间特别稀疏辽阔,那么在类别空间得到的用于反向传播的误差丢给样本空间后简直变得微不足道,也就是会导致模型的训练非常缓慢。同样,如果类别空间特别稀疏,样本空间特别稠密,那么在类别空间算出来的误差丢给样本空间后简直是爆炸般的存在,即导致模型发散震荡,无法收敛。因此,我们要让样本空间与类别空间的分布差异(密度差别)不要太大,也就是要让它们的方差尽可能相等。

参考文章:https://zhuanlan.zhihu.com/p/27919794

三. 防止过拟合

1. 正则化

L2正则化是正则化(regularization)中比较常用的形式,它的想法是对于权重过大的部分进行惩罚,也就是直接在损失函数中增加权重的二范数量级,也就是 1 2 λ w 2 \frac{1}{2}\lambda w^{2} 21λw2,其中 λ \lambda λ是正则化强度,常使用0.5,因为对于 w 2 w^{2} w2的梯度是2w,使用 1 2 \frac{1}{2} 21就能使得梯度是 λ w \lambda w λw而不是 2 λ w 2\lambda w 2λw。所以使用L2正则化可以看成是权重更新在原来的基础上再减去 λ w \lambda w λw,这样可以让参数更新后更加靠近0.

L1正则化是另外一种正则化方法,其在损失函数中增加权重的1范数,也就是 λ ∣ w ∣ \lambda \left | w \right | λw,我们也可以将L1正则化和L2正则化结合起来,如 λ 1 ∣ w ∣ + λ 2 w 2 \lambda_{1} \left | w \right |+\lambda_{2}w^{2} λ1w+λ2w2。L1正则化相对于L2正则化的优势是优化的过程中可以让权重变得更加稀疏,换句话说,也就是在优化结束的时候,权重只会取一些与最重要的输入有关的权重,这就使得与噪声相关的权重被尽可能降为0。L2正则化的优势在于最终的效果会比L1正则化更加发散,权重也会被限制得更小。

https://zhuanlan.zhihu.com/p/35356992

2. Dropout

现在介绍一种非常有效、简单、同时也是现在深度学习使用最为广泛的防止过拟合的方法——Dropout。其核心方法就是在训练的时候依概率P保留每个神经元,也就是说每次训练的时候有些神经元会被设置为0,其简单的示意图如下:
PyTorch 学习笔记(四):激活函数对比、权重初始化、防止过拟合的方法_第4张图片
通过上图可以看出来每次训练都有神经元并没有参与到网络中,但是在预测的时候不再这样处理,这也很好理解,如果预测应用Dropout,由于随机性,每次预测出来的结果都不一样,这样预测的时候完全靠运气,这显然是不行的。所以会保留网络全部的权重,取代应用Dropout,在每层网络的输出上应用上P的缩放。这个想法是很重要的,因为我们不做任何处理,那么网络的行为在预测时和训练时就会不同,这不是所希望的,所以需要应用缩放。

那么为什么在网络的输出部分应用P缩放可以达到相同的效果呢?考虑一个神经元在应用Dropout之前的输出为x,那么应用Dropout之后的输出期望值就是 Px+(1-p)0,所以在预测的时候,如果保留所有的权重,就必须调整 x → P x x\rightarrow Px xPx来保证其输出期望相同。

其具体实现可以参考:https://blog.csdn.net/hjimce/article/details/50413257
关于Dropout的两种理解:https://blog.csdn.net/dod_jdi/article/details/78379781

3. 批量归一化(Batch Normalization)

前向传播:卷积层的每一个输出通道对应一组 γ \gamma γ μ \mu μ,全连接层的每一个神经元对应一组 γ \gamma γ μ \mu μ
PyTorch 学习笔记(四):激活函数对比、权重初始化、防止过拟合的方法_第5张图片反向传播:
PyTorch 学习笔记(四):激活函数对比、权重初始化、防止过拟合的方法_第6张图片
代码实现:https://blog.csdn.net/stesha_chen/article/details/84999511#commentBox

以上内容皆为廖星宇编著的《深度学习入门之PyTorch》这本书上的内容,代码部分略有改动。

你可能感兴趣的:(pytorch框架)