メインコンテンツへスキップ

10.2.4 画像分類の学習テクニック

この節の位置づけ

画像分類のプロジェクトは、モデルを変えればすぐにうまくいくわけではありません。多くの場合、実際に結果を左右するのは学習の細かい部分です。たとえば、データ拡張が適切か、学習率が安定しているか、検証用データが信頼できるか、エラーサンプルを分析しているか、などです。

学習目標

  • 学習が収束しない、過学習する、学習不足になる、といったよくある原因を判断できる
  • 学習率、batch size、データ拡張、正則化の役割を理解する
  • クラス不均衡やデータリークが分類結果にどう影響するかを知る
  • エラーサンプルの分析を次の改善につなげられる

まずは学習問題の全体図を見る

一、学習率は最初に確認するつまみ

学習率が大きすぎると、loss が上下に振れたり、発散したりすることがあります。逆に学習率が小さすぎると、学習がとても遅くなり、モデルが何も学べていないように見えます。初心者のうちは、まずよくあるデフォルト値から始めて、学習曲線を観察するとよいです。

まずはフレームワークに結びつける前に、スケジュールの考え方を小さく動かしてみましょう。次の例は、よくある StepLR の考え方を模擬しています。数 epoch は学習率を保ち、その後 gamma を掛けます。

initial_lr = 1e-3
step_size = 5
gamma = 0.1

for epoch in [1, 5, 6, 10, 11]:
lr = initial_lr * (gamma ** ((epoch - 1) // step_size))
print(f"epoch={epoch:02d} lr={lr:.5f}")

実行結果の例:

epoch=01 lr=0.00100
epoch=05 lr=0.00100
epoch=06 lr=0.00010
epoch=10 lr=0.00010
epoch=11 lr=0.00001

学習 loss も検証 loss も高い場合は、学習不足か学習率が合っていない可能性があります。学習 loss が低いのに検証 loss が高い場合は、たいてい過学習か、データ分割に問題があります。

二、データ拡張は実際の場面に合わせる

データ拡張は、多ければ多いほどよいわけではありません。現実世界で起こりうる変化をうまく再現することが大切です。猫と犬の分類なら左右反転は使えますが、数字認識で 180 度回転を自由に行うと意味が変わることがあります。医療画像でも、撮影のルールに合わない拡張は避けるべきです。

augmentation_policy = [
{"name": "RandomResizedCrop", "label_safe": True, "reason": "対象は通常まだ認識できる"},
{"name": "HorizontalFlip", "label_safe": True, "reason": "左右方向がラベルの一部ではない"},
{"name": "Rotate180", "label_safe": False, "reason": "数字や向きに敏感なラベルを変える可能性がある"},
]

for rule in augmentation_policy:
status = "use" if rule["label_safe"] else "avoid"
print(f"{status}: {rule['name']} - {rule['reason']}")

実行結果の例:

use: RandomResizedCrop - 対象は通常まだ認識できる
use: HorizontalFlip - 左右方向がラベルの一部ではない
avoid: Rotate180 - 数字や向きに敏感なラベルを変える可能性がある

拡張の基本原則は、学習用データだけに拡張を使い、検証用データにはランダム拡張を使わないことです。また、拡張後もラベルの意味が保たれている必要があります。さらに、拡張した画像は数枚でもよいので、手で確認すると安心です。

三、過学習と学習不足の見分け方

現象可能な原因優先して行う対応
学習も検証も悪いモデルが弱い、学習不足、学習率の問題学習回数を増やす、学習率を調整する、backbone を変える
学習は良いが検証が悪い過学習、データ不足、拡張不足拡張を強くする、正則化、早期終了、データ追加
学習が大きく揺れるbatch が小さすぎる、学習率が大きい学習率を下げる、batch を増やす、データを確認する
検証スコアが異常に高いデータリーク重複画像や、同じ対象が両方の集合に入っていないか確認する

画像分類の学習診断マトリクス

図の見方

この図は、学習の問題を「データ」「学習」「評価」の3つに分けています。分類結果がよくないときは、すぐにモデルを変えるのではなく、まず loss 曲線、検証データのリーク、クラス不均衡、エラーサンプルを確認しましょう。

四、クラス不均衡は混同行列で見る

クラス不均衡があると、accuracy は簡単にだまされます。たとえば、95% の画像が正常サンプルなら、モデルがすべて正常と予測するだけで 95% の accuracy が出ます。しかし、そのモデルは異常をまったく識別できません。

labels = ["normal", "scratch", "stain"]
y_true = ["normal", "normal", "scratch", "scratch", "stain", "stain"]
y_pred = ["normal", "normal", "normal", "scratch", "normal", "stain"]

index = {label: i for i, label in enumerate(labels)}
matrix = [[0 for _ in labels] for _ in labels]

for truth, pred in zip(y_true, y_pred):
matrix[index[truth]][index[pred]] += 1

print("confusion_matrix:")
for label, row in zip(labels, matrix):
print(label, row)

print("\nrecall_by_class:")
for label, row in zip(labels, matrix):
recall = row[index[label]] / sum(row)
print(label, round(recall, 2))

実行結果の例:

confusion_matrix:
normal [2, 0, 0]
scratch [1, 1, 0]
stain [1, 0, 1]

recall_by_class:
normal 1.0
scratch 0.5
stain 0.5

クラス不均衡への対策としては、再サンプリング、class weight、focal loss、少数クラスのデータ追加などが考えられます。どの方法を選ぶかは、少数クラスのサンプルがどれだけ信頼できるかによって変わります。

五、エラーサンプルの分析

学習のたびに、少なくとも 20 個のエラーサンプルを確認しましょう。それらをいくつかの種類に分けます。たとえば、ラベルミス、画像品質が悪い、クラスの境界があいまい、モデルが見ている場所がずれている、学習データに似たサンプルが少なすぎる、などです。エラーサンプルの分析は、むやみにモデルを変えるよりも、次の改善につながります。

六、最小限の学習記録テンプレート

README や実験記録には、次の項目を残しておくとよいです。データセットのバージョン、学習/検証の分け方、モデル構造、入力サイズ、拡張方法、学習率、batch size、epoch、最良の指標、混同行列、エラーサンプルの画像、次の予定です。

よくある誤解

1つ目の誤解は、accuracy だけを見てクラスごとの指標を見ないことです。2つ目の誤解は、検証用データにもランダム拡張を使ってしまうことです。3つ目の誤解は、同じ対象や同じ動画フレームが学習と検証の両方に入ってしまい、リークが起きることです。4つ目の誤解は、結果が悪いとすぐにモデルを変えてしまい、先にデータや学習曲線を確認しないことです。

練習

  1. 小さな分類モデルを学習させ、train loss と val loss の曲線を描いてみましょう。
  2. 同じモデルに対して、弱い拡張と強い拡張をそれぞれ使い、検証結果を比較しましょう。
  3. 混同行列を出力して、もっとも混同しやすい 2 つのクラスを見つけましょう。
  4. エラーサンプルを 10 枚整理し、それぞれに「原因の候補」を 1 文ずつ書きましょう。

合格基準

この節を学んだ後は、学習曲線からよくある問題を判断できること、適切なデータ拡張を設計できること、混同行列でクラスの問題を分析できること、そしてエラーサンプル分析を画像分類プロジェクトの README に書けることが目標です。