4-4模型选择、欠拟合和过拟合

4-4模型选择、欠拟合和过拟合

1.多项式回归

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

2.生成数据集

代码解释

  1. max_degree = 20
    • 定义多项式的最大阶数为20。这意味着生成的特征中最高次项为20次幂。
  2. n_train, n_test = 100, 100
    • 定义训练数据集和测试数据集的大小,均为100个样本。
  3. true_w = np.zeros(max_degree)
    • 创建一个长度为max_degree的数组true_w,并初始化为全零。这个数组用于存储多项式的真实权重。
  4. true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])
    • true_w数组的前4个元素分别设置为5, 1.2, -3.4, 5.6。这意味着多项式的前4项权重分别为5、1.2、-3.4和5.6,其余项的权重为0。
  5. features = np.random.normal(size=(n_train + n_test, 1))
    • 使用np.random.normal生成一个形状为(n_train + n_test, 1)的数组features,其元素服从标准正态分布(均值为0,标准差为1)。这个数组表示输入特征,每个样本有一个特征值。
  6. np.random.shuffle(features)
    • 使用np.random.shufflefeatures数组进行随机打乱,确保数据的随机性。
  7. poly_features = np.power(features, np.arange(max_degree).reshape(1, -1))
    • 计算多项式特征。np.arange(max_degree)生成一个从0到max_degree-1的整数数组,表示多项式的阶数。将其重塑为(1, max_degree)形状后,与features进行逐元素的幂运算,生成多项式特征矩阵poly_features。每一列代表一个阶数的特征。
  8. for i in range(max_degree):
    • 遍历每个阶数。
  9. poly_features[:, i] /= math.gamma(i+1)
    • 对每个阶数的特征进行归一化处理。math.gamma(i+1)计算阶乘的值(gamma(n) = (n-1)!)。将每个特征值除以对应的阶乘值,以避免高阶项的数值过大。
  10. labels = np.dot(poly_features, true_w)
    • 使用np.dot计算poly_featurestrue_w的点积,生成标签labels。这个操作根据多项式的真实权重和特征矩阵计算出每个样本的理论标签值。
  11. labels += np.random.normal(scale=0.1, size=labels.shape)
    • 在标签中加入噪声。使用np.random.normal生成一个形状与labels相同的正态分布噪声(标准差为0.1),并将其加到labels中。这模拟了现实世界数据中的噪声。
max_degree = 20 #多项式的最大阶数
n_train, n_test = 100, 100  #训练和测试数据集大小
true_w = np.zeros(max_degree) #分配大量的空间
# 设置多项式的前4项权重
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])

# 生成输入特征,形状为(n_train + n_test, 1),标准服从正态分布
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]]

代码解释

  1. features[:2]
    • 提取features数组中的前两个样本的特征值。features是一个形状为(n_train + n_test, 1)的二维数组,每一行代表一个样本的特征值。[:2]表示取前两行,即前两个样本的特征值。
  2. poly_features[:2, :]
    • 提取poly_features数组中的前两个样本的所有多项式特征。poly_features是一个形状为(n_train + n_test, max_degree)的二维数组,每一行代表一个样本的多项式特征,每一列代表一个阶数的特征值。
    • [:2, :]表示取前两行(前两个样本)的所有列(所有阶数的特征值)。
  3. labels[:2]
    • 提取labels数组中的前两个样本的标签值。labels是一个形状为(n_train + n_test,)的一维数组,每个元素代表一个样本的标签值。
    • [:2]表示取前两个元素,即前两个样本的标签值。
# 提取前两个样本的原始特征值
print(features[:2])

# 提取前两个样本的所有多项式特征值
print(poly_features[:2, :])

# 提取前两个样本的标签值
print(labels[:2])
tensor([[0.6493],
        [1.0639]])
tensor([[1.0000e+00, 6.4926e-01, 2.1077e-01, 4.5614e-02, 7.4038e-03, 9.6139e-04,
         1.0403e-04, 9.6490e-06, 7.8309e-07, 5.6491e-08, 3.6677e-09, 2.1648e-10,
         1.1713e-11, 5.8497e-13, 2.7128e-14, 1.1742e-15, 4.7647e-17, 1.8197e-18,
         6.5637e-20, 2.2429e-21],
        [1.0000e+00, 1.0639e+00, 5.6594e-01, 2.0070e-01, 5.3381e-02, 1.1358e-02,
         2.0140e-03, 3.0611e-04, 4.0708e-05, 4.8122e-06, 5.1197e-07, 4.9516e-08,
         4.3900e-09, 3.5927e-10, 2.7302e-11, 1.9364e-12, 1.2876e-13, 8.0582e-15,
         4.7628e-16, 2.6669e-17]])
