[深度学习]动手学深度学习笔记-4

Task2——过拟合、欠拟合及其解决方案
在这里插入图片描述

4.1 欠拟合与过拟合的概念

  1. 欠拟合:模型拟合不够,在训练集(training set)上表现效果差,没有充分的利用数据,预测的准确度低。
  2. 过拟合:模型过度拟合,在训练集(training set)上表现好,但是在测试集上效果差,也就是说在已知的数据集合中非常好,但是在添加一些新的数据进来训练效果就会差很多,造成这样的原因是考虑影响因素太多,超出自变量的维度过于多了。

在表征线性回归模型的下面三张图中,左图使用一条直线来做预测模型,很明显无论如何调整起始点和斜率,该直线都不可能很好的拟合给定的五个训练样本,更不要说给出的新数据(欠拟合);右图使用了高阶的多项式,过于完美的拟合了训练样本,当给出新数据时,很可能会产生较大误差(过拟合);而中间的模型则刚刚好,既较完美的拟合训练数据,又不过于复杂,基本上描绘清晰了在预测房屋价格时Size和Prize的关系(正常)。
在这里插入图片描述
逻辑回归中亦如此:
在这里插入图片描述

4.2 欠拟合与过拟合的解决方法

4.2.1 欠拟合

❶引入新的特征
❷添加多项式特征
❸减少正则化参数
欠拟合在训练集上表现就不太好,解决欠拟合可以说就是增加模型复杂度。

4.2.2 过拟合

❶获取更多数据
这是解决过拟合最有效的方法,只要给足够多的数据,让模型训练到尽可能多的例外情况,它就会不断修正自己,从而得到更好的结果。
❷减少特征变量
前面说了,过拟合主要使有两个原因造成的:数据太少+模型太复杂。所以我们可以通过使用合适复杂度的模型来防止过拟合问题,让其足够拟合真正的规则,同时又不至于拟合太多抽样误差。
①减少网络的层数、神经元的个数等均可以限制网络的拟合能力;
②Early stopping早停止
❸限制权值(正则化)
正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常用手段。
L 2 L_2 范数正则化在模型原损失函数基础上添加 L 2 L_2 范数惩罚项,从而得到训练所需要最小化的函数。 L 2 L_2 范数惩罚项指的是模型权重参数每个元素的平方和与一个正的常数的乘积。以3.1节(线性回归)中的线性回归损失函数

( w 1 , w 2 , b ) = 1 n i = 1 n 1 2 ( x 1 ( i ) w 1 + x 2 ( i ) w 2 + b y ( i ) ) 2 \ell(w_1, w_2, b) = \frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right)^2

为例,其中 w 1 , w 2 w_1, w_2 是权重参数, b b 是偏差参数,样本 i i 的输入为 x 1 ( i ) , x 2 ( i ) x_1^{(i)}, x_2^{(i)} ,标签为 y ( i ) y^{(i)} ,样本数为 n n 。将权重参数用向量 w = [ w 1 , w 2 ] \boldsymbol{w} = [w_1, w_2] 表示,带有 L 2 L_2 范数惩罚项的新损失函数为

( w 1 , w 2 , b ) + λ 2 n w 2 , \ell(w_1, w_2, b) + \frac{\lambda}{2n} \|\boldsymbol{w}\|^2,

其中超参数 λ > 0 \lambda > 0 。当权重参数均为0时,惩罚项最小。当 λ \lambda 较大时,惩罚项在损失函数中的比重较大,这通常会使学到的权重参数的元素较接近0。当 λ \lambda 设为0时,惩罚项完全不起作用。上式中 L 2 L_2 范数平方 w 2 \|\boldsymbol{w}\|^2 展开后得到 w 1 2 + w 2 2 w_1^2 + w_2^2 。有了 L 2 L_2 范数惩罚项后,在小批量随机梯度下降中,我们将线性回归一节中权重 w 1 w_1 w 2 w_2 的迭代方式更改为

w 1 ( 1 η λ B ) w 1 η B i B x 1 ( i ) ( x 1 ( i ) w 1 + x 2 ( i ) w 2 + b y ( i ) ) , w 2 ( 1 η λ B ) w 2 η B i B x 2 ( i ) ( x 1 ( i ) w 1 + x 2 ( i ) w 2 + b y ( i ) ) . \begin{aligned} w_1 &\leftarrow \left(1- \frac{\eta\lambda}{|\mathcal{B}|} \right)w_1 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_1^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right),\\ w_2 &\leftarrow \left(1- \frac{\eta\lambda}{|\mathcal{B}|} \right)w_2 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_2^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right). \end{aligned}

可见, L 2 L_2 范数正则化令权重 w 1 w_1 w 2 w_2 先自乘小于1的数,再减去不含惩罚项的梯度。因此, L 2 L_2 范数正则化又叫权重衰减。权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能对过拟合有效。实际场景中,我们有时也在惩罚项中添加偏差元素的平方和。
❹贝叶斯方法
❺结合多种模型
简而言之,训练多个模型,以每个模型的平均输出作为结果。
Bagging
简单理解,就是分段函数的概念:用不同的模型拟合不同部分的训练集。以随机森林(Rand Forests)为例,就是训练了一堆不关联的决策树。但由于训练神经网络本身需要耗费较多自由,所以一般不单独使用神经网络做Bagging。
Boosting
既然训练复杂神经网络比较慢,那我们就可以只使用简单的神经网络(层数、神经元数限制等),通过训练一系列简单的神经网络,加权平均其输出。
Dropout
这是一个很高效的方法

