PyTorch实现L1和L2正则化

PyTorch实现L1和L2正则化

L1正则化和L2正则化是两种常用的正则化方法,用于防止模型过拟合,提高模型的泛化能力,它们是通过向损失函数中添加惩罚项来限制模型的复杂度。

1.L1正则化与L2正则化介绍

L1正则化(Lasso回归)

L1正则化的惩罚项是模型权重的绝对值之和,即: L1正则化项 = λ ∑ i ∣ w i ∣ \text{L1正则化项}=\lambda\sum_i|w_i| L1正则化项=λiwi,其中 λ \lambda λ是正则化参数,用于控制正则化的强度, w i w_i wi是模型的权重。带有L1正则化的损失函数通常表示为:
L 1 损失 = 原始损失 + λ ∑ i ∣ w i ∣ \mathrm{L}1\text{损失}=\text{原始损失}+\lambda\sum_i|w_i| L1损失=原始损失+λiwi
L1正则化倾向于使一些权重变为零,从而使模型更加稀疏。由于使用绝对值,L1正则化可以使得小的权重趋向于零,但不会对大的权重有很大影响。

L2正则化(Ridge回归)

L2正则化的惩罚项是模型权重的平方和,即: L2正则化项 = λ ∑ i w i 2 \text{L2正则化项}=\lambda\sum_iw_i^2 L2正则化项=λiwi2,同样 λ \lambda λ是正则化参数,用于控制正则化的强度, w i w_i wi是模型的权重。带有L2正则化的损失函数通常表示为:
L 2 损失 = 原始损失 + λ ∑ i w i 2 \mathrm{L}2\text{损失}=\text{原始损失}+\lambda\sum_iw_i^2 L2损失=原始损失+λiwi2
L2正则化会使权重逐渐减小,但不会使它们变为零。这使得模型的权重分布更加平滑。由于使用平方和,L2正则化对大的权重有更大的影响,能有效防止权重变得过大。

L1和L2正则化是控制模型复杂度的有效手段,如果希望模型具有特征选择功能,可以选择L1正则化,因为它会使一些权重变为零,从而自动选择特征。如果希望模型权重更加平滑且不希望一些权重变为零,可以选择L2正则化。也可以同时使用L1和L2正则化,这被称为Elastic Net正则化,结合了两者的优点。

Elastic Net正则化

Elastic Net正则化(弹性网络正则化)结合了L1正则化和L2正则化的优点,是一种在模型训练过程中防止过拟合的强大工具。它通过在损失函数中同时包含L1和L2正则化项来实现。Elastic Net正则化的损失函数如下:
Elastic Net损失 = 原始损失 + α ( λ 1 ∑ i ∣ w i ∣ + λ 2 ∑ i w i 2 ) \text{Elastic Net损失}=\text{原始损失}+\alpha\left(\lambda_1\sum_i|w_i|+\lambda_2\sum_iw_i^2\right) Elastic Net损失=原始损失+α(λ1iwi+λ2iwi2)
其中 α \alpha α是总体正则化的权重参数, λ 1 \lambda_1 λ1是L1正则化的权重参数, λ 2 \lambda_2 λ2是L2正则化的权重参数, w i w_i wi是模型的权重。

Elastic Net正则化通过结合L1和L2正则化,可以同时实现特征选择和权重平滑。L1正则化可以使一些权重变为零,从而选择重要特征;L2正则化则可以防止权重过大,保持权重的平滑性。Elastic Net特别适用于高维数据(特征数多于样本数)和存在多重共线性的情况。与L1正则化不同,Elastic Net可以选择一组相关特征,而不仅仅是单个特征。

2.利用torch.optim实现正则化

PyTorch中的官方优化器集成了L2正则化,使用很简单,只需要在创建优化器时,添加weight_decay参数即可。该参数相当于L2正则化项中的 λ \lambda λ参数,用于指定权重衰减的程度。

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

注意:

  • torch.optim所集成的优化器只有L2正则化方法,并且自带的L2正则化是将所有参数都进行了正则化,包括权重和偏置。但是一般的正则化,我们只是对权重W进行正则化,对偏置B是不进行惩罚的。
  • 在应用正则化技术时,我们会观察到正则化项的增加会导致整体损失(loss)变大。例如,当我们设置weight_decay为1时,损失值为10;当weight_decay增加到100时,损失值理论上应增加到1000。然而,如果我们使用torch.optim的优化器,并依然使用nn.CrossEntropyLoss()计算损失,我们会发现,无论如何改变weight_decay的值,损失值几乎与未加正则化时的损失值相差无几。这是因为在这种情况下,损失函数nn.CrossEntropyLoss()并未包含权重W的正则化项。(注意:并不是没有实现正则化)

3.利用torch.optim实现正则化进阶(建议使用👏)

当模型过拟合的时候,大多数其实我们用L2正则化就可以了。但是在网上查了很多的资料,都说利用torch.optim所实现的L2正则化有误,因为其不仅对权重W进行了惩罚,还对偏置B也进行了惩罚。但是一般,我们实现正则化是不对偏置B进行惩罚的。故学习了一些自定义正则化的方法,譬如下面的方法一和方法二,但是当偶然看到官方文档的一个技术点后,我发现可以通过下面的方式来实现L2正则化,并且自定义对哪些参数进行惩罚。实现方式如下:

# 指定了不进行权重衰减的参数名称,包括偏置项(bias)和 LayerNorm 的权重(LayerNorm.weight)
no_decay = ["bias", "LayerNorm.weight"]

