0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

本文検索の対象にPDFを追加しようとしたら、オーパスとトラブった話

0
Posted at

はじめに

私は事務員です。プログラマーではありません。

前回の記事で、10年以上育てているExcel VBA製のファイル管理ツール「ファイル一覧」に本文検索を足し、①ファイル名 → ②本文 → ③AIが読んで答える、の三段構えにした話を書きました。

その最後で、次は「AI要約説明」をやると書きました。ところが、その前に片付けておかないといけない宿題がありました。本文検索のPDF対応です。テキスト系ファイルと、新形式Office(.docx / .xlsx / .pptx)のZIP展開読みまでは対応済み。残っていたのがPDFでした。

今回はこれをClaude Code(Opus)と一緒に片付けようとして、途中で盛大にぶつかった話です。技術の話でもありますが、どちらかというと「AIとの分業で何がずれるか」の記録です。

TL;DR

  • 本文検索の対象にPDFを追加しました。文字を持たないスキャンやAI生成のPDFがあるので、OCRが要ります
  • AI(Opus)の第一案は「全PDFの本文を外部の索引ファイル1本に畳む」方式。技術的には筋が通っていて、実際に動きました
  • しかし「外部に台帳を作って管理する」形は私の運用に真っ向から反するので、全部撤去しました
  • 答えは25年前からありました。DocuWorksと同じ、文字はファイル自身に持たせる。OCRの文字をPDF自身に不可視で埋めます
  • 検索の瞬間は文字層を抜くだけ。実測3本0.26秒。外部には何も残りません
  • 教訓:技術の正解と運用の正解は別物。運用の正解はコードのどこにも書いていないので、流儀を言葉にして渡すしかない

PDFが厄介な理由

本文検索の仕組みは単純で、ファイルを開いて文字列を探すだけです。テキストファイルはそのまま読める。Office新形式は実体がZIPなので、Windows標準のtar.exeで本文XMLだけ展開して読める。

PDFはそうはいきません。

  • 中身は圧縮されたバイナリで、テキストとして読んでも文字にならない
  • 文字を持っているPDFでも、外から見えるのはフォントのグリフ番号
  • スキャンやAI生成のPDFは、そもそも文字を持っていない(ただの画像)

つまり「文字を取り出す工程」と「画像から文字を起こす工程(OCR)」が要ります。ここはVBAだけでは詰むので、Pythonに出ました。PyMuPDF内蔵のTesseractを使うと、Ghostscriptも別インストールのTesseractも不要で、OCRまで1本で済みます。

OCRの精度は正解の分かる日本語文書8ページで実測して決めました。

エンジン 再現率 速度
Tesseract dpi200 94.3% 1.10秒/ページ
Tesseract dpi300 93.5% 1.56秒/ページ
Windows標準OCR 84.8% 0.09秒/ページ

dpi300より200の方が速くて精度も上、という結果は測ってみないと分かりませんでした。

AIの第一案:索引ファイル方式

さて、取り出した文字をどこに置くか。ここでAIが作ったのが「索引方式」でした。

全PDFの本文をタブ区切りのファイル1本に畳む。行の形式は「PDFパス、ページ、本文」。差分更新のために、ファイルの中身から指紋(サイズ+先頭末尾のハッシュ)を取って台帳を兼ねる。検索の瞬間はPythonを呼ばず、この1本を読むだけだから速い——。

技術的には筋が通っています。実際に動きました。16本のPDFから索引ができて、検索も当たる。

途中で崩れも見つけました。AIにHTMLから作らせたレポートPDFは文字が1つずつ座標指定で置かれているため、テキストを抜くと1文字ごとに改行が入り、索引が1文字1行になっていました。12ページで625行、1行平均5字。これでは「北秋田」で検索しても行の中に「北」しかないので永久に当たりません。ページ単位で本文を繋ぎ直して解決しました(625行が19行に、1行平均5字が137字になりました)。ついでにOCRが「令和8年」を「令和⑧年」と読む丸囲み数字の誤読も、機械変換で戻すようにしました(1回の索引で617個ありました)。

直して、検索も当たるようになって、さあ次へ——というところで、根本からひっくり返ります。

「その台帳っていうのは何だ?」

Claude Opusが索引ファイルの説明をした瞬間、問い詰めました。

外部に台帳を作って管理する方式は、私の流儀に真っ向から反していたのです。

  • ファイルを移動したら台帳のパスが狂う
  • 台帳は目に見えない場所にあり、中身も読めたものではない
  • 誰が面倒を見るのか。AIしか見られないファイルが増えていく

どれだけ技術的に正しくても、管理できないものは使えない。この却下は感覚論ではなくて、私には比較対象がありました。

「DocuWorksはわかってるか」

昔、DocuWorksを使い込んでいました。富士ゼロックス(現・富士フイルムビジネスイノベーション)が1996年に出した「電子の紙」ソフトです。実は「ファイル一覧」のファイル管理の仕方も、DocuWorks Deskをかなり参考にしています。

