欢迎光临散文网 会员登陆 & 注册

5.4 常见问题及对策

2023-02-09 10:55 作者:梗直哥丶  | 我要投稿

学完多层感知机,知道了什么是前向传播、反向传播,其实你已经窥见了深度学习的精髓。为什么这么说呢?因为整个深度学习后面所涉及的内容其实全是在这个基础上发展起来的。说白了,都没有离开这个小圈圈。那咱们为啥还要学后面那么多章节内容呢?原因啊,就在于实际情况千奇百怪十分复杂,远不是简单的多层感知机就能解决好的。因此,想学好更复杂的深层神经网络知识,先要明确要解决的问题是什么。各种看似高级的技术,其实只不过在不停解决各种问题。本节咱们就来看数据和模型匹配方面的常见问题。先来看看模型复杂度。

5.4.1 模型复杂度

有了多层感知机这样的神经网络,你有没有好奇到底该怎么决定网络结构啊?该用多少层,每层用多少个神经元,是越多越好吗?选择的原则是什么?这些问题就涉及到了模型复杂度,也就是神经网络的复杂程度。模型复杂度指的是模型的表示能力,即模型可以拟合的数据的复杂度程度。模型复杂度越高,模型就能表示的数据就越复杂,但同时也会增加过拟合的风险。

在深度学习中,模型复杂度主要取决于网络的层数和神经元数量。随着网络层数的增加或神经元数量的增加,模型的复杂度也会增加。当模型复杂度过高时,模型容易过拟合训练数据,导致在测试数据上的表现不佳。因此,在训练深度学习模型时,需要适当控制模型的复杂度,以避免过拟合问题。常用的方法包括使用正则化技术(如L1 和 L2 正则化)、使用 K 折交叉验证选择最佳模型、使用数据增强技术等。此外,还可以通过减小网络层数或神经元数量、使用 dropout 等方法来限制模型的复杂度。

这里面一下子提到了不少新词:过拟合、正则化、K折交叉验证、数据增强、dropout等,看不懂别捉急,咱们一个个讲清楚。

5.4.2 过拟合问题

过拟合是指在训练深度学习模型时,模型在训练数据上表现良好,但在测试数据上表现不佳的情况。这意味着模型对训练数据进行了过度拟合,从而无法适用于真实世界中的数据。

过拟合通常是由模型复杂度过高导致的。当模型复杂度增加时,它可以更好地拟合训练数据中的噪声和细节。但是,这同时也会使模型对真实世界中的数据过度拟合,因此表现不佳。

为了避免过拟合,可以使用正则化技术来限制模型的复杂度。常用的正则化技术包括 L1 和 L2 正则化。这些技术通过在损失函数中添加惩罚项来减少模型的复杂度。此外,还可以通过使用更少的训练数据、减小网络层数或神经元数量、使用 dropout 等方法来防止过拟合。

在实际应用中,过拟合是一个常见问题,因此需要通过合适的方法来避免。过拟合不仅会导致模型在测试数据上的表现不佳,还会使模型对真实世界中的数据不具有泛化能力,因此需要谨慎对待。

5.4.3 欠拟合问题

欠拟合是指在训练深度学习模型时,模型在训练数据上的表现不佳,即模型没有很好地拟合训练数据。这意味着模型的复杂度过低,无法很好地捕捉数据中的特征。

欠拟合常常是由模型复杂度过低导致的。当模型复杂度过低时,它无法很好地拟合训练数据中的细节和特征。因此,模型在训练数据上的表现不佳。

为了避免欠拟合,可以通过增加模型的复杂度来提高模型的表示能力。常用的方法包括增加网络层数或神经元数量、使用更复杂的模型结构(如卷积神经网络或循环神经网络)等。此外,还可以使用数据增强技术来增加训练数据的数量和多样性,从而提高模型的泛化能力。正确地处理欠拟合问题可以提高模型的性能和泛化能力。

5.4.4 相互关系

模型复杂度、过拟合、欠拟合三者之间存在一定依存关联,下面这张图能清晰表达此消彼长的关系。

