神经网络基础之正则化

发布于:2025-03-23 ⋅ 阅读:(30) ⋅ 点赞:(0)

引言:正则化 (Regularization) 是机器学习中一种用于防止模型过拟合技术。核心思想是通过在模型损失函数中添加一个惩罚项 (Penalty Term),对模型的复杂度进行约束,从而提升模型在新数据上的泛化能力。

一、正则化目的

  • 防止过拟合:当模型过于复杂(例如神经网络层数过多、参数过多)时,容易在训练数据上“记忆”噪声或细节,导致在测试数据上表现差。
  • 简化模型:正则化通过限制模型参数的大小或数量,迫使模型学习更通用的特征,而非过度依赖训练数据的细节。

二、正则化原理

正则化的本质是在损失函数(Loss Function)中添加一个 惩罚项,其形式为:
总损失 = 原始损失 + λ ⋅ 惩罚项 \text{总损失} = \text{原始损失} + \lambda \cdot \text{惩罚项} 总损失=原始损失+λ惩罚项

  • 原始损失:模型在训练数据上的误差(如均方误差、交叉熵等)。
  • 惩罚项:对模型参数的约束,例如参数的大小或稀疏性。
  • λ:正则化系数,控制惩罚项的强度。λ越大,惩罚越强,模型越简单。

三、 常见正则化方法

1. L1 正则化(Lasso 正则化):
  • 定义:通过在损失函数中添加权重的 L1 范数惩罚项,使部分权重趋于零达到特征选择的效果。

  • 算法原理


在机器学习中,模型通过最小化损失函数进行训练。L1 正则化在原始损失函数中添加一个基于参数 L1 范数的惩罚项,优化目标变为:

J ( θ ) = L ( θ ) + λ ⋅ R ( θ ) J(\theta) = L(\theta) + \lambda \cdot R(\theta) J(θ)=L(θ)+λR(θ)

其中:

  • J ( θ ) J(\theta) J(θ):总损失函数(带正则化的目标函数)。
  • L ( θ ) L(\theta) L(θ):原始损失函数(例如均方误差、交叉熵等),衡量模型预测与真实值的差距。
  • R ( θ ) = ∥ θ ∥ 1 = ∑ i ∣ θ i ∣ R(\theta) = \|\theta\|_1 = \sum_{i} |\theta_i| R(θ)=θ1=iθi 惩罚项:L1 正则化项,即参数绝对值之和
  • λ \lambda λ:正则化强度的超参数(惩罚系数),控制正则化的影响程度, λ ≥ 0 \lambda \geq 0 λ0
  • θ \theta θ:模型的参数(权重)。

通过最小化 J ( θ ) J(\theta) J(θ),模型不仅要拟合数据,还要尽量减少参数的绝对值总和,这会导致部分参数被压缩到 0。


  • 梯度更新原理

由于 L1 范数的绝对值函数在 0 点不可导,梯度更新需要使用次梯度(subgradient)。以一个权重 w w w 为例:

  • 原始梯度更新(无正则化): w ← w − η ∂ L ∂ w w \leftarrow w - \eta \frac{\partial L}{\partial w} wwηwL
  • 带 L1 正则化的次梯度更新:
    w ← w − η ( ∂ L ∂ w + λ ⋅ sign ( w ) ) w \leftarrow w - \eta \left( \frac{\partial L}{\partial w} + \lambda \cdot \text{sign}(w) \right) wwη(wL+λsign(w))
    • η \eta η:学习率。
    • ∂ L ∂ w \frac{\partial L}{\partial w} wL:原始损失对权重的梯度。
    • sign ( w ) \text{sign}(w) sign(w):符号函数, w > 0 w > 0 w>0时为 1, w < 0 w < 0 w<0时为 -1, w = 0 w = 0 w=0时次梯度取值范围为 [ − 1 , 1 ] [-1, 1] [1,1]

