本番RAGパイプラインに向けたIBM DoclingとPythonによる複雑なドキュメント解析

AI tutorial - IT technology blog
AI tutorial - IT technology blog

6か月前、私たちのチームは本番運用の検索拡張生成(RAG)システムがほぼ必ず直面する大きな壁にぶつかりました。レイアウトが入り組んだ、複雑なPDFの解析です。取り込むべきデータは、四半期ごとの財務報告書や技術仕様書など4万5,000件以上にのぼりました。

どのドキュメントも3段組レイアウト、入れ子になった12列の貸借対照表、インラインの数式表記などで埋め尽くされていました。従来のテキスト抽出ツールではすべてが平坦化されて判読不能な状態になり、表の行はバラバラに崩れ、微分方程式は意味不明な文字列へと化してしまっていました。

そこで、脆い正規表現ベースのパイプラインをIBM Doclingへと刷新しました。導入後、抽出エラー率は約82%低下し、何万ページもの複雑なドキュメントを安定して処理できるようになりました。本稿では、Doclingが従来ツールとどう違うのか、そしてデータ取り込みスタックにどのように組み込むべきかについて、実際の運用経験に基づき率直に解説します。

ドキュメント抽出アプローチの比較

ベクトル検索向けにPDFドキュメントを解析する手法は、主に以下の3つのアーキテクチャに分類されます。

1. ルールベースおよび座標抽出アプローチ(PyPDF、pdfplumber、PyMuPDF)

これらのライブラリは、PDFのバイトストリームに埋め込まれたバウンディングボックスの座標に基づいて生の文字グリフを抽出します。処理速度は極めて高速で(多くの場合1ページあたり40ミリ秒未満)、RAMもほとんど消費しません。

  • 弱点: 段組レイアウトで簡単につまずきます。段組をまたいで文が不自然につながってしまうことが頻繁にあります。また、枠線のない表は崩れ、数式のタイポグラフィは検索不可能な記号の断片に分解されてしまいます。

2. 大規模ビジョン・言語パイプライン(Nougat、OCR + マルチモーダルLLM)

このアプローチでは、ページを高解像度画像にラスタライズし、ビジョンモデルやGPT-4o、Claude 3.5 Sonnetなどの最先端マルチモーダルLLMに直接渡してMarkdownに書き起こさせます。

  • 弱点: クラウドAPIのコストが急増します。100ページのレポートを処理するのに1.50ドルから3.00ドルかかり、レイテンシも1ページあたり3〜8秒まで悪化します。さらに厄介なことに、過密な貸借対照表などでは、LLMによる数値のハルシネーション(幻覚)が依然として発生します。

3. レイアウト認識ビジョンモデル(IBM Docling)

Doclingは速度と精度のバランスが最も優れた選択肢です。DocLayNetなどのデータセットで学習した専用のレイアウト検出モデルと、特化した表構造予測モデル(TableFormer)を組み合わせています。外部APIを呼び出すことなく、自然な読み順を復元し、結合されたセルを再構築し、数式を直接LaTeXに変換します。

長所と短所:本番環境での6か月間の運用実績

数百万ページを処理する中で、Doclingの優れた点と、慎重な調整が必要な課題が明らかになりました。

長所

  • 信頼性の高い読み順の復元: 段組構成の論文でもテキストの流れを正確に追跡し、関係のない段落同士が混ざり合うのを防ぎます。
  • 強牢な表構造認識: 複数行のヘッダーや結合セルをクリーンなMarkdownまたはHTMLテーブルに正確に再構築し、検索コンテキストに破損した数値が混入するのを防ぎます。
  • ネイティブなLaTeX数式出力: 数式がLaTeX文字列として直接抽出されるため、数式対応の埋め込みモデルに対しても数式を完全に保ったまま渡せます。
  • エアギャップ環境対応とプライバシー保護: すべてが自社のVPC内で完結して実行されるため、機密性の高い財務データや医療記録がセキュリティ境界の外に出ることはありません。
  • 後続処理に適したチャンキング機能: ドキュメントのセクション境界を維持しながら、LangChainやLlamaIndexと直接統合できるチャンカーを標準搭載しています。

