在60分钟闪电战中,我们像你展示了如何加载数据,通过为我们定义的nn.Module的子类的model提供数据,在训练集上训练模型,在测试集上测试模型。为了了解发生了什么,我们在模型训练时打印了一些统计数据,以观察训练是否正在进行。但是,我们可以做的比这更好:PyTorch和TensorBoard的集成,是一个用来可视化神经网络运行结果的工具。本教程使用Fashion-MNIST数据集说明它的一些功能,该数据集可以使用torchvision.datasets读到Pytorch中。
在本教程中,我们会学习如何:
特别是第5点,我们会看到:
我们将从CIFAR-10教程中类似的样例代码开始:
# importsimport matplotlib.pyplot as pltimport numpy as npimport torchimport torchvisionimport torchvision.transform as transformimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optim# transformstransform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])# datasetstrainset = torchvision.datasets.FashionMNIST('./data', download=True, train=True, transform=transform)testset = torchvision.datasets.FashionMNIST('./data', download=True, train=False, transform=transform)# dataloaderstrainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)# constant for classesclasses = ('T-shirt/torp', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle Boot')# 显示单张图片的辅助函数# (在下面的'plot_classes_preds'函数会使用)def matplotlib_imshow(img, one_channel=False): if one_channel: img = img.mean(dim=0) img = img / 2 + 0.5 # unnormalize npimg = img.numpy() if one_channel: plt.imshow(npimg, cmap='Greys') else: plt.imshow(np.transpose(npimg, (1, 2, 0)))我们将在本教程中定义一个类似的模型架构,只做一些小的修改以说明图片现在是单通道而非3通道、2828而非3232。
class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16*4*4, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16 * 4 * 4) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return xnet = Net() 我们将定义同样的优化器和损失函数:
criterion = nn.CrossEntropyLoss()optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)现在我们要设置TensorBoard,从torch.utils导入tensorboard,并定义一个SummaryWriter,它是将信息写入TensorBoard的关键对象。
from torch.utils.tensorboard import SummaryWriter# 默认的日志目录是'runs' - 在这里,我们会更加具体writer = SummaryWriter('runs/fashion_mnist_experiment_1')注意,此行创建了runs/fashion_mnist_experiment_1文件夹。
现在,让我们把一个图片写入TensorBoard - 具体来说,a grid - using make_grid。
# 获取一些随机的训练样本dataiter = iter(trainloader)images, labels = dataiter.next()# 创建图片网格img_grid = torchvision.utils.make_grid(images)# show imagesmatplotlib_imshow(img_grid, one_channel=True)# 写入TensorBoardwriter.add_image('four_fashion_mnist_images', img_grid)运行:
tensorboard --logdir=runs从命令行中导航到http://localhost:6006,应该会显示以下内容:

