ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

TensorBoard可视化实战:看懂模型训练中的计算图与指标曲线

TensorBoard可视化实战:看懂模型训练中的计算图与指标曲线 训练跑得正起劲却只能靠控制台一行行跳出来的数字判断模型死活——这是我最初接触深度学习时的状态。直到有人给我扔过来一句你试试TensorBoard第一次打开那个浏览器页面看着loss曲线、计算图、权重直方图整整齐齐摆在面前时我才明白为什么大家都在说可视化是debug的另一种方式。这系列文章我会按主题拆开讲不追求一次性铺满所有概念。第一篇先聊最核心的东西TensorBoard如何帮我们把训练过程中那些看不见的关系式画出来。这里的关系式不是数学公式而是两类看得见摸得着的关系一是模型计算图里张量之间的依赖流动关系二是训练过程中指标随迭代步数变化的数值关系。搞清楚这两类关系你就等于拥有了模型调试的地图。TensorBoard本身免费、开源装完TensorFlow或PyTorch就能直接用适合刚入门的同学也适合训练时想把中间过程拍下来的进阶玩家。这篇我尽量不讲虚的直接把数据怎么来、图表怎么读、踩过哪些坑一次说清楚。1. 第一个关键问题TensorBoard到底能把哪些关系式画出来先把概念对齐。很多人一提到TensorBoard就只想到loss曲线其实它是一套完整的可视化套件不同面板可视化的关系完全不同。1.1 三张核心面板分别对应三种关系Scalars标量面板可视化的是一组指标随训练步数的变化关系。比如横轴是step纵轴是loss曲线反映的就是损失函数和迭代次数之间的关系。训练是否收敛、是否过拟合主要靠这一面板判断。Graph计算图面板可视化的是一组张量在模型内部流动时的依赖关系。输入Tensor经过哪些算子变成中间结果中间结果又经过哪些变量变成输出节点和边把这条链路画成了图。Histograms直方图面板可视化的是一组权重和梯度随训练步数的分布关系。横轴是权重的取值区间纵轴是权重落在该区间内的数量颜色深浅对应不同训练步数。通过它可以判断参数是否爆炸、是否出现梯度消失。这三张面板基本覆盖了训练过程中的核心关系式。此外还有Projector、PR曲线、Image面板不过第一讲先不展开等后续文章挨个说。1.2 常被忽略的另两类关系式除了上面三张主力面板还有两类关系很容易被忽略但对实际调参很有帮助。第一类是数据分布关系。把样本或特征向量喂给Projector它会通过PCA或t-SNE降维把高维样本投影到三维空间里。每一个点代表一个样本点的颜色代表标签点和点之间的距离则代表样本在特征空间中的相似关系。类别是否混在一起、特征是否逐渐分离一眼就能看出个大概。第二类是预测结果与真实标签之间的概率关系。在分类任务里PR曲线和ROC曲线画的就是查准率、召回率、真正例率、假正例率在不同阈值下的相互制约关系。模型输出分数之后应该把阈值定在多少这套曲线会给出直观的提示。1.3 如何理解可视化关系式这个说法说白了TensorBoard本质上把训练过程中的两类定量关系翻译成了图像。一类是静态结构关系模型里谁依赖谁哪个张量是哪个算子的输入哪个变量在哪个命名空间中。一类是动态数值关系loss随着step怎么变权重分布随着训练怎么变梯度在反向传播时怎么流回叶子节点。动态和静态结合就构成了一张完整的模型健康状态图。你不需要事后再去分析训练过程中就能随时拉出图来看。2. 可视化关系式的数据从哪来采集端的三个动作很多人卡在面板上什么都看不到根源不是TensorBoard不会用而是压根没往日志里写数据。TensorBoard作为前端展示端本身不碰模型它只负责读取日志目录下的event文件。所以关键问题就变成了数据怎么落盘2.1 在模型结构上画关系名称作用域与层命名计算图上的节点和边不是自动产生的TensorFlow会在构建模型时记录每个算子的输入和输出但如果不加整理图会乱成一团。给关键节点包上tf.name_scopeGraph面板中就会按作用域自动分组。我在实际项目里养成的习惯是自定义层和自定义损失函数里必须写清楚名称作用域。比如with tf.name_scope(feature_extractor): x tf.nn.conv2d(inputs, filters, strides[1, 1, 1, 1], paddingSAME)这样做的好处是Graph面板默认会按命名空间折叠展开后依然能看清内部结构。Keras的Sequential模型自带分层命名不太需要额外操心但自定义训练循环和自定义层命名作用域就非常有必要。2.2 在训练循环里记录指标回调和SummaryWriterTensorFlow 2.x最省事的做法是使用tf.keras.callbacks.TensorBoard回调。你只要把日志目录传进去训练过程中的loss、accuracy、学习率等指标会自动写入event文件tensorboard_cb tf.keras.callbacks.TensorBoard( log_dir./logs, histogram_freq1, write_graphTrue, update_freqepoch ) model.fit(x_train, y_train, epochs10, callbacks[tensorboard_cb])PyTorch侧则要显式创建SummaryWriter然后在训练循环里手动记录from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(log_dir./logs) for epoch in range(epochs): ... writer.add_scalar(loss/train, loss.item(), epoch) writer.add_histogram(weights/fc1, model.fc1.weight.data, epoch) writer.close()这个差异新手最容易踩坑——PyTorch不会自动帮你记录你必须明确告诉TensorBoard把那个标量、那个张量在哪个时间点写进去。2.3 通过钩子把中间张量接到关系式里我第一回用PyTorch时面临一个尴尬光记loss不够还想看看卷积层输出的特征图长什么样、注意力权重在某个step变成了什么形态。后来发现两步就能解决——先用register_forward_hook把中间张量Hook出来再在hook函数里写summarydef hook_fn(module, input, output): writer.add_histogram(conv1_out, output.detach().cpu().numpy(), global_stepstep) conv_layer.register_forward_hook(hook_fn)这样各类中间变量就都接进了关系式里。Graph面板上虽然不会因此多出节点但Histograms和Images面板能把特征输出变化展示得很清晰。2.4 事件文件的存储结构与目录规划TensorBoard日志目录会自动生成events.out.tfevents.*文件还有可能的train/、validation/子目录。目录规划直接决定后续对比的效果——训练集指标放一个子目录验证集指标放另一个子目录TensorBoard就能把两组曲线画在同一张图里。我通常这样组织logs/ ├── run_001/ │ ├── train/ │ └── validation/ ├── run_002/ │ ├── train/ │ └── validation/每个run一套独立目录方便对比不同超参数下的表现。3. 实操演练5分钟跑通一个完整的关系式可视化实例光说不练假把式。下面带大家从零搭一个可运行的最小示例TensorFlow 2.x和PyTorch两边我都跑一遍任选一个跟着做就行。3.1 快速搭一个小模型并准备数据我用的是一个两卷积层两全连接层的分类网络在自造的二维点集上做二分类。数据通过make_moons生成代码直接写在脚本里import numpy as np import tensorflow as tf from sklearn.datasets import make_moons from sklearn.model_selection import train_test_split X, y make_moons(n_samples1000, noise0.1, random_state42) X_train, X_val, y_train, y_val train_test_split(X, y, test_size0.2, random_state42) model tf.keras.Sequential([ tf.keras.layers.Dense(32, activationrelu, namedense_1), tf.keras.layers.Dense(16, activationrelu, namedense_2), tf.keras.layers.Dense(1, activationsigmoid, nameoutput) ]) model.compile(optimizertf.keras.optimizers.Adam(0.01), lossbinary_crossentropy, metrics[accuracy])数据量不需要大跑上几十轮就能看到曲线变化方便实验迭代。3.2 把TensorBoard装进训练流程创建日志目录然后配置回调log_dir ./logs/moons_run tensorboard_cb tf.keras.callbacks.TensorBoard( log_dirlog_dir, histogram_freq1, write_graphTrue, write_imagesFalse, update_freqepoch ) model.fit(X_train, y_train, validation_data(X_val, y_val), epochs50, batch_size32, callbacks[tensorboard_cb])需要注意histogram_freq1表示每1个epoch记录一次权重直方图。频率太高日志文件会膨胀但太低又看不出分布变化趋势。50轮的实验里写在epoch粒度就够用。3.3 启动TensorBoard并查看关系式命令行进入日志目录的上一级启动服务tensorboard --logdir./logs --port6006浏览器访问http://localhost:6006。正常情况下左侧面板会出现Scalars、Graph、Histograms等入口我刚跑通时第一次看的是Graph因为它直接展示出了两层全连接的前后依赖关系——输入Xi被送入dense_1dense_1的输出进入dense_2dense_2的输出再过sigmoid输出预测值整条链路清清楚楚。如果打不开先确认端口是否被占用再检查是不是防火墙拦截了本机端口。3.4 用PyTorch复现同一个关系式PyTorch侧演示代码逻辑上更手动但更透明import torch import torch.nn as nn from torch.utils.tensorboard import SummaryWriter class SimpleNet(nn.Module): def __init__(self): super().__init__() self.fc1 nn.Linear(2, 32) self.fc2 nn.Linear(32, 16) self.out nn.Linear(16, 1) def forward(self, x): x torch.relu(self.fc1(x)) x torch.relu(self.fc2(x)) return torch.sigmoid(self.out(x)) model SimpleNet() writer SummaryWriter(log_dir./logs/pytorch_run)训练循环中手动写标量for epoch in range(50): for xb, yb in train_loader: loss criterion(model(xb), yb) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 5 0: val_loss evaluate(model, val_loader) writer.add_scalars(loss, {train: loss.item(), val: val_loss}, epoch)注意这里用了add_scalars能在一张图里同时画训练和验证两条曲线对照过拟合非常好用。4. 读懂Graph面板中的节点与边关系首次打开Graph面板的人多半会被密密麻麻的节点吓到。这很正常TensorFlow默认会把所有算子都画出来不整理就是一个毛线球。但真正读懂这个面板是排查网络结构问题的关键。4.1 节点、边和Tensor之间的依赖逻辑Graph面板中的每个矩形代表一个算子圆角矩形通常代表命名空间分组椭圆代表常量或变量。连接节点的有向边代表Tensor流动关系——从哪个节点输出流入哪个节点作为输入。到了依赖关系层面我习惯先看最外层的分组再一层层下钻。比如dense_1这个命名空间点开之后能看到MatMul、BiasAdd、Relu三个算子组成的链输入Tensor先做矩阵乘法再加偏置最后过激活函数。这条链就是线性层内部的完整计算关系式。4.2 命名空间与模块依赖从trace看模型分组Graph面板会按TensorFlow的name scope自动折叠层级。在Keras模型里每个Dense层自动对应一个命名空间如果你用了自定义层且没加name scope那所有算子会全部平铺在顶层看起来特别乱。我见过一个真实案例同事写了一个多分支注意力网络所有分支层都没加scopeGraph面板上完全分不清哪个分支对应哪个输入。排查半天才发现是命名空间缺失。正确做法是每个分支单独包一层name scope比如with tf.name_scope(attention_branch)这样面板上直接就是三个折叠块点开才看到内层算子。养成这个习惯之后结构问题一眼就能定位。4.3 顺着梯度边排查断流问题Graph面板还有一个常被忽略的用法查看反向传播路径。训练曲线不下降时可以切换到Backward视图看看梯度有没有成功传到每层。具体操作是在Graph面板左上角选择反向视图观察哪些节点没有梯度边。如果没有梯度流到某个层的权重节点说明这层的梯度断流了典型的元凶有ReLU打死的神经元、自定义算子不可导、或者某一层被tf.stop_gradient隔断。我在调Transformer时遇到过注意力权重NaN的问题就是靠这种查法找到在哪一个Softmax节点上梯度出了问题。比对着代码一行行看快得多。5. Scalars面板训练指标之间的动态关系Graph面板看的是结构Scalars面板看的是训练过程中指标和步长的关系。这也是大多数人最常用的面板因为loss曲线、accuracy曲线直接反映模型状态。5.1 从loss曲线判断模型到底在不在学训练初期loss不降不代表没在学可能是学习率太大导致震荡也可能是数据的loss初始值就偏高。正常情况应该是loss在最初几个step快速下降随后下降速度放缓最终趋于平稳。如果曲线在某个位置突然往上抬常见原因有三个——学习率过高导致跨过最优点、数据集里混入异常标签、batch size太小引入了较大梯度噪声。验证是不是学习率过高可以同时在Scalars里记录lr这个标量把每条曲线的横轴调成同步观察。TensorBoard支持同一个面板里画多组实验左侧勾选不同run即可对比。5.2 训练曲线和验证曲线的关系变化把训练loss和验证loss画在同一张图里能直接看出模型处于欠拟合还是过拟合。训练loss持续下降但验证loss先降后升说明模型开始记忆训练样本这时的决策边界已经过度逼近训练集上的模式。知道了这个关系你自然会想到去调正则化强度或增大数据增强。一个小技巧训练指标以step为单位记录验证指标通常只在一个epoch结束时记录。两者横轴不统一时容易误读我一般在记录训练loss时也按epoch粒度记一份两张图对照着看。5.3 读曲线形态和参数调节的对应关系曲线形态能提示该调什么参数。常见对应关系我整理成了一个表曲线现象通常原因优先尝试方向loss下降很慢学习率过小调大学习率loss先降后升幅大学习率过大调小学习率或加warmup训练好验证差过拟合加正则、加数据增强、降模型容量训练验证都差欠拟合加深加宽网络、增加训练时长loss震荡剧烈batch太小或lr过高增batch或降lr验证loss阶段性跳变数据分布不均匀检查验证集、shuffle这张表不是万能公式但能提供第一波排查思路。真遇到问题至少不至于毫无头绪地改参数。6. 常见问题与排查技巧实录以下是这几次实践下来最常见的坑和解决办法整理成速查表方便大家遇到时直接抄答案。6.1 TensorBoard打不开或看不到图问题原因解决办法浏览器访问6006端口无响应端口占用换端口加--port6007日志目录下没有event文件训练没写summary检查回调是否配置正确PyTorch检查writer是否close只有loss曲线没有Graphwrite_graph被关了回调里设置write_graphTrue曲线一直不动日志数据未刷新浏览器点右上角刷新按钮或设置reload_interval只有训练曲线没有验证曲线没有传validation_data回调里给足验证数据直方图空白histogram_freq0回调里设置histogram_freq16.2 几个值得收藏的实战技巧第一日志目录一定要带run标识。./logs/run1_batch32、./logs/run2_batch64对比时会发现所有曲线都在同一张图上非常直观。不要都用./logs不然多次训练会把数据混在一个event文件里曲线会乱。第二用add_scalars或tf.summary.scalar记录多个相关指标。单独一条loss曲线信息量有限记录loss的同时把学习率、梯度范数一起记下来曲线联动时能更快归因。梯度范数突然归零比看到acc骤降要早好几步。第三训练完先看Histograms而不是Scalars。权重分布整体塌缩到零附近说明激活值大量饱和权重分布方差突然变大说明参数可能爆炸。这些信息在Scalars上一时半会儿看不出来。第四自定义训练循环时记得给summary加step。不加step参数多个时间点的数据会互相覆盖曲线直接变成一条竖线。这是我踩过最冤的一次坑。TensorBoard把那些平时只能靠print硬啃的关系式变成了一张张有意义的图表本质上是给你一双外部视角的眼睛。我个人的体会是模型调试效率的提升很多时候不是靠更多日志而是靠更会看。第一篇先讲到这里下一篇我打算拆开Scalars面板把loss曲线、学习率曲线、梯度范数怎么联动起来分析这件事聊透。
返回列表