Regularization

正则化(Regularization) #

Regularization 是一种用于防止机器学习模型过拟合(overfitting)的技术。通过在损失函数中添加正则化项(regularization term)等技术,限制模型的复杂度,从而提高模型的泛化能力(generalization ability)

Note: 正则化通常只在模型的训练(training)阶段生效,不会直接影响 验证(validation)阶段推理(inference)阶段

参数范数惩罚(Parameter Norm Penalties) #

许多正则化方法都是通过向目标函数 \(J\) 添加参数范数惩罚 \(\Omega(\theta)\) 来限制模型(例如神经网络、线性回归或逻辑回归)的容量。我们将正则化的目标函数表示为 \(\tilde{J}\)

\[ \tilde{J}(\theta;X,y) = J(\theta;X,y) + \lambda\Omega(\theta) \\ \]

其中 \(\lambda \in [0,\infty)\) 是一个超参数,它加权范数惩罚项 \(\Omega\) 相对于标准目标函数 \(J\) 的相对贡献。将 \(\lambda\) 设置为 0 会导致无正则化,使模型更关注数据拟合。较大的 \(\lambda\) 值对应更多的正则化,从而限制模型复杂度。

当我们的训练算法最小化正则化目标函数 \(\tilde{J}\) 时,它将同时降低训练数据上的原始目标 \(J\) 和参数 \(\theta\) (或参数的某些子集)大小的某些度量。参数范数 \(\Omega\) 的不同选择可能导致不同的解决方案被优先考虑。


直观理解参数范数正则化的作用 #

  • 抑制模型复杂度,防止过拟合

    • 参数范数正则化通过惩罚参数,限制了参数 \(\theta\) 的大小,促使模型学习更小的权重。
      • 大权重通常意味着模型对输入数据的小变化非常敏感,容易过拟合。
      • 小权重会使模型输出更加平滑,对噪声不敏感,泛化能力更强。
    • 这使得模型在高噪声或复杂数据下不会对数据点过度拟合。
  • 大权重的危害:如果某个权重 \(\theta_i\) 特别大,模型输出就会对输入 \(x_i\) 微小变化极为敏感,训练数据的噪声会被放大,导致模型记住了训练集中的噪声(过拟合)

  • 正则化后:通过惩罚项,正则化会迫使所有权重尽量小,让模型输出更平滑,对噪声更鲁棒。


L2 正则化(L2 Regularization) #

L2 正则化(Ridge 正则化)通过在损失函数中添加权重平方和的惩罚项,限制模型参数的大小,降低模型复杂度。公式如下: \[ J(\theta) = Loss(\theta) + \lambda\lVert \theta \rVert_{2}^{2} = Loss(\theta) + \frac{\lambda}{2} \sum_{i=1}^n \theta_i^2 \]

  • \(\lambda\) :正则化强度,权衡损失函数与正则化项的重要性。
  • \(\sum_{i=1}^n \theta_i^2\) :模型权重的 L2 范数(欧几里得范数)。
  • 正则化项前的 \(\frac{1}{2}\) 是为了后续求导方便。

优化参数 \(\theta\) 时,我们对目标函数 \(J(\theta) \) 关于 \(\theta\) 求偏导:

\[ \frac{\partial J(\theta)}{\partial \theta} = \nabla J(\theta) + \lambda \theta \]

之后将求导结果用于梯度下降法的参数更新规则:

