我们已经讨论了常见的调试难点和可视化工具,现在是时候应用这些原理了。这个练习部分提供了动手练习,以巩固你识别错误、检查模型行为以及使用 TensorBoard 和标准调试方法监控训练进度的技能。我们将处理涉及形状不匹配、设备放置错误和设置可视化的场景。练习 1:修正形状不匹配形状不匹配是构建或修改神经网络时常见的错误。请看下面这个简单模型,它旨在处理 28x28 灰度图像(如 MNIST),并将其展平为 784 元素的向量:import torch import torch.nn as nn class SimpleMLP(nn.Module): def __init__(self): super().__init__() self.layer1 = nn.Linear(784, 128) # 输入 784,输出 128 self.activation = nn.ReLU() self.layer2 = nn.Linear(128, 64) # 输入 128,输出 64 # layer3 的输入大小不正确 - 应该是 64 self.layer3 = nn.Linear(100, 10) # 输入 100(错误!),输出 10 def forward(self, x): x = self.layer1(x) x = self.activation(x) x = self.layer2(x) x = self.activation(x) # 这行代码将导致错误 x = self.layer3(x) return x # 创建一个模拟输入批次(批次大小 4,特征 784) dummy_input = torch.randn(4, 784) model = SimpleMLP() # 尝试前向传播 try: output = model(dummy_input) print("模型运行成功!") except RuntimeError as e: print(f"捕获到错误:{e}") 运行代码: 执行上面的代码片段。你会遇到一个 RuntimeError。仔细查看错误信息。它通常指向尺寸不匹配,常常会提到特定层的预期输入尺寸和实际输入尺寸(在本例中是 mat1 and mat2 shapes cannot be multiplied)。诊断: 错误发生在输入 x 到达 self.layer3 时。前一个层 self.layer2 输出一个形状为 (batch_size, 64) 的张量。然而,self.layer3 被定义为 nn.Linear(100, 10),它预期输入有 100 个特征。这种不匹配导致了错误。你可以在报错行之前插入打印语句来确认形状:# 在 forward 方法中,在 self.layer3(x) 之前 print("layer3 之前的形状:", x.shape) x = self.layer3(x)修正代码: 修改 __init__ 方法中 self.layer3 的定义,以接受正确数量的输入特征(即 64,self.layer2 的输出大小)。# 修正后的层定义 self.layer3 = nn.Linear(64, 10) # 输入 64,输出 10验证: 使用修正后的层定义重新运行脚本。前向传播现在应该能顺利完成,没有错误。练习 2:修正设备放置使用 GPU 时,很重要的一点是模型和数据都在同一个设备上。我们来模拟一个错误场景,其中模型被移到 GPU,但输入张量仍留在 CPU 上。import torch import torch.nn as nn # 假设有可用的 CUDA GPU if torch.cuda.is_available(): device = torch.device("cuda") print("正在使用 GPU:", torch.cuda.get_device_name(0)) else: device = torch.device("cpu") print("正在使用 CPU") class SimpleNet(nn.Module): def __init__(self): super().__init__() self.linear = nn.Linear(10, 5) def forward(self, x): return self.linear(x) # 创建模型并将其移动到目标设备(例如,GPU) model = SimpleNet().to(device) print(f"模型参数位于:{next(model.parameters()).device}") # 创建输入数据 - 有意留在 CPU 上 input_data = torch.randn(8, 10) print(f"输入数据位于:{input_data.device}") # 尝试前向传播 - 如果设备是 'cuda',这可能会导致错误 try: output = model(input_data) print("前向传播成功!") except RuntimeError as e: print(f"\n捕获到错误:{e}") print("\n提示:检查模型和输入数据是否在同一个设备上。") 运行代码: 如果你有支持 CUDA 的 GPU,运行这段代码会产生一个 RuntimeError。错误信息很可能会显示类似 Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu! 的内容。诊断: 打印语句确认模型位于 cuda 设备上(如果可用),而 input_data 位于 cpu 设备上。PyTorch 操作通常要求操作数位于同一个设备上。修正代码: 在将 input_data 传递给模型之前,将其移动到模型所在的设备上。# 将输入数据移动到正确设备 input_data = input_data.to(device) print(f"输入数据已移至:{input_data.device}") # 现在,再次尝试前向传播 output = model(input_data) print("数据移动后前向传播成功!")验证: 重新运行修正后的脚本。前向传播应该能顺利执行,没有设备不匹配错误。记住这个原理也适用于训练循环中;从 DataLoader 获取的每个批次都需要被移动到相应的设备上。练习 3:使用 TensorBoard 可视化训练TensorBoard 为训练过程提供了宝贵的参考信息。让我们将其集成到一个简化训练循环中。我们将模拟训练数据并追踪一个模拟损失值。import torch import torch.nn as nn import torch.optim as optim from torch.utils.tensorboard import SummaryWriter import time # 1. 设置 TensorBoard 写入器 # 日志文件将保存在 'runs/simple_experiment' 目录中 writer = SummaryWriter('runs/simple_experiment') # 2. 定义一个简单模型、损失函数和优化器 model = nn.Linear(10, 2) # 简单的线性模型 criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=0.01) # 模拟一个简单数据集 inputs = torch.randn(100, 10) # 100 个样本,10 个特征 targets = torch.randn(100, 2) # 100 个样本,2 个输出值 # 3. 简单训练循环 print("开始模拟训练...") num_epochs = 50 for epoch in range(num_epochs): optimizer.zero_grad() # 梯度清零 outputs = model(inputs) # 前向传播 loss = criterion(outputs, targets) # 计算损失 # 模拟损失变化(在实际训练中替换为真实损失) # 为演示目的,让损失随周期递减 simulated_loss = loss + torch.randn(1) * 0.1 + (num_epochs - epoch) / num_epochs simulated_loss.backward() # 反向传播(使用模拟损失进行演示) optimizer.step() # 更新权重 # 4. 将指标记录到 TensorBoard if (epoch + 1) % 5 == 0: # 每 5 个周期记录一次 # 记录标量“损失”值 writer.add_scalar('Training/Loss', simulated_loss.item(), epoch) # 记录模型权重分布(以线性层为例) writer.add_histogram('Model/Weights', model.weight, epoch) writer.add_histogram('Model/Bias', model.bias, epoch) print(f'周期 [{epoch+1}/{num_epochs}],模拟损失:{simulated_loss.item():.4f}') time.sleep(0.1) # 模拟训练时间 # 5. 添加模型图(可选) # 确保输入形状与模型预期一致 # writer.add_graph(model, inputs[0].unsqueeze(0)) # 提供一个样本输入批次 # 6. 关闭写入器 writer.close() print("模拟训练完成。TensorBoard 日志已保存到 'runs/simple_experiment'。") print("在你的终端中运行 'tensorboard --logdir=runs' 来查看。") 运行代码: 执行 Python 脚本。它会打印周期进度并提及保存日志。启动 TensorBoard: 打开你的终端或命令提示符,导航到 包含 runs 文件夹的目录(而不是 runs 文件夹内部),并运行命令:tensorboard --logdir=runs在浏览器中查看: TensorBoard 会输出一个 URL(通常是 http://localhost:6006/)。在你的网页浏览器中打开这个 URL。浏览: 浏览 TensorBoard 界面。你应该能找到 simple_experiment 运行记录。在“标量”(Scalars)标签页下,你会看到“训练/损失”(Training/Loss)图表,它显示了我们模拟损失的递减趋势。在“直方图”(Histograms)或“分布”(Distributions)标签页下,你可以观察模型权重和偏差的分布如何随周期变化(或者在这个简单模拟中变化不大)。如果你取消注释了 add_graph 那行代码,你还会发现模型架构的可视化图表在“图”(Graphs)标签页下。下面这个图表显示了一个在 TensorBoard 中查看时,损失如何随周期递减的例子。{"data":[{"type": "scatter", "mode": "lines", "x": [0, 5, 10, 15, 20, 25, 30, 35, 40, 45], "y": [1.8, 1.5, 1.2, 1.0, 0.8, 0.65, 0.5, 0.4, 0.3, 0.25], "name": "训练损失", "line": {"color": "#228be6"}}], "layout": {"title": "模拟训练损失随周期变化", "xaxis": {"title": "周期"}, "yaxis": {"title": "损失"}, "width": 600, "height": 400}}一个折线图,描绘了 50 个周期内模拟训练损失的递减情况,每 5 个周期记录一次。练习 4:使用 Python 调试器 (pdb)有时,打印语句不足以解决问题,你需要交互式地检查程序状态。Python 调试器(pdb)是一个强大的工具。让我们回顾练习 1 中的形状不匹配场景,并使用 pdb。通过在顶部添加 import pdb 并在导致错误的行之前添加 pdb.set_trace(),修改练习 1 中 原始的 失败代码:import torch import torch.nn as nn import pdb # 导入调试器 class SimpleMLP(nn.Module): def __init__(self): super().__init__() self.layer1 = nn.Linear(784, 128) self.activation = nn.ReLU() self.layer2 = nn.Linear(128, 64) # layer3 的输入大小不正确 self.layer3 = nn.Linear(100, 10) # 这里有错误! def forward(self, x): x = self.layer1(x) x = self.activation(x) x = self.layer2(x) x = self.activation(x) print("即将进入 pdb...") pdb.set_trace() # 在此设置断点 # 执行将在此暂停 print("layer3 之前的形状:", x.shape) # 我们可以在 pdb 中检查 x x = self.layer3(x) # 这行代码将导致错误 return x dummy_input = torch.randn(4, 784) model = SimpleMLP() output = model(dummy_input) # 运行现在将在 forward() 内部暂停 运行修改后的代码: 执行脚本。当程序执行到 pdb.set_trace() 时,它会暂停,你会在终端中看到 (Pdb) 提示符。与 pdb 交互:输入 p x.shape(打印 x.shape)然后按回车键。你会看到 torch.Size([4, 64])。输入 p self.layer3 然后按回车键。你会看到定义 Linear(in_features=100, out_features=10, bias=True)。比较输入形状(64 个特征)与层预期输入(100 个特征),可以清楚地看到不匹配。输入 n(下一步)然后按回车键。这将尝试执行下一行代码(x = self.layer3(x)),这会导致 RuntimeError,并可能退出调试器或在其中显示错误堆栈。或者,输入 c(继续)让程序继续运行直到下一个断点或出现错误。输入 q(退出)立即退出调试器并终止脚本。修正并删除: 一旦你理解了问题,你可以输入 q 退出,然后像练习 1 中那样修正代码,并删除 import pdb 和 pdb.set_trace() 行。这个练习为你在 PyTorch 项目中处理调试和监控任务打下了基础。记住,使用打印语句进行快速检查,pdb 进行交互式检查,以及 TensorBoard 用于可视化训练过程和模型结构。这些工具对于构建、训练和改进高效的深度学习模型来说非常重要。