エクサウィザーズ Engineer Blog

株式会社エクサウィザーズのエンジニアチームブログ

MS GraphRAGを使用してみた

GraphRAGの概要

RAGとは、保持したドキュメントから検索して参考情報を生成AIに渡すことで、生成AIの学習に含まれていない情報を活用した回答が可能になる技術です
最も一般的なVectorRAGは、ドキュメントを断片化(チャンク化)し、意味合いの近いチャンクだけを利用しています。そのため、特定の情報を取得することには優れているが、データ全体についての要約や質問に対して回答することに適していません

そこで今、GraphRAGが注目されています

GraphRAGでは、ドキュメントからナレッジグラフを生成することで、要素同士(エンティティ)の関係性を保持しています。回答時には、この関係性グラフを用い、入力クエリから派生する関係性を考慮することで、情報がチャンクで分断されることなく抽出することができます

GraphRAGのメリット・デメリット

メリット

  • データ全体についての要約や質問に対して回答ができる
    • 複数の資料からグラフを作成した上で入力クエリ(検索文)に対して近い単語や文章を抽出するため、資料をまたがった内容で出力が可能(VectorRAGの場合、チャンクごとに検索し抽出される)
    • 質問文に関連なさそうな事柄も芋づる式に検索・取得し、回答を生成する必要がある場合に有効
  • ナレッジグラフを描画することで各エンティティの関係性を可視化することができる

デメリット

  • ナレッジグラフの作成に時間・コストがかかる
    • 質問に対して答えが固まっている場合はVectorRAGを使用する方が効率が良いように感じる
  • ナレッジグラフを作成する方法の自由度が高い分、作成難易度も高い

MicrosoftのGraphRAGの処理手順

MicrosoftのGraphRAGではglobal(Global Search)local(Local Search)の2種類の検索方法があります

  • global: 入力データ全体の情報に対する質問の回答に適しています。

  • local: 入力データに存在する特定のエンティティを理解する必要がある質問(特定の単語に対する質問など)の回答に適しています。

この記事ではVectorRAGが苦手な分野のGlobal Searchに焦点を当てて説明していきます。

引用: Edge, D., Trinh, H., Cheng, N., Bradley, J., Chao, A., Mody, A., Truitt, S., & Larson, J. (2024). From Local to Global: A Graph RAG Approach to Query-Focused Summarization

  1. Source Documents → Text Chunks

    1. ソース文書から抽出された入力テキストをテキストチャンクに分割
      • 長いテキストチャンクはLLMの呼び出し回数が少ないが、リコール(記憶)力の低下につながるため調整が必要
  2. Text Chunks → Element Instances

    1. 各テキストチャンクからグラフの情報(ノードやエッジなど)を抽出する
      • ドメインに特化した例を提供することで抽出精度を向上させることが可能
      • 複数回のgleaningsを行うことでエンティティの抽出漏れを防いでいる
  3. Element Instances → Element Summaries

    1. 抽出されたグラフの情報を要約
      • エッジも含んだ要約を行うことで元のテキストにある暗示的な関係も含んだ要約を作成することが可能
      • 重複するエンティティを適切に処理する
  4. Element Summaries → Graph Communities

    1. 前のステップで作成された要約をもとにノードがエッジで接続され、グラフが作成される
      • この時作成される各エッジは重み(関係の強さ)を持つ
    2. Leiden Hierarchicalアルゴリズムで内部で強く結びついたコミュニティに分割することで、大規模なグラフの階層的コミュニティ構造を効率的に検出できる
  5. Graph Communities → Community Summary

    1. Leiden階層の各コミュニティについて、報告書のような要約を作成します。これにより、データセットの全体構造と意味を理解するための独立した要素として役立つ。
    2. 要約の生成プロセス:
      • リーフレベルのコミュニティ: リーフレベルのコミュニティの要素要約(ノード、エッジ、ノードやエッジの特徴や特性)を優先順位に従ってLLMのコンテキストウィンドウに追加します。優先順位は、ノードの総次数に基づいて決定する
      • 上位レベルのコミュニティ: 要素要約がコンテキストウィンドウのトークン制限内に収まらない場合、サブコミュニティの要約を優先順位に従って追加する
  6. Community Summary → Community Answers → Global Answer

    1. ユーザーからのクエリに対して以下のようにクローバルな回答を行う
      • 関連情報が均等に分布されるよう、コミュニティ要約をランダムにシャッフルし、事前に指定されたトークンサイズに分割する
      • 各チャンクに対して並列的に中間回答を生成し、LLMが生成した回答の有用性を0-100でスコアリングする。スコアが0の回答は除外
      • 有用性スコアの高い順に中間回答を新しいコンテキストウィンドウに追加し、最終的なグローバル回答を生成する