\[ \theta := \theta - \alpha \left( \nabla J(\theta) + \lambda \theta \right) \]
  • L2 正则化的优点
    1. 平滑性更强: L2 正则化引入了一个约束,使得参数向量 \(\theta\) 不会无限增大,最终可以提高模型在测试集上的表现。
      • 普通模型的参数 \(\theta\) 没有任何约束,模型可能会将一些特征的权重学得非常大,导致输出对输入变化非常敏感。
      • L2 正则化的模型参数 \(\theta\) 约束在一个较小的范围内,所有权重都会趋于较小的值,模型的输出曲线更加平滑
    2. 解决多重共线性问题 降低模型的方差
      • 当输入特征高度相关时(多重共线性),普通的最小二乘法会导致模型参数的方差非常大。
      • 参数范数正则化通过惩罚参数的大小,有效降低了模型的方差,方差大,意味着模型过拟合。虽然会略微增加模型的偏差(对训练集拟合得稍差),但整体的泛化误差(训练集与测试集之间的误差)会降低。
  • L2 正则化的缺点:
    • 无法实现特征选择: L2 正则化会将权重缩小到接近 0,但不完全为 0,因此模型仍然保留所有特征。
  • L2 正则化的适用场景:
    • 如果需要提升模型的 鲁棒性 和 泛化能力,并且数据是 低维非稀疏数据,优先选择 L2 正则化。
  • L2 正则化代码实现
    # <--- From scratch --->
    import numpy as np
    
    # 损失函数:均方误差(MSE) + L2 正则化
    def compute_loss(X, y, theta, bias, lambda_reg):
        n_samples = X.shape[0]
        predictions = np.dot(X, theta) + bias
        mse_loss = (1 / (2 * n_samples)) * np.sum((predictions - y) ** 2)
        l2_penalty = (lambda_reg / 2) * np.sum(theta ** 2)  # 正则化项
        return mse_loss + l2_penalty
    
    # 梯度更新
    def compute_gradients(X, y, theta, bias, lambda_reg):
        n_samples = X.shape[0]
        predictions = np.dot(X, theta) + bias
        error = predictions - y
    
        # 梯度计算 包含 L2 惩罚
        d_theta = (1 / n_samples) * np.dot(X.T, error) + lambda_reg * theta
        d_bias = (1 / n_samples) * np.sum(error)
    
        return d_theta, d_bias
    

L1 正则化(L1 Regularization) #

L1 正则化(Lasso 正则化)通过在损失函数中添加权重绝对值和的惩罚项,限制模型参数的大小,同时可以实现特征选择的作用。公式如下: \[ J(\theta) = Loss(\theta) + \lambda\lVert \theta \rVert_{1} = Loss(\theta) + \lambda \sum_{i=1}^n |\theta_i| \]

  • \(\lambda\) :正则化强度,权衡损失函数与正则化项的重要性。
  • \(\sum_{i=1}^n |\theta_i|\) :模型权重的 L1 范数(曼哈顿范数)。

优化参数 \(\theta\) 时,我们对目标函数 \(J(\theta) \) 关于 \(\theta\) 求偏导:

\[ \frac{\partial J(\theta)}{\partial \theta_i} = \nabla J(\theta_i) + \lambda \cdot \text{sign}(\theta_i) \]

其中 \(\text{sign}(\theta_i)\) 是符号函数,定义为: \[ \text{sign}(\theta_i) = \begin{cases} 1, & \text{if } \theta_i > 0 \\ -1, & \text{if } \theta_i < 0 \\ 0, & \text{if } \theta_i = 0 \end{cases} \]

