【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现

前文回顾:多层感知机、详细代码实现

文章目录

  • 一、模型选择
    • 1.1 两种误差
    • 1.2 两种数据集
    • 1.3 K-折交叉验证
    • 1.4 总结
  • 二、过拟合和欠拟合
    • 2.1 过拟合和欠拟合
    • 2.2 模型容量
      • 2.2.1 模型容量定义
      • 2.2.2 模型容量的影响
    • 2.3 估计模型容量
      • 2.3.1 VC维
      • 2.3.2 VC维的用处
    • 2.4 数据复杂度
    • 2.5 总结
  • 三、代码实现
    • 3.1 人工数据集
    • 3.2 损失函数
    • 3.3 训练函数
    • 3.4 拟合

一、模型选择

首先,我们学习模型选择,如何选择超参数。

1.1 两种误差

  • 训练误差: 模型在训练数据上的误差。
  • 泛化误差: 模型在新数据上的误差。

例子: 根据模考成绩来预测未来考试分数。

  • 在过去的考试中表现很好(训练误差),不代表未来考试一定会好(泛化误差)。

1.2 两种数据集

  • 验证数据集: 一个用来评估模型好坏的数据集。
    • 例如,拿出50%的训练数据集。我们可以用一半的数据集来训练模型,然后在另一半的数据集(验证数据集)上评估误差。如果我们觉得这个超参数的效果不好,我们可以更换超参数,然后再次在验证数据集上评估精度,从而比较两次超参数的好坏。
    • 验证数据集不要和训练数据集混在一起(很重要)。
  • 测试数据集: 只用一次的数据集。
    • 例如,未来的考试。

1.3 K-折交叉验证

  • 在没有足够多的数据使用时,我们可以采用K-折交叉验证。
  • 算法:
    • 将训练数据分割成K块。
    • f o r    i = 1 , ⋯   , K for \; i = 1, \cdots ,K fori=1,,K
      • 使用第 i 块作为验证数据集,其余数据库作为训练数据集。
      • 因此,我们可以分别使用K个不同的数据库作为验证集,进行K次训练和验证。
    • 我们对K次验证得到的误差求平均值,就得到了K-折交叉验证的误差。
  • 常用: K = 5 或 10

1.4 总结

  • 训练数据集: 训练模型参数。
  • 验证数据集: 选择模型超参数。
  • 非大数据集上通常使用K-折交叉验证。

二、过拟合和欠拟合

2.1 过拟合和欠拟合

过拟合和欠拟合的一个简单例子如下:

欠拟合 正常 过拟合
【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第1张图片 【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第2张图片 【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第3张图片

我们需要根据数据的简单和复杂程度来选择模型容量
通常,处理简单的数据需要比较低的模型容量,来获得一个比较正常的结果。如果在简单的数据上使用了较高的模型容量,就容易产生过拟合。
而对于复杂的数据,我们需要使用较高的模型容量。过低的模型容量可能会导致无法很好地拟合复杂数据,从而导致欠拟合。

【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第4张图片

2.2 模型容量

2.2.1 模型容量定义

  • 定义: 模型拟合各种函数的能力。
  • 低容量的模型难以拟合训练数据。
  • 高容量的模型可以记住所有训练数据。

2.2.2 模型容量的影响

随着模型容量的增加,模型的训练误差逐渐下降,模型会记住更多数据中的信息。但数据中存在噪音,因此泛化误差一开始随模型容量的增加而下降,但到一定程度会反而会上升。因此,模型容量存在一个最优值。
【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第5张图片

2.3 估计模型容量

我们难以在不同种类的算法之间比较模型容量,例如:树模型和神经网络。
对于一个给定的模型种类,有两个主要因素影响他的模型容量:参数的个数参数值的选择范围

2.3.1 VC维

  • VC维是统计学习理论的一个核心思想。
  • 对于一个分类模型,VC维等价于一个最大的数据集的大小,不管如何给定标号,都存在一个模型来对他进行完美分类。
  • 例如二维输入的单层感知机,它的VC维 = 3。
    • 即能够分类任意三个点,但存在不能分类的四个点。
      【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第6张图片
  • 支持 N 维输入的感知机的VC维是 N+1
  • 一些多层感知机的VC维是 O ( N log ⁡ 2 N ) O(N \log_2N) O(Nlog2N)

2.3.2 VC维的用处

  • 提供了为什么一个模型好的理论依据。
    • 它可以衡量训练误差和泛化误差之间的间隔。
  • 但是在深度学习中很少使用。
    • 对模型容量的衡量不是很准确。
    • 计算深度学习模型的VC维很困难。

2.4 数据复杂度

  • 对于一个数据,我们可以通过多个因素来衡量其复杂度:
    • 样本个数
    • 每个样本的元素个数
    • 时间、空间结构
    • 多样性

