权重衰减
应对过拟合问题的常用方法:权重衰减(weight decay
)
方法
权重衰减等价于\(L_2\)范数正则化(regularization
)
\(L_2\)范数正则化在模型原损失函数基础上添加\(L_2\)范数惩罚项,从而得到训练所需要最小化的函数.\(L_2\)范数惩罚项指的是模型权重参数每个元素的平方和与一个正的常数的乘积。
带有\(L_2\)范数惩罚项的新损失函数为
\[\ell(w_1, w_2, b) + \frac{\lambda}{2} \|\boldsymbol{w}\|^2, \]其中超参数\(\lambda > 0\)。
权重\(w_1\)和\(w_2\)的迭代方式更改为
\(L_2\)范数正则化令权重\(w_1\)和\(w_2\)先自乘小于1的数,再减去不含惩罚项的梯度。\(L_2\)范数正则化又叫权重衰减。权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能对过拟合有效。
高维线性回归实验
设数据样本特征的维度为\(p\)。对于训练数据集和测试数据集中特征为\(x_1, x_2, \ldots, x_p\)的任一样本,我们使用如下的线性函数来生成该样本的标签:
\[y = 0.05 + \sum_{i = 1}^p 0.01x_i + \epsilon, \]其中噪声项\(\epsilon\)服从均值为0、标准差为0.01的正态分布。考虑高维线性回归问题,如设维度\(p=200\);同时,把训练数据集的样本数设低,如20。
#导包
%matplotlib inline
import d2lzh as d2l
from mxnet import autograd, gluon, init, nd
from mxnet.gluon import data as gdata, loss as gloss, nn
#训练集大小:20,测试集大小:100,输入大小:200,也就是权重个数是200
n_train, n_test, num_inputs = 20, 100, 200
#真实权重是(200,1) * 0.01,真实偏置b = 0.05
true_w, true_b = nd.ones((num_inputs, 1)) * 0.01, 0.05
#初始化特征,按照正态分布随机生成shape是(120,200)
features = nd.random.normal(shape=(n_train + n_test, num_inputs))
#生成标签(120,200) * (200,1) + 真实偏置
labels = nd.dot(features, true_w) + true_b
#加上噪声
labels += nd.random.normal(scale=0.01, shape=labels.shape)
#得到训练特征,测试特征
train_features, test_features = features[:n_train, :], features[n_train:, :]
#得到训练标签,测试标签
train_labels, test_labels = labels[:n_train], labels[n_train:]
从零开始实现
初始化模型参数
定义随机初始化模型参数的函数。该函数为每个参数都附上梯度。
def init_params():
#随机初始化权重shape(200,1)
w = nd.random.normal(scale=1, shape=(num_inputs, 1))
#初始化偏置为0
b = nd.zeros(shape=(1,))
#申请内存
w.attach_grad()
b.attach_grad()
#返回权重、偏置
return [w, b]
定义 \(L_2\) 范数惩罚项
只惩罚模型的权重参数。
def l2_penalty(w):
return (w**2).sum() / 2
定义训练和测试
#mini数据的大小是1,迭代次数100,学习率0.003
batch_size, num_epochs, lr = 1, 100, 0.003
#调用已经保存的函数,net是dot,loss是计算损失
net, loss = d2l.linreg, d2l.squared_loss
#读取batch_size数据
train_iter = gdata.DataLoader(gdata.ArrayDataset(
train_features, train_labels), batch_size, shuffle=True)
#定义函数,lambd是超参数lamda
def fit_and_plot(lambd):
#获取初始w,b
w, b = init_params()
train_ls, test_ls = [], []
#迭代num_epoch=100次
for _ in range(num_epochs):
#得到特征、标签
for X, y in train_iter:
#记录偏导
with autograd.record():
# 添加了L2范数惩罚项,广播机制使其变成长度为batch_size的向量
#lambd = \lambda
#l2_penalty就是l2惩罚值
l = loss(net(X, w, b), y) + lambd * l2_penalty(w)
l.backward()
#参数迭代
d2l.sgd([w, b], lr, batch_size)
#训练集上的损失
train_ls.append(loss(net(train_features, w, b),
train_labels).mean().asscalar())
#测试集上的损失
test_ls.append(loss(net(test_features, w, b),
test_labels).mean().asscalar())
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
#
print('L2 norm of w:', w.norm().asscalar())
观察过拟合
当lambd
设为0
时,没有使用权重衰减。结果训练误差远小于测试集上的误差。
fit_and_plot(lambd=0)
使用权重衰减
可以看出,训练误差虽然有所提高,但测试集上的误差有所下降。
fit_and_plot(lambd=3)
简洁实现
def fit_and_plot_gluon(wd):
#实例化nn
net = nn.Sequential()
#添加一个输出全连接层
net.add(nn.Dense(1))
#初始化参数,通过调用init对象
net.initialize(init.Normal(sigma=1))
# 对权重参数衰减。权重名称一般是以weight结尾
net = nn.Sequential()
help(net.collect_params)
返回一个包含这个块的参数字典和它所有子参数字典,同时可以返回满足一些给定规则的参数字典
Returns a ParameterDict containing this Block and all of its children’s Parameters(default)
, also can returns the select ParameterDict which match some given regular expressions.
选择所有以'weight','bias'为结尾的参数
collect all parameters whose names end with ‘weight’ or ‘bias’, this can be done using regular expressions:
model.collect_params('.*weight|.*bias')
help(gluon.Trainer)
优化器参数(字典),使用关键字调用优化器构造器。例如:
{学习速率:0.1}.wd(权重损失)
optimizer_params (dict) – Key-word arguments to be passed to optimizer constructor.
For example, {‘learning_rate’: 0.1}.
All optimizers accept learning_rate, wd (weight decay), clip_gradient, and lr_scheduler. See each optimizer’s constructor for a list of additional supported arguments.
trainer_w = gluon.Trainer(net.collect_params('.*weight'), 'sgd',
{'learning_rate': lr, 'wd': wd})
# 不对偏差参数衰减。偏差名称一般是以bias结尾
trainer_b = gluon.Trainer(net.collect_params('.*bias'), 'sgd',
{'learning_rate': lr})
#保存训练loss和测试loss
train_ls, test_ls = [], []
#训练迭代次数
for _ in range(num_epochs):
#将训练集特征和标签取出
for X, y in train_iter:
with autograd.record():
#计算loss
l = loss(net(X), y)
#backward()
l.backward()
# 对两个Trainer实例分别调用step函数,从而分别更新权重和偏差
trainer_w.step(batch_size)
trainer_b.step(batch_size)
#计算训练loss的平均值
train_ls.append(loss(net(train_features),
train_labels).mean().asscalar())
#计算测试loss的平均值
test_ls.append(loss(net(test_features),
test_labels).mean().asscalar())
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
#打印出权重的L2范数值
print('L2 norm of w:', net[0].weight.data().norm().asscalar())
调用
fit_and_plot_gluon(0)
fit_and_plot_gluon(3)