w w w 的更新步长不足以抵消 λ ⋅ sign ( w ) \lambda \cdot \text{sign}(w) λsign(w) 时, w w w会被压缩到 0,从而产生稀疏性。


  • 作用

    • 稀疏解:L1 正则化倾向于将不重要的参数直接置为 0,形成稀疏的权重向量。这使得模型只保留对预测最重要的特征。
    • 特征选择:由于部分权重变为 0,L1 正则化可以自动识别和剔除不相关或冗余的特征,特别适用于高维数据。
    • 防止过拟合:通过减少有效参数数量,L1 正则化降低了模型复杂度,从而提升了泛化能力。
  • 优缺点

    • 优点:
      • 特征选择能力: L1 正则化能够生成稀疏模型,自动筛选重要特征,适用于特征维度高或需要解释性的场景。
      • 降低模型复杂度:通过剔除不必要参数,简化模型结构,减少计算开销。
      • 对噪声鲁棒:忽略不相关特征后,模型对数据中的噪声更不敏感。
    • 缺点:
      • 不稳定性:当特征之间高度相关时,L1 正则化可能随机选择其中一个特征置为非 0,而忽略其他相关特征,导致结果不稳定。
      • 优化难度:由于 L1 范数不可导,优化过程需要使用次梯度或近端梯度下降等方法,计算复杂度可能高于 L2 正则化。
      • 依赖超参数 λ \lambda λ:正则化强度 λ \lambda λ 的选择至关重要,过大可能导致重要特征也被置为 0,过小则正则化效果不足。
