TensorBoard
TensorBoard 安装
在已安装 PyTorch 的环境下使用 pip 安装即可:
pip install tensorboardX
也可以使用 PyTorch 自带的 tensorboard 工具,此时不需要额外安装 tensorboard。
TensorBoard 可视化的基本逻辑
我们可以将 TensorBoard 看做一个记录员,它可以记录我们指定的数据,包括模型每一层的 feature map,权重,以及训练 loss 等等。TensorBoard 将记录下来的内容保存在一个用户指定的文件夹里,程序不断运行中 TensorBoard 会不断记录。记录下的内容可以通过网页的形式加以可视化。
TensorBoard 的配置与启动
在使用 TensorBoard 前,我们需要先指定一个文件夹供 TensorBoard 保存记录下来的数据。然后调用 tensorboard 中的 SummaryWriter 作为上述“记录员”
'''SummaryWriter类'''
class SummaryWriter(object):
def __init__(self, log_dir=None, comment='', purge_step=None, max_queue=10,
flush_secs=120, filename_suffix=''):
# 参数如下:
# log_dir:eventfile输出文件夹地址
# comment:当不指定log_dir时,该参数为文件夹的后缀
# filename_suffix:eventfile文件名的后缀
2
3
4
5
6
7
8
9
from tensorboardX import SummaryWriter
writer = SummaryWriter('./runs')
2
3
上面的操作实例化 SummaryWritter 为变量 writer,并指定 writer 的输出目录为当前目录下的"runs"目录。也就是说,之后 tensorboard 记录下来的内容都会保存在 runs。
如果使用 PyTorch 自带的 tensorboard,则采用如下方式 import:
from torch.utils.tensorboard import SummaryWriter
启动 tensorboard 也很简单,在命令行中输入
tensorboard --logdir=/path/to/logs/ --port=xxxx
其中“path/to/logs/"是指定的保存 tensorboard 记录结果的文件路径(等价于上面的“./runs",port 是外部访问 TensorBoard 的端口号,可以通过访问 ip:port 访问 tensorboard,这一操作和 jupyter notebook 的使用类似。如果不是在服务器远程使用的话则不需要配置 port。
有时,为了 tensorboard 能够不断地在后台运行,也可以使用 nohup 命令或者 tmux 工具来运行 tensorboard。
TensorBoard 模型结构可视化
首先定义模型:
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3,out_channels=32,kernel_size = 3)
self.pool = nn.MaxPool2d(kernel_size = 2,stride = 2)
self.conv2 = nn.Conv2d(in_channels=32,out_channels=64,kernel_size = 5)
self.adaptive_pool = nn.AdaptiveMaxPool2d((1,1))
self.flatten = nn.Flatten()
self.linear1 = nn.Linear(64,32)
self.relu = nn.ReLU()
self.linear2 = nn.Linear(32,1)
self.sigmoid = nn.Sigmoid()
def forward(self,x):
x = self.conv1(x)
x = self.pool(x)
x = self.conv2(x)
x = self.pool(x)
x = self.adaptive_pool(x)
x = self.flatten(x)
x = self.linear1(x)
x = self.relu(x)
x = self.linear2(x)
y = self.sigmoid(x)
return y
model = Net()
print(model)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
输出如下:
Net(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1))
(pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1))
(adaptive_pool): AdaptiveMaxPool2d(output_size=(1, 1))
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear1): Linear(in_features=64, out_features=32, bias=True)
(relu): ReLU()
(linear2): Linear(in_features=32, out_features=1, bias=True)
(sigmoid): Sigmoid()
)
2
3
4
5
6
7
8
9
10
11
writer.add_graph(model, input_to_model = torch.rand(1, 3, 224, 224))
writer.close()
2
展示结果如下(其中框内部分初始会显示为“Net",需要双击后才会展开):
TensorBoard 图像可视化
当我们做图像相关的任务时,可以方便地将所处理的图片在 tensorboard 中进行可视化展示。
- 对于单张图片的显示使用 add_image
- 对于多张图片的显示使用 add_images
- 有时需要使用 torchvision.utils.make_grid 将多张图片拼成一张图片后,用 writer.add_image 显示
def make_grid(tensor, nrow=8, padding=2,
normalize=False, range=None, scale_each=False, pad_value=0):
# 参数:
# tensor:必须是B*C*H*W形式
# nrow:指定行数
# padding:图像与图像之间的间隔多少个像素单位pad_value
# normalize:是否将像素值标准化到(0~255)
# range:指定标准化范围,先将像素值规范到该range,然后再进行(0~255)转换
# scale_each:是否对单张图像进行range
# pad_value:指定padding的大小(黑色0、白色255等等)
2
3
4
5
6
7
8
9
10
11
12
'''当然也可以直接调用add_image或add_images'''
def add_image(self, tag, img_tensor, global_step=None, walltime=None, dataformats='CHW'):
# 参数:
# 非常不方便,只能查看一个图像!!!!
# 当像素值为【0,1】,则乘以255,缩放回【0,255】;若像素值不在【0,1】之间,默认是0~255范围内。
# tag:图像的标签名
# img_tensor:注意shape,可以是3HW、HW3、HW、1HW,但不能是四维的,即有batch!!!!!!!(可以通过add_images解决或torchvision.utils.make_grid())
# global_step:x轴
def add_images(self, tag, img_tensor, global_step=None, walltime=None, dataformats='NCHW'):
# 参数:
# 无法指定行数!!!!!
# tag:图像的标签名
# img_tensor:注意shape,可以是NCHW or NHWC,但是,这里Channel在后续处理的时候只能为1或者3!!!!!
# global_step:x轴
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里我们使用 torchvision 的 CIFAR10 数据集为例:
import torchvision
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
transform_train = transforms.Compose(
[transforms.ToTensor()])
transform_test = transforms.Compose(
[transforms.ToTensor()])
train_data = datasets.CIFAR10(".", train=True, download=True, transform=transform_train)
test_data = datasets.CIFAR10(".", train=False, download=True, transform=transform_test)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
test_loader = DataLoader(test_data, batch_size=64)
images, labels = next(iter(train_loader))
# 仅查看一张图片
writer = SummaryWriter('./pytorch_tb')
writer.add_image('images[0]', images[0])
writer.close()
# 将多张图片拼接成一张图片,中间用黑色网格分割
# create grid of images
writer = SummaryWriter('./pytorch_tb')
img_grid = torchvision.utils.make_grid(images)
writer.add_image('image_grid', img_grid)
writer.close()
# 将多张图片直接写入
writer = SummaryWriter('./pytorch_tb')
writer.add_images("images",images,global_step = 0)
writer.close()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
依次运行上面三组可视化(注意不要同时在 notebook 的一个单元格内运行),得到的可视化结果如下(最后运行的结果在最上面):
另外注意上方 menu 部分,刚刚只有“GRAPHS"栏对应模型的可视化,现在则多出了”IMAGES“栏对应图像的可视化。左侧的滑动按钮可以调整图像的亮度和对比度。
此外,除了可视化原始图像,TensorBoard 提供的可视化方案自然也适用于我们在 Python 中用 matplotlib 等工具绘制的其他图像,用于展示分析结果等内容。
TensorBoard 连续变量可视化
TensorBoard 可以用来可视化连续变量(或时序变量)的变化过程,通过 add_scalar 实现:
writer.add_scalar()
- 功能:将标量添加到 summary
- 参数:
- tag (string):数据标识符
- scalar_value (float or string/blobname):要保存的数值
- global_step (int):全局步值
- walltime (float):可选参数,用于记录发生的时间,默认为 time.time()
writer.add_scalars()
- 功能:添加多个标量数据到 summary 中
- 参数:
- main_tag (string):tag 的父级名称
- tag_scalar_dict (dict):保存 tag 及相应的值的键值对
- global_step (int):全局步值
- walltime (float) 可选参数,可选参数,用于记录发生的时间,默认为 time.time()
两者区别
- writer.add_scalar() 添加一个标量到 summary
- writer.add_scalars() 可以同时添加多个标量到 summary 中,多个标量需要使用键值对的形式输入
两种共同点
- 第一个参数可简单理解为保存到 tensorboard 日志文件中的标量图像的名称
- 第二个参数可简单理解为图像的 y 轴数据
- 第三个参数可简单理解为图像的 x 轴数据
- 第四个参数都是可选参数,用于记录发生的时间,默认为 time.time()
writer = SummaryWriter('./runs')
for _, epoch in enumerate(prog_bar):
……
writer.add_scalars('Loss/Accuracy', {'train_loss': losses / len(train_loader),'train_acc': train_acc}, epoch+1)
writer.flush() # 及时刷新,否则无法同时观看到训练和验证
writer.close()
2
3
4
5
6

writer = SummaryWriter('./pytorch_tb')
for i in range(500):
x = i
y = x**2
writer.add_scalar("x", x, i) #日志中记录x在第step i 的值
writer.add_scalar("y", y, i) #日志中记录y在第step i 的值
writer.close()
2
3
4
5
6
7
可视化结果如下:
如果想在同一张图中显示多个曲线,则需要分别建立存放子路径(使用 SummaryWriter 指定路径即可自动创建,但需要在 tensorboard 运行目录下),同时在 add_scalar 中修改曲线的标签使其一致即可:
writer1 = SummaryWriter('./pytorch_tb/x')
writer2 = SummaryWriter('./pytorch_tb/y')
for i in range(500):
x = i
y = x*2
writer1.add_scalar("same", x, i) #日志中记录x在第step i 的值
writer2.add_scalar("same", y, i) #日志中记录y在第step i 的值
writer1.close()
writer2.close()
2
3
4
5
6
7
8
9
这里也可以用一个 writer,但 for 循环中不断创建 SummaryWriter 不是一个好选项。此时左下角的 Runs 部分出现了勾选项,我们可以选择我们想要可视化的曲线。曲线名称对应存放子路径的名称(这里是 x 和 y)。
这部分功能非常适合损失函数的可视化,可以帮助我们更加直观地了解模型的训练情况,从而确定最佳的 checkpoint。左侧的 Smoothing 滑动按钮可以调整曲线的平滑度,当损失函数震荡较大时,将 Smoothing 调大有助于观察 loss 的整体变化趋势。
TensorBoard 参数分布可视化
查看权重、梯度,判断是否梯度消失或发散
当我们需要对参数(或向量)的变化,或者对其分布进行研究时,可以方便地用 TensorBoard 来进行可视化,通过 add_histogram 实现。
def add_histogram(self, tag, values, global_step=None, bins='tensorflow', walltime=None, max_bins=None):
2
参数
- tag:图像的标签名
- values:要统计的参数
- global_step:y 轴
- 注意:values 传入的是多维数组(Tensor 或者 array)而不是标量,如果传入的是高维数组,会将其先扁平到一维,再分桶统计成直方图。统计方法与 numpy.histogram 类似。
import torch
import numpy as np
# 创建正态分布的张量模拟参数矩阵
def norm(mean, std):
t = std * torch.randn((100, 20)) + mean
return t
writer = SummaryWriter('./pytorch_tb/')
for step, mean in enumerate(range(-10, 10, 1)):
w = norm(mean, 1)
writer.add_histogram("w", w, step)
writer.flush()
writer.close()
2
3
4
5
6
7
8
9
10
11
12
13
14
结果如下:
可以看到相比之前多了两个选项卡【HISTOGRAMS】和【DISTRIBUTIONS】,其实这两个都是用来查看 histogram 统计结果的,只不过前者以直方形式显示统计结果, 后者提供更为抽象的统计信息。
在选项卡【HISTOGRAMS】中提供了两种显示模式:OVERLAY 和 OFFSET(左上角),可以看到在不同视角下的直方图分布情况。
HISTOGRAMS
在选项卡【HISTOGRAMS】中点开名为 dec_embedding.feature_embedding.Embed.weight
的图像窗口后,如下所示:

可以看到这张图是展示了该网络层的梯度分布图像。梯度就是个向量,可以看成数组,在统计时会把所有的梯度扁平成一个一维数组,然后用直方图统计起来。
横轴表示这些梯度中元素值的分布范围,纵轴表示第几轮。
当把鼠标放在图上时,会出现的一条黑线和数字点,如上图这个黑线就表示第 30 轮的时候统计的直方图,这个黑色数字点表示第 30 轮时有 922 个梯度元素值等于 0.0000175。
从上图可以得以下信息:
大约在第 15 轮之后,梯度中元素值的分布就不再改变了,且都集中在 0 附近。
结合【SCALARS】中 loss 曲线一直保持不变,说明模型可能遇到了训练瓶颈或者鞍点,或者是网络退化?
DISTRIBUTIONS
【DISTRIBUTIONS】的图和【HISTOGRAMS】图显示的数据源都是相同的,只是用不同的方式对相同的内容进行展示而已。
在选项卡【DISTRIBUTIONS】中点开名为dec_embedding.feature_embedding.Embed.weight
的图像窗口后,如下图:

从上图可以看出以下信息:
总体来看,梯度中元素值在 0 附近颜色普遍最深,也就是说在 0 附近这个区域权重值的取值频次最高。
在第 15 轮之后,梯度值出现频次不再改变且总体出现频次范围变大了,说明很有可能梯度的方向一直在来回改变,梯度的元素值不变,我觉得可能是小幅度的梯度震荡。
如有错误或者补充,望指出。
服务器端使用 TensorBoard
一般情况下,我们会连接远程的服务器来对模型进行训练,但由于服务器端是没有浏览器的(纯命令模式),因此,我们需要进行相应的配置,才可以在本地浏览器,使用 tensorboard 查看服务器运行的训练过程。 本文提供以下几种方式进行,其中前两种方法都是建立 SSH 隧道,实现远程端口到本机端口的转发,最后一种方法适用于没有下载 Xshell 等 SSH 连接工具的用户
MobaXterm
- 在 MobaXterm 点击 Tunneling
- 选择 New SSH tunnel,我们会出现以下界面。
3. 对新建的 SSH 通道做以下设置,第一栏我们选择
Local port forwarding
,< Remote Server>
我们填写localhost,< Remote port>
填写 6006,tensorboard 默认会在 6006 端口进行显示,我们也可以根据 tensorboard --logdir=/path/to/logs/ --port=xxxx的命令中的 port 进行修改,< SSH server>
填写我们连接服务器的 ip 地址,<SSH login>
填写我们连接的服务器的用户名,<SSH port>
填写端口号(通常为 22),< forwarded port>
填写的是本地的一个端口号,以便我们后面可以对其进行访问。 4. 设定好之后,点击 Save,然后 Start。在启动 tensorboard,这样我们就可以在本地的浏览器输入http://localhost:6006/
对其进行访问了Xshell
- Xshell 的连接方法与 MobaXterm 的连接方式本质上是一样的,具体操作如下:
- 连接上服务器后,打开当前会话属性,会出现下图,我们选择隧道,点击添加
- 按照下方图进行选择,其中目标主机代表的是服务器,源主机代表的是本地,端口的选择根据实际情况而定。
- 启动 tensorboard,在本地 127.0.0.1:6006 或者 localhost:6006 进行访问。
SSH
- 该方法是将服务器的 6006 端口重定向到自己机器上来,我们可以在本地的终端里输入以下代码:其中 16006 代表映射到本地的端口,6006 代表的是服务器上的端口。
ssh -L 16006:127.0.0.1:6006 username@remote_server_ip
1- 在服务上使用默认的 6006 端口正常启动 tensorboard
tensorboard --logdir=xxx --port=6006
1- 在本地的浏览器输入地址
127.0.0.1:16006 或者 localhost:16006
1
总结
对于 TensorBoard 来说,它的功能是很强大的,可以记录的东西不只限于本节所介绍的范围。
主要的实现方案是构建一个 SummaryWriter,然后通过add_XXX()
函数来实现。
其实 TensorBoard 的逻辑还是很简单的,它的基本逻辑就是文件的读写逻辑,写入想要可视化的数据,然后 TensorBoard 自己会读出来。