2.5 总结

  • 模型容量需要匹配数据复杂度,否则可能导致欠拟合和过拟合。
  • 统计机器学习提供数学工具来衡量模型复杂度。
  • 实际中一般靠观察训练误差和验证误差。

三、代码实现

我们通过代码来研究一下模型选择和欠拟合、过拟合之间的关系。

import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l

3.1 人工数据集

我们通过多项式拟合来探索这些概念,使用一下三阶多项式来生成训练和测试数据的标签:
y = 5 + 1.2 x − 3.4 x 2 2 ! + 5.6 x 3 3 ! + ϵ    w h e r e    ϵ ∼ N ( 0 , 0. 1 2 ) y=5+1.2x-3.4\frac{x^2}{2!}+5.6\frac{x^3}{3!}+ \epsilon \; \mathop{where} \; \epsilon \sim N(0, 0.1^2) y=5+1.2x3.42!x2+5.63!x3+ϵwhereϵN(0,0.12)

# 使用三阶多项式来生成训练和测试数据的标签
max_degree = 40
n_train, n_test = 100, 100
true_w = np.zeros(max_degree)
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])

features = np.random.normal(size=(n_train + n_test, 1))
np.random.shuffle(features)
poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))
for i in range(max_degree):
    poly_features[:, i] /= math.gamma(i + 1)
labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)

这里的 n_test 是验证集的数量

上述代码中,我们创建了一个长为 40 的向量 true_w,它只有前4个值为有意义的数值,后36位全为0(噪音)。此外,我们使用 np.random.normal() 方法添加噪音。

我们可以观察一些前2个样本:

# 看一下前两个样本
true_w, features, poly_features, labels = [
    torch.tensor(x, dtype=torch.float32)
    for x in [true_w, features, poly_features, labels]
]

3.2 损失函数

实现一个函数来评估模型在给定数据集上的损失。

def evaluate_loss(net, data_iter, loss):
    """ 评估给定数据集上模型的损失 """
    metric = d2l.Accumulator(2)
    for X, y in data_iter:
        out = net(X)
        y = y.reshape(out.shape)
        l = loss(out, y)
        metric.add(l.sum(), l.numel())
    return metric[0] / metric[1]

3.3 训练函数

接下来,我们定义一个训练函数。

# 定义训练函数
def train(train_features, test_features, train_labels, test_labels, num_epochs=400):
    loss = nn.MSELoss(reduction='none')
    input_shape = train_features.shape[-1]
    net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))
    batch_size = min(10, train_labels.shape[0])
    train_iter = d2l.load_array((train_features, train_labels.reshape(-1, 1)), batch_size)
    test_iter = d2l.load_array((test_features, test_labels.reshape(-1, 1)),
                               batch_size, is_train=False)
    trainer = torch.optim.SGD(net.parameters(), lr=0.01)
    animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log',
                            xlim=[1, num_epochs], ylim=[1e-3, 1e2],
                            legend=['train', 'test'])
    for epoch in range(num_epochs):
        d2l.train_epoch_ch3(net, train_iter, loss, trainer)
        if epoch == 0 or (epoch + 1) % 20 == 0:
            animator.add(epoch+1, (evaluate_loss(net, train_iter, loss),
                                   evaluate_loss(net, test_iter, loss))) 
    d2l.plt.show()
    print('weight: ', net[0].weight.data.numpy())

3.4 拟合

我们在训练模型的过程中,使用验证集进行三阶多项式函数拟合。
正态: 我们使用前4位有效信息训练模型,可以得到比较好的拟合效果。因为我们之前构造人工数据集的时候只有前4位是有效的数据,所以数据与模型是匹配的。

# 拟合 正态
train(poly_features[:n_train, :4], poly_features[n_train:, :4],
      labels[:n_train], labels[n_train:])

【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第7张图片

线性函数拟合(欠拟合): 我们只使用前两位有效信息进行拟合,由于少使用了一半的有效数据,所以产生了比较严重的欠拟合。

# 拟合 欠拟合
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
      labels[:n_train], labels[n_train:])

【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第8张图片

高阶多项式函数拟合(过拟合): 我们使用全部的40维数据训练模型,由于后36维饱含噪音,所以产生过拟合。我们可以观察到在 epoch = 300 左右的地方,损失值略有增加。

# 拟合 过拟合
train(poly_features[:n_train, :], poly_features[n_train:, :],
      labels[:n_train], labels[n_train:], num_epochs=1500)

【动手学深度学习v2李沐】学习笔记06:模型选择、欠拟合和过拟合、代码实现_第9张图片
下一篇:【动手学深度学习v2李沐】学习笔记07:权重衰退、正则化

你可能感兴趣的:(深度学习笔记整理,深度学习,学习,人工智能)