通过观察神经网络和深度学习模型在训练过程中随时间的表现,您可以了解很多关于神经网络和深度学习模型的知识。例如,如果您发现训练精度随着训练周期的增加而变差,您就知道优化存在问题。可能是你的学习速度太快了。在这篇文章中,您将了解如何在训练期间查看和可视化 PyTorch 模型随时间的性能。完成这篇文章后,您将了解:
- 训练期间收集哪些指标
- 如何在训练中绘制训练和验证数据集的指标
- 如何解读剧情来讲述模型和训练进度
概述
本章分为两部分;他们是:
- 从训练循环中收集指标
- 绘制训练历史图
从训练循环中收集指标
在深度学习中,使用梯度下降算法训练模型意味着使用模型和损失函数进行前向传递以从输入推断损失度量,然后进行后向传递以根据损失度量计算梯度,并更新过程应用梯度来更新模型参数。虽然这些是您必须采取的基本步骤,但您可以在此过程中执行更多操作来收集其他信息。
正确训练的模型应该预期损失指标会减少,因为损失是优化的目标。使用的损失度量应该取决于问题。
对于回归问题,模型的预测越接近实际值越好。因此,您需要跟踪均方误差 (MSE),或者有时需要跟踪均方根误差 (RMSE)、平均绝对误差 (MAE) 或平均绝对百分比误差 (MAPE)。尽管不用作损失指标,您可能也对模型产生的最大误差感兴趣。
对于分类问题,通常损失度量是交叉熵。但交叉熵的值并不是很直观。因此,您可能还想跟踪预测的准确性、真阳性率、精确度、召回率、F1 分数等。
从训练循环中收集这些指标非常简单。让我们从使用 PyTorch 和加州住房数据集的深度学习的基本回归示例开始:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Read data
data = fetch_california_housing()
X, y = data.data, data.target
# train-test split for model evaluation
X_train_raw, X_test_raw, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
# Standardizing data
scaler = StandardScaler()
scaler.fit(X_train_raw)
X_train = scaler.transform(X_train_raw)
X_test = scaler.transform(X_test_raw)
# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(–1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(–1, 1)
# Define the model
model = nn.Sequential(
nn.Linear(8, 24),
nn.ReLU(),
nn.Linear(24, 12),
nn.ReLU(),
nn.Linear(12, 6),
nn.ReLU(),
nn.Linear(6, 1)
)
# loss function and optimizer
loss_fn = nn.MSELoss() # mean square error
optimizer = optim.Adam(model.parameters(), lr=0.001)
n_epochs = 100 # number of epochs to run
batch_size = 32 # size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)
for epoch in range(n_epochs):
for start in batch_start:
# take a batch
X_batch = X_train[start:start+batch_size]
y_batch = y_train[start:start+batch_size]
# forward pass
y_pred = model(X_batch)
loss = loss_fn(y_pred, y_batch)
# backward pass
optimizer.zero_grad()
loss.backward()
# update weights
optimizer.step()
|
此实现很原始,但您loss
在过程中的每个步骤中都以张量的形式获得,这为优化器提供了改进模型的提示。要了解训练的进度,您当然可以在每一步打印这个损失指标。但您也可以保存该值,以便稍后可视化。当你这样做时,请注意你不想保存张量,而只是保存它的值。这是因为这里的 PyTorch 张量会记住它的值是如何产生的,因此可以进行自动微分。这些附加数据占用内存,但您不需要它们。
因此,您可以将训练循环修改为以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
mse_history = []
for epoch in range(n_epochs):
for start in batch_start:
# take a batch
X_batch = X_train[start:start+batch_size]
y_batch = y_train[start:start+batch_size]
# forward pass
y_pred = model(X_batch)
loss = loss_fn(y_pred, y_batch)
mse_history.append(float(loss))
# backward pass
optimizer.zero_grad()
loss.backward()
# update weights
optimizer.step()
|
在训练模型时,您应该使用与训练集分离的测试集对其进行评估。通常,在一个时期内完成一次,在该时期的所有训练步骤之后完成。测试结果也可以保存以便以后可视化。事实上,如果您愿意,您可以从测试集中获取多个指标。因此,您可以按如下方式添加到训练循环中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
mae_fn = nn.L1Loss() # create a function to compute MAE
train_mse_history = []
test_mse_history = []
test_mae_history = []
for epoch in range(n_epochs):
model.train()
for start in batch_start:
# take a batch
X_batch = X_train[start:start+batch_size]
y_batch = y_train[start:start+batch_size]
# forward pass
y_pred = model(X_batch)
loss = loss_fn(y_pred, y_batch)
train_mse_history.append(float(loss))
# backward pass
optimizer.zero_grad()
loss.backward()
# update weights
optimizer.step()
# validate model on test set
model.eval()
with torch.no_grad():
y_pred = model(X_test)
mse = loss_fn(y_pred, y_test)
mae = mae_fn(y_pred, y_test)
test_mse_history.append(float(mse))
test_mae_history.append(float(mae))
|
您可以定义自己的函数来计算指标,也可以使用 PyTorch 库中已实现的函数。在评估时将模型切换到评估模式是一个很好的做法。在上下文中运行评估也是一种很好的做法no_grad()
,在这种情况下,您明确告诉 PyTorch 您无意对张量运行自动微分。
然而,上面的代码存在一个问题:训练集中的 MSE 在每个训练步骤中基于一个批次计算一次,而测试集中的指标在每个 epoch 上计算一次并基于整个测试集。它们没有直接可比性。事实上,如果你从训练步骤看 MSE,你会发现它非常嘈杂。更好的方法是将同一时期的 MSE 总结为一个数字(例如,它们的平均值),以便您可以与测试集的数据进行比较。
进行此更改,以下是完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Read data
data = fetch_california_housing()
X, y = data.data, data.target
# train-test split for model evaluation
X_train_raw, X_test_raw, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
# Standardizing data
scaler = StandardScaler()
scaler.fit(X_train_raw)
X_train = scaler.transform(X_train_raw)
X_test = scaler.transform(X_test_raw)
# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(–1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(–1, 1)
# Define the model
model = nn.Sequential(
nn.Linear(8, 24),
nn.ReLU(),
nn.Linear(24, 12),
nn.ReLU(),
nn.Linear(12, 6),
nn.ReLU(),
nn.Linear(6, 1)
)
# loss function, metrics, and optimizer
loss_fn = nn.MSELoss() # mean square error
mae_fn = nn.L1Loss() # mean absolute error
optimizer = optim.Adam(model.parameters(), lr=0.001)
n_epochs = 100 # number of epochs to run
batch_size = 32 # size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)
train_mse_history = []
test_mse_history = []
test_mae_history = []
for epoch in range(n_epochs):
model.train()
epoch_mse = []
for start in batch_start:
# take a batch
X_batch = X_train[start:start+batch_size]
y_batch = y_train[start:start+batch_size]
# forward pass
y_pred = model(X_batch)
loss = loss_fn(y_pred, y_batch)
epoch_mse.append(float(loss))
# backward pass
optimizer.zero_grad()
loss.backward()
# update weights
optimizer.step()
mean_mse = sum(epoch_mse) / len(epoch_mse)
train_mse_history.append(mean_mse)
# validate model on test set
model.eval()
with torch.no_grad():
y_pred = model(X_test)
mse = loss_fn(y_pred, y_test)
mae = mae_fn(y_pred, y_test)
test_mse_history.append(float(mse))
test_mae_history.append(float(mae))
|
绘制训练历史图
在上面的代码中,您在 Python 列表中收集了指标,每个时期各一个。因此,使用 matplotlib 将它们绘制成线图是很简单的。下面是一个例子:
1
2
3
4
5
6
7
8
9
|
import matplotlib.pyplot as plt
import numpy as np
plt.plot(np.sqrt(train_mse_history), label=“Train RMSE”)
plt.plot(np.sqrt(test_mse_history), label=“Test RMSE”)
plt.plot(test_mae_history, label=“Test MAE”)
plt.xlabel(“epochs”)
plt.legend()
plt.show()
|
例如,它绘制以下内容:
像这样的图可以指示有关模型训练的有用信息,例如:
- 历元内的收敛速度(斜率)
- 模型是否已经收敛(线的平台期)
- 模型是否可能过度学习训练数据(验证线的变形)
在上面的回归示例中,如果模型变得更好,指标 MAE 和 MSE 都应该下降。然而,在分类示例中,随着进行更多训练,准确度度量应该增加,而交叉熵损失应该减少。这就是您期望从情节中看到的内容。
这些曲线最终应该变平,这意味着您无法根据当前数据集、模型设计和算法进一步改进模型。您希望这种情况尽快发生,以便您的模型收敛得 更快,因为您的训练效率很高。您还希望指标在高精度或低损失区域变平,以便您的模型能够有效进行预测。
图中需要注意的另一个属性是训练和验证的指标有何不同。在上面,您可以看到训练集的 RMSE 在开始时高于测试集的 RMSE,但很快,曲线交叉并且测试集的 RMSE 在最后更高。这是预期的,因为最终模型将更好地适应训练集,但测试集可以预测模型在未来未见过的数据上的表现。
您需要小心地解释微观尺度的曲线或指标。在上图中,您可以看到训练集的 RMSE 与 epoch 0 中测试集的 RMSE 相比非常大。它们的差异可能没有那么大,但是因为您通过在 epoch 0 期间获取每个步骤的 MSE 来收集训练集的 RMSE。第一个纪元,您的模型可能在前几步中表现不佳,但在该纪元的最后几步中表现得更好。对所有步骤取平均值可能不是一个公平的比较,因为测试集中的 MSE 基于最后一步之后的模型。
如果您发现训练集的指标比测试集的指标好得多,则您的模型过度拟合。这可能暗示您应该在较早的时期停止训练,或者您的模型设计需要一些正则化,例如 dropout 层。
在上图中,虽然您收集了回归问题的均方误差 (MSE),但绘制了均方根误差 (RMSE),因此您可以与相同比例的平均绝对误差 (MAE) 进行比较。也许您还应该收集训练集的 MAE。两条 MAE 曲线的行为应与 RMSE 曲线类似。
将所有内容放在一起,以下是完整的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Read data
data = fetch_california_housing()
X, y = data.data, data.target
# train-test split for model evaluation
X_train_raw, X_test_raw, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
# Standardizing data
scaler = StandardScaler()
scaler.fit(X_train_raw)
X_train = scaler.transform(X_train_raw)
X_test = scaler.transform(X_test_raw)
# Convert to 2D PyTorch tensors
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).reshape(–1, 1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).reshape(–1, 1)
# Define the model
model = nn.Sequential(
nn.Linear(8, 24),
nn.ReLU(),
nn.Linear(24, 12),
nn.ReLU(),
nn.Linear(12, 6),
nn.ReLU(),
nn.Linear(6, 1)
)
# loss function, metrics, and optimizer
loss_fn = nn.MSELoss()# mean square error
mae_fn = nn.L1Loss()# mean absolute error
optimizer = optim.Adam(model.parameters(), lr=0.001)
n_epochs = 100# number of epochs to run
batch_size = 32# size of each batch
batch_start = torch.arange(0, len(X_train), batch_size)
train_mse_history = []
test_mse_history = []
test_mae_history = []
for epoch in range(n_epochs):
model.train()
epoch_mse = []
for start in batch_start:
# take a batch
X_batch = X_train[start:start+batch_size]
y_batch = y_train[start:start+batch_size]
# forward pass
y_pred = model(X_batch)
loss = loss_fn(y_pred, y_batch)
epoch_mse.append(float(loss))
# backward pass
optimizer.zero_grad()
loss.backward()
# update weights
optimizer.step()
mean_mse = sum(epoch_mse) / len(epoch_mse)
train_mse_history.append(mean_mse)
# validate model on test set
model.eval()
with torch.no_grad():
y_pred = model(X_test)
mse = loss_fn(y_pred, y_test)
mae = mae_fn(y_pred, y_test)
test_mse_history.append(float(mse))
test_mae_history.append(float(mae))
plt.plot(np.sqrt(train_mse_history), label=“Train RMSE”)
plt.plot(np.sqrt(test_mse_history), label=“Test RMSE”)
plt.plot(test_mae_history, label=“Test MAE”)
plt.xlabel(“epochs”)
plt.legend()
plt.show()
|
概括
在本章中,您发现了在训练深度学习模型时收集和审查指标的重要性。你学到了:
- 在模型训练期间要寻找哪些指标
- 如何在 PyTorch 训练循环中计算和收集指标
- 如何可视化训练循环中的指标
- 如何解释指标以推断有关培训体验的详细信息