趋近智
既然您已经了解了训练的各个组成部分,我们将其整合到一个完整的、可运行的例子中。本实践练习将引导您设置模型、准备数据,并实现训练和评估循环,从而巩固本章讨论的内容。我们还会提到保存训练好的模型状态。
一个使用合成数据的简单线性回归问题将被解决。训练模型的目标是学习 的关系。
首先,我们导入必要的PyTorch模块,并为训练过程定义一些基本超参数。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader
# 超参数
learning_rate = 0.01
num_epochs = 100
batch_size = 16
# 设备配置(如果可用则使用GPU)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"正在使用设备: {device}")
我们将生成用于线性关系的合成数据,并使用 TensorDataset 和 DataLoader 对其进行包装。
# 生成合成数据: y = 2x + 1 + 噪声
true_weight = torch.tensor([[2.0]])
true_bias = torch.tensor([1.0])
# 生成训练数据
X_train_tensor = torch.randn(100, 1) * 5 # 100 个样本, 1 个特征
y_train_tensor = true_weight * X_train_tensor + true_bias + torch.randn(100, 1) * 0.5 # 添加一些噪声
# 生成验证数据(独立数据集)
X_val_tensor = torch.randn(20, 1) * 5 # 20 个样本, 1 个特征
y_val_tensor = true_weight * X_val_tensor + true_bias + torch.randn(20, 1) * 0.5
# 创建数据集
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
val_dataset = TensorDataset(X_val_tensor, y_val_tensor)
# 创建数据加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=False) # 验证数据无需打乱
这里,TensorDataset 方便地将输入特征 (X) 和目标标签 (y) 张量包装起来。DataLoader 接着接收此数据集,并提供可迭代的批次,自动处理打乱和批次化。
现在,定义模型架构、损失函数 (loss function)和优化器。由于我们正在模拟线性关系 ,因此一个简单的线性层就足够了。
# 定义模型(一个简单的线性层)
# 输入特征尺寸 = 1, 输出特征尺寸 = 1
model = nn.Linear(1, 1).to(device) # 将模型移动到选定设备
# 定义损失函数(用于回归的均方误差)
loss_fn = nn.MSELoss()
# 定义优化器(随机梯度下降)
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
print("模型定义:")
print(model)
print("\n初始参数:")
for name, param in model.named_parameters():
if param.requires_grad:
print(f"{name}: {param.data.squeeze()}")
我们实例化 nn.Linear,它表示操作 。PyTorch 会自动初始化权重 (weight) () 和偏置 (bias) () 参数 (parameter)。我们使用均方误差 (nn.MSELoss),因为它是回归任务的标准方法,用于衡量预测值与真实值之间的平均平方差。选择随机梯度下降 (gradient descent) (optim.SGD) 来根据计算出的梯度更新模型的参数。请注意,我们将 model.parameters() 传递给优化器,以便它知道要更新哪些张量。最后,我们将模型移动到配置好的设备(CPU 或 GPU)上。
这是模型从数据中迭代学习过程的核心。
print("\n开始训练...")
for epoch in range(num_epochs):
model.train() # 将模型设置为训练模式
running_loss = 0.0
num_batches = 0
# 遍历DataLoader中的批次数据
for i, (features, labels) in enumerate(train_loader):
# 将批次数据移动到与模型相同的设备上
features = features.to(device)
labels = labels.to(device)
# 1. 前向传播:计算模型的预测
outputs = model(features)
# 2. 计算损失
loss = loss_fn(outputs, labels)
# 3. 反向传播:计算梯度
# 首先,清除上一步的梯度
optimizer.zero_grad()
# 然后,执行反向传播
loss.backward()
# 4. 优化器步骤:更新模型权重
optimizer.step()
# 累加损失以便报告
running_loss += loss.item()
num_batches += 1
# 打印本轮的平均损失
avg_epoch_loss = running_loss / num_batches
if (epoch + 1) % 10 == 0: # 每10轮打印一次
print(f"Epoch [{epoch+1}/{num_epochs}], Training Loss: {avg_epoch_loss:.4f}")
print("训练完成!")
我们来逐一分析训练轮次循环中的步骤:
model.train():将模型设置为训练模式。这对于像 Dropout 或 BatchNorm 这样的层很重要,因为它们在训练和评估期间行为不同。train_loader 以获取 features 和 labels 的批次。device 上。这可以防止运行时错误。outputs = model(features) 计算模型对输入批次的预测。loss = loss_fn(outputs, labels) 使用 MSE 准则计算预测与实际标签之间的差异。optimizer.zero_grad():清除旧梯度。如果忘记此步骤,梯度将从之前的迭代中累积,导致不正确的更新。loss.backward():计算损失相对于所有 requires_grad=True 的模型参数 (parameter)的梯度。optimizer.step() 使用反向传播中计算的梯度和优化算法(本例中为 SGD)更新模型的参数 (model.parameters())。running_loss 来报告本轮的平均损失。训练之后(或在训练期间定期,例如每轮结束后),我们需要在不更新模型权重 (weight)的情况下,评估模型在未见数据(验证集)上的表现。
print("\n开始评估...")
model.eval() # 将模型设置为评估模式
total_val_loss = 0.0
num_val_batches = 0
# 评估时禁用梯度计算
with torch.no_grad():
for features, labels in val_loader:
# 将批次数据移动到设备上
features = features.to(device)
labels = labels.to(device)
# 前向传播
outputs = model(features)
# 计算损失
loss = loss_fn(outputs, labels)
total_val_loss += loss.item()
num_val_batches += 1
avg_val_loss = total_val_loss / num_val_batches
print(f"验证损失: {avg_val_loss:.4f}")
# 检查学习到的参数
print("\n学习到的参数:")
for name, param in model.named_parameters():
if param.requires_grad:
print(f"{name}: {param.data.squeeze()}")
print(f"(真实权重: {true_weight.item():.4f}, 真实偏置: {true_bias.item():.4f})")
评估循环中的主要区别:
model.eval():将模型设置为评估模式。with torch.no_grad()::此上下文 (context)管理器在该代码块内禁用梯度计算。这很重要,因为我们在评估时不需要梯度,并且它能减少内存消耗并加快计算速度。loss.backward() 或 optimizer.step(),因为我们只测量性能,而不进行训练。评估后,我们打印学习到的参数 (parameter)。将它们与我们用于生成数据的 true_weight (2.0) 和 true_bias (1.0) 进行比较。在 100 轮训练后,它们应该相当接近。
保存您训练好的模型非常重要。标准做法是保存模型的 state_dict,其中包含所有学习到的参数 (parameter)(权重 (weight)和偏置 (bias))。
# 保存模型学习到的参数
model_save_path = 'linear_regression_model.pth'
torch.save(model.state_dict(), model_save_path)
print(f"\n模型 state_dict 已保存到 {model_save_path}")
# 加载模型状态的例子
# 首先,再次实例化模型架构
loaded_model = nn.Linear(1, 1).to(device)
# 然后,加载保存的状态字典
loaded_model.load_state_dict(torch.load(model_save_path))
print("模型 state_dict 加载成功。")
# 请记住,如果用于推断,请将加载的模型设置为评估模式
loaded_model.eval()
# 现在您可以使用 loaded_model 进行预测了
# 使用已加载模型进行预测的例子:
with torch.no_grad():
sample_input = torch.tensor([[10.0]]).to(device) # 示例输入
prediction = loaded_model(sample_input)
print(f"输入 10.0 的预测值: {prediction.item():.4f}")
# 预期输出应接近 2*10 + 1 = 21
通常,保存 state_dict 比保存整个模型对象更好,因为它更灵活,且在底层代码更改时更不容易出错。要加载状态,您需要首先创建相同模型架构的实例,然后将字典加载到其中。
以下是结合所有部分的完整脚本:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader
# 1. 设置:超参数和设备
learning_rate = 0.01
num_epochs = 100
batch_size = 16
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"正在使用设备: {device}")
# 2. 数据准备
true_weight = torch.tensor([[2.0]])
true_bias = torch.tensor([1.0])
X_train_tensor = torch.randn(100, 1, device=device) * 5 # 直接在设备上生成数据
y_train_tensor = true_weight.to(device) * X_train_tensor + true_bias.to(device) + torch.randn(100, 1, device=device) * 0.5
X_val_tensor = torch.randn(20, 1, device=device) * 5
y_val_tensor = true_weight.to(device) * X_val_tensor + true_bias.to(device) + torch.randn(20, 1, device=device) * 0.5
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
val_dataset = TensorDataset(X_val_tensor, y_val_tensor)
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=False)
# 3. 模型、损失和优化器
model = nn.Linear(1, 1).to(device)
loss_fn = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
print("模型定义:")
print(model)
print("\n初始参数:")
for name, param in model.named_parameters():
if param.requires_grad:
print(f"{name}: {param.data.squeeze()}")
# 4. 训练循环
print("\n开始训练...")
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
num_batches = 0
for i, (features, labels) in enumerate(train_loader):
# 数据已在正确设备上
outputs = model(features)
loss = loss_fn(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
num_batches += 1
avg_epoch_loss = running_loss / num_batches
if (epoch + 1) % 10 == 0:
print(f"Epoch [{epoch+1}/{num_epochs}], Training Loss: {avg_epoch_loss:.4f}")
print("训练完成!")
# 5. 评估循环
print("\n开始评估...")
model.eval()
total_val_loss = 0.0
num_val_batches = 0
with torch.no_grad():
for features, labels in val_loader:
# 数据已在正确设备上
outputs = model(features)
loss = loss_fn(outputs, labels)
total_val_loss += loss.item()
num_val_batches += 1
avg_val_loss = total_val_loss / num_val_batches
print(f"验证损失: {avg_val_loss:.4f}")
print("\n学习到的参数:")
for name, param in model.named_parameters():
if param.requires_grad:
print(f"{name}: {param.data.squeeze().item():.4f}") # 单个值使用 .item()
print(f"(真实权重: {true_weight.item():.4f}, 真实偏置: {true_bias.item():.4f})")
# 6. 保存和加载模型状态
model_save_path = 'linear_regression_model.pth'
torch.save(model.state_dict(), model_save_path)
print(f"\n模型 state_dict 已保存到 {model_save_path}")
loaded_model = nn.Linear(1, 1).to(device)
loaded_model.load_state_dict(torch.load(model_save_path))
loaded_model.eval()
print("模型 state_dict 加载成功。")
with torch.no_grad():
sample_input = torch.tensor([[10.0]]).to(device)
prediction = loaded_model(sample_input)
print(f"输入 10.0 的预测值: {prediction.item():.4f}")
(注:在合并脚本中,数据生成略有修改,直接在目标 device 上创建张量以提高效率,从而无需在循环内部对批次数据使用 .to(device)。)
这个动手示例呈现了在 PyTorch 中训练几乎任何模型的基本结构。您现在有了一个结合数据加载、模型定义、训练迭代、评估和持久化的模板。您可以通过更改步骤3中的模型架构和步骤2中的数据准备来调整此结构以适应更复杂的模型和数据集。训练和评估循环的核心逻辑保持了显著的一致性。
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•