目录

一、认识

二、L1正则化

三、L2正则化

四、Elastic Net 正则化

五、L1 和 L2 正则化的区别

六、Dropout

七、早停

八、数据增强


一、认识

正则化是指在机器学习和统计建模中的一种技术,用于控制模型的复杂度,防止模型在训练数据上过度拟合(overfitting)。当模型过度拟合时,会学习到训练数据中的噪声和细微变化,导致在新数据上的性能下降

正则化通过在模型的损失函数中引入额外的惩罚项,来对模型的参数进行约束,从而降低模型的复杂度。这个额外的惩罚通常与模型参数的大小或者数量相关,旨在鼓励模型学习简单的规律,而不是过度拟合训练数据

在深度学习中,正则化通常涉及到对网络的权重进行约束,以防止其变得过大或过复杂。常见的正则化技术有 L1 和 L2 正则化,分别通过对权重的 L1 范数和 L2 范数进行惩罚来实现。这些技术有助于降低模型的复杂度,并提高模型在未见过的数据上的泛化能力

二、L1正则化

L1 正则化,也称为 Lasso 正则化,是一种常用的正则化技术,用于控制模型的复杂度和防止过拟合。其原理是通过在模型的损失函数中增加权重的 L1 范数(权重向量的绝对值之和)作为惩罚项,从而鼓励模型产生稀疏权重,即让一部分权重趋近于零,实现特征选择的效果

L_{L1} = L_{data} + \lambda\sum_{i = 1}^{n} \left | w_{i} \right |

  • L_{data} 是模型的数据损失,通常是模型的预测值与真实标签之间的误差,如均方误差(MSE)或交叉熵损失(Cross-entropy loss)
  • \lambda 是正则化参数,用于控制正则化项的强度
  • \left | w_{i} \right | 表示模型的权重的绝对值
import torch
import torch.nn as nn

# 构建模型和损失函数(L1)
model = nn.Linear(10, 1)
criterion = nn.L1Loss()

# 输入与目标数据
torch.manual_seed(100)
input = torch.randn([32, 10])
target = torch.randn([32, 1])
print(input)
print(target)

# 推理
output = model(input)
print(output)

# 计算损失
loss = criterion(output, target)
print(loss)

三、L2正则化

L2 正则化,也称为 Ridge 正则化。通过向模型的损失函数添加一个权重参数的 L2 范数的惩罚项来实现。L2 正则化中,惩罚项通常被定义为权重参数的 L2 范数的平方

L_{L2} = L_{data} + \lambda\left \| w \right \|_{2}^{2}

L_{data} 是模型的数据损失,通常是模型的预测值与真实标签之间的误差

\lambda 是正则化参数,用于控制正则化的强度

\left \| w \right \|_{2}^{2} 是权重向量w的 L2 范数的平方,表示为权重向量中各个参数的平方和

使用 L2 正则化的损失函数时,优化算法在优化过程中会同时考虑数据损失和正则化项,从而在保持对训练数据的拟合能力的同时,尽可能减小模型参数的大小,降低模型的复杂度

import torch
import torch.nn as nn

# 定义模型
model = nn.Linear(10, 1)
# 定义损失函数,包括L2正则化项
criterion = nn.MSELoss()

# 计算损失
input = torch.randn(1, 10)
target = torch.randn(1, 1)
output = model(input)
loss = criterion(output, target)
# 打印损失
print(loss)

权重衰减

optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)

权重衰减是一种 L2 正则化,通过在优化器中添加权重衰减项来限制权重的大小

四、Elastic Net 正则化

Elastic Net(弹性网络)正则化是一种结合L1和L2正则化的技术,以实现特征选择和权值收缩之间的平衡

L_{elastic} = L_{data} + \lambda_{1}\sum_{i = 1}^{n} \left | w_{i} \right | + \lambda_{2}\left \| w \right \|_{2}^{2}

