文書画像にクエリを投げて対応する値を抽出する【Azure, Document Intelligence, QueryFields】

# 概要
Azure Document IntelligenceはMicrosoft Azureが提供するAIサービスの一つであり、文書処理とデータ抽出のための高度なツールとAPIを提供しているものです。このサービスは、さまざまな形式の電子文書から情報を読み取り、理解し、利用可能なデータに変換する能力を持っています。

`Layout`モデルを用いると文書のレイアウト情報を抽出することができます。さらに、アドオン機能として追加で料金を払い`QueryFields`を有効にすると、**ユーザーが自由に指定した項目名に対して画像から該当する値を抽出することができます。** 今回はこの`QueryFields`の処理イメージとPython SDKを用いたAPIの操作の紹介となります。


https://learn.microsoft.com/ja-jp/azure/ai-services/document-intelligence/concept-layout?view=doc-intel-4.0.0

https://learn.microsoft.com/ja-jp/azure/ai-services/document-intelligence/concept-query-fields?view=doc-intel-4.0.0

# 処理例
まずはどのように利用できるのか、結果を示します。
- クエリとして指定した、欲しい項目名（20個まで指定可）
    - `DocumentTitle`: 文書名
    - `CustomerName`: 顧客名
    - `InvoiceDate`: 請求日
    - `InvoiceNumber`: 請求書番号
    - `OurCompanyName`: 当社名
    - `Subject`: 件名
    - `Subtotal`: 小計
    - `TotalCost`: 合計
:::note info
参考：指定する項目名はキャメルケース（camelCase）またはパスカルケース（PascalCase）が推奨されていますが、日本語でも指定可能でした。
:::
- 抽出結果の可視化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2249065/fef8f9ec-a1b8-9e0b-8856-7640b04c93c0.png)

<details><summary>参考：元画像（折り畳み）</summary>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2249065/a5199e49-af0d-a202-47f6-e7878bcee89c.png)

画像はExcelのテンプレートを改変してダミーデータで作成しました。
</details>