DocuWorksは四半世紀前から、スキャンした画像文書にOCRの文字を埋め込んでいました。文字は文書自身の中に入る。だからファイルを移動しても改名しても、検索はそのまま効く。外部に台帳なんて持たない。

つまり答えは25年前から現場にありました。文字はファイル自身に持たせる。

作り直し

ここからはモデルをClaude Fableに切り替えて、作り直しました。方針転換後の構成はこうなりました。

① 画像PDFには文字を埋め込む

一覧でB列のセルを選んで実行すると、そこから下のPDFに対して、OCRで読んだ文字を元のページと同じ座標へ不可視で重ねます。見た目は変わらず、Ctrl+Fだけ効くようになる。いわゆる「検索可能PDF」です。

実測はこうでした。

  • 3ファイル42ページ:18.3秒
  • 容量:13.4MB → 13.6MB(ほぼ不変)
  • 更新日時:元の値を復元(ここを外すと一覧の日付順が一日で潰れる)

原本を書き換える処理なので、実行前に件数を出して確認を挟んでいます。

② 本文検索は、検索の瞬間に文字層を抜く

本文検索の対象拡張子に.pdfを追加。検索開始時に、対象のPDFだけをPython1回の呼び出しでまとめて文字層抽出します(1本ずつPythonを起動すると起動コストで遅くなるため)。実測で3本0.26秒。抜いた結果は読んだら即削除で、外部に何も残しません。

結果一覧の抜粋列には「3頁 …」とページ番号が付くので、どのPDFの何ページに当たったかまで分かります。

③ 道具の置き場は「見える1フォルダ」

PythonスクリプトとOCRの言語データは、デスクトップの「道具」フォルダに全部まとめました。場所の定義はブックの設定シートに1セル。引っ越しは「フォルダを移す→セルを書き替える」の2手だけです。

コードに固定パスは書きません。最初はスクリプトの置き場をコードに直書きしていて、これも「引っ越しできないだろ」と直させました。

教訓:技術の正解と運用の正解は別物

今回の件を一言でまとめるとこうなります。

AIは技術の正解を出してくる。しかし運用の正解は、使う人の頭の中にしかない。

索引方式は技術的には悪くない設計です。検索エンジンの世界では常識的な作りでもある。でも「ファイルを手で動かし、目で見て管理する」私の運用には合わない。この判断基準はコードのどこにも書いていないので、AIには見えません。

だから、ぶつかったときに流儀を言葉にして渡すしかない。今回吐き出した言葉はこんな感じです。

  • 台帳を作るな。データはファイル自身に持たせろ
  • 連続処理は選択したセルから下へ、空白まで
  • 道具は見えるフォルダに1つ、必要なものを全部そこへ
  • 成功は無言。終わったらステータスバーも消せ

一度言語化した流儀は、次の開発からは最初から効きます。トラブった時間は、流儀を仕様書に変える時間だったと思えば、悪くない投資でした。

おまけ:OpusとFableで性格が違う話

今回、途中でモデルをOpusからFableに切り替えたわけですが、性格の違いに驚きました。好きなYouTuberがOpusのことを「偏屈」と言っていて、正直、賛成です。頼んでいないことを良かれと思って足してくる。台帳を勝手に設計する、置き場を勝手に決める。今回のトラブルの根も、結局そこでした。Fableに切り替えてからは、言ったとおりに速く組んで、実測で確かめて、黙って終わる。スムーズでした。

不思議に思ってFable本人に聞いてみたら、こういう答えでした。モデルの性格は、土台の学習の後にある「仕上げの工程」でほぼ決まる。大量の返答例を人間が評価して「こういう答え方が良い」と磨き込む工程があり、そこで何を良しとするかのさじ加減がモデルごとに違う。慎重さを重く見れば但し書きの多い性格になり、簡潔さを重く見れば黙って手を動かす性格になる。同じ会社の兄弟でも、育て方の方針が違えば別人になる。しかも作っている側も性格を完全には設計できず、出来上がってみると想定外の癖が付いている——人間の子育てに似ていて、再現もしきれないのだそうです。

公平のために書いておくと、Fableがスムーズだったのは、Opusとぶつかる過程で私の流儀が言葉になった後だった、という事情もあります。それでも実務的な結論は変わりません。モデルの性格の違いは、流儀の言語化で吸収する。 一度言葉にしてメモに残した流儀は、次にどのモデルが座っても最初から効きます。

1.png

おわりに

DocuWorksは主流の座をPDFに譲りましたが、製品は今も現役ですし、設計思想は正しかった。その思想を、勝った側の形式であるPDFの上で、自分の道具に再現した——今回やったのは結局それだけのことです。

道具は消えても、正しい設計は古びない。AIと組んで一番効くのは、そういう「現場が昔から知っていた答え」を言葉にして渡せる人なのかもしれません。

オーパスに悪気はありませんでした。あれはあれで、技術の正解を出す性格に育てられただけです。

お前は、お前です。

次回こそ、予告していた「AI要約説明」の話に戻る予定です。なお「ファイル一覧」は前回書いたとおり、まだ公開していません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?