MicrosoftがGraphRAGを出していたので使用してみた

今回はエクサウィザーズの決算短信を使用してGraphRAGで質問を行ってみました GraphRAGの詳細についてはMicrosoftがドキュメントを作成しているためこちらを確認してください

GraphRAGをインストール

pip install graphrag

実行を行うディレクトリの作成

ragtestの部分は自由に名前をつけて問題ないです

mkdir -p ./ragtest/input

作成するナレッジグラフの基になるデータ

今回は自分でデータを準備していますが、以下のコードでサンプルデータを格納することも可能

curl https://www.gutenberg.org/cache/epub/24022/pg24022.txt > ./ragtest/input/book.txt

今回はPDFを使用するため以下のコードを用いて簡単に.pdf → .txtに変換しています

import os
import pdfplumber # pip install pdfplumberが必要
import argparse # pip install argparseが必要
def extract_text_and_tables(pdf_path, output_txt_path):
    with pdfplumber.open(pdf_path) as pdf:
        all_text = ""
        for page_number, page in enumerate(pdf.pages, start=1):
            # Extract text
            text = page.extract_text()
            if text:
                all_text += text + "\n"
            # Extract tables
            tables = page.extract_tables()
            for table in tables:
                for row in table:
                    clean_row = [cell if cell else "" for cell in row]
                    if None in row:
                        print(f"注意: {page_number}ページ目に空のセルがあります。")
                    all_text += ", ".join(clean_row) + "\n"
    # Write to text file
    if not os.path.exists(output_txt_path):
        os.makedirs(os.path.dirname(output_txt_path), exist_ok=True)
    with open(output_txt_path, "w", encoding="utf-8") as f:
        f.write(all_text)
if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description='PDFからテキストと表を抽出してTXTファイルに保存します。'
    )
    parser.add_argument('pdf_path', type=str, help='入力のPDFファイルのパス')
    output_txt_path = "./ir_graphrag/input/pdf_to_txt.txt"
    args = parser.parse_args()
    extract_text_and_tables(args.pdf_path, output_txt_path)
    print(f"{args.pdf_path} からテキストと表を {output_txt_path} に抽出しました。")

GraphRAGを実行するために必要な設定ファイルやプロンプトを取得

python -m graphrag.index --init --root ./ragtest このコマンドだけで実行に必要なファイルが全てragtest内に格納されます
注意点:./ragtest/.envのGRAPHRAG_API_KEYにChatGPTのAPI Keyを設定する必要があります。

設定の変更

./ragtest/settings.yaml で設定の変更が可能です。 モデルやチャンクの設定など色々調整できますが、今回はLLMのモデルとグラフ描画のための変更をしています。

embed_graph:

 enabled: false → true

umap:

 enabled: false → true

snapshots:

 graphml: false → true

その他の変更もできるのでこちらを参考にしてください。

プロンプトの自動修正を行う

MicrosoftのGraphRAGには使用するプロンプトの調整を行ってくれる機能があります。

python -m graphrag.prompt_tune --root ./ragtest --config ./ragtest/settings.yaml --domain "決算短信" --language Japanese

