李沐大神源代码是用Jupyter写的,笔者想用Pycharm实现并仅作为学习笔记,如有侵权,请联系笔者删除。
权重衰退是最广泛使用的正则化技术之一,一般有L1正则化和L2正则化,这里就不详细介绍了,具体看这位大佬的博客吧:点这里。 其主要思想就是通过在损失函数中添加正则项来让参数的取值变小,因为训练数据中常常有噪音,而噪音越大,在训练中w把这些噪音数据也学习了,w也会越大(w越大这点可以证明,但沐神没说,我也不会,哈哈),所以需要对w进行惩罚,来让w的值变小。
python版本:3.8.6
torch版本:1.11.0
d2l版本:0.17.5
含有笔者自己的注释。
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)
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)
如何查看其数据的代码如下:
# 查看数据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太多了就没有显示了,有兴趣自己运行一下吧。
# 初始化参数
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)
可以看出:
当lambd=0时,正则化相当于没有,所以虽然训练误差在不断减少,但测试误差很大并且没有变动;
当lambd=3时,正则化起到了让权值变小的作用,所以测试误差也在减少。