神经网络的正则化

  神经网络在训练集中的损失值很低,但在测试集中的损失却很大,这就是过拟合(overfitting)现象。过拟合时,神经网络对于特定的特征数据表现极佳,但对于大多数数据表现很差,即模型的泛化性(generalization)差。
在这里插入图片描述
  在上图中我们可以看到,黄线非常好的拟合了我们给出的样本点,但看起来没有那么合理。蓝线尽管没有完全拟合上样本点,但似乎看起来就是我们要找的预测线。我们假设蓝线就是正确的预测线,那么黄线就是一种过拟合现象。解决过拟合的方法主要有三种(出自吴恩达《机器学习》2022 版):

  • 增加样本数量。如果我们给出更多的样本点,那么就可以把上图中的黄线逐渐拉向蓝线。
  • 去除不必要的特征,防止其对预测结果产生较大的干扰。比如我们做一个房价预测,显然不需要把明天会不会下雨这个特征引入。
  • 正则化(regularization)。这也是本文主要论述的内容。

  正则化目前主流的方法有:参数惩罚、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 正则化(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=1a 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 正则化的特点。

L1 正则化

  与 L2 正则化类似,L1 正则化中使用了 L1 范数。L1 正则函数的表达式如下:
R = ∑ l = 1 a ∥ W [ l ] ∥ 1 R = \sum_{l=1}^a{\left\| W^{[l]} \right\|_1} R=l=1a W[l] 1  通过上式我们可以得出:对于某权重 w w w,正则项对其偏导数为 κ \kappa κ。通过这一点我们可以推理出 L1 正则化的特点是会让不必要的权重等于零。如下图:
在这里插入图片描述
   w w w 是以每次 − κ -\kappa κ 的递减方式向零靠近,每次的步长相同,最终会使之等于零。接下来我们对 L2 正则化与 L1 正则化进行对比。对比总共有以下几点:

  • 计算效率。L2 正则化中的函数是全域可导的,而 L1 正则化中的函数含有绝对值,相比之下 L2 正则化求导计算效率更高。(不必深究,这可能牵涉到自动求导的相关理论)
  • 稀疏性。稀疏性是指一个矩阵或向量中只有少数元素是非零的。前面提到 L1 正则化会将大部分非必要的权重值置零,L2 正则化会将大部分非必要的权重值无限接近零。所以使用 L1 正则化的网络的预测结果稀疏性较强。

Dropout

  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 在一定程度上也打破了神经元之间相互的依赖关系,使各个神经元能够更好的发挥自己的能力,而不是依赖其它神经元。从这个角度看,网络也就拥有了更高的鲁棒性。

threshold
weight 1
weight 2
weight i
weight n
sum
Activation
function
times
output
input 1
input 2
input i
input n
Bernounlli

  加入 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 1p 的概率输出 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 =pyBernounlli(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 yl=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 1p 的概率不参加训练。
  至于为何要除以 p p p,原因如下:由于加入了概率, y ⋅ B e r n o u n l l i ( p ) y \cdot \mathrm{Bernounlli}(p) yBernounlli(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(yBernounlli(p))=py 。我们知道在结束训练后,神经网路应用时不会再有神经元被剔除,那时该神经元输出值的数学期望为 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(pyBernounlli(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()
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值