前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【动手学深度学习】多层感知机模型选择、欠拟合和过拟合研究详情

【动手学深度学习】多层感知机模型选择、欠拟合和过拟合研究详情

作者头像
SarPro
发布2024-06-06 08:24:24
1050
发布2024-06-06 08:24:24
举报
文章被收录于专栏:【计网】Cisco【计网】Cisco

🌊1. 研究目的

  • 多层感知机模型选择:比较不同多层感知机模型的性能,选择最适合解决给定问题的模型;
  • 欠拟合和过拟合:研究模型在训练数据上出现欠拟合或过拟合的情况,以便了解模型的泛化能力和优化方法的效果;
  • 模型正则化和调参:通过实验观察和比较,研究正则化技术和调参对模型的影响,以改善模型的泛化性能;
  • 模型复杂度与性能:探究多层感知机模型的复杂度对训练和测试性能的影响,以及如何找到合适的模型复杂度。

🌊2. 研究准备

  • 根据GPU安装pytorch版本实现GPU运行研究代码;
  • 配置环境用来运行 Python、Jupyter Notebook和相关库等相关库。

🌊3. 研究内容

启动jupyter notebook,使用新增的pytorch环境新建ipynb文件,为了检查环境配置是否合理,输入import torch以及torch.cuda.is_available() ,若返回TRUE则说明研究环境配置正确,若返回False但可以正确导入torch则说明pytorch配置成功,但研究运行是在CPU进行的,结果如下:


🌍3.1 多层感知机模型选择、⽋拟合和过拟合

(1)使用jupyter notebook新增的pytorch环境新建ipynb文件,完成基本数据操作的研究代码与练习结果如下:

导入相关库:

代码语言:javascript
复制
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l

生成数据集

代码语言:javascript
复制
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)

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

features[:2], poly_features[:2, :], labels[:2]

对模型进行训练和测试

代码语言:javascript
复制
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(reduction='mean')
    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())

三阶多项式函数拟合(正常)

代码语言:javascript
复制
# 从多项式特征中选择前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:])

线性函数拟合(欠拟合)

代码语言:javascript
复制
# 从多项式特征中选择前2个维度,即1和x
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
      labels[:n_train], labels[n_train:])

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

代码语言:javascript
复制
# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :],
      labels[:n_train], labels[n_train:], num_epochs=1500)

🌍3.2 基础练习

1.这个多项式回归问题可以准确地解出吗?提示:使用线性代数。

可以求解。多项式回归问题可以转化为一个线性方程组的求解问题。

假设多项式回归模型是一个关于特征变量 x 的多项式函数,形式为 y = w0 + w1 * x + w2 * x^2 + ... + wn * x^n,其中 wi 是待求解的系数。

现在假设有 m 个样本点,每个样本点的特征变量 x 和对应的目标变量 y 都已知。你可以将这 m 个样本点组成一个矩阵 X 和一个向量 Y,其中 X 是一个 m×(n+1) 的矩阵,每一行包含特征变量 x 的不同幂次的值,Y 是一个 m 维的向量,包含每个样本点的目标变量 y。

通过线性代数的方法,可以使用最小二乘法求解以下方程组来找到系数向量 w:

X^T * X * w = X^T * Y

其中 X^T 表示 X 的转置矩阵。

解这个方程组可以得到系数向量 w 的准确解,从而得到多项式回归模型。

需要注意的是,这种方法只在样本点的数量 m 大于等于系数的数量 n+1 时才能准确求解。当 m 小于 n+1 时,这个线性方程组是一个超定方程组,可能没有准确解。在这种情况下,可以使用最小二乘法的近似解。

总结起来,对于多项式回归问题,当样本点的数量充足(m >= n+1)时,可以使用线性代数的方法准确求解系数向量 w。当样本点的数量不足时,可以使用最小二乘法来近似求解。

2.考虑多项式的模型选择。

2.1.绘制训练损失与模型复杂度(多项式的阶数)的关系图。观察到了什么?需要多少阶的多项式才能将训练损失减少到0?

