- 検索エンジンに PDFインデックス化 が追加されたが、PDFから検索に使える構造化テキストを得ることはHTMLの処理よりはるかに難しい
- PDFはテキスト文書というより 座標ベースのグラフィック形式 に近く、グリフが回転・重なり・順序の混在した状態で配置されうる
- PDFBoxの PDFTextStripper は出発点にはなるが、見出し・段落のような意味構造を十分に保持できず、検索関連性の判断には限界がある
- 見出しと段落を区別するには、文書全体基準より ページごとのフォントサイズ統計 や行間の中央値といったヒューリスティクスのほうが有用である
- 完璧なPDFテキスト抽出は難しいため、検索エンジンでは見出し・要約・本文構造を安定して得る 十分に良い解法 が現実的な目標となる
PDFインデックス化を追加して見えてきた問題
- 検索エンジンは最近 PDFファイル形式 をインデックス化できるようになり、この変更は数か月かけて展開される予定である
- 検索エンジンにとっては整った HTML が最も扱いやすいが、PDFは同レベルの構造化テキストをそのまま提供しない
- 見た目にはテキストが見えていても、PDF内部から検索可能な文や段落を復元する作業は別問題に近い
PDFがテキスト抽出を難しくする理由
- PDFは一般的なテキスト形式ではなく グラフィック形式 に近い
- 内部表現は文や段落ではなく、「紙」の上の座標に グリフ を配置した形に近い
- グリフは次のような状態になりうる
- 回転している
- 互いに重なっている
- 読む順序と異なる位置に現れる
- 付随する意味情報が非常に少ない
- PDFビューアやブラウザで
ctrl+f 検索ができること自体、かなり驚くべき結果である
- ビジョンベースの機械学習モデルは現時点では有望なアプローチかもしれないが、GPUのない単一サーバーで数百GBのPDFを処理する規模には向かない
PDFBoxで始められるが、それだけでは不十分
- 完全に未開拓の問題ではないため、PDFBox の PDFTextStripper クラスを出発点にできる
- PDFTextStripperはPDFからテキストを抽出するが、名前どおりテキストを剥がし取ることに重点がある
- 検索エンジンでは 見出し のような意味情報が重要な関連性シグナルだが、基本的な抽出結果だけではこうした構造を十分に得にくい
見出しを見つけるためのヒューリスティクス
- 最も単純な見出し検出は、他のテキストから離れている semibold以上の太さの行 を見つける方式である
- しかし、すべての見出しが太字とは限らず、多くの文書では フォントサイズ で見出しを区別している
- グローバルなフォントサイズの基準点は文書ごとにぶれる
- ある文書では本文に10pt、見出しに16ptを使うことがある
- 別の文書では本文に14pt、見出しに20ptを使うことがある
- 文書全体より ページごとの統計 のほうが適している場合がある
- 1ページ目は要約や著者一覧のため、残りのページとフォント選択が大きく異なることがある
- 例の文書では文書タイトルに17サイズのフォントが使われ、その後の見出しには14サイズのフォントが使われている
- 各ページには概して本文に相当する 支配的なフォントサイズ がある
- ページごとの中央値フォントサイズに約 20%程度の係数 をかけると、見出しをかなり安定して識別できる
- 例外は残るが、重要なケースの大半は拾える
複数行の見出しを一つにまとめる
- 見出しはスタイルの都合で複数行に分かれることが多く、連続した見出し行を一つにまとめる必要がある場合がある
- いつ結合するかを決める基準は単純ではない
- 例では次の条件が同時に現れる
- 2行の見出し
- 右揃えの見出し
- 見出しの後に続くsemiboldの著者名
- 数行下にある太字ではない見出し
- 字下げは段落区切りのシグナルとしても使われるため、右揃えの見出しは判断をさらに難しくする
- 同じフォントサイズと太さを持つ連続見出しを結合する方式は概ねうまく機能するが、望ましくない結果も生みうる
段落識別で行間が作る落とし穴
- PDFTextStripperは 段落識別 をかなりうまく行う
- 行間と字下げをあわせて見ながら、段落を分ける位置を判断する
- 改善の余地は特に 行間ロジック にある
- PDFTextStripperの大きな問題は、行の分離に固定基準点を使っていることにある
- 基準点より行間が広ければ、同じ段落ではないと判断する
- 文書ごとに行間が異なる点を十分に反映できていない
- 学術ドラフトやプレプリントでは 1.5〜2倍の行間 がかなり一般的である
- 基準値を大きくしすぎると、一部の見出しが本文段落に吸収され、見出し識別を妨げる可能性がある
行間統計で補正する
- フォントサイズ処理と同様に、行間にも 統計的手法 を適用できる
- テキストページで行間距離のヒストグラムを作ると、大きな外れ値が現れる傾向がある
- 平均値は外れ値に振られやすいが、中央値 は本文で使われた行間と正確に一致する場合がある
- 中央値に一定の係数をかければ、多様な行間でも耐えられる段落分離ヒューリスティクスを作れる
現実的な目標は完璧さより安定性
- PDFからテキストを抽出する作業を完璧にするのは難しい
- PDF形式自体がそのために設計されておらず、「十分に良い」解法を選ぶときにはさまざまな トレードオフ が生じる
- 検索エンジンが特に関心を持つのは次の情報である
- 見出しのような関連性シグナル
- 要約の識別
- 残りのテキストのある程度一貫した構造
- こうした目標であれば、大半の関連文書を比較的無理なく処理できる解法に近づける
使用されたサンプルテキスト
Can Education be Standardized? Evidence from Kenya (2022) - Working Paper
Guthrie Gray-Lobe, Anthony Keats, Michael Kremer, Isaac Mbiti, Owen W. Ozier
The theory of ideas and Plato’s philosophy of mathematics (2019)
Dembiński, B.
The role of phronesis in Knowledge-Based Economy (2024)
Anna Ceglarska, Cymbranowicz Katarzyna
1件のコメント
Hacker News の意見
以前、何かに何カ月も何年も没頭して専門家レベルになっていたことをすっかり忘れていて、後になって「これは新しくて面白いな」と感じることがある。
面白いことをかなりたくさんやってきたのに記憶から消えていて、何かをきっかけに思い出すまでは、人生を新しく始める人のように感じる。
6〜7年ほど前に PDF と OCR でかなり良い作業をした記憶がおぼろげにあり、探してみると Tesseract だったようだ。
ヒューリスティックに近いハックで、当時の Poppler の古い API は、アクセシビリティ API に適した形でテキストランを表現していなかったように思う。
多段組み選択機能はある程度取り込まれたが、その後の性能改善提案は、ヒューリスティックが少し変わることで一部の状況で選択結果が変わるという理由で、メンテナーを説得するのが難しかった。
そもそも「正解」が一つではないので、結果の一致を求めるのは筋が通らなかった。そうして kpdf の多段組み選択 がある程度できた。
今では、この用途には Tesseract を直接使うほうが合理的である可能性が高い。
この狂気がいつ終わるのか分からない。
デフォルトの精度がより高く、GPU アクセラレーションに対応し、複数のテキスト検出・認識モデル構造を組み合わせ可能なパイプラインとして実装している。
PyTorch や TensorFlow で学習・ファインチューニングすれば、特定ドメインでの性能もさらに引き上げられる。
なので、実際にそういうことは起こる。
次の仕事はまったく別の分野なので、また基礎から始めることになる。
誰か PDF 向けのブラウザ開発者ツールのようなものを作ってくれるとよい。
「要素を検査」のように PDF の コンテンツストリーム をソースとして見て、
BT … ETのテキスト演算子やTjのテキスト配置演算子が各ピクセルをどのように指定・生成しているのか確認できるとよい。最近の流れのように、視覚モデルが PDF を人間のように「見て」読むのとは逆だが、実際に PDF ファイルの中に何が入っているのか理解できるなら、そのほうがはるかに良い。
PDF の内容を検査するツールはいくつかあるが(https://news.ycombinator.com/item?id=41379101)、PDF オブジェクトのレベルで止まっていて、コンテンツストリーム全体が一つのオブジェクトとしてしか見えない。
たとえば記事で言及されている https://bfi.uchicago.edu/wp-content/uploads/2022/06/BFI_WP_2... の PDF 6ページ目、ページ番号で8には、
BT、フォント設定、Td、TJといった演算子でテキストが配置されている。こうした「ソース」とレンダリングされた PDF を横に並べ、一方にマウスを乗せるともう一方の対応領域がハイライトされるような形なら、HTML ページのようにデバッグできそうだ。
たとえば各
Tjが DOM 要素一つになり、TJは複数要素のまとまりになるような形だと思う。元の文書を正しく反映しなければ動かないので、かなり忠実に変換する可能性が高い。
cpdf -output-json -output-json-parse-content-streams in.pdf -o out.jsonで JSON を作成し、その JSON を編集した後、cpdf -j out.json -o out.pdfで再び PDF に戻せる。ただしリアルタイムの双方向連携はない。
ただしページを検査する方式ではなく、コンテンツツリーをたどる方式で、ページ上のオブジェクトはハイライトしてくれた。
コマンド単位までは無理で、オブジェクトやストリームのレベルまでだった。
https://observablehq.com/@player1537/pdf-utilities
PDF を人間のように「見て」テキストだけを読むのではなく、表・画像・テキスト・数式・手書き文字まで含む内容を 理解 しようとする方向だ。
そのため PDF をパースし、複数のモデルを動かして Markdown 片や JSON を抽出し、実データを AI エージェント・LLM・その他のアプリケーションに入れられるようにしている。
https://tensorlake.ai
「PDFをテキストに」は単純化しすぎた表現
このカテゴリーの中には、1) 検索インデックスやベクターDB入力のための信頼性の高いOCR、2) 特定の値を取り出す構造化データ抽出、3) 住宅ローン申請の自動化のようなエンドツーエンドの文書パイプラインがある
Marginaliaが解くべきなのは1番のOCRで、Gemini Flashのようなモデルのおかげで急速に汎用化している
複数の企業が既存のOCRパイプラインをFlashに置き換えてコストを大きく削減しているのを見て、かなり驚いた
しかし2番と3番ははるかに難しく、生のOCR出力から本番環境のミッションクリティカルな文書パイプラインに至るには、まだ大きな隔たりがある
LLMと視覚言語モデルは魔法ではなく、100%の自動化を期待すると驚かされることになる
データセットの構築・ラベリング、
分類 -> 分割 -> 抽出パイプラインのオーケストレーション、不確実性の検知と人手によるレビュー、ファインチューニングなどが依然として必要長期的にはほぼ完全な自動化に近づける可能性はあるが、時間と労力が必要で、未来がこの方向に向かっているのは明らかに見える
関連して、https://extend.ai というLLM文書処理会社を始めた
ビジネスワークフローは、決まった数種類の文書だけを扱うことが多いが、汎用PDFリーダーはユーザーがどんな文書を開くか分からない
テキストだけでなく、表、ヘッダー・フッター、脚注、見出し、数式なども認識しなければならない
人が利用するものなので誤りを最小限にする必要があり、不要なときにOCRを使うべきではなく、PDFに含まれる基本テキストを活用しつつ意味構造も抽出しなければならない
結局、画像だけのPDFと、コンテンツストリームから情報を得られるPDFのために、2つの経路が必要になる
ところがコンテンツストリームには、実際の画面表示と異なるテキストが入っていることがある。白い文字で隠した情報や、Unicodeのアクセント付き文字の代わりにコマンドでアクセント記号を描くLaTeX式の実装のような場合がある
たいていはユーザーの低性能な端末上でローカルアプリとして動き、サーバーやサブスクリプションモデルがない可能性が高いため、クラウドAIモデルも使えない
アクセシビリティソフトウェアの利用者は、印刷してペンで記入するのが難しい場合があるため、仕様に沿ったきれいなフォームだけでなく、印刷用フォームも処理しなければならない
これはまだ解決にまったく近づいていない未解決問題であり、現在の解法はいずれも何らかの形で不十分で、上の5点すべてをきちんと解く単一の解法はない
画像ラベリングは非常に得意で、単一カラムのテキスト・中央揃えの単一段階の見出し・1ページあたり画像や表が1つといった単純な文書なら問題ない
ほとんどのMVPデモはこうした例を見せている
しかし表と画像が混在する複雑な文書では、まだはるかに多くのパラメータがあってようやく「ひどい水準」にでもなりそうだ
現状ではハルシネーションがひどすぎて、上に見出しがあり中央にデータがあり下に要約があるような単純な表でさえ、そのまま使うのは難しい
最近のOCRは優秀だが、文書の全体構造を保つのははるかに厄介
大きな文書で一貫したHTMLを得るのはまだ遠いように見え、LLMを複数回通して文書構造を抽出し、それをページごとの抽出時の文脈として入れる方式なら、Markdownではある程度よい結果が出る
Appleでは長年この問題にかなりうまく取り組んでいたが、核心的なコツはすべてを幾何構造として受け取り、クラスタ分析で単語間隔と文字間隔を区別しようとしたことだった
多くのPDFでは非常にうまく機能するが、PDFの種類があまりにも多様なので、結果がいまいちな場合も常にある
今またやるなら、幾何構造は維持し、OCRは完全に避けつつ、機械学習を使うと思う
機械学習の大きな利点は、既知のテキストからPDFを生成する既存ツールを活用して、学習段階を完全に自動化できる点にある
Bertrand SerletがWWDC 2009でこの機能を発表した場面: https://youtu.be/FTfChHwGFf0?si=wNCfI9wZj1aj9rY7&t=308
より良い解法は、PDFの中に編集可能な元文書を一緒に埋め込むこと
LibreOfficeでは簡単にでき、圧縮がよく効くため通常は容量もほとんど増えない
そうすればテキストと意味についてはるかに良い情報が得られ、既存のPDFリーダーでもうまく動作する
電子証拠開示の分野では、証拠提供者が相手側の弁護士に内容を消費しにくくさせるため、わざとPDFとしてダンプすることがよくある
双方に資金が潤沢なら障壁にはならないが、たとえば国選弁護人にはPDFを読みやすい形式に処理する人を雇う資金がなく、処理時間がはるかに長くなり、被告人への心理的負担が大きくなる
そもそもデータを適切に処理できないことすらある
解決策はこうした行為を違法化すること
たとえば盗聴データは標準化された機械可読形式で提供されるべきで、単なる技術的摩擦が刑事手続の結果に影響を与えることに倫理的な理由はない
この解法が効果を発揮するまで、どれくらい時間がかかるのか疑問だ
会社にもこうしたファイルが数千個あり、一部はひどいスキャンで、一部にはAdobe OCRが埋め込まれているが、大半には何もない
しかし一般的にはそのような制御権はない
次のPDFは実は
.txtファイルである。.pdf拡張子で保存すればPDFビューアで開けるし、テキストエディタで修正することもできる。たとえばこのテキストファイルを編集すると、PDFを開いたときに画面に表示されるテキスト、フォント、文字サイズ、行間、1行あたりの最大文字数、1ページあたりの行数、用紙の幅と高さ、縦向き・横向きまで変えられる。
例は
%PDF-1.4で始まる最小限のPDF構造で、Catalog、Pages、Font、Page、Contents、xref、trailerなどをテキストで直接書いた形である。PDFはテキストのために作られたものではなく、レイアウトとグラフィックスのために作られたものだ。
例としてはよいが、各行は1文字ごとに1回の呼び出し、1単語ごとに1回の呼び出しに分割されることもあり、順序が入れ替わることもある。
PDF 1.0リファレンス文書の“2.3.2 Portability”では、PDFファイルを7ビットASCIIファイルとして説明しており、画像や特殊文字を含む文書もASCIIの印字可能な部分集合だけを使って記述するとされている。
その結果、さまざまなハードウェアやOS環境で非常に高い移植性を持つと説明している。
https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
最近のPDFのほとんどは、すべてのオブジェクトがdeflateで圧縮されている。
さらに追いかけにくくするため、多くのPDFは大半のオブジェクトをオブジェクトストリーム型のオブジェクト内にまとめ、そのうえで再び圧縮している。
そのため
6 0 Rの終端を追跡しようとしても、テキストエディタで6 0 Objを検索することはできない。この記事で説明されている難しさを示す、気に入っている文書がある: https://academic.oup.com/auk/article/126/4/717/5148354
最初のページから典型的な2段組テキスト、中央揃えのタイトル、2つの段の間に入り込んで行長とインデントを変えるテキストボックスがある。
その後には奇数ページ・偶数ページごとに変わるヘッダー、大きく異なるセクション見出しのルールまで出てくる。
さらに悪いことに、段落間の追加スペースがなく、先頭行のインデントも常にあるわけではないため、あらゆる難しさが詰まっている。
95%くらいはかなりうまくいき、MacのPDFKitとPreviewでは長年それで十分だった。
PDFを作成した元のアプリ、たとえばワードプロセッサを思い浮かべると、自身のテキストバッファからPDFコンテキストへ、かなり合理的な順序でテキストをレンダリングしていた可能性が高い。
そのため2段組の文書でも、左の段から右の段へテキストが正しく流れる場合が多く、PDF内でもすでに正しい順序で入っていた。
ただしページフッターやヘッダーについては、PDF生成アプリがどの順序でコンテキストに吐き出したのかは分からない。
PDFからテキスト、まして構造化されたテキストを取り出すのはまったく簡単ではない。
HTML文書から表をスクレイピングするのは、サイトがすべてを
divにするアンチパターンを使っていても単純なことが多く、意味のある要素を使っていればさらに簡単だ。PDFはそうではない。
形式の専門家ではないので、意味構造のサポートがどの程度あるのかは分からないが、レンダリングしたときにだけ表に見える、グラフィックス要素とテキスト要素の緩い集合で表を作っているPDFを多く見てきた。
実際には、Poppler PDFユーティリティでPDFをHTMLに変換したうえで、想定される表ヘッダーを探し、各値のx座標から列を計算して行ごとの値を抽出する方法で、表データ抽出にかなり成功した。
汚いやり方ではあるが、必要な用途には安定して動作し、間隔が不ぞろいだったり行の途中に改行が挟まったりする書式付きの通常テキストよりはるかにましだった。
いろいろ雑多な機能もあるが、中核となる目標は
page.find('text:bold:contains("Summary").below().extract_text()のように、より人間らしい方法でやり取りすることだ。PDFごとに個別対応の悪夢なので、抽出が難しい例を集めて方法論ライブラリの基盤にしようとしているところだ。
https://jsoma.github.io/natural-pdf/
https://badpdfs.com/
C++アプリに統合できて、無料または数百ドル未満のPDF表抽出ライブラリを知っている人がいたら教えてほしい。
同じPDFを作る方法は本当にたくさんある。
あるツールは、グラフィックエディタからテキストとグラフィックスを含むレイアウトをエクスポートするのに近く、別のツールはワードプロセッサのように単語が先にあるテキストとグラフィックスをエクスポートするのに近い。
生成アプリが情報を扱う観点は、PDFの出力方法にもよく影響する。
既成のユーティリティを探すなら、cisdem のようなツールは、ローカルユーザー向けに構造化データをかなりうまく抽出できる水準までは解決しているほうだ。
こうしたツールは多く、構造化データ対応をうたうものも多いが、やろうとしている作業とうまく合っている必要がある。
PDFは表示形式である
人間の目とプリンターに最適化されており、機能はますます増えてきた
機械同士のデータ転送手段としてはひどいものだが、人が読んだりA4の1ページを保存したりするには非常に優れている
.pdfがテキストを保存しているのだからそのテキストが欲しい、という前提で始めるなら、単に目を鍛えろと言っているようなものだそうでなければ、非常に複雑なものを扱わなければならない
まず、そのテキストが本物のテキストなのか画像なのかからして問題になる
人間の目なら眼鏡をかけ直すだけで問題なく読めるが、パーサーはセグメンテーションエラーを起こして壊れる可能性がある
PDFは人間が読むためのものなので、PDFを読むには人間をまねる必要がある
以前、おもちゃのPDFパーサーを作ってみたことがあるが、この形式がどう動いているのかを知って大いに戸惑った
その点を考えると、テキストの多い用途にPDFが頻繁に使われているのはなおさら奇妙だ
特に請求書のようなものを思い浮かべる
デジタルシステムはファイルからデータを簡単に抽出できるべきであり、同時に人間にとっては見やすくフォーマットされているべきだ
技術業界がより良い形式へ移行すれば、ずっと良くなると思う