Elastic Net 正则化结合了 L1 和 L2 正则化的优点。L1 正则化项鼓励稀疏性和特征选择,将一些系数驱动到恰好为零。这有助于选择最相关的特征并降低模型的复杂性。另一方面,L2 正则化项鼓励使用较小但非零的系数,防止任何一个特征主导模型的预测并提高模型的稳定性

\lambda_{1} 和 \lambda_{2} 控制 L1 和 L2 正则化之间的平衡。较高的值 \lambda_{1} 强调稀疏性,促进特征选择,而较高的值则 \lambda_{2} 强调权重收缩和整体复杂性控制

import torch
import torch.nn as nn
import torch.optim as optim

class MLP(nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.layer = nn.Sequential(
            nn.Linear(768, 254),
            nn.ReLU(),
            nn.Linear(254, 128),
            nn.ReLU(),
            nn.Linear(128, 10)
        )

    def forward(self, data):
        return self.layer(data)
    
    def compute_l1_loss(self, w):
        return torch.abs(w).sum()
    
    def compute_l2_loss(self, w):
        return torch.pow(w, 2.).sum()

torch.manual_seed(100)
x = torch.randn([5, 768])
y = torch.randn([5, 10])

model = MLP()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

outputs = model(x)
loss = criterion(outputs, y)

# Elastic Net正则化
l1_weight = 0.3
l2_weight = 0.7
parameters = []
for parameter in model.parameters():
    parameters.append(parameter.view(-1))
l1 = l1_weight * model.compute_l1_loss(torch.cat(parameters))
l2 = l2_weight * model.compute_l2_loss(torch.cat(parameters))
loss += l1 + l2

loss.backward()
optimizer.step()

五、L1 和 L2 正则化的区别

L1和L2正则化的主要区别在于训练期间添加到损失函数中的惩罚项

L1正则化(Lasso)

L1正则化将模型系数(权重)的绝对值之和添加到损失函数中。表示为\lambda\sum_{i = 1}^{n} \left | w_{i} \right |

L1 正则化倾向于通过将某些系数驱动为零来生成稀疏模型

其执行自动特征选择,因为模型实际上忽略了系数为零的特征。当处理高维数据集或需要特征选择和可解释性时,这可能是有利的

通过忽略不相关或不太重要的特征来帮助控制模型复杂性。导致模型具有更少的非零系数和更简单的表示。通过减少模型的复杂度,L1 正则化可以提高模型的泛化能力,减少过拟合的风险

但 L1 正则化项在零处不可微,这给优化带来了挑战。次梯度方法可以通过 L1 正则化有效地优化损失函数

L2 正则化(Ridge)

L2 正则化将模型系数的平方值之和添加到损失函数中。可以表示为\lambda\sum_{i=1}^{n}(w_{i}^{2})

L2 惩罚鼓励较小但非零的系数,防止任何一个特征主导模型的预测并促进整体权重收缩

通过缩小所有系数的大小来控制模型复杂性。其在特征之间提供更均匀分布的权重收缩,防止任何一个特征主导模型的预测,有效减少了过拟合,提高了模型的稳定性

如何选择 L1 正则化和 L2 正则化

使用 L1 正则化(Lasso)

  • 特征选择:当拥有包含许多特征的高维数据集,并且希望通过将某些系数精确为零来执行特征选择时,L1 正则化是合适的选择。其鼓励稀疏性,有效地选择最相关的特征并忽略不相关或不太重要的特征
  • 可解释的模型:若可解释性很重要,L1 正则化会很有帮助,因为其生成的稀疏模型仅包含具有非零系数的特征子集。这可以帮助了解模型预测中最有影响力的组成部分

使用 L2 正则化(Ridge)

  • 强特征相关性:当数据集包含高度相关的特征时,L2正则化比L1正则化更有效。L2 正则化在系数之间更均匀地分配相关特征的影响,防止任何一个特征主导模型的预测
  • 泛化性能:众所周知,L2 正则化可以通过减少过度拟合来提高模型的泛化性能。当没有特定的特征选择需要并且您想要控制模型的整体复杂性时,它通常是一个不错的选择

也可以使用Elastic Net 正则化来平衡两种方法

六、Dropout

在训深层练神经网络时,由于模型参数较多,在数据量不足的情况下,很容易过拟合。Dropout 就是在神经网络中一种缓解过拟合的方法

缓解过拟合的方式就是降低模型的复杂度,而 Dropout 就是通过减少神经元之间的连接,把稠密的神经网络神经元连接,变成稀疏的神经元连接,从而达到降低网络复杂度的目的

import torch
import torch.nn as nn

def test01():
    inputs = torch.randint(0, 10, size=[5, 8], dtype=float)
    print(inputs)
    print('-' * 50)

    dropout = nn.Dropout(p=0.8)
    outputs = dropout(inputs)
    print(outputs)

if __name__ == "__main__":
    test01()

# tensor([[2., 5., 0., 0., 9., 9., 6., 7.],
#         [1., 7., 4., 7., 0., 1., 9., 6.],
#         [1., 5., 4., 8., 8., 5., 9., 1.],
#         [1., 5., 8., 6., 7., 5., 3., 4.],
#         [3., 6., 9., 8., 0., 5., 4., 9.]], dtype=torch.float64)
# --------------------------------------------------
# tensor([[ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000, 35.0000],
#         [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000, 30.0000],
#         [ 0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000, 45.0000,  5.0000],
#         [ 5.0000,  0.0000,  0.0000, 30.0000,  0.0000,  0.0000,  0.0000,  0.0000],
#         [ 0.0000,  0.0000,  0.0000, 40.0000,  0.0000,  0.0000,  0.0000,  0.0000]],
#        dtype=torch.float64)

将 Dropout 层的概率 p 设置为 0.8,此时经过 Dropout 层计算的张量中就出现了很多 0,概率 p 设置值越大,则张量中出现的 0 就越多。上面结果的计算过程如下:

  1. 先按照 p 设置的概率,随机将部分的张量元素设置为 0
  2. 为了校正张量元素被设置为 0 带来的影响,需要对非 0 的元素进行缩放,其缩放因子为: 1/(1-p),上面代码中 p 的值为 0.8, 根据公式缩放因子为:1/(1-0.8) = 5
  3. 如:第 8 个元素,原来是 5,乘以缩放因子之后变成 25

丢弃概率 p 的值越大,则缩放因子的值就越大,相对其他未被设置的元素就要更多的变大。丢弃概率 P 的值越小,则缩放因子的值就越小,相对应其他未被置为 0 的元素就要有较小的变大

dropout 对网络参数的影响

# 设置随机数种子
torch.manual_seed(0)

def caculate_gradient(x, w):
    y = x @ w
    y = y.sum()
    y.backward()
    print('Gradient:', w.grad.reshape(1, -1).squeeze().numpy())

def test02():
    w = torch.randn(15, 1, requires_grad=True)
    x = torch.randint(0, 10, size=[5, 15]).float()
    caculate_gradient(x, w)

def test03():
    w = torch.randn(15, 1, requires_grad=True)
    x = torch.randint(0, 10, size=[5, 15]).float()
    dropout = nn.Dropout(p=0.8)
    x = dropout(x)
    caculate_gradient(x, w)

if __name__ == '__main__':
    test02()
    print('-' * 70)
    test03()

# Gradient: [19. 15. 16. 13. 34. 23. 20. 22. 23. 26. 21. 29. 28. 22. 29.]
# ----------------------------------------------------------------------
# Gradient: [ 0.  0. 25. 25. 45. 45. 45. 40.  0. 15. 30.  0. 20.  0. 70.]

经过 Dropout 层之后一些梯度为 0,这使得参数无法得到更新,从而达到了降低网络复杂度的目的

七、早停

早停是一种简单而有效的正则化方法,在训练过程中监视模型在验证集上的性能,一旦验证集上的性能开始下降,就停止训练。这样可以避免模型在训练集上过拟合
 

八、数据增强

数据增强是通过对训练数据进行变换来增加数据的多样性,从而减少过拟合的风险

  • 图像数据:旋转、翻转、裁剪、缩放等
  • 文本数据:同义词替换、插入、删除、交换等

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