import numpy as np
from sklearn.linear_model import LinearRegression, Lasso
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# 1、生成模拟数据
X, y = make_regression(n_samples=100, n_features=10, noise=0.1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 2、普通线性回归
lr = LinearRegression()
lr.fit(X_train, y_train)
y_predict_lr = lr.predict(X_test)
mse_lr = mean_squared_error(y_test, y_predict_lr)
print(f"普通线性回归 MSE: {mse_lr:.4f}")

# 3、L1 正则化(Lasso)
lasso = Lasso(alpha=0.1)  # alpha 是正则化强度,对应 λ
lasso.fit(X_train, y_train)
y_predict_lasso = lasso.predict(X_test)
mse_lasso = mean_squared_error(y_test, y_predict_lasso)
print(f"L1 正则化(Lasso) MSE: {mse_lasso:.4f}")

# 4、查看权重
print("普通线性回归权重:", lr.coef_[:10])
print("L1 正则化权重:", lasso.coef_[:10])
普通线性回归 MSE: 0.0103
L1 正则化(Lasso) MSE: 0.1824
普通线性回归权重: [16.7712358  54.13782324  5.18097686 63.64362199 93.61309994 70.63686589 87.0713662  10.43882574  3.15690876 70.90887261]
L1   正则化权重: [16.68551572 54.04466658  5.03023843 63.54923618 93.45872786 70.54211442 86.95689868 10.27114941  3.06974112 70.78354482]
2. L2 正则化(Ridge 正则化):
  • 定义:在损失函数中添加权重的 L2 范数平方惩罚项,防止权重过大,促进模型的平滑性。
  • 算法原理

在机器学习中,模型通常通过最小化一个损失函数(例如均方误差)来训练。L2 正则化通过在原始损失函数中添加一个额外的项,改变优化的目标。数学上,带 L2 正则化的损失函数可以表示为:

J ( θ ) = L ( θ ) + λ ⋅ R ( θ ) J(\theta) = L(\theta) + \lambda \cdot R(\theta) J(θ)=L(θ)+λR(θ)

其中:

  • J ( θ ) J(\theta) J(θ):总损失函数(带正则化的目标函数)。
  • L ( θ ) L(\theta) L(θ):原始损失函数(例如均方误差、交叉熵等),衡量模型预测与真实值之间的差距。
  • R ( θ ) = 1 2 ∥ θ ∥ 2 2 = 1 2 ∑ i θ i 2 R(\theta) = \frac{1}{2} \|\theta\|_2^2 = \frac{1}{2} \sum_{i} \theta_i^2 R(θ)=21θ22=21iθi2:L2 正则化项,即参数的 L2 范数平方,即参数权重平方和
  • λ \lambda λ:正则化强度的超参数(也叫惩罚系数),控制正则化的影响程度, λ ≥ 0 \lambda \geq 0 λ0
  • θ \theta θ:模型的参数(权重)。

优化目标变为同时最小化原始损失 L ( θ ) L(\theta) L(θ) 和正则化项 λ ⋅ R ( θ ) \lambda \cdot R(\theta) λR(θ),从而平衡模型的拟合能力和复杂性。


  • 梯度更新

在梯度下降优化中,L2 正则化会影响参数的更新规则。以一个权重 ( w ) 为例:

  • 原始梯度更新(无正则化): w ← w − η ∂ L ∂ w w \leftarrow w - \eta \frac{\partial L}{\partial w} wwηwL
  • 带 L2 正则化的梯度更新:
    w ← w − η ( ∂ L ∂ w + λ w ) w \leftarrow w - \eta \left( \frac{\partial L}{\partial w} + \lambda w \right) wwη(wL+λw)
    • η \eta η:学习率。
    • ∂ L ∂ w \frac{\partial L}{\partial w} wL:原始损失对权重的梯度。
    • λ w \lambda w λw L 2 L2 L2 正则化的梯度贡献。

可以看到,L2 正则化在每次更新时额外引入了一个衰减 λ w \lambda w λw,使得权重倾向于变小。因此,L2 正则化也被称为 权重衰减(weight decay)。


  • 作用

    • 避免过拟合:只需在损失函数中添加一项即可,计算和优化都很直接。使参数值整体变小(但不会变为 0),避免参数过大导致过拟合。
    • 提升模型的稳定性:通过限制权重大小,L2 正则化使模型更能抵抗数据中的异常值或噪声。 提升模型的稳定性(减少参数波动)。
    • 广泛适用:可用于线性回归、逻辑回归、神经网络等多种模型。
  • 局限性

    • 无法产生稀疏解:与 L1 正则化(Lasso)不同,L2 正则化不会将权重变为 0,因此不具备特征选择的能力。
    • 依赖超参数 :正则化强度 𝜆需要通过交叉验证等方法调整,过大可能导致欠拟合,过小则效果不足。

import numpy as np
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# 1、生成模拟数据
X, y = make_regression(n_samples=100, n_features=10, noise=0.1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 2、普通线性回归
lr = LinearRegression()
lr.fit(X_train, y_train)
y_predict_lr = lr.predict(X_test)
mse_lr = mean_squared_error(y_test, y_predict_lr)
print(f"普通线性回归 MSE: {mse_lr:.4f}")

# 3、L2 正则化(Ridge alpha=λ是正则化强度)
ridge = Ridge(alpha=3.0)  
ridge.fit(X_train, y_train)
y_predict_ridge = ridge.predict(X_test)
mse_ridge = mean_squared_error(y_test, y_predict_ridge)
print(f"L2 正则化(Ridge) MSE: {mse_ridge:.4f}")

# 4、查看权重
print("普通线性回归权重:", lr.coef_[:10])
print("L2 正则化权重:", ridge.coef_[:10])
普通线性回归 MSE: 0.0103
L2 正则化(Ridge) MSE: 99.6320
普通线性回归权重: [16.7712358  54.13782324  5.18097686 63.64362199 93.61309994 70.63686589 87.0713662  10.43882574  3.15690876 70.90887261]
L2   正则化权重: [17.26631225 51.58306238  5.07374538 60.93844646 89.34000194 67.95302325 83.99492712  8.91228771  3.41758485 67.23661507]
3. Dropout 正则化:
  • 定义:在训练过程中随机丢弃部分神经元,减少神经元间的共适应性,防止过拟合。
  • 算法原理:在训练时随机“关闭”(置零)部分神经元,强制网络学习更鲁棒的特征。

假设某一层的输入是 x x x,经过 Dropout 后的输出为 y y y

  • 训练时 r ∼ Bernoulli ( p ) (生成 0/1 掩码,1 的概率为  1 − p ) r \sim \text{Bernoulli}(p) \quad \text{(生成 0/1 掩码,1 的概率为 } 1-p\text{)} rBernoulli(p)(生成 0/1 掩码,的概率为 1p

y = r ⋅ x 1 − p (其中  r  是随机掩码, p  是丢弃概率) y = \frac{r \cdot x}{1-p} \quad \text{(其中 \( r \) 是随机掩码,\( p \) 是丢弃概率)} y=1prx(其中 r 是随机掩码,p 是丢弃概率)

  • 推理时 y = x y = x y=x 不丢弃任何神经元,直接使用完整输出。

  • 位置输入 -> 全连接/卷积 -> 激活函数 -> Dropout -> 输出

  • 作用

    • 防止过拟合:通过随机丢弃神经元,减少了神经元之间的“共适应”(co-adaptation),即避免模型过度依赖某些特定的神经元组合。
    • 类似集成学习:Dropout 相当于在训练过程中生成了多个不同的子网络,最终的模型可以看作这些子网络的平均效果,具有类似集成学习的正则化作用。
    • 简单高效:Dropout 实现简单,只需在网络中添加一层操作,且计算开销低。
    • 提高泛化能力:模型在训练时无法依赖单一路径或特征,使得其在测试数据上的表现更鲁棒。
  • 局限性

    • 训练时间增加:由于引入了随机性,Dropout 可能需要更多的迭代才能收敛。
    • 不适用于所有场景:对于小型网络或数据量不足的情况,Dropout 可能会削弱模型的表达能力,反而降低性能。
    • 推理阶段无随机性:Dropout 只在训练时起作用,推理时不丢弃神经元,因此其正则化效果仅通过训练过程间接体现。
import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个带 Dropout 的简单网络
class DropNet(nn.Module):
    def __init__(self):
        super(DropNet, self).__init__()
        # 定义输入输出维度
        self.fc1 = nn.Linear(10, 20)
        # 丢弃概率 0.5
        self.dropout = nn.Dropout(p=0.5)
        self.fc2 = nn.Linear(20, 1)
        # 使用ReLU激活函数
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.dropout(x)  # 应用 Dropout
        x = self.fc2(x)
        return x

# 1、生成随机数据(共64个样本,每个样本10个特征)
X = torch.randn(64, 10)
y = torch.randn(64, 1)
print(f"第一个条样本: {X[:1]}")
print(f"第一个目标值: {y[:1]}")

# 2、初始化模型、损失函数和优化器
model = DropNet()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 3、训练模型
model.train()
for epoch in range(100):
    # 清除上次梯度
    optimizer.zero_grad()
    # 前向传播,计算模型的输出
    output = model(X)
    # 计算损失函数
    loss = criterion(output, y)
    # 反向传播,计算梯度
    loss.backward()
    # 更新模型参数
    optimizer.step()
    if epoch % 20 == 0:
        print(f"Epoch {epoch}, 损失: {loss.item():.4f}")

# 4、推理模式
model.eval()  # 设置为评估模式(Dropout 关闭)
with torch.no_grad():
    predict = model(X)
    print("推理结果:", predict[:1])
第一个条样本: tensor([[-0.3384, -0.3127,  0.4141,  1.0404,  0.8872, -1.2251, -0.1888,  0.4323, 0.0642, -1.3889]])
第一个目标值: tensor([[-0.6342]])
Epoch 0, 损失: 1.0907
Epoch 20, 损失: 1.0641
Epoch 40, 损失: 0.9568
Epoch 60, 损失: 0.9666
Epoch 80, 损失: 0.9806
推理结果: tensor([[0.0403]])
4. 批归一化(Batch Normalization):
  • 定义:对每一层的输入进行归一化处理,稳定数据分布,加速训练并具有一定的正则化效果。
  • 原理:在每一层的输入上,对每个小批量(mini-batch)的数据进行标准化 ,使得每层的输入分布更稳定,从而加速训练并提升模型性能。

对于一个小批量数据 B = { x 1 , x 2 , . . . , x m } B = \{x_1, x_2, ..., x_m\} B={x1,x2,...,xm} m m m是批量大小),计算:

  • 均值: μ B = 1 m ∑ i = 1 m x i \mu_B = \frac{1}{m} \sum_{i=1}^m x_i μB=m1i=1mxi
  • 方差: σ B 2 = 1 m ∑ i = 1 m ( x i − μ B ) 2 \sigma_B^2 = \frac{1}{m} \sum_{i=1}^m (x_i - \mu_B)^2 σB2=m1i=1m(xiμB)2
    x ^ i = x i − μ B σ B 2 + ϵ ( ϵ  是防止除零的小常数,如  1 × 1 0 − 5 ) \hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} \quad \text{(\(\epsilon\) 是防止除零的小常数,如 \(1 \times 10^{-5}\))} x^i=σB2+ϵ xiμBϵ 是防止除零的小常数,如 1×105

为了保留模型的表达能力,批量归一化引入两个可学习的参数 γ \gamma γ和偏移 β \beta β(平移),对标准化后的值进行线性变换:
y i = γ x ^ i + β ( γ  控制输出的标准差; β  控制输出的均值) y_i = \gamma \hat{x}_i + \beta \quad \text{($\gamma$ 控制输出的标准差;$\beta$ 控制输出的均值)} yi=γx^i+βγ 控制输出的标准差;β 控制输出的均值)


  • 位置输入 -> 卷积/全连接 -> 批量归一化 -> 激活函数 -> 输出
  • 作用
    • 加速训练:通过标准化输入,减少了梯度消失或爆炸的风险,使模型可以使用更高的学习率,从而加快收敛。
    • 提高稳定性:减少了每一层输入分布的变化,使训练过程更稳定。
    • 正则化效果:批量归一化引入了噪声(由于小批量均值和方差的随机性),一定程度上具有正则化作用,可能减少对 Dropout 等其他正则化方法的需求。
    • 对初始化不敏感:减少了对参数初始化的依赖,即使初始值不太理想,模型也能较好地收敛。
  • 局限性
    • 依赖批量大小:当批量大小(batch size)太小时,均值和方差的估计不够准确,会影响效果。通常需要较大的批量大小(如 32 或 64)。
    • 推理阶段的处理:在训练时,BN 使用小批量的均值和方差;但在推理(测试)时,小批量不可用。因此,BN 会维护一个全局的移动均值和移动方差(通过训练时的指数移动平均计算),用于推理阶段。
    • 不适用于某些任务:对于动态网络(如 RNN)或小批量难以定义的场景(如在线学习),BN 的效果可能不佳。
