利用Tensorboard視覺化模型、資料和訓練過程

Deep_RS發表於2022-02-15

60分鐘閃電戰中,我們像你展示瞭如何載入資料,通過為我們定義的nn.Module的子類的model提供資料,在訓練集上訓練模型,在測試集上測試模型。為了瞭解發生了什麼,我們在模型訓練時列印了一些統計資料,以觀察訓練是否正在進行。但是,我們可以做的比這更好:PyTorch和TensorBoard的整合,是一個用來視覺化神經網路執行結果的工具。本教程使用Fashion-MNIST資料集說明它的一些功能,該資料集可以使用torchvision.datasets讀到Pytorch中。

在本教程中,我們會學習如何:

  • 讀入資料,並進行適當的轉換(幾乎與之前的教程的相同)
  • 設定TensorBoard
  • 寫入TensorBoard
  • 使用TensorBoard檢查模型架構
  • 使用TensorBoard和更少的程式碼建立上一教程中視覺化的互動式版本

特別是第5點,我們會看到:

  • 檢查訓練資料的幾種方法
  • 如何在訓練時跟蹤模型的效能
  • 訓練後如何評估模型效能

我們將從CIFAR-10教程中類似的樣例程式碼開始:

# imports
import matplotlib.pyplot as plt
import numpy as np

import torch
import torchvision
import torchvision.transform as transform

import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

# transforms
transform = transforms.Compose(
    [transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))]
)

# datasets
trainset = torchvision.datasets.FashionMNIST('./data',
    download=True,
    train=True,
    transform=transform)
testset = torchvision.datasets.FashionMNIST('./data',
    download=True,
    train=False,
    transform=transform)

# dataloaders
trainloader = 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 classes
classes = ('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 x

net = Net()  

我們將定義同樣的優化器和損失函式:

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

TensorBoard 設定

現在我們要設定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

現在,讓我們把一個圖片寫入TensorBoard - 具體來說,a grid - using make_grid

# 獲取一些隨機的訓練樣本
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 建立圖片網格
img_grid = torchvision.utils.make_grid(images)

# show images
matplotlib_imshow(img_grid, one_channel=True)

# 寫入TensorBoard
writer.add_image('four_fashion_mnist_images', img_grid)

執行:

tensorboard --logdir=runs

從命令列中導航到http://localhost:6006,應該會顯示以下內容:

現在,你知道如何使用TensorBoard了!但是對於該例,Jupyter Notebook也可以做,TensorBoard真正擅長的是建立可互動的視覺化介面。我們將在接下來展示其中一個,並在教程最後再介紹幾個。

使用TensorBoard檢查模型

TensorBoard的一個強大的功能是視覺化複雜的模型結構。讓我們視覺化我們構建的模型

writer.add_graph(net, images)
writer.close()

現在重新整理TensorBoard,你會看到‘Graphs’ tab:

雙擊‘Net’展開,檢視構成模型的哥哥操作的詳細檢視。

TensorBoard有一個非常方便的功能,可以在低維空間視覺化高維資料,例如圖片,我們接下來會介紹這個:

為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如何讓跟蹤模型訓練和驗證更清晰。

使用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.0
for 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.0
print('Finished Training')

現在你可以在scalars tab看到訓練15000次的執行loss被繪製出來了。

此外,還可以看到模型在隨機的batches上通過學習得到預測。檢視‘Images’ tab 並在‘predictions vs. actual’中向下滾動可以看到檢視此內容,這表明,例如,經過3000次訓練迭代,該模型已經能夠區分視覺上不同的類別,例如襯衫、運動鞋和外套,儘管它不像之後的訓練那麼自信:

在之前的教程中,我們看到了模型訓練後在每個類別上的準確率,現在,我們將用TensorBoard為每類繪製precision-recall曲線(關於P-R曲線)。

使用TensorBoard評估訓練模型

# 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預設可獲得互動的視覺效果。

相關文章