单层感知机
从二分类讲起
感知机 (Perceptron )是 Frank Rosenblatt 在1957年所发明的一种人工神经网络。它可以被视为一种最简单形式的神经网络 ,是一种二元分类器。对于两种类别 y = ± 1 y=\pm 1 y = ± 1 ,一个单层感知机的预测原理如下
o = σ ( ⟨ w , x ⟩ + b ) , σ ( x ) = { 1 , x > 0 − 1 , x ⩽ 0 o=\sigma(\langle\boldsymbol{w},\boldsymbol{x}\rangle+b),\quad \sigma(x)=\begin{cases}1,&x>0\\-1,&x\leqslant 0\\\end{cases}
o = σ (⟨ w , x ⟩ + b ) , σ ( x ) = { 1 , − 1 , x > 0 x ⩽ 0
对于这个感知机的训练,可以按照如下步骤进行
初始化 w = 0 , b = 0 \boldsymbol{w}=\boldsymbol{0},b=0 w = 0 , b = 0
对于某个样本,如果 y ( i ) ( ⟨ w , x ( i ) ⟩ + b ) ⩽ 0 y^{(i)}(\langle\boldsymbol{w},\boldsymbol{x}^{(i)}\rangle+b)\leqslant 0 y ( i ) (⟨ w , x ( i ) ⟩ + b ) ⩽ 0 ,说明分类错误,更新参数
w ← w + y ( i ) x ( i ) , b ← b + y ( i ) \boldsymbol{w}\leftarrow\boldsymbol{w}+y^{(i)}\boldsymbol{x}^{(i)},\quad b\leftarrow b+y^{(i)}
w ← w + y ( i ) x ( i ) , b ← b + y ( i )
所有训练集分类正确,停止训练
上述训练过程等价于 ∣ B ∣ = 1 |\mathcal{B}|=1 ∣ B ∣ = 1 的梯度下降,并使用下述损失函数
L ( w , x , y ) = max ( 0 , − y ⟨ w , x ( i ) ⟩ ) L(\boldsymbol{w},\boldsymbol{x},y)=\max(0,-y\langle\boldsymbol{w},\boldsymbol{x}^{(i)}\rangle)
L ( w , x , y ) = max ( 0 , − y ⟨ w , x ( i ) ⟩)
收敛定理
对于一个线性可分 的 R n \mathbb{R}^n R n 维数据集,如果满足
所有数据都在一个半径为 r r r 的超球体内
存在一个阈值 ρ \rho ρ 满足
y ( i ) ( x T w + b ) ⩾ ρ y^{(i)}(\boldsymbol{x}^T\boldsymbol{w}+b)\geqslant \rho
y ( i ) ( x T w + b ) ⩾ ρ
参数 ∥ w ∥ + b ⩽ 1 \|\boldsymbol{w}\|+b\leqslant 1 ∥ w ∥ + b ⩽ 1
那么,有Novikoff收敛定理保证感知机的收敛步数 N N N 满足
N ⩽ r 2 + 1 ρ 2 N\leqslant\frac{r^2+1}{\rho^2}
N ⩽ ρ 2 r 2 + 1
上述过程中的 ρ \rho ρ 可以看做支持向量机 算法中的间隔 。
然而,收敛定理成立的前提是数据集必须线性可分 。当问题不可避免的出现非线性可分特性时,我们就有必要考虑引入其他的非线性假设了。一个最常见的例子是 XOR 问题,其数据样本就是一个非线性可分集合,此时使用感知机会得到较差的训练结果。
多层感知机
隐藏层
为了解决单层感知机的局限性,我们可以考虑在其神经网络结构中引入隐藏层 (Hidden Layers )。隐藏层的层数和单元个数都是超参数,可以人为自由规定,但他们都是全连接层 。
一个神经网络模型除去输出层之外的层数记为 L − 1 L-1 L − 1 ,如果 L − 1 ⩾ 1 L-1\geqslant 1 L − 1 ⩾ 1 ,那么这个架构就被称为多层感知机 (Multilayer Perceptron, MLP ),下图是一个简单形式的感知机模型。
[{"url":"/img/Pytorch/p3f1.png","alt":"一个简单的MLP","title":""}]
以上述模型为例,设输入为 x ∈ R n \boldsymbol{x}\in\mathbb{R}^n x ∈ R n ,其到隐藏层 h ∈ m \boldsymbol{h}\in\mathbb{m} h ∈ m 的映射可以表示为
h = W ( 1 ) x + b ( 1 ) \boldsymbol{h}=\boldsymbol{W}^{(1)}\boldsymbol{x}+\boldsymbol{b}^{(1)}
h = W ( 1 ) x + b ( 1 )
从隐藏层到输出层 y ∈ R K \boldsymbol{y}\in\mathbb{R}^K y ∈ R K 的映射可以表示为
y = s o f t m a x ( o ) = s o f t m a x ( W ( 2 ) h + b ( 2 ) ) \boldsymbol{y}=\mathrm{softmax}(\boldsymbol{o})=\mathrm{softmax}(\boldsymbol{W}^{(2)}\boldsymbol{h}+\boldsymbol{b}^{(2)})
y = softmax ( o ) = softmax ( W ( 2 ) h + b ( 2 ) )
事实上,上述过程没有改变感知机的线性映射关系 。为了发挥多层架构模型的潜力,我们需要选取合适的方式引入非线性模式 。一个常见形式是在隐藏层中加入激活函数 (Activation Function )。
h = σ ( W ( 1 ) x + b ( 1 ) ) \boldsymbol{h}=\sigma(\boldsymbol{W}^{(1)}\boldsymbol{x}+\boldsymbol{b}^{(1)})
h = σ ( W ( 1 ) x + b ( 1 ) )
其中 σ ( ⋅ ) \sigma(\cdot) σ ( ⋅ ) 是激活函数的输出值,也称作活性值 。对于更多层数的感知机,我们也可以在每层分别引入非线性的激活函数,以保证模型具有更强大的表达能力。
激活函数
机器学习篇中,我们介绍过一种简单的激活函数,即 Sigmoid 函数
s i g m o i d ( x ) = 1 1 + e − x \mathrm{sigmoid}(x)=\frac{1}{1+e^{-x}}
sigmoid ( x ) = 1 + e − x 1
Sigmoid函数把全体实数映射到 ( 0 , 1 ) (0,1) ( 0 , 1 ) 区间上。类似的还有 Tanh 函数
t a n h ( x ) = 1 − e − 2 x 1 + e − 2 x \mathrm{tanh}(x)=\frac{1-e^{-2x}}{1+e^{-2x}}
tanh ( x ) = 1 + e − 2 x 1 − e − 2 x
Tanh 函数把全体实数映射到 ( − 1 , 1 ) (-1,1) ( − 1 , 1 ) 区间上。此外还有 ReLU 函数
R e L U ( x ) = max ( x , 0 ) = { x , x > 0 0 , x ⩽ 0 \mathrm{ReLU}(x)=\max(x,0)=\begin{cases}x,&x>0\\0,&x\leqslant 0\end{cases}
ReLU ( x ) = max ( x , 0 ) = { x , 0 , x > 0 x ⩽ 0
目前主流模型使用 ReLU 的原因有:
求导效果好,要么让参数消失,要么让参数通过。
减轻了神经网络的梯度消失问题
ReLU激活函数也有一些变体,比如参数化ReLU函数
p R e L U ( x ) = max ( x , 0 ) + α min ( x , 0 ) , α ∈ R \mathrm{pReLU}(x)=\max(x,0)+\alpha\min(x,0),\quad \alpha\in\mathbb{R}
pReLU ( x ) = max ( x , 0 ) + α min ( x , 0 ) , α ∈ R
下图给出了一些常用激活函数的图像。
[{"url":"/img/Pytorch/p3f2.png","alt":"常用的激活函数图像","title":""}]
代码实现
本节仍然以 FashionMNIST 为数据训练集。在正式编写代码之前,请确保代码文件目录满足上一篇日志中的要求。为了实现多层感知机,需要引入新的 torch.nn 包,并且可以重复利用 softmax 回归中的训练部分代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import torchfrom IPython import displayfrom torch import nnfrom d2l import torch as d2lbatch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) num_inputs, num_outputs, num_hiddens = 784 , 10 , 256 W1 = nn.Parameter(torch.randn( num_inputs, num_hiddens, requires_grad=True ) * 0.01 ) b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True )) W2 = nn.Parameter(torch.randn( num_hiddens, num_outputs, requires_grad=True ) * 0.01 ) b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True )) params = [W1, b1, W2, b2] def relu (X ): a = torch.zeros_like(X) return torch.max (X, a) def net (X ): X = X.reshape((-1 , num_inputs)) H = relu(X@W1 + b1) return (H@W2 + b2) loss = nn.CrossEntropyLoss(reduction='none' ) def accuracy (y_hat, y ): if len (y_hat.shape) > 1 and y_hat.shape[1 ] > 1 : y_hat = y_hat.argmax(axis=1 ) cmp = y_hat.type (y.dtype) == y return float (cmp.type (y.dtype).sum ()) class Accumulator : def __init__ (self, n ): self .data = [0.0 ] * n def add (self, *args ): self .data = [a + float (b) for a, b in zip (self .data, args)] def reset (self ): self .data = [0.0 ] * len (self .data) def __getitem__ (self, idx ): return self .data[idx] def evaluate_accuracy (net, data_iter ): if isinstance (net, torch.nn.Module): net.eval () metric = Accumulator(2 ) with torch.no_grad(): for X, y in data_iter: metric.add(accuracy(net(X), y), y.numel()) return metric[0 ] / metric[1 ] class Animator : def __init__ (self, xlabel=None , ylabel=None , legend=None , xlim=None , ylim=None , xscale='linear' , yscale='linear' , fmts=('-' , 'm--' , 'g-.' , 'r:' ), nrows=1 , ncols=1 , figsize=(3.5 , 2.5 ) ): if legend is None : legend = [] d2l.use_svg_display() self .fig, self .axes = d2l.plt.subplots(nrows, ncols, figsize=figsize) if nrows * ncols == 1 : self .axes = [self .axes, ] self .config_axes = lambda : d2l.set_axes( self .axes[0 ], xlabel, ylabel, xlim, ylim, xscale, yscale, legend) self .X, self .Y, self .fmts = None , None , fmts def add (self, x, y ): if not hasattr (y, "__len__" ): y = [y] n = len (y) if not hasattr (x, "__len__" ): x = [x] * n if not self .X: self .X = [[] for _ in range (n)] if not self .Y: self .Y = [[] for _ in range (n)] for i, (a, b) in enumerate (zip (x, y)): if a is not None and b is not None : self .X[i].append(a) self .Y[i].append(b) self .axes[0 ].cla() for x, y, fmt in zip (self .X, self .Y, self .fmts): self .axes[0 ].plot(x, y, fmt) self .config_axes() display.display(self .fig) display.clear_output(wait=True ) def train_epoch (net, train_iter, loss, updater ): if isinstance (net, torch.nn.Module): net.train() metric = Accumulator(3 ) for X, y in train_iter: y_hat = net(X) l = loss(y_hat, y) if isinstance (updater, torch.optim.Optimizer): updater.zero_grad() l.mean().backward() updater.step() else : l.sum ().backward() updater(X.shape[0 ]) metric.add(float (l.sum ()), accuracy(y_hat, y), y.numel()) return metric[0 ] / metric[2 ], metric[1 ] / metric[2 ] def train (net, train_iter, test_iter, loss, num_epochs, updater ): animator = Animator(xlabel='epoch' , xlim=[1 , num_epochs], ylim=[0.3 , 0.9 ], legend=['train loss' , 'train acc' , 'test acc' ]) for epoch in range (num_epochs): train_metrics = train_epoch(net, train_iter, loss, updater) test_acc = evaluate_accuracy(net, test_iter) animator.add(epoch + 1 , train_metrics + (test_acc,)) train_loss, train_acc = train_metrics assert train_loss < 0.5 , train_loss assert train_acc <= 1 and train_acc > 0.7 , train_acc assert test_acc <= 1 and test_acc > 0.7 , test_acc lr = 0.1 num_epochs = 10 updater = torch.optim.SGD(params, lr=lr) train(net, train_iter, test_iter, loss, num_epochs, updater)
训练模式
误差
一个模型的误差通常指的是其训练误差 和泛化误差 ,前者指的是模型在训练数据上的误差,后者指的是模型在新数据上的误差。当我们有简单的模型和大量的数据时,我们期望泛化误差与训练误差相近。 当我们有更复杂的模型和更少的样本时,我们预计训练误差会下降,但泛化误差会增大。
在机器学习中,我们通常在评估几个候选模型后选择最终的模型,因为不同模型的误差表现往往是不同的。选取模型主要分为两个方面
选取模型的算法本质
选取模型的超参数设置
对于表现不佳的模型,通常会出现欠拟合 或者过拟合 的情形。
验证集
对于给定的训练数据集,为了更好的评估一个模型的好坏,我们通常把原来的数据集分为三部分:训练数据集 ,测试数据集 与验证数据集 (Validation Dataset )。在这里,我借用机器学习篇的一个表格直观展示三者的区别。
区别
训练集
验证集
测试集
经验划分比例
60%
20%
20%
是否参与模型训练
是
否
否
训练对象
普通参数
超参数
-
评估对象
-
超参数
模型表现
验证数据和测试数据是很容易搞混的。因此,除非另有明确说明,接下来的讨论中,所谓的“模型测试”等概念都是基于验证数据所言,而把测试数据视作外界输入的全新数据。
K折交叉验证
当训练数据集的规模较小时,我们甚至可能无法提供足够的数据来构成一个合适的验证集。这个问题的一个流行的解决方案是采用 K K K 折交叉验证 。原始训练数据被分成 K K K 个不重叠的子集。然后执行 K K K 次模型训练和验证,每次在 K − 1 K-1 K − 1 个子集上进行训练,并在剩余的一个子集上进行验证。最后,通过对 K K K 次实验的结果取平均来估计训练和验证误差。
过拟合与欠拟合
模型容量 (capacity )描述了模型拟合各种函数的能力。模型容量的大小直接影响到模型在训练集上的表现以及对新数据的泛化能力。一般而言会有如下情况
低模型容量的模型难以拟合训练数据,即欠拟合
高模型容量的模型可以记住所有训练数据,但可能导致过拟合
下图直观地描述了模型容量(模型复杂度)和欠拟合与过拟合之间的关系。
[{"url":"/img/Pytorch/p3f3.png","alt":"过拟合与欠拟合","title":""}]
因此,选取一个合适大小的模型容量是至关重要的。
权重衰减
L2正则化
对于大部分问题,权重 w \boldsymbol{w} w 的L2范数 通常表示了其对应模型容量的大小。前面我们提到过,模型容量不应该过大,因此我们想要使得权重的L2范数较小,因此有优化目标
w ∗ = arg min w L ( w , b ) + λ 2 ∥ w ∥ 2 \boldsymbol{w}^{*}=\arg\min_{\boldsymbol{w}}L(\boldsymbol{w},b)+\frac{\lambda}{2}\|\boldsymbol{w}\|^2
w ∗ = arg w min L ( w , b ) + 2 λ ∥ w ∥ 2
这种优化方法称作L2正则化 ,其中 λ > 0 \lambda>0 λ > 0 称作正则化参数,是一个超参数。
当 λ \lambda λ 过小时,L2正则化几乎无作用
当 λ \lambda λ 过大时,w ∗ \boldsymbol{w}^{*} w ∗ 趋于 0 \boldsymbol{0} 0
梯度更新
根据梯度的计算公式有
∂ ∂ w ( L ( w , b ) + λ 2 ∥ w ∥ 2 ) = ∂ L ∂ w + λ w \frac{\partial}{\partial\boldsymbol{w}}\left(L(\boldsymbol{w},b)+\frac{\lambda}{2}\|\boldsymbol{w}\|^2\right)=\frac{\partial L}{\partial \boldsymbol{w}}+\lambda\boldsymbol{w}
∂ w ∂ ( L ( w , b ) + 2 λ ∥ w ∥ 2 ) = ∂ w ∂ L + λ w
整理梯度,可以得到梯度下降的迭代式
w t = ( 1 − η λ ) w t − 1 − η ∂ L ∂ w t − 1 , η λ < 1 \boldsymbol{w}_t=(1-\eta\lambda)\boldsymbol{w}_{t-1}-\eta\frac{\partial L}{\partial \boldsymbol{w}_{t-1}},\quad \eta\lambda<1
w t = ( 1 − η λ ) w t − 1 − η ∂ w t − 1 ∂ L , η λ < 1
可以发现,原始权重 w t − 1 \boldsymbol{w}_{t-1} w t − 1 前面的因子 1 − η λ < 1 1-\eta\lambda<1 1 − η λ < 1 ,因此权重在更新过程中不断有变小的趋势。整个过程也因此称作权重衰减 (Weight Decay )。
代码实现
实现L2正则化,需要定义函数 l2_penalty 求一个向量的L2范数。
1 2 def l2_penalty (w ): return torch.sum (w.pow (2 )) / 2
在线性回归模型的基础上,修改训练函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def train (lambd ): w, b = init_params() net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss num_epochs, lr = 100 , 0.003 animator = d2l.Animator(xlabel='epochs' , ylabel='loss' , yscale='log' , xlim=[5 , num_epochs], legend=['train' , 'test' ]) for epoch in range (num_epochs): for X, y in train_iter: l = loss(net(X), y) + lambd * l2_penalty(w) l.sum ().backward() d2l.sgd([w, b], lr, batch_size) if (epoch + 1 ) % 5 == 0 : animator.add(epoch + 1 , (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print ('w的L2范数是:' , torch.norm(w).item())
读者可以自行更改超参数 lambd,观察损失函数图像的变化。
1 2 3 4 5 6 train(lambd=0 ) train(lambd=0.01 ) train(lambd=3 ) train(lambd=100 )
Dropout
原理
丢弃法 (Dropout )本质上是一种正则化方法,其目的在于训练模型使得其对其输入的微小变化不敏感 。例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的,而大部分情况下,人类的确是能够完成这项任务的。
对输入 x \boldsymbol{x} x 加入噪音 ε \boldsymbol{\varepsilon} ε ,得到新的输入 x ′ \boldsymbol{x}^{\prime} x ′ ,而我们希望
E ( x ′ ) = x E(\boldsymbol{x}^{\prime})=\boldsymbol{x}
E ( x ′ ) = x
如果噪音 ε \boldsymbol{\varepsilon} ε 满足 E ( ε ) = 0 E(\boldsymbol{\varepsilon})=0 E ( ε ) = 0 ,那么上式显然成立。而还有一种扰动方法是
x i ′ = { 0 , with probablity 0 < p < 1 x i 1 − p , otherwise x_i^{\prime}=\begin{cases}0,&\text{with probablity } 0<p<1\\\\\dfrac{x_i}{1-p},&\text{otherwise}\end{cases}
x i ′ = ⎩ ⎨ ⎧ 0 , 1 − p x i , with probablity 0 < p < 1 otherwise
不难验证,对于每一个输入的分量 x i x_i x i 都有
E ( x i ′ ) = p ⋅ 0 + ( 1 − p ) ⋅ x i 1 − p = x i E(x_i^{\prime})=p\cdot 0+(1-p)\cdot\dfrac{x_i}{1-p}=x_i
E ( x i ′ ) = p ⋅ 0 + ( 1 − p ) ⋅ 1 − p x i = x i
应用
把Dropout应用到多层感知机上,主要是对每一个隐藏全连接层的输出 运用Dropout,此时会出现部分节点被丢弃 的情况,因此又得名丢弃法 。下图直观地展现了这一点。
[{"url":"/img/Pytorch/p3f4.png","alt":"Dropout与隐藏层","title":""}]
通常,我们在测试时不用Dropout。给定一个训练好的模型和一个新的样本,我们不会丢弃任何节点,因此不需要标准化。然而,Dropout有时会被估计神经网络预测的“不确定性”:如果通过许多不同的暂退法遮盖后得到的预测结果都是一致的,那么我们可以说模型的表现是稳定的。
代码实现
如果调用一些高级API,我们只需在每个全连接层之后添加一个Dropout 层,将Dropout化的概率作为唯一的参数传递给它的构造函数。在训练时,Dropout 层将根据指定的暂退概率随机丢弃上一层的输出。在测试时,Dropout 层仅传递数据。
1 2 3 4 5 6 7 8 9 net = nn.Sequential() net.add(nn.Dense(256 , activation="relu" ), nn.Dropout(dropout1), nn.Dense(256 , activation="relu" ), nn.Dropout(dropout2), nn.Dense(10 )) net.initialize(init.Normal(sigma=0.01 ))
数值稳定性
梯度爆炸和消失
考虑一个 L L L 层的感知机(即神经网络)对应输入 x \boldsymbol{x} x 和输出 o = h ( L ) \boldsymbol{o}=\boldsymbol{h}^{(L)} o = h ( L ) 。对于第 l l l 层而言,其变换 f l f_l f l 以权重矩阵 W ( l ) \boldsymbol{W}^{(l)} W ( l ) 和隐藏变量 h ( l ) \boldsymbol{h}^{(l)} h ( l ) 决定,因此我们可以用一个复合映射关系表示整个神经网络模型
o = ( f L ∘ f L − 1 ∘ ⋯ ∘ f 1 ) ( x ) , h ( l ) = f l ( h ( l − 1 ) ) \boldsymbol{o}=(f_L\circ f_{L-1}\circ\cdots\circ f_1)(\boldsymbol{x}),\quad \boldsymbol{h}^{(l)}=f_l(\boldsymbol{h}^{(l-1)})
o = ( f L ∘ f L − 1 ∘ ⋯ ∘ f 1 ) ( x ) , h ( l ) = f l ( h ( l − 1 ) )
根据链式法则有
∂ o ∂ W ( l ) = ∂ o ∂ h ( L − 1 ) ∂ h ( L − 1 ) ∂ h ( L − 2 ) ⋯ ∂ h ( l + 1 ) ∂ h ( l ) ∂ h ( l ) ∂ W ( l ) , l = 1 , 2 ⋯ , L \frac{\partial \boldsymbol{o}}{\partial \boldsymbol{W}^{(l)}}=\frac{\partial \boldsymbol{o}}{\partial \boldsymbol{h}^{(L-1)}}\frac{\partial \boldsymbol{h}^{(L-1)}}{\partial \boldsymbol{h}^{(L-2)}}\cdots\frac{\partial \boldsymbol{h}^{(l+1)}}{\partial \boldsymbol{h}^{(l)}}\frac{\partial \boldsymbol{h}^{(l)}}{\partial \boldsymbol{W}^{(l)}},\quad l=1,2\cdots,L
∂ W ( l ) ∂ o = ∂ h ( L − 1 ) ∂ o ∂ h ( L − 2 ) ∂ h ( L − 1 ) ⋯ ∂ h ( l ) ∂ h ( l + 1 ) ∂ W ( l ) ∂ h ( l ) , l = 1 , 2 ⋯ , L
上述梯度可以看做 L − l L-l L − l 个矩阵和梯度向量 ∂ h ( l ) ∂ W ( l ) \dfrac{\partial \boldsymbol{h}^{(l)}}{\partial \boldsymbol{W}^{(l)}} ∂ W ( l ) ∂ h ( l ) 的乘积。线性代数的知识表明,这样计算得到的梯度很容易会出现以下几个问题
对于后者,用专业概念描述即梯度爆炸 (Gradient Exploding )和梯度消失 (Gradient Vanishing ),这些问题合称为不稳定梯度问题 ,在某些时候会严重影响梯度下降法和参数的更新。
现在讨论一个多层感知机,为了简单起见省略偏移项 b \boldsymbol{b} b ,可以得到
h ( l ) = f l ( h ( l − 1 ) ) = σ ( W ( l ) h ( l − 1 ) ) \boldsymbol{h}^{(l)}=f_l(\boldsymbol{h}^{(l-1)})=\sigma(\boldsymbol{W}^{(l)}\boldsymbol{h}^{(l-1)})
h ( l ) = f l ( h ( l − 1 ) ) = σ ( W ( l ) h ( l − 1 ) )
记激活函数 σ \sigma σ 的导数(如果可导)为 σ ′ \sigma^{\prime} σ ′ ,则有导数
∂ h ( l ) ∂ h ( l − 1 ) = ∂ h ( l ) ∂ ( W ( l ) h ( l − 1 ) ) ∂ ( W ( l ) h ( l − 1 ) ) ∂ h ( l − 1 ) = d i a g ( σ ′ ( W ( l ) h ( l − 1 ) ) ) ( W ( l ) ) T \frac{\partial \boldsymbol{h}^{(l)}}{\partial \boldsymbol{h}^{(l-1)}}=\frac{\partial \boldsymbol{h}^{(l)}}{\partial (\boldsymbol{W}^{(l)}\boldsymbol{h}^{(l-1)})}\frac{\partial \boldsymbol(\boldsymbol{W}^{(l)}\boldsymbol{h}^{(l-1)})}{\partial \boldsymbol{h}^{(l-1)}}=\mathrm{diag}\big(\sigma^{\prime}(\boldsymbol{W}^{(l)}\boldsymbol{h}^{(l-1)})\big)\big(\boldsymbol{W}^{(l)}\big)^T
∂ h ( l − 1 ) ∂ h ( l ) = ∂ ( W ( l ) h ( l − 1 ) ) ∂ h ( l ) ∂ h ( l − 1 ) ∂ ( W ( l ) h ( l − 1 ) ) = diag ( σ ′ ( W ( l ) h ( l − 1 ) ) ) ( W ( l ) ) T
因此,计算梯度时会包含大量的连乘项
∏ i = l L − 1 d i a g ( σ ′ ( W ( i ) h ( i − 1 ) ) ) ( W ( i ) ) T \prod_{i=l}^{L-1}\mathrm{diag}\big(\sigma^{\prime}(\boldsymbol{W}^{(i)}\boldsymbol{h}^{(i-1)})\big)\big(\boldsymbol{W}^{(i)}\big)^T
i = l ∏ L − 1 diag ( σ ′ ( W ( i ) h ( i − 1 ) ) ) ( W ( i ) ) T
现在来看看不同的激活函数对该连乘项的影响
当 σ = R e L U \sigma=\mathrm{ReLU} σ = ReLU 时,由于 x > 0 x>0 x > 0 时 σ ′ = 1 \sigma^{\prime}=1 σ ′ = 1 ,当 L − l L-l L − l 很大时梯度值可能非常大
当 σ = s i g m o i d \sigma=\mathrm{sigmoid} σ = sigmoid 时,由于 σ ′ = σ ( 1 − σ ) \sigma^{\prime}=\sigma(1-\sigma) σ ′ = σ ( 1 − σ ) ,当 L − l L-l L − l 很大时梯度值可能非常小
因此,当一个神经网络模型很复杂时,很可能会发生梯度爆炸或梯度消失的情况,此时即使不断调整学习率,也通常是无济于事。因此有必要使得训练更加稳定。
Xavier初始化
Xavier 初始化 方法是由 Xavier Glorot 和 Yoshua Bengio 提出的一种参数初始化方法(点此前往原文 )。这里简单介绍一下其参数初始化的主张:保持每一层输入和输出的方差一致,同时在前向和反向传播中平衡信号传播 。
Xavier初始化规定:设某个全连接层的输入维度和输出维度分别为 n i n , n o u t n_{in},n_{out} n in , n o u t ,其对应权重矩阵为 W \boldsymbol{W} W ,那么 W \boldsymbol{W} W 的每个权重元素 w i j w_{ij} w ij 相互独立且视作总体 X X X 的随机样本。总体 X X X 满足
E ( X ) = 0 , D ( X ) = 2 n i n + n o u t E(X)=0,\quad D(X)=\frac{2}{n_{in}+n_{out}}
E ( X ) = 0 , D ( X ) = n in + n o u t 2
特别地,有下述两种常用情况。
当总体服从高斯分布时,即有 w i j ∼ N ( 0 , 2 n i n + n o u t ) w_{ij}\sim N\left(0,\sqrt{\dfrac{2}{n_{in}+n_{out}}}\right) w ij ∼ N ( 0 , n in + n o u t 2 )
当总体服从均匀分布时,即有 w i j ∼ U ( − 6 n i n + n o u t , 6 n i n + n o u t ) w_{ij}\sim U\left(-\sqrt{\dfrac{6}{n_{in}+n_{out}}},\sqrt{\dfrac{6}{n_{in}+n_{out}}}\right) w ij ∼ U ( − n in + n o u t 6 , n in + n o u t 6 )
选取激活函数
选取合适的激活函数对于神经网络训练至关重要,这里给出一张表一总结
特征
R e L U \mathrm{ReLU} ReLU
s i g m o i d \mathrm{sigmoid} sigmoid
t a n h \mathrm{tanh} tanh
s o f t m a x \mathrm{softmax} softmax
值域
[ 0 , + ∞ ) [0,+\infty) [ 0 , + ∞ )
( 0 , 1 ) (0,1) ( 0 , 1 )
( − 1 , 1 ) (-1,1) ( − 1 , 1 )
( 0 , 1 ) (0,1) ( 0 , 1 )
优点
计算简单、收敛快
连续函数、便于求导
奇函数、包含负值情况
可把多个输入归一化
适用层
隐藏层
输出层
隐藏层
输出层
适用问题
深度学习问题
二分类问题
需要输出为负的问题
多分类问题
缺点
可能出现神经元坏死
计算复杂度高、易导致梯度消失
计算复杂度高、易导致梯度消失
一般只适用于输出层