第十三课 权重衰退

目录

理论部分

实践部分

从零开始:

简洁实现:


理论部分

权重衰退是一种常见的处理过拟合的方法。

之前讲过控制模型容量的方法是:

1、把模型变小点,这样参数就少;

2、让每个参数值的可选范围小一些。

那么权重衰退就是上述的第二种方法。

pytorch改变样本权重_python

相比于“使用均方范数作为硬性限制”,其实最常用的是“使用均方范数作为柔性限制”。

pytorch改变样本权重_深度学习_02

上面的公式没有约束条件了

λ控制着正则项的重要程度,当λ为零时,正则项也就是0了,也就是说正则项不起作用了,此时等价于“使用均方范数作为硬性限制”中的θ趋于无穷(小的θ才意味着更强的正则项);

反之,当λ为无穷时,正则项也就是无穷了,也就是说正则项非常起作用,此时等价于“使用均方范数作为硬性限制”中的θ等于0,因为当θ等于零时,对于前者来说它的w也是0了。

下面将演示一下,权重衰退对最优解的影响。

pytorch改变样本权重_pytorch改变样本权重_03

假设绿线是损失l,对于损失来说绿点是最优解。黄线是罚也就是(λ/2)*||w||^2黄线的横纵坐标分别是w1和w2。

在这种情况下,虽然w~*对于损失来说是最优的,但对罚来说不是最优解,也就是说罚会对w~*有一个左下的拉动力,,直到拉到w*处时,损失对w*以及罚对w*的拉动力平衡了,这才是两者的平衡点。

罚的引入会将最优解拉向原点,对于最优的值,它的绝对值会变小,一旦绝对值变小,且把所有的最优解都向原点拉伸的话,对于整个模型来讲,模型的复杂度就会降低。

pytorch改变样本权重_深度学习_04

权重衰退名字的由来或许可以从上图中看出来。

我们可以回忆一下之前求梯度的方法。对带罚的式子求梯度的话会多出一项λw。

然后

pytorch改变样本权重_pytorch改变样本权重_05

把梯度的结果代入进去,得到Wt+1的整体式子。

从式子中我们可以发现,其主要“权重衰退”的“衰退”体现在wt前的系数是小于1的,因此当权重乘上一个小于1的数的话,就会做到相应的衰退。

pytorch改变样本权重_深度学习_06

实践部分

pytorch改变样本权重_pytorch_07

从零开始:

pytorch改变样本权重_人工智能_08

pytorch改变样本权重_python_09

pytorch改变样本权重_python_10

pytorch改变样本权重_深度学习_11

代码:

#权重衰减是最广泛使用的正则化的技术之一
#%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参数调的过大的话,权重的可选范围会少之又少,导致本该用到的权值参数都被过滤了,这会导致欠拟合。

简洁实现:

pytorch改变样本权重_pytorch_12

pytorch改变样本权重_深度学习_13

pytorch改变样本权重_pytorch_14

pytorch改变样本权重_pytorch改变样本权重_15

代码:

#简洁实现
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

pytorch改变样本权重_pytorch_16

 

pytorch改变样本权重_python_17