中间是最佳分界线,横轴是模型复杂度,越往右我们可以看出越容易过拟合,越往左越容易欠拟合。模型越复杂,训练误差越小,这很容易理解。但是泛化误差就不一定了,无论过拟合,还是欠拟合都不好,两种情况下泛化误差,也就是测试误差都会变大。只有在中间线这个地方泛化误差最小,因此这就是我们模型选择的目标了。

这里涉及到的训练误差和泛化误差两个概念,稍微多说两句。

在训练深度学习模型时,泛化误差指的是模型在真实世界中的数据上的误差。泛化误差是我们最终关心的误差,因为它反映了模型对真实世界数据的预测能力。训练误差指的是模型在训练数据上的误差。训练误差可以作为训练过程中的一个指标,但最终我们关心的是模型的泛化能力,即在真实世界中的数据上的表现。在训练时,通常会使用训练数据和测试数据来评估模型的性能。训练数据用于训练模型,测试数据用于评估模型的泛化能力。如果模型在训练数据上表现良好,但在测试数据上表现不佳,则可能存在过拟合问题。

举个例子,假设我们要建立一个机器学习模型来预测房屋价格。我们收集了一些历史房屋交易数据,并用这些数据来训练模型。这些数据就是我们的训练数据。在训练过程中,我们希望模型能够准确地拟合训练数据,也就是希望训练误差尽可能小。但是,我们也希望模型能够很好地适用于新数据,也就是说,希望模型的泛化误差尽可能小。

在实际应用中,我们通常希望泛化误差尽可能小,因为这意味着模型能够很好地适用于新数据。但是,训练误差和泛化误差之间通常存在一定的关系,即训练误差越小,泛化误差就越小。但是,如果训练误差过小,模型就可能出现过拟合的情况,即模型过于复杂,只能很好地拟合训练数据,但是对新数据的预测能力较差。因此,在训练机器学习模型时,我们需要尽量减小泛化误差,同时避免过拟合的情况。

5.4.5 应对策略

面对一不小心就过拟合、欠拟合这些问题,怎么办呢?可以从数据复杂度、模型复杂度、训练策略三方面下手。与之对应的就是数据集大小选择、数据增强、增加验证集;模型选择、K折交叉验证;正则化、dropout方法了。咱们分别来看看。

数据集大小选择

在训练深度学习模型时,数据集的大小可能会对过拟合问题产生影响。通常来说,如果数据集较小,模型很容易就会出现过拟合的情况。因为在较小的数据集上,模型很容易就能够很好地拟合训练数据,但是在新数据上的表现却很差。这是因为模型在训练过程中学习到的特征可能只针对训练数据有效,对新数据并没有太大的意义。

相反,如果数据集较大,模型很难出现过拟合的情况。这是因为在较大的数据集上,模型有更多的数据可以学习,因此模型很难将训练数据完全拟合。这样,模型就可以学习到更加泛化的特征,对新数据的表现就会更好。然而,数据集过大也可能导致模型的训练效率降低。因此,我们在选择数据集大小时,需要权衡这些因素,并选择合适的数据集大小。

数据增强是什么

在训练深度学习模型时,数据增强是指通过对训练数据进行变换,以增加训练数据的数量和多样性的一种技术。数据增强通常是通过对训练数据进行旋转、翻转、剪切、改变亮度等操作来实现的。

使用数据增强可以有效解决过拟合问题。同时,使用数据增强还可以提高模型在新数据上的泛化能力。因为通过数据增强生成的新数据可以更好地代表真实数据的分布,因此模型在新数据上的表现也会更好。

为什么要用验证集

在训练深度学习模型时,通常会使用训练集、验证集和测试集进行模型训练和评估。其中,训练集用于训练模型,测试集用于最终评估模型的性能,而验证集则是辅助模型选择和调参的一种工具。

使用验证集的好处在于,我们可以在训练过程中就对模型进行评估,而不需要等到训练结束才进行评估。这样,我们就可以及时发现模型的问题,并可以及时调整模型的超参数或模型结构。同时,使用验证集进行模型评估可以避免测试集的偏差,从而使得模型的评估更加准确。

