実験トラッキングは、機械学習プロジェクトを効率的に管理するために不可欠です。TensorBoardとWeights & Biases(W&B)は、最も広く使われているツールです。
本記事では、両ツールの基本的な使い方と、実践的な実験管理のテクニックを解説します。
本記事の内容
- 実験トラッキングの重要性
- TensorBoardの使い方
- Weights & Biasesの使い方
- ハイパーパラメータスイープ
- 実験の比較と分析
実験トラッキングの重要性
なぜ実験管理が必要か
- 再現性: どの実験がどのパラメータで行われたか記録
- 比較: 複数の実験結果を並べて比較
- デバッグ: 訓練過程を可視化して問題を発見
- コラボレーション: チームで実験結果を共有
記録すべき情報
| カテゴリ | 内容 |
|---|---|
| メトリクス | 損失、精度、学習率など |
| ハイパーパラメータ | モデル構成、最適化設定など |
| コード | 実験時のコードバージョン |
| データ | 使用したデータセット、前処理 |
| 環境 | ハードウェア、ライブラリバージョン |
TensorBoard
基本的な使い方
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.tensorboard import SummaryWriter
import numpy as np
# SummaryWriterの作成
writer = SummaryWriter('runs/experiment_1')
# スカラー値のロギング
for step in range(100):
loss = 1.0 / (step + 1)
accuracy = 1 - loss
learning_rate = 0.001 * (0.95 ** step)
writer.add_scalar('Loss/train', loss, step)
writer.add_scalar('Accuracy/train', accuracy, step)
writer.add_scalar('Learning_rate', learning_rate, step)
# 複数のスカラーを同じグラフに
for step in range(100):
train_loss = 1.0 / (step + 1)
val_loss = 1.2 / (step + 1)
writer.add_scalars('Loss', {
'train': train_loss,
'validation': val_loss
}, step)
writer.close()
ヒストグラムと分布
# モデルパラメータの分布をロギング
model = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
writer = SummaryWriter('runs/histogram_example')
for epoch in range(10):
# パラメータの分布をロギング
for name, param in model.named_parameters():
writer.add_histogram(f'Parameters/{name}', param, epoch)
if param.grad is not None:
writer.add_histogram(f'Gradients/{name}', param.grad, epoch)
writer.close()
画像のロギング
import torchvision
import matplotlib.pyplot as plt
writer = SummaryWriter('runs/images_example')
# ダミー画像
images = torch.randn(16, 3, 32, 32)
# 画像グリッドをロギング
img_grid = torchvision.utils.make_grid(images, nrow=4)
writer.add_image('Sample Images', img_grid, 0)
# matplotlibの図をロギング
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(np.sin(np.linspace(0, 10, 100)))
ax.set_title('Sine Wave')
writer.add_figure('Matplotlib Figure', fig, 0)
plt.close(fig)
writer.close()
モデルグラフ
model = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 10)
)
writer = SummaryWriter('runs/graph_example')
# ダミー入力でモデルグラフを追加
dummy_input = torch.randn(1, 784)
writer.add_graph(model, dummy_input)
writer.close()
完全な訓練ループ
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from torch.utils.tensorboard import SummaryWriter
from datetime import datetime
class TensorBoardTrainer:
"""TensorBoardを使った訓練クラス"""
def __init__(self, model, train_loader, val_loader, config):
self.model = model
self.train_loader = train_loader
self.val_loader = val_loader
self.config = config
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.model.to(self.device)
# TensorBoardの設定
run_name = f"{config['name']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
self.writer = SummaryWriter(f"runs/{run_name}")
# ハイパーパラメータをロギング
self.writer.add_text('config', str(config))
self.criterion = nn.CrossEntropyLoss()
self.optimizer = optim.Adam(model.parameters(), lr=config['lr'])
self.scheduler = optim.lr_scheduler.StepLR(
self.optimizer, step_size=config['lr_step'], gamma=config['lr_gamma']
)
def train_epoch(self, epoch):
self.model.train()
total_loss = 0
correct = 0
total = 0
for batch_idx, (data, target) in enumerate(self.train_loader):
data, target = data.to(self.device), target.to(self.device)
self.optimizer.zero_grad()
output = self.model(data)
loss = self.criterion(output, target)
loss.backward()
self.optimizer.step()
total_loss += loss.item()
pred = output.argmax(dim=1)
correct += (pred == target).sum().item()
total += target.size(0)
# バッチごとのロギング
global_step = epoch * len(self.train_loader) + batch_idx
self.writer.add_scalar('Loss/train_step', loss.item(), global_step)
avg_loss = total_loss / len(self.train_loader)
accuracy = correct / total
return avg_loss, accuracy
def validate(self, epoch):
self.model.eval()
total_loss = 0
correct = 0
total = 0
with torch.no_grad():
for data, target in self.val_loader:
data, target = data.to(self.device), target.to(self.device)
output = self.model(data)
loss = self.criterion(output, target)
total_loss += loss.item()
pred = output.argmax(dim=1)
correct += (pred == target).sum().item()
total += target.size(0)
avg_loss = total_loss / len(self.val_loader)
accuracy = correct / total
return avg_loss, accuracy
def train(self, epochs):
for epoch in range(epochs):
train_loss, train_acc = self.train_epoch(epoch)
val_loss, val_acc = self.validate(epoch)
self.scheduler.step()
current_lr = self.scheduler.get_last_lr()[0]
# エポックごとのロギング
self.writer.add_scalars('Loss', {
'train': train_loss,
'val': val_loss
}, epoch)
self.writer.add_scalars('Accuracy', {
'train': train_acc,
'val': val_acc
}, epoch)
self.writer.add_scalar('Learning_rate', current_lr, epoch)
# パラメータの分布
for name, param in self.model.named_parameters():
self.writer.add_histogram(f'weights/{name}', param, epoch)
if param.grad is not None:
self.writer.add_histogram(f'grads/{name}', param.grad, epoch)
print(f"Epoch {epoch}: train_loss={train_loss:.4f}, "
f"val_loss={val_loss:.4f}, val_acc={val_acc:.4f}")
self.writer.close()
# 使用例
model = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(256, 10)
)
config = {
'name': 'mnist_experiment',
'lr': 0.001,
'lr_step': 10,
'lr_gamma': 0.5,
'batch_size': 64,
}
# ダミーデータ
train_data = TensorDataset(torch.randn(1000, 1, 28, 28), torch.randint(0, 10, (1000,)))
val_data = TensorDataset(torch.randn(200, 1, 28, 28), torch.randint(0, 10, (200,)))
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
val_loader = DataLoader(val_data, batch_size=64)
# trainer = TensorBoardTrainer(model, train_loader, val_loader, config)
# trainer.train(epochs=20)
TensorBoardの起動
# TensorBoardサーバーを起動
tensorboard --logdir=runs --port=6006
# ブラウザで http://localhost:6006 にアクセス
Weights & Biases
基本的な使い方
import wandb
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
# W&Bの初期化
wandb.init(
project="my-ml-project",
name="experiment-1",
config={
"learning_rate": 0.001,
"epochs": 100,
"batch_size": 64,
"architecture": "MLP",
"dataset": "MNIST",
}
)
# スカラー値のロギング
for step in range(100):
loss = 1.0 / (step + 1)
accuracy = 1 - loss
wandb.log({
"loss": loss,
"accuracy": accuracy,
"step": step
})
wandb.finish()
完全な訓練ループ
import wandb
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
def train_with_wandb():
# W&Bの初期化
config = {
"learning_rate": 0.001,
"epochs": 20,
"batch_size": 64,
"hidden_size": 256,
"dropout": 0.2,
}
run = wandb.init(
project="mnist-experiments",
config=config,
tags=["baseline", "mlp"]
)
# configはwandb.configからアクセス
config = wandb.config
# モデル定義
model = nn.Sequential(
nn.Flatten(),
nn.Linear(784, config.hidden_size),
nn.ReLU(),
nn.Dropout(config.dropout),
nn.Linear(config.hidden_size, 10)
)
# W&Bでモデルを監視
wandb.watch(model, log="all", log_freq=100)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=config.learning_rate)
# ダミーデータ
train_data = TensorDataset(
torch.randn(1000, 1, 28, 28),
torch.randint(0, 10, (1000,))
)
val_data = TensorDataset(
torch.randn(200, 1, 28, 28),
torch.randint(0, 10, (200,))
)
train_loader = DataLoader(train_data, batch_size=config.batch_size, shuffle=True)
val_loader = DataLoader(val_data, batch_size=config.batch_size)
# 訓練ループ
for epoch in range(config.epochs):
model.train()
train_loss = 0
train_correct = 0
train_total = 0
for data, target in train_loader:
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
train_loss += loss.item()
pred = output.argmax(dim=1)
train_correct += (pred == target).sum().item()
train_total += target.size(0)
# 検証
model.eval()
val_loss = 0
val_correct = 0
val_total = 0
with torch.no_grad():
for data, target in val_loader:
data, target = data.to(device), target.to(device)
output = model(data)
loss = criterion(output, target)
val_loss += loss.item()
pred = output.argmax(dim=1)
val_correct += (pred == target).sum().item()
val_total += target.size(0)
# メトリクスをロギング
wandb.log({
"epoch": epoch,
"train/loss": train_loss / len(train_loader),
"train/accuracy": train_correct / train_total,
"val/loss": val_loss / len(val_loader),
"val/accuracy": val_correct / val_total,
})
# モデルの保存
torch.save(model.state_dict(), "model.pt")
wandb.save("model.pt")
wandb.finish()
# train_with_wandb()
画像とテーブルのロギング
import wandb
import numpy as np
import matplotlib.pyplot as plt
wandb.init(project="visualization-demo")
# 画像のロギング
images = np.random.rand(16, 32, 32, 3)
wandb.log({
"images": [wandb.Image(img, caption=f"Image {i}") for i, img in enumerate(images[:4])]
})
# matplotlibの図
fig, ax = plt.subplots()
ax.plot(np.sin(np.linspace(0, 10, 100)))
ax.set_title("Sine Wave")
wandb.log({"sine_wave": wandb.Image(fig)})
plt.close(fig)
# テーブルのロギング
data = [
["cat", 0.95, 0.98],
["dog", 0.90, 0.92],
["bird", 0.85, 0.88],
]
table = wandb.Table(columns=["class", "precision", "recall"], data=data)
wandb.log({"metrics_table": table})
# 混同行列
confusion_matrix = np.random.randint(0, 100, (5, 5))
wandb.log({
"confusion_matrix": wandb.plot.confusion_matrix(
y_true=[0, 1, 2, 3, 4] * 20,
preds=np.random.randint(0, 5, 100).tolist(),
class_names=["A", "B", "C", "D", "E"]
)
})
wandb.finish()
ハイパーパラメータスイープ
import wandb
# スイープ設定
sweep_config = {
'method': 'bayes', # 'grid', 'random', 'bayes'
'metric': {
'name': 'val/accuracy',
'goal': 'maximize'
},
'parameters': {
'learning_rate': {
'min': 0.0001,
'max': 0.01,
'distribution': 'log_uniform_values'
},
'hidden_size': {
'values': [64, 128, 256, 512]
},
'dropout': {
'min': 0.0,
'max': 0.5
},
'batch_size': {
'values': [32, 64, 128]
}
}
}
def train_sweep():
"""スイープ用の訓練関数"""
run = wandb.init()
config = wandb.config
# モデル構築
model = nn.Sequential(
nn.Flatten(),
nn.Linear(784, config.hidden_size),
nn.ReLU(),
nn.Dropout(config.dropout),
nn.Linear(config.hidden_size, 10)
)
# ... 訓練ループ ...
# 最終的な精度をログ
wandb.log({"val/accuracy": 0.95}) # 実際の値に置き換え
# スイープの作成と実行
# sweep_id = wandb.sweep(sweep_config, project="sweep-demo")
# wandb.agent(sweep_id, train_sweep, count=20)
Artifacts(モデルとデータの管理)
import wandb
import torch
wandb.init(project="artifacts-demo")
# モデルをアーティファクトとして保存
model = nn.Linear(10, 5)
torch.save(model.state_dict(), "model.pt")
artifact = wandb.Artifact("my-model", type="model")
artifact.add_file("model.pt")
wandb.log_artifact(artifact)
# データセットをアーティファクトとして保存
data_artifact = wandb.Artifact("my-dataset", type="dataset")
data_artifact.add_dir("data/")
wandb.log_artifact(data_artifact)
# アーティファクトの使用
artifact = wandb.use_artifact("my-model:latest")
artifact_dir = artifact.download()
wandb.finish()
TensorBoard vs W&B
比較表
| 機能 | TensorBoard | Weights & Biases |
|---|---|---|
| 価格 | 無料 | 個人無料、チーム有料 |
| ホスティング | ローカル | クラウド |
| スカラー | あり | あり |
| ヒストグラム | あり | あり |
| 画像 | あり | あり |
| モデルグラフ | あり | あり |
| ハイパラスイープ | なし(外部ツール) | 組み込み |
| 実験比較 | 基本的 | 高度 |
| コラボレーション | 難しい | 容易 |
| オフライン | 可能 | 可能(同期は後) |
使い分けの指針
TensorBoardを選ぶ場合: – ローカルでの開発・デバッグ – シンプルなプロジェクト – コスト重視
W&Bを選ぶ場合: – チームでの共同作業 – ハイパーパラメータ最適化 – 実験の長期管理
両方を使う
import wandb
from torch.utils.tensorboard import SummaryWriter
class DualLogger:
"""TensorBoardとW&Bの両方にロギング"""
def __init__(self, config, project_name, run_name):
self.tb_writer = SummaryWriter(f'runs/{run_name}')
wandb.init(project=project_name, name=run_name, config=config)
def log_scalar(self, name, value, step):
self.tb_writer.add_scalar(name, value, step)
wandb.log({name: value}, step=step)
def log_scalars(self, main_tag, tag_scalar_dict, step):
self.tb_writer.add_scalars(main_tag, tag_scalar_dict, step)
wandb.log({f"{main_tag}/{k}": v for k, v in tag_scalar_dict.items()}, step=step)
def log_histogram(self, name, values, step):
self.tb_writer.add_histogram(name, values, step)
wandb.log({f"histogram/{name}": wandb.Histogram(values.cpu().numpy())}, step=step)
def close(self):
self.tb_writer.close()
wandb.finish()
まとめ
本記事では、実験トラッキングツールについて解説しました。
- TensorBoard: ローカルで動作、PyTorchと統合、無料
- Weights & Biases: クラウドベース、ハイパラスイープ、チーム機能
- ロギング対象: メトリクス、ハイパーパラメータ、画像、モデル構造
- ベストプラクティス: 一貫した命名規則、定期的なロギング、コードのバージョン管理
実験トラッキングは、機械学習プロジェクトの再現性と効率を大きく向上させます。
次のステップとして、以下の記事も参考にしてください。