为什么要正则化?

在探讨这个问题之前,我们需要先引入欠拟合和过拟合的概念。

  • 左侧:欠拟合,high bias, $error_{train} \gg error_{dev}$
  • 右侧:过拟合,high variance, $error_{train} \rightarrow 0\%$

在过拟合的时候我们需要引入正则化,来避免过拟合。

这里介绍两种正则化方式:

  • 权重衰减 (weight decay)
  • Dropout

Weight Decay

Weight Decay 的主要思想就是加快参数的更新幅度,也就是说增大损失函数,使得梯度下降的步幅变大。但为什么这样可以防止过拟合?根据奥卡姆剃刀法则,我们倾向于认为,参数越小,网络越不复杂,越不容易过拟合,而实践也证明了这一点。

下面的公式节选自我之前的汇报笔记:

  • 逻辑回归: $\min_{w, b}J(w, b), w\in \mathbb R^{n_x}, b\in \mathbb R$
    • L2 正则化:重定义 $J(w, b)$
      $$
      J(w, b) = \frac{1}{m}\sum_{i = 1}^{m}\mathcal L(\hat y^{(i)}, y^{(i)}) + \frac{\lambda}{2m}||w||_2^2,~||w||_2^2 = \sum_{j = 1}^{n_x}w_j^2 = w^\mathrm T w
      $$
    • L1 正则化:重定义 $J(w, b)$
      $$
      J(w, b) = \frac{1}{m}\sum_{i = 1}^{m}\mathcal L(\hat y^{(i)}, y^{(i)}) + \frac{\lambda}{2m}||w||_1,~||w||_1 = \sum_{j = 1}^{n_x} |w_j|
      $$
  • 神经网络:重定义损失函数:

$$
J(W^{[1]}, b^{[1]},\cdots, W^{[l]}, b^{[l]}) = \frac{1}{m}\sum_{i = 1}^m \mathcal L(\hat y^{(i)}, y^{(i)}) + \frac{\lambda}{2m}\sum_{i = 1}^m ||W^{[l]}||_\mathrm F^2
$$

$$
||W^{[l]}||_\mathrm F^2 = \displaystyle\sum_{i = 1}^{n^{[l]}}\displaystyle\sum_{j=1}^{n^{[l-1]}}(W^{[l]}_{ij})^2
$$

红色的项标出了正则化后迭代中增加的项:

$$
dW^{[l]} = \frac{1}{m} dZ^{[l]} A^{[l-1] T} \color{red}{ + \frac {\lambda}{m}W^{[l]}}
$$

$$
W^{[l]} \leftarrow W^{[l]} - \alpha\frac{1}{m} dZ^{[l]} A^{[l-1] T}\color{red}{ - \alpha \frac {\lambda}{m}W^{[l]}}
$$

下面的公式出自 d2l 教科书,能让我们更好的理解超参数 $\lambda$ 的作用:

对于线性回归的损失函数:

$$
L(\mathbf{w}, b) = \frac{1}{n}\sum_{i=1}^n \frac{1}{2}\left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right)^2
$$

我们将其重定义为:

$$
J(\mathbf{w}, b) = L(\mathbf{w}, b) + \frac{\lambda}{2} \|\mathbf{w}\|^2,
$$

我们来看参数的迭代式发生了什么变化。这是原来的迭代式:

$$
\begin{aligned} \mathbf{w} &\leftarrow \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b) = \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right),\\ b &\leftarrow b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_b l^{(i)}(\mathbf{w}, b) = b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right). \end{aligned}
$$

这是经过 L2 正则化后的迭代式:

$$
\begin{aligned}
\mathbf{w} & \leftarrow \left(1{\color{red}{- \eta\lambda}} \right) \mathbf{w} - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right)
\end{aligned}
$$

Intuitive understanding of L2 Regularization

$$
\lambda \uparrow \Longrightarrow W \downarrow \Longrightarrow \text{ some of the hidden units may loss effect}
$$

$$
\lambda \uparrow \Longrightarrow W \downarrow \Longrightarrow Z = WA + b \downarrow \Longrightarrow \tanh (z) \rightarrow \text {linear} \Longrightarrow \text {simpler network}
$$

$$
\lambda \uparrow \Longrightarrow \text {high variance, overfit} \rightarrow \text{just right} \rightarrow \text {high bias, underfit}
$$

Weight Decay 的代码实现

如果是自己写,那么我们只需要:

def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2

l = loss(net(X), y) + lambd * l2_penalty(w)

