【深度学习】过拟合抑制(一)权重衰减(weight decay)

理论

在我的上一篇博文【深度学习】模型评估与选择介绍了模型的过拟合是机器学习中不可避免的挑战,那么除了在数据集规模和模型复杂度的考虑上,有没有一些其它方法可以抑制过拟合现象呢?

权重衰减(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)+λw2
λ \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=1p0.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超参数来指定权重衰减;
  • 权重衰减会抑制模型的拟合效果,包括正常拟合。
  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值