动手学深度学习之过拟合与欠拟合

曾听人说过,DL强大之处在于它的拟合能力,只要你能给出的曲线,其方程都能用一套神经网络表示。不过,这套神经网络需要足够的数据以供训练,这里就引出了过拟合和欠拟合的概念。当神经网络很庞大,数据却不多,神经网络能够记住每个数据的特征,这会导致过拟合。反之,当神经网络规模较小或拟合能力还很弱,数据却很多时,就会出现欠拟合问题。

过拟合、欠拟合

  1. 训练误差和泛化误差
    训练误差指模型在训练数据集上表现出的误差;
    测试误差指模型在任意一个测试数据样本上表现出的误差的期望,并常常通过测试数据集上的误差来近似。
  2. 模型选择
    在训练过程中,为了得到较好的参数,我们需要一份验证数据集。这通常从训练数据集划分得到。划分的方法通常用K折交叉验证的方法。一般做论文,K=10。最终的结果都是取平均。
  3. 过拟合、欠拟合
    从训练误差和泛化误差的表现上看,模型无法得到较低的训练误差,我们将这一现象称作欠拟合(underfitting);模型的训练误差远小于它在测试数据集上的误差,我们称该现象为过拟合(overfitting)。出现这两种现象通常跟模型复杂度和训练数据集大小有关。

多项式函数拟合实验

在模型为n阶多项式的情况下(n越多,模型复杂度越高),给定训练数据集,模型复杂度和误差之间的关系如下图所示:

在这里插入图片描述

这里值得一提的是,一般在DL中,训练数据集总是不够的而模型却足够强,所以出现过拟合的情况会更多。

%matplotlib inline
import torch
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
print(torch.__version__)

#初始化模型参数
n_train, n_test, true_w, true_b = 100, 100, [1.2, -3.4, 5.6], 5
features = torch.randn((n_train + n_test, 1))
poly_features = torch.cat((features, torch.pow(features, 2), torch.pow(features, 3)), 1) 
labels = (true_w[0] * poly_features[:, 0] + true_w[1] * poly_features[:, 1]
          + true_w[2] * poly_features[:, 2] + true_b)
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)

#定义模型
def semilogy(x_vals, y_vals, x_label, y_label, x2_vals=None, y2_vals=None,
             legend=None, figsize=(3.5, 2.5)):
    # d2l.set_figsize(figsize)
    d2l.plt.xlabel(x_label)
    d2l.plt.ylabel(y_label)
    d2l.plt.semilogy(x_vals, y_vals)
    if x2_vals and y2_vals:
        d2l.plt.semilogy(x2_vals, y2_vals, linestyle=':')
        d2l.plt.legend(legend)

num_epochs, loss = 100, torch.nn.MSELoss()

def fit_and_plot(train_features, test_features, train_labels, test_labels):
    # 初始化网络模型
    net = torch.nn.Linear(train_features.shape[-1], 1)
    # 通过Linear文档可知,pytorch已经将参数初始化了,所以我们这里就不手动初始化了
    
    # 设置批量大小
    batch_size = min(10, train_labels.shape[0])    
    dataset = torch.utils.data.TensorDataset(train_features, train_labels)      # 设置数据集
    train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True) # 设置获取数据方式
    
    optimizer = torch.optim.SGD(net.parameters(), lr=0.01)                      # 设置优化函数,使用的是随机梯度下降优化
    train_ls, test_ls = [], []
    for _ in range(num_epochs):
        for X, y in train_iter:                                                 # 取一个批量的数据
            l = loss(net(X), y.view(-1, 1))                                     # 输入到网络中计算输出,并和标签比较求得损失函数
            optimizer.zero_grad()                                               # 梯度清零,防止梯度累加干扰优化
            l.backward()                                                        # 求梯度
            optimizer.step()                                                    # 迭代优化函数,进行参数优化
        train_labels = train_labels.view(-1, 1)
        test_labels = test_labels.view(-1, 1)
        train_ls.append(loss(net(train_features), train_labels).item())         # 将训练损失保存到train_ls中
        test_ls.append(loss(net(test_features), test_labels).item())            # 将测试损失保存到test_ls中
    print('final epoch: train loss', train_ls[-1], 'test loss', test_ls[-1])    
    semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
             range(1, num_epochs + 1), test_ls, ['train', 'test'])
    print('weight:', net.weight.data,
          '\nbias:', net.bias.data)

#测试
fit_and_plot(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:]) #正常
fit_and_plot(features[:n_train, :], features[n_train:, :], labels[:n_train], labels[n_train:]) #欠拟合
fit_and_plot(poly_features[0:2, :], poly_features[n_train:, :], labels[0:2], labels[n_train:]) #过拟合
  • 三阶多项式拟合(正常)
    在这里插入图片描述

这里的模型虽然是线性模型,但输入的样本特征却是多项式计算过的,所以与参数的线性组合就是多项式模型。

  • 线性拟合(欠拟合)
    在这里插入图片描述
  • 训练集过少(过拟合)
    在这里插入图片描述

防止过拟合的方法

  1. L2正则化(又叫权重衰减)
    加入L2正则化,能够防止个别参数极端大的情况,从而防止过拟合。在全局最小约束下,给损失函数加上个L2正则化项:
    ( w 1 , w 2 , b ) + λ 2 n w 2 \ell\left(w_{1}, w_{2}, b\right)+\frac{\lambda}{2 n}|w|^{2}
optimizer_w = torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd) # 对权重参数衰减
  1. dropout(又叫丢弃法)
    丢弃法通过一定概率把某些单元的灭活(即对应值置0),来避免训练过程中对某些神经元的过分依赖。下面公式证明,丢弃法不改变输入期望值。
    h i = ξ i 1 p h i E ( h i ) = E ( ξ i ) 1 p h i = h i h_{i}^{\prime}=\frac{\xi_{i}}{1-p} h_{i} \\ E\left(h_{i}^{\prime}\right)=\frac{E\left(\xi_{i}\right)}{1-p} h_{i}=h_{i}
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()
    print(mask)
    
    return mask * X / keep_prob
# 使用说明
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

#pytorch实现
nn.Dropout(drop_prob1)

有些话说

一些问题:

  1. 如何看出是过拟合?防止过拟合的方法有哪些?
  2. L2正则化和dropout防止过拟合的原理各自是什么?如何使用pytorch实现它们?
发布了52 篇原创文章 · 获赞 69 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/gongsai20141004277/article/details/104359464