现在,你知道如何使用TensorBoard了!但是对于该例,Jupyter Notebook也可以做,TensorBoard真正擅长的是创建可交互的可视化界面。我们将在接下来展示其中一个,并在教程最后再介绍几个。
TensorBoard的一个强大的功能是可视化复杂的模型结构。让我们可视化我们构建的模型
writer.add_graph(net, images)writer.close()现在刷新TensorBoard,你会看到‘Graphs’ tab:
双击‘Net’展开,查看构成模型的哥哥操作的详细视图。
TensorBoard有一个非常方便的功能,可以在低维空间可视化高维数据,例如图片,我们接下来会介绍这个:
我们可以通过add_embedding可视化高维数据的低维表示。
# 辅助函数def selec_n_random(data, labels, n=100): ''' 从dataset中选择n个随机的数据点及其标签 ''' # assert用于判断一个表达式,在为True时,正常运行,为False时触发异常:AssertionError。 assert len(data) == len(labels) perm = torch.randperm(len(data)) # 将0~n-1(包括0和n-1)打乱后获得的数字序列 return data[perm][:n], labels[perm][:n]# 选择随机图片及其标签索引images, labels = select_n_random(trainset.data, trainset.targets)# 获得每一个图片的类别标签class_labels = [classes[lab] for lab in labels]# 日志嵌入features = images.view(-1, 28 * 28)writer.add_embedding(features, metadata=class_labels, label_img = images.unsqueeze(1))writer.close()此时,在TensorBoard的‘Projector’,你会看到这100个图片,每个都是784维,被投影到3维度空间。而且,这是可交互的:你可以点击和拖拽来翻转这三个维度的投影。最后,一些易于可视化的手段:选择左上角的‘color:label’,还有启用‘夜间模式’,这将使图像更易查看,因为它们的背景是白色的。
现在,我们已经彻底检查了我们的数据,让我们从训练开始,展示TensorBoard如何让跟踪模型训练和验证更清晰。
在之前的例子中,我们每2000次迭代打印模型的运行loss。现在,我们将把运行loss记录到TensorBoard,并通过plot_classes_preds函数查看模型预测。
# 辅助函数def images_to_probs(net, images): ''' 从一个训练后的模型和一系列图片生成predictions及对应的probabilities ''' output = net(images) # 将输出的probabilities转换为预测的类别 _, preds_tensor = torch.max(output, 1) # preds_tensor是最大值的索引 preds = np.squeeze(preds_tensor.numpy()) # softmax将网络输出值映射到(0, 1) return preds, [F.softmax(el, dim=0)[i].item() for i, el in zip(preds, output)]def plot_classes_preds(net, images, labels): ''' 使用训练后的网络生成matplotlib图片,以及1个batch的图片和标签,显示网络最高的预测及概率,以及实际 标签,根据预测是否正确为该信息着色。使用'images_to_probs'函数。 ''' preds, probs = images_to_probs(net, images) # 绘制batch中的图片、预测和真值 fig = plt.figure(figsize=(12, 48)) for idx in np.arange(4): ax = fig.add_subplot(1, 4, idx+1, xticks=[], yticks=[]) matplotlib_imshow(images[idx], one_channel=True) ax.set_title("{0}, {1:.1f}%\n(label: {2})".format( classes[preds[idx]], probs[idx] * 100.0, classes[labels[idx]]), color=('green' if preds[idx]==labels[idx].item() else 'red')) return fig最后,让我们使用与之前教程中相同的训练代码训练模型,但是每1000batches将结果写入TensorBoard而不是打印到控制台;可以通过add_scalar。
此外,训练时,我们将生成图片来展示模型的预测与该批次中包含的四张图片的实际结果。
running_loss = 0.0for epoch in range(1): # 遍历数据集的次数 for i, data in enumerate(trainloader, 0): # 获得输入;data是一个[inputs, labels]的列表 inputs, labels = data # 将梯度置零 optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 1000 == 999: # 每1000 mini-batche # 记录running_loss writer.add_scalar('training loss', running_loss / 1000, epoch * len(trainloader) + i) # 第三个参数是步值(可理解为X轴) # 记录一个Matplotlib图片,在和一个随机的mini-batch上展示模型的预测 writer.add_figure('predictions vs. actuals', plot_classes_preds(net, inputs, labels), global_step=epoch * len(trainloader) + i) running_loss = 0.0print('Finished Training')现在你可以在scalars tab看到训练15000次的运行loss被绘制出来了。

此外,还可以看到模型在随机的batches上通过学习得到预测。查看‘Images’ tab 并在‘predictions vs. actual’中向下滚动可以看到查看此内容,这表明,例如,经过3000次训练迭代,该模型已经能够区分视觉上不同的类别,例如衬衫、运动鞋和外套,尽管它不像之后的训练那么自信:

在之前的教程中,我们看到了模型训练后在每个类别上的准确率,现在,我们将用TensorBoard为每类绘制precision-recall曲线(关于P-R曲线)。
# 1. 获取test_size的具有x个num_classes Tensor的概率值预测# 2. 获得test_zie Tensor的预测class_probs = []class_label = []with torch.no_grad(): for data in testloader: images, labels = data output = net(images) class_probs_batch = [F.softmax(el, dim=0) for el in output] class_probs.append(class_probs_batch) class_label.append(labels)test_probs = torch.cat([torch.stack(batch) for batch in class_probs])test_label = torch.cat(class_label)# 辅助函数def add_pr_curve_tensorboard(class_idx, test_probs, test_label, globa_step=0): ''' 接受从0到9的‘class_index’并绘制相应的p-r曲线 ''' tensorboard_truth = test_label == class_index tensorboard_probs = test_probs[:, class_index] writer.add_pr_curve(classes[class_index], tensorboard_truth, tensorboard_probs, global_step=global_step) writer.close()# 绘制所有的pr曲线for i in range(len(classes)): add_pr_curve_tensorboard(i, test_probs, test_label)现在,你会看到‘PR CURVES’包含了每一类别的pr曲线,细看之后你会发现,在一些类,模型几乎拥有曲线下100%的面积,而其它类很低:

这是对TensorBoard和PyTorch与其集成的介绍。当然,你可以在Jupyter Notebook中执行TensorBoard可做的所有事情,但TensorBoard默认可获得交互的视觉效果。