【动手学深度学习Pycharm实现6】权重衰退(正则化)以及查看DataLoader返回的数据

前言

李沐大神源代码是用Jupyter写的,笔者想用Pycharm实现并仅作为学习笔记,如有侵权,请联系笔者删除。


一、简介

权重衰退是最广泛使用的正则化技术之一,一般有L1正则化和L2正则化,这里就不详细介绍了,具体看这位大佬的博客吧:点这里。 其主要思想就是通过在损失函数中添加正则项来让参数的取值变小,因为训练数据中常常有噪音,而噪音越大,在训练中w把这些噪音数据也学习了,w也会越大(w越大这点可以证明,但沐神没说,我也不会,哈哈),所以需要对w进行惩罚,来让w的值变小。

二、权重衰退从0开始代码

python版本:3.8.6
torch版本:1.11.0
d2l版本:0.17.5

含有笔者自己的注释。

2.1 准备训练数据与测试数据:

import torch
from torch import nn
from d2l import torch as d2l

n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)            # 生成人造训练数据集
train_iter = d2l.load_array(train_data, batch_size)                 # 训练数据集中选出batch_size的数据(数据包含features,labels)
test_data = d2l.synthetic_data(true_w, true_b, n_test)              # 生成人造测试数据集
test_iter = d2l.load_array(test_data, batch_size, is_train=False)   # 测试数据集中选出batch_size的数据(数据包含features,labels)

2.1.1 查看上面代码中的load_array函数

torch中的load_array的函数如下,可以看出先转化为张量再调用DataLoader返回

def load_array(data_arrays, batch_size, is_train=True):
    """Construct a PyTorch data iterator.

    Defined in :numref:`sec_linear_concise`"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

2.1.2 查看DataLoader返回的数据

如何查看其数据的代码如下:

# 查看数据load_array返回的train_iter
for step,(train_iter_features, train_iter_labels) in enumerate(train_iter):
    print(step)                       # 步数
    print(train_iter_features.shape)  # train_iter的shape,5*200 所以是batch_size * num_inputs,也就是5个数据输入
    print(train_iter_labels)          # train_iter的标签
    break

结果如下:
在这里插入图片描述
代码中的train_iter_features太多了就没有显示了,有兴趣自己运行一下吧。

2.2 正则化函数定义以及训练代码


# 初始化参数
def init_params():
    w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)  # 均值为0,方差为1的正态分布,大小为200*1
    b = torch.zeros(1, requires_grad=True)  # 偏置项
    return [w, b]

# L2正则化,通过缩小w的取值范围,防止模型过拟合
def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2

"""
L1正则化,对同样的数据集,测试数据误差小很多,但是过程比较“崎岖”,
不像L2那样平滑,原因可能是这里的"惩罚"小一点,w值的取值范围更大,抗干扰能力较弱
"""
def l1_penalty(w):
    return torch.sum(torch.abs(w))

def train(lambd):
    w, b = init_params()
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
    num_epochs, lr = 100, 0.003
    animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
                            xlim=[5, num_epochs], legend=['train', 'test'])
    for epoch in range(num_epochs):
        for X, y in train_iter:
            # 增加了L2范数惩罚项,
            # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
            print(loss(net(X), y))
            l = loss(net(X), y) + lambd * l2_penalty(w)   # 在损失函数中添加L2正则项
            l.sum().backward()         # 损失求和后再求梯度
            d2l.sgd([w, b], lr, batch_size)
        if (epoch + 1) % 5 == 0:
            animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
                                     d2l.evaluate_loss(net, test_iter, loss)))
    d2l.plt.show()
    print('w的L2范数是:', torch.norm(w).item())

train(lambd=0)

train(lambd=5)

权重衰退从0开始总代码如下:

import torch
from torch import nn
from d2l import torch as d2l

n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)            # 生成人造训练数据集
train_iter = d2l.load_array(train_data, batch_size)                 # 训练数据集中选出batch_size的数据(数据包含features,labels)
test_data = d2l.synthetic_data(true_w, true_b, n_test)              # 生成人造测试数据集
test_iter = d2l.load_array(test_data, batch_size, is_train=False)   # 测试数据集中选出batch_size的数据(数据包含features,labels)

# 查看数据load_array返回的train_iter
for step,(train_iter_features, train_iter_labels) in enumerate(train_iter):
    print(step)                       # 步数
    # print(train_iter_features)        # train_iter的数据
    print(train_iter_features.shape)  # train_iter的shape,5*200 所以是batch_size * num_inputs,也就是5个数据输入
    print(train_iter_labels)          # train_iter的标签
    break

# 初始化参数
def init_params():
    w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)  # 均值为0,方差为1的正态分布,大小为200*1
    b = torch.zeros(1, requires_grad=True)  # 偏置项
    return [w, b]

# L2正则化,通过缩小w的取值范围,防止模型过拟合
def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2

"""
L1正则化,对同样的数据集,测试数据误差小很多,但是过程比较“崎岖”,
不像L2那样平滑,原因可能是这里的"惩罚"小一点,w值的取值范围更大,抗干扰能力较弱
"""
def l1_penalty(w):
    return torch.sum(torch.abs(w))

def train(lambd):
    w, b = init_params()
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
    num_epochs, lr = 100, 0.003
    animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
                            xlim=[5, num_epochs], legend=['train', 'test'])
    for epoch in range(num_epochs):
        for X, y in train_iter:
            # 增加了L2范数惩罚项,
            # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
            l = loss(net(X), y) + lambd * l2_penalty(w)   # 在损失函数中添加L2正则项
            l.sum().backward()         # 损失求和后再求梯度
            d2l.sgd([w, b], lr, batch_size)
        if (epoch + 1) % 5 == 0:
            animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
                                     d2l.evaluate_loss(net, test_iter, loss)))
    d2l.plt.show()
    print('w的L2范数是:', torch.norm(w).item())

train(lambd=0)

train(lambd=5)

三、运行结果如下

1.lambd=0:

在这里插入图片描述

2.lambd=3:

在这里插入图片描述

可以看出:
当lambd=0时,正则化相当于没有,所以虽然训练误差在不断减少,但测试误差很大并且没有变动;
当lambd=3时,正则化起到了让权值变小的作用,所以测试误差也在减少。


猜你喜欢

转载自blog.csdn.net/weixin_45887062/article/details/124562029