概述
训练日志只打印在命令行里,很难长期观察趋势。尤其是训练轮数多、实验配置多时,只看几行输出很容易误判。
VisualDL 是飞桨生态中的可视化分析工具。它可以把训练过程中的 loss、accuracy、参数分布、图像样本等写入日志文件,再通过 Web 面板展示。
常见用途:
- 观察训练 loss 是否下降。
- 对比训练集和验证集 accuracy。
- 查看学习率变化。
- 查看参数或梯度分布。
- 检查输入图像是否预处理正确。
- 对比多个实验。
读完本文,你应该能安装 VisualDL,在训练循环中记录指标,并启动面板查看训练曲线。
VisualDL 的工作方式
官方文档把 VisualDL 使用过程分成"写"和"读"两部分:
text
训练脚本写日志
|
LogWriter 写入 runs 目录
|
命令行启动 visualdl 服务
|
浏览器查看曲线和图像
也就是说,VisualDL 不直接改变训练流程,它只是把训练过程中的关键数据记录下来。
安装与启动
安装:
bash
python -m pip install visualdl
查看版本:
bash
visualdl --version
启动面板:
bash
visualdl --logdir ./runs/mlp_experiment --host 0.0.0.0 --port 8040
浏览器打开:
text
http://localhost:8040
如果端口被占用,换一个端口:
bash
visualdl --logdir ./runs/mlp_experiment --port 8041
LogWriter:训练脚本中的日志写入器
在 Python 中使用:
python
from visualdl import LogWriter
writer = LogWriter(logdir="./runs/mlp_experiment")
更推荐使用上下文管理器:
python
from visualdl import LogWriter
with LogWriter(logdir="./runs/mlp_experiment") as writer:
writer.add_scalar(tag="train/loss", value=0.8, step=1)
常见标量写入:
python
writer.add_scalar(tag="train/loss", value=train_loss, step=epoch)
writer.add_scalar(tag="val/acc", value=val_acc, step=epoch)
其中:
tag:曲线名称。value:记录的数值。step:横轴步数。
记录训练和验证指标
在训练循环中记录:
python
with LogWriter(logdir="./runs/mlp_experiment") as writer:
for epoch in range(num_epochs):
train_loss, train_acc = train_one_epoch(model, train_loader, optimizer)
val_loss, val_acc = evaluate(model, val_loader)
writer.add_scalar("train/loss", train_loss, epoch)
writer.add_scalar("train/acc", train_acc, epoch)
writer.add_scalar("val/loss", val_loss, epoch)
writer.add_scalar("val/acc", val_acc, epoch)
print(epoch, train_loss, train_acc, val_loss, val_acc)
命名建议:
text
train/loss
train/acc
val/loss
val/acc
lr
使用带斜杠的 tag,可以在面板里更清楚地区分训练和验证。
记录每个 batch 还是每个 epoch
两种粒度都可以:
| 粒度 | 优点 | 缺点 |
|---|---|---|
| batch | 更细,能看到短期波动 | 日志多,曲线噪声大 |
| epoch | 简洁,适合观察长期趋势 | 看不到 batch 内波动 |
入门建议:
text
每个 epoch 记录 train_loss、val_loss、val_acc
需要排查训练不稳定时,再记录 batch loss
batch 记录示例:
python
global_step = 0
for epoch in range(num_epochs):
model.train()
for batch_x, batch_y in train_loader:
logits = model(batch_x)
loss = F.cross_entropy(logits, batch_y)
loss.backward()
optimizer.step()
optimizer.clear_grad()
writer.add_scalar("batch/loss", float(loss.numpy()), global_step)
global_step += 1
记录学习率
如果使用学习率调度器,建议记录当前学习率:
python
current_lr = optimizer.get_lr()
writer.add_scalar("train/lr", current_lr, epoch)
这能帮助判断:
- 调度器是否生效。
- 学习率是否衰减过快。
- loss 变化是否与学习率变化相关。
尤其使用 StepDecay 或 CosineAnnealingDecay 时,学习率曲线很有价值。
记录参数直方图
VisualDL 可以记录参数分布:
python
for name, param in model.named_parameters():
writer.add_histogram(tag=f"params/{name}", values=param.numpy(), step=epoch)
用途:
- 查看参数是否异常变大。
- 观察参数分布是否集中。
- 排查训练中参数是否出现
NaN。
不要每个 batch 都记录所有参数直方图,日志会变大。通常每个 epoch 记录一次即可。
记录输入图像
图像任务中,最容易出现的问题是预处理错误。可以把输入图片写入 VisualDL。
示例思路:
python
images, labels = next(iter(train_loader))
writer.add_image(tag="sample/image_0", img=images[0], step=0)
如果输入是 [C, H, W] 格式,确保 VisualDL 接口接收的格式与数据一致。图像记录最重要的目的不是美观,而是确认:
- 图片是否归一化过度。
- 通道顺序是否正确。
- Resize 或 Crop 是否裁错。
- 标签是否对应。
完整示例:MLP 训练接入 VisualDL
python
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from visualdl import LogWriter
class CircleDataset(paddle.io.Dataset):
def __init__(self, num_samples, seed):
super().__init__()
paddle.seed(seed)
self.x = paddle.rand([num_samples, 2], dtype="float32") * 4.0 - 2.0
radius_square = paddle.sum(self.x ** 2, axis=1)
self.y = (radius_square > 1.0).astype("int64")
def __len__(self):
return self.y.shape[0]
def __getitem__(self, index):
return self.x[index], self.y[index]
class MLP(nn.Layer):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(2, 32),
nn.ReLU(),
nn.Linear(32, 2),
)
def forward(self, x):
return self.net(x)
def accuracy(logits, labels):
pred = paddle.argmax(logits, axis=1)
return paddle.mean((pred == labels).astype("float32"))
def train_one_epoch(model, loader, optimizer):
model.train()
total_loss = 0.0
total_acc = 0.0
count = 0
for batch_x, batch_y in loader:
logits = model(batch_x)
loss = F.cross_entropy(logits, batch_y)
acc = accuracy(logits, batch_y)
loss.backward()
optimizer.step()
optimizer.clear_grad()
total_loss += float(loss.numpy())
total_acc += float(acc.numpy())
count += 1
return total_loss / count, total_acc / count
def evaluate(model, loader):
model.eval()
total_loss = 0.0
total_acc = 0.0
count = 0
with paddle.no_grad():
for batch_x, batch_y in loader:
logits = model(batch_x)
loss = F.cross_entropy(logits, batch_y)
acc = accuracy(logits, batch_y)
total_loss += float(loss.numpy())
total_acc += float(acc.numpy())
count += 1
return total_loss / count, total_acc / count
def main():
train_loader = paddle.io.DataLoader(CircleDataset(2048, 2026), batch_size=64, shuffle=True)
val_loader = paddle.io.DataLoader(CircleDataset(512, 2027), batch_size=128, shuffle=False)
model = MLP()
optimizer = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
with LogWriter(logdir="./runs/mlp_experiment") as writer:
for epoch in range(20):
train_loss, train_acc = train_one_epoch(model, train_loader, optimizer)
val_loss, val_acc = evaluate(model, val_loader)
writer.add_scalar("train/loss", train_loss, epoch)
writer.add_scalar("train/acc", train_acc, epoch)
writer.add_scalar("val/loss", val_loss, epoch)
writer.add_scalar("val/acc", val_acc, epoch)
writer.add_scalar("train/lr", optimizer.get_lr(), epoch)
for name, param in model.named_parameters():
writer.add_histogram(f"params/{name}", param.numpy(), epoch)
print("epoch:", epoch, "train_loss:", train_loss, "val_acc:", val_acc)
if __name__ == "__main__":
main()
运行训练脚本后启动:
bash
visualdl --logdir ./runs/mlp_experiment --host 0.0.0.0 --port 8040
如何阅读可视化曲线
看 loss:
text
train/loss 下降,val/loss 下降:正常学习
train/loss 下降,val/loss 上升:可能过拟合
loss 剧烈震荡:学习率可能过大
loss 几乎不变:学习率太小、模型太弱或数据错误
看 accuracy:
text
train/acc 和 val/acc 同步提升:正常
train/acc 高,val/acc 低:过拟合
train/acc 低,val/acc 低:欠拟合或训练错误
看学习率:
text
确认 scheduler 是否按预期变化
观察 lr 下降后 loss 是否更稳定
多实验对比
建议每次实验使用不同日志目录:
text
runs/mlp_adam_lr001
runs/mlp_adam_lr003
runs/mlp_sgd_lr01
runs/mlp_dropout03
启动时可以指定上级目录:
bash
visualdl --logdir ./runs --port 8040
这样可以在同一个面板里对比多个实验。
记录实验配置:
python
writer.add_text("config", "optimizer=Adam, lr=0.001, hidden_dim=32", step=0)
常见错误:VisualDL 排查清单
错误一:logdir 写错
训练时:
python
LogWriter(logdir="./runs/mlp_experiment")
启动时也要指向同一个目录:
bash
visualdl --logdir ./runs/mlp_experiment
错误二:step 一直相同
如果所有记录都使用 step=0,曲线不会正常展开。应使用 epoch 或 global_step。
错误三:value 不是可序列化数值
建议写入 Python float:
python
writer.add_scalar("train/loss", float(loss.numpy()), step)
错误四:日志太大
不要每个 batch 都记录所有参数直方图。图像和 histogram 记录频率要控制。
建议练习:把训练过程看见
- 记录
train/loss和val/loss。 - 记录
train/acc和val/acc。 - 记录
optimizer.get_lr()。 - 对比两个不同学习率实验。
- 每个 epoch 记录一次参数直方图。
- 故意设置过大学习率,观察 loss 曲线震荡。
总结:可视化让训练过程可诊断
VisualDL 的价值不是让日志更好看,而是让训练过程更容易诊断:
- 用 Scalar 看 loss、accuracy、learning rate。
- 用 Histogram 看参数和梯度分布。
- 用 Image 检查图像输入是否正确。
- 用多实验目录对比不同配置。
- 用曲线趋势判断过拟合、欠拟合和学习率问题。
如果只能记住一句话,那就是:
训练可视化的核心价值,是把模型是否正常学习从猜测变成可观察证据。