import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个简单的网络
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 定义输入输出维度
        self.fc1 = nn.Linear(10, 20)
        # 使用nn对20个特征进行批量归一化
        self.bn = nn.BatchNorm1d(20)  
        self.fc2 = nn.Linear(20, 1)
        # 使用ReLU激活函数
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.fc1(x)
        # 应用批量归一化
        x = self.bn(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x

# 1、生成随机数据(共64个样本,每个样本10个特征)
X = torch.randn(64, 10)
y = torch.randn(64, 1)
print(f"第一个条样本: {X[:1]}")
print(f"第一个目标值: {y[:1]}")

# 2、初始化模型、损失函数和优化器
model = Net()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 3、训练模型
model.train()
for epoch in range(100):
    # 清除上次梯度
    optimizer.zero_grad()
    # 前向传播,计算模型的输出
    output = model(X)
    # 计算损失函数
    loss = criterion(output, y)
    # 反向传播,计算梯度
    loss.backward()
    # 更新模型参数
    optimizer.step()
    if epoch % 20 == 0:
        print(f"Epoch {epoch}, 损失: {loss.item():.4f}")

# 4、推理模式
model.eval()  # 设置为评估模式(使用移动均值和方差)
with torch.no_grad():
    predict = model(X)
    print("推理结果:", predict[:1])
第一个条样本: tensor([[-0.4048,  1.3088, -1.8305,  0.6533, -0.3900, -1.1319,  0.3992,  1.9025, -0.0115, -0.1645]])
第一个目标值: tensor([[0.0327]])
Epoch 0, 损失: 1.0700
Epoch 20, 损失: 0.7397
Epoch 40, 损失: 0.6255
Epoch 60, 损失: 0.5578
Epoch 80, 损失: 0.5163
推理结果: tensor([[-0.1543]])

四、总结

正则化方法 描述 公式表示 特点
L1 正则化(Lasso) 在损失函数中加入权重的 L1 范数惩罚项,鼓励模型产生稀疏权重,即部分权重被压缩为零,从而实现特征选择。 J ( θ ) = Loss + λ ∑ i ∣ θ i ∣ J(\theta) = \text{Loss} + \lambda \sum_{i} \vert\theta_i\vert J(θ)=Loss+λiθi 有助于特征选择,产生稀疏模型,但可能导致解的不稳定性。
L2 正则化(Ridge) 在损失函数中加入权重的 L2 范数惩罚项,防止权重过大,使模型更加平滑。 J ( θ ) = Loss + λ ∑ i θ i 2 J(\theta) = \text{Loss} + \lambda \sum_{i} \theta_i^2 J(θ)=Loss+λiθi2 防止权重过大,适用于处理多重共线性问题,但不会导致权重为零。
Dropout 在训练过程中以一定概率随机丢弃神经元,减少神经元间的共适应性,防止过拟合。 - 简单有效,适用于神经网络,但增加了训练时间。
早停法(Early Stopping) 在验证集性能不再提升时停止训练,防止模型过度拟合训练数据。 - 简单易行,但需要验证集,可能错过最佳模型。
数据增强(Data Augmentation) 通过对训练数据进行随机变换,增加数据量,增强模型的泛化能力。 - 增强模型鲁棒性,但可能增加训练时间。
批归一化(Batch Normalization) 对每一层的输入进行归一化处理,稳定数据分布,加速训练并具有一定的正则化效果。 - 加速训练,稳定性强,但增加了模型复杂度。