TensorBoard 是深度学习训练中必不可少的“仪表盘”。它可以实时展示损失曲线、准确率变化、模型结构、参数分布,甚至图像和音频。 好消息是:PyTorch 原生支持 TensorBoard,装一个包就能用,完全不用写 TensorFlow 代码。
本文会带你从零上手 TensorBoard 的核心 API,每个功能都配有参数表、代码示例和运行效果说明。这是一篇面向初学者的完整笔记
1.SummaryWriter– 创建日志记录器
作用:创建日志目录,TensorBoard 会读取该目录下的所有事件文件。
核心参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
log_dir | str | None | 日志保存的文件夹路径。若为None,会自动创建runs/下带时间戳的目录。推荐手动指定,如'logs/exp1'。 |
comment | str | '' | 仅当log_dir=None时有效,追加到自动生成的文件夹名后。 |
flush_secs | int | 120 | 每隔多少秒将缓冲区数据强制写入磁盘。训练长时间任务可设为60或30。 |
示例:
from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter('logs/my_experiment') # 最常用 writer.close()启动 TensorBoard,在终端中,切换到存放logs文件夹的目录,执行:
tensorboard --logdir=logs
2.add_scalar– 记录标量
作用:记录单个数值随步数的变化,生成曲线。
使用场景:训练损失、验证准确率、学习率、自定义指标等。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
tag | str | 曲线的名字。用/可以分组,例如'Loss/train'和'Loss/val'会被归入同一组。 |
scalar_value | float/int | 要记录的值(y 轴)。 |
global_step | int | 步数(x 轴),通常用 epoch 或 iteration 编号。 |
walltime | float | 可选,实际时间戳(一般不用)。 |
示例(记录训练损失和验证准确率):
for epoch in range(10): train_loss = ... # 计算得到 val_acc = ... writer.add_scalar('Loss/train', train_loss, epoch) writer.add_scalar('Accuracy/val', val_acc, epoch)add_scalars– 在一张图上画多条曲线
需求:将训练损失和验证损失放在同一张图中对比。
writer = SummaryWriter('logs/two_curves') for epoch in range(10): train_loss = ... # 计算得到 val_loss = ... writer.add_scalars('Loss', {'train': train_loss, 'validation': val_loss}, epoch) writer.close()在 SCALARS 面板中,会出现一张名为Loss的图,包含两条不同颜色的曲线。
3.add_image/add_images– 记录图像
作用:将图像(或一批图像拼成的网格)写入 TensorBoard。
使用场景:检查输入样本、生成模型的输出、特征图、分割掩码等。
参数(add_image):
| 参数 | 类型 | 说明 |
|---|---|---|
tag | str | 图像组名称。 |
img_tensor | torch.Tensor / numpy.ndarray | 图像数据。add_image要求形状(C, H, W)或(H, W, C)。 |
global_step | int | 步数,用于观察训练不同阶段的图像变化。 |
dataformats | str | 指定维度顺序,默认'CHW'。若张量为(H, W, C)则需设为'HWC'。 |
常用技巧:用torchvision.utils.make_grid将一批图像拼成网格,再用add_image记录。
示例(显示一批样本):
import torchvision.utils as vutils images, _ = next(iter(dataloader)) grid = vutils.make_grid(images, nrow=8, normalize=True) writer.add_image('samples', grid, 0)也使用不同的tag分组展示
writer.add_image('cat', cat_img, global_step=0, dataformats='HWC') writer.add_image('dog', dog_img, global_step=0, dataformats='HWC')4.add_graph– 记录模型计算图
作用:可视化模型的结构和数据流向。
使用场景:检查模型定义是否正确,调试前向传播形状。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
model | torch.nn.Module | 要可视化的模型实例。 |
input_to_model | torch.Tensor 或 tuple | 示例输入,形状与真实输入一致。TensorBoard 会用它执行一次前向传播来追踪图。 |
verbose | bool | 是否打印详细日志(默认False)。 |
示例:
dummy_input = torch.randn(1, 1, 28, 28) writer.add_graph(model, dummy_input)
打开 GRAPHS 面板,可以像看电路图一样展开每个卷积、全连接、残差块,检查连接是否正确。
5.add_histogram– 记录张量分布(直方图)
作用:显示权重、梯度等张量的数值分布随时间的变化。
使用场景:诊断梯度消失/爆炸,观察参数更新是否健康。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
tag | str | 直方图名称,通常用参数名,如'fc1.weight'。 |
values | torch.Tensor | 要统计分布的张量(任意形状,会自动展平)。 |
global_step | int | 步数。 |
bins | str/int | 分箱方式,默认'tensorflow'。也可指定整数箱数。 |
max_bins | int | 最大箱数(当bins为字符串时有效)。 |
示例(每个 epoch 记录所有参数的权重和梯度):
for name, param in model.named_parameters(): writer.add_histogram(name, param, epoch) if param.grad is not None: writer.add_histogram(name + '.grad', param.grad, epoch)
怎么看结果(HISTOGRAMS 面板):
正常:梯度值集中在 e-2 到 e0 之间,分布对称;权重直方图逐渐展宽。
梯度消失:几轮之后梯度直方图缩成一条细线贴在 0 上。
梯度爆炸:直方图出现很长的尾巴,数值远超其他部分。
掌握这个,你就能很快定位训练失败的根本原因。
6.add_hparams– 记录超参数
作用:系统化地记录一组超参数及其对应的最终指标,生成表格和图表。
使用场景:在你获得该组超参数对应的最终评价指标之后调用,通常有几种常见做法,
单次实验结束后:训练完一个模型,得到最佳验证集准确率,立即调用
add_hparams记录这组超参数和最终结果。多次实验的脚本中:比如用循环或并行搜索超参数时,每次实验跑完就调用一次,这样 TensorBoard 的 HParams 插件里会累积多组实验记录。
注意不要在每个 epoch 都调用:
add_hparams是为最终结果设计的。如果你想记录训练过程中指标的变化,应该用add_scalar。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
hparam_dict | dict | 超参数字典,键为超参数名(字符串),值为数值或字符串。 |
metric_dict | dict | 指标字典,键为指标名(建议以'hparam/'开头),值为数值。 |
global_step | int | 可选,步数。 |
示例:
# 待搜索的超参数组合 learning_rates = [0.001, 0.01] batch_sizes = [32, 64] epochs = 3 # 为了快速演示,只训练3轮;实际调参可以增加 # 根日志目录 base_log_dir = "./runs/mnist_hparam_search" for lr in learning_rates: for batch_size in batch_sizes: # 1) 为每组超参数创建独立的子目录 run_name = f"lr_{lr}_bs_{batch_size}" log_dir = os.path.join(base_log_dir, run_name) # 2) 使用 with 语句自动管理 SummaryWriter with SummaryWriter(log_dir) as writer: print(f"\n=== Running: {run_name} ===") # 3) 准备数据加载器(不同的 batch_size) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=1000, shuffle=False) # 4) 创建模型和优化器(可加入 dropout 等超参数,这里固定 dropout=0.2) model = SimpleCNN(dropout=0.2).to(device) optimizer = optim.Adam(model.parameters(), lr=lr) # 5) 训练多个 epoch,并记录曲线 best_acc = 0.0 for epoch in range(1, epochs + 1): train(model, device, train_loader, optimizer, epoch, writer) acc = validate(model, device, val_loader, writer, epoch) if acc > best_acc: best_acc = acc # 6) 最后记录超参数和最终验证准确率(使用最佳或最终) hparams = {'lr': lr, 'batch_size': batch_size} metrics = {'hparam/val_accuracy': best_acc} writer.add_hparams(hparams, metrics) print(f"Finished {run_name}, best accuracy = {best_acc:.2f}%")| lr | batch_size | val_accuracy |
|---|---|---|
| 0.001 | 64 | 0.93 |
| 0.001 | 32 | 0.91 |
| 0.01 | 64 | 0.88 |
| 0.01 | 32 | 0.85 |
运行脚本后,日志会保存在./runs/mnist_hparam_search/下,子目录结构为:
runs/mnist_hparam_search/ ├── lr_0.001_bs_32/ ├── lr_0.001_bs_64/ ├── lr_0.01_bs_32/ └── lr_0.01_bs_64/
启动 TensorBoard:
tensorboard --logdir=./runs/mnist_hparam_search
浏览器操作:
打开
http://localhost:6006左侧 Runs 列表中,你会看到四个可勾选的 Run(对应四个子目录)。
Scalars 面板:勾选多个 Run 后,
train/loss、val/loss、val/accuracy曲线会叠加在同一张图上,颜色不同,可以清晰对比不同超参数下的收敛速度和最终性能。HParams 面板:自动汇总所有 Run 的超参数和最终指标(
hparam/val_accuracy),呈现为一个表格,支持排序和交互式图表(平行坐标、散点图)。
8.add_embedding– 高维嵌入可视化
深度学习模型(尤其是分类任务)通常会在最后一层(softmax 之前)输出一个高维特征向量(比如 128 维、512 维)。这个向量就是模型对输入数据的内部表示。add_embedding的作用就是把这些高维向量投影到2D 或 3D 空间(使用 PCA、t-SNE 或 UMAP),然后在 TensorBoard 的PROJECTOR插件中展示出来。你可以:
用鼠标拖拽旋转/缩放三维空间
观察不同类别的样本是否聚集在一起
点击某个点,查看对应的原始图片或标签
搜索特定标签的样本
参数:
writer.add_embedding(mat, metadata=None, label_img=None, global_step=None, tag='default')
| 参数名 | 类型 | 说明 |
|---|---|---|
mat | Tensororndarray | 形状为(N, D)的特征矩阵。N是样本数量,D是特征维度。必须是浮点数。 |
metadata | listofstr | 可选。长度为N的标签列表,每个元素是该样本的类别名称(字符串)。如果不提供,只显示点坐标。 |
label_img | Tensor | 可选。形状(N, C, H, W)的图像张量。当鼠标悬浮在投影空间的点上时,会显示该样本对应的图片缩略图。 |
global_step | int | 可选。记录当前是第几步/第几个 epoch,可用于观察训练过程中特征的可分性如何演变。 |
tag | str | 可选。嵌入向量的名称,默认'default'。如果你多次调用add_embedding(比如不同层或不同时间),可以用不同tag区分。 |
注意:metadata和label_img可以同时提供,也可以只提供其中一个。
示例(提取测试集特征并可视化):
features = torch.cat(features) # (N, D) labels = [f"class_{l}" for l in labels.tolist()] writer.add_embedding(features, metadata=labels, label_img=images, global_step=0)打开PROJECTOR面板,选择不同的降维算法(PCA/t-SNE 等),就可以玩起来了。
完整ResNet18 + CIFAR-10代码:
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter import torchvision import torchvision.transforms as transforms import torchvision.utils as vutils # ---------- 模型定义---------- class ResNet18ForCIFAR(nn.Module): def __init__(self, num_classes=10): super().__init__() backbone = torchvision.models.resnet18(weights=torchvision.models.ResNet18_Weights.IMAGENET1K_V1) backbone.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) backbone.maxpool = nn.Identity() self.features = nn.Sequential(*list(backbone.children())[:-1]) self.fc = nn.Linear(512, num_classes) def forward(self, x): feat = self.features(x) feat = feat.view(feat.size(0), -1) out = self.fc(feat) return out, feat # 分类结果 + 特征 # ---------- 数据 ---------- transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) transform_val = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) trainset = torchvision.datasets.CIFAR10('./data', train=True, download=True, transform=transform_train) valset = torchvision.datasets.CIFAR10('./data', train=False, download=True, transform=transform_val) train_loader = DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2) val_loader = DataLoader(valset, batch_size=128, shuffle=False, num_workers=2) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = ResNet18ForCIFAR(num_classes=10).to(device) # ---------- TensorBoard ---------- writer = SummaryWriter('runs/cifar10_demo') # 记录模型图(注意:dummy input 形状匹配) dummy_input = torch.randn(1, 3, 32, 32).to(device) writer.add_graph(model, dummy_input) # 记录一批训练样本图像 sample_images, _ = next(iter(train_loader)) grid = vutils.make_grid(sample_images[:16], nrow=4, normalize=True) writer.add_image('train_samples', grid, 0) # ---------- 训练 ---------- optimizer = optim.Adam(model.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() for epoch in range(5): model.train() train_loss = 0.0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs, _ = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() * inputs.size(0) train_loss /= len(trainset) writer.add_scalar('Loss/train', train_loss, epoch) for name, param in model.named_parameters(): writer.add_histogram(name, param, epoch) if param.grad is not None: writer.add_histogram(name+'.grad', param.grad, epoch) print(f"Epoch {epoch+1}, Loss: {train_loss:.4f}") # ---------- 提取特征并调用 add_embedding ---------- model.eval() feature_list, label_list, image_list = [], [], [] with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) _, feats = model(inputs) feature_list.append(feats.cpu()) label_list.append(labels.cpu()) image_list.append(inputs.cpu()) features = torch.cat(feature_list, dim=0) labels = torch.cat(label_list, dim=0) images = torch.cat(image_list, dim=0) metadata = [f"class_{l}" for l in labels.tolist()] embed_writer = SummaryWriter('runs/cifar10_embedding') embed_writer.add_embedding(features, metadata=metadata, label_img=images, global_step=0) embed_writer.close() writer.close() print("训练完成!运行 tensorboard --logdir=runs 查看结果")如果本文对你有帮助,欢迎点赞 👍、收藏 ⭐、评论 💬!