参数更新规则(梯度下降法)为: \[ \theta := \theta - \alpha \left( \nabla J(\theta) + \lambda \cdot \text{sign}(\theta) \right) \]

  • L1 正则化的优点
    1. 特征选择功能:L1 正则化会将部分不重要的特征权重缩小到 0,从而直接移除这些特征。对于高维稀疏数据(如文本分类、基因数据分析)效果显著。
    2. 模型可解释性: 由于部分权重为 0,模型变得更加简洁,方便解读哪些特征对预测结果最重要。
  • L1 正则化的缺点:
    1. 参数稀疏性可能损失重要信息: 对于相关性较高的特征,L1 可能随机选择部分特征置零,而丢失其他有价值的信息。
    2. 优化复杂性: L1 正则化的目标函数由于绝对值的非连续性,可能在优化时收敛较慢
  • L1 正则化的适用场景:
    1. 高维稀疏数据: 如文本分类或图像处理中的稀疏特征。
    2. 需要特征选择: 适合在数据预处理阶段对重要特征进行筛选
  • L1 正则化代码实现
    # <--- From scratch --->
    import numpy as np
    
    # 损失函数:均方误差(MSE) + L1 正则化
    def compute_loss(X, y, theta, bias, lambda_reg):
        n_samples = X.shape[0]
        predictions = np.dot(X, theta) + bias
        mse_loss = (1 / (2 * n_samples)) * np.sum((predictions - y) ** 2)
        l1_penalty = lambda_reg * np.sum(np.abs(theta))  # 正则化项(L1 范数)
        return mse_loss + l1_penalty
    
    # 梯度更新
    def compute_gradients(X, y, theta, bias, lambda_reg):
        n_samples = X.shape[0]
        predictions = np.dot(X, theta) + bias
        error = predictions - y
    
        # 梯度计算 包含 L1 惩罚
        d_theta = (1 / n_samples) * np.dot(X.T, error) + lambda_reg * np.sign(theta)
        d_bias = (1 / n_samples) * np.sum(error)
    
        return d_theta, d_bias
    

数据增强(Dataset Augmentation) #

数据增强是一种通过对原始数据进行变换或扩展,生成额外的训练样本的方法,旨在提升模型的鲁棒性和泛化能力。它在训练数据不足、分布偏差或过拟合等问题上尤为有效,特别是在深度学习任务(如计算机视觉和自然语言处理)中,数据增强常被用作关键的预处理技术。数据增强的核心目的有:

  1. 增加数据多样性:在保持原始数据标签不变的前提下,生成不同的样本,模拟现实中数据的潜在变化
  2. 减少过拟合风险:通过增加训练样本,降低模型对原始训练数据的记忆,提高模型的泛化性能。
  3. 提升模型鲁棒性:让模型更好地适应数据的扰动和不确定性,例如噪声或旋转等变化。

图像数据增强 #

图像数据增强方法通常直接作用于图像像素,以生成不同变体。常见技术包括:

  • 几何变换:旋转(Rotation),翻转(Flip),缩放(Scaling),平移(Translation),剪裁(Cropping)。
  • 颜色变换:亮度调整(Brightness Adjustment),对比度调整(Contrast Adjustment),色调调整(Hue Adjustment),饱和度调整(Saturation Adjustment)。
  • 添加噪声:高斯噪声(Gaussian Noise),盐噪声与椒噪声(Salt-and-Pepper Noise)。

文本数据增强 #

文本数据的增强方法相较于图像更加复杂,因为文本的语法和语义需要保持一致。常用技术包括:

  • 同义词替换(Synonym Replacement):替换文本中部分单词为同义词,例如将 “happy” 替换为 “joyful”。
  • 随机插入(Random Insertion):向句子中随机插入同义词或相关词汇。
  • 随机删除(Random Deletion):随机删除句子中的某些单词。
  • 回译(Back Translation):将句子翻译成另一种语言,再翻译回原语言,生成语义一致但表述不同的句子。
  • 句法变换(Syntactic Transformations):改变句子的语法结构,例如主动语态转被动语态。

时间序列数据增强 #

针对时间序列数据(如音频信号、传感器数据、股票数据等),常用方法包括:

  • 随机噪声(Random Noise):在时间序列信号中加入少量随机噪声。
  • 时间偏移(Time Shifting):将时间序列信号整体向前或向后移动。
  • 插值与采样(Interpolation and Sampling):对原始序列进行上下采样或插值。
  • 窗口切片(Window Slicing):随机选择时间序列的一部分作为新样本。

噪声注入(Noise Injection) #

Noise Injection 是一种在机器学习模型训练过程中有意加入噪声的技术,旨在增强模型的鲁棒性、泛化能力以及避免过拟合。噪声注入的核心思想是通过扰动输入数据、模型参数、或隐藏层的激活值,使模型更能适应训练数据中的噪声和不确定性,从而提高其对真实数据的适应能力。

