← LOG

PYTHON2026.02.03

レシートをローカルLLMでマネーフォワードCSVまで作る前に知っておきたい4つの失敗

#python#llm#ocr#ローカルllm#ollama

はじめに

レシート画像をローカルLLMで読み取り、マネーフォワード(会計ソフト)のインポート用CSVまで自動生成するシステムを構築しました。

この記事では、Phase 1(OCR + テキストLLM方式)で実際にハマった4つの失敗を紹介します。同じことを試そうとしている方の参考になれば幸いです。


この記事で伝えること


アンチパターン①: OCRの誤認識をLLMのプロンプトでカバーしようとする

現象

PaddleOCRでレシートを読み取った際、以下のような誤認識が発生しました:

実際の文字OCR結果影響
¥108半106車王 キ106金額が完全に崩壊
¥合計金額の誤認識
¥消費税の誤認識
¥2,717半2,717金額の先頭文字が誤認識

これらの誤認識テキストをそのままLLMに渡した結果:

{
  "store_name": "FamilyMart",
  "date": "2024-01-05",
  "time": "09:38",
  "items": [{"name": "青NO", "price": "999"}],
  "total": "4497",  // 実際は108円
  "tax": "5248",    // 実際は8円
  "payment_method": "クレジット"
}

実際の金額は108円(税込)だったのに、4,497円と認識されてしまいました。

なぜ失敗したか

  1. プロンプトで誤認識を修正しようとした

    • ¥の誤認識の可能性がある」とプロンプトに書いた
    • しかし、LLMは文脈から推測するしかなく、確実な修正はできない
  2. 2段階処理で誤差が累積

    画像 → OCR → テキスト → LLM → JSON
           ❌誤認識      ❌誤解釈
    
    • OCRの誤認識がLLMの誤解釈を引き起こす
    • レイアウト情報(どこに何が書かれているか)が失われる

推奨: 入力品質を先に固める

# 推奨: OCR誤字の確実な修正(プロンプトに頼らない)
def correct_ocr_errors(text):
    """OCR誤字を確実に修正"""
    corrections = {
        '半': '¥',
        'ギ': '¥',
        '米': '¥',
    }
    for wrong, correct in corrections.items():
        text = text.replace(wrong, correct)
    return text

アンチパターン②: 「表示サイズ3GB」=「8GB PCで動く」と思い込む

現象

Vision LLMモデルを選定する際、以下のような誤解をしていました:

モデル表示サイズ実際のメモリ要求結果
qwen2.5vl:3b約3GB8.4GB以上❌ 8GB RAMで落ちる
qwen2.5vl:7b約7GB12GB以上❌ 8GB RAMで落ちる

「3GBのモデルだから8GB PCで動く」と思い込んでいましたが、実際には実行時メモリが8GBを超えてエラーになりました。

エラーメッセージ例

RuntimeError: CUDA out of memory. Tried to allocate 2.5GB (GPU 0; 4.0GB total capacity; 1.2GB already allocated; 2.8GB free; 4.0GB reserved in total by PyTorch)

なぜ失敗したか

  1. 表示サイズ ≠ 実行時メモリ

    • モデルファイルのサイズと、実際にメモリに展開されるサイズは異なる
    • 推論時の一時メモリも必要
  2. 量子化の重要性を理解していなかった

    • llama3.2-vision:11b(Q4_K_M量子化)なら8GB RAMで動作
    • 量子化により、モデルサイズを約1/4に圧縮できる

推奨: 量子化と実機検証を必ず実施

# 推奨: 量子化モデルを使用
ollama pull llama3.2-vision:11b-q4_k_m  # 量子化版

アンチパターン③: 細長いレシートをそのままVision LLMに渡す

現象

コンビニの細長いレシート(アスペクト比が大きい)を処理した際、以下の問題が発生しました:

画像サイズアスペクト比(高さ/幅)結果
314×18365.85❌ JSONエラー
400×12003.0⚠️ 処理時間5分以上
600×18003.0❌ ハング(応答なし)

アスペクト比が3以上の細長いレシートで、JSONエラーまたは長時間のハングが発生しました。

エラーメッセージ例

