> For the complete documentation index, see [llms.txt](https://imhuay.gitbook.io/studies/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://imhuay.gitbook.io/studies/notes/_archives/2022/05/guo-ni-he-yu-zheng-ze-hua.md).

# 过拟合与正则化

![last modify](https://img.shields.io/static/v1?label=last%20modify\&message=2022-10-13%2001%3A56%3A19\&color=yellowgreen\&style=flat-square)

```
```

## 过拟合与欠拟合

### 过拟合

**现象**

* **训练集**效果很好，但是**验证集**很差，这种现象称为过拟合，表现为**高方差**。

#### 常见解决方法

* 训练数据不足
  * 数据增强
    * NLP-EDA
    * CV-切割、裁剪、旋转
    * Mixup
  * 对抗训练
* 训练数据中存在噪声：
  * 交叉验证
  * 集成学习（Bagging）
* 模型复杂度较高：
  * 正则化项
  * Dropout
  * Early Stoping
  * 降低模型复杂度
    * 减少模型参数
    * 使用简单模型

### 欠拟合

**现象**

* **训练集**和**验证集**的效果都很差，这种现象称为欠拟合，表现为**高偏差**；

#### 常见解决方法

* 特征工程
  * FM、FFM
* 集成学习（Boosting）
* 提高模型复杂度
  * 增加模型参数
  * 使用复杂模型

### 参考

* [Bagging和Boosting的区别（面试准备） - Earendil - 博客园](https://www.cnblogs.com/earendil/p/8872001.html)
* [NLP-Interview-Notes/过拟合和欠拟合.md at main · km1994/NLP-Interview-Notes](https://github.com/km1994/NLP-Interview-Notes/blob/main/BasicAlgorithm/%E8%BF%87%E6%8B%9F%E5%90%88%E5%92%8C%E6%AC%A0%E6%8B%9F%E5%90%88.md)

## 常见正则化方法

### 数据增强

* 简单数据增强
  * NLP（EDA：增删改、替换）
  * 图像（切割、裁剪、旋转等）
* 数据融合
  * Mixup

### 为损失函数添加正则化项

* 常见的正则化项有 L1、L2 正则项；

**参考**

* L1、L2 正则化的特点与区别：[NLP-Interview-Notes/正则化.md at main · km1994/NLP-Interview-Notes](https://github.com/km1994/NLP-Interview-Notes/blob/main/BasicAlgorithm/%E6%AD%A3%E5%88%99%E5%8C%96.md)
* [机器学习中正则化项L1和L2的直观理解\_阿拉丁吃米粉的博客-CSDN博客](https://blog.csdn.net/jinping_shi/article/details/52433975)

#### L1 正则化

#### L2 正则化

### 提前结束训练（Early Stopping）

* 当模型在验证集上的性能开始下降时，提前结束训练；
* 一般会配合交叉验证来使用；

**参考**

* [【关于 早停法 EarlyStopping 】那些你不知道的事](https://mp.weixin.qq.com/s?__biz=MzAxMTU5Njg4NQ==\&mid=2247488608\&idx=1\&sn=5c484d2d0177dc968265ca1f3c9221e0\&scene=21#wechat_redirect)

### Dropout

**作用**

* 提升泛化能力，减少过拟合；

**原理**

* Dropout 提供了一种廉价的 Bagging 集成近似（模型平均）；

**思想**

* 遗传算法，通过随机变异（随机删除神经元），来促使整个种群的进化；

#### 常见问题

**Dropout 在训练和测试时有什么区别？为什么？**

* 训练时，经过 Dropout 的输出值会乘以 $\frac{1}{1-p}$；测试时不会。
* 经过 Dropout 后，输入 `x` 的期望输出将变为 `p*0 + (1-p)*x = (1-p)x`（`p` 的可能变为 0，`1-p` 的可能保持不变）；
* 为了还原未经过 Dropout 的期望值，故需要乘以 $\frac{1}{1-p}$

**为什么 Dropout 能防止过拟合？**

* 直观上，Dropout 会使部分神经元失活，减小了模型容量，从而降低了模型的拟合能力；
* 宏观上，Dropout 提供了一种廉价的 Bagging 集成方法（共享权重）；
* 隐藏单元经过 Dropout 后，必须学习与不同采样的神经元合作，使得神经元具有更强的健壮性（减少神经元之间复杂的共适应关系）；

#### PyTorch 实现

* 【训练阶段】前向传播时，对每个神经元以概率 `p` 失活（即乘以 `0.`），而其他未失活的单元则乘以 `1/(1-p)`（放大）
* 【测试阶段】使 dropout 失效，即正常使用所有神经元；

```python
class Dropout(nn.Module):

    def __init__(self, p):
        super().__init__()
        self.p = p  # 以 p 的概率丢弃

    def forward(self, x):
        if not self.training:
            return x
        
        mask = (torch.rand(x.shape) > self.p).float()
        return x * mask / (1.0 - self.p)
```

### R-Drop

> [\[2106.14448\] R-Drop: Regularized Dropout for Neural Networks](https://arxiv.org/abs/2106.14448)

**动机 & 作法**

* 尝试解决 Dropout 在训练与预测时使用不一致的问题；
* **Dropout 本身不是已经尝试解决了这个不一致问题吗？它的解决方案有什么问题？**
  * Dropout 通过缩放神经元的输出值来缓解训练与预测时不一致的影响。Dropout 的本意是为了得到一个“模型平均”的结果，而这种通过缩放来还原实际上是一种“权重平均”（见 Dropout 的推导），这两者未必等价；
  * 具体来说，Dropout 的正确使用方式应该是预测时打开 Dropout，然后计算多次预测的平均值作为结果；但实际并不是这样使用的。
* **R-Drop 是怎么解决这个问题的？**
  * 通过对同一样本 Dropout 两次，然后加入 KL 散度来保持不同 Dropout 下预测结果的一致性；
* **KL 散度损失是怎么保证预测一致性的？**
  * 交叉熵损失只关注目标类的得分，非目标类的得分不影响最终 loss；相当于训练目标是 “**不同 Dropout 下目标类的得分都大于非目标类的得分**”。
  * 举例来说 `[0.5, 0.2, 0.3]`、`[0.5, 0.3, 0.2]` 与 `[1, 0, 0]` 的交叉熵损失是一样的，都是 `-log(0.5)`，非目标类的 `0.2` 和 `0.3` 都没有起作用；
  * KL 散度则会关注每一个类别的得分，相当于训练目标是“**不同 Dropout 下每个类别的得分一致**”
  * 就上例来说，计算 `[0.5, 0.2, 0.3]`、`[0.5, 0.3, 0.2]` 与 `[1, 0, 0]` 的 KL 散度都会产生非零损失；

#### PyTorch 实现

```python
class RDrop(nn.Module):

    def __init__(self, encoder, kl_alpha=1.0):
        super().__init__()

        self.encoder = encoder
        self.kl_alpha = kl_alpha
        self.ce = nn.CrossEntropyLoss()
        self.kl = nn.KLDivLoss()

    def forward(self, x, labels):
        logits1 = self.encoder(x)
        logits2 = self.encoder(x)
        ce_loss = (self.ce(logits1, labels) + self.ce(logits2, labels)) / 2
        kl_loss1 = self.kl(F.log_softmax(logits1, dim=-1), F.softmax(logits2, dim=-1))
        kl_loss2 = self.kl(F.log_softmax(logits2, dim=-1), F.softmax(logits1, dim=-1))
        return ce_loss + self.kl_alpha * (kl_loss1 + kl_loss2) / 2
```

**参考**

* [又是Dropout两次！这次它做到了有监督任务的SOTA - 苏剑林](https://kexue.fm/archives/8496)

### 各种 Normalization

#### 参考

* [详解深度学习中的 Normalization，BN/LN/WN - 知乎](https://zhuanlan.zhihu.com/p/33173246)

#### Batch Normalization

`per channel per batch`

**使用场景**：CV

**前向过程**

$$
\begin{aligned} \mu &= \frac{1}{m} \sum\_{i=1}^m x\_i &//&\text{batch mean} \ \sigma^2 &= \frac{1}{m} \sum\_{i=1}^m (x\_i-\mu)^2 &//&\text{batch variance} \ \hat{x}\_i &= \frac{x\_i - \mu}{\sqrt{\sigma^2 + \epsilon}} &//&\text{normalization} \ y\_i &= \gamma \hat{x}\_i + \beta &//&\text{scale and shift} \end{aligned}
$$

**PyTorch实现**

```python
def batch_norm(x, eps=1e-5):
    """x: [batch_size, time_step, channel]"""
    C = x.shape[-1]
    mean = torch.mean(x, dim=(0, 1), keepdim=True)  # [1, 1, C]
    std = torch.std(x, dim=(0, 1), unbiased=False, keepdim=True)  # [1, 1, C]
    gamma = torch.nn.Parameter(torch.empty(C))
    beta = torch.nn.Parameter(torch.empty(C))
    output = gamma * (x - mean) / (std + eps) + beta
    return output
```

**参考**

* [【深度学习】深入理解Batch Normalization批标准化 - 郭耀华 - 博客园](https://www.cnblogs.com/guoyaohua/p/8724433.html)
* [Batch Normalization的通俗解释 - 知乎](https://zhuanlan.zhihu.com/p/54073204)

#### Layer Normalization

`per sample per layer`

**使用场景**：NLP

**前向过程**

```python
```

#### Instance Normalization

`per sample per channel`

**作用**

**使用场景**：CV 风格迁移

#### Group Normalization

`per sample per group`

#### Weight Normalization

#### 常见问题

**BN 与 LN 的区别**

* BN 在 batch 维度为归一；
* LN 在 feature 维度做归一；

**为什么 BN 一般不用于 NLP ？**

### 对抗训练
