毎日、バンキング業務オペレーションのチームは数千件の書類を手作業でレビューしています -
ローン申請書、KYCフォーム、契約書 - 正しいスタンプ、
正しい署名を、正しい場所にあるかどうかを探すのです。これは遅くて高コストで、そして
まさにコンピュータビジョンが自動化すべき種類の仕事です。
問題は、オンライン上のほとんどのYOLOチュートリアルが、自然写真の中で車や
犬、人物を検出する方法を教えていることです。ところが、それは
書類にはうまく適用できません。書類は構造化されており、品質はさまざまで、
携帯電話で斜めから撮影されることもあれば、ファックスで送られることもあり、
しばしば透かしが入り、そしてほとんどの場合、照明が一貫していません。同じフォーム上のスタンプを
きれいなPDFで検出できるモデルは、そのフォームを
電話で撮影した写真では崩壊します。
"ここ数週間、規制のある銀行環境で、書類上のスタンプと署名を検出する
YOLOv11ベースのディテクタの実装に深く取り組んできました。"
その作業は、既製のチュートリアルが終わる場所と、
本当のエンジニアリングが始まる場所を教えてくれました。ここにプレイブックがあります。
なぜ代替案よりYOLOv11なのか
書類のオブジェクト検出には、いくつか妥当な出発点があります。
-
LayoutLMv3やDonutのようなレイアウト対応モデル - 構造化されたフォームには強いですが、重く、限定された用途(狭いタスク)向けにファインチューニングするのが難しく、推論も遅いです。検出したいのがオブジェクトの一部(スタンプ、署名、頭文字)だけなら過剰です。
- 古典的なOpenCVのアプローチ - テンプレートマッチング、輪郭検出、Hough変換。高速で軽量ですが、実運用のスキャンには脆くて不安定です。
- YOLOファミリー(v8、v11) - 書類に対するオブジェクト検出のちょうど良いところです。速く、よくドキュメントされており、ファインチューニングも容易で、精度/再現率のトレードオフはオペレーションチームの要件に合わせて調整できます。
私はYOLOv11を選びました。
ultralyticsのPythonパッケージが大部分の面倒な作業を引き受けてくれます。推論は適度なGPUであればページあたり100ms未満で動き、さらにアーキテクチャは小さな物体を扱います - スタンプがスキャン解像度の低い場所にあることが多いため - 古いバージョンよりも得意です。 ## 80%:データ準備とアノテーション 実運用でCVを出荷した人なら誰でも同じことを言うはずです。モデルを作るのは簡単です。時間を奪われるのはデータです。 アノテーションツール。 私はRoboflowを使いました - バウンディングボックスのラベリング用のクリーンなWeb UI、学習/検証/テストの自動分割、YOLO形式への簡単なエクスポートが可能です。コンプライアンス上の理由でSaaSが使えない場合は、CVATがオープンソースの代替です。 クラス分類。 初日から10クラスを定義したくなる衝動を抑えてください。まずはビジネス課題を解く最小のセットから始めます: -
signature-stamp-(必要に応じて、フォームに含まれる場合はhandwritten_initials) クラス数が増えるほど、クラスごとのラベル付き例の数が増えるとは限らず、失敗パターンが増え、デバッグしづらいモデルになります。あとからクラスを分けることはできますが、ぐちゃぐちゃになったものをきれいに統合することはめったにできません。 学習/検証/テスト分割の規律。 ドキュメントを3つの分割(splits)に分けるときは、ランダムに行うのではなく出典(source)ごとに分けてください。同じフォームテンプレートが学習(train)と検証(val)の両方に出ていると、あなたの検証指標は嘘をつくことになります - モデルはオブジェクトではなくフォームのレイアウトを学習しているのです。誤った予測が実際のお金につながるような規制環境では、嘘をつく検証セットは許されません。 データ拡張戦略 - そしてなぜデフォルトは書類では間違っているのか。 市販/既製のYOLOデータ拡張のデフォルトは、自然画像向けに設計されています。回転は最大30°まで含まれ、mosaic、MixUpも含まれます。書類ではそれは積極的に間違いです: - 回転は厳密に制限する(±5°)。 書類はまっすぐ(upright)です。大きく回転させると、実運用の入力を反映しない学習例が生成されます。 - mosaic拡張はオフにすべき。 4つの書類を2×2のグリッドに貼り付けると、推論時には存在しない入力が作られてしまいます。 - 代わりに役立つもの: 明るさ/コントラストの変動(スキャン品質の違い)、JPEG圧縮ノイズ(低品質スキャン)、部分的な遮蔽(書類の一部が隠れている)、ガウスぼかし(ピンぼけの携帯写真)。 "私のプロジェクトで最も大きな精度向上は、携帯で撮影したスキャン向けに拡張を行ったことでした。実運用データは、私の学習セットが前提としていたよりもずっと雑でした - そのギャップを埋めることの方が、どんなアーキテクチャ変更よりも重要だったのです。" ## 本当に効く学習設定 ほとんどのYOLOのハイパーパラメータはデフォルトのままで問題ありません。書類で効果が出るのは、次の部分です:
from ultralytics import YOLO
model = YOLO('yolo11m.pt')
results = model.train(
data='dataset.yaml',
epochs=100,
imgsz=1024, # 小さなスタンプには大きいimgszが効く
batch=8,
lr0=0.001,
patience=20, # mAPが頭打ちになったら早期停止
augment=True,
mosaic=0.0, # 書類ではオフ
degrees=5, # 回転を制限
fliplr=0.0, # 書類を左右反転しない
)
```
{% endraw %}
返却形式: {"translated": "翻訳されたHTML"}2 つ の 点 、ここで 注意 喚起:
**{% raw %}`imgsz=1024`{% endraw %} は 640.** 低 解像度 で スタンプ を 適用 すると 、数 枚
の ピクセル - 小さすぎる ため モデル が 確実 に 検出 でき。 入力 サイズ
を 大きく すると 計算 コスト は 画像 あたり 増えますが、 小さい オブジェクト に 対する 精度 向上
は 大きい。
**水平 反転を** 無効化 する。 ** 反転 された フォーム は 誤った フォーム です。
製品 入力 の 中 で 一度-も-見られたことのない 状態 を 生成 して しまう 拡張 は
実際 に あなた が 重要視 している 入力 に 対する 汎化 を 損ないます。
## 実際に最適化すべき指標
多く の チュートリアル では デフォルト で {% raw %}`mAP@0.5`{% endraw %}. になっています。 規制 された
環境 での ドキュメントAI では、 それ が
間違った 主要 指標 です。
Ops チーム は **精度** を 重視します。 モデルが 「ここに 署名が あります」
と言ったとき 、 それが 正しい 必要が あります。 誤検出(false positive) は そこに あるはず のない ドキュメント を 下流 に 流し、 レビュアー
時間 を 無駄にします。 見逃し(false negative) は 回復 可能です - ドキュメント は 手動 レビュー に 戻り、 それが 既存 の ベースライン
です。 両方 を 追跡 しますが、 1つ だけ 最適化 する 必要 が ある なら 精度 を 最適化 してください。 あなた の Ops マネージャー が 感謝 する でしょう。
## 推論とデプロイ
GPU 上で 動く モデル は 楽しい です。 CPU 上で 動く モデル は
出荷 可能です。 ドキュメントAI の ほとんど の ワークロード—あなた が 1分あたり 数十〜数百ページ を 処理 する 一方、 数百万 では ない—
では、ONNX に エクスポート した モデル による CPU 推論 のほうが デプロイ が 速く、 運用 コスト も 安く、
GPU ドライバ の 取り合い になって ほしく ない 、 強く 制限 された 本番環境 との 互換性 も 非常に 高い です。
流れ は 次のとおり:
1. 訓練 は {% raw %}`ultralytics` (PyTorch バックエンド、訓練時はGPU)
2. 訓練済み 重み を ONNX に エクスポート
3. `ultralytics` の ONNX-runtime 経由で CPU 上 で 提供
Step 2 は 1行 です:
```python
from ultralytics import YOLO
model = YOLO( 'best.pt')
model.export(format= 'onnx') # best.pt の横に best.onnx を書き出します
```
Step 3 - 推論 サービス:
```python
from fastapi import FastAPI, UploadFile
from ultralytics import YOLO
from PIL import Image
import io
app = FastAPI()
model = YOLO( 'best.onnx') # ONNX runtime、CPUのみ
@app.post( '/detect')
async def detect(file: UploadFile):
image = Image.open(io.BytesIO(await file.read()))
results = model(image)
detections = []
for r in results:
for box in r.boxes:
detections.append({
'class': model.names[int(box.cls)],
'confidence': float(box.conf),
'bbox': box.xyxy.tolist()[0],
})
return { 'detections': detections}
```
この スニペット で 最も 重要な 行 は `model = YOLO( 'best.onnx')` で
、モジュール レベル(先頭) に 置いて います。 起動 時に モデルを 1回 だけ ロード し、 リクエスト ごと には ロード しません。
各 リクエスト で 毎回 モデル を リロード する のが 、YOLO エンドポイントで 最も よく ある 本番 の ミスです。 私は それを 見た こと が あります。 その差 は、レスポンス 時間
が 50ms か 5,000ms か という ことです。
コンテナ について:スリムな Python ベース イメージ (`python:3.11-slim`) で 十分です。 CUDA なし、GPU ドライバ なし、NVIDIA 依存 なし。 イメージ サイズ は
500MB 未満 に 収まり、秒 単位 で 起動 でき、 どこ でも 動きます—強く 制限 された 社内 VM や オンプレミス 環境 にも。
GPU依存 サービス を 出荷 する ため に 必要な 承認が 数 か月 かかる ような 現場 でも、その 問題 は ありません。
本当 の トレードオフは こうです。 つまり、次の四半期 では なく 今日 デプロイ できる サービス を 得る 代わり に、リクエスト ごとの
レイテンシ を 少し 手放す こと です。
返却形式: {"translated": "翻訳されたHTML"}## What the tutorials don't tell you
チュートリアルが教えてくれないこと
標準的なYOLOブログ記事が飛ばしてしまう3つの教訓:
**1. 奇妙なスキャンのロングテールこそが、現場で破綻が起きる場所です。**
横方向のバンディングが入ったFAXのページ、部分的にコピーされた書類、1つの角が切り取られた電話の撮影画像、裏面から滲み出てしまうウォーターマーク。あなたの学習データセットには、これらが十分に含まれていないはずです。できるだけ早く実際の現場入力のサンプルを用意してください(たとえ50枚だけでも)—それを訓練ではなく評価に使います。そうすれば、世界が実際にどのように見えるかが分かります。
**2. 入力画像のハッシュとともに、すべての予測を記録してください。**
モデルが本番で失敗したとき、後からでも「それを壊したまさにその入力」を特定できる状態にしたいはずです。入力をハッシュ化し、予測をログに残し、両方を保存します。それが、ハンティングせずにラウンド2の学習データを作る方法です。
**3. mAP@0.95を追いかけないでください。**
収穫逓減です。ビジネスで「リコール70%のときに精度95%」が必要なら、その運用ポイントに最適化します—曲線全体を要約する指標のために最適化しないでください。運用チームに相談してください。彼らが本当に気にしている数値を取り出します。それに対して学習してください。
## Closing
ドキュメントAIにおけるボトルネックはモデルではありません。ボトルネックは、
アノテーションの規律、実際の本番入力に合わせてチューニングされたデータ拡張、そして負荷で破綻しないデプロイです。規制産業向けにコンピュータビジョンを作っているなら—銀行、保険、法律、ヘルスケア—上のプレイブックが、私がうまくいったやり方です。フレームワークは変わります。データの規律は変わりません。



