どうもニコイチです。前回の「ResNetを使った転移学習なしのモデルの選定と作成」では、転移学習を使わないモデルをどのように選び、構築するかを学びました。今回はいよいよそのモデルを実際に学習させる手順を確認していきます。モデルの学習はやや複雑そうに見えますが、やっていることをかみ砕いてみると「間違いを見つけてパラメータを直し、また試してみる」という試行錯誤の繰り返しです。ここでしっかり理解しておけば、次の「転移学習ありのモデル」と比較する際にも「なぜ転移学習は便利なのか」を実感しやすくなります。
学習の全体像
学習フェイズと検証フェイズ
学習を行ううえで重要になるのは、以下の2つのフェイズを意識することです。
- 学習(train)フェイズ
- 誤差逆伝播(バックプロパゲーション)を用いてパラメータを更新する
- バッチサイズごとに入力データを読み込み、損失(loss)を計算し、パラメータを少しずつ修正する
- 検証(val)フェイズ
- 学習で更新したパラメータが、未知のデータ(厳密には学習に使わなかった検証用データ)でどのくらい正しく分類できるかを評価する
- パラメータの更新は行わない(推論モードで動作させる)
学習フェイズと検証フェイズを1エポック(epoch) につきそれぞれ1回ずつ実施し、これを設定したエポック数だけ繰り返します。「1エポック」とは「学習用データ全体を1巡すること」を指します。
たとえば学習データが120枚あってバッチサイズが4の場合は、学習フェイズ中に「120 ÷ 4 = 30回」バッチを処理することになります。検証データが80枚でバッチサイズが4なら、検証フェイズ中は「80 ÷ 4 = 20回」バッチを処理します。
学習関数(train_model)の流れ
ここでは、学習の流れを一括でまとめるためにtrain_model()
という関数を作ってみます。以下は主な処理のステップです。
def train_model(model, criterion, optimizer, num_epochs=5, is_saved=False):
best_acc = 0.0
# エポック数だけ下記工程を繰り返し
for epoch in range(num_epochs):
# 'train' と 'val' の2種類のフェイズ
for phase in ['train', 'val']:
print(f"{phase}:フェイズ")
# フェイズに応じてモードを切り替え
if phase == 'train':
model.train() # 学習モード
else:
model.eval() # 推論モード(検証モード)
running_loss = 0.0
running_corrects = 0
# データローダからバッチサイズずつデータを取得
for i, (inputs, labels) in enumerate(image_dataloaders[phase]):
inputs = inputs.to(DEVICE)
labels = labels.to(DEVICE)
# 1. optimizerの勾配初期化
optimizer.zero_grad()
# 2. モデルへ入力し、出力を取得
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
# 3. 損失(loss)を計算
loss = criterion(outputs, labels)
# 学習フェイズのみ勾配計算とパラメータ更新を行う
if phase == 'train':
# 4. 誤差逆伝播の実行
loss.backward()
# 5. パラメータの更新
optimizer.step()
# 統計用の合計値を更新
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
# 1エポックが終了したら平均lossとaccuracyを計算
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() / dataset_sizes[phase]
print(f"{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}")
# 検証フェイズ(val)で過去最高の精度を更新した場合はモデルを保存
if phase == 'val' and epoch_acc > best_acc:
best_acc = epoch_acc
if is_saved:
torch.save(model.state_dict(), f'./original_model_{epoch}.pth')
print(f'Best val Acc: {best_acc:.4f}')
ポイントとなるのは以下の流れです。
- optimizer.zero_grad()
前のバッチでの勾配をリセット - model(inputs)
入力をモデルに通して出力を得る - loss = criterion(outputs, labels)
出力と正解ラベルを用いて損失(loss)を算出 - loss.backward()
誤差逆伝播(バックプロパゲーション)で勾配を計算(学習フェイズ時のみ) - optimizer.step()
計算された勾配をもとにパラメータを更新(学習フェイズ時のみ)
これをバッチごとに繰り返し、さらにエポック数分だけ繰り返すことで、モデルが徐々に精度を上げていきます。
学習ログを可視化する意義
学習の途中で得られるlossやaccuracyの数値を記録し、グラフ化しておくといろいろなことが読み取れます。たとえば、
- 学習データ(train)のlossが下がり続けているのに、検証データ(val)のlossが下がらない場合は過学習の恐れがある
- ある程度学習を回すとlossが下げ止まる → そのエポック数前後で学習を打ち切ってもよい
など、学習効率を考える際にとても役立ちます。特にエポック数を無闇に増やすと時間がかかりすぎたり、過学習になったりするので、「グラフで様子を見ながら学習を止める」という判断もよく行われます。
学習ログ例(転移学習なし)
たとえば次のような条件で学習した結果、最終的に約97.5%の精度が得られたというケースもあります。
- モデル:ResNet18
- 転移学習:無
- データ量:学習データ 6633枚 / 検証データ 715枚
- optimizer:SGD(lr=0.001, momentum=0.9)
- loss関数:CrossEntropyLoss
- バッチサイズ:32
- エポック:100
学習の過程を可視化すると、40エポック前後でlossやaccuracyがほぼ落ち着いてしまうことが確認できます。こういった場合は40エポック程度で学習を打ち切るのも一つの判断です。
「エポック数は多ければ多いほど良い」というわけではなく、必要以上に長く学習させると時間や計算資源がもったいないだけでなく、過学習リスクが高まります。
大切なのは、学習曲線を眺めながら最適と思われるタイミングを探ることです。
理解度チェック(テスト)
以下の中で誤っているものを1つ選んでください。
- 転移学習なしで構築したモデルでも、学習ログを可視化することで過学習の有無や最適なエポック数の目安を確認できる。
- 学習フェイズ(train)と検証フェイズ(val)を分けることで、実際に未知のデータにどの程度汎化できるのかを把握できる。
- 学習する場合のエポックは多ければ多いほど必ず良い結果が得られるため、100回以上に設定するのが適切である。
回答
3.「学習する場合のエポックは多ければ多いほど必ず良い結果が得られるため、100回以上に設定するのが適切である」
【解説】
「エポックを増やしすぎれば絶対に良くなる」というわけではありません。たとえば今回のケースでは40エポック程度で精度がほぼ収束していました。エポックを増やしても改善が見込めないばかりか、過学習のリスクが高まるおそれもあります。適度なエポック数を見極めることが大切です。
まとめと次回予告
- 転移学習なしのモデルは、一からパラメータを更新していくため、学習データや計算リソースを比較的多く必要とします。
- しかし、学習の手順自体は「(1) データを読み込む → (2) 損失を求める → (3) 勾配を計算してパラメータを更新する」という流れの繰り返しです。
- 適切なエポック数やバッチサイズの設定をすることで、無理なくモデルの性能を高められます。
私たちはこのように、一歩ずつ試行錯誤をくり返すことで**「必ず前進・成長していける」**と信じています。焦らず一歩ずつ理解を深めていきましょう。
次回は 「転移学習なしの学習済みモデルでの推論」 です。学習で得られたモデルを用いて、実際に欠陥の有無をどのように推論(判定)するのかを見ていきますので、ぜひお楽しみに!
コメント