常见的噪声注入方法 #

  1. 输入数据中的噪声注入:在训练过程中,直接对输入数据进行扰动。例如:
    • 对数值型输入数据添加随机噪声\[ x' = x + \mathcal{N}(0, \sigma^2) \] 其中, \(\mathcal{N}(0, \sigma^2)\) 是均值为 0,方差为 \(\sigma^2\) 的高斯噪声。
    • 对图像数据应用随机变换,例如:
      • 随机裁剪、旋转、翻转、亮度调节等。
      • 使用像素级别的随机扰动(例如,加盐噪声或椒盐噪声)。
    • 作用
      • 使模型更能适应输入数据的多样性,降低过拟合风险。
      • 提升对输入数据中的小扰动(如测量误差)的鲁棒性
  2. 参数中的噪声注入:对模型的参数(例如权重)添加随机噪声:
    • 对参数添加噪声:在每次梯度更新后,对模型的权重或偏置值添加噪声: \[ W' = W + \mathcal{N}(0, \sigma^2) \]
    • 作用
      • 防止模型权重过度依赖某些特定的参数值
      • 增强模型对权重初始化的鲁棒性。

Note: 参数中的噪声注入 直接作用在模型的参数上,通常是权重更新过程中加入的随机扰动,改变了参数的优化路径,使得训练过程更加稳健。

隐藏层中的噪声注入,噪声被直接加入到隐藏层的激活值(神经元的输出)中,会使得每一次前向传播的输出不同,但不改变权重值本身,从而增强模型对输入数据的变化或中间表示不确定性的鲁棒性。

  1. 隐藏层中的噪声注入:在神经网络的隐藏层中对激活值进行随机扰动
    • Dropout 是一种常用的噪声注入方法,在每次前向传播时,随机将隐藏层的某些神经元置为 0(断开连接),以防止神经网络对特定神经元的过度依赖。详见 Dropout
    • 添加噪声:在隐藏层激活值中加入高斯噪声: \[ h{\prime} = h + \mathcal{N}(0, \sigma^2) \]
    • 作用
      • 加入随机噪声可以模拟训练中的不确定性,使模型更加稳健。
  2. 标签中的噪声注入: 有时也会对标签进行扰动(Label Smoothing):
    • 将离散标签的 “硬边界” 分布转化为 “软边界” 分布,例如,将类别标签从 [0, 1] 转变为 [0.1, 0.9]
    • 作用
      • 防止模型对训练数据中的标签过于自信(即减少过拟合)。
      • 提升模型对噪声标签的鲁棒性。

正则化在神经网络中的应用 #


Weight Decay #

Weight Decay 是一种用于正则化机器学习模型的技术,其核心思想是通过向损失函数中添加正则化项来约束模型权重的大小,从而防止过拟合。它的本质是 L2 正则化 的一种实现方式。Weight Decay 的目标是减少模型权重过大的情况,这样可以使模型更加简单、泛化能力更强。通过在优化过程中对权重进行衰减(decay),限制其无限增大。引入 Weight Decay 后,损失函数为: \[ L_{\text{total}}(\theta) = \frac{1}{N} \sum_{i=1}^N \mathcal{L}(f(x_i; \theta), y_i) + \lambda \|\theta\|_2^2 \] 其中:

  • \(\mathcal{L}(\cdot)\) :是预测值和真实值之间的损失(例如均方误差或交叉熵)。
  • \(\|\theta\|_{2}^2 = \sum_{j=1}^m \theta_j^2\) :表示所有权重参数的平方和(即 L2 范数的平方)。
  • \(\lambda\) :正则化系数(Weight Decay 参数),控制权重的惩罚力度。

在优化过程中,Weight Decay 实现了以下更新规则\[ \theta_{t+1} = \theta_t - \eta \frac{\partial L}{\partial \theta_t} - \eta \lambda \theta_t \]

Note: Weight Decay 是 L2 正则化的具体实现,但在优化器实现时,它直接作用于梯度更新规则,而不需要显式地将正则化项加到损失函数中。两者的效果基本等价,但 Weight Decay 的实现方式更简洁,适合深度学习优化器的需求。

  • Weight Decay 代码实现
    # <--- From Pytorch --->
    import torch
    import torch.nn as nn
    import torch.optim as optim
    # 模型定义
    model = nn.Linear(10, 1)
    # 损失函数
    criterion = nn.MSELoss()
    # 优化器
    optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=0.01)
    

Early Stopping #

Early Stopping 是一种防止模型过拟合的正则化方法。通过在验证集性能不再提升时提前终止训练,可以有效减少模型的过拟合并提高泛化能力。它的工作原理是:

  1. 分割数据集:将数据集划分为训练集、验证集 和测试集。
  2. 训练监控:在训练过程中,同时监控训练损失和验证损失。通常,随着训练的进行:
    • 训练损失持续降低。
    • 验证损失先降低,随后可能开始上升(表示过拟合)。
  3. 停止条件
    • 当验证损失连续多个 epoch 不再降低(或验证性能不再提升)时,提前终止训练。
    • 模型停止在验证性能最佳的那一刻,而不是继续训练到指定的最大 epoch 数。

关键步骤

  1. 监控指标:通常监控验证损失(如均方误差、交叉熵损失等)。
  2. 耐心(Patience)机制:如果验证损失在设定的耐心步数(patience steps)内没有改善,停止训练。
  3. 保存最佳模型:在每次验证性能提升时保存当前模型的参数(checkpoint)。训练结束时,恢复到性能最佳时的模型
  • Weight Decay 代码实现
    # <--- From Pytorch --->
    import torch
    
    class Early_Stopping:
        def __init__(self, patience=5, delta=0.0, path='checkpoint.pt', verbose=False):
            """
            参数:
            - patience: 允许验证损失不降低的连续 epoch 数
            - delta: 最小的验证损失降低幅度,避免浮动引起的误判
            - path: 保存最佳模型的文件路径
            - verbose: 是否输出日志信息
            """
            self.patience = patience
            self.delta = delta
            self.path = path
            self.verbose = verbose
            self.counter = 0
            self.best_loss = float('inf')
            self.early_stop = False
    
        def __call__(self, val_loss, model):
            if val_loss < self.best_loss - self.delta:
                self.best_loss = val_loss
                self.counter = 0
                self.save_checkpoint(val_loss, model)
            else:
                self.count += 1
                if self.counter >= self.patience:
                    self.early_stop = True
    
        def save_checkpoint(self, val_loss, model):
            """保存当前最佳模型"""
            if self.verbose:
                print(f"Validation loss improved to {val_loss:.4f}. Saving model...")
            torch.save(model.state_dict(), self.path)
    
    
    model = SimpleModel(input_dim=10)
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr=0.01)
    # 实例化 Early Stopping
    early_stopping = EarlyStopping(patience=5, verbose=True, path='best_model.pt')
    
    # 训练循环
    num_epochs = 100
    for epoch in range(num_epochs):
        # 训练阶段
        model.train()
        for batch_X, batch_y in train_loader:
            optimizer.zero_grad()
            outputs = model(batch_X)
            loss = criterion(outputs, batch_y)
            loss.backward()
            optimizer.step()
        # 验证阶段
        model.eval()
        val_loss = 0.0
        with torch.no_grad():
            for batch_X, batch_y in val_loader:
                outputs = model(batch_X)
                loss = criterion(outputs, batch_y)
                val_loss += loss.item()
    
        val_loss /= len(val_loader)
        # 调用 Early Stopping
        early_stopping(val_loss, model)
    
        # 判断是否停止训练
        if early_stopping.early_stop:
            print("Early stopping triggered. Restoring the best model...")
            model.load_state_dict(torch.load('best_model.pt'))
            break
    

