使用 TensorBoard 視覺化模型、資料和訓練

極客鋒行發表於2021-01-20

使用 TensorBoard 視覺化模型、資料和訓練

60 Minutes Blitz 中,我們展示瞭如何載入資料,並把資料送到我們繼承 nn.Module 類的模型,在訓練資料上訓練模型,並在測試集上測試模型。為了看到發生了什麼,當模型訓練的時候我們列印輸出一些統計值獲得對模型是否有進展的感覺。我們可以做的比這更好:PyTorch 整合了 TensorBoard,為視覺化訓練中的神經網路結果的工具。這篇博文說明了它的一些功能,使用可以被 torchvision.datasets 讀入 PyTorch 中的 Fashion-MNIST 資料集。

在本文中,我們將學會如何:

  1. 以合適的轉換(transforms)讀入資料(幾乎等同於之前的博文)。
  2. 設定 TensorBoard
  3. 寫入 TensorBoard
  4. 使用 TensorBoard 檢查模型結構
  5. 以少量的程式碼 TensorBoard 建立之前的視覺化的互動式版本

尤其在第 5 點,我們將看到:

  • 幾個檢查我們訓練資料的方法
  • 當訓練時如何跟蹤我們模型的效能(performance
  • 一旦訓練之後我們如何評估我們的模型效能(performance

我們將開始於相似於 訓練分類器 的模板程式碼。

# 導包
import matplotlib.pyplot as plt
import numpy as np

import torch
import torchvision
import torchvision.transforms as transforms

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/top', '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  # 反規範化(unormalize)
    npimg = img.numpy()
    if one_channel:
        plt.imshow(npimg, cmap='Greys')
    else:
        plt.imshow(np.transpose(npimg, (1, 2, 0)))

如果你下載的比較慢,可是使用我下載好的:本地下載

我們將從這個演示定義一個類似的模型結構,僅做較少的改動解釋事實:圖片現在是單通道而不是三通道和尺寸為 28x28 而不是 32x32。

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()

我們將定義和之前一樣 optimizercriterion

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

1. 設定 TensorBoard

現在我們將設定 TensorBoard,從 torch.utils 匯入 tensorboard 並定義一個 SummaryWriter,我們向 TensorBoard 寫入資訊的關鍵物件。

import tensorflow as tf
import tensorboard as tb
tf.io.gfile = tb.compat.tensorflow_stub.io.gfile

from torch.utils.tensorboard import SummaryWriter

# 預設 `log_dir` 是 "runs" —— 在這裡我們更具體一些
writer = SummaryWriter("runs/fashion_mnist_experiment_1")

注意上面這一行建立一個 runs/fashion_mnist_experiment_1 資料夾。

2. 寫入 TensorBoard

現在,讓我們寫入一張圖片到 TensorBoard —— 確切的來說,一個網格(grid)—— 使用 make_grid

# 獲得一些隨機訓練影像
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 建立影像網格(grid of images)
img_grid = torchvision.utils.make_grid(images)

# 顯示影像
matplotlib_imshow(img_grid, one_channel=True)

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

output_10_0

現在執行。

tensorboard --logdir=runs

從命令列然後導航到 http://localhost:6006/ 應該會看到如下。

image

現在你知道如何使用 TensorBoard 了!然而,這個例子可被完成在 Jupyter Notebook 中 —— TensorBoard 真正擅長的是建立互動式視覺化。我們下一步將介紹這些中的一個,在本博文的最後你會看到幾個更多的例子。

3. 使用 TensorBoard 檢查模型

TensorBoard 的一個強大的能力就是它可以視覺化複雜的模型結構。讓我們視覺化我們構建的模型。

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

現在重新整理 TensorBoard 你應該能從上面看到一個“Graphs”就像這樣。

image

繼續並雙擊“Net”以檢視它的擴充套件,檢視組成模型的每個單獨的操作的詳細的檢視。

image

TensorBoard 對於視覺化高維資料有著非常方便的特徵,比如低維空間中影像資料;我們將在下一步討論它。

4. 向 TensorBoard 新增“Projector”

我們可以通過 add_embedding 方法視覺化更高維度資料的低維表示。

# 輔助函式
def select_n_random(data, labels, n=100):
    '''
    從一個資料集選擇 n 個隨機資料點和它們對應的標籤
    '''
    assert len(data) == len(labels)
    
    perm = torch.randperm(len(data))
    return data[perm][:n], labels[perm][:n]

# 選擇隨即影像和它們的目標索引
images, labels = select_n_random(trainset.data, trainset.targets)

# 對每一張影像獲得類標籤
class_labels = [classes[lab] for lab in labels]

# 載入 embeddings
features = images.view(-1, 28 * 28)
writer.add_embedding(
    features, metadata=class_labels,
    label_img=images.unsqueeze(1)
)
writer.close()

現在,在 TensorBoard 的“Projector”標籤裡,你可以看到這 100 張影像(每個都是 784 維)投影到三維空間中。除此之外,這還是互動式的:你可以點選並且拖拽旋轉三維的投影。最終,幾個小提示讓視覺化更輕鬆地看到:選擇左上方的“color: label”,並且也啟用“night mode”,因為它們的背景是白色,這將讓影像更輕鬆地顯示出來。

image

現在我們徹底地檢查了我們的資料,現在讓我們的展示 TensorBoard 如何更清楚的從訓練開始跟蹤模型訓練和評估。

5. 使用 TensorBoard 跟蹤模型訓練

在之前的例子中,我們只是每 2000 次迭代就簡單地列印輸出模型的損失值(loss)。現在,我們將把執行中的損失值(loss)記錄到 TensorBoard,以及通過 plot_classes_pred 函式檢視模型所做的預測。

# 輔助函式

def images_to_probs(net, images):
    '''
    從一個訓練好的網路和一系列影像中生成預測和對應的概率
    '''
    output = net(images)
    # 轉換輸出概率到預測的類別
    _, preds_tensor = torch.max(output, 1)
    preds = np.squeeze(preds_tensor.numpy())
    return preds, [F.softmax(el, dim=0)[i].item() for i, el in zip(preds, output)]

def plot_classes_preds(net, images, labels):
    '''
    使用一個訓練的網路生成 matplotlib 影像,以及從一個資料批量的圖片和標籤,
    顯示出網路的最高的預測和概率以及真實的標籤,並且根據預測是否正確資訊上色。
    使用 images_to_prbs 函式。
    '''
    preds, probs = images_to_probs(net, images)
    # 畫出批量中的影像以及預測的和真實的標籤。
    fig = plt.figure(figsize=(12, 6))
    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

最後,讓我們使用與之前的演示相同的程式碼訓練模型,但是將每 1000 批量的結果寫入 TensorBoard 而不是列印輸出到控制檯(console),這一工作使用 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()
        
        # 前向傳播 + 反向傳播 + 優化
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
        if i % 1000 == 999:  # 每 1000 的資料批量
            # 記錄執行的損失值(loss)
            writer.add_scalar('training loss', running_loss / 1000, epoch * len(trainloader) + i)
            # 在隨機的資料批量記錄一個 Matplotlib 影像,展示模型的預測
            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')
Finished Training

你現在可以在“SCALARS”標籤下看到訓練的 15,000 次的迭代執行中的 loss

image

此外,我們可以看到學習過程中任意的資料批量模型做的預測。在 Images 標籤裡,滾到 predictions vs. actuals 視覺化下方檢視;這向我們展示,舉個例子,剛過 3000 訓練迭代後,即使它還沒有訓練之後那樣自信,模型就有能力區分視覺上有明顯區分的類別,比如 shirtssneakerscoats

image

在之前的教程裡,模型一旦訓練完成,我們就看每個類別的準確率;這裡,我們將使用 TensorBoard 對每一個類別畫出精度—召回率曲線(precision_recall curves),這裡 有一個很好的解釋。

6. 使用 TensorBoard 評估訓練好的模型

# 1. 獲得預測概率 test_size * num_classes 張量(Tensor)
# 2. 獲取 preds 尺寸為 test_size 張量(Tensor)
# 執行時間大約為 10 秒
class_probs = []
class_preds = []
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_preds_batch = torch.max(output, 1)
        
        class_probs.append(class_probs_batch)
        class_preds.append(class_preds_batch)

test_probs = torch.cat([torch.stack(batch) for batch in class_probs])
test_preds = torch.cat(class_preds)

# 輔助函式
def add_pr_curve_tensorboard(class_index, test_probs, test_preds, global_step=0):
    '''
    接收從 0 到 9 的 class_index 並且畫出對應的 precision-recall curve
    '''
    tensorboard_preds = test_preds == class_index
    tensorboard_probs = test_probs[:, class_index]
    
    writer.add_pr_curve(classes[class_index], tensorboard_preds, tensorboard_probs, global_step=global_step)
    writer.close()

# 畫出所有的 precision-recall curve
for i in range(len(classes)):
    add_pr_curve_tensorboard(i, test_probs, test_preds)

你現在將看到 PR CURVES 選單標籤,其中包含了每一個類別的 precision-recall curves。去四處看看,你將看到一些類別模型有接近 100% 的曲線下的面積(area under the curve),然而其它類別中,這個標記會低一點。

image

這是一篇關於 TensorBoard 與 PyTorch 的整合的介紹。當然,你可以在 Jupyter Notebook 做 TensorBoard 能做到的事情,但是在 TensorBoard,預設情況下可以得到互動式的視覺效果。

相關文章