"""
* optimizer_grouped_parameters 列表包含两个字典,用于分别处理需要进行权重衰减和不需要进行权重衰减的参数。
* 第一个字典中的 "params" 键:筛选出参数名称中不包含 no_decay 列表中任一元素的参数,并为这些参数设置权重衰减率 decay。  (需要进行权重衰减)
* 第二个字典中的 "params" 键:筛选出参数名称中包含 no_decay 列表中任一元素的参数,并将这些参数的权重衰减率设为 0.0。  (不需要进行权重衰减)
"""
optimizer_grouped_parameters = [
    {
        "params": [parameter for name, parameter in model.named_parameters() if not any(nd in name for nd in no_decay)],
        "weight_decay": decay,
    },
    {
        "params": [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)],
        "weight_decay": 0.0,
    },
]

# 定义优化器,并传入参数列表
optimizer = torch.optim.AdamW(params=optimizer_grouped_parameters, lr=lr, amsgrad=False)

优化器支持指定每个参数的选项。为此,不要传递变量s的iterable,而是传递dict s的iterable。它们中的每一个都将定义一个单独的参数组,并且应该包含一个params键,其中包含属于它的参数列表。其他键应该与优化器接受的关键字参数相匹配,并将用作该组的优化选项。

4.方法一:直接在loss后面加对应的惩罚项

4.1 L1正则化实现思路

损失函数后面加惩罚项torch.sum(torch.abs(param))

weight_decay = 0.01  # 权重系数
regularization_loss = 0  # L1正则化的损失

for param in model.parameters():
    # L1正则化的方式,参数的绝对值求和
    regularization_loss += torch.sum(torch.abs(param))

origin_loss = criterion(input=output, target=label)  # 计算原始损失
loss = origin_loss + weight_decay * regularization_loss  # 计算总损失

optimizer.zero_grad()
loss.backward()
optimizer.step()

4.2 L2正则化实现思路

损失函数后面加惩罚项torch.sum(torch.pow(param, 2))

weight_decay = 0.01  # 权重系数
regularization_loss = 0  # L2正则化的损失

for param in model.parameters():
    # L2正则化的方式,将参数中的权重全部求平方后再求和,结果就是L2范数
    regularization_loss += torch.sum(torch.pow(param, 2))

origin_loss = criterion(input=output, target=label)  # 计算原始损失
loss = origin_loss + weight_decay * regularization_loss  # 计算总损失

optimizer.zero_grad()
loss.backward()
optimizer.step()

5.方法二:通过封装一个正则化类实现正则化

5.1 封装一个实现正则化的Regularization类

import torch
from torch import nn


class Regularization(nn.Module):
    def __init__(self, model, weight_decay, p=2):
        """
        :param model 模型
        :param weight_decay:正则化参数
        :param p: 范数计算中的幂指数值,默认求2范数,
                  当p=2为L2正则化,p=1为L1正则化
        """
        super().__init__()
        if weight_decay <= 0:
            print("param weight_decay can not <=0")
            exit(0)
        self.model = model
        self.weight_decay = weight_decay
        self.p = p
        self.weight_list = self.get_weight(model)
        self.weight_info(self.weight_list)

    def forward(self, model):
        self.weight_list = self.get_weight(model)  # 获得最新的权重
        reg_loss = self.regularization_loss(self.weight_list, self.weight_decay, p=self.p)
        return reg_loss

    def get_weight(self, model):
        """
        获得模型的权重列表
        :param model:
        :return:
        """
        weight_list = []
        for name, param in model.named_parameters():
            if 'weight' in name:
                weight = (name, param)
                weight_list.append(weight)
        return weight_list

    def regularization_loss(self, weight_list, weight_decay, p=2):
        """
        计算张量范数
        :param weight_list:
        :param p: 范数计算中的幂指数值,默认求2范数
        :param weight_decay:
        :return:
        """
        reg_loss = 0
        for name, w in weight_list:
            l2_reg = torch.norm(w, p=p)
            reg_loss = reg_loss + l2_reg

        reg_loss = weight_decay * reg_loss
        return reg_loss

    def weight_info(self, weight_list):
        """
        打印权重列表信息
        :param weight_list:
        :return:
        """
        print("---------------regularization weight---------------")
        for name, w in weight_list:
            print(name)
        print("---------------------------------------------------")

5.2 利用Regularization类添加正则化

首先初始化Regularization类,然后在计算损失时,加上Regularization类所返回的损失就可以了(其实加的就是正则化项)。实现思路如下:

weight_decay = 100.0 # 正则化参数

model = Model()
# 初始化正则化
if weight_decay>0:
   reg_loss=Regularization(model, weight_decay, p=2)
else:
   print("no regularization")


criterion= nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(),lr=learning_rate) # 注意不需要指定参数weight_decay

# 训练数据和对应的标签
batch_train_data=...
batch_train_label=...

out = model(batch_train_data)

# loss and regularization
loss = criterion(input=out, target=batch_train_label)
if weight_decay > 0:
   loss = loss + reg_loss(model)  # 添加正则化
total_loss = loss.item()

# backprop
optimizer.zero_grad() # 清除当前所有的累积梯度
total_loss.backward()
optimizer.step()

这种方法相比torch.optim优化器自带的L2正则化,没有将偏置B加入正则化,并且可以自由的选择L1或者L2正则化。通过大倍数的改变weight_decay,如果loss的变化很大,那么正则化的加入就成功了。

参考

😃😃😃

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值