Error: JSON parsing failed. Model output was not valid JSON.

または、処理が5分以上かかり、最終的にタイムアウト。

なぜ失敗したか

  1. Vision LLMは「普通の写真」用に学習されている

    • レシートのような細長い画像は想定外
    • アスペクト比が極端だと、モデルが混乱する
  2. 前処理をしていなかった

    • 画像をそのままモデルに渡していた
    • パディングやリサイズなどの前処理が必要

推奨: 前処理で比率を抑える

# 推奨: アスペクト比の調整
from PIL import Image

def adjust_aspect_ratio(image_path, target_ratio=0.75):
    """アスペクト比を調整(パディング追加)"""
    img = Image.open(image_path)
    width, height = img.size
    current_ratio = height / width
    
    if current_ratio > target_ratio:
        # 高さが長すぎる場合、左右にパディング
        new_width = int(height / target_ratio)
        new_img = Image.new('RGB', (new_width, height), (255, 255, 255))
        new_img.paste(img, ((new_width - width) // 2, 0))
        return new_img
    return img

アンチパターン④: 「ローカル=無料=正義」で終わりにしない

現象

「全部ローカルで完結させたい」という理想を追い求めましたが、現実は以下の通りでした:

項目ローカルVision LLMGoogle Vision API(無料枠)
処理時間1枚30秒〜5分1枚1-3秒
月100枚の処理時間約8時間以上約3-5分
速度差-約500倍速
コスト電気代のみ月1,000枚まで無料
精度金額30-40%(素のモデル)95%以上

月100枚のレシートを処理するのに、ローカルでは8時間以上かかることが判明しました。

なぜ失敗したか

  1. 処理時間を軽視していた

    • 「動けばいい」と思っていた
    • しかし、実務では「待てる時間」が重要
  2. 無料APIの存在を無視していた

    • Google Vision APIは月1,000枚まで無料
    • 無料枠内で速度・精度が圧倒的に高い
  3. 「ローカル必須」の要件を再確認していなかった

    • プライバシー要件が本当に必要か?
    • 無料枠で足りるなら、クラウドも選択肢に入れるべき

推奨: 要件で判断基準を決める

判断基準ローカルVision LLMクラウドAPI
プライバシー要件が厳しい✅ 推奨❌ 不向き
処理速度を重視❌ 不向き✅ 推奨
月100枚以下⚠️ 検討✅ 推奨
月1,000枚以上⚠️ 検討⚠️ 有料

やるべきこと・判断の目安

1. ベースラインを数字で取る

測定すべき項目:

測定結果の例(Phase 1の結果):

項目正解率備考
店舗名80%候補から選択できている
日付95%ほぼ正確
金額30-40%実用不可
消費税70%誤字が残る

2. プロンプトとファインチューニングの役割を分けて考える

3. 「ローカル必須か」「無料枠で足りるか」を要件で決める

判断フローチャート:

プライバシー要件が厳しい?
├─ Yes → ローカル必須(Vision LLM + ファインチューニング)
└─ No → 無料枠で足りる?
    ├─ Yes → クラウドAPI(Google Vision API等)
    └─ No → ローカル + クラウドのハイブリッド

まとめ: 技術選定チェックリスト

レシートをローカルLLMで処理する前に、以下を確認してください:

ハードウェア要件

処理要件

プライバシー要件

技術選定の選択肢

選択肢向いているケース
ローカルVision LLMプライバシー要件が厳しい、月100枚以下
クラウドOCR + ローカルLLMプライバシー要件は緩いが、LLMはローカルで
クラウドAPI中心処理速度を重視、無料枠で足りる

おわりに

Phase 1(OCR + テキストLLM方式)では、上記の4つの失敗を経験しました。

現在はGoogle Vision API(OCR) + Ollama(ローカルLLM)のハイブリッド構成に移行し、実用的な速度と精度を実現しています。

「全部ローカルで」という理想は素晴らしいですが、ユーザー体験を優先して技術選定することが重要だと学びました。

同じことを試そうとしている方の参考になれば幸いです。


参考リンク


次回予定: Phase 2(Vision LLM方式)の検証結果も記事にする予定です。

Zennでも読む ← LOGに戻る