【学习day4】模型选择+过拟合和欠拟合

主要来源:
李沐老师的pytorch 动手学习深度学习(鞠躬感谢)
记录每日所学,欢迎讨论

目录

  • 一. 模型选择
  • 二、训练误差和泛化误差
  • 三、多项式回归
    • 1.生成数据集
    • 2.对模型进行测试和训练
    • 3.三阶多项式函数拟合(正态)
    • 4.线性函数拟合(欠拟合)
    • 5. 高阶多项式函数拟合(过拟合)

通过多项式拟合

一. 模型选择

  • 训练数据集:训练模型参数
  • 验证数据集:选择模型超参数
  • 非大数据集上通常使用k-折交叉验证
    k-折交叉验证:把数据分成k份,做k次,每一次留一份做测试,通过k折平均误差来判断好坏。
    【学习day4】模型选择+过拟合和欠拟合_第1张图片

作为机器学习科学家,我们的目标是发现模式(pattern)。但是,我们如何才能确定模型是真正发现了一种泛化的模式,而不是简单地记住了数据呢?例如,我们想要在患者的基因数据与痴呆状态之间寻找模式,其中标签是从集合 { 痴呆 , 轻度认知障碍 , 健康 } \{\text{痴呆}, \text{轻度认知障碍}, \text{健康}\} {痴呆,轻度认知障碍,健康}中提取的。因为基因可以唯一确定每个个体(不考虑双胞胎),所以在这个任务中是有可能记住整个数据集的。

我们不想让模型只会做这样的事情:“那是鲍勃!我记得他!他有痴呆症!”。
原因很简单。当我们将来部署该模型时,模型会遇到从未见过的患者。只有当我们的模型真正发现了一种泛化模式时,才会作出有效的预测。

更正式地来说,我们的目标是发现模式,这些模式捕捉到了我们训练集所来自的潜在总体的规律。如果成功做到了这点,即使是对我们以前从未遇到过的个体,我们也可以成功地评估风险。如何发现可以泛化的模式是机器学习的根本问题。

困难在于,当我们训练模型时,我们只能访问数据中的小部分样本。最大的公开图像数据集包含大约一百万张图像。而在大部分时候,我们只能从数千或数万个数据样本中学习。在大型医院系统中,我们可能会访问数十万份医疗记录。当我们使用有限的样本时,可能会遇到这样的问题:当收集到更多的数据时,会发现之前找到的明显关系并不成立。

将模型在训练数据上拟合得比在潜在分布中更接近的现象称为过拟合(overfitting)用于对抗过拟合的技术称为正则化(regularization)。在实验中调整模型结构或超参数时,你会发现,如果有足够多的神经元、层数和训练迭代周期,模型最终可以在训练集上达到完美的精度,此时测试集的准确性却下降了。

二、训练误差和泛化误差

训练误差(training error)是指,我们的模型在训练数据集上计算得到的误差。泛化误差(generalization error)是指,当我们将模型应用在同样从原始样本的分布中抽取的无限多的数据样本时,我们模型误差的期望。

问题是,我们永远不能准确地计算出泛化误差。这是因为无限多的数据样本是一个虚构的对象。在实际中,我们只能通过将模型应用于一个独立的测试集来估计泛化误差,该测试集由随机选取的、未曾在训练集中出现的数据样本构成。

为了给你一些直观的印象,我们将重点介绍几个倾向于影响模型泛化的因素:

  1. 可调整参数的数量。当可调整参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。
  2. 参数采用的值。当权重的取值范围较大时,模型可能更容易过拟合。
  3. 训练样本的数量。即使你的模型很简单,也很容易过拟合只包含一两个样本的数据集。而过拟合一个有数百万个样本的数据集则需要一个极其灵活的模型。

三、多项式回归

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

1.生成数据集

首先,我们需要数据。给定 x x x,我们将使用以下三阶多项式来生成训练和测试数据的标签:

