《动手学深度学习》——4.4模型选择、权重衰减、暂退法

1.模型选择、过拟合、欠拟合

1.1模型选择

        一般来说,简单的数据使用简单的模型,复杂的数据使用复杂的模型。

  • 数据的复杂度,取决于样本个数、每个样本的元素个数、时间和空间结构、多样性。
  • 模型的复杂度,取决于参数个数、参数值范围。

        高阶多项式函数比低阶多项式函数复杂得多。 高阶多项式的参数较多,模型函数的选择范围较广。 因此在固定训练数据集的情况下, 高阶多项式函数相对于低阶多项式的训练误差应该始终更低(最坏也是相等)。

        同时,随着训练数据量的增加,泛化误差通常会减小。

1.2数据集和误差

        通常把一个数据集分为训练数据集、验证数据集、测试数据。

        模型的超参数(除了权重和偏置外的参数)根据模型在验证数据集上的性能进行调整,最后再用于测试数据集。

        训练误差(training error)是指, 模型在训练数据集上计算得到的误差。 泛化误差(generalization error)是指, 模型应用在验证集上的误差。

1.3K-折交叉验证

        当训练数据集样本较少,可以采用该方法,具体操作:原始训练数据被分成K个不重叠的子集。 然后执行K次模型训练和验证,每次在K-1个子集上进行训练, 并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。 最后,通过对K次实验的结果取平均来估计训练和验证误差。

        该方法不是用来确定超参数,而是评估该超参数下该模型的性能。

1.4过拟合、欠拟合

        欠拟合:模型太简单了,根本预测不正确。(做奥数题,我只会1+1=2,对于高等数据的题我只会乱写)

  • 训练误差和验证误差都很严重, 但它们之间仅有一点差距。 如果模型不能降低训练误差,这可能意味着模型过于简单(即表达能力不足), 无法捕获试图学习的模式。 此外,由于我们的训练和验证误差之间的泛化误差很小, 我们有理由相信可以用一个更复杂的模型降低训练误差。 这种现象被称为欠拟合(underfitting)。

        过拟合:我需要抽象派、简笔画。你给我整个写实派。

  • 当我们的训练误差明显低于验证误差时要小心, 这表明严重的过拟合(overfitting)。 注意,过拟合并不总是一件坏事。 特别是在深度学习领域,众所周知, 最好的预测模型在训练数据上的表现往往比在保留(验证)数据上好得多。 最终,我们通常更关心验证误差,而不是训练误差和验证误差之间的差距。

2.权重衰减

        正则化是处理过拟合的常用方法:在训练集的损失函数中加入惩罚项,以降低学习到的模型的复杂度。

        保持模型简单的一个特别的选择是使用L2惩罚的权重衰减,这会导致学习算法更新步骤中的权重衰减。(L2范数:向量平方和的平方根)

        要保证权重向量比较小, 最常用方法是将其范数作为惩罚项加到最小化损失的问题中。 将原来的训练目标最小化训练标签上的预测损失, 调整为最小化预测损失和惩罚项之和。 (后面加入的惩罚项没有特别的意义,只是为了美观和简单。其中\lambda为超参数)

L(w, b) +\frac{\lambda }{2}||w||^2

2.1案例

        设计一个过拟合的案例,以查看惩罚项是否有效。

2.1.1生成数据

%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l

#为了使过拟合的效果更加明显,我们可以将问题的维数增加到d = 200, 并使用一个只包含20个样本的小训练集。
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)

2.1.2定义初始化模型参数和惩罚项

#随机初始化模型参数
def init_params():
    w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]

#定义范数惩罚
def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2

2.1.3定义训练代码

def train(lambd): #lambd为超参数
    w, b = init_params()
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss #线性网络和平方损失
    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:
            # 增加了L2范数惩罚项,
            # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
            l = loss(net(X), y) + lambd * l2_penalty(w)
            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())

2.1.4开始训练

        惩罚项无效时:训练误差一直在减小,而测试误差基本不变,说明了过拟合。

train(lambd=0)

        惩罚项有效时:可以明显看到测试误差逐渐减小。

train(lambd=3)

代码简洁实现

def train_concise(wd):
    net = nn.Sequential(nn.Linear(num_inputs, 1))
    for param in net.parameters():
        param.data.normal_()
    loss = nn.MSELoss(reduction='none')
    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:
            trainer.zero_grad()
            l = loss(net(X), y)
            l.mean().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())

3.暂退法(dropout)

        暂退法又叫丢弃法,可以理解为将一部分输出结果丢弃掉,再进入下一层。这是用于防止模型过拟合,它通常与控制权重向量的维数和大小结合使用的。

        暂退法将活性值(h)替换为具有期望值(h)的随机变量。(因此,隐藏层的输出要么是0,要么要除以1-p,确保期望不变)

  • 暂退法常作用在多层感知机的隐藏层输出上。

  • 丢弃概率是控制模型复杂度的超参数。(常为0.1,0.5,0.9)

  • 丢弃法是一种正则化,只在训练中使用:他们影响模型参数的更新。而不用于预测。

3.1案例

        案列:在隐藏层后面加入一个丢弃层。

        定义一个丢弃层。

 # torch.rand(X.shape) 随机生成X形状的0~1之间的值
 # torch.rand(X.shape) > dropout,这些值若大于dropout则为True,否则为False
 # 为了满足期望不变,所以x/(1-p)

import torch
from torch import nn
from d2l import torch as d2l


def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    # 在本情况中,所有元素都被丢弃
    if dropout == 1:
        return torch.zeros_like(X)
    # 在本情况中,所有元素都被保留
    if dropout == 0:
        return X
    mask = (torch.rand(X.shape) > dropout).float()
    return mask * X / (1.0 - dropout)

训练时才使用dropout。

X= torch.arange(16, dtype = torch.float32).reshape((2, 8))

num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

dropout1, dropout2 = 0.2, 0.5

class Net(nn.Module):
    def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,
                 is_training = True):
        super(Net, self).__init__()
        self.num_inputs = num_inputs
        self.training = is_training
        self.lin1 = nn.Linear(num_inputs, num_hiddens1)
        self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
        self.lin3 = nn.Linear(num_hiddens2, num_outputs)
        self.relu = nn.ReLU()

    def forward(self, X):
        H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
        # 只有在训练模型时才使用dropout
        if self.training == True:
            # 在第一个全连接层之后添加一个dropout层
            H1 = dropout_layer(H1, dropout1)
        H2 = self.relu(self.lin2(H1))
        if self.training == True:
            # 在第二个全连接层之后添加一个dropout层
            H2 = dropout_layer(H2, dropout2)
        out = self.lin3(H2)
        return out


net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

         进行训练,上边是dropout都为0,即不丢弃的情况,下边是丢弃的情况。

        可以看到,原来训练误差很小,之后训练误差稍微大了点。确实有防止过拟合的效果。

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值