理论
在我的上一篇博文【深度学习】模型评估与选择介绍了模型的过拟合是机器学习中不可避免的挑战,那么除了在数据集规模和模型复杂度的考虑上,有没有一些其它方法可以抑制过拟合现象呢?
权重衰减(weight decay) 是一种常用的应对过拟合的方法,其等价于 L 2 L_2 L2范数正则化(regularization)。正则化通过为模型损失函数添加惩罚项使得学出的模型参数较小,通常接近于0。
L
2
L_2
L2范数正则化在模型原来的损失函数基础上增加
L
2
L_2
L2范数惩罚项,从而得到训练所需要的损失函数。
L
2
L_2
L2惩罚项是指模型参数每个元素的平方和与一个正的常数的乘积。比如模型原有的损失函数我们记为:
l
(
w
,
b
)
l(\boldsymbol w,\boldsymbol b)
l(w,b),则增加了
L
2
L_2
L2惩罚项的新的损失函数记为:
l
(
w
,
b
)
+
λ
∣
∣
w
∣
∣
2
l(\boldsymbol w,\boldsymbol b)+\lambda||\boldsymbol w||^2
l(w,b)+λ∣∣w∣∣2
λ
\lambda
λ为引入的超参数,且
λ
>
0
\lambda > 0
λ>0。特殊值
λ
=
0
\lambda=0
λ=0时,惩罚项不起作用。
双层感知机实验
这里,我们仍然使用【深度学习】模型评估与选择一文中双层感知机在MNIST数据集上的实验来观察梯度衰减的效果。权重衰减可以通过Gluon的wd
参数来指定。
# 定义优化算法:小批量随机梯度下降
lambd = 0 # 惩罚项系数
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.3, 'wd': lambd})
另外,我们打印出参数的 L 2 L_2 L2范数,以此来观察添加惩罚项前后参数的大小变化:
# 打印参数的L2范数
print("L2 norm of params: ", net[0].weight.data().norm().asscalar())
过拟合
首先,设置lambd=0
(实验1),即惩罚项不起作用,可以得到和【深度学习】模型评估与选择中一致的过拟合结果:
这时模型参数的
L
2
L_2
L2范数为:
权重衰减
设置lambd=0.05
(实验二),即惩罚项起作用,可以得到如下实验结果:
这时模型参数的
L
2
L_2
L2范数为:
从上图中可以看到:
- 此时训练误差和测试误差接近,但是误差较大,可以认为模型欠拟合;
- 参数 L 2 L_2 L2范数为2.7,参数值和不使用惩罚项时相比,各个元素更加接近0。
难道是在lambd=0.05
时权重衰减的抑制作用大大了,都使得模型欠拟合了?
我们进一步调整权重衰减的超参数
λ
\lambda
λ。设置lambd=0.01
(实验三),结果如下:
模型参数的
L
2
L_2
L2范数为:
这时的结果较之lambd=0.05
时模型拟合效果更好(误差更小),同时参数值变大了,其
L
2
L_2
L2范数为6.0。
小结及延伸
在上述使用权重衰减来观察模型过拟合现象抑制的实验中,可以观察到权重衰减对于过拟合的抑制作用,但同时也可以看到权重衰减也会影响模型的正常拟合。并且可以观察到,lambd
越大,对于模型的拟合抑制得越厉害。
另外,可以观察到,实验二和实验三的测试误差几乎等于实验一测试误差的两倍,即模型的拟合效果不好,那是不是权重衰减会使得结果变坏呢?肯定不是啦,这是因为实验一本身只出现了轻微的过拟合现象,实验二和三使用了权重衰减,使得拟合效果被抑制(抑制了正常的拟合),所以误差比较大。
对此,有兴趣的筒子们可以设定更加合适的实验条件,比如:使用高维线性回归模型拟合低维模型,并且使得训练数据集很小。这时过拟合现象将会非常明显,权重衰减的作用也将更加明显。
线性回归实验
我们对如下高维线性回归模型进行实验:
y
=
0.05
+
∑
i
=
1
p
0.01
x
i
y=0.05+\sum_{i=1}^p0.01x_i
y=0.05+i=1∑p0.01xi
我们在生成数据集时,增加一个 ϵ \epsilon ϵ噪声项,其服从均值为0标准差为0.01的正态分布。实验中上式 p = 200 p=200 p=200,且训练数据集规模设置为很小的20个样本。由于上面的MNIST数据集实验使用了MXNet深度学习框架,为了更好地展示权重衰减的原理,这里不使用框架,从0开始实现。
# coding=utf-8
# author: BebDong
# 2018/12/25
from mxnet.gluon import data as gdata
from mxnet import autograd, nd
from matplotlib import pyplot as plt
from IPython import display
import pylab
# 作图
def semilogy(x_vals, y_vals, x_label, y_label, x2_vals=None, y2_vals=None,
legend=None, figsize=(3.5, 2.5)):
# 矢量图显示并设置大小
display.set_matplotlib_formats('svg')
plt.rcParams['figure.figsize'] = figsize
# 画图
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.semilogy(x_vals, y_vals)
if x2_vals and y2_vals:
plt.semilogy(x2_vals, y2_vals, linestyle=":")
plt.legend(legend)
pylab.show()
# 初始化参数
def init_params(num_inputs):
w = nd.random.normal(scale=0.1, shape=(num_inputs, 1))
b = nd.zeros(shape=(1, ))
w.attach_grad()
b.attach_grad()
return [w, b]
# L2范数惩罚项
def l2_penalty(w):
return (w ** 2).sum() / 2
# 线性回归模型
def linreg(X, w, b):
return nd.dot(X, w) + b
# 平方损失函数
def squared_loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
# 小批量随机梯度下降更新参数
def sgd(params, lr, batch_size):
"""Mini-batch stochastic gradient descent."""
for param in params:
param[:] = param - lr * param.grad / batch_size
# 模型定义
n_train, n_test, num_inputs = 20, 100, 200
true_w, true_b = nd.ones((num_inputs, 1)) * 0.01, 0.05
# 生成数据集
features = nd.random.normal(shape=(n_train + n_test, num_inputs))
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:]
# 指定参数进行实验
batch_size, epochs, lr = 1, 100, 0.003
train_iter = gdata.DataLoader(gdata.ArrayDataset(train_features, train_labels), batch_size, shuffle=True)
lambd = 0
# lambd = 3
w, b = init_params(num_inputs)
train_loss, test_loss = [], []
for i in range(epochs):
for X, y in train_iter:
with autograd.record():
# 添加L2惩罚项
l = squared_loss(linreg(X, w, b), y) + lambd * l2_penalty(w)
l.backward()
sgd([w, b], lr, batch_size)
train_loss.append(squared_loss(linreg(train_features, w, b), train_labels).mean().asscalar())
test_loss.append(squared_loss(linreg(test_features, w, b), test_labels).mean().asscalar())
# 作图
print('L2 norm of params: ', w.norm().asscalar())
semilogy(range(1, epochs + 1), train_loss, 'epoch', 'loss', range(1, epochs + 1), test_loss, ['train', 'test'])
过拟合现象
设置权重衰减超参数lambd=0
,即惩罚项不起作用,可观察到如下明显的过拟合现象:
权重衰减
设置权重衰减超参数lambd=3
,可观察到如下明显的过拟合抑制现象:
此时模型参数的
L
2
L_2
L2范数:
总结
- 正则化通过为损失函数添加惩罚项使得学习到的模型参数较小,是应对过拟合的常用方法;
- 权重衰减等价于 L 2 L_2 L2范数正则化,通常会使学习到的权重参数较接近0;
- 使用MXNet深度学习框架,可以通过Gluon的
wd
超参数来指定权重衰减; - 权重衰减会抑制模型的拟合效果,包括正常拟合。