pytorch 実践(二)
この記事では、画像を用いた顔表情認識を例に、PyTorch を使った実践的なタスクの開発プロセスを一から解説します。
第二部となる今回は、PyTorch を使った実装的な技術ポイントを紹介します。
code の例:main.ipynb
プロジェクトアドレス:https://github.com/lijunjie2232/emotion_analyse_pytorch
目次
Tips
基本的な任務
要するに、画像のファイルには顔がある、その画像を読み込んて、pytorch モデルで顔表情を判断します。
- データ準備と画像前処理
- モデルアーキテクチャ設計
- モデルトレーニング
- モデル推論
データセットの割当り
データセットは、通常に以下の 3 つに分かれます。
-
訓練用(train)データセット
- モデルの学習に使用するデータ
- 通常に 8 割〜9 割のデータを訓練用として使用する。
-
検証用(validation)データセット
- モデルの性能を評価するデータ
- 通常に 1 割〜2 割のデータを検証用として使用する。
-
テスト用(test)データセット(可選択)
- テスト用データセット
- 通常に 1 割〜2 割のデータをテスト用として使用する。
- 通常に最後の評価として使用する。
testset と validationset の違い
- Validation Set は学習中の「チューニング用」であり、Test Set は「最終評価用」。
- Test Set を Validation として再利用すると、評価結果がバイアスされる。
目的の違い
| 項目 | Validation Set | Test Set |
|---|---|---|
| 目的 | モデルのハイパーパラメータ調整や早期停止の判断に利用。学習過程でモデル性能を監視し、過学習を防止。 | モデルの最終的な汎化性能を評価。学習・調整が完了した後の「未知のデータ」に対する性能を測定。 |
| 使用タイミング | 学習中に定期的に評価。例えば、Early Stopping や学習率の調整に活用。 | モデルの学習・調整が完全に終了した後のみ使用。 |
データの扱いの違い
- Validation Set:
- 学習プロセスに間接的に影響を与える(例: 検証損失が改善しなければ学習を停止)。
- ハイパーパラメータ(例: ニューロン数、学習率)の最適化に活用。
- Test Set:
- 学習プロセスに一切関与しない。
- 最終評価のみに使用されるため、「未知のデータ」として完全に独立している必要がある。
実装例
# データセットの分割例(train/val/test)
from torch.utils.data import random_split
# 全データを8:1:1に分割
train_size = int(0.8 * len(dataset))
val_size = int(0.1 * len(dataset))
test_size = len(dataset) - train_size - val_size
train_dataset, val_dataset, test_dataset = random_split(dataset, [train_size, val_size, test_size])
# データローダーの構築
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32)
test_loader = DataLoader(test_dataset, batch_size=32)
注意点
- データリークの防止:
- Test Set は学習・検証プロセスで絶対に使用しない。リークすると評価結果が過剰に楽観視される。
- サイズの目安:
- Validation Set: 全データの 10〜20%程度。
- Test Set: 同様に 10〜20%程度(タスクによって調整)。
実践的なワークフロー
- 学習: Train Set でモデルを学習。
- 検証: Val Set でハイパーパラメータ調整や Early Stopping の判定。
- 評価: Test Set で最終的な性能を測定(例: Accuracy, F1 Score)。
データの transoforms
torchvision.transforms は、PyTorch で画像データを処理するための一般的な変換(前処理・水増し)を行うモジュールです。主に torchvision.datasets と組み合わせて使用され、画像をテンソルに変換したり、正規化や拡張などの操作を行います。
主な特徴
- 画像変換:画像をテンソルに変換します。
- 正規化:画像のピクセル値を特定の範囲に正規化します。
- データ水増し(Data Augmentation):ランダムな回転・反転などを行い学習データを多様化します。
- パイプライン構築:複数の変換を順番に適用する処理を簡単に構成できます(
Composeを使用)。
よく使われるクラス一覧
| クラス名 | 機能 |
|---|---|
ToTensor() |
PIL 形式の画像を PyTorch の Tensor に変換(0〜255 → 0.0〜1.0) |
Normalize(mean, std) |
テンソル画像に対して平均 mean、標準偏差 std で正規化する |
Resize(size) |
画像を指定されたサイズにリサイズする |
CenterCrop(size) |
画像の中心部分を指定されたサイズに切り出す |
RandomHorizontalFlip(p=0.5) |
画像を確率 p で左右反転する(デフォルトは 50%) |
RandomRotation(degrees) |
画像をランダムに回転させる(角度は degrees 以内) |
RandomAffine(degrees, translate, scale, shear) |
画像をランダムに回転・平行移動・拡大縮小・傾斜させる |
RandomCrop(size) |
画像をランダムに切り出す |
使用例
from torchvision import transforms
transform = transforms.Compose([
transforms.Resize(256), # 256x256にリサイズ
transforms.CenterCrop(224), # 中心を224x224に切り抜き
transforms.ToTensor(), # Tensorに変換
transforms.Normalize( # 正規化
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
このようにして作成した transform は、ImageFolder や自作の Dataset クラスで画像に適用されます。
適用方法の例
## データ変換の構築
train_transformer = transforms.Compose(
[
transforms.Resize((256, 256)), # 画像を256x256にリサイズ
transforms.RandomCrop(224), # 224x224の範囲でランダムに切り抜き
transforms.RandomHorizontalFlip(), # 水平方向にランダムに反転(データ拡張)
transforms.RandomRotation(degrees=15),# 最大15度の範囲でランダムに回転
transforms.RandomVerticalFlip(), # 垂直方向にランダムに反転
transforms.ToTensor(), # PIL画像をテンソルに変換
transforms.Normalize( # 正規化
mean=[0.485, 0.456, 0.406], # ImageNetの平均値を使用
std=[0.229, 0.224, 0.225], # ImageNetの標準偏差を使用
),
]
)
val_transformer = transforms.Compose(
[
transforms.Resize((224, 224)), # 画像を224x224にリサイズ
transforms.ToTensor(), # PIL画像をテンソルに変換
transforms.Normalize( # 正規化
mean=[0.485, 0.456, 0.406], # ImageNetの平均値を使用
std=[0.229, 0.224, 0.225], # ImageNetの標準偏差を使用
),
]
)
## データセットの構築
train_dataset = ImageFolder(
root=data_root / "train" / "train", # 学習用データのルートディレクトリ
transform=train_transformer, # 学習用の変換を適用
)
val_dataset = ImageFolder(
root=data_root / "test" / "test", # 検証用データのルートディレクトリ
transform=val_transformer, # 検証用の変換を適用
)
モデルの保存と読み込み
Pytorch モデルの保存と読み込み
# モデル全体(構造+パラメータ)を保存
torch.save(model, 'model.pth')
# モデル全体を読み込み
model = torch.load('model.pth')
model.eval() # 推論モードに設定
Pytorch 状態辞書の保存と読み込み
モデルのパラメータ(重みとバイアス)を保存する際の推奨方法。モデル構造が変わった場合でも、同じ構造の新しいモデルにパラメータを読み込むことが可能になる。
# パラメータのみ保存
torch.save(model.state_dict(), 'model_state.pth')
# パラメータを読み込み(事前にモデル定義が必要)
model = MyModelClass() # モデルクラスのインスタンス化
model.load_state_dict(torch.load('model_state.pth'))
model.eval()
ベストプラクティス
- ファイル拡張子:
.pthまたは.ptを使用 - 推論時は必ず
model.eval()を呼び出す - デバイス指定の例:
# CPUで読み込み model.load_state_dict(torch.load('model_state.pth', map_location='cpu'))
主な注意点
state_dictの利点: モデル構造の変更に柔軟に対応可能(バージョン管理に適す)- モデル全体保存の制約: 再現性に依存(同じコード環境でしか使えない)
実践的な保存方法
チェックポイントとして保存
トレーニング状態を再開するために、モデルとオプティマイザの状態を一緒に保存する場合:
# チェックポイント保存例
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pth')
チェックポイントから読み込み
保存したチェックポイントを読み込み、トレーニングを再開する場合:
# チェックポイント読み込み
checkpoint = torch.load('checkpoint.pth')
# モデルとオプティマイザの状態を復元
model = MyModelClass() # 事前にモデル定義が必要
model.load_state_dict(checkpoint['model_state_dict'])
optimizer = torch.optim.Adam(model.parameters()) # オプティマイザを再構築
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
# その他の情報も復元可能
epoch = checkpoint['epoch']
loss = checkpoint['loss']
主な注意点
-
オプティマイザの再構築:
オプティマイザの状態を読み込むには、事前に同じアルゴリズム(例:Adam)とパラメータでオプティマイザを再構築する必要があります。 -
デバイス指定:
map_location='cpu'で保存したモデルを CPU で読み込むと、GPU の RAM を節約できる:model.load_state_dict(torch.load('checkpoint.pth', map_location='cpu')) -
strict=False オプション:
モデル構造が変更された場合に部分的に読み込み可能にする:model.load_state_dict(checkpoint['model_state_dict'], strict=False) -
学習再開時の注意:
読み込み後はmodel.train()を呼び出し、学習モードに戻す必要があります。
AMP を使った学習
AMP とは、自動混合精度(Automatic Mixed Precision)の略であり、PyTorch 1.6 以降から公式にサポートされる機能です。これは、モデルを浮動小数点数精度(通常は 32 ビットの float 型)と半精度で学習するようにします。つまり、モデルの計算を半精度で行い、最終的な出力を 32 ビットの float 型で出力します。この方法により、GPU のメモリ使用量を削減し、学習速度を向上させることができます。
概要
AMP は混合精度計算を自動化する技術で、FP32(単精度)と FP16(半精度)を動的に切り替えて計算効率を向上させます。
PyTorch では torch.cuda.amp モジュールが提供されており、以下の 2 つのコンポーネントで構成されます:
autocast: 計算を FP16 で実行できるか自動判定し、適切な精度を選択します。GradScaler: FP16 による勾配消失を防ぐため、損失(loss)をスケーリングして勾配更新を安定化させます。
優位性
- メモリ削減: FP16 は FP32 の半分のメモリを使用するため、バッチサイズを増やせる。
- 高速化: GPU の Tensor Cores を活用し、行列演算が高速化されます。
- エネルギー効率向上: 計算量の削減により消費電力が低下。
使用方法
以下は典型的な AMP トレーニングのコード例です:
from torch.cuda.amp import autocast, GradScaler
USE_AMP = True # AMP を使用するかどうかのフラグ
scaler = GradScaler() # グラジエントスケーラーを初期化
for data, target in dataloader:
optimizer.zero_grad()
with autocast(enabled=USE_AMP): # autocastコンテキスト内で順伝播
output = model(data)
loss = loss_fn(output, target)
scaler.scale(loss).backward() # スケーリングされた損失で逆伝播
scaler.step(optimizer) # オプティマイザのステップ
scaler.update() # スケーラーの更新
注意点
- サポートされるハードウェア: AMP は NVIDIA GPU(Tensor Cores 対応)で最大限の効果を発揮します。
- 数値不安定性: FP16 ではオーバーフロー/アンダーフローが発生する可能性があるため、
GradScalerが必要です。 - 非対応操作: 一部の演算(例: 損失関数の log など)は FP32 で実行されるため、パフォーマンス改善の効果が限定的な場合があります。
Early Stopping
概要
Early Stopping は、過学習(Overfitting)を防止するためのテクニックで、検証データ(Validation Data)の性能が改善しなくなった時点でトレーニングを自動的に停止します。
通常、検証損失(Validation Loss)や精度(Accuracy)を監視し、一定のエポック数(patience)改善が見られなければトレーニングを終了します。
優位性
- 過学習の防止: 検証データの性能が悪化した時点で停止し、モデルの汎化性能を維持。
- リソース効率化: 無駄なエポックを実行せず、計算時間とメモリを節約。
- 最適なモデル選択: 最良の検証性能を達成した時点のモデルを保存可能。
実装手順
以下に Early Stopping の基本的な実装フローを示します:
初期化
- 検証損失の最小値を保存する変数(
min_val_loss)を初期化。 - 改善が停止したカウンター(
counter)を初期化。
各エポックでの処理
- 学習データでモデルを更新。
- 検証データで損失と精度を評価。
- 検証損失が最小値を更新した場合:
min_val_lossを更新。- 最良のモデルパラメータを保存。
- カウンターをリセット。
- 検証損失が改善しない場合:
- カウンターをインクリメント。
- カウンターが
patienceに達した場合、トレーニングを停止。
コード例
import torch
from torch.utils.data import DataLoader
# パラメータ設定
patience = 5 # 改善が停止するまでのエポック数
min_val_loss = float('inf') # 最小検証損失の初期化
counter = 0 # 改善停止カウンター
# ダミーのデータローダー
train_loader = DataLoader(dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32)
# Early Stopping付きトレーニングループ
for epoch in range(100):
# 学習フェーズ
model.train()
for inputs, labels in train_loader:
outputs = model(inputs)
loss = loss_fn(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 検証フェーズ
model.eval()
val_loss = 0
with torch.no_grad():
for inputs, labels in val_loader:
outputs = model(inputs)
val_loss += loss_fn(outputs, labels).item()
val_loss /= len(val_loader)
# Early Stopping判定
if val_loss < min_val_loss:
min_val_loss = val_loss
counter = 0
torch.save(model.state_dict(), 'best_model.pth') # 最良モデルを保存
else:
counter += 1
if counter >= patience:
print(f"Early Stopping at epoch {epoch+1}")
break
主な注意点
patienceの設定: 小さすぎると学習が早すぎる段階で停止する可能性があり、大きすぎると過学習のリスクが生じます。- 検証指標の選定: 検証損失ではなく精度や F1 スコアなど、タスクに合った指標を使用することも可能です。
- モデル保存のタイミング: 最良のモデルを保存しておき、推論時に読み込みます。
- ランダム性の影響: シード値(Seed)を固定することで再現性を確保。
pytorchlighting の応用
PyTorch Lightning や ignite には組み込みの Early Stopping 機能があります。
# PyTorch Lightningの例
from pytorch_lightning.callbacks import EarlyStopping
early_stop = EarlyStopping(monitor="val_loss", patience=5)
trainer = Trainer(callbacks=[early_stop])
トレニングの並行化
-
PyTorch の
DataParallelは、単一ノード内の複数 GPU を利用してモデルを並列化するためのクラスです。データ並列化(Data Parallelism)を実現し、学習速度の高速化やバッチサイズの拡大に役立ちます。 -
PyTorch の
DistributedDataParallel(DDP) は、複数の GPU または複数ノードでモデルを分散して学習するためのフレームワークです。データ並列処理を実現し、大規模なモデルやデータセットの学習を効率化します。 -
DataParallelとDistributedDataParallelもは,GPU を利用してモデルを並列処理するためのフレームワークですが、主な違いは、通信のオーバーヘッドや分散学習の設定方法にあります。通常、大規模システムで複数ノードを使用する場合
DDP を使用したモデルの並列化
今回の実装例は、DDPを使用して複数 GPU でモデルを並列化するため、torch.nn.parallel.DistributedDataParallelを説明、ほかのてんは別の文章で説明する.
- 目的:
複数の GPU/ノードでモデルの並列化を行い、学習速度の高速化とメモリ負荷の分散を実現。 - 特徴:
- 各 GPU に独立したプロセスを生成し、データを分割して並列処理。
- 勾配の同期(AllReduce)により、分散環境でも正確な更新を行う。
torch.distributedパッケージを基盤に動作。
使う手順
分散環境の初期化
import torch.distributed as dist
# 分散プロセスグループの初期化
dist.init_process_group(backend='nccl') # backend は 'nccl' または 'gloo' など
# nccl は NVIDIA GPU 用、gloo は CPU/多種の環境用、mpi もあります.
モデルのラップ
from torch.nn.parallel import DistributedDataParallel as DDP
# モデルを DDP でラップ
model = DDP(model, device_ids=[local_rank]) # local_rank は GPU のローカル ID
データローダーの設定
from torch.utils.data.distributed import DistributedSampler
# 分散用サンプラーを使用
sampler = DistributedSampler(dataset)
train_loader = DataLoader(dataset, batch_size=32, sampler=sampler)
学習ループ
for epoch in range(epochs):
model.train()
for inputs, labels in train_loader:
inputs, labels = inputs.cuda(), labels.cuda()
outputs = model(inputs)
loss = loss_fn(outputs, labels)
# 勾配計算と更新
optimizer.zero_grad()
loss.backward()
optimizer.step()
終了処理
# 分散プロセスグループの終了
dist.destroy_process_group()
torchrun を使用した実行
torchrun を使用することで、PyTorch のモデルを分散プロセスグループで実行することができます。以下は torchrun を使用してモデルを実行するための基本的なコマンドの例です。
主なオプション
--nproc_per_node: 1 ノードあたりのプロセス数を指定。--nnodes: 使用するノード数を指定。--start_method: プロセスの起動方法を指定、デフォルトは spawn、fork はプロセスを fork する方法で起動します。--log_dir: ログ出力先のディレクトリを指定。--master_addr: マスターノードのアドレスを指定。--master_port: マスターノードのポートを指定。--monitor_interval: ログの出力間隔を秒単位で指定。--run_path: 実行ファイルのパスを指定。
実行例:
torchrun --nproc_per_node=2 --master_port=23450 train.py --batch_size=32 --epochs=100 --lr=0.001