目录
权重衰退是一种常见的处理过拟合的方法。
之前讲过控制模型容量的方法是:
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的效果好一些。