在这个修改后的代码中,循环遍历不同的多项式阶数,对于每个阶数,调用train函数并将返回的训练损失添加到train_losses列表中。最后使用plt.plot函数绘制多项式阶数与训练损失之间的关系图表。

运行修改后的代码,将会生成训练损失与多项式阶数之间的图表。观察图表,可以看到随着多项式阶数的增加,训练损失逐渐减小。然而,当多项式阶数过高时,训练损失可能会变得很小,甚至降低到接近零的程度。但需要注意的是,过高的多项式阶数可能导致过拟合问题。

根据图表,可以看出多项式阶数为4时,训练损失已经减少到接近零的程度。因此,至少需要4阶的多项式才能将训练损失减少到零。

代码语言:javascript
复制
import matplotlib.pyplot as plt

def train(train_features, test_features, train_labels, test_labels,
          num_epochs=400):
    loss = nn.MSELoss(reduction='mean')
    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)))
    
    train_loss = evaluate_loss(net, train_iter, loss)
    return net, train_loss

degrees = np.arange(max_degree)
train_losses = []

for degree in degrees:
    _, train_loss = train(poly_features[:n_train, :(degree+1)],
                          poly_features[n_train:, :(degree+1)],
                          labels[:n_train], labels[n_train:])
    train_losses.append(train_loss)

plt.plot(degrees, train_losses)
plt.xlabel('Degree of Polynomial')
plt.ylabel('Train Loss')
plt.show()

2.2 在这种情况下绘制测试的损失图。

代码语言:javascript
复制
degree = 4
_, test_loss = train(poly_features[:n_train, :degree+1],
                     poly_features[n_train:, :degree+1],
                     labels[:n_train], labels[n_train:])

print(f"Test Loss (Degree {degree}): {test_loss}")

plt.plot([degree], [test_loss], 'ro')
plt.xlabel('Degree of Polynomial')
plt.ylabel('Test Loss')
plt.show()

2.3 生成同样的图,作为数据量的函数。

代码语言:javascript
复制
degrees = np.arange(1, max_degree+1)
test_losses = []

for degree in degrees:
    _, test_loss = train(poly_features[:degree*n_train, :4],
                         poly_features[n_train:, :4],
                         labels[:degree*n_train], labels[n_train:])
    test_losses.append(test_loss)

plt.plot(degrees * n_train, test_losses)
plt.xlabel('Number of Data Points')
plt.ylabel('Test Loss')
plt.show()

依次是1阶至20阶的图像数据:

3.如果不对多项式特征x^i进行标准化(1/i!),会发生什么事情?能用其他方法解决这个问题吗?

如果不对多项式特征 x^i 进行标准化(1/i!),会导致不同阶数的多项式特征具有不同的数值范围和尺度。这可能会使训练过程变得困难,因为不同特征的权重更新可能会受到不同程度的影响,使得模型难以收敛。

另一种解决这个问题的方法是使用特征缩放(feature scaling)。特征缩放是将不同特征的数值范围映射到相同范围的过程,常见的方法是将特征值减去均值并除以标准差(即进行标准化)。这样做可以确保所有特征具有相似的尺度,有助于加速模型的训练过程和收敛性。

在多项式特征的情况下,如果不进行标准化,可以尝试对原始特征进行标准化,而不是对多项式特征进行标准化。这意味着对输入特征 x 进行标准化,然后再进行多项式特征的构建。这样可以确保所有输入特征具有相似的尺度,并且多项式特征也会受到相同的标准化影响。

以下是修改后的代码示例:

代码语言:javascript
复制
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt

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)

# 特征缩放:对原始特征进行标准化
mean = np.mean(features)
std = np.std(features)
features = (features - mean) / std

poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))
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]]

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]

def train(train_features, test_features, train_labels, test_labels, num_epochs=400):
    loss = nn.MSELoss(reduction='mean')
    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())

# 从多项式特征中选择前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:])

这段代码会生成训练损失与模型复杂度(多项式的阶数)的关系图。观察图可以帮助理解模型复杂度和训练损失之间的关系。