手軽に楽しむために必要な設定は以下のパラメータになります

  • --root : 先ほど作成したプロジェクトのルートディレクトリ

  • --config(必須): プロジェクト内のsettings.yamlまでのパス

  • --domain: 入力データに関するドメイン

  • --language: 入力する資料の言語(デフォルトは入力から自動的に検出されます)

その他、chank_sizeやmax_tokenなどの設定も行えるため、詳細はこちらをご確認ください

インデックスの作成

python -m graphrag.index --root ./ragtest

以下のコードで実行すると入力したデータとプロンプトをもとにインデックス処理が行われます。

実際に行っている流れをざっくり説明すると以下のようになります 説明を使用している図は
Auto Tuning - GraphRAG を参考にしています。

※ 文書処理とネットワークの可視化はoptionのため、デフォルトでは使用されません。

1. TextUnitを作成

  • 論文のSource Documents → Text Chunks部分に相当

2. グラフ抽出

  • TextUnitからエンティティとエッジ(関係)を抽出し、要約する
    • TextUnitからクレームを抽出する(オプションのため、デフォルトでは使用していない)
      • クレームはエンティティやエッジなどの要素に対する時系列情報

3. グラフ拡張

  • 抽出したグラフの情報からコミュニティ構造を理解し、追加情報でグラフを拡張する
    • Leiden Hierarchicalアルゴリズムを使用してエンティティコミュニティの階層を生成する
    • Node2Vec アルゴリズムでグラフのベクトル表現を生成する
  • エンティティとエッジのテーブルを作成

4. コミュニティの要約

  • 各コミュニティのレポートを生成
  • 生成したコミュニティレポートを要約
  • コミュニティレポートの要約を埋め込む
  • コミュニティとコミュニティレポートのテーブルを作成

5. 文書処理(option)

  • 各ドキュメントを1で作成したTextUnitとリンクさせる
  • ドキュメントを平均埋め込みでベクトル化する
  • Umapを実行するためにドキュメントのテーブルを作成する

6. ネットワークの可視化(option)

  • ドキュメントとグラフ(ノードとエッジ)をUmapで次元削減する
  • ノードテーブルを作成する

クエリエンジンの実行

python -m graphrag.query --root ./ragtest --method global "ここに質問文を入力してください"

※ Local Searchを行いたい場合、--method localと変更してください。

出力結果

実際に以下のコマンドで出力してみた結果を記します。

python -m graphrag.query --root ./ragtest --method global "エクサウィザーズの決算に対する評価を行ってください"

creating llm client with {'api_key': 'REDACTED,len=51', 'type': "openai_chat", 'model': 'gpt-4-turbo-preview', 'max_tokens': 4000, 'temperature': 0.0, 'top_p': 1.0, 'n': 1, 'request_timeout': 180.0, 'api_base': None, 'api_version': None, 'organization': None, 'proxy': None, 'cognitive_services_endpoint': None, 'deployment_name': None, 'model_supports_json': True, 'tokens_per_minute': 0, 'requests_per_minute': 0, 'max_retries': 10, 'max_retry_wait': 10.0, 'sleep_on_rate_limit_recommendation': True, 'concurrent_requests': 25}

SUCCESS: Global Search Response:

エクサウィザーズの財務状況評価

エクサウィザーズの最近の決算報告によると、同社の財務状況は著しく改善していることが示されています。特に、営業利益、財務健全性、キャッシュフローの各面で顕著な進展が見られます。

営業利益の改善

2021年3月期の営業損失から大幅な改善が見られ、2022年3月期にはさらに改善し、2023年3月期の営業利益予想は1,000千円から50,000千円の間であることが示されています。これは、AIプラットフォームとAIプロダクトの両セグメントでの黒字化を想定しており、企業の戦略的方向性と財務健全性の向上を示しています [Data: Reports (14)]。

