引言:
正则化
(Regularization) 是机器学习中一种用于防止模型过拟合技术。核心思想是通过在模型损失函数中添加一个惩罚项 (Penalty Term),对模型的复杂度进行约束,从而提升模型在新数据上的泛化能力。
防止过拟合
:当模型过于复杂(例如神经网络层数过多、参数过多)时,容易在训练数据上“记忆”噪声或细节,导致在测试数据上表现差。简化模型
:正则化通过限制模型参数的大小或数量,迫使模型学习更通用的特征,而非过度依赖训练数据的细节。正则化的本质是在损失函数(Loss Function)中添加一个 惩罚项
,其形式为:
总损失 = 原始损失 + λ ⋅ 惩罚项 \text{总损失} = \text{原始损失} + \lambda \cdot \text{惩罚项} 总损失=原始损失+λ⋅惩罚项
原始损失
:模型在训练数据上的误差(如均方误差、交叉熵等)。惩罚项
:对模型参数的约束,例如参数的大小或稀疏性。λ
:正则化系数,控制惩罚项的强度。λ越大,惩罚越强,模型越简单。定义:通过在损失函数中添加权重的 L1 范数惩罚项,使部分权重趋于零达到特征选择的效果。
算法原理:
在机器学习中,模型通过最小化损失函数进行训练。L1 正则化在原始损失函数中添加一个基于参数 L1 范数的惩罚项,优化目标变为:
J ( θ ) = L ( θ ) + λ ⋅ R ( θ ) J(\theta) = L(\theta) + \lambda \cdot R(\theta) J(θ)=L(θ)+λ⋅R(θ)
其中:
惩罚项
:L1 正则化项,即参数绝对值之和
。通过最小化 J ( θ ) J(\theta) J(θ),模型不仅要拟合数据,还要尽量减少参数的绝对值总和,这会导致部分参数被压缩到 0。
由于 L1 范数的绝对值函数在 0 点不可导,梯度更新需要使用次梯度(subgradient)。以一个权重 w w w 为例:
当 w w w 的更新步长不足以抵消 λ ⋅ sign ( w ) \lambda \cdot \text{sign}(w) λ⋅sign(w) 时, w w w会被压缩到 0,从而产生稀疏性。
作用:
稀疏解
:L1 正则化倾向于将不重要的参数直接置为 0,形成稀疏的权重向量。这使得模型只保留对预测最重要的特征。特征选择
:由于部分权重变为 0,L1 正则化可以自动识别和剔除不相关或冗余的特征,特别适用于高维数据。防止过拟合
:通过减少有效参数数量,L1 正则化降低了模型复杂度,从而提升了泛化能力。优缺点:
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]
在机器学习中,模型通常通过最小化一个损失函数(例如均方误差)来训练。L2 正则化通过在原始损失函数中添加一个额外的项,改变优化的目标。数学上,带 L2 正则化的损失函数可以表示为:
J ( θ ) = L ( θ ) + λ ⋅ R ( θ ) J(\theta) = L(\theta) + \lambda \cdot R(\theta) J(θ)=L(θ)+λ⋅R(θ)
其中:
参数权重平方和
。优化目标变为同时最小化原始损失 L ( θ ) L(\theta) L(θ) 和正则化项 λ ⋅ R ( θ ) \lambda \cdot R(\theta) λ⋅R(θ),从而平衡模型的拟合能力和复杂性。
在梯度下降优化中,L2 正则化会影响参数的更新规则。以一个权重 ( w ) 为例:
可以看到,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]
假设某一层的输入是 x x x,经过 Dropout 后的输出为 y y y:
y = r ⋅ x 1 − p (其中 r 是随机掩码, p 是丢弃概率) y = \frac{r \cdot x}{1-p} \quad \text{(其中 \( r \) 是随机掩码,\( p \) 是丢弃概率)} y=1−pr⋅x(其中 r 是随机掩码,p 是丢弃概率)
位置:输入 -> 全连接/卷积 -> 激活函数 -> 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]])
对于一个小批量数据 B = { x 1 , x 2 , . . . , x m } B = \{x_1, x_2, ..., x_m\} B={x1,x2,...,xm}( m m m是批量大小),计算:
为了保留模型的表达能力,批量归一化引入两个可学习的参数 γ \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) | 对每一层的输入进行归一化处理,稳定数据分布,加速训练并具有一定的正则化效果。 | - | 加速训练,稳定性强,但增加了模型复杂度。 |