
1. 为什么训练 PyTorch 模型时我离不开 TensorBoard1.1 单靠 loss 日志根本看不出训练是否健康很多人刚上手 PyTorch 时习惯在训练循环里 print 一下 loss盯着控制台跑完几百个 epoch。短期看没什么问题一旦模型变深、数据变多你就不得不承认控制台那点数字根本拼不出训练过程的全貌。loss 在下降但到底是正常收敛还是掉进了某个震荡状态学习率该不该调某一层权重是消失了还是分布异常这些信息藏在成百上千个 step 的数据里只靠肉眼扫日志几乎等于盲人摸象。我自己最早踩的坑也在这儿有一次训练一个图像分类模型前 20 个 epoch 的 loss 曲线看起来非常正常但准确率一直上不去。后来把每一层的权重直方图导出来一看才发现后几层的梯度基本是零整个网络处于退化状态。那种问题你盯着 loss 数字是永远看不到的。所以后来每个实验我都会上可视化而 PyTorch 里最省事的方案就是接上 TensorBoard。1.2 TensorBoard 在 PyTorch 生态里的真实定位先说个容易混淆的点TensorBoard 并不是 PyTorch 亲儿子它原本是 TensorFlow 生态的可视化套件。但 PyTorch 从 1.2 版本开始就在torch.utils.tensorboard里内置了兼容接口你不需要装什么第三方插件直接用官方模块就能把训练数据写进 TensorBoard 的事件文件里。用起来之后你会发现它其实是一个开在浏览器里的仪表盘。左侧是导航右侧是各种面板scalars 看损失曲线、images 看输入样本、graphs 看模型结构、histograms 看参数分布、projector 看高维向量。这套东西的好处是实时更新训练一边跑面板一边刷新不需要把训练停下来等完再画图。它的定位和价值可以这么理解训练过程是动态的可视化也应该动态。matplotlib 适合事后总结但不适合过程监控TensorBoard 则专为过程监控设计。免费、离线、本地运行对大多数单机训练场景来说已经够用。2. PyTorch 环境搭建与 TensorBoard 接入2.1 安装环节最容易踩的版本坑接入第一步先把 PyTorch 装对。网上一搜「pytorch 安装」能看到一堆教程但我建议尽量去 PyTorch 官网根据你的 CUDA 版本选安装命令。CPU 版本也能跑 TensorBoard只是训练慢GPU 版本注意要选和你本机驱动匹配的 CUDA 版本。装完之后先验证一下python -c import torch; print(torch.__version__)能正常输出版本号说明 PyTorch 基础框架没问题。接着装 TensorBoard。如果你用的 PyTorch 是 1.2 以上torch.utils.tensorboard已经自带大多数情况下不需要单独装但tensorboard这个命令行工具本身还是要安装的pip install tensorboard我个人习惯用pip install tensorboard2.14.0这类固定版本号避免和 PyTorch 内置 writer 产生兼容性差异。至于tensorboardX老项目里有人用但新项目建议直接用官方模块少装一个依赖少一个问题。2.2 用tensorboard --logdir把面板跑起来写完训练脚本后你会生成一个存放日志的目录。假设目录叫runs启动方式很简单tensorboard --logdir runs默认端口是 6006启动后浏览器打开http://localhost:6006。如果你在同一台机器上开了多个实验目录直接扫描整个runs目录左侧会出现不同实验的标签方便对比。实际使用中我强烈建议给每个实验起一个含信息的目录名比如runs/0421_resnet18_batch64。TensorBoard 会根据目录名显示曲线名目录名越有辨识度后面做实验对比时越不容易看串。2.3 各种可视化工具之间怎么选除了 TensorBoard这几年也冒出不少训练可视化工具比如 Weights Biases、MLflow、本地自建面板。如果你是个人开发者、离线训练居多的场景TensorBoard 成本最低如果你要团队协作、想直接共享实验链接那 WB 更合适。画图方面网上经常提到的 ECharts 数据可视化、Redis 客户端可视化工具、Kafka 可视化工具更多是通用数据展示或运维场景和 PyTorch 训练过程可视化不是一回事。做深度学习项目时老老实实用 TensorBoard 就足够。对比项列出来就清楚了工具适用场景实时性成本适合谁TensorBoardPyTorch/TF 训练监控高免费绝大多数个人、团队WB团队协作、远程记录高云端收费深度调参、实验管理Matplotlib 事后画图离线分析低免费论文出图、总结自建 Web 面板特殊定制需求中高需要和生产系统整合3. 可视化面板的四种核心记录方式3.1 标量曲线损失、准确率与学习率最核心的接口是SummaryWriter它负责创建事件文件并写入数据。最小可用代码大概这样from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/mnist_exp_v1) for epoch in range(10): train_loss compute_loss(epoch) writer.add_scalar(Loss/train, train_loss, epoch) writer.close()add_scalar三个参数分别是标签名、数值、全局步数。第三步的步数可以是 epoch也可以是 iteration取决于你想看多细的曲线。这里有个小技巧标签名里用/分层比如Loss/train和Loss/validTensorBoard 会自动把相同前缀的曲线合并到同一张图里对比起来一目了然。实际项目里我通常不只在循环末尾记录一次 loss还会记录当前学习率lr optimizer.param_groups[0][lr] writer.add_scalar(LR/step, lr, global_step)学习率变化和 loss 变化放到同一个时间轴上能很清楚地看出学习率策略是不是合理。比如 warmup 阶段 loss 下降不明显不代表模型坏了配合学习率曲线才好判断。3.2 直方图与分布掌握权重、梯度变化状态很多人在 TensorBoard 里只用 scalars那就有点浪费了。我觉得最有诊断价值的是add_histogram它能显示每一层权重和梯度的数值分布随 step 的变化。训练初期看权重初始化是否合理训练中段看梯度是否健康、是否有梯度爆炸或消失。代码模式固定在训练循环里遍历模型参数for name, param in model.named_parameters(): writer.add_histogram(name, param.clone().detach().cpu().numpy(), global_step) if param.grad is not None: writer.add_histogram(name .grad, param.grad.clone().detach().cpu().numpy(), global_step)注意两点第一param.grad是梯度张量如果为None说明该参数没有参与梯度计算这本身就可能是个 bug第二直接把 CUDA 上的张量传给直方图接口会报错所以我习惯先.detach().cpu()转成 numpy 数组。从分布图上你能直观看到权重是否快速塌缩到 0、梯度是否集中在非常小的区间。这个信息比单独看 loss 值可靠得多。3.3 图像输入把训练样本、特征图、重建结果贴上去计算机视觉任务里输入图像可视化是排障利器。模型吃进去的到底是什么、预处理有没有问题、增强后的图像是否离谱这些在 TensorBoard 里一眼就能确认。images, _ next(iter(train_loader)) writer.add_images(Input/data, images[:8], global_step, dataformatsNCHW)dataformats参数特别要注意。PyTorch 默认图像张量形状是(batch, channel, height, width)对应的格式就是NCHW。如果你拿到的数据是(batch, height, width, channel)就需要指定NHWC否则图像会显示成乱色或者直接报错。如果做 GAN 或自编码器还可以把重建结果和原始输入放在同一批图像里训练过程中每隔几个 epoch 截图一次观察生成质量有没有实质提升。3.4 模型与文本add_graph 和 add_text 的细节TensorBoard 的 Graphs 面板可以展示模型结构。PyTorch 里只需要dummy_input torch.randn(1, 3, 224, 224) writer.add_graph(model.cpu(), dummy_input)add_graph需要传入一个维度和真实输入一致的小张量比如 batch size 设为 1。模型会被内部 trace 一遍。首次 trace 时带缓存、带条件分支的代码可能会有兼容问题所以我建议模型结构复杂时先跑一次前向确认没有动态控制流报错再接入。另外add_text接口可以用来记录每个实验的备注比如数据增强方式、超参数组合、遇到了什么问题。把这些文本跟着实验目录一起记录比单独建一个实验笔记文档更不容易丢。4. 一个完整实例从训练脚本到实时看板4.1 拿极小的 CNN 做一个端到端演示理论讲再多不如跑通一个最小例程。下面这个是完整可跑的脚本训练一个简单 CNN 分类 CIFAR-10边训练边写入 TensorBoardimport torch import torch.nn as nn import torchvision import torchvision.transforms as transforms from torch.utils.tensorboard import SummaryWriter transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ]) trainset torchvision.datasets.CIFAR10( root./data, trainTrue, downloadTrue, transformtransform) trainloader torch.utils.data.DataLoader( trainset, batch_size64, shuffleTrue, num_workers2) class TinyCNN(nn.Module): def __init__(self): super().__init__() self.conv1 nn.Conv2d(3, 32, 3, padding1) self.conv2 nn.Conv2d(32, 64, 3, padding1) self.pool nn.MaxPool2d(2) self.fc nn.Linear(64 * 8 * 8, 10) def forward(self, x): x torch.relu(self.conv1(x)) x self.pool(torch.relu(self.conv2(x))) x x.view(x.size(0), -1) return self.fc(x) model TinyCNN() criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.9) writer SummaryWriter(runs/cifar_tinycnn) global_step 0 for epoch in range(10): running_loss 0.0 for inputs, labels in trainloader: optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() if global_step % 50 0: writer.add_scalar(Loss/train_batch, loss.item(), global_step) writer.add_scalar(LR/sgd, optimizer.param_groups[0][lr], global_step) writer.add_images(Input/cifar, inputs[:8], global_step) global_step 1 epoch_loss running_loss / len(trainloader) writer.add_scalar(Loss/train_epoch, epoch_loss, epoch) for name, param in model.named_parameters(): writer.add_histogram(name, param.clone().detach().cpu().numpy(), epoch) writer.close() print(done)运行完打开 TensorBoard你会看到 Loss 曲线、CIFAR 输入图像、每一层权重的分布。把tensorboard --logdir runs开着脚本再次运行面板上的曲线就会自动多出一段不需要重启 TensorBoard。4.2 训练过程中的实时刷新策略TensorBoard 不是完全实时的它默认每隔 120 秒刷新一次或者读取到事件文件变化后会主动读取。如果觉得刷新太慢有两个办法在训练脚本里定期调用writer.flush()强制把缓冲区写入磁盘要么在训练循环里用子进程定期调用tensorboard之外的服务这只是刷新的问题不用过度处理。我自己习惯让 writer 留在训练主进程里每隔几百个 step 记录一次标量和图像。记录频率过高事件文件会迅速变大GPU 训练本来资源紧张过多 CPU 端的 IO 会影响速度记录频率过低又抓不住训练早期的细节。折中方案是每个 iteration 都记录 loss每 50 个 iteration 记录一次图像每个 epoch 记录一次直方图。5. 常用的问题排查技巧5.1 端口被占、换端口与局域网访问TensorBoard 默认占用 6006端口被占用是最常见的问题。有时旧的 tensorboard 进程没关干净新进程启动后右上角会提示端口冲突。换端口很简单tensorboard --logdir runs --port 6007如果需要在局域网另一台机器上访问可以加--host 0.0.0.0。要注意的是这么做相当于把这个端口暴露给网内所有机器如果环境比较敏感不要轻易挂公网 IP控制好访问范围。5.2 面板空白时八成卡在 logdir训练了半天打开面板一片空白这是新手最容易懵的点。排查顺序很简单第一看启动命令里的--logdir是不是指向了SummaryWriter实际写的那个目录。比如代码里写的是writer SummaryWriter(run123)但你启动时写了--logdir logs那自然看不到。第二看目录里是否生成了events.out.tfevents.*开头的文件ls runs/cifar_tinycnn没有这个文件说明 writer 没写进去或者还没 flush。训练脚本还在运行但你看不到数据通常是因为数据还在缓冲区里等几秒或者调一下writer.flush()。第三检查标签名是否设定得太随意TensorBoard 对同名标签不同数值类型比较敏感如果同一个标签你既写 scalar 又写 text曲线面板可能不显示。5.3 远程服务器上的训练本地看板如何搭训练在远程 GPU 服务器本地想打开看板最稳的做法是用 SSH 端口转发把远程的 6006 转发到本地ssh -L 6006:localhost:6006 userserver_ip然后本地还是访问http://localhost:6006。这样 TensorBoard 进程跑在远程但浏览器用本地的端口看到界面数据不经过不安全的明文传输。这个方法比在远程直接开 6006 端口更推荐除非你的网络环境本身是可信内网。5.4 Jupyter Notebook 里怎么看 TensorBoard如果你习惯在 Notebook 里做实验可以直接嵌入面板。先启用扩展pip install jupyter-tensorboardNotebook 里用魔法命令%load_ext tensorboard %tensorboard --logdir runs面板会直接显示在 cell 下方。不过我个人还是更推荐独立进程启动Notebook 内核一旦断开面板可能跟着失效独立进程更稳定。5.5 写日志拖慢训练怎么处理add_histogram如果每步都调用训练速度会肉眼可见地下降尤其是大模型的参数全打一遍直方图CPU 序列化和 IO 开销都不小。处理方法是限制频率直方图一个 epoch 记一次就够了标量也不一定每一步都记录可以每隔 20 步记一次。图像记录更是每 100 步甚至每个 epoch 一次。另外DistributedDataParallel 多卡训练时不要每个进程都往同一个SummaryWriter实例写最常见做法是只在主进程构建 writer其他 rank 只参与训练否则事件文件里会出现多个进程的数据混在一起曲线变乱。6. 可视化这件事我更看重哪些长期习惯用 TensorBoard 做了几十个实验之后我最大的感受是工具本身不难难的是怎么让它真正服务于实验迭代。我现在养成的习惯很固定每个实验都有独立目录目录名写清楚日期、模型、batch sizeScalar 分组用统一的层级命名比如Metrics/acc、Loss/train、LR/epoch每次开始新实验前先看一眼上一轮实验的权重分布和梯度分布再决定要不要调初始化、换优化器。这套流程跑顺之后我不太依赖记忆因为 TensorBoard 里保留着每个实验的过程曲线和直方图回溯起来比翻文档还快。还有一点不要只看 loss。画出来的曲线是结果但真正有用的信息隐藏在梯度分布、权重变化、输入图像这些容易被忽略的面板里。可视化不是为了好看是为了在模型“悄悄出问题”的时候让你有迹可循。一开始多做几个 demo 练手把训练循环和 writer 的配合模式记熟后面迁移到生成模型、目标检测、时序模型时TensorBoard 这一套依然能用。这大概是我觉得最值得长期投入的一个调试习惯。