短所

  • 計算リソースの要求: ほぼリソースを消費しないPyPDFとは異なり、Doclingは内部でPyTorchディープラーニングモデルを実行します。4コアCPUの場合、1ページあたり約1.5〜3.5秒かかります。高スループットが求められるキューでは、GPUによる高速化が不可欠です。
  • メモリ使用量: ワーカープロセス1つあたり、通常2.5GB〜4GBのRAMを消費します。Kubernetes環境でOOM(Out of Memory)による強制終了を防ぐには、コンテナの並行数を慎重に調整する必要があります。
  • スキャン文書処理時のフォールバック: PDFにテキストレイヤーが埋め込まれておらず、EasyOCRやTesseractへのフォールバックが発生した場合、処理時間が1ページあたり6秒以上に跳ね上がることがあります。

推奨アーキテクチャと環境構築

DoclingをWebアプリケーションサーバー内で同期的に実行することは避けてください。Celery、Temporal、AWS SQSなどのバックグラウンドワーカーキューを使用して、データ取り込み処理を切り離します。各ワーカーノードには最低4 vCPUと8GBのRAMをプロビジョニングするか、NVIDIA T4(16GB VRAM)のようなコスト効率の高いGPUを搭載してください。GPUを利用することで、1ページの変換時間を350ミリ秒まで大幅に短縮できます。

コアパッケージとCUDA対応のPyTorchをインストールします:

# Doclingおよびチャンキングユーティリティのインストール
pip install docling docling-core

# CUDA 12.1対応のPyTorchをインストール
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121

本番デプロイ時に予期しないモデルの重みがダウンロードされるのを防ぐため、requirements.txtでこれらのパッケージのバージョンを必ず固定してください。

実装ガイド:抽出パイプラインの構築

以下のスクリプトは、パーサーの設定、構造化された表や数式の抽出、チャンク埋め込みの生成を行う、エンドツーエンドの抽出パイプラインの実装例です。

ステップ1: ドキュメント変換と機能の設定

DoclingはDocumentConverterを介して高レベルの変換機能を提供し、OCRエンジンや表解析の深度を細かく制御できます:

from pathlib import Path
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.pipeline_options import PdfPipelineOptions, TableStructureOptions

def configure_converter() -> DocumentConverter:
    # 解析およびOCRのオプションを設定
    pipeline_options = PdfPipelineOptions()
    pipeline_options.do_ocr = True
    pipeline_options.do_table_structure = True
    pipeline_options.table_structure_options = TableStructureOptions(
        do_cell_matching=True
    )
    
    # カスタムオプションでコンバーターを初期化
    converter = DocumentConverter(
        format_options={
            "pdf": PdfFormatOption(pipeline_options=pipeline_options)
        }
    )
    return converter

converter = configure_converter()
result = converter.convert("sample_financial_report.pdf")
doc = result.document

ステップ2: 表、数式、Markdown出力の確認

変換が完了したら、統合されたMarkdownとしてドキュメントを取得するか、構造化された各要素を個別に確認します:

# ドキュメント全体をクリーンなMarkdownとしてエクスポート
markdown_content = doc.export_to_markdown()
print("--- 抽出されたドキュメントのMarkdownサンプル ---")
print(markdown_content[:500])

# 検出された個々の表を確認
print(f"\n検出された表の数: {len(doc.tables)}")
for idx, table in enumerate(doc.tables):
    print(f"\n--- 表 {idx + 1} ---")
    # Pandas DataFrameに直接エクスポート
    df = table.export_to_dataframe()
    print(df.head())

# 数式を抽出
if hasattr(doc, "formulas"):
    for idx, formula in enumerate(doc.formulas):
        print(f"数式 {idx + 1}: {formula.text}")

ステップ3: RAG埋め込みに向けたレイアウト認識チャンキング

固定文字数での分割は表を途中で分断してしまい、ベクトル検索時のセマンティックな文脈を損なう原因になります。DoclingのHybridChunkerは構造的な境界を考慮し、関連するデータをひとまとめに維持します:

from docling.chunking import HybridChunker

# レイアウトを考慮し、最大512トークンを目標とするチャンカーを初期化
chunker = HybridChunker(max_tokens=512, merge_peers=True)
chunks = list(chunker.chunk(doc))

print(f"生成されたセマンティックチャンクの総数: {len(chunks)}\n")
for idx, chunk in enumerate(chunks[:3]):
    print(f"[チャンク {idx + 1}] 文字数: {len(chunk.text)}")
    print(chunk.text)
    # ベクトルペイロードのフィルタリング用にセクションのメタデータを保持
    print(f"メタデータ: {chunk.meta.headings}\n")

これらの構造化チャンクを埋め込みモデルに渡すことで、検索エンジンに対してクリーンで整合性のある文脈を提供できます。表は損なわれず、文は自然な読み順を保ち、数式は正確な表記のまま維持されます。

Share: