深度学习 —— 个人学习笔记5(模型选择、欠拟合和过拟合)

声明

本文章为个人学习使用,版面观感若有不适请谅解,文中知识仅代表个人观点,若出现错误,欢迎各位批评指正。

十一、模型选择

  • 训练误差:模型在训练数据上的误差。
  • 泛化误差:模型在新数据上的误差。
  • 验证数据集:一个用来评估模型好坏的数据集。
  • 测试数据集:只用一次的数据集。
  • K 折交叉验证:当训练数据稀缺时,可能无法提供足够的数据来构成一个合适的验证集。解决方法为,将原始训练数据分成 K 个不重叠的子集。然后执行 K 次模型训练和验证,每次在 K - 1 个子集上进行训练,并在剩余的一个子集(在该轮中没有用于训练的子集)上进行验证。最后,通过对 K 次实验的结果取平均来估计训练和验证误差。

十二、过拟合和欠拟合

  • 模型容量:指拟合各种函数的能力。
  • 过拟合:‌指在训练数据上模型表现良好,但在未知数据上表现较差的现象。
  • 欠拟合:指模型拟合程度不高,数据距离拟合曲线较远,或指模型没有很好地捕捉到数据特征,不能够很好地拟合数据。

使用以下三阶多项式做演示:

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.2 x - 3.4 \frac{x^2}{ 2 ! } + 5.6\frac{x^3}{ 3 ! } + \varepsilon \quad where \quad \varepsilon \; ~ \; N ( 0 , 0.1^2 ) y=5+1.2x−3.42!x2+5.63!x3+εwhereε~N(0,0.12)

分别以从多项式特征值中选取不同维度以演示拟合情况:

复制代码
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
from IPython import display

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])                                         # 只有 4 个参数有值,其余全部为噪音

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)

# NumPy ndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=
    torch.float32).cuda() for x in [true_w, features, poly_features, labels]]

print(features[:2], poly_features[:2, :], labels[:2])

def accuracy(y_hat, y):                                                             # 定义一个函数来为预测正确的数量计数
    """计算预测正确的数量"""
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        y_hat = y_hat.argmax(axis=1)
    cmp = y_hat.type(y.dtype) == y                                                  # bool 类型,若预测结果与实际结果一致,则为 True
    return float(cmp.type(y.dtype).sum())

class Accumulator:                                                                  # 定义一个实用程序类 Accumulator,用于对多个变量进行累加
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

class Animator:                                                                     # 定义一个在动画中绘制数据的实用程序类 Animator
    """在动画中绘制数据"""
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
                 figsize=(3.5, 2.5)):
        # 增量地绘制多条线
        if legend is None:
            legend = []
        d2l.use_svg_display()
        d2l.plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes, ]
        # 使用lambda函数捕获参数
        self.config_axes = lambda: d2l.set_axes(
            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):
        # 向图表中添加多个数据点
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)):
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla()
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)

def evaluate_loss(net, data_iter, loss):
    """评估给定数据集上模型的损失"""
    metric = Accumulator(2)  # 损失的总和,样本数量
    for X, y in data_iter:
        X, y = X.cuda(), y.cuda()
        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(reduction='none').cuda()
    input_shape = train_features.shape[-1]
    # 不设置偏置,因为我们已经在多项式中实现了它
    net = nn.Sequential(nn.Linear(input_shape, 1, bias=False)).cuda()
    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 = Animator(xlabel='epoch', ylabel='loss', yscale='log',
                            xlim=[1, num_epochs], ylim=[1e-3, 1e2],
                            legend=['train', 'test'])
    for epoch in range(num_epochs):
        train_epoch(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)))
    net[0].cpu()
    print('weight:', net[0].weight.data.numpy())

def train_epoch(net, train_iter, loss, updater):                                    # 定义一个函数来训练一个迭代周期
    """训练模型一个迭代周期"""
    if isinstance(net, torch.nn.Module):
        net.train()                                                                 # 将模型设置为训练模式
    metric = Accumulator(3)                                                         # 训练损失总和、训练准确度总和、样本数
    for X, y in train_iter:                                                         # 计算梯度并更新参数
        X, y = X.cuda(), y.cuda()
        y_hat = net(X)
        l = loss(y_hat, y)
        if isinstance(updater, torch.optim.Optimizer):                              # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad()
            l.mean().backward()
            updater.step()
        else:                                                                       # 使用定制的优化器和损失函数
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    return metric[0] / metric[2], metric[1] / metric[2]                             # 返回训练损失和训练精度

# 从多项式特征中选择前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:], num_epochs=500)
d2l.plt.title("三阶多项式函数拟合(正常)")
d2l.plt.show()

# 从多项式特征中选择前2个维度,即1和x
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
      labels[:n_train], labels[n_train:], num_epochs=500)
d2l.plt.title("线性函数拟合(欠拟合)")
d2l.plt.show()

# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :],
      labels[:n_train], labels[n_train:], num_epochs=500)
d2l.plt.title("高阶多项式函数拟合(过拟合)")
d2l.plt.show()




过拟合情况说明:

参照李沐课程复现后发现过拟合情况并未出现,查阅相关资料后了解到可能的原因为 GPU 提高了计算效率和数据处理速度,避免了过拟合的情况。