Dropout #

Dropout 是一种广泛用于深度学习的正则化技术,旨在通过减少神经网络的过拟合来提高模型的泛化能力。它的核心思想是在训练过程中随机地“丢弃”一些神经元,使得网络在每一次前向传播和反向传播中都使用不同的结构。这种随机性强迫网络在不同的子网络上训练,从而增强其鲁棒性。

  1. 训练阶段
    • 对于每一层的每个神经元,按照一个固定的概率 \(p\) (通常在 0.5 左右)随机将其“丢弃”。
    • 被丢弃的神经元不会参与前向传播和反向传播,其输出被设置为 0。
    • 未被丢弃的神经元的输出会被放大为 \(\frac{1}{1-p}\) 倍,以保持激活值的期望不变。
  2. 测试阶段
    • Dropout 不会应用在测试阶段,因为网络需要全量的神经元进行预测。
    • 为了与训练阶段保持一致,神经元的输出按比例缩放,即保持其原始激活值。

为什么 Dropout 有效?

  1. 防止过拟合
    • Dropout 强迫网络不会过分依赖某些特定的神经元或路径,而是学会利用多个不同的路径。
    • 通过这种方式,网络在面对噪声或新数据时更具鲁棒性。
  2. 类似于模型集成
    • 训练时,Dropout 在每次训练的前向传播中随机丢弃一些神经元,等价于在每次迭代中训练一个从完整网络中抽取的“子网络”。不同的“子网络”共享参数并被优化,最终可以看作是训练了大量的子模型
      • 每次迭代训练的是网络的一个随机子集,相当于训练了一些弱模型。
    • 测试时,因为 Dropout 在训练时让模型学到了很多不同的权重组合和特征表示,所以即使在测试时每次计算时都用到了完整的神经元结构,最终的预测也会综合了所有子网络可能学习到的信息通过对所有神经元的输出进行缩放(保留激活值的期望不变),等价于对所有训练过的子网络的预测进行平均
      • 训练时,丢弃掉的神经元会导致模型在每次训练时只用到部分神经元,因此每个神经元的输出会被缩小,它的输出期望值等于 \(1 - p\)
      • 在测试时,Dropout 不再丢弃任何神经元,所有的神经元都参与计算。为了保持 训练时和测试时的输出期望一致,我们需要在测试时对每个神经元的输出进行缩放。缩放因子是 ( \(1 - p\) ) ,因为训练时每个神经元输出的期望是 \((1 - p) \cdot x\) ,我们通过缩放使得测试时的输出期望保持一致。
  • Dropout 代码实现

    在 PyTorch 中,神经网络模型有两种模式:训练模式(training)和推理模式(evaluation)。可以通过 model.train()model.eval() 来切换模型的模式。在训练时,Dropout 会启用,而在推理时,Dropout 会禁用并自动调整输出。

    # <--- From Pytorch --->
    import torch
    import torch.nn as nn
    
    # 定义一个简单的神经网络,包含一个 Dropout 层
    class SimpleNN(nn.Module):
        def __init__(self, p=0.5):
            super(SimpleNN, self).__init__()
            self.fc1 = nn.Linear(10, 50)  # 输入层到隐藏层
            self.fc2 = nn.Linear(50, 1)   # 隐藏层到输出层
            self.dropout = nn.Dropout(p)  # Dropout 层,丢弃概率为 p
    
        def forward(self, x):
            x = self.fc1(x)
            x = torch.relu(x)
            x = self.dropout(x)  # 在激活之后应用 Dropout
            x = self.fc2(x)
            return x
    
    # 创建一个简单的神经网络实例
    model = SimpleNN(p=0.5)
    
    # 训练模式
    model.train()
    input_data = torch.randn(32, 10)  # 假设输入有32个样本,每个样本有10个特征
    output_train = model(input_data)   # 训练时,Dropout 会被应用
    
    # 推理模式
    model.eval()  # 切换到推理模式
    output_eval = model(input_data)   # 推理时,Dropout 会被禁用
    

