メインコンテンツへスキップ
Try in Colab このチュートリアルでは、PyTorch と MNIST データを使用して、トレーニングの過程でモデルの予測を追跡、可視化、比較する方法について説明します。 以下の方法を学ぶことができます:
  1. モデルのトレーニングまたは評価中に、メトリクス、画像、テキストなどを wandb.Table() にログを記録する
  2. これらのテーブルを表示、ソート、フィルタリング、グループ化、結合、対話的なクエリ、探索する
  3. モデルの予測や結果を、特定の画像、ハイパーパラメーター/モデルバージョン、またはタイムステップにわたって動的に比較する

特定の画像の予測スコアを比較する

ライブサンプル:トレーニングの 1 エポック後と 5 エポック後の予測を比較する →
Training epoch comparison
ヒストグラムは、2 つのモデル間のクラスごとのスコアを比較しています。各ヒストグラムの上の緑色のバーは、1 エポックのみトレーニングしたモデル「CNN-2, 1 epoch」(id 0) を表しています。下の紫色のバーは、5 エポックトレーニングしたモデル「CNN-2, 5 epochs」(id 1) を表しています。画像はモデル間で予測が一致しないケースにフィルタリングされています。例えば、1 行目では、「4」という数字に対して 1 エポック後にはすべての数字で高いスコアが出ていますが、5 エポック後には正解のラベルで最も高いスコアを出し、それ以外では非常に低いスコアになっています。

時間経過による主なエラーに注目する

ライブサンプル → テストデータ全体で誤った予測を確認します(“guess” != “truth” の行でフィルタリング)。トレーニング 1 エポック後には 229 件の誤予測がありますが、5 エポック後には 98 件に減少していることがわかります。
Side-by-side epoch comparison

モデルのパフォーマンスを比較しパターンを見つける

ライブサンプルで詳細を確認する → 正解を除外してから予測値(guess)でグループ化し、誤分類された画像の例と、それに対応する正解ラベル(true labels)の分布を 2 つのモデルで並べて確認します。左側はレイヤーサイズと学習率を 2 倍にしたモデルバリアントで、右側はベースラインです。ベースラインの方が、予測された各クラスにおいてわずかにミスが多いことがわかります。
Error comparison

サインアップまたはログイン

W&B に サインアップまたはログイン して、ブラウザで実験をインタラクティブに確認しましょう。 この例では、便利なホスト環境として Google Colab を使用していますが、独自のトレーニングスクリプトをどこからでも実行し、W&B の実験管理ツールでメトリクスを可視化することができます。
!pip install wandb -qqq
アカウントにログを記録する

import wandb
wandb.login()

WANDB_PROJECT = "mnist-viz"

0. セットアップ

依存関係のインストール、MNIST のダウンロード、および PyTorch を使用したトレーニング用とテスト用のデータセットの作成を行います。
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as T 
import torch.nn.functional as F


device = "cuda:0" if torch.cuda.is_available() else "cpu"

# トレーニング用データローダーを取得
def get_dataloader(is_train, batch_size, slice=5):
    "Get a training dataloader"
    ds = torchvision.datasets.MNIST(root=".", train=is_train, transform=T.ToTensor(), download=True)
    loader = torch.utils.data.DataLoader(dataset=ds, 
                                         batch_size=batch_size, 
                                         shuffle=True if is_train else False, 
                                         pin_memory=True, num_workers=2)
    return loader

1. モデルとトレーニングスケジュールの定義

  • 実行するエポック数を設定します。各エポックはトレーニングステップと検証(テスト)ステップで構成されます。オプションで、テストステップごとにログを記録するデータ量を設定します。ここではデモを簡略化するため、可視化するバッチ数とバッチあたりの画像数を少なく設定しています。
  • シンプルな畳み込みニューラルネットワークを定義します(pytorch-tutorial のコードを参考にしています)。
  • PyTorch を使用してトレーニングセットとテストセットを読み込みます。
# 実行するエポック数
# 各エポックにはトレーニングステップとテストステップが含まれるため、
# ログに記録されるテスト予測テーブルの数が決まります
EPOCHS = 1

# 各テストステップでテストデータからログを記録するバッチ数
# (デモ簡略化のためデフォルトは低めに設定)
NUM_BATCHES_TO_LOG = 10 #79

# テストバッチごとにログを記録する画像数
# (デモ簡略化のためデフォルトは低めに設定)
NUM_IMAGES_PER_BATCH = 32 #128

# トレーニング設定とハイパーパラメーター
NUM_CLASSES = 10
BATCH_SIZE = 32
LEARNING_RATE = 0.001
L1_SIZE = 32
L2_SIZE = 64
# これを変更すると、隣接する層の形状を変更する必要がある場合があります
CONV_KERNEL_SIZE = 5

