手書き伝票のOCRを安いモデルで試した記録:DeepSeek と Gemini の同条件ベンチと、Gemini Flash での全件照合
手書き伝票のOCRを安いモデルで試した記録
朝、DeepSeek V4 Flash を API で試してみたくなった。 記帳業務での正確さを試し、画像を渡せるなら OCR の精度も見たい。
比べる相手は、記帳アプリで使ってきた Gemini である。 安いモデルに替えても、同じ伝票を同じだけ読めるのか。
去年の記帳アプリを掘り起こす
Claude Code に調べてもらうと、V4 Flash はすでに退役していた。 いま API で呼ぶと、2026年9月10日に出た V4.1 Flash に振り替わる。 V4.1 Flash は画像入力にネイティブで対応しているので、記帳の精度と OCR の精度を同じモデルで試せる。
試す材料には心当たりがあった。 去年の3月ごろ、手書きの売上伝票を読んで仕訳にする記帳アプリを作っている。 どのリポジトリだったかを忘れていて、少し考えてから思い出した。 アプリを起動してもらうと、ある事業者の手書き伝票と、画面で直して確定した正解データが見つかった。 処理をそろえたいので、既存のプロンプトを読ませ、使っていた Gemini のモデル名もメモさせた。
修正前の Gemini の出力は、残っていなかった。 アプリは OCR の結果を行に直接書き、画面での修正がその行を上書きする。 生テキスト用の列は全行空で、バックアップの DB 3本もすべて修正後の値と一致した。 「当時の Gemini」とは比べられないので、同じプロンプトと同じスキーマで Gemini を今日もう一度回すことになった。 プレビュー版のモデルなので、当時と同一とは言い切れない。
対象は、アプリでいま開いているバッチの78枚にした。 いまのスキーマで処理した最後のバッチで、正解データの項目がそろっている。 送る前に、顧客名の行だけを白く塗る。 3枚で確かめると、日付の行は残り、名前の行だけが消えていた。
チャージ2ドルと、APIキーの置き場所
DeepSeek は前払い制で、チャージは2ドルで足りると見積もってもらった。 69枚を3条件で回すパイロットなら0.5ドルに届かず、全量963枚を thinking 込みで回しても1〜2.5ドルで収まる。 平日の日本時間10〜13時と15〜19時は単価が倍になるので、全量はそれ以外の時間に回す。
引っかかったのはキーの置き場所だ。
見積もりの報告には、キーを 1Password に入れてアプリの .env に書く前提、とあった。
1Password に入れるのに .env にも書くとはどういうことか、と聞き返した。
あわせて、1Password に空の項目を作ってもらえれば、そこへ自分で入れる、と頼んだ。
Claude Code の説明では、この環境のルールで正本は 1Password、.env はそこから書き出したコピーである。
Windows では op run が実行のたびに承認ダイアログを出すので、何十回も起動するアプリ本体は .env に書き出して使っている。
今回のベンチは1プロセスを1回起動するだけなので、起動時に op read で 1Password から直接読めば .env は要らない。
「.env に書く前提」は撤回され、1Password には空の項目が用意された。
DeepSeek 側でキーに付ける名前も相談した。 勧められたのは、リポジトリ名に用途を足した名前である。 用途と機体が後で分かれば、漏れたときにそのキーだけを失効できる。 発行したキーを 1Password の認証情報の欄に入れ、値を画面に出さないまま疎通を確かめてもらった。
ベンチ用のスクリプトを書く段で、書き込みが止まった。 書いてもらってかまわないし、明示的に OK を出してもだめなのかと返し、オートモードを切らずに進めたいと伝えた。 明示の OK を受けて許可ルールが効き、スクリプトは書き込めた。
最初の2枚と、thinking を切った78枚
スクリプトはローカルの動作確認まで通ったが、実行の段で止まった。
そこでベンチのコマンドは、行頭に ! を付けて自分で打った。
まず2枚。 9秒、6,287トークンで返り、日付も金額も摘要も、2枚とも正解と一致した。 マスクした顧客名は空で返ってきている。
1枚あたりいくらかを円で聞いた。 実測は入力1,740トークン、出力1,400トークン前後で、出力の大半は thinking だった。 公式単価(ピーク時で100万トークンあたり入力0.30ドル、出力1.20ドル)に当てはめ、1ドル150円で換算すると、1枚1円に届かない。 同時に、thinking を切るとさすがに精度が落ちるのか、とも聞いた。
thinking を切って、顧客名を塗った1枚をそのまま渡す条件(masked)で78枚を回した。
58秒、13万7,824トークンで全件が返る。
金額3項目がすべて合ったのは65枚(83%)だった。
実務にはまだ足りないが、誤りの型ははっきりしていた。
次に、画像を上下2つに割って渡した(masked-split)。
38秒で終わり、金額は少し上がり、日付は下がった。
残りの3条件(thinking あり、temperature 0、3部位に切り出す masked-crops)は、まとめて打った。
2行目以降の行頭の ! が bash の否定演算子として効き、終了コードが反転して「失敗」と表示された。
しかも120秒の制限にかかり、バックグラウンドへ退避されている。
出力ファイルを確かめてもらうと、3本とも78枚分の応答が保存されていた。
失敗は、thinking ありの2枚だけだった。
5条件がそろうと、thinking を切ると落ちるという見立ては外れた。 金額の一致は増えず、所要は4倍になり、費用も4倍以上に膨らんだ。 そのうえ、明細に無い商品の売上を作る、足し間違える、JSON の前にスキーマを復唱して出力が壊れる、という副作用が出た。 DeepSeek で成績が最もよかったのは、3部位に切り出し、thinking なし、temperature 0 の組み合わせだった。
DGX Spark で回すなら
このベンチは、NVIDIA の DGX Spark で手元に置けるかの検証も兼ねている。 そこで、DGX Spark でも thinking を使えるのかを聞いた。
thinking 自体はローカルでも使える。
クラウド限定の機能ではなくモデルに組み込まれた挙動で、推論サーバー側のフラグで切り替える(vLLM や SGLang ならテンプレート引数の thinking、llama.cpp なら --reasoning on/off)。
問題は thinking ではなく、いま API で測っている V4.1 Flash が1台に載らないことだった。
1台の128GBに収まらず、3台か4台を束ねる構成になる。
それなら今は300万〜400万円かかる、というのが自分の理解だった。
ここで、thinking が要らないという結果が効く。 DGX Spark で回す場合でも、thinking なしで済めば速度が10倍違う。
Gemini も同じスクリプトで回す
DeepSeek の結果が出たところで、Gemini も同じ条件で回すよう頼んだ。
Gemini 用の別スクリプトは作らず、同じ bench.py に --provider gemini を足してもらった。
同じ画像加工、同じプロンプト、同じ採点を通すには、1本にまとめるのが比較の前提になる。
compare サブコマンドで、両者が1つの表に並ぶ。
ここでもキーでつまずいた。
環境変数に入っていた Gemini のキーは、文字だけの最小呼び出しで「API key not valid」を返した。
失効したか、差し替え済みである。
アプリのインポート機能も同じ環境変数を読むので、次に使うときに落ちる可能性がある。
新しいキーを 1Password に入れると、gemini-3-flash-preview から応答が返った。
thinkingLevel=minimal を付けると思考トークンがゼロになるので、DeepSeek の thinking なしとほぼ同じ条件で比べられる。
Gemini は3本を1本ずつ打ったが、3本とも120秒の制限にかかってバックグラウンドへ回った。 しばらくして「これ進んでますか」と聞いた。
2本は止まっていて、残る1本は22枚まで進んで途切れていた。
新しいキーを確かめる途中で、op read が一度「authorization timeout」で失敗した(1Password アプリ側の承認かロックの可能性)。
読み直すとキーは有効で、小さな呼び出しを7回続けても通ったので、無料枠ではないと分かった。
失敗した分だけを同じ出力フォルダへ再実行する --resume を、スクリプトに足してもらった。
bench.py run --provider gemini --key-source op --condition masked --thinking off --temperature 0
bench.py run --key-source op --resume <前回の出力フォルダ>
--resume で1本ずつ回し直すと、アプリと同じ設定の1本は再開から約10分で78枚がそろった。
thinking 最小、temperature 0 の1本は52秒、3部位の切り出しは62秒で終わった。
thinking 最小、temperature 0 の Gemini は、金額3項目の全一致が75枚(96.2%)で、既定設定の74枚と変わらない。 所要は1枚2.6秒、費用は0.067ドルで、既定の4分の1だった。 摘要だけは既定のほうが少し良い(71対67)。
結局どちらが優秀だったのか
結果を公開記事にまとめさせたあとで、結論をつかみ損ねて聞き直した。 Gemini がやはり優秀だったのか、それとも日付を見れば DeepSeek のほうが良かったのか。
総合では、ほぼ互角だった。 金額は Gemini(78枚中75対70)、日付は DeepSeek(66対55)が強い。 日付と金額の両方が合った枚数は、DeepSeek が59、Gemini が55だった。 78枚の試験では、4枚の差は誤差の範囲に収まる。
間違え方は違った。 DeepSeek は細い「1」と「7」で落ち、Gemini は年と月で落ちる。 Gemini の日付誤り23件のうち19件が、年か月の誤りだった。 途中の報告では誤りを26件(うち年と月で22件)と数えていたが、記事を書く段で実測値に数え直された。
プロンプトで「その月の伝票の束だ」と伝えていたから、実運用の Gemini はうまくいっていたのではないか、とも聞いた。 これは違った。 今回使ったのはアプリのプロンプトそのままで、年月のヒントは入っていない。 実運用でも Gemini は同じ年月の誤りを出していて、それを自分が画面で直していたことになる。 修正前の出力が残っていないので、何件直したのかは分からない。
条件ごとの表と誤読の実例は、公開記事 手書き伝票のOCRで、DeepSeek V4.1 Flash と Gemini 3 Flash を同じプロンプトで比べた に置いた。
安さと、データの置き場所
費用は78枚で DeepSeek が7円、Gemini が10円ほどで、全量の963枚でも DeepSeek は100円弱、Gemini は130円ほどで済む(1ドル150円で換算)。
それでも、結果を聞いたあとで口にしたのは値段の話ではなかった。 DeepSeek のサーバーにデータが保管されるリスクは、桁違いに高い。 みんなが欲しいのは安い OCR ではなく、顧問先のデータを外に出さない処理環境である。 Claude Code の確認でも、DeepSeek の API は利用規約上、入力が中国のサーバーに保管され、学習にも使われうる。 税理士がそのまま使う選択肢にはならない。
Claude のチームプランや Codex のビジネス向けプランもあるが、やはり高い。 API なら従量課金で学習には使われないものの、予算として取りにくい。 それなら問題は同じなのではないかと聞くと、Claude Code の答えもそうだった。 従量課金はいくらかかるか分からないので予算の行に書けず、金額が小さくても稟議に通らない。
午後、番号表の写真を Gemini Flash で読み直す
別のリポジトリでも、画像から CSV を作る OCR が途中で止まっていた。 どこを確認すればいいかを忘れていたので、置き場所をエクスプローラーで開き、見るべきファイルを選択した状態にしてもらった。
1ページ目を30個ほど照らし合わせたところで、面倒になった。 さっき 1Password に Gemini のキーを入れていたので、それを使ってもらう。 これまでの読み取りは、3.7 Flash 系だったはずだ。 3.8 Flash にして推論レベルを0にし、全画像をもう一度通すといくらかかるかを聞いた。
料金と使えるモデルを調べてもらい、API 用のスクリプトで1枚目のトークン数と精度を実測してから、42枚を全部読み直させた。 正式版の CSV と突き合わせると、5,666件すべてが一致し、数字の不一致はゼロだった。 料金は実測で0.39ドル(約59円)だった。
1つだけ、3つ目の数字が空欄の行が出た。 紙で見ても、3つ目の数字はたしかに書かれていない。 画像と同じなので間違いではなく、空欄のままでよいと伝えた。 その判断は計画書に書き残させ、正式版の CSV はその行が入っていない今のままとして、コミットとプッシュを頼んだ。
元の写真もプッシュして、Mac から取れるのかも聞いた。
HEIC の42枚(合計約59MB)は9月15日のコミットでプッシュ済みで、Mac で git pull すれば取れる。
振り返り
- thinking を切れば落ちる、と思っていた。DeepSeek でも Gemini でも thinking は金額の一致を増やさず、所要と費用だけを押し上げた
!を付けたコマンドを複数行まとめて打つと、2行目以降の!が否定演算子として効く。「失敗」と表示されても、出力ファイルを見るまで失敗とは決められない- 120秒で裏へ回ったベンチは、失敗分だけやり直す
--resumeがあれば、最初から回し直さずに済んだ - 番号表は、人が30個で止めたところを、42枚、5,666件の機械照合が引き継いだ。空欄の1行は、紙を見ても空欄だった
年と月の誤りは、伝票の束の年月をプロンプトに渡せば減るのか。 プロンプトや書き方を工夫すれば精度は上がりそうだが、既存アプリと同じプロンプトという今回の前提から外れるので、まだ試していない。