Batch Normalization #

Batch Normalization (BN) 是一种深度学习中常用的正则化和加速训练的方法,最早由 Sergey Ioffe 和 Christian Szegedy 在 2015 年提出。其主要目的是通过减少输入特征的分布变化(Internal Covariate Shift)来稳定训练过程,并加快神经网络的收敛速度。

Batch Normalization 在每一层网络的激活值(或输入特征)上进行归一化。通过对小批量数据(Batch)的统计信息进行归一化,将激活值标准化为具有零均值和单位方差的分布,同时保留一个可学习的缩放参数和偏移参数来恢复模型的表达能力。它的公式可以表达为:

  1. 计算 Batch 均值和方差:对当前小批量的输入 \(\mathbf{x} = [x_1, x_2, \ldots, x_m]\) 计算均值和方差: \[ \mu_B = \frac{1}{m} \sum_{i=1}^m x_i, \quad \sigma_B^2 = \frac{1}{m} \sum_{i=1}^m (x_i - \mu_B)^2 \] 其中 \(m\) 是 batch 的大小。

  2. 归一化:使用均值和方差对输入进行标准化: \[ \hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} \] 其中 \(\epsilon\) 是一个很小的值,用于避免分母为零。

  3. 缩放和平移:为了保留模型的表达能力,引入两个可学习参数:缩放参数 \(\gamma\) 和偏移参数 \(\beta\) 。归一化后,输出为: \[ y_i = \gamma \hat{x}_i + \beta \] 这里, \(\gamma\) \(\beta\) 通过反向传播学习( \(\frac{\partial L}{\partial \gamma}\) , \(\frac{\partial L}{\partial \beta}\) ),可以调整标准化后的分布,使其适应当前任务。优化器(如 SGD、Adam)会根据学习率和梯度更新 \(\gamma\) \(\beta\)

    虽然归一化操作能稳定训练,但直接使用这种标准化后的数据可能会限制模型的表达能力。例如,某些任务可能需要激活值在特定范围,而不是被严格限制在零均值和单位方差。缩放和平移允许 BN 后的数据分布与任务需求一致,而不仅仅局限于零均值单位方差

