カスタムデータセットでYOLOをファインチューニングする方法
ファインチューニングでは、ランダムな初期化ではなく、学習済みの重みを使って事前学習済みモデルを新しいクラスの認識に適応させます。数百エポックにわたってゼロから学習する代わりに、事前学習済みのCOCOの特徴を再利用することで、カスタムデータに対してわずかな時間で収束します。
このガイドでは、基本的な使い方からレイヤーの凍結や2段階トレーニングなどの高度な手法まで、カスタムデータセットでYOLO26をファインチューニングする方法を説明します。
ファインチューニングとゼロからのトレーニングの比較#
事前学習済みモデルは、数百万枚の画像から、エッジ検出、テクスチャ認識、形状理解などの一般的な視覚特徴をすでに学習しています。ファインチューニングによる転移学習では、その知識を再利用し、新しいクラスの外観だけをモデルに学習させるため、より速く収束し、必要なデータ量も少なくなります。ゼロからトレーニングする場合は、そうした知識をすべて破棄し、ピクセルレベルのパターンからすべてを学習させるため、はるかに多くのリソースが必要です。アーキテクチャのみを定義する.yamlファイルとチェックポイントの違いについては、モデルYAML設定ガイドをご覧ください。
| ファインチューニング | ゼロからのトレーニング | |
|---|---|---|
| 開始時の重み | COCO(80クラス)で事前学習済み | ランダム初期化 |
| コマンド | YOLO("yolo26n.pt") | YOLO("yolo26n.yaml") |
| 収束 | より速い - バックボーンがすでに学習済み | より遅い - すべてのレイヤーをゼロから学習 |
| データ要件 | 少ない - 事前学習済みの特徴がデータ不足を補う | 多い - データセットだけを使ってすべての特徴を学習する必要がある |
| 使用する場面 | 自然画像のカスタムクラス | COCOとは根本的に異なるドメイン(医療、衛星、レーダー) |
.pt ファイルを YOLO("yolo26n.pt") で読み込むと、事前学習済みの重みがモデルに格納されます。その後 .train(data="custom.yaml") を呼び出すと、互換性のあるすべての重みが新しいモデルアーキテクチャに自動的に転送され、一致しないレイヤー(クラス数が異なる場合の最終分類レイヤーなど)は再初期化されて、トレーニングが始まります。手動での重みの読み込み、レイヤー操作、カスタムの転移学習コードは必要ありません。
事前学習済み重みの転送の仕組み#
クラス数が異なるデータセット(たとえば、COCOの80クラスからカスタムの5クラス)で事前学習済みモデルをファインチューニングする場合、Ultralyticsは形状を考慮した重み転送を行います。
- バックボーンとネックは完全に転送されます - これらのレイヤーは一般的な視覚特徴を抽出し、その形状はクラス数に依存しません。
- 検出ヘッドの一部が再初期化されます - 各分類ブランチ(
cv3、one2one_cv3)の最終畳み込み層のみ、クラス数(80対5)に応じて形状が変わります。両方のモデルでスケールが同じ場合、ブランチの隠れ層は転送されます。一致するクラス名の互換性のある行は、未一致の行を初期化する前に再マッピングされます。ヘッド内のバウンディングボックス回帰レイヤー(cv2、one2one_cv2)は、クラス数に関係なく形状が固定されているため、通常どおり転送されます。 - クラス数を変更しても、重みの大部分は転送されます。たとえば、COCO(80クラス)で学習したYOLO26nを5クラスのデータセットにファインチューニングする場合、708個の重みテンソルのうち696個に加え、名前が一致する互換性のある分類行が転送されます。
データセットのクラス数が事前学習済みモデルと同じ場合(たとえば、COCOで事前学習した重みを別の80クラスデータセットでファインチューニングする場合)、検出ヘッドを含むすべての重みが転送されます。
別名を使ったクラスの転送#
Ultralyticsは、データセット間でクラス名が一致する分類ヘッドの行を転送します。この処理では、大文字と小文字の違いや前後の空白は無視されます。同等のクラスに異なる名前が付いている場合は、読み込み前にメモリ上でソースチェックポイントのクラス名を変更してください。これにより、名前が一致しないものとして扱われてランダムに初期化されるはずだった、共通概念の事前学習済み分類重みを維持できます。
このObjects365 v2からCOCOへの例では、読み込んだチェックポイントのソースクラス名を変更し、その名前をtrain()が事前学習済み重みとして引き継ぎます。
from ultralytics import YOLO
# ソースのObjects365 v2名(小文字) -> ターゲットのCOCO名
ALIASES = {
"wild bird": "bird",
"handbag/satchel": "handbag",
"luggage": "suitcase",
"bowl/basin": "bowl",
"orange/tangerine": "orange",
"monitor/tv": "tv",
"stuffed toy": "teddy bear",
"hair dryer": "hair drier",
}
model = YOLO("path/to/yolo26s-objects365.pt")
# Objects365のクラス名は各単語の先頭が大文字のため、小文字に変換した名前で照合します
model.model.names = {i: ALIASES.get(name.lower(), name) for i, name in model.model.names.items()}
model.train(data="coco.yaml", epochs=100, imgsz=640)cls_remap設定では、名前に基づくこの転送がデフォルトで有効になります。互換性のある行がコピーされると、トレーニング時にRemapped N/M cls head rows from pretrained weights by class nameが出力されます。無効にするには、cls_remap=Falseを設定してください。
ファインチューニングの基本例#
from ultralytics import YOLO
model = YOLO("yolo26n.pt") # 事前学習済みモデルを読み込む
model.train(data="custom.yaml", epochs=50, imgsz=640)モデルサイズの選択#
大きなモデルは表現力が高い一方で、更新するパラメーターも多く、学習データが限られている場合は過学習のリスクが高まる可能性があります。まず小さなモデル(YOLO26nまたはYOLO26s)から始め、検証指標が頭打ちになった場合にのみ規模を拡大するのが実用的な方法です。最適なモデルサイズは、タスクの複雑さ、クラス数、データセットの多様性、デプロイに利用できるハードウェアによって異なります。
オプティマイザーと学習率の選択#
デフォルトのoptimizer=auto設定では、トレーニングの総反復回数に基づいてオプティマイザーと学習率が選択されます。
- 反復回数が10,000回以下(小規模なデータセットまたは少ないエポック数):AdamWと、自動計算される低い学習率
- 10,000イテレーション超(大規模データセット): MuSGD(MuonとSGDのハイブリッドオプティマイザー)をlr=0.01で使用します(MuSGD)。
ほとんどのファインチューニングタスクでは、デフォルト設定で十分な結果が得られ、手動でのチューニングは不要です。次の場合は、オプティマイザーを明示的に設定することを検討してください。
- トレーニングが不安定な場合(損失が急増する、または発散する):より安定した収束を得るために、
optimizer=AdamW, lr0=0.001を試してください。 - 小規模なデータセットで大規模モデルをファインチューニングする場合:
optimizer=AdamW, lr0=0.001など、学習率を低くしたオプティマイザーを明示的に指定すると、事前学習済みの特徴を維持しやすくなります。
optimizer=autoの場合、lr0の値は無視されます。学習率を手動で制御するには、オプティマイザーを明示的に設定してください:optimizer=SGD, lr0=0.005。
レイヤーの凍結#
レイヤーを凍結すると、トレーニング中に特定のレイヤーが更新されなくなります。これにより、データセットの規模がモデルの能力に比べて小さい場合に、トレーニングを高速化し、過学習を抑制できます。
freezeパラメーターには、整数またはリストを指定できます。整数のfreeze=10は最初の10レイヤー(インデックス0~9)を凍結し、YOLO26バックボーンの大部分が対象になります。バックボーンはレイヤー0~10にわたるため、freeze=10では最後のC2PSAブロック(レイヤー10)が学習可能なまま残ります。バックボーン全体を凍結するには、freeze=11を使用してください。リストには、バックボーンの一部を凍結するfreeze=[0, 3, 5]のようなレイヤーインデックスや、レイヤー内の特定の分岐を細かく制御するfreeze=["23.cv2", "23.one2one_cv2"]のようなモジュール名文字列を指定できます(この例では検出ヘッドの両方のボックス回帰分岐が対象です)。
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="custom.yaml", epochs=50, freeze=10)適切な凍結深度は、対象ドメインが事前学習データにどの程度類似しているか、また利用できる学習データがどの程度あるかによって異なります。
| シナリオ | 推奨事項 | 根拠 |
|---|---|---|
| 大規模データセット、類似ドメイン | freeze=None(デフォルト) | 過学習を起こさずにすべてのレイヤーを適応させるのに十分なデータがあります |
| 小規模データセット、類似ドメイン | freeze=10 | バックボーンの特徴を維持し、学習可能なパラメーターを削減します |
| 非常に小規模なデータセット | freeze=23 | 検出ヘッドのみを学習させ、過学習のリスクを最小限に抑えます |
| COCOと大きく異なるドメイン | freeze=None | バックボーンの特徴がうまく転移せず、再学習が必要な可能性があります |
凍結する層の深さもハイパーパラメーターとして扱えます。いくつかの値(0、5、10)を試して検証mAPを比較する方法は、特定のデータセットに最適な設定を見つける実用的な手段です。
ファインチューニングの主なハイパーパラメーター#
ファインチューニングでは通常、スクラッチから学習する場合ほど多くのハイパーパラメーター調整は必要ありません。特に重要なパラメーターは次のとおりです。
epochs: ファインチューニングはスクラッチからの学習より速く収束します。中程度の値から始め、検証指標が頭打ちになったらpatienceを使用して早期に停止します。patience: デフォルトの100は、長時間の学習を想定した設定です。これを10~20に減らすと、すでに収束した学習に時間を費やすことを避けられます。warmup_epochs: ウォームアップでは、最初の数エポックにわたって学習率をスケジュールされた値まで徐々に引き上げるため、初期のバッチが事前学習済みの特徴を乱す可能性を抑えられます。ファインチューニング時はゼロ以外に設定しますが、デフォルトの3エポックすべては必要ありません。Objects365の重みから複数エポック継続して学習する公式YOLO26 COCOファインチューニングの基盤となった進化的探索では、すべてのモデルサイズで約1エポックに落ち着きました。
学習パラメーターの全リストについては、学習設定のリファレンスをご覧ください。レイヤーごとの学習率、勾配クリッピング、カスタム検証指標など、パラメーターでは設定できない動作については、トレーナーをサブクラス化してください。
2段階ファインチューニング#
2段階ファインチューニングでは、学習を2つのフェーズに分けます。第1段階ではバックボーンを凍結し、ネックとヘッドのみを学習させます。これにより、事前学習済みの特徴を損なわずに、検出レイヤーを新しいクラスに適応させることができます。第2段階ではすべてのレイヤーの凍結を解除し、より低い学習率でモデル全体を学習させて、対象ドメインに合わせてバックボーンを調整します。
この方法は、対象ドメインがCOCOと大きく異なる場合(医療画像、航空画像、顕微鏡画像など)に特に有効です。このようなケースではバックボーンの適応が必要になる一方、すべてを一度に学習すると不安定になる可能性があります。コールバックを使って自動的に凍結を解除する方法については、バックボーンの凍結と解除をご覧ください。
from ultralytics import YOLO
# 第1段階: バックボーンを凍結し、ヘッドとネックを学習させます
model = YOLO("yolo26n.pt")
model.train(data="custom.yaml", epochs=20, freeze=10, name="stage1", exist_ok=True)
# 第2段階: すべての凍結を解除し、低いlrでファインチューニングします
model = YOLO("runs/detect/stage1/weights/best.pt")
model.train(data="custom.yaml", epochs=30, optimizer="AdamW", lr0=0.001, name="stage2", exist_ok=True)よくある問題#
モデルが予測を出力しない#
-
学習データが不十分: サンプルが非常に少ない状態で学習することが最も一般的な原因です。データが少なすぎると、モデルは学習も汎化もできません。他の原因を調べる前に、各クラスについて十分に多様なサンプルがあることを確認してください。
-
データセットのパスを確認: 無効な画像パスはデータセットエラーになります。個々のラベルファイルが欠落または空の場合は背景画像として扱われ、スキャン中に報告されます。学習分割にラベルが1つもない場合はエラーになります。学習前にデータセットを検証してください。
yolo detect val model=yolo26n.pt data=custom.yaml -
信頼度しきい値を下げる: 予測は存在するもののフィルタリングされている場合は、推論時に
conf=0.1を試してください。 -
クラス数を確認:
data.yaml内のncが、ラベルファイルに実際に含まれるクラス数と一致していることを確認してください。
検証用mAPが早期に頭打ちになる#
- データを追加: ファインチューニングでは、学習データを増やすことで大きな効果が得られます。特に、角度、照明、背景がさまざまな多様なサンプルが有効です。
- クラスのバランスを確認: サンプル数の少ないクラスはAPが低くなります。サンプルを追加するか、検証セットで
cls_pwを調整してください。 - データ拡張を減らす: 非常に小規模なデータセットでは、強いデータ拡張は効果よりも悪影響のほうが大きくなる場合があります。
mosaic=0.5またはmosaic=0.0を試してください。 - 解像度を上げる: 小さなオブジェクトを含むデータセットでは、細部を保持するために
imgsz=1280を試してください。
ファインチューニング後、元のクラスに対する性能が低下する#
これは破滅的忘却と呼ばれ、新しいデータだけでファインチューニングした際に、モデルが以前に学習した知識を失う現象です。新しいデータとともに元のデータセットの画像を含めない限り、忘却はほぼ避けられません。これを軽減するには、次の方法があります。
- データセットを統合: ファインチューニング時に、新しいクラスとともに元のクラスのサンプルも含めます。忘却を防ぐ確実な方法はこれだけです。
- バックボーンとネックを凍結: バックボーンとネックの両方を凍結して検出ヘッドのみを学習させる方法は、学習率を非常に低くした短期間のファインチューニングで有効です。
- エポック数を減らす: 新しいデータだけでモデルを学習する期間が長くなるほど、忘却が進みます。
よくある質問#
決まった最低枚数はありません。結果はタスクの複雑さ、クラス数、ドメインがCOCOにどの程度類似しているかによって異なります。画像の総数よりも、照明、角度、背景がさまざまな多様な画像のほうが重要です。まず手元のデータで始め、検証指標が不十分であればデータを増やしてください。
事前学習済みの
.ptファイルを読み込み、カスタムのdata.yamlへのパスを指定して.train()を呼び出します。Ultralyticsが重みの転送、分類レイヤーの再初期化、オプティマイザーの選択を自動的に処理します。完全なコード例は基本的なファインチューニングのセクションをご覧ください。最も一般的な原因は、無効な画像パス、ラベルファイルの欠落または空、YAML内の
ncと実際のラベルファイルとの不一致、または信頼度しきい値が高すぎることです。トラブルシューティングの全項目については、よくある問題をご覧ください。データセットのサイズとドメインの類似度によって異なります。COCOに近いドメインの小規模なデータセットでは、バックボーン(
freeze=10)を凍結すると過学習を防げます。COCOと大きく異なるドメインでは、すべてのレイヤーの凍結を解除(freeze=None)すると、バックボーンを適応させることができます。詳しい推奨事項については、レイヤーの凍結をご覧ください。新しいクラスとともに、元のクラスのサンプルも学習データに含めてください。それができない場合は、より多くのレイヤー(
freeze=10以上)を凍結し、学習率を下げると、事前学習済みの知識を維持しやすくなります。詳細については、元のクラスに対する性能が低下するをご覧ください。
コメント4
質問したり、結果を共有したり、ほかの人をサポートしたりできます。
Yuan Yao(編集済み)这是我在训练时遇到的一个问题,
Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 81/1000 6.68G 0 0 0 195 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.1s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 82/1000 6.68G 0 0 0 184 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.0it/s 11.3s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 83/1000 6.68G 0 0 0 221 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:19 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.1s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 84/1000 6.68G 0 0 0 212 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.2s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 85/1000 6.68G 0 0 0 221 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.2s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 86/1000 6.68G 0 0 0 216 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.0s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 87/1000 6.68G 0 0 0 198 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:19 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.0it/s 11.4s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 88/1000 6.68G 0 0 0 229 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.2s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 89/1000 6.68G 0 0 0 182 640: 100% ━━━━━━━━━━━━ 121/121 1.1s/it 2:18 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.0s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 90/1000 6.68G 0 0 0 218 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.0s all 1449 8364 0 0 0 0 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 91/1000 6.68G 0 0 0 181 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:19 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.1s all 1449 8364 0 0 0 0mAP一直是0。当时我用的是yaml文件用于训练模型,
model_name = r"H:\1_yaoyuan\code\01_yolo26\yolo_Project\ultralytics-main\ultralytics\cfg\models\11\yolo11.yaml"
之后改成了,
model_name = 'yolo11n.pt' # 首次运行会自动下载
然后训练才变的正常,
Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 2/1000 5.47G 2.825 1.856 0.8776 131 640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.0it/s 11.5s all 1449 8364 0.554 0.314 0.32 0.117 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 3/1000 5.47G 2.904 1.726 0.8738 206 640: 100% ━━━━━━━━━━━━ 121/121 1.1s/it 2:19 Class Images Instances Box(P R mAP50 mAP50-95): 100% ━━━━━━━━━━━━ 23/23 1.9it/s 11.9s all 1449 8364 0.496 0.29 0.269 0.085这是为什么?微调和从头开始训练差别很大吗? 我问了deepseek,他说:“.yaml 路径迫使模型在毫无知识的情况下承受初始的梯度冲击,导致了数值崩溃;而 .pt 路径让模型从一个稳定的知识基础开始,顺利进入了正常的学习轨道。”
可如果是这样,如果这个世界还没有yolo的pt文件,那么是不是mAP永远都是0了?
Yuan Yao我还是刚学习的阶段,如果我问的问题很幼稚我非常抱歉
Paula DerrengerUltralytics不用道歉,这是很合理的问题!不是的:从
.yaml构建模型是随机初始化,从yolo11n.pt开始则利用预训练特征,通常收敛更快、需要的数据更少;YOLO11 官方示例也支持从零训练,并不需要已有.pt才能学会检测。你这里不仅 mAP 为 0,三个 loss 也连续为 0,这不能简单解释为“从零训练比较慢”,更应优先排查训练数值异常;仅凭这些日志,还不能确认 DeepSeek 所说的“梯度冲击”。建议保留 YAML,在原来的model.train(...)中加入amp=False,重新启动训练以排查混合精度问题(AMP 检查说明)。如果仍然全为 0,请贴出完整训练代码、启动到首个异常 epoch 的文本日志,以及 GPU 型号、Ultralytics 和 PyTorch 版本。
somerongit
Thank You, It's Really a great elaborate guide.