假设我们要训练一个深度学习模型来进行图像分类。我们收集了一些图像数据,共有1000张图片,将这些图片分为训练集、验证集和测试集,每个集合各占图片总数的60%、20%和20%。

首先,我们使用训练集来训练模型。我们设定了两种不同的模型,分别是模型A和模型B。在训练过程中,我们使用验证集来评估模型的性能。我们发现,模型A在验证集上的性能略优于模型B。因此,我们决定选择模型A进行进一步训练。

在进一步训练模型A的过程中,我们发现模型A的训练误差逐渐降低,但是验证集上的误差却逐渐升高。这种情况通常是由于模型A出现了过拟合的情况。因此,我们决定停止模型A的训练,并使用最终训练好的模型A来在测试集上进行评估。

最终,我们使用测试集评估模型A的性能,发现模型A的性能较优。因此,我们选择使用模型A来进行实际的图像分类任务。

模型选择

在训练深度学习模型时,模型选择是很重要的一环。模型选择的好坏直接影响到模型的性能,因此选择合适的模型是很重要的。

常用的模型选择方法有以下几种:

  1. 网格搜索:通过对模型的超参数进行网格搜索,找到最优的超参数组合。

  2. 随机搜索:随机选择模型的超参数进行搜索,找到最优超参数。

  3. 交叉验证:将训练数据分为训练集和验证集,利用验证集来选择最优的模型。

  4. 集成方法:利用多个模型的输出结果进行集成,得到最终的模型。

在选择模型时,还需要考虑模型的泛化能力、训练时间、模型复杂度、计算资源、预测效率等因素。因此,选择合适的模型是很重要的。

K折交叉验证

K 折交叉验证(K-fold cross-validation)是一种在机器学习中常用的评估模型性能的方法。它的基本思想是将训练数据划分为 K 份,然后进行 K 次训练和验证。K 折交叉验证在深度学习中也是常用的方法。它可以有效评估模型的性能,并且可以在训练过程中发现模型的过拟合情况。

使用 K 折交叉验证的步骤如下:

  1. 将训练数据划分为 K 份。

  2. 对于每一份数据,将其作为验证集,剩余的 K-1 份数据作为训练集,进行训练和验证。

  3. 计算 K 次验证的平均值。

K 折交叉验证的优点是可以充分利用训练数据,提高模型的泛化能力。但是,它的计算时间较长,因此在实际应用中需要权衡时间和精度的折中。

梗直哥提示:我们看到模型选择、验证集使用和K折交叉验证几个策略息息相关,如果你对这方面的知识不是很熟悉,欢迎补足,来听听过来人怎么说。“解一卷而众篇明”之机器学习核心概念精讲

正则化和dropout稍微复杂些,咱们下面两节单独讲。这里先来看个过拟合的实际例子,感受一下。

5.4.6 代码示例

为了让大家有更加感性的认知,我们用一个 PyTorch 的例子,演示不同模型情况下欠拟合、正常和过拟合三种情况,并可视化训练和测试误差。

首先,导入所需库,并用 PyTorch 的 torch.utils.data.DataLoader 类来生成一些虚拟数据。这些数据将满足一个线性方程 y = x^2 + 1,但是我们将会增加一些噪声来使它不完全符合这个方程。

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np

np.random.seed(32)

# 生成满足 y = x^2 + 1 的数据
num_samples = 100
X = np.random.uniform(-5, 5, (num_samples, 1))
Y = X ** 2 + 1 + 5 * np.random.normal(0, 1, (num_samples, 1))

# 将 NumPy 变量转化为浮点型 PyTorch 变量
X = torch.from_numpy(X).float()
Y = torch.from_numpy(Y).float()

# 绘制数据点
plt.scatter(X, Y)
plt.show()


# 将数据拆分为训练集和测试集
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.3, random_state=0)

# 将数据封装成数据加载器
train_dataloader = DataLoader(TensorDataset(train_X, train_Y), batch_size=32, shuffle=True)
test_dataloader = DataLoader(TensorDataset(test_X, test_Y), batch_size=32, shuffle=False)

接下来,定义三种不同的模型,分别演示欠拟合、正常和过拟合三种情况。