它的作用有:

  1. 加速训练:归一化使得梯度传播更加平滑,减少了梯度爆炸或消失问题,使得训练可以使用更高的学习率,加快收敛速度。
  2. 正则化效果:小批量数据的均值和方差会产生一定的随机性,这种扰动类似于 Dropout 的正则化效果,有助于减少过拟合。
    • 不同小批量会导致略微不同的归一化结果,产生了噪声。这种随机性迫使模型不能过分依赖特定特征或特定路径,对训练数据的微小变化(如噪声或扰动)不敏感,从而降低了过拟合风险
  3. 更深的网络更稳定:BN 的引入让深度模型(如 ResNet 等)更容易训练,避免梯度消失和参数更新不稳定的问题。
    • 由于每一层的输入在每个小批量上被归一化,模型在训练时变得更加稳定

Note: 通常在线性变换之后激活函数之前使用 Batch Normalization。

Batch Normalization 在优化(Optimization)问题上的具体细节详见 优化章节的 Batch Normalization

  • Batch Normalization 代码实现
    # <--- From Pytorch --->
    import torch
    import torch.nn as nn
    
    # 定义模型
    class ModelWithBN(nn.Module):
        def __init__(self):
            super(ModelWithBN, self).__init__()
            self.fc1 = nn.Linear(10, 50)
            self.bn1 = nn.BatchNorm1d(num_features=50)  # Batch Normalization
            self.fc2 = nn.Linear(50, 20)
            self.bn2 = nn.BatchNorm1d(num_features=20)  # Batch Normalization
            self.relu = nn.ReLU()
    
        def forward(self, x):
            x = self.relu(self.bn1(self.fc1(x)))
            x = self.relu(self.bn2(self.fc2(x)))
            return x
    

    Note: \(\gamma\) \(\beta\) BatchNorm自动管理


