我们之前做的目的都是评估训练的损失,训练的损失Loss告诉我们,我们的模型是否能够完全拟合训练集,也就是说我们的模型是否有足够的能力处理数据中的相关信息。但是我们之前都是评价训练的好坏,并没有引入验证集。接下来我们就需要用训练集训练模型然后在验证集中检验模型的好坏,因为如果模型的参数在训练集中表现很好,但是如果应用与实际场景中,必然有很多训练集中没有的数据,可能在这些数据中表现比较差,那对于整个模型来说也不是特别好,所以我们的模型不仅要在训练集中表现良好,更要在验证集(val)中表现良好才行。
一、为了评估模型性能
- 避免过拟合 模型在训练集上表现良好,但在验证集上表现不佳可能意味着过拟合。通过观察训练集和验证集的损失,能及时发现并调整模型,避免模型过度学习训练数据中的噪声和特定模式2。
- 选择最佳超参数 利用验证集的损失来比较不同超参数配置下模型的性能,从而选择最优的超参数,提升模型的泛化能力2。
二、更好地理解模型学习过程
- 观察模型收敛情况 比较训练集和验证集损失的变化趋势,了解模型在学习过程中的表现。例如,训练集损失持续降低但验证集损失趋于收敛,可能提示模型过拟合或训练集与测试集不是独立同分布2。
三、提高模型的泛化能力
- 调整模型结构和参数 根据训练集和验证集损失的差异,对模型的结构和参数进行优化,使模型能够更好地适应新的数据,提高泛化能力2。
四、确保模型的可靠性
- 验证模型的稳定性 观察不同随机种子下训练集和验证集的损失变化,评估模型对数据划分的敏感性,确保模型的稳定性和可靠性1。
获取数据集
那我们如何获取验证集呢,首先我们需要获得足够多的原始数据,这些数据往往是通过实际场景中收集的,但是小编这里直接写了一个程序来模拟数据,代码就分享给大家。
t_c = torch.tensor([0.5, 14.0, 15.0, 28.0, 11.0,
8.0, 3.0, -4.0, 6.0, 13.0, 21.0])
t_u = torch.tensor([35.7, 55.9, 58.2, 81.9, 56.3, 48.9,
33.9, 21.8, 48.4, 60.4, 68.4])
# 按照一定的规律生成100组原始数据集
# 假设的线性关系参数(这里我们先用简单的均值和方差来估计,但更准确的方法是使用最小二乘法)
# 注意:这里只是为了演示,实际中应该使用统计方法来准确估计
slope_est = (t_c.mean() - t_c.min()) / (t_u.mean() - t_u.min())
intercept_est = t_c.mean() - slope_est * t_u.mean()
# 生成额外的数据点
np.random.seed(0) # 为了可重复性设置随机种子
additional_u = np.linspace(t_u.min(), t_u.max() * 2, 90) # 生成额外的u值
additional_c = slope_est * torch.tensor(additional_u, dtype=torch.float32) + intercept_est + 0.5 * torch.randn(
90) # 加上一些噪声
# 合并原始数据和额外数据
t_u = torch.cat((t_u, torch.tensor(additional_u, dtype=torch.float32)))
t_c = torch.cat((t_c, additional_c))
感兴趣的可以把他们打印出来看一下,因为我设置的随机的种子,所以大家打印出来的数据是完全一样的
tensor([ 35.7000, 55.9000, 58.2000, 81.9000, 56.3000, 48.9000,
33.9000, 21.8000, 48.4000, 60.4000, 68.4000, 21.8000,
23.3955, 24.9910, 26.5865, 28.1820, 29.7775, 31.3730,
32.9685, 34.5640, 36.1595, 37.7551, 39.3506, 40.9461,
42.5416, 44.1371, 45.7326, 47.3281, 48.9236, 50.5191,
52.1146, 53.7101, 55.3056, 56.9011, 58.4966, 60.0921,
61.6876, 63.2831, 64.8787, 66.4742, 68.0697, 69.6652,
71.2607, 72.8562, 74.4517, 76.0472, 77.6427, 79.2382,
80.8337, 82.4292, 84.0247, 85.6202, 87.2157, 88.8112,
90.4067, 92.0023, 93.5978, 95.1933, 96.7888, 98.3843,
99.9798, 101.5753, 103.1708, 104.7663, 106.3618, 107.9573,
109.5528, 111.1483, 112.7438, 114.3393, 115.9348, 117.5303,
119.1258, 120.7214, 122.3169, 123.9124, 125.5079, 127.1034,
128.6989, 130.2944, 131.8899, 133.4854, 135.0809, 136.6764,
138.2719, 139.8674, 141.4629, 143.0584, 144.6539, 146.2494,
147.8449, 149.4404, 151.0360, 152.6315, 154.2270, 155.8225,
157.4180, 159.0135, 160.6090, 162.2045, 163.8000])
tensor([ 0.5000, 14.0000, 15.0000, 28.0000, 11.0000, 8.0000, 3.0000,
-4.0000, 6.0000, 13.0000, 21.0000, -4.2347, -3.2014, -2.1179,
-1.6428, -0.9509, -0.5199, 0.2784, 1.8311, 2.9875, 2.8588,
4.4212, 4.5838, 5.1310, 6.8234, 5.9734, 7.1035, 9.4547,
8.6025, 10.6773, 9.9846, 11.9821, 11.9866, 13.3628, 13.4712,
14.0208, 14.8172, 16.4681, 17.4892, 18.3710, 18.8813, 18.5837,
19.6088, 20.5467, 21.7026, 21.6737, 21.9981, 24.0391, 24.4194,
25.7778, 25.5416, 26.1872, 27.3508, 27.3104, 29.4915, 29.1853,
31.0119, 31.5497, 32.4764, 33.0286, 32.5419, 34.0226, 35.3561,
36.8098, 37.1465, 37.9528, 38.3130, 38.8218, 40.1028, 40.7696,
42.1289, 41.1947, 42.5819, 43.5023, 43.5928, 45.2544, 45.7708,
47.4863, 48.2580, 48.3122, 49.1236, 50.2917, 51.2436, 50.8630,
52.8491, 52.9410, 53.7065, 54.7200, 54.7298, 56.8450, 57.1763,
56.5910, 58.7009, 59.2632, 59.9197, 61.0314, 61.5569, 61.7117,
62.8750, 63.9156, 65.0431])
生成训练集与验证集
然后我们先获取t_u的行数,通过对行数加权来获取我们想要的验证集的个数,小编这里加权给的是0.2,然后对原始数据进行打乱,把100个原始数据分为2份,一份是80个数据的训练集,另一份是20个数据的验证集。
n_samples = t_u.shape[0] # 这条代码的作用是获取数组t_u的行数,并将这个值赋给变量n_samples
n_val = int(0.2 * n_samples)
shuffled_indices = torch.randperm(n_samples) # 返回一个长度为 n_samples 的随机排列的整数序列
train_indices = shuffled_indices[:-n_val] # 训练集
val_indices = shuffled_indices[-n_val:] # 验证集
接下来的训练和之前几节的训练是一样的,区别就是,在训练的过程中顺便用训练训练集更新出来的params参数对验证集进行损失计算,并把Loss的值打印出来,方便后期的分析。
import numpy as np
import torch
import torch.optim as optim # 导入优化器模块
torch.set_printoptions(edgeitems=2, linewidth=75) # 设置打印格式
def model(t_u, w, b):
return w * t_u + b
def loss_fn(t_p, t_c):
squared_diffs = (t_p - t_c)**2
return squared_diffs.mean()
# 初始化参数
t_c = torch.tensor([0.5, 14.0, 15.0, 28.0, 11.0,
8.0, 3.0, -4.0, 6.0, 13.0, 21.0])
t_u = torch.tensor([35.7, 55.9, 58.2, 81.9, 56.3, 48.9,
33.9, 21.8, 48.4, 60.4, 68.4])
# 按照一定的规律生成100组原始数据集
# 假设的线性关系参数(这里我们先用简单的均值和方差来估计,但更准确的方法是使用最小二乘法)
# 注意:这里只是为了演示,实际中应该使用统计方法来准确估计
slope_est = (t_c.mean() - t_c.min()) / (t_u.mean() - t_u.min())
intercept_est = t_c.mean() - slope_est * t_u.mean()
# 生成额外的数据点
np.random.seed(0) # 为了可重复性设置随机种子
additional_u = np.linspace(t_u.min(), t_u.max() * 2, 90) # 生成额外的u值
additional_c = slope_est * torch.tensor(additional_u, dtype=torch.float32) + intercept_est + 0.5 * torch.randn(
90) # 加上一些噪声
# 合并原始数据和额外数据
t_u = torch.cat((t_u, torch.tensor(additional_u, dtype=torch.float32)))
t_c = torch.cat((t_c, additional_c))
print(t_u)
print(t_c)
t_un = 0.1 * t_u # 归一化处理,防止梯度爆炸
n_samples = t_u.shape[0] # 这条代码的作用是获取数组t_u的行数,并将这个值赋给变量n_samples
n_val = int(0.2 * n_samples)
shuffled_indices = torch.randperm(n_samples) # 返回一个长度为 n_samples 的随机排列的整数序列
print(shuffled_indices)
# tensor([ 6, 3, 2, 0, 7, 4, 5, 8, 9, 10, 1])
train_indices = shuffled_indices[:-n_val] # 训练集
# print(train_indices)
val_indices = shuffled_indices[-n_val:] # 验证集
# print(val_indices)
# 随机把原始数据t_u,t_c分成验证集和训练集
train_indices, val_indices
train_t_u = t_u[train_indices]
train_t_c = t_c[train_indices]
val_t_u = t_u[val_indices]
val_t_c = t_c[val_indices]
# 归一化处理
train_t_un = 0.1 * train_t_u
val_t_un = 0.1 * val_t_u
def training_loop(n_epochs, optimizer, params, train_t_u, val_t_u,
train_t_c, val_t_c):
for epoch in range(1, n_epochs + 1):
train_t_p = model(train_t_u, *params)
train_loss = loss_fn(train_t_p, train_t_c)
val_t_p = model(val_t_u, *params)
val_loss = loss_fn(val_t_p, val_t_c)
optimizer.zero_grad()
train_loss.backward() # <2>
optimizer.step()
if epoch <= 3 or epoch % 500 == 0:
print(f"Epoch {epoch}, Training loss {train_loss.item():.4f},"
f" Validation loss {val_loss.item():.4f}")
return params
params = torch.tensor([1.0, 0.0], requires_grad=True)
learning_rate = 1e-3
optimizer = optim.SGD([params], lr=learning_rate)
training_loop(
n_epochs = 3000,
optimizer = optimizer,
params = params,
train_t_u = train_t_un,
val_t_u = val_t_un,
train_t_c = train_t_c,
val_t_c = val_t_c)
我们把epochs改为13000轮
不难发现,训练集和验证集收敛的还是比较快的,Loss值也比较豪,效果还是比较好的