前回の第7回では、追加学習で「覚えたもの」と「見逃すようになったもの」が混ざる結果を見ました。ここまでの連載で確かめたとおり、画像認識AIは、間違えることを前提に使う必要があります。
では、どう使うのか。現実的な答えの1つが、AIが自信を持てた結果は自動で採用し、自信がない結果だけ人が確認するという役割分担です。今回は、この「人が確認するフロー」を作ります。
この回で作るもの
- 検出結果を「自動で採用」「人が確認」「捨てる」に振り分ける処理
- 人の判断(そのまま・却下・種類の修正)を、結果に反映する処理
- 自動採用の結果から一定の割合を抜き取って確認する仕組み(抜き取り確認)
仕組み:信頼度で3つに振り分ける
第1回で学んだ信頼度を使い、2つのしきい値で結果を振り分けます。
| 信頼度 | 扱い | 意味 |
|---|---|---|
| 0.60以上 | 自動で採用 | 人の手間をかけずに使う |
| 0.25以上、0.60未満 | 人が確認 | 画面で見て、そのまま・却下・種類の修正を決める |
| 0.25未満 | 捨てる | 自信が低すぎるので、結果に出さない |
しきい値の数字は、この連載の練習用の値です。実際は、第6回の方法で測った精度と、人が確認できる件数の余裕を見て決めます。
実装
振り分ける app/review.py
# app/review.py(抜粋)
ACCEPT_THRESHOLD = 0.60 # これ以上は自動で採用する
REVIEW_THRESHOLD = 0.25 # これ未満は捨てる。間は人が確認する
@dataclass(frozen=True)
class Triage:
accepted: list[Detection] # 自動で採用
needs_review: list[Detection] # 人が確認する
discarded: list[Detection] # 自信が低すぎるので捨てる
def triage(detections, accept=ACCEPT_THRESHOLD, review=REVIEW_THRESHOLD) -> Triage:
if not 0 <= review <= accept <= 1:
raise ValueError("しきい値は 0 <= 確認 <= 採用 <= 1 にしてください")
return Triage(
accepted=[d for d in detections if d.confidence >= accept],
needs_review=[d for d in detections if review <= d.confidence < accept],
discarded=[d for d in detections if d.confidence < review],
)
triage は、フランス語の「選別」が語源で、救急医療で患者の緊急度を分ける意味でも使われる言葉です。
人の判断を反映する
人が確認した結果は、「そのまま」「却下」「種類の修正」の3つです。
# app/review.py(抜粋)
def apply_decisions(detections, decisions) -> list[Detection]:
"""人の判断を反映して、最終的な結果を返す。
decisions の各要素: {"box": [左,上,右,下], "action": "accept" | "reject" | "relabel", "label": "kiwi"}
判断が付いていない検出は、そのまま残す。
"""
by_box = {tuple(d["box"]): d for d in decisions}
final: list[Detection] = []
for det in detections:
decision = by_box.get(det.box)
action = decision["action"] if decision else "accept"
if action == "reject":
continue
if action == "relabel":
if not decision or not decision.get("label"):
raise ValueError(f"relabel には label が必要です: {det.box}")
det = replace(det, label=str(decision["label"]))
final.append(det)
return final
判断は枠の位置(box)で結びつけます。種類の修正には、新しい種類名が必須です。書き忘れると、間違ったまま通すのではなく、エラーで止めます。
抜き取り確認
# app/review.py(抜粋)
def sample_for_audit(accepted: list[Detection], rate: float, seed: int = 0) -> list[Detection]:
"""自動採用した結果からも、一定の割合を抜き取って人が確認する(自信が高いのに間違う結果を拾うため)。"""
if not accepted or rate <= 0:
return []
count = max(1, round(len(accepted) * rate))
return random.Random(seed).sample(accepted, min(count, len(accepted)))
動かして確かめる
python -m app.review samples/fruits.jpg --decisions samples/decisions_example.json --audit-rate 0.5
開発環境での結果は次のとおりです(一覧の一部を省略)。
自動採用 2 件 / 人が確認 2 件 / 捨てる 0 件
確認待ち: 信頼度0.398(右上の枠)、信頼度0.469(左下の枠)
抜き取り確認: [('orange', 0.72)]
人の判断を反映した最終結果:
- lemon 0.87 (317, 247, 512, 477)
- orange 0.72 (69, 44, 348, 470)
- kiwi 0.47 (0, 277, 130, 477)
- lime 0.40 (321, 121, 512, 305)
信頼度0.47と0.40の2件は、人の確認に回りました。サンプルの判断(decisions_example.json)では、第6回の正解データに合わせて、それぞれkiwiとlimeに修正しています。
「自信が高い間違い」は、しきい値だけでは防げない
ここで重要な点があります。最終結果の1行目、信頼度0.87の枠は、実際にはレモンなのに、AIは「orange」と答え、自動採用に振り分けられました。 人が確認する枠には入りません。
この例で、レモンがlemonに直っているのは、サンプルの判断ファイルに「この枠はレモン」という修正を、私が入れているためです。実際の運用では、人が見なければ、この間違いは残ります。
- 信頼度は、正しさの保証ではない:第2回で見たとおり、信頼度が最も高い結果が、間違いでした
- 抜き取り確認の限界:今回の抜き取りは、たまたま正しい「0.72」の枠を選び、間違いの「0.87」を選びませんでした。抜き取りは、割合を上げるほど間違いを拾えますが、確認の手間も増えます
つまり、「自信の低い結果だけ人が確認」する設計は、見逃しを減らす助けにはなっても、すべての間違いを防げるわけではありません。 どこまでの間違いを許容するかを、業務の側で決めておく必要があります。
テスト
pytest -q # 31件成功
ruff check . # 問題なし
振り分けの境界値、判断の反映(修正・却下・判断なしはそのまま)、種類名を書き忘れたときのエラー、抜き取りが同じ条件で同じ結果になることを確認しています。
つまずきやすい点
- 確認の件数が多すぎる:しきい値の幅を広く取ると、人の確認が追いつきません。1日に確認できる件数を先に見積もります
- 確認する人によって判断がばらつく:判断基準(何を「正しい」とするか)を文書にして、共有します
- 確認結果が使われない:人が直した結果を、次の学習用データとして貯めておくと、モデルの改善に使えます(第7回)
- 画面がないと運用できない:この回は、確認待ちの一覧と判断の反映まで。実際には、第5回のような画面に、確認のボタンを付けます
発注者向けメモ:AIは「全自動」ではなく「人の作業を減らす道具」
画像認識の導入を検討するときは、「全部自動で判定できる」という期待を、「人の確認を減らせる」という期待に置き換えると、現実的な設計になります。
発注者がやること チェックリスト
- ☐ 確認が必要な件数の目安(1日に何件まで人が確認できるか)を決めた
- ☐ 確認する人(担当者)と、判断基準を決めた
- ☐ 自動採用した結果を、一定の割合で抜き取って確認する運用を決めた
- ☐ 人が直した結果を、記録に残す(監査と再学習に使える)ことを決めた
- ☐ AIが間違えたときの責任の所在(誰が最終判断をするか)を決めた
開発会社への質問例
- 「信頼度のしきい値は、どのデータで、どう決めますか。人の確認に回る件数は、どのくらいになりそうですか」
- 「確認用の画面は、見積もりに含まれていますか。修正の履歴は残りますか」
- 「自信が高いのに間違っている結果を、拾う仕組みはありますか」
- 「人が直した結果を、再学習に使うことはできますか。その作業は保守に含まれますか」
まとめと次回予告
第8回では、次のことを行いました。
- 検出結果を、信頼度で「自動採用・人が確認・捨てる」に振り分けた
- 人の判断(そのまま・却下・種類の修正)を反映できるようにした
- 抜き取り確認を用意し、自信が高い間違いを拾う工夫をした
- しきい値だけでは、自信が高い間違いを防げないことを確認した
次回の第9回では、認識を速く・軽くするために、モデルをONNXという形式に変換し、速度を比べます。
この連載の記事一覧
この記事は連載「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):精度の低下に気づく仕組みと、連載のまとめ

