一 使用均方范数作为硬性限制
通过限制参数值的选择范围来控制模型容量:
subject to
- 通常不限制偏移b(限不限制都差不多)
- 小的意味着更强的正则项
二 使用均方范数作为柔性限制
对每个,都可以找到使得之前的目标函数等价于下面
可以通过拉格朗日乘子来证明
超参数控制了正则项的重要程度
- :无作用
之所以为柔性限制,是因为相比于硬性限制,可以通过调整超参数的值来增大或减小对loss的罚,控制正则项的重要程度,而非单纯的小于某个值。
三 参数更新法则
通常,在深度学习中通常叫做权重衰退。
不难发现,它与普通的w的时间t更新不同之处在于,前面是而非为1,即首先对 进行缩小,这就是所谓的权重衰退。
总结
- 权重衰退通过L2正则项使得模型参数不会过大,从而控制模型复杂度
- 正则项权重是控制模型复杂度的超参数
代码:从零开始实现
导入必要的模块
%matplotlib inline
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)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
#把训练和测试数据集loader进来
train_iter = d2l.load_array(train_data, batch_size)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)
初始化模型参数
def init_params():
#w均值为0,方差为1,大小为200*1向量,获得梯度
w = torch.normal(0, 1, size = (num_inputs, 1), requires_grad = True)
b = torch.zeros(1, requires_grad = True) #b为1的标量,获得梯度
return [w, b]
定义L2范数惩罚
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2 #pow是阶乘,在阶乘后再求和,最后除以2,就是L2范数的定义
定义训练代码实现
def train(lambd): #超参数lambd,后面会指定
w, b = init_params() #初始化w和b
net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss #net为简单的线性回归,损失为均方损失
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
l = loss(net(X), y) + lambd * l2_penalty(w) #变化的地方就是多加了一项:超参数lambd * L2惩罚项
l.sum().backward()
d2l.sgd([w, b], lr, batch_size) #sgd优化,传入参数、学习率
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) #没有规约(罚)
L2范数代表模型的复杂程度,L2越大越容易过拟合。
可以发现训练损失一直在减小,一直在拟合噪音,导致过于拟合,而测试损失几乎没有变化,说明模型在新的数据上的泛化能力较弱,两者之间的gap在越拉越大 。
使用权重衰减
train(lambd = 3) #添加罚
可以发现此时的测试损失在一直往下降,两者之间的gap在减小,过拟合现象有所减弱。
尝试多迭代一些轮数 或 把lambd设置的更大一些,效果可能会更好。
代码:简洁实现
前面的代码都一样。
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
#使用torch.optim.SGD定义一个优化器trainer
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'])
# 在每个epoch中,遍历train_iter,对每个小批量数据(X, y)进行以下操作
for epoch in range(num_epochs):
for X, y in train_iter:
trainer.zero_grad() #优化器梯度清零
l = loss(net(X), y) #计算损失
l.backward() #损失反向传播
trainer.step() #再使用优化器来更新模型参数
# 每5个epoch,计算训练和测试集上的损失,用animator将损失可视化
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())
对于此行:trainer = torch.optim.SGD([{"params": net[0].weight,'weight_decay': wd},{"params": net[0].bias}], lr=lr)
由于权重衰减在神经网络优化中很常用, 深度学习框架为了便于我们使用权重衰减,将权重衰减集成到优化算法中,以便与任何损失函数结合使用。
使用torch.optim.SGD定义一个优化器trainer,该优化器的参数包括网络的权重、偏差和权重衰减系数wd,以及学习率lr。在实例化优化器时直接通过weight_decay指定weight decay超参数。 默认情况下,PyTorch会同时衰减权重weight和偏移bias,但这里只为权重设置了weight_decay,所以偏置参数b不会衰减。对于一个二维曲线,bias只是让曲线整体上下移动,并不能减小模型的复杂度,所以通常不需要对bias做正则化。
忽略正则化直接训练
#lambd = 0时
train_concise(0)
使用权重衰减
#lambd = 3时
train_concise(3)
可以发现效果与从零开始实现时差不多
更多权重衰退理论知识可以看这里->权重衰退