如果使用 pytorch,我们可以更好地定义 optimizer:

trainer = torch.optim.SGD([
    {"params": net[0].weight,'weight_decay': wd},
    {"params": net[0].bias}], lr=lr)

Dropout

另一种正则化,也就是防止过拟合的方法就是 dropout。

下面的内容引用自 d2l 教课书:

在探究泛化性之前,我们先来定义一下什么是一个“好”的预测模型? 我们期待“好”的预测模型能在未知的数据上有很好的表现: 经典泛化理论认为,为了缩小训练和测试性能之间的差距,应该以简单的模型为目标。 简单性以较小维度的形式展现, 我们在 4.4节 讨论线性模型的单项式函数时探讨了这一点。 此外,正如我们在 4.5节 中讨论权重衰减(正则化)时看到的那样, 参数的范数也代表了一种有用的简单性度量。

简单性的另一个角度是平滑性,即函数不应该对其输入的微小变化敏感。 例如,当我们对图像进行分类时,我们预计向像素添加一些随机噪声应该是基本无影响的。 1995年,克里斯托弗·毕晓普证明了 具有输入噪声的训练等价于Tikhonov正则化 [Bishop, 1995]。 这项工作用数学证实了“要求函数光滑”和“要求函数对输入的随机噪声具有适应性”之间的联系。

然后在2014年,斯里瓦斯塔瓦等人 [Srivastava et al., 2014] 就如何将毕晓普的想法应用于网络的内部层提出了一个想法: 在训练过程中,他们建议在计算后续层之前向网络的每一层注入噪声。 因为当训练一个有多层的深层网络时,注入噪声只会在输入-输出映射上增强平滑性。

这个想法被称为暂退法(dropout)。 暂退法在前向传播过程中,计算每一内部层的同时注入噪声,这已经成为训练神经网络的常用技术。 这种方法之所以被称为暂退法,因为我们从表面上看是在训练过程中丢弃(drop out)一些神经元。 在整个训练过程的每一次迭代中,标准暂退法包括在计算下一层之前将当前层中的一些节点置零。

需要说明的是,暂退法的原始论文提到了一个关于有性繁殖的类比: 神经网络过拟合与每一层都依赖于前一层激活值相关,称这种情况为“共适应性”。 作者认为,暂退法会破坏共适应性,就像有性生殖会破坏共适应的基因一样。

那么关键的挑战就是如何注入这种噪声。 一种想法是以一种无偏向(unbiased)的方式注入噪声。 这样在固定住其他层时,每一层的期望值等于没有噪音时的值。

在毕晓普的工作中,他将高斯噪声添加到线性模型的输入中。 在每次训练迭代中,他将从均值为零的分布 $\epsilon \sim \mathcal{N}(0,\sigma^2)$ 采样噪声添加到输入 $\mathbf{x}$, 从而产生扰动点 $\mathbf{x}' = \mathbf{x} + \epsilon$, 期望是 $E[\mathbf{x}'] = \mathbf{x}$。

在标准暂退法正则化中,通过按保留(未丢弃)的节点的分数进行规范化来消除每一层的偏差。 换言之,每个中间值 $h$ 以暂退概率 $p$ 由随机变量 $h'$ 替换,如下所示:

$$
\begin{aligned}
h' = \begin{cases}
0 & \text{ 概率为 } p \\
\frac{h}{1-p} & \text{ 其他情况}
\end{cases}
\end{aligned}
$$

根据此模型的设计,其期望值保持不变,即 $E[h'] = h$。

Dropout 的代码实现

纯手写:

def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    # 在本情况中,所有元素都被丢弃
    if dropout == 1:
        return torch.zeros_like(X)
    # 在本情况中,所有元素都被保留
    if dropout == 0:
        return X
    mask = (torch.rand(X.shape) > dropout).float()
    return mask * X / (1.0 - dropout)

pytorch:

net = nn.Sequential(nn.Flatten(),
        nn.Linear(784, 256),
        nn.ReLU(),
        # 在第一个全连接层之后添加一个dropout层
        nn.Dropout(dropout1),
        nn.Linear(256, 256),
        nn.ReLU(),
        # 在第二个全连接层之后添加一个dropout层
        nn.Dropout(dropout2),
        nn.Linear(256, 10))

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights)

注意:对于 pytorch

net.eval() # 不启用 dropout (这两个 method 还有其他作用)
net.train() # 启用 dropout

Reference

最后修改:2022 年 03 月 29 日 08 : 23 PM
真的不买杯奶茶嘛....qwq