目录
理论部分
实践部分
从零开始:
简洁实现:
权重衰退是一种常见的处理过拟合的方法。
之前讲过控制模型容量的方法是:
1、把模型变小点,这样参数就少;
2、让每个参数值的可选范围小一些。
那么权重衰退就是上述的第二种方法。
相比于“使用均方范数作为硬性限制”,其实最常用的是“使用均方范数作为柔性限制”。
上面的公式没有约束条件了
λ控制着正则项的重要程度,当λ为零时,正则项也就是0了,也就是说正则项不起作用了,此时等价于“使用均方范数作为硬性限制”中的θ趋于无穷(小的θ才意味着更强的正则项);
反之,当λ为无穷时,正则项也就是无穷了,也就是说正则项非常起作用,此时等价于“使用均方范数作为硬性限制”中的θ等于0,因为当θ等于零时,对于前者来说它的w也是0了。
下面将演示一下,权重衰退对最优解的影响。
假设绿线是损失l,对于损失来说绿点是最优解。黄线是罚也就是(λ/2)*||w||^2黄线的横纵坐标分别是w1和w2。
在这种情况下,虽然w~*对于损失来说是最优的,但对罚来说不是最优解,也就是说罚会对w~*有一个左下的拉动力,,直到拉到w*处时,损失对w*以及罚对w*的拉动力平衡了,这才是两者的平衡点。
罚的引入会将最优解拉向原点,对于最优的值,它的绝对值会变小,一旦绝对值变小,且把所有的最优解都向原点拉伸的话,对于整个模型来讲,模型的复杂度就会降低。
权重衰退名字的由来或许可以从上图中看出来。
我们可以回忆一下之前求梯度的方法。对带罚的式子求梯度的话会多出一项λw。
然后
把梯度的结果代入进去,得到Wt+1的整体式子。
从式子中我们可以发现,其主要“权重衰退”的“衰退”体现在wt前的系数是小于1的,因此当权重乘上一个小于1的数的话,就会做到相应的衰退。
代码:
#权重衰减是最广泛使用的正则化的技术之一
#%matplotlib inline
import torch
#from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
#像以前一样生成一些数据(人工数据集)
#训练样本,测试样本,输入数量(特征维度),批量大小
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
#真实的w和b,分别是0.01*全1的向量,b是0.05
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)#给训练数据集分配批量
test_data = d2l.synthetic_data(true_w, true_b, n_test)#构造测试数据集
test_iter = d2l.load_array(test_data, batch_size, is_train=False)#给测试数据集分配批量
#初始化模型参数
def init_params():
w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)#均值为0,方差为1,长度为200*1向量的初始化w。
b = torch.zeros(1, requires_grad=True)#
return [w, b]
#定义L2范数惩罚
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2
#定义训练代码实现
def train(lambd):
w, b = init_params()#初始化权重和偏移
net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss#lambda定义了一个 net(X)函数
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:#每次拿出x和y
#with torch.enable_grad():
l = loss(net(X), y) + lambd * l2_penalty(w)#带罚的损失
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)))
print('w的L2范数是:', torch.norm(w).item())
#忽略正则化直接训练
train(lambd=0)#当没有罚时,范数是12.877
plt.show()#从它生成的图中,我们可以看到,训练误差在一直减小,但测试误差却没怎么变,也就是说,
# 当没有罚时,它甚至把噪声都拟合得很好了,因此会导致测试误差居高不下。
#使用权重衰减
train(lambd=3)#当有罚时,范数是0.380
plt.show()#从它生成的图中,我们可以看到,训练误差在一直减小,测试误差也进行了相应的减小,也就是说,
# 当有罚时,权重的可选范围变小了,因此会过滤掉多余的噪声,这就导致测试误差会有相应的减小。
# 但当lambd参数调的过大的话,权重的可选范围会少之又少,导致本该用到的权值参数都被过滤了,这会导致欠拟合。
代码:
#简洁实现
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
#像以前一样生成一些数据
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)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)
def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs, 1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss()
num_epochs, lr = 100, 0.003
trainer = torch.optim.SGD([{
"params": net[0].weight,
'weight_decay': wd}, {#这个相当于是λ,这就不用手动加罚了
"params": net[0].bias}], lr=lr)
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:
with torch.enable_grad():
trainer.zero_grad()
l = loss(net(X), y)
l.backward()
trainer.step()
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数:', net[0].weight.norm().item())
#这些图看起来和我们从零开始实现权重衰减时的图相同
train_concise(0)
plt.show()
train_concise(3)
plt.show()
拓展:
把L2范数换成L1范数,可以尝试练习一下。
代码:
#权重衰减是最广泛使用的正则化的技术之一
#%matplotlib inline
import torch
#from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
#像以前一样生成一些数据(人工数据集)
#训练样本,测试样本,输入数量(特征维度),批量大小
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
#真实的w和b,分别是0.01*全1的向量,b是0.05
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)#给训练数据集分配批量
test_data = d2l.synthetic_data(true_w, true_b, n_test)#构造测试数据集
test_iter = d2l.load_array(test_data, batch_size, is_train=False)#给测试数据集分配批量
#初始化模型参数
def init_params():
w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)#均值为0,方差为1,长度为200*1向量的初始化w。
b = torch.zeros(1, requires_grad=True)#
return [w, b]
#定义L1范数惩罚
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#lambda定义了一个 net(X)函数
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:#每次拿出x和y
#with torch.enable_grad():
l = loss(net(X), y) + lambd * l1_penalty(w)#带罚的损失
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)))
print('w的L2范数是:', torch.norm(w).item())
#忽略正则化直接训练
train(lambd=0)#当没有罚时,范数是12.877
plt.show()#从它生成的图中,我们可以看到,训练误差在一直减小,但测试误差却没怎么变,也就是说,
# 当没有罚时,它甚至把噪声都拟合得很好了,因此会导致测试误差居高不下。
#使用权重衰减
train(lambd=3)#当有罚时,范数是0.380
plt.show()#从它生成的图中,我们可以看到,训练误差在一直减小,测试误差也进行了相应的减小,也就是说,
# 当有罚时,权重的可选范围变小了,因此会过滤掉多余的噪声,这就导致测试误差会有相应的减小。
# 但当lambd参数调的过大的话,权重的可选范围会少之又少,导致本该用到的权值参数都被过滤了,这会导致欠拟合。
w的L2范数是: 14.286995887756348
w的L2范数是: 0.07397375255823135
可以看出,对于没有罚的情况来讲,L1,L2的效果是一样的;
对于有罚的情况来讲,L1的效果好一些。