前回の第6回では、正解データを使って精度を数字で測りました。結果は、レモン・キウイ・ライム・バナナの再現率が0%。学習済みモデルが知らない対象は、見つけられませんでした。
今回は、これらを自分たちの写真で追加学習(ファインチューニング)して覚えさせる手順を実際にやってみます。そして、その結果をどう読めば正しいかを、うまくいかなかった部分も含めて確認します。
この回は、「追加学習がうまくいった」という話ではありません。追加学習のやり方と、注意点の見つけ方を学ぶ回です。
この回で作るもの
- 練習用の学習データを自動で作るスクリプト
tools/make_dataset.py - 学習済みモデルを出発点に追加学習する
tools/train.py - 学習前後のモデルを、第6回の評価で比べる
追加学習とは:すでに学んだAIに、新しいことを教え足す
学習済みのYOLOは、80種類の物体を見分ける力を持っています。この土台の力を残したまま、自分たちの写真とラベル(何がどこに写っているかの正解)を追加で学習させるのが追加学習です。ゼロから学習させるより、少ない写真と短い時間で済みます。
必要なのは、次の3つです。
- 写真:対象を、現場と同じ条件で撮ったもの
- ラベル:写真ごとに、何がどこにあるかを示す正解(人が付ける)
- 検証用の写真:学習に使わず、あとで精度を測るために取り分けた写真
実際の業務では、1〜2つ目の準備が、費用と期間の大半を占めます。
今回は「手順の体験」のため、練習用のデータを自動で作る
この連載の手元には、実物の写真が fruits.jpg の1枚しかありません。そこで、第6回の正解データ(枠)で果物を切り出し、ランダムな背景に貼り付けて、学習用の画像とラベルを自動で作りました。
# tools/make_dataset.py(抜粋)
CLASSES = ["orange", "banana", "lime", "lemon", "kiwi"]
SIZE = 640
def make_image(rng: random.Random, crops):
background = Image.new("RGB", (SIZE, SIZE), tuple(rng.randint(90, 230) for _ in range(3)))
labels: list[str] = []
placed: list[tuple[int, int, int, int]] = []
for _ in range(rng.randint(2, 5)):
class_id, crop = rng.choice(crops)
... # 大きさ・左右反転・明るさをランダムに変えて貼り付ける
labels.append(f"{class_id} {cx:.6f} {cy:.6f} {piece.width / SIZE:.6f} {piece.height / SIZE:.6f}")
ラベルは、YOLO形式(種類の番号と、枠の中心・幅・高さを0〜1に直した数字)の文字ファイルです。200枚(学習160枚・検証40枚)を作りました。
ここには大きな落とし穴があります。 材料の写真が1枚だけなので、学習用と検証用の写真は、同じ果物の切り抜きから作った、よく似た写真です。これは、実務でやってはいけないやり方です(後述)。
追加学習を実行する
# tools/train.py(抜粋)
def main(epochs: int = 30, imgsz: int = 320) -> None:
model = YOLO(str(ROOT / "models" / "yolo26n.pt"))
model.train(
data=str(ROOT / "data" / "fruits.yaml"),
epochs=epochs,
imgsz=imgsz,
batch=16,
device="cpu",
workers=0,
project=str(ROOT / "runs"),
name="fruits",
exist_ok=True,
plots=False,
seed=0,
)
best = ROOT / "runs" / "fruits" / "weights" / "best.pt"
shutil.copy(best, ROOT / "models" / "fruits_finetuned.pt")
- 出発点は、第0回から使っている
yolo26n.pt(学習済みモデル)です epochs=30:全データを30周して学習しますdevice="cpu":GPUなしで動かしました- 学習できたモデルは
models/fruits_finetuned.ptに保存します
python tools/make_dataset.py
python tools/train.py
開発環境のCPUで、学習は約4分(0.069時間)で終わりました。画像が少なく小さい(320ピクセル)ためで、実際の業務のデータ量では、もっと時間がかかります。
学習の途中で出る数字は、高く見える
学習の最後に、検証用の40枚での成績が表示されます。
all 40 126 0.88 0.877 0.958 0.958
orange 30 40 0.926 0.94 0.975 0.975
banana 19 21 0.947 0.858 0.98 0.975
lime 19 21 0.727 0.888 0.939 0.939
lemon 18 23 0.895 0.745 0.931 0.931
kiwi 18 21 0.902 0.952 0.968 0.968
(左から、種類・画像数・正解の数・適合率・再現率・mAP50・mAP50-95。mAP50は、精度をまとめた総合点の一種で、1に近いほどよい)
適合率0.88、再現率0.877、mAP50は0.958と、非常に良い数字です。しかし、この数字を信じてはいけません。 検証用の写真も、学習用と同じ切り抜きから作った写真だからです。「見たことのある問題」でテストしているだけで、新しい写真でどうなるかは分かりません。
本当の確認:第6回の評価をもう一度
学習に使っていない実際の写真の見え方に近いものとして、元の fruits.jpg を、学習前後の2つのモデルで評価します。(ただし、切り抜きの元になった写真そのものでもあります。これも「完全に新しい写真」ではありません。それでも、追加学習の効果と限界が見える結果になりました。)
モデルは、環境変数 YOLO_MODEL_PATH で切り替えます。
YOLO_MODEL_PATH=models/fruits_finetuned.pt python -m app.evaluate samples/ground_truth/fruits.json --conf 0.25
| 種類 | 学習前(再現率) | 学習後(再現率) |
|---|---|---|
| orange(2個) | 50% | 0% |
| banana | 0% | 0% |
| lime | 0% | 0% |
| lemon | 0% | 100% |
| kiwi | 0% | 100% |
| 全体 | 適合率25%・再現率17% | 適合率100%・再現率33% |
学習後のモデルの結果は、次のとおりでした(しきい値0.25)。
2 件見つかりました(しきい値 0.25)
- kiwi 信頼度 0.29 位置 (0, 275, 128, 479)
- lemon 信頼度 0.28 位置 (327, 256, 512, 480)
何が良くなり、何が悪くなったか
- 良くなった:学習前は見つけられなかったキウイとレモンを、見つけられるようになりました。誤検出(レモンをorangeと言う等)は消え、適合率は上がりました
- 悪くなった:学習前は見つけられていたオレンジ(切ったオレンジ)を、見つけられなくなりました
- 変わらない:ライムとバナナは、見つかりません
- 自信が低い:見つけたものの信頼度は0.29と0.28で、しきい値0.25をぎりぎり超えた程度です
つまり、追加学習で「覚えた」ものもあれば、「忘れた」ものもあり、全体では再現率33%にとどまりました。 検証用の数字(mAP50が0.958)とは、大きくかけ離れています。
なぜこうなったのか(考えられる原因)
原因を1つに断定はできませんが、次の点が影響していると考えられます。
- 学習データが少なく、偏っている:材料は1枚の写真から切り出した5個の果物だけ。同じ見た目の果物が、貼り付け位置を変えて何度も出てくるだけです
- 練習用の画像が、実際の写真と違う:切り抜きを単色の背景に貼っただけで、実際の写真にある「重なり」や「影」がありません
- 学習の量が少ない:30周・4分程度の学習で、自信(信頼度)が十分に育っていません
- 覚え直しで元の知識が薄れた:新しい種類を学ばせる過程で、もともと見分けられていたものの判定が変わることがあります
この回のいちばんの学び
- 学習用と検証用の写真が似ていると、検証の数字は高く出る。本当に測りたいのは、初めて見る写真でどうなるかです
- 追加学習は「魔法」ではない。効果は、データの量・質・現場との近さでほとんど決まります
- 良くなった点だけでなく、悪くなった点も、学習の前後で同じテスト写真を使って確認する
テスト・確認
pytest -q # 25件成功(この回で追加したスクリプトは、学習を実行して確認)
ruff check . # 問題なし
学習用のスクリプトは、自動テストではなく、実際に学習を実行して動作を確認しました。
つまずきやすい点
- ラベルの付け間違い・ばらつき:人が付けるラベルは、人によってずれます。付け方のルール(枠のどこまでを含めるか)を決めます
- 学習データと現場のずれ:照明・角度・背景が現場と違う写真で学習すると、現場で使えません
- 学習に使った写真でテストしてしまう:検証の数字が高く見えます。写真は、学習用・検証用・最終テスト用に分けます
- 学習環境の準備:実際のデータ量では、GPU(画像処理用の高性能な部品)が必要になることが多く、費用に影響します
発注者向けメモ:追加学習の費用は「データの用意」に出る
追加学習の見積もりでは、学習の計算そのものより、写真の撮影とラベル付けに大きな費用と期間がかかることが多くあります。
発注者がやること チェックリスト
- ☐ 学習に使う写真を、何枚、どこで、いつまでに撮れるかを決めた
- ☐ ラベル付け(正解の作成)を、誰が・何人で行うかを決めた
- ☐ 学習用・検証用・最終テスト用の写真を、別々に取り分けることを決めた
- ☐ 追加学習の前後で、同じテスト写真で精度を比べることを契約に入れた
- ☐ 追加学習で悪くなる部分(以前は見つけられていたもの)がないか確認することを決めた
開発会社への質問例
- 「学習に使う写真は、何枚くらい必要ですか。少ない枚数で始めて、増やしていく進め方はできますか」
- 「ラベル付けは、どちらが担当しますか。付け方のルールは、どう決めますか」
- 「学習用と検証用の写真は、どう分けますか。最終的な精度は、学習に使っていない写真で測りますか」
- 「追加学習をしたあとに、以前は見つけられていたものが見つからなくなることはありますか。その確認は契約に含まれますか」
- 「対象が増えたり、現場の条件が変わったりしたときの再学習は、保守に含まれますか」
まとめと次回予告
第7回では、次のことを行いました。
- 練習用のデータを作り、学習済みモデルに追加学習した(CPUで約4分)
- 学習前は見つけられなかったレモン・キウイを見つけられるようになった一方、オレンジを見逃すようになった
- 学習中の検証の数字が高くても、実際の写真では再現率33%にとどまることを確認した
- 追加学習の成否は、データの量・質・現場との近さで決まることを整理した
次回の第8回では、AIの結果に自信がないものだけを人が確認する、人が確認するフローを作ります。今回のように精度が完全でない場合の、現実的な運用の形です。
この連載の記事一覧
この記事は連載「YOLOで作る画像認識ツール」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【YOLOで作る画像認識ツール 第0回】全体像と環境づくり:YOLOで最初の1枚を認識する
- 【YOLOで作る画像認識ツール 第1回】信頼度のしきい値とJSON出力:認識結果を「使える形」にする
- 【YOLOで作る画像認識ツール 第2回】認識結果を画像に描いて確認する:枠とラベルで「間違い」を見つけやすくする
- 【YOLOで作る画像認識ツール 第3回】種類ごとに何個あるか数えて、CSVに集計する
- 【YOLOで作る画像認識ツール 第4回】FastAPIで画像認識を「API」にして、ほかのシステムから使えるようにする
- 【YOLOで作る画像認識ツール 第5回】ブラウザから写真をアップロードして、認識結果をその場で見る
- 【YOLOで作る画像認識ツール 第6回】精度を数字で測る:正解データと、適合率・再現率
- 【YOLOで作る画像認識ツール 第7回】学習済みモデルにない対象を覚えさせる:追加学習の手順と、結果の正しい読み方(この記事)
- 【YOLOで作る画像認識ツール 第8回】自信の低い結果だけ人が確認する:AIと人の役割分担を設計する
- 【YOLOで作る画像認識ツール 第9回】速く・軽くする:ONNXに変換してCPUで動かし、変換前後を比べる
- 【YOLOで作る画像認識ツール 第10回】Dockerで動かす:設定・ログ・ヘルスチェックを運用に近い形にする
- 【YOLOで作る画像認識ツール 第11回(最終回)】運用の注意点とライセンス(AGPL-3.0):精度の低下に気づく仕組みと、連載のまとめ