将数据放入 CUDA 后,‌没有出现过拟合的原因可能在于 CUDA 的使用有助于提高计算效率和数据处理速度,‌从而使得模型训练过程更加高效。‌ CUDA 是一种由 NVIDIA 开发的并行计算平台和 API,‌它使得开发者可以使用图形处理单元(‌ GPU )‌来进行高性能的数值计算。‌通过使用 CUDA,‌可以加速模型的训练过程,‌减少训练时间,‌这在某种程度上可能有助于避免过拟合。‌

过拟合通常发生在模型在训练数据上表现良好,‌但在未见过的数据上表现不佳的情况。‌使用 CUDA 进行计算可以加快模型的训练速度,‌使得模型能够更快地收敛到一个较好的解决方案,‌这在一定程度上减少了模型在训练数据上过度拟合的风险。‌此外,‌CUDA 的使用还可以提高模型的计算精度和性能,‌进一步减少过拟合的可能性。

为使拟合演示更加明显,使用 CPU 重新演示拟合情况。

复制代码
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
from IPython import display

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])                                         # 只有 4 个参数有值,其余全部为噪音

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)

# NumPy ndarray转换为tensor
true_w, features, poly_features, labels = [torch.tensor(x, dtype=
    torch.float32) for x in [true_w, features, poly_features, labels]]

print(features[:2], poly_features[:2, :], labels[:2])

def accuracy(y_hat, y):                                                             # 定义一个函数来为预测正确的数量计数
    """计算预测正确的数量"""
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        y_hat = y_hat.argmax(axis=1)
    cmp = y_hat.type(y.dtype) == y                                                  # bool 类型,若预测结果与实际结果一致,则为 True
    return float(cmp.type(y.dtype).sum())

class Accumulator:                                                                  # 定义一个实用程序类 Accumulator,用于对多个变量进行累加
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

class Animator:                                                                     # 定义一个在动画中绘制数据的实用程序类 Animator
    """在动画中绘制数据"""
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
                 figsize=(3.5, 2.5)):
        # 增量地绘制多条线
        if legend is None:
            legend = []
        d2l.use_svg_display()
        d2l.plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes, ]
        # 使用lambda函数捕获参数
        self.config_axes = lambda: d2l.set_axes(
            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):
        # 向图表中添加多个数据点
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)):
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla()
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)

def evaluate_loss(net, data_iter, loss):
    """评估给定数据集上模型的损失"""
    metric = 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(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 = Animator(xlabel='epoch', ylabel='loss', yscale='log',
                            xlim=[1, num_epochs], ylim=[1e-3, 1e2],
                            legend=['train', 'test'])
    for epoch in range(num_epochs):
        train_epoch(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())

def train_epoch(net, train_iter, loss, updater):                                    # 定义一个函数来训练一个迭代周期
    """训练模型一个迭代周期"""
    if isinstance(net, torch.nn.Module):
        net.train()                                                                 # 将模型设置为训练模式
    metric = Accumulator(3)                                                         # 训练损失总和、训练准确度总和、样本数
    for X, y in train_iter:                                                         # 计算梯度并更新参数
        y_hat = net(X)
        l = loss(y_hat, y)
        if isinstance(updater, torch.optim.Optimizer):                              # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad()
            l.mean().backward()
            updater.step()
        else:                                                                       # 使用定制的优化器和损失函数
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    return metric[0] / metric[2], metric[1] / metric[2]                             # 返回训练损失和训练精度

# 从多项式特征中选择前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:], num_epochs=500)
d2l.plt.title("三阶多项式函数拟合(正常)")
d2l.plt.show()

# 从多项式特征中选择前2个维度,即1和x
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
      labels[:n_train], labels[n_train:], num_epochs=500)
d2l.plt.title("线性函数拟合(欠拟合)")
d2l.plt.show()

# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :],
      labels[:n_train], labels[n_train:], num_epochs=1000)
d2l.plt.title("高阶多项式函数拟合(过拟合)")
d2l.plt.show()





文中部分知识参考:B 站 ------ 跟李沐学AI;百度百科

相关推荐
多巴胺与内啡肽.7 分钟前
深度学习--自然语言处理统计语言与神经语言模型
深度学习·语言模型·自然语言处理
深度之眼32 分钟前
2025时间序列都有哪些创新点可做——总结篇
人工智能·深度学习·机器学习·时间序列
晓数1 小时前
【硬核干货】JetBrains AI Assistant 干货笔记
人工智能·笔记·jetbrains·ai assistant
我的golang之路果然有问题1 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
genggeng不会代码1 小时前
用于协同显著目标检测的小组协作学习 2021 GCoNet(总结)
学习
lwewan1 小时前
26考研——存储系统(3)
c语言·笔记·考研
搞机小能手2 小时前
六个能够白嫖学习资料的网站
笔记·学习·分类
nongcunqq2 小时前
爬虫练习 js 逆向
笔记·爬虫
汐汐咯3 小时前
终端运行java出现???
笔记
不吃香菜?3 小时前
PyTorch 实现食物图像分类实战:从数据处理到模型训练
人工智能·深度学习