財務健全性の向上

株式会社エクサウィザーズの財務健全性は、1株当たり純資産額の大幅な増加、純資産の部の合計額の増加、新株予約権の増加、1株当たり当期純損失の改善、株式分割、非支配株主持分の増加など、複数の財務指標の改善により示されています。特に、1株当たり純資産額は2020年度の31.64円から2021年度には82.44円へと増加し、純資産の部の合計額も大幅に増加しています [Data: Reports (11)]。

キャッシュフローの改善

営業活動によるキャッシュ・フローは、2022年3月期には前年度の△528百万円から24百万円へと改善されました。これは企業の収入増加を反映しており、営業活動の効率化が成功していることを示しています。また、現金及び現金同等物の残高が前連結会計年度末に比べ3,200,177千円増加し、5,537,876千円になりました。この増加は企業の流動性と財務の柔軟性を高め、将来の投資や不測の事態に対する準備ができていることを示しています [Data: Reports (27)]。

結論

エクサウィザーズの財務報告は、同社が財務健全性、営業利益、キャッシュフローの各面で顕著な改善を遂げていることを示しています。これらの改善は、AIプラットフォームとAIプロダクトの両セグメントの黒字化、収益性の向上、および企業の成長戦略と技術投資の成果を反映しています。エクサウィザーズの将来に向けた財務戦略と成長潜在力は、市場での競争力を高め、収益性を向上させるための戦略的な取り組みとして評価されます。

出力された結果を見てみると、、、

以下のような表からもデータが取得できているように見える

営業活動によるキャッシュ・フローは、2022年3月期には前年度の△528百万円から24百万円へと改善されました。

また、資料全体から情報が抽出されていました。

グラフの可視化

”インデックスの作成” で作成した際に出力したグラフを表示してみる

import networkx as nx # pip install networkx が必要
from pyvis.network import Network # pip install pyvis が必要
net = Network(height='1000px', width='1500px') # 表示する画面のサイズ変更可能
# グラフの読み込み
graph = nx.read_graphml('{summarized_graph.graphmlまでのpath}')
nx.draw(graph, with_labels=True)
net.from_nx(graph)
net.show("pyvis.html", notebook=False)

出力されたpyvis.htmlを表示すると以下のようになります

VSCodeを使用している場合、以下の手順で開くことができます
1. open in browser をインストール
2. ファイルを右クリック
3. Open in Default Browser もしくは Open in Other Browsersを押す

※エッジの太さはWeightの値によって変わります

処理時間の算出

入力に使用したテキストデータのサイズによって作成されるグラフのサイズが変わるため、あくまで参考程度にしてください

今回のテキストデータのサイズ: 58 KB

全体を通しての処理時間は 3分18秒

ですが、今回のテキストデータはかなり小さい方だと思うのでファイルサイズと実行環境によっては処理時間が長くなることもあるのでご注意ください。

また、内訳は以下のようになります

処理内容 処理時間
プロンプト自動修正 27秒
インデックス作成 2分3秒
クエリエンジンの実行 48秒

所感

今回、MicrosoftのGraphRAGを試してみましたが、特にVectorRAGでは実現が難しい - 複数ページ(チャンク)にわたる情報の検索 - 文書内の関係性の可視化

について有用な機能であると感じました。

特定の検索についてはVectorRAGとGraphRAGのLocal Searchで精度検証を行う必要がありますが、Microsoftの場合、GlobalとLocalを切り替えることで両方対応可能なため使い勝手は良いのではないかなと思います。

ただ、懸念点としては以下の2点が挙げられます。

  • コスト面
  • 処理時間

今回コスト面に関してはOpenAIを使用すると算出するパラメータは見当たらないため、

コスト管理を行いながら使用する場合はAzure OpenAI の GraphRAG Accelerator を使用することをお勧めします。

Microsoft GraphRAGは簡単に試すことができますのでぜひお試しいただければと思います