# 2層の畳み込みニューラルネットワークを定義
class ConvNet(nn.Module):
    def __init__(self, num_classes=10):
        super(ConvNet, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(1, L1_SIZE, CONV_KERNEL_SIZE, stride=1, padding=2),
            nn.BatchNorm2d(L1_SIZE),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))
        self.layer2 = nn.Sequential(
            nn.Conv2d(L1_SIZE, L2_SIZE, CONV_KERNEL_SIZE, stride=1, padding=2),
            nn.BatchNorm2d(L2_SIZE),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))
        self.fc = nn.Linear(7*7*L2_SIZE, NUM_CLASSES)
        self.softmax = nn.Softmax(NUM_CLASSES)

    def forward(self, x):
        # 特定の層の形状を確認するにはコメントを解除してください:
        #print("x: ", x.size())
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.reshape(out.size(0), -1)
        out = self.fc(out)
        return out

train_loader = get_dataloader(is_train=True, batch_size=BATCH_SIZE)
test_loader = get_dataloader(is_train=False, batch_size=2*BATCH_SIZE)

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

2. トレーニングの実行とテスト予測のログ記録

各エポックで、トレーニングステップとテストステップを実行します。各テストステップでは、テスト予測を保存するための wandb.Table() を作成します。これらはブラウザ上で可視化、動的なクエリ、並べて比較することが可能です。
# テスト画像のバッチの予測をログに記録するための便利な関数
def log_test_predictions(images, labels, outputs, predicted, test_table, log_counter):
  # すべてのクラスの信頼度スコアを取得
  scores = F.softmax(outputs.data, dim=1)
  log_scores = scores.cpu().numpy()
  log_images = images.cpu().numpy()
  log_labels = labels.cpu().numpy()
  log_preds = predicted.cpu().numpy()
  # 画像の順序に基づいた ID の追加
  _id = 0
  for i, l, p, s in zip(log_images, log_labels, log_preds, log_scores):
    # データテーブルに必要な情報を追加:
    # id, 画像ピクセル, モデルの推測, 正解ラベル, すべてのクラスのスコア
    img_id = str(_id) + "_" + str(log_counter)
    test_table.add_data(img_id, wandb.Image(i), p, l, *s)
    _id += 1
    if _id == NUM_IMAGES_PER_BATCH:
      break

# W&B: このモデルのトレーニングを追跡するための新しい run を初期化
with wandb.init(project="table-quickstart") as run:

    # W&B: config を使用してハイパーパラメーターをログに記録
    cfg = run.config
    cfg.update({"epochs" : EPOCHS, "batch_size": BATCH_SIZE, "lr" : LEARNING_RATE,
                "l1_size" : L1_SIZE, "l2_size": L2_SIZE,
                "conv_kernel" : CONV_KERNEL_SIZE,
                "img_count" : min(10000, NUM_IMAGES_PER_BATCH*NUM_BATCHES_TO_LOG)})

    # モデル、損失関数、オプティマイザーの定義
    model = ConvNet(NUM_CLASSES).to(device)
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)

    # モデルのトレーニング
    total_step = len(train_loader)
    for epoch in range(EPOCHS):
        # トレーニングステップ
        for i, (images, labels) in enumerate(train_loader):
            images = images.to(device)
            labels = labels.to(device)
            # forward pass
            outputs = model(images)
            loss = criterion(outputs, labels)
            # 逆伝播と最適化
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
    
            # W&B: トレーニングステップごとの損失をログに記録し、UI でライブ可視化
            run.log({"loss" : loss})
            if (i+1) % 100 == 0:
                print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
                    .format(epoch+1, EPOCHS, i+1, total_step, loss.item()))
                

        # W&B: 各テストステップの予測を保存する Table を作成
        columns=["id", "image", "guess", "truth"]
        for digit in range(10):
        columns.append("score_" + str(digit))
        test_table = wandb.Table(columns=columns)

        # モデルのテスト
        model.eval()
        log_counter = 0
        with torch.no_grad():
            correct = 0
            total = 0
            for images, labels in test_loader:
                images = images.to(device)
                labels = labels.to(device)
                outputs = model(images)
                _, predicted = torch.max(outputs.data, 1)
                if log_counter < NUM_BATCHES_TO_LOG:
                log_test_predictions(images, labels, outputs, predicted, test_table, log_counter)
                log_counter += 1
                total += labels.size(0)
                correct += (predicted == labels).sum().item()

            acc = 100 * correct / total
            # W&B: トレーニングエポックごとの精度をログに記録し、UI で可視化
            run.log({"epoch" : epoch, "acc" : acc})
            print('Test Accuracy of the model on the 10000 test images: {} %'.format(acc))

        # W&B: 予測テーブルを wandb にログ記録
        run.log({"test_predictions" : test_table})

次のステップ

次のチュートリアルでは、W&B Sweeps を使用してハイパーパラメーターを最適化する方法 を学びます。