神经网络在训练集中的损失值很低,但在测试集中的损失却很大,这就是过拟合(overfitting)现象。过拟合时,神经网络对于特定的特征数据表现极佳,但对于大多数数据表现很差,即模型的泛化性(generalization)差。
在上图中我们可以看到,黄线非常好的拟合了我们给出的样本点,但看起来没有那么合理。蓝线尽管没有完全拟合上样本点,但似乎看起来就是我们要找的预测线。我们假设蓝线就是正确的预测线,那么黄线就是一种过拟合现象。解决过拟合的方法主要有三种(出自吴恩达《机器学习》2022 版):
正则化目前主流的方法有:参数惩罚、Dropout等。接下来将分别对其进行论述。
再次回到上文中的图片,我们为什么会觉得图中蓝线更合理呢?这是因为我们下意识地遵循了奥卡姆剃刀原理(Occam’s Razor),换成中国话来说就是避繁就简(西方人真麻烦,就一个词还得搞个原理)。当一个问题有多种解决办法时,我们选择其中最简单地一个,而往往简单的办法在现实中泛化性最好。参数惩罚就是遵循了奥卡姆剃刀原理,即让神经网络中那些无关紧要的权重值变为零或使其趋向于零,这也就相当于在网络中去除了那条对应的通路。参数惩罚最终目的就是得到一个符合训练集的最简单的网络。其概念公式如下:
ℓ
~
=
ℓ
+
κ
⋅
R
(
W
[
1
]
,
W
[
2
]
,
⋯
,
W
[
a
]
)
\tilde{\ell} = \ell+\kappa\cdot R\left(W^{[1]}, W^{[2]},\cdots,W^{[a]}\right)
ℓ~=ℓ+κ⋅R(W[1],W[2],⋯,W[a]) 其中
ℓ
\ell
ℓ 为损失值,
ℓ
~
\tilde{\ell}
ℓ~ 为新的损失值,
R
R
R 是正则化函数,
W
[
l
]
W^{[l]}
W[l] 是第
l
l
l 层的权重矩阵,
κ
\kappa
κ 是常量参数(常取值为
0.01
0.01
0.01)。原损失值在式中被称为损失项;在损失值中新加入的这部分被称为正则项,它也将参与损失值求梯度的过程。这样权重在反向传播的过程中,不单单得到是原损失值对其的偏导,还有正则项对其的偏导。其中正则项就起到惩罚的作用,它会促使所有权重值向零靠近。这是读者可能有疑问:它对所有的权重进行惩罚,那最后所有的权重不都变成零了?对于网络中那些重要的权重施加惩罚,必然会导致网络的预测结果不理想,那么损失项的值就会上升,损失项的偏导又会把它拉回原来的位置。而对于那些不重要的权重施加惩罚,则不会导致损失值的上升,也就没有被拉回原值的机会,最终就会逐渐向零靠近。
L2 正则化(L2 regularization)即正则函数中使用了 L2 范数(L2 norm)。正则函数的表达式如下:
R
=
1
2
∑
l
=
1
a
∥
W
[
l
]
∥
2
2
R = \frac{1}{2}\sum_{l=1}^a{{\left\| W^{[l]} \right\|_2}^2}
R=21l=1∑a∥
∥W[l]∥
∥22 通过上式我们可以得出:对于某权重
w
w
w,正则项对其偏导数为
κ
w
\kappa w
κw。通过这一点我们可以推理出 L2 正则化的特点是会让不必要的权重靠近零而不是等于零。如下图所示:
w
w
w 是以每次
−
κ
w
-\kappa w
−κw 的递减方式向零靠近,但同时
κ
w
\kappa w
κw 也在不断减小,最终
w
w
w 会无限趋近于零。在下文中我们将与 L1 正则化进行对比,进一步论述 L2 正则化的特点。
与 L2 正则化类似,L1 正则化中使用了 L1 范数。L1 正则函数的表达式如下:
R
=
∑
l
=
1
a
∥
W
[
l
]
∥
1
R = \sum_{l=1}^a{\left\| W^{[l]} \right\|_1}
R=l=1∑a∥
∥W[l]∥
∥1 通过上式我们可以得出:对于某权重
w
w
w,正则项对其偏导数为
κ
\kappa
κ。通过这一点我们可以推理出 L1 正则化的特点是会让不必要的权重等于零。如下图:
w
w
w 是以每次
−
κ
-\kappa
−κ 的递减方式向零靠近,每次的步长相同,最终会使之等于零。接下来我们对 L2 正则化与 L1 正则化进行对比。对比总共有以下几点:
Alex Krizhevsky 在2012年提出了 AlexNet 神经网络,在该网络中首次应用了 Dropout 技术来缓解过拟合现象。AlexNet 也赢得了 2012 ImageNet 竞赛冠军。文章《Improving neural networks by preventing co-adaptation of feature detectors》第一次对该技术进行了介绍。另一篇文章《Dropout: A Simple Way to Prevent Neural Networks from Overfitting》对该技术进行了更为详尽的介绍。笔者参考以上两篇文章完成了本章节的内容。
在论述之前,我们先讲一些其它的技术以便能更好的理解 Dropout 的思想。集成学习是机器学习的一种范式。在集成学习中,我们会同时训练多个模型(被称为弱模型)来解决同一个问题。在应用时,所有弱模型会同时作出预测,而后将所有预测结果组合成一个结果。集成学习相比单一模型会有更准确的预测结果和更高的鲁棒性。
引导聚集(Bootstrap aggregating, Bagging)算法是集成学习的一种。该算法首先从训练集中有放回的抽取多个子训练集,而后用这些子训练集分别训练一个神经网络。而后在应用时,让所有网络对同一个特征同时作出预测,最后选取所有预测结果中占比最高的一个作为最终结果(假设网络是一个分类模型)。实验结果表明,Bagging 算法能够有效抑制过拟合现象。我们从一个直观的角度来解释 Bagging 抑制过拟合的原因。训练过程中,各个网络都会产生过拟合,而由于训练数据集不同,各个网络过拟合的方向会有不同。在最终的对预测结果进行组合的时候,各网络的过拟合会被相互抵消一部分,从而使整体显示出抑制过拟合的效果。
Bagging 算法有抑制过拟合的优势,但训练成本、模型的复杂度极高。因此 Dropout 的作者想在单一网络中实现类似集成学习的方式,这样就可以克服 Bagging 的缺点。Dropout 的大体思想是,在每次训练时各神经元都会以一定的概率被剔除出本次训练,这样对于每次训练的网络,其结构都不尽相同;在应用时,神经元将不会再被剔除,全体神经元都将参与预测。通过以上方式,Dropout 也就将单一网络改造成了类似集成学习的结构,以 “多个网络” 参与训练,而以 “多个网络” 的叠加形态参与应用。
Dropout 在一定程度上也打破了神经元之间相互的依赖关系,使各个神经元能够更好的发挥自己的能力,而不是依赖其它神经元。从这个角度看,网络也就拥有了更高的鲁棒性。
加入 Dropout 的神经元结构如上图所示。接下来,我们通过公式对其进行具体解释。设激活函数的输出值为
y
y
y;设最终输出结果为
y
~
\widetilde{y}
y
;设神经元参与训练不被剔除的概率为
p
p
p;记
B
e
r
n
o
u
n
l
l
i
(
p
)
\mathrm{Bernounlli}(p)
Bernounlli(p) 为随机函数,其特性是以
p
p
p 的概率输出
1
1
1,以
1
−
p
1-p
1−p 的概率输出
0
0
0。那么,加入 Dropout 后的计算公式如下:
y
~
=
y
⋅
B
e
r
n
o
u
n
l
l
i
(
p
)
p
\widetilde{y}=\frac{y \cdot \mathrm{Bernounlli}(p)}{p}
y
=py⋅Bernounlli(p) 接下来对以上公式进行解释。当
B
e
r
n
o
u
n
l
l
i
(
p
)
\mathrm{Bernounlli}(p)
Bernounlli(p) 输出
0
0
0 时,有
y
~
=
0
\widetilde{y}=0
y
=0 和
d
y
~
d
y
=
0
\frac{\mathrm{d}\widetilde{y}}{\mathrm{d}y}=0
dydy
=0,则
∂
l
∂
y
=
∂
l
∂
y
~
d
y
~
d
y
=
0
\frac{\partial l}{\partial y}=\frac{\partial l}{\partial \widetilde{y}}\frac{\mathrm{d}\widetilde{y}}{\mathrm{d}y}=0
∂y∂l=∂y
∂ldydy
=0,则损失值
l
l
l 对于该神经元内所有可训练参数的偏导数值都将为
0
0
0,这也就意味着所有可训练参数得不到更新(不考虑优化器提供的动量)。此时,该神经元正向传播时输出了
0
0
0,反向传播时其所有可训练参数没有得到更新,即该神经元没有参与训练。相应的当
B
e
r
n
o
u
n
l
l
i
(
p
)
\mathrm{Bernounlli}(p)
Bernounlli(p) 输出
1
1
1 时,神经元即参与训练。因此该神经元以
p
p
p 的概率参加训练,以
1
−
p
1-p
1−p 的概率不参加训练。
至于为何要除以
p
p
p,原因如下:由于加入了概率,
y
⋅
B
e
r
n
o
u
n
l
l
i
(
p
)
y \cdot \mathrm{Bernounlli}(p)
y⋅Bernounlli(p) 符合 0-1 分布,其数学期望为
E
(
y
⋅
B
e
r
n
o
u
n
l
l
i
(
p
)
)
=
p
⋅
y
\mathbb{E}\left( y \cdot \mathrm{Bernounlli}(p) \right)=p \cdot y
E(y⋅Bernounlli(p))=p⋅y 。我们知道在结束训练后,神经网路应用时不会再有神经元被剔除,那时该神经元输出值的数学期望为
y
y
y 。因此为了保持训练时和应用时该神经元输出值的数学期望保持一致,就需要在训练时除以
p
p
p,即
E
(
y
⋅
B
e
r
n
o
u
n
l
l
i
(
p
)
p
)
=
y
\mathbb{E}\left( \frac{y \cdot \mathrm{Bernounlli}(p)}{p} \right) = y
E(py⋅Bernounlli(p))=y。当然,你也可以在应用时将输出值乘以
p
p
p 来使数学期望保持一致,实际上这也是最原始的 Dropout 的做法。但现在不推荐这么做,因为通过上文我们了解到 Dropout 其实就是一种在神经网路训练时应用的一种方法,所以我们应当把所有有关 Dropout 的操作都集中到训练阶段,而不是将其分散开。
以下为生成过拟合示意图的代码。
import matplotlib.pyplot as plt
import numpy as np
def f1(x):
return x**2
def f2(x):
a, b, c, d, e = -3.804788961038961, 1, 6.662608225108225, -3.2467532467532467, 0.38893398268398266
return a * x + b * x**2 + c * x**3 + d * x**4 + e * x**5
x = np.array([-0.5, 0.8, 3.1, 5.02])
y = f2(x)
ax = plt.gca()
ax.scatter(x, y, color='black', label='sample points')
x = np.linspace(-1, 5.2, 100)
y = f1(x)
ax.plot(x, y, label='just right')
y = f2(x)
ax.plot(x, y, label='overfit')
ax.legend()
plt.show()
以下为生成 L2 正则化中使用到的图的代码。
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2, 100)
y = x**2
ax = plt.gca()
ax.plot(x, y, label='$y=x^2$')
x = 1.9
xa = [x]
for _ in range(0, 10):
x = x - 0.3*x
xa.append(x)
ya = [x**2 for x in xa]
ax.plot(xa, ya, 'o:', label='Descending path of $x$')
ax.legend()
plt.show()
以下为生成 L1 正则化中使用到的图的代码。
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-1, 2, 100)
y = abs(x)
ax = plt.gca()
ax.plot(x, y, label='$y=|x|$')
x = 1.8
xa = [x]
for _ in range(0, 6):
x = x - 0.3
xa.append(x)
ya = [x for x in xa]
ax.plot(xa, ya, 'o:', label='Descending path of $x$')
ax.legend()
plt.show()