欠拟合可以使用一个线性回归模型,但是输入维度为 1,输出维度也为 1。因为线性回归模型没有隐藏层,所以它可能无法很好地拟合复杂的数据。

正常情况使用一个包含一个隐藏层的多层感知机(MLP)。这个模型的输入维度仍为 1,输出维度也为 1。

过拟合用一个比上述模型更复杂的 MLP,它具有更多的隐藏层和更多的隐藏单元。

# 定义线性回归模型(欠拟合)
class LinearRegression(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(1, 1)

    def forward(self, x):
        return self.linear(x)

# 定义多层感知机(正常)
class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(1, 8)
        self.output = nn.Linear(8, 1)

    def forward(self, x):
        x = torch.relu(self.hidden(x))
        return self.output(x)

# 定义更复杂的多层感知机(过拟合)
class MLPOverfitting(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden1 = nn.Linear(1, 256)
        self.hidden2 = nn.Linear(256, 256)
        self.output = nn.Linear(256, 1)

    def forward(self, x):
        x = torch.relu(self.hidden1(x))
        x = torch.relu(self.hidden2(x))
        return self.output(x)

接下来,我们可以使用这三种模型来训练模型并计算训练和测试误差。为了计算误差,我们可以使用均方误差(MSE)作为损失函数。

def plot_errors(models, num_epochs, train_dataloader, test_dataloader):
    # 定义损失函数
    loss_fn = nn.MSELoss()

    # 定义训练和测试误差数组
    train_losses = []
    test_losses = []

    # 迭代训练
    for model in models:
        # 初始化模型和优化器
        optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

        train_losses_per_model = []
        test_losses_per_model = []
        for epoch in range(num_epochs):
            # 在训练数据上迭代
            model.train()
            train_loss = 0
            for inputs, targets in train_dataloader:
                optimizer.zero_grad()
                outputs = model(inputs)
                loss = loss_fn(outputs, targets)
                loss.backward()
                optimizer.step()
                train_loss += loss.item()
            train_loss /= len(train_dataloader)
            train_losses_per_model.append(train_loss)

            # 在测试数据上评估
            model.eval()
            test_loss = 0
            with torch.no_grad():
                for inputs, targets in test_dataloader:
                    outputs = model(inputs)
                    loss = loss_fn(outputs, targets)
                    test_loss += loss.item()
                test_loss /= len(test_dataloader)
                test_losses_per_model.append(test_loss)

        train_losses.append(train_losses_per_model)
        test_losses.append(test_losses_per_model)

    return train_losses, test_losses


最后,我们可以使用 Matplotlib 绘制训练和测试误差曲线,以便我们可以清楚地看到三种模型的变化趋势。

# 获取训练和测试误差曲线
num_epochs = 300
models = [LinearRegression(), MLP(), MLPOverfitting()]
train_losses, test_losses = plot_errors(models, num_epochs, train_dataloader, test_dataloader)

# 绘制训练和测试误差曲线
for i, model in enumerate(models):
    plt.figure()
    plt.plot(range(num_epochs), train_losses[i], label=f"Train {model.__class__.__name__}")
    plt.plot(range(num_epochs), test_losses[i], label=f"Test {model.__class__.__name__}")
    plt.legend()
    plt.ylim((0, 200))
    plt.show()


从上图中可以看到,欠拟合的训练和测试误差曲线都呈现出较高的损失值,这表明模型在训练数据和测试数据上的表现都不佳,也就是说模型无法很好地拟合数据。在正常的情况下,训练曲线和验证曲线通常会呈现出较低的损失值,并且损失值随着训练的进行而逐渐下降。而过拟合则是在测试集上的表现较差,出现不稳定的情况甚至随着训练次数的增加反而逐渐增大。

广而告之:如果你对实战感兴趣,比如上述代码的深度分析,如何调参等更加进阶的话题,欢迎入群讨论(微信:gengzhige99),请告知我“我想选修梗直哥课程《深度学习必修课:Python实战》”



5.4 常见问题及对策的评论 (共 条)

分享到微博请遵守国家法律