在训练Ai模型的过程中,常常会遇到一个棘手的问题——过拟合(Overfitting)。当企图增加epoch提高精度的时候,发现accurancy的值始终徘徊,而测试集的损失开始不降反增,则意味着来到了过拟合的阶段,此时要么停止训练,要么选择一些特殊的手段来优化网络,比如本文提到的两种正则化技术:L1正则化和L2正则化。
在线性模型中也被称为Lasso回归(Lasso Regression),是另一种广泛用于防止模型过拟合的技术。它与L2正则化非常相似,都是通过在损失函数中添加一个惩罚项来实现的,但惩罚的方式有所不同。
核心思想:L1正则化添加的惩罚项是模型所有参数(权重)的绝对值和
。这种惩罚方式有一个非常独特且强大的特性:它倾向于将不重要的特征的权重精确地压缩到零。
我们同样以一个标准的损失函数为基础,看看加入L1正则化项后它会变成什么样。
:表示模型中所有权重(同样不包括偏置项 b)的绝对值之和。这在数学上被称为权重的 L1范数,记作。
(Lambda): 同样是正则化参数,控制着正则化的强度。λ越大,模型就越倾向于将更多权重变为零,模型的稀疏性就越强。
:缩放因子。
在模型训练(如使用梯度下降)时,L1正则化项对梯度的影响是关键。的导数是 (当),即:
于是,梯度下降的更新规则大致变为:
与L2的权重衰减不同,L1正则化项在梯度中增加了一个恒定的值,这意味着,无论权重的大小(只要它不为零),每次更新时都会朝着零的方向被“推”一个固定的步长。
L1的惩罚是恒定的。即使权重已经非常小(例如0.001),它仍然会受到一个固定的“拉力”把它往0拉。这使得权重很容易跨过0点,并被固定在0上。这个就是L1正则化的效果:它迫使模型只保留最有效的特征(权重不为零),而将其他所有无关或冗余的特征“关闭”(权重变为零)。
在线性模型中也常被称为岭回归(Ridge Regression),是一种在机器学习和统计学中广泛使用的技术,其主要目的是防止模型过拟合(Overfitting)。
举个例子,一个学生参加考试:
没有正则化 (λ=0):只关心考试成绩,可能会用非常复杂的方法去解题(模型权重可以变得很大),即使这些方法只对特定题目有效。这就像模型完美拟合了训练数据,但泛化能力差(过拟合)。
有L2正则化 (λ>0):你现在必须在“提高考试成绩”和“保持作业简洁”之间找到一个平衡。你不会再使用那些虽然能提高一点点分数但极其复杂的方法,因为“简洁度”的扣分会得不偿失。你会倾向于使用更通用、更简单的方法来解题。这就像模型找到了一个权重较小的解,从而获得了更好的泛化能力。
核心思想:在模型的损失函数(Cost Function)中加入一个惩罚项,这个惩罚项是模型所有参数(权重)的平方和
。通过这种方式,它会“惩罚”那些值过大的权重,迫使模型在学习时倾向于选择更小、更分散的权重值。
在模型训练(如使用梯度下降)时,L2正则化项对梯度的影响是让权重在每次更新时都“衰减”一点。 对添加正则化项后的损失函数求导:
于是,更新规则变为:
合并同类项得到:
根据公式可以知道:学习率 和 惩罚系数 值都很小,表示样本数量,比较大。并且三个参数都是正值,所以 是一个小于1数值,**这意味着:每次梯度更新,都会衰减,逐渐变小。**因此,L2正则化也被称为权重衰减。
特性 | L2 正则化 (岭回归) | L1 正z则化 (Lasso) |
---|---|---|
惩罚项 | 权重的平方和 () | 权重的 绝对值和 ( | | ) |
对权重的影响 | 使权重趋向于0,但很难变为精确的0。产生“平滑”的权重分布。 | 能使不重要的特征权重精确地变为0。 |
效果 | 权重衰减(Weight Decay) | 稀疏性(Sparsity),可用于特征选择。 |
这个选择主要取决于数据特性和建模目标。 L2 正则化 (岭回归)是默认选项,尤其是需要防止过拟合,认为所有特征都可能对结果有贡献时,即使贡献很小,当特征之间存在多重共线性(高度相关)时,L2比L1更稳定。
当数据中存在大量无关或冗余的特征时可以选择尝试L1正则化,或者需要一个可解释性强的模型时。L1会把不重要特征的权重降为零,可以清楚地看到哪些特征是模型做出预测的关键。
注意:如果有一组高度相关的特征,L1可能会随机选择其中一个保留,而将其他的权重设为零。
Example:
import torch import torch.nn as nn import torch.optim as optim # 1. 准备数据 (虚拟数据) X_train = torch.randn(100, 10) # 100个样本, 10个特征 y_train = torch.randn(100, 1) # 对应的目标值 # 2. 定义模型 class SimpleModel(nn.Module): def __init__(self): super(SimpleModel, self).__init__() self.linear1 = nn.Linear(10, 5) self.relu = nn.ReLU() self.linear2 = nn.Linear(5, 1) def forward(self, x): x = self.linear1(x) x = self.relu(x) x = self.linear2(x) return x model = SimpleModel() # 3. 定义损失函数 criterion = nn.MSELoss() # 4. 定义优化器并设置 weight_decay # 这就是L2正则化的关键所在! learning_rate = 0.01 # weight_decay 的值通常是一个小的正数, 比如 1e-4, 1e-5 等 # 这个值就是正则化强度 lambda weight_decay_lambda = 1e-4 # 使用 Adam 优化器,并传入 weight_decay 参数 optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay_lambda) # optimizer = optim.SGD(model.parameters(), lr=learning_rate, weight_decay=weight_decay_lambda) # 也可以用SGD # 5. 训练循环 num_epochs = 100 for epoch in range(num_epochs): # 前向传播 outputs = model(X_train) loss = criterion(outputs, y_train) # 反向传播和优化 optimizer.zero_grad() # 清空之前的梯度 loss.backward() # 计算梯度 optimizer.step() # 更新权重 (已包含L2正则化) if (epoch+1) % 10 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') # 训练后可以查看权重范数,与不加正则化的模型对比会发现权重值更小 total_norm = 0 for p in model.parameters(): if p.grad is not None: param_norm = p.data.norm(2) # 计算L2范数 total_norm += param_norm.item() ** 2 print(f'Sum of squared L2 norms of weights: {total_norm**0.5:.4f}')
在代码中的weight_decay 参数相当于以下公式中的:
或者参考AdamW算法论文中的公式:
本文作者:James
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!