引言:
正则化
(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} w←w−η∂w∂L。
- 带 L1 正则化的次梯度更新:
w ← w − η ( ∂ L ∂ w + λ ⋅ sign ( w ) ) w \leftarrow w - \eta \left( \frac{\partial L}{\partial w} + \lambda \cdot \text{sign}(w) \right) w←w−η(∂w∂L+λ⋅sign(w))- η \eta η:学习率。
- ∂ L ∂ w \frac{\partial L}{\partial w} ∂w∂L:原始损失对权重的梯度。
- 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=21∑iθ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} w←w−η∂w∂L。
- 带 L2 正则化的梯度更新:
w ← w − η ( ∂ L ∂ w + λ w ) w \leftarrow w - \eta \left( \frac{\partial L}{\partial w} + \lambda w \right) w←w−η(∂w∂L+λw)- η \eta η:学习率。
- ∂ L ∂ w \frac{\partial L}{\partial w} ∂w∂L:原始损失对权重的梯度。
- λ 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{)} r∼Bernoulli(p)(生成 0/1 掩码,1 的概率为 1−p)
y = r ⋅ x 1 − p (其中 r 是随机掩码, p 是丢弃概率) y = \frac{r \cdot x}{1-p} \quad \text{(其中 \( r \) 是随机掩码,\( p \) 是丢弃概率)} y=1−pr⋅x(其中 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=m1∑i=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=m1∑i=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×10−5)
为了保留模型的表达能力,批量归一化引入两个可学习的参数 γ \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) | 对每一层的输入进行归一化处理,稳定数据分布,加速训练并具有一定的正则化效果。 | - | 加速训练,稳定性强,但增加了模型复杂度。 |