y = 5 + 1.2 x − 3.4 x 2 2 ! + 5.6 x 3 3 ! + ϵ  where  ϵ ∼ N ( 0 , 0. 1 2 ) . y = 5 + 1.2x - 3.4\frac{x^2}{2!} + 5.6 \frac{x^3}{3!} + \epsilon \text{ where } \epsilon \sim \mathcal{N}(0, 0.1^2). y=5+1.2x3.42!x2+5.63!x3+ϵ where ϵN(0,0.12).

噪声项 ϵ \epsilon ϵ服从均值为0且标准差为0.1的正态分布。在优化的过程中,我们通常希望避免非常大的梯度值或损失值。这就是我们将特征 x i x^i xi调整为 x i i ! \frac{x^i}{i!} i!xi的原因,这样可以避免很大的 i i i带来的特别大的指数值。我们将为训练集和测试集各生成100个样本。

max_degree = 20  # 多项式的最大阶数
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)  # `gamma(n)` = (n-1)!
# `labels`的维度: (`n_train` + `n_test`,)
labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape)
  • true_w 长度为20,前4个数字已经给出,后面都是0
  • np.random.normal:size是输出,size(A,1)是返回矩阵A的行数

【学习day4】模型选择+过拟合和欠拟合_第2张图片

size 总结用法:
size():获取矩阵的行数和列数
(1)s=size(A):
当只有一个输出参数时,返回一个行向量,该行向量的第一个元素时矩阵的行数,第二个元素是矩阵的列数。
(2)[r,c]=size(A):
当有两个输出参数时,size函数将矩阵的行数返回到第一个输出变量r,将矩阵的列数返回到第二个输出变量c。
(3)size(A,n):
如果在size函数的输入参数中再添加一项n,并用1或2为n赋值,则 size将返回矩阵的行数或列数。
其中r=size(A,1)该语句返回的时矩阵A的行数, c=size(A,2) 该语句返回的时矩阵A的列数。

  • no.power:power(x, y) 函数,计算 x 的 y 次方。
# NumPy ndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=
    d2l.float32) for x in [true_w, features, poly_features, labels]]
features[:2], poly_features[:2, :], labels[:2]

测试结果:
【学习day4】模型选择+过拟合和欠拟合_第3张图片

2.对模型进行测试和训练

首先让我们[实现一个函数来评估模型在给定数据集上的损失]。

def evaluate_loss(net, data_iter, loss):  #@save
    """评估给定数据集上模型的损失。"""
    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]

定义训练函数:

def train(train_features, test_features, train_labels, test_labels,
          num_epochs=400):
    loss = nn.MSELoss()
    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)))
    print('weight:', net[0].weight.data.numpy())
  • net是单层的线性回归网络

3.三阶多项式函数拟合(正态)

# 从多项式特征中选择前4个维度,即 1, x, x^2/2!, x^3/3!
train(poly_features[:n_train, :4], poly_features[n_train:, :4],
      labels[:n_train], labels[n_train:])
  • 取前4列(有数的完整版)

【学习day4】模型选择+过拟合和欠拟合_第4张图片

4.线性函数拟合(欠拟合)

# 从多项式特征中选择前2个维度,即 1, x
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
      labels[:n_train], labels[n_train:])

【学习day4】模型选择+过拟合和欠拟合_第5张图片

  • 数据太少,导致损失根本没下降——欠拟合

5. 高阶多项式函数拟合(过拟合)

# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :],
      labels[:n_train], labels[n_train:], num_epochs=1500)

【学习day4】模型选择+过拟合和欠拟合_第6张图片

  • 在这种情况下,没有足够的数据用于学到高阶系数应该具有接近于零的值。因此,这个过于复杂的模型会轻易受到训练数据中噪声的影响。虽然训练损失可以有效地降低,但测试损失仍然很高。结果表明,复杂模型对数据造成了过拟合。

你可能感兴趣的:(机器学习,深度学习,人工智能)