集成学习算法(Ensemble Methods) #

详见 集成学习算法


对抗训练(Adversarial Training) #

Adversarial Training(对抗训练)是一种增强模型鲁棒性、提升其对抗样本(adversarial examples)抵抗力的训练方法。在深度学习中,对抗样本是经过精心设计,旨在迷惑模型的输入数据。对抗训练通过将这些对抗样本添加到训练集中,使得模型在学习过程中能够处理这些干扰并增强其泛化能力。

对抗样本的概念 #

对抗样本是指那些通过对原始输入数据添加微小但精心设计的扰动(通常是通过优化算法生成),使得模型产生错误预测的数据点。尽管这些扰动对于人眼来说几乎不可察觉,但却能够显著改变模型的输出。

举个例子:假设我们训练一个图片分类模型,模型可能把一张猫的图片正确分类为“猫”。但通过对图片添加微小的噪声,模型可能将其错误分类为“狗”。这个微小的噪声就是对抗样本。

Note: Adversarial Training 中的噪声是 针对模型的弱点设计的对抗噪声。这些噪声是通过优化过程生成的,目的是让模型在对抗样本中犯错。因此,噪声的设计是 精心的、具体的,每个样本的扰动都是有目的地用来欺骗模型的。

Noise Injection 中的噪声通常是 随机的、不具目标性,并不是有意识地去迷惑模型。它通常是简单的随机噪声,直接加到输入数据、隐藏层或权重中。其目的是通过扰动来防止模型过拟合,提高模型的泛化能力。

对抗训练的过程 #

在对抗训练中,我们通过以下步骤来训练模型,使其对抗样本具有鲁棒性:

  1. 生成对抗样本:首先,生成对抗样本。对抗样本是通过对输入数据施加一定的扰动使得模型产生错误预测。常见的生成对抗样本的方法包括:

    • Fast Gradient Sign Method (FGSM):使用模型的梯度信息来生成对抗样本。通过计算损失函数对输入数据的梯度,并按梯度方向添加一个小的扰动。在每一次正向传播(Forward Pass)和反向传播(Backward Pass)后,计算损失函数相对于输入样本 \(x\) 的梯度,即 \(\nabla_x \mathcal{L}(\theta, x, y)\) ,根据计算得到的输入梯度,通过扰动样本 \(x\) 来生成对抗样本 \(x_{\text{adv}}\) \[ x_{\text{adv}} = x + \epsilon \cdot \text{sign}(\nabla_x \mathcal{L}(\theta, x, y)) \] 其中, \(\epsilon\) 是扰动的大小,通常是一个小的常数,控制扰动的幅度。
  2. 结合对抗样本与正常样本进行训练:使用生成的对抗样本 \(x_{\text{adv}}\) 进行正向传播,并计算损失函数。然后使用对抗样本来计算损失函数相对于模型参数的梯度,并进行参数更新。Adversarial Training 的最终损失函数通常是 原始损失 和 对抗损失 的加权和。具体的损失函数形式可以写成: \[ \mathcal{L}_{\text{total}} = \mathbb{E}_{x,y} \left[ \mathcal{L}(\theta, x, y) \right] + \lambda \cdot \mathbb{E}_{x,y} \left[ \mathcal{L}(\theta, x + \delta, y) \right] \] 其中:

    • \(\mathcal{L}(\theta, x, y)\) 是标准的原始损失,通常是交叉熵损失(或其他损失函数),用于普通训练样本。
    • \(\mathcal{L}(\theta, x + \delta, y)\) 是在对抗样本 \(x + \delta\) 上计算的损失。
    • \(\lambda\) 是一个超参数,用于平衡标准损失和对抗损失的贡献。
  3. 优化训练过程:通过常规的优化方法(如梯度下降),模型会学习如何在对抗样本中保持较高的准确性。这通常会导致模型对潜在的攻击具有更强的抵抗能力。