虚线处是随机丢失的隐藏单元。由于在训练中隐藏层神经元的丢弃是随机的,即 h 1 , , h 5 h_1, \ldots, h_5 都有可能被清零,输出层的计算无法过度依赖 h 1 , , h 5 h_1, \ldots, h_5 中的任一个,从而在训练模型时起到正则化的作用,并可以用来应对过拟合。

4.3 代码实现

4.3.1 正则化

导入相应的包

%matplotlib inline
import torch
import torch.nn as nn
import numpy as np
import sys
sys.path.append("..") 
import d2lzh_pytorch as d2l

n_train, n_test, num_inputs = 20, 100, 200
true_w, true_b = torch.ones(num_inputs, 1) * 0.01, 0.05

features = torch.randn((n_train + n_test, num_inputs))
labels = torch.matmul(features, true_w) + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
train_features, test_features = features[:n_train, :], features[n_train:, :]
train_labels, test_labels = labels[:n_train], labels[n_train:]

初始化模型参数

def init_params():
    w = torch.randn((num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]

定义 L 2 L_2 范数惩罚项

def l2_penalty(w):
    return (w**2).sum() / 2

定义训练和测试

batch_size, num_epochs, lr = 1, 100, 0.003
net, loss = d2l.linreg, d2l.squared_loss

dataset = torch.utils.data.TensorDataset(train_features, train_labels)
train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)

def fit_and_plot(lambd):
    w, b = init_params()
    train_ls, test_ls = [], []
    for _ in range(num_epochs):
        for X, y in train_iter:
            # 添加了L2范数惩罚项
            l = loss(net(X, w, b), y) + lambd * l2_penalty(w)
            l = l.sum()
            
            if w.grad is not None:
                w.grad.data.zero_()
                b.grad.data.zero_()
            l.backward()
            d2l.sgd([w, b], lr, batch_size)
        train_ls.append(loss(net(train_features, w, b), train_labels).mean().item())
        test_ls.append(loss(net(test_features, w, b), test_labels).mean().item())
    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().item())

观察过拟合

fit_and_plot(lambd=0)

输出:

L2 norm of w: 15.114808082580566

在这里插入图片描述
使用权重衰减

fit_and_plot(lambd=3)

输出:

L2 norm of w: 0.035220853984355927

在这里插入图片描述

4.3.2 Dropout

导入相应的包

%matplotlib inline
import torch
import torch.nn as nn
import numpy as np
import sys
sys.path.append("..") 
import d2lzh_pytorch as d2l

def dropout(X, drop_prob):
    X = X.float()
    assert 0 <= drop_prob <= 1
    keep_prob = 1 - drop_prob
    # 这种情况下把全部元素都丢弃
    if keep_prob == 0:
        return torch.zeros_like(X)
    mask = (torch.rand(X.shape) < keep_prob).float()
    
    return mask * X / keep_prob

我们运行几个例子来测试一下dropout函数。其中丢弃概率分别为0、0.5和1。

X = torch.arange(16).view(2, 8)
dropout(X, 0)
dropout(X, 0.5)
dropout(X, 1.0)

定义模型参数

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

W1 = torch.tensor(np.random.normal(0, 0.01, size=(num_inputs, num_hiddens1)), dtype=torch.float, requires_grad=True)
b1 = torch.zeros(num_hiddens1, requires_grad=True)
W2 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens1, num_hiddens2)), dtype=torch.float, requires_grad=True)
b2 = torch.zeros(num_hiddens2, requires_grad=True)
W3 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens2, num_outputs)), dtype=torch.float, requires_grad=True)
b3 = torch.zeros(num_outputs, requires_grad=True)

params = [W1, b1, W2, b2, W3, b3]

定义模型

drop_prob1, drop_prob2 = 0.2, 0.5

def net(X, is_training=True):
    X = X.view(-1, num_inputs)
    H1 = (torch.matmul(X, W1) + b1).relu()
    if is_training:  # 只在训练模型时使用丢弃法
        H1 = dropout(H1, drop_prob1)  # 在第一层全连接后添加丢弃层
    H2 = (torch.matmul(H1, W2) + b2).relu()
    if is_training:
        H2 = dropout(H2, drop_prob2)  # 在第二层全连接后添加丢弃层
    return torch.matmul(H2, W3) + b3
# 本函数已保存在d2lzh_pytorch
def evaluate_accuracy(data_iter, net):
    acc_sum, n = 0.0, 0
    for X, y in data_iter:
        if isinstance(net, torch.nn.Module):
            net.eval() # 评估模式, 这会关闭dropout
            acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
            net.train() # 改回训练模式
        else: # 自定义的模型
            if('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
                # 将is_training设置成False
                acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
            else:
                acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
        n += y.shape[0]
    return acc_sum / n

训练和测试模型

num_epochs, lr, batch_size = 5, 100.0, 256
loss = torch.nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)

输出

epoch 1, loss 0.0044, train acc 0.574, test acc 0.648
epoch 2, loss 0.0023, train acc 0.786, test acc 0.786
epoch 3, loss 0.0019, train acc 0.826, test acc 0.825
epoch 4, loss 0.0017, train acc 0.839, test acc 0.831
epoch 5, loss 0.0016, train acc 0.849, test acc 0.850

推荐一篇大佬的文章

发布了24 篇原创文章 · 获赞 11 · 访问量 708

猜你喜欢

转载自blog.csdn.net/qq_42662568/article/details/104377090