在代码中,通过标准化原始特征来处理多项式特征,即将特征进行了缩放,以便在训练过程中更好地优化模型。这样做有助于保持不同特征的相对重要性,并提高模型的训练效果。

如果不对多项式特征进行标准化,可能会导致模型训练过程中的数值不稳定性。特征缩放有助于避免梯度爆炸或梯度消失等问题,提高训练的稳定性和效果。

除了标准化之外,还可以考虑使用其他方法来处理多项式特征。例如采用正则化方法(如L1正则化、L2正则化)来控制模型的复杂度,以避免过拟合。另外,还可以尝试使用特征选择方法,选择对模型性能影响较大的特征,以减少特征的维度和模型的复杂度。

综上所述,标准化多项式特征是一种常用且有效的方法,可以提高模型的训练效果和稳定性。

4.泛化误差可能为零吗?

在实际情况下,泛化误差几乎不可能为零。泛化误差是指模型在未见过的数据上的误差,即在训练集之外的数据上的性能。即使模型在训练集上表现得非常好,泛化误差仍然存在,因为模型需要适应新的数据和不同的样本。

泛化误差的存在是由于数据的噪声、样本的多样性、模型的假设等因素。即使使用了良好的模型选择和训练方法,泛化误差也无法完全消除。

一个模型的目标是尽可能减小泛化误差,即在训练集和测试集上都能获得较低的误差。通过合适的模型选择、正则化技术、交叉验证等方法,可以帮助减小模型的泛化误差,并提高模型的性能。

因此,泛化误差几乎不可能为零,但可以通过优化模型和数据处理的方法来尽可能地减小泛化误差,以获得更好的模型性能。


🌊4. 研究体会

通过这次实验,我尝试使用不同的多层感知机模型架构,如不同的隐藏层数和隐藏单元数等超参数组合来构建多个模型。通过在训练集上训练这些模型,并在验证集上进行评估,比较它们在给定问题上的性能。

在实验中,可以选择使用流行的深度学习框架如TensorFlow或PyTorch来实现和训练多层感知机模型。需要定义模型的结构,包括输入层、多个隐藏层和输出层,并选择适当的激活函数和损失函数。

在模型训练过程中,使用适当的优化算法(如随机梯度下降)和合适的学习率来更新模型参数。通过记录训练集和验证集上的性能指标,比如准确率和损失函数值,评估不同模型的性能。根据实验结果,可以选择性能最好的模型,并进一步进行优化,以提高其性能。

研究模型在训练数据上出现欠拟合或过拟合现象,是为了了解模型的泛化能力和优化方法的效果。欠拟合指模型在训练数据上表现不佳,而过拟合指模型过度拟合了训练数据,导致在新数据上的性能下降。为了探究这些问题,可以通过调整模型的复杂度来观察欠拟合和过拟合的现象。通过增加隐藏层数或隐藏单元数,增加了模型的复杂度,可能更好地拟合训练数据,但也可能导致过拟合。相反,减少隐藏层数或隐藏单元数,模型的复杂度降低,可能导致欠拟合。

模型正则化是解决过拟合的常用方法之一。可以尝试引入正则化项,如L1正则化或L2正则化,来限制模型参数的大小,防止过拟合。此外,还可以使用Dropout技术,在训练过程中随机地将一些隐藏单元设置为零,以减少不同单元之间的依赖关系,从而增加模型的泛化能力。另外,调参也是改善模型泛化性能的重要步骤。在实验中,我们可以尝试调整学习率、批量大小、优化算法等超参数,以找到最佳的组合。使用网格搜索、随机搜索或贝叶斯优化等方法,可以自动搜索超参数空间,以寻找最佳的超参数配置。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🌊1. 研究目的
  • 🌊2. 研究准备
  • 🌊3. 研究内容
    • 🌍3.1 多层感知机模型选择、⽋拟合和过拟合
      • 🌍3.2 基础练习
      • 🌊4. 研究体会
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档