4-4模型选择、欠拟合和过拟合
4-4模型选择、欠拟合和过拟合
1.多项式回归
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
2.生成数据集
代码解释
max_degree = 20- 定义多项式的最大阶数为20。这意味着生成的特征中最高次项为20次幂。
n_train, n_test = 100, 100- 定义训练数据集和测试数据集的大小,均为100个样本。
true_w = np.zeros(max_degree)- 创建一个长度为
max_degree的数组true_w,并初始化为全零。这个数组用于存储多项式的真实权重。
- 创建一个长度为
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。
- 将
features = np.random.normal(size=(n_train + n_test, 1))- 使用
np.random.normal生成一个形状为(n_train + n_test, 1)的数组features,其元素服从标准正态分布(均值为0,标准差为1)。这个数组表示输入特征,每个样本有一个特征值。
- 使用
np.random.shuffle(features)- 使用
np.random.shuffle对features数组进行随机打乱,确保数据的随机性。
- 使用
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。每一列代表一个阶数的特征。
- 计算多项式特征。
for i in range(max_degree):- 遍历每个阶数。
poly_features[:, i] /= math.gamma(i+1)- 对每个阶数的特征进行归一化处理。
math.gamma(i+1)计算阶乘的值(gamma(n) = (n-1)!)。将每个特征值除以对应的阶乘值,以避免高阶项的数值过大。
- 对每个阶数的特征进行归一化处理。
labels = np.dot(poly_features, true_w)- 使用
np.dot计算poly_features和true_w的点积,生成标签labels。这个操作根据多项式的真实权重和特征矩阵计算出每个样本的理论标签值。
- 使用
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]]
代码解释
features[:2]- 提取
features数组中的前两个样本的特征值。features是一个形状为(n_train + n_test, 1)的二维数组,每一行代表一个样本的特征值。[:2]表示取前两行,即前两个样本的特征值。
- 提取
poly_features[:2, :]- 提取
poly_features数组中的前两个样本的所有多项式特征。poly_features是一个形状为(n_train + n_test, max_degree)的二维数组,每一行代表一个样本的多项式特征,每一列代表一个阶数的特征值。 [:2, :]表示取前两行(前两个样本)的所有列(所有阶数的特征值)。
- 提取
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 提供)上的损失。以下是对代码的详细解释和逐行注释:
代码解释
def evaluate_loss(net, data_iter, loss):- 定义一个函数
evaluate_loss,接受以下参数:net:模型(例如一个神经网络)。data_iter:一个数据迭代器,用于提供数据集中的输入特征x和标签y。loss:损失函数,用于计算模型输出和真实标签之间的损失。
- 定义一个函数
metric = d2l.Accumulator(2)- 创建一个
d2l.Accumulator对象,用于累计两个值:- 损失的总和。
- 样本数量。
d2l.Accumulator(2)初始化一个包含两个累加器的工具,分别用于累加损失总和和样本数量。
- 创建一个
for x, y in data_iter:- 遍历数据迭代器
data_iter,每次获取一批输入特征x和对应的标签y。
- 遍历数据迭代器
out = net(x)- 使用模型
net对输入特征x进行前向传播,计算模型的输出out。
- 使用模型
y = y.reshape(out.shape)- 将标签
y的形状调整为与模型输出out的形状一致。这是为了确保损失函数能够正确计算二者之间的差异。
- 将标签
l = loss(out, y)- 使用损失函数
loss计算模型输出out和真实标签y之间的损失l。
- 使用损失函数
metric.add(l.sum(), l.numel())- 将当前批次的损失总和
l.sum()和样本数量l.numel()累加到metric中。l.sum():计算当前批次损失的总和。l.numel():计算当前批次的样本数量(即损失张量中的元素总数)。
- 将当前批次的损失总和
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] # 返回平均损失
定义训练函数
代码解释
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。
- 定义一个函数
loss = nn.MSELoss(reduction='none')- 定义损失函数为均方误差(MSE),
reduction='none'表示不对损失进行归约,返回每个样本的损失值。
- 定义损失函数为均方误差(MSE),
input_shape = train_features.shape[-1]- 获取输入特征的维度(即特征的数量)。
train_features.shape[-1]表示特征矩阵的最后一维大小。
- 获取输入特征的维度(即特征的数量)。
net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))- 定义一个简单的线性回归模型,使用
nn.Linear层将输入特征映射到一个输出值。这里不设置偏置项(bias=False),因为偏置项可能已经在特征中隐含(例如通过添加一列全为1的特征)。
- 定义一个简单的线性回归模型,使用
batch_size = min(10, train_labels.shape[0])- 设置批量大小为10或训练集样本数量的最小值。如果训练集样本数量小于10,则批量大小等于样本数量。
train_iter = d2l.load_array((train_features, train_labels.reshape(-1,1)), batch_size)- 使用
d2l.load_array创建训练数据迭代器。将训练特征和标签组合成一个数据集,并设置批量大小。
- 使用
test_iter = d2l.load_array((test_features, test_labels.reshape(-1,1)), batch_size=False)- 创建测试数据迭代器。这里将
batch_size设置为False,表示在测试时不对数据进行分批,直接使用整个测试集。
- 创建测试数据迭代器。这里将
trainer = torch.optim.SGD(net.parameters(), lr=0.01)- 定义优化器为随机梯度下降(SGD),学习率为0.01。
animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log', ...)- 创建一个动画绘制器,用于动态绘制训练过程中的损失曲线。设置横轴为训练轮数(
epoch),纵轴为损失(loss),并使用对数刻度(yscale='log')。
- 创建一个动画绘制器,用于动态绘制训练过程中的损失曲线。设置横轴为训练轮数(
for epoch in range(num_epochs):- 遍历训练轮数。
d2l.train_epoch_ch3(net, train_iter, loss, trainer)- 调用
d2l.train_epoch_ch3函数,完成一个训练轮次。该函数会遍历训练数据迭代器,计算损失,并通过优化器更新模型参数。
- 调用
if epoch == 0 or (epoch + 1) % 20 == 0:- 如果是第一个轮次,或者每20个轮次,记录训练集和测试集上的损失。
animator.add(epoch + 1, (evaluate_loss(net, train_iter, loss), evaluate_loss(net, test_iter, loss)))- 调用
evaluate_loss函数计算当前模型在训练集和测试集上的平均损失,并将结果添加到动画绘制器中。
- 调用
print('weight:', net[0].weight.data.numpy())- 在训练完成后,打印模型的权重。
总结
这段代码实现了一个简单的线性回归模型的训练过程,包括:
- 定义模型、损失函数和优化器。
- 创建训练和测试数据迭代器。
- 在每个训练轮次中更新模型参数。
- 定期记录并绘制训练集和测试集上的损失。
- 最后打印模型的权重。
通过这种方式,可以直观地观察模型的训练过程和性能变化。
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.三阶多项式函数拟合(正常)
代码解释
poly_features[:n_train, :4]- 提取
poly_features数组中前n_train行(训练集样本)的前4列(即只使用最高阶为3的多项式特征)。poly_features是一个二维数组,其中每一行代表一个样本,每一列代表一个阶数的特征值。
- 提取
poly_features[n_train:, :4]- 提取
poly_features数组中从第n_train行开始的所有行(测试集样本)的前4列。这部分数据用于测试模型的性能。
- 提取
labels[:n_train]- 提取
labels数组中前n_train个元素,作为训练集的标签。
- 提取
labels[n_train:]- 提取
labels数组中从第n_train个元素开始的所有元素,作为测试集的标签。
- 提取
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 ]]
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]]
6.高阶多项式函数拟合(过拟合)
代码解释
参数说明
poly_features[:n_train, :]- 提取
poly_features数组中前n_train行(训练集样本)的所有列(即使用所有阶数的多项式特征)。poly_features是一个二维数组,其中每一行代表一个样本,每一列代表一个阶数的特征值。 - 这里使用了完整的多项式特征,而不是限制特征的阶数。
- 提取
poly_features[n_train:, :]- 提取
poly_features数组中从第n_train行开始的所有行(测试集样本)的所有列。这部分数据用于测试模型的性能。 - 测试集也使用了完整的多项式特征。
- 提取
labels[:n_train]- 提取
labels数组中前n_train个元素,作为训练集的标签。
- 提取
labels[n_train:]- 提取
labels数组中从第n_train个元素开始的所有元素,作为测试集的标签。
- 提取
num_epochs=1500- 设置训练的总轮数为1500。这是
train函数的一个参数,用于控制训练过程的迭代次数。
- 设置训练的总轮数为1500。这是
代码的作用
- 训练集:使用
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 ]]

本章主要说明模型的过拟合和欠拟合问题,并使用三阶多项式函数拟合(正常)、线性函数拟合(欠拟合)、高阶多项式函数拟合(过拟合)等
浙公网安备 33010602011771号