tensor([5.3262, 5.4452])

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

这段代码定义了一个函数 evaluate_loss,用于评估给定模型(net)在指定数据集(通过 data_iter 提供)上的损失。以下是对代码的详细解释和逐行注释:

代码解释

  1. def evaluate_loss(net, data_iter, loss):
    • 定义一个函数 evaluate_loss,接受以下参数:
      • net:模型(例如一个神经网络)。
      • data_iter:一个数据迭代器,用于提供数据集中的输入特征 x 和标签 y
      • loss:损失函数,用于计算模型输出和真实标签之间的损失。
  2. metric = d2l.Accumulator(2)
    • 创建一个 d2l.Accumulator 对象,用于累计两个值:
      • 损失的总和。
      • 样本数量。
    • d2l.Accumulator(2) 初始化一个包含两个累加器的工具,分别用于累加损失总和和样本数量。
  3. for x, y in data_iter:
    • 遍历数据迭代器 data_iter,每次获取一批输入特征 x 和对应的标签 y
  4. out = net(x)
    • 使用模型 net 对输入特征 x 进行前向传播,计算模型的输出 out
  5. y = y.reshape(out.shape)
    • 将标签 y 的形状调整为与模型输出 out 的形状一致。这是为了确保损失函数能够正确计算二者之间的差异。
  6. l = loss(out, y)
    • 使用损失函数 loss 计算模型输出 out 和真实标签 y 之间的损失 l
  7. metric.add(l.sum(), l.numel())
    • 将当前批次的损失总和 l.sum() 和样本数量 l.numel() 累加到 metric 中。
      • l.sum():计算当前批次损失的总和。
      • l.numel():计算当前批次的样本数量(即损失张量中的元素总数)。
  8. return metric[0] / metric[1]
    • 返回整个数据集上的平均损失。metric[0] 是累计的损失总和,metric[1] 是累计的样本数量,二者相除得到平均损失。
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]  # 返回平均损失

定义训练函数

代码解释

  1. def train(train_features, test_features, train_labels, test_labels, num_epochs=400):
    • 定义一个函数 train,接受以下参数:
      • train_features:训练集的特征。
      • test_features:测试集的特征。
      • train_labels:训练集的标签。
      • test_labels:测试集的标签。
      • num_epochs:训练的总轮数,默认为400。
  2. loss = nn.MSELoss(reduction='none')
    • 定义损失函数为均方误差(MSE),reduction='none' 表示不对损失进行归约,返回每个样本的损失值。
  3. input_shape = train_features.shape[-1]
    • 获取输入特征的维度(即特征的数量)。train_features.shape[-1] 表示特征矩阵的最后一维大小。
  4. net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))
    • 定义一个简单的线性回归模型,使用 nn.Linear 层将输入特征映射到一个输出值。这里不设置偏置项(bias=False),因为偏置项可能已经在特征中隐含(例如通过添加一列全为1的特征)。
  5. batch_size = min(10, train_labels.shape[0])
    • 设置批量大小为10或训练集样本数量的最小值。如果训练集样本数量小于10,则批量大小等于样本数量。
  6. train_iter = d2l.load_array((train_features, train_labels.reshape(-1,1)), batch_size)
    • 使用 d2l.load_array 创建训练数据迭代器。将训练特征和标签组合成一个数据集,并设置批量大小。
  7. test_iter = d2l.load_array((test_features, test_labels.reshape(-1,1)), batch_size=False)
    • 创建测试数据迭代器。这里将 batch_size 设置为 False,表示在测试时不对数据进行分批,直接使用整个测试集。
  8. trainer = torch.optim.SGD(net.parameters(), lr=0.01)
    • 定义优化器为随机梯度下降(SGD),学习率为0.01。
  9. animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log', ...)
    • 创建一个动画绘制器,用于动态绘制训练过程中的损失曲线。设置横轴为训练轮数(epoch),纵轴为损失(loss),并使用对数刻度(yscale='log')。
  10. for epoch in range(num_epochs):
    • 遍历训练轮数。
  11. d2l.train_epoch_ch3(net, train_iter, loss, trainer)
    • 调用 d2l.train_epoch_ch3 函数,完成一个训练轮次。该函数会遍历训练数据迭代器,计算损失,并通过优化器更新模型参数。
  12. if epoch == 0 or (epoch + 1) % 20 == 0:
    • 如果是第一个轮次,或者每20个轮次,记录训练集和测试集上的损失。
  13. animator.add(epoch + 1, (evaluate_loss(net, train_iter, loss), evaluate_loss(net, test_iter, loss)))
    • 调用 evaluate_loss 函数计算当前模型在训练集和测试集上的平均损失,并将结果添加到动画绘制器中。
  14. print('weight:', net[0].weight.data.numpy())
    • 在训练完成后,打印模型的权重。

总结

这段代码实现了一个简单的线性回归模型的训练过程,包括:

  1. 定义模型、损失函数和优化器。
  2. 创建训练和测试数据迭代器。
  3. 在每个训练轮次中更新模型参数。
  4. 定期记录并绘制训练集和测试集上的损失。
  5. 最后打印模型的权重。

通过这种方式,可以直观地观察模型的训练过程和性能变化。

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)

    # 定义优化器为随机梯度下降,学习率为0.01
    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)

        # 如果是第一个轮次,或者每20个轮次,记录损失
        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. poly_features[:n_train, :4]
    • 提取 poly_features 数组中前 n_train 行(训练集样本)的前4列(即只使用最高阶为3的多项式特征)。poly_features 是一个二维数组,其中每一行代表一个样本,每一列代表一个阶数的特征值。
  2. poly_features[n_train:, :4]
    • 提取 poly_features 数组中从第 n_train 行开始的所有行(测试集样本)的前4列。这部分数据用于测试模型的性能。
  3. labels[:n_train]
    • 提取 labels 数组中前 n_train 个元素,作为训练集的标签。
  4. labels[n_train:]
    • 提取 labels 数组中从第 n_train 个元素开始的所有元素,作为测试集的标签。
  5. train(...)
    • 调用之前定义的 train 函数,将上述提取的训练特征、测试特征、训练标签和测试标签传递给函数,开始训练过程。

代码的作用

  • 训练集:使用 poly_features[:n_train, :4]labels[:n_train],即多项式特征的前4阶和对应的标签。
  • 测试集:使用 poly_features[n_train:, :4]labels[n_train:],即多项式特征的前4阶和对应的标签。
  • 特征选择:这里只使用了多项式的前4阶特征(即最高阶为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:])
weight: [[ 4.982525   1.2616063 -3.4017472  5.500646 ]]

image

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

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

image

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

代码解释

参数说明

  1. poly_features[:n_train, :]
    • 提取 poly_features 数组中前 n_train 行(训练集样本)的所有列(即使用所有阶数的多项式特征)。poly_features 是一个二维数组,其中每一行代表一个样本,每一列代表一个阶数的特征值。
    • 这里使用了完整的多项式特征,而不是限制特征的阶数。
  2. poly_features[n_train:, :]
    • 提取 poly_features 数组中从第 n_train 行开始的所有行(测试集样本)的所有列。这部分数据用于测试模型的性能。
    • 测试集也使用了完整的多项式特征。
  3. labels[:n_train]
    • 提取 labels 数组中前 n_train 个元素,作为训练集的标签。
  4. labels[n_train:]
    • 提取 labels 数组中从第 n_train 个元素开始的所有元素,作为测试集的标签。
  5. num_epochs=1500
    • 设置训练的总轮数为1500。这是 train 函数的一个参数,用于控制训练过程的迭代次数。

代码的作用

  • 训练集:使用 poly_features[:n_train, :]labels[:n_train],即所有阶数的多项式特征和对应的标签。
  • 测试集:使用 poly_features[n_train:, :]labels[n_train:],即所有阶数的多项式特征和对应的标签。
  • 特征选择:这里使用了完整的多项式特征(最高阶为 max_degree),而不是限制特征的阶数。这可能会使模型更复杂,但也可能更好地拟合数据。
  • 训练轮数:设置训练轮数为1500,比之前的400轮更多,这可能有助于模型更好地收敛
# 从多项式特征中选取所有维度
train(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:], num_epochs=1500)
weight: [[ 4.955819    1.3009582  -3.2217398   5.099573   -0.4372955   1.3955926
   0.05601848  0.37696385 -0.12708202 -0.19423363 -0.1472916  -0.10355466
  -0.00746728 -0.09219566  0.0188027  -0.05638858 -0.15116963  0.11219677
  -0.10344743 -0.0637895 ]]

image


posted @ 2025-06-20 22:53  小西贝の博客  阅读(43)  评论(0)    收藏  举报