- 抽出結果
```text
クエリ: CustomerName, 値（確信度: 0.995）: デ ジ タ ル イ マ ジ ン 株 式 会 社
クエリ：'DocumentTitle'は見つかりませんでした。
クエリ: InvoiceDate, 値（確信度: 0.995）: 2024 年 4 月 1 日
クエリ: InvoiceNumber, 値（確信度: 0.995）: INV00040101
クエリ: OurCompanyName, 値（確信度: 0.802）: 株 式 会 社 XXX Solutions DX 推 進 部
クエリ: Subject, 値（確信度: 0.644）: コ ン サ ル テ ィ ン グ & デ ザ イ ン サ ー ビ ス
クエリ: Subtotal, 値（確信度: 0.899）: ¥750.00
クエリ: TotalCost, 値（確信度: 0.446）: ¥825.00
```
## 所感
- 活用先として、生成AIを用いたRAGの応用が考えられます。画像に対してほしい項目をLLMに考えさせて、問い合わせると対応する情報が抽出できるものです。
- もうひとつすぐに出てくる活用先として、フォーマットの定められていない帳票から、事務処理上チェックが必要な記載を自動で抽出するものに利用可能と考えます。
:::note warn
料金の参考情報として、`QueryFields`機能は通常のレイアウトモデルの10＄/1,000ページに対して、追加で10＄/1,000ページとなります。
`QueryFields`では文書のレイアウト情報やOCR情報も抽出することができますが、精度と処理速度の要件を満たしているようであれば、GPT-4Vを使用するという選択肢もあるため、機能面やコスト面での比較が必要になる場合があります。ただし、Document Intelligenceのカスタム抽出機能よりは安いですね（50＄/1,000ページ）。
（※価格は記事公開時点の情報であり、変動する可能性があるため、最新情報を[公式サイト](https://azure.microsoft.com/ja-jp/pricing/details/ai-document-intelligence/#pricing)で確認してください。）

:::
- 抽出は現時点では安定しないこともあるようです。例えば、今回は金額について、`TotalCost`（合計金額）のみを指定している状態では小計の`¥750.00`を誤って抽出してしまっていました。しかし、指定する項目名を増やすなどの工夫でうまくいくことがあります。`Subtotal`（小計）と`TotalCost`（合計金額）を両方抽出するように指定すると正しい値を抽出することができました。
- 文書名は別途帳票を変えたり、項目名を変えたりしましたが、抽出がいまいち安定しませんでした。


# 処理コードサンプル
APIを用いて処理を行うサンプルを以下に記載します。ちなみにDocument Intelligence Studioからでも機能を利用することが可能ですので、簡単に試してみたい場合はDocument Intelligence Studioから試すのが良いと思います。

- Document Intelligence Studioを用いる例

https://learn.microsoft.com/ja-jp/azure/ai-services/document-intelligence/concept-query-fields?view=doc-intel-4.0.0

## 実行条件
- `azure-ai-documentintelligence==1.0.0b1`
- 作成するDocument Intelligenceのリソースのリージョン：米国東部
    - [確認時点](https://learn.microsoft.com/ja-JP/azure/ai-services/document-intelligence/whats-new?view=doc-intel-4.0.0&tabs=csharp#february-2024)では、当該機能を利用するAPIバージョン`2024-02-29-preview`が利用可能なリージョンは米国東部、米国西部 2、西ヨーロッパのいずれかとなるためです。

## コードサンプル
```python
import base64

from azure.core.credentials import AzureKeyCredential
from azure.ai.documentintelligence import DocumentIntelligenceClient
from azure.ai.documentintelligence.models import (
    AnalyzeDocumentRequest,
    AnalyzeResult,
    # ContentFormat,
    DocumentAnalysisFeature
)

endpoint = "xxx"    # Document Intelligenceのリソースから得たものを指定
key = "xxx"         # Document Intelligenceのリソースから得たものを指定
image_path = "xxx"  # サンプル画像を配置したパスを設定

query_fields = [
    "DocumentTitle",
    "CustomerName",
    "InvoiceDate",
    "InvoiceNumber",
    "OurCompanyName",
    "Subject",
    "TotalCost",
    "Subtotal"
]

def get_analyze_result(
    query_fields: list[str],
    endpoint: str,
    key: str,
    image_path: str,
    api_version: str = "2024-02-29-preview",
    polling_interval: int = 3,
) -> AnalyzeResult:

    document_intelligence_client = DocumentIntelligenceClient(
        endpoint=endpoint, credential=AzureKeyCredential(key),
        api_version=api_version, polling_interval=polling_interval
    )
        
    with open(image_path, "rb") as f:
        data = f.read()
    data_b64 = base64.b64encode(data).decode("utf-8")
    poller = document_intelligence_client.begin_analyze_document(
        "prebuilt-layout",
        AnalyzeDocumentRequest(base64_source=data_b64),
        # MARKDOWNを指定すると、QueryFieldsが効かない。
        # output_content_format=ContentFormat.MARKDOWN,
        features=[DocumentAnalysisFeature.QUERY_FIELDS],
        query_fields=query_fields
    )
    result: AnalyzeResult = poller.result()
    return result

analyze_result = get_analyze_result(query_fields, endpoint, key, image_path)

assert analyze_result.documents is not None, "No Query Fields Result."

for doc in analyze_result.documents:
    for field_name, field_data in doc.fields.items():
        if field_data.bounding_regions is None:
            # 座標情報がない場合は該当するものを見つけられていない。
            print(f"クエリ：'{field_name}'は見つかりませんでした。")
            continue
        
        # 標準化された文字列を出力する場合
        print(f"クエリ: {field_name}, 値（確信度: {field_data.confidence}）: {field_data.value_string}")
        
        # 参考：標準化される前の生の文字列を出力する場合
        # print(f"クエリ: {field_name}, 値（確信度: {field_data.confidence}）: {field_data.content}")
```

### コードの注意点
- 利用したAPI Version
    - `2024-02-29-preview`を利用しています。ちなみに、古いバージョンとしては`2023-10-31-preview`も利用可能なようです。
- 利用するモデル
    - `prebuilt-layout`：Azureにて事前に学習されたレイアウトモデルを使用しています。
- `output_content_format`について
    - このAPIでは、全体的な文書の抽出結果として`output_content_format`を指定することにより、マークダウンやテキストを選択可能です。ただし、確認時点ではマークダウンを指定するとquery_fieldsの処理結果となる`analyze_result.documents`が格納されない事象が発生するようです。
- API利用時の処理時間について
    - 今回のPython SDKを用いてAPIを利用する場合、処理結果を得る際の待ち時間`polling_interval`が30秒に設定されているため、デフォルトだと遅く感じる可能性があります。これは、Python SDKが非同期処理によるAPI実行を行っているためです。3秒などに変更することによって、早めることが可能です。後述のレートリミットと相談になると思われます。

- 基本的なレイアウトに関する抽出について
    - 今回は`Layout`モデルのうち、`QueryFields`に限って機能を紹介しました。その他のレイアウト情報の取得については以下の記事に従うと良いです。

        - <https://learn.microsoft.com/ja-jp/azure/ai-services/document-intelligence/quickstarts/get-started-sdks-rest-api?view=doc-intel-4.0.0&preserve-view=true&pivots=programming-language-python>
        - 確認時点の上記リンク内のコードは存在しない関数（`document_intelligence_client.begin_analyze_document_from_url`）を呼び出しているようでした（旧FormRecognizerライブラリの関数を使っているようです。）。私のコードサンプル上は`document_intelligence_client.begin_analyze_document`に修正して利用しています。そのうち対応されるでしょう。

    - 詳細なレイアウト情報（レスポンスの内容(`2024-02-29-preview`)）は以下の最下部を参考にすると良いです。

https://westus.dev.cognitive.microsoft.com/docs/services/document-intelligence-api-2024-02-29-preview/operations/GetAnalyzeDocumentResult

- レートリミット
    - 以下に無料版・有料版における制限が記載されています。

https://learn.microsoft.com/ja-jp/azure/ai-services/document-intelligence/service-limits?view=doc-intel-4.0.0#model-usage