グラフ埋め込み(Graph Embedding)によるAI関連性計算の高度化

グラフ埋め込みでRAGの検索精度は進化する:PythonとNetworkXによる関係性計算の実装ハンズオン

約18分で読めます
文字サイズ:
グラフ埋め込みでRAGの検索精度は進化する:PythonとNetworkXによる関係性計算の実装ハンズオン
目次

この記事の要点

  • グラフ構造データを低次元ベクトルに変換
  • AIのデータ関連性計算を飛躍的に向上
  • RAGの検索精度と回答品質を改善

企業のAI活用が本格化し、膨大な「データの海」から価値を引き出す取り組みが加速しています。特に最近、多くの組織で導入が進むRAG(検索拡張生成)システムにおいて、共通の壁に直面するケースは決して珍しくありません。

「ベクトル検索を導入したのに、AIが複雑な文脈や前提条件を正しく理解してくれない」

このような課題に直面したことはないでしょうか。

この問題は、RAGの仕組みそのものに起因しています。一般的なアプローチでは、ドキュメントをチャンクに分割し、Embeddingモデルに通してベクトル化します。現在、OpenAIの標準モデルは「GPT-5.2」へ移行し、コーディング特化の「GPT-5.3-Codex」が登場するなど、AIモデル自体の推論能力やコンテキスト処理能力は飛躍的に向上しています。しかし、生成を担うモデルがどれほど進化しても、検索で取得するデータ構造に限界があれば、本来のパフォーマンスは発揮できません。現実世界のデータは極めて複雑です。「AはBの上司であり、BはプロジェクトCのリーダーである」といった構造的な関係性は、単純なテキストの類似度だけでは捉えきれないことが多いのです。

そこで今回のチュートリアルでは、「グラフ埋め込み(Graph Embedding)」に焦点を当てます。近年、Amazon Bedrock Knowledge BasesでAmazon Neptune Analyticsを活用したGraphRAGサポートがプレビュー提供されるなど、エンタープライズ領域でもグラフ技術とRAGの融合に注目が集まっています。しかし、いきなり大規模なGraphRAGツールやクラウドサービスを導入する前に、まずは手元のPython環境でプロトタイプを作り、複雑なデータ構造をどのように「ベクトル」に変換できるのか、その原理と実装を肌感覚でつかむことが重要です。

この基礎的なアプローチの理解は、単なる検索精度の向上にとどまらず、組織のAIシステムに高度な「推論の翼」を与える確実な第一歩となります。

1. イントロダクション:なぜ今「グラフ埋め込み」なのか

ベクトル検索が見落とす「関係性」の情報

皆さんが普段使っているベクトル検索(Semantic Search)は、言葉の「意味的な近さ」を測るのに優れています。例えば、「猫」と「子猫」はベクトル空間上で非常に近い位置に配置されます。これはRAG(検索拡張生成)の基盤として素晴らしい技術です。

しかし、ビジネスの現場や複雑なデータ構造においてはどうでしょうか。
例えば、製品のマニュアルデータにおいて、「エラーコード503」と「サーバー再起動」という言葉自体には意味的な類似性は低いかもしれません。ですが、トラブルシューティングのグラフ構造においては、これらは「原因と対策」として太い線で結ばれているはずです。

従来の単純なベクトル検索では、この「繋がりの強さ(構造的コンテキスト)」を見落としてしまうリスクがあります。特に、GraphRAG(グラフ構造を活用したRAG)やエージェント型RAGへと進化しつつある現在のトレンドにおいて、単なる意味的な近さだけでは不十分なケースが珍しくありません。

構造的なつながりを無視した結果、システムは関連性の低いドキュメントを拾ってきたり、重要な文脈を無視した回答(ハルシネーションの一因)を生成したりする傾向にあります。OpenAI公式サイトによると、2026年2月にGPT-4oなどのレガシーモデルが廃止され、100万トークン級のコンテキストと高度な推論能力(Thinking機能)を備えたGPT-5.2への移行が進んでいます。しかし、LLM自体の推論能力がどれほど向上しても、入力される検索結果に「構造的なつながり」が欠けていれば、複雑な文脈把握や正確な回答の生成は困難です。

グラフ埋め込み(Graph Embedding)が解決する課題

ここで登場するのが「グラフ埋め込み」です。これは、ノード(実体)とエッジ(関係)で構成されるグラフ構造を、低次元のベクトル空間に変換する技術です。

簡単に言えば、「グラフ上の地図」を作る作業を指します。

  • 構造的類似性: 直接繋がっていなくても、ネットワーク内で似たような役割(ハブになっている、末端にいるなど)を持つノード同士を近くに配置します。
  • 関係性の保存: グラフ上で近くにあるノードは、ベクトル空間でも近くになるように学習します。
  • ハイブリッド検索への応用: テキストの「意味」とグラフの「構造」を組み合わせることで、マルチホップな質問(複数の情報を経由しないと答えられない質問)にも対応可能になります。

これにより、AIは単語の意味だけでなく、「データ同士がどう繋がっているか」というコンテキストを含めて情報を検索できるようになります。最新のGPT-5.2のような高度な推論モデルをRAGの生成エンジンとして活用する際にも、この「構造化された関係性データ」を適切に渡すことが、モデルの真価を引き出し、ハルシネーションを極限まで抑えるための重要な基盤となります。

本チュートリアルのゴール:RAG精度向上への第一歩

今回は、理論を並べるよりも手を動かすことを重視します。GraphRAGのような高度なシステムも、基本となるのは「ノードとエッジの数値化」です。なお、実装の過程でコーディング特化型のGPT-5.3-Codex等のエージェント型モデルを活用して開発効率を上げるアプローチも有効ですが、まずは動くものを作り、ベースとなる仕組み自体を理解することが不可欠です。

  1. テキスト情報から擬似的なグラフ構造を作る
  2. Node2Vec の考え方を使ってグラフを学習する
  3. 得られたベクトルで「隠れた関係性」が見えるか検証する

このプロセスを通じて、ブラックボックスになりがちな「埋め込み生成」の中身を透明化し、皆さんのRAGパイプラインにどう組み込めるかのイメージを掴んでいただきます。最新のLLMや評価ツールを導入する前に、まずはデータの「構造」をどう捉えるか、その本質を理解します。

2. 開発環境のセットアップ

それでは、ターミナルを開く準備はいいですか?一緒に手を動かしていきましょう。
今回はPythonを使用します。Google Colabなどのノートブック環境があれば、すぐに試すことができます。

必要なライブラリのインストール

グラフ処理のデファクトスタンダードであるNetworkXと、自然言語処理ライブラリとして有名なGensimを使用します。Node2Vecの実装には専用ライブラリもありますが、今回は学習のためにGensimのWord2Vecを流用する「DIYスタイル」で進めます。これにより、バージョン依存のトラブルを避けつつ、アルゴリズムの本質を理解できます。

以下のコマンドで必要なライブラリをインストールしてください。

!pip install networkx gensim matplotlib scikit-learn numpy

ライブラリのインポート

コード全体で使用するモジュールをインポートしておきましょう。

import networkx as nx
import random
import numpy as np
import matplotlib.pyplot as plt
from gensim.models import Word2Vec
from sklearn.manifold import TSNE
from sklearn.metrics.pairwise import cosine_similarity

# 乱数シードの固定(再現性のため)
SEED = 42
random.seed(SEED)
np.random.seed(SEED)

これで準備は整いました。シンプルですが、強力なツールセットです。これらを組み合わせることで、データの見え方が劇的に変わります。


3. Step 1: テキストデータから「関係性」をグラフ化する

まずは、分析対象となるデータ構造を定義します。従来のナレッジグラフ構築では、専用の自然言語処理モデルを用いたNER(固有表現抽出)によってドキュメントからノードやエッジを抽出する手法が一般的でした。しかし現在では、GPT-4oやClaude 3.5 Claudeといった大規模言語モデル(LLM)の構造化出力機能を活用し、非構造化テキストからエンティティと関係性を直接JSON形式などで抽出するアプローチが主流となっています。

こうした最新の抽出パイプラインを構築する前段階として、まずはグラフデータ構造の基本概念をしっかりと掴むことが重要です。ここでは例として、「ITプロジェクトにおける人員と技術スタックの関係性」を定義したサンプルデータを用いて、グラフのモデル化を進めます。

NetworkXによるグラフオブジェクトの作成

従業員、保有スキル、そしてアサインされたプロジェクトという3つの要素が複雑に絡み合う小規模なナレッジグラフを構築します。NetworkXライブラリを使用すると、ノード(実体)とエッジ(関係)の追加が直感的なコードで記述できます。

# グラフの初期化
G = nx.Graph()

# ノードとエッジの定義(関係性)
relationships = [
    # 従業員とプロジェクトの関係(所属)
    ("Alice", "Project_Alpha"),
    ("Bob", "Project_Alpha"),
    ("Charlie", "Project_Beta"),
    ("David", "Project_Beta"),
    ("Eve", "Project_Gamma"),
    
    # 従業員とスキルの関係(保持)
    ("Alice", "Python"),
    ("Alice", "Cloud"),
    ("Bob", "Java"),
    ("Bob", "SQL"),
    ("Charlie", "Python"),
    ("Charlie", "AI"),
    ("David", "Java"),
    ("David", "Cloud"),
    ("Eve", "AI"),
    ("Eve", "Python"),
    
    # プロジェクトと関連技術の関係(採用技術)
    ("Project_Alpha", "Cloud"),
    ("Project_Alpha", "Java"),
    ("Project_Beta", "AI"),
    ("Project_Beta", "Python"),
    ("Project_Gamma", "AI")
]

# グラフにエッジを追加
G.add_edges_from(relationships)

print(f"ノード数: {G.number_of_nodes()}")
print(f"エッジ数: {G.number_of_edges()}")

グラフ構造の可視化と確認

構築したデータ構造は、視覚的に確認することで全体像をより正確に把握できます。ノード同士がどのように結びついているか、Matplotlibを使用してネットワークのトポロジーを描画します。

plt.figure(figsize=(10, 8))
pos = nx.spring_layout(G, seed=SEED)  # ノードの配置決定

# ノードの描画
nx.draw_networkx_nodes(G, pos, node_size=700, node_color='skyblue')
# エッジの描画
nx.draw_networkx_edges(G, pos, width=2, alpha=0.5, edge_color='gray')
# ラベルの描画
nx.draw_networkx_labels(G, pos, font_size=10, font_family='sans-serif')

plt.title("Project Knowledge Graph")
plt.axis('off')
plt.show()

実行結果の考察:
出力された図を分析すると、「Project_Beta」と「Project_Gamma」は直接的なつながりを持たないものの、「AI」や「Python」という共通の技術スキルを介して間接的に結びついていることがわかります。また、「Alice」と「David」は異なるプロジェクトに所属していますが、共に「Cloud」スキルを保持しているという共通点が見て取れます。

このような「間接的な繋がり」や「潜在的な関係性」こそが、単純なキーワード検索では捉えきれない、グラフ埋め込みによって抽出・活用したい重要な情報となります。


4. Step 2: Node2Vecによるグラフ埋め込みの実装

Step 1: テキストデータから「関係性」をグラフ化する - Section Image

ここからが本番です。グラフ構造をベクトルに変換します。
今回採用するアルゴリズムはNode2Vecです。これは、グラフ上の探索を自然言語処理の「文章」に見立てるという、非常にエレガントなアイデアに基づいています。

ランダムウォーク:グラフ上を「散歩」して文脈を作る

Node2Vecの核心は、「グラフの上をランダムに歩き回った履歴(足跡)」を「文章」と見なすことです。

  • 文章: 「私は 猫 が 好き です」
  • ランダムウォーク: 「Alice → Project_Alpha → Java → Bob」

このようにノードの列を生成すれば、あとは自然言語処理で定評のあるWord2Vecを使って、各ノードをベクトル化できるというわけです。

では、ランダムウォークを生成する関数を実装しましょう。

def get_random_walk(graph, start_node, walk_length):
    """
    指定されたノードから始まるランダムウォークを生成する
    """
    walk = [start_node]
    
    for _ in range(walk_length - 1):
        current_node = walk[-1]
        neighbors = list(graph.neighbors(current_node))
        
        if not neighbors:
            break
            
        # 次の移動先をランダムに決定
        next_node = random.choice(neighbors)
        walk.append(next_node)
        
    return walk

# パラメータ設定
WALK_LENGTH = 10  # 1回の散歩の長さ
NUM_WALKS = 20    # 各ノードから何回散歩するか

# 全ノードからランダムウォークを生成
walks = []
for node in G.nodes():
    for _ in range(NUM_WALKS):
        walks.append(get_random_walk(G, node, WALK_LENGTH))

# 生成されたウォークの例を確認
print(f"生成されたウォークの総数: {len(walks)}")
print(f"ウォーク例: {walks[0]}")

Gensimを用いたベクトル化の学習プロセス

「散歩の履歴」ができたら、それをWord2Vecに食わせます。ここで重要なのは、グラフの構造情報が、単語(ノード)の共起関係として学習される点です。

# Word2Vecモデルの学習
# vector_size: ベクトルの次元数(小さいグラフなので16〜32程度で十分)
# window: 文脈とみなす範囲
# min_count: 出現回数が少ないノードを無視するか(今回は0で全て含める)
# sg: 1=Skip-gram(推奨), 0=CBOW

model = Word2Vec(
    sentences=walks,
    vector_size=32,
    window=5,
    min_count=0,
    sg=1,
    workers=2,
    seed=SEED
)

print("学習完了!")
print(f"Aliceのベクトル(一部): {model.wv['Alice'][:5]}...")

専門家の視点:
ここでsg=1(Skip-gram)を選んでいるのがポイントです。Skip-gramは「中心の単語から周囲の単語を予測する」モデルであり、グラフにおいては「あるノードから近隣のノードを予測する」ことに相当します。これがグラフの局所的な構造を保存するのに非常に適しているのです。


5. Step 3: 類似度計算による「隠れた関係」の発見

Step 2: Node2Vecによるグラフ埋め込みの実装 - Section Image

学習が完了しました。次に、生成されたベクトルを用いて「関係性」を計算します。これがRAGの検索精度を左右する重要な指標となります。

コサイン類似度によるノード間の距離計算

特定のノード(例えば「Python」)に最も関連性が高いと判断されたノードを確認します。ここでは gensimmost_similar メソッドを使用し、コサイン類似度に基づいて近いベクトルを探索します。

keyword = "Python"

# 類似度が高い上位5つのノードを取得
similar_nodes = model.wv.most_similar(keyword, topn=5)

print(f"【{keyword}】と関連性が高いノード:")
for node, score in similar_nodes:
    print(f"  - {node}: {score:.4f}")

予想される結果の解釈と洞察:
結果には、直接つながっている「Charlie」や「Project_Beta」といったノードが表示されるのは当然ですが、注目すべきは直接のエッジがないノード(例えば「AI」や「Eve」など)も上位にランクインする可能性がある点です。

これは、「Pythonを使用するメンバーは、AI関連のプロジェクトにも関与する傾向がある」といった構造的な文脈をAIが学習したことを意味します。グラフ理論でいう「構造的等価性(Structural Equivalence)」が捉えられており、直接の接点がなくても役割が似ているノード同士は近くに配置されます。

ベクトル検索との比較:何が違うのか

ここで、通常のテキスト埋め込み(OpenAIのEmbeddingモデルなど)と、今回作成したグラフ埋め込みの違いを整理します。

  • 一般的なテキスト埋め込み:
    「Python」に近い単語として「Programming」や「Code」を想起します。これは、インターネット上の膨大なテキストデータから学習した一般的な意味論的類似性です。2026年の主力モデルである「GPT-5.2(InstantおよびThinking)」は、長い文脈理解や汎用知能が飛躍的に向上しています。しかし、こうした最新の推論モデルであっても、基本的にはこの「世の中の一般的な知識」に基づいています。
    (なお、GPT-4oやGPT-4.1などの旧モデルは2026年2月13日に廃止されました。既存のRAGシステムで旧モデルのAPIを使用している場合は、応答速度や構造化能力が向上しているGPT-5.2系への速やかな移行と、エンドポイントの更新が必要です。)

  • グラフ埋め込み(Node2Vec):
    組織固有のデータ構造から、「文脈的な使用状況」を学習します。

例えば、自社のプロジェクトにおいて「Python」が主に「経理システムの自動化」に使われていると仮定します。この場合、グラフ埋め込みの結果では「Python」の近くに「経理部」や「請求書処理」といったノードが配置されます。

これは、汎用的なAIモデルや最新のエージェント機能だけでは決して知り得ない、組織固有の暗黙知です。この独自の関係性データをRAG(検索拡張生成)に組み込むことで、AIは単なる一般論ではなく、実際の業務環境に即した精度の高い回答を出力できるようになります。

t-SNEを用いた埋め込み空間の可視化

最後に、32次元のベクトル空間を2次元に圧縮して可視化します。数字の羅列を確認するよりも、プロット図を生成することでクラスター(まとまり)を直感的に把握できます。

# ノードリストとベクトルの取得
nodes = list(G.nodes())
vectors = np.array([model.wv[node] for node in nodes])

# t-SNEによる次元削減
tsne = TSNE(n_components=2, random_state=SEED, perplexity=5)
vectors_2d = tsne.fit_transform(vectors)

# プロット
plt.figure(figsize=(10, 8))
plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1], c='lightgreen', edgecolors='black', s=100)

for i, node in enumerate(nodes):
    plt.annotate(node, (vectors_2d[i, 0], vectors_2d[i, 1]), xytext=(5, 2), textcoords='offset points')

plt.title("Graph Embeddings Visualization")
plt.grid(True, linestyle='--', alpha=0.5)
plt.show()

この図を確認すると、プロジェクトごとにグループが形成されていたり、特定のスキルセットを持つメンバーが近くに集まっていたりすることが視覚的に確認できるはずです。このベクトル空間上の「距離感」こそが、RAGシステムがドキュメントを検索する際の強力な羅針盤として機能します。

6. 応用:RAGパイプラインへの統合イメージ

生成されたグラフ埋め込みを実際のRAGシステムへ統合する際、実践的なアプローチとして「ハイブリッド・リランキング」が挙げられます。

ハイブリッド検索:テキストベクトル × グラフベクトル

RAGの検索フェーズにおいて、以下の2つのスコアを計算し、統合します。

  1. テキスト類似度: ユーザの質問文とドキュメント内容の意味的類似度(従来のベクトル検索)。
  2. グラフ関連度: ユーザの質問に含まれるキーワード(エンティティ)と、ドキュメントに関連付けられたエンティティ間のグラフ上の近さ。

例えば、ユーザが「クラウドのエラーについて」と質問したとします。
テキスト検索では「クラウド」「エラー」という単語を含むドキュメントがヒットします。ここでグラフ埋め込みを使います。質問から抽出した「Cloud」ノードに近いノード(例えば「Project_Alpha」や「David」)に関連するドキュメントのスコアをブースト(加点)します。

リランキングへの活用

具体的なフローは以下のようになります。

  1. 一次検索: ベクトル検索で上位50件のドキュメントを取得。
  2. エンティティ抽出: 質問文と取得ドキュメントから主要エンティティを特定。
  3. グラフスコア計算: 今回作成したモデル(model.wv.similarity)を使って、質問エンティティとドキュメントエンティティの類似度を計算。
  4. 再順位付け: テキストスコアとグラフスコアを加重平均し、最終的に上位5件のドキュメントをLLMのプロンプトコンテキストとして渡します。2026年2月時点の最新環境であれば、長文の安定処理や高度な推論(Thinking機能)に優れたGPT-5.2を汎用タスクに、コード生成を伴う技術的な質問であればエージェント型コーディングモデルのGPT-5.3-Codexを選択することで、精度の高い回答生成が期待できます。GPT-4oなどのレガシーモデルは廃止されているため、最新モデルの特性に合わせて抽出するドキュメントのチャンクサイズを調整することも有効です。
# 擬似コード:スコア統合のイメージ
def calculate_hybrid_score(text_score, entity_query, entity_doc, model, alpha=0.7):
    """
    alpha: テキストスコアの重み(0.0〜1.0)
    """
    try:
        # グラフ埋め込みによる類似度
        graph_score = model.wv.similarity(entity_query, entity_doc)
    except KeyError:
        # エンティティがグラフにない場合は0または平均値を割り当て
        graph_score = 0.0
        
    final_score = alpha * text_score + (1 - alpha) * graph_score
    return final_score

このシンプルな再計算を入れるだけで、文脈違いの検索結果を大幅に減らすことができます。

実運用に向けたスケーラビリティの考慮

今回の手法は数千〜数万ノード程度ならPythonスクリプトで十分高速に動作します。しかし、数百万ノードを超える巨大なナレッジグラフを扱う場合は、以下の点に注意が必要です。

  • 増分学習: データが追加されるたびに全学習し直すのは非効率です。Gensimのbuild_vocabtrainを使って既存モデルを更新するか、定期的なバッチ処理にします。
  • 推論速度: リアルタイムで全ペアの類似度を計算するのは重いため、あらかじめ主要なエンティティ間の類似度は計算してキャッシュ(Redisなど)に入れておくのが定石です。

7. まとめと次の学習ステップ

生成されたウォークの例を確認 - Section Image 3

お疲れ様でした。テキストデータからグラフを構築し、そこからベクトルを生成して、隠れた関係性をあぶり出すプロセスを体験していただきました。

本チュートリアルの振り返り

  • 構造の重要性: 単純なベクトル検索だけでは見落としてしまう「関係性」を、グラフ構造として明確に表現しました。
  • Node2Vecの実践: 「グラフ上の散歩(ランダムウォーク)」という直感的なアプローチで、複雑な構造情報を計算可能な数値へと変換しました。
  • RAGへの応用: テキストのセマンティック検索とグラフ検索を組み合わせることで、回答の精度と信頼性を飛躍的に高める道筋が見えました。特に、グラフから抽出したリッチな文脈情報を、GPT-5.2のような高度な推論能力と長大なコンテキストウィンドウ(100万トークン級)を持つ最新モデルに渡すことで、RAGのパフォーマンスは劇的に向上します。もし現在、GPT-4oなどのレガシーモデルを用いたシステムを運用している場合は、自動ルーティング機能が強化されたGPT-5.2ベースのアーキテクチャへの移行を検討することで、グラフデータが持つ本来のポテンシャルをさらに引き出すことが可能です。

Graph Neural Networks (GNN) への展望

今回紹介したNode2Vecは、極めて有用ですが「静的」な埋め込み手法に分類されます。つまり、ネットワークの形状は捉えますが、ノード自体が持つ固有の属性(ユーザーの年齢、ドキュメントの更新日時、テキストのメタデータなど)は直接的には考慮していません。

さらなる高みを目指すなら、次のステップは GNN(Graph Neural Networks) の導入です。GraphSAGEGATといったアルゴリズムを採用すれば、ノードの属性情報とグラフの構造的特徴を同時に学習し、未知のノードに対しても柔軟な推論が可能になります。これには PyTorch GeometricDeepGraphLibrary (DGL) といった専用のフレームワークが役立ちます。また、こうした高度なモデルの実装やパイプライン構築においては、GPT-5.3-Codexのようなエージェント型のコーディング特化モデルを活用することで、複雑なGNNの設計やデバッグ作業を効率的に進めることができるでしょう。

あなたのプロジェクトに「文脈」を実装しましょう

技術的な実装の基本は理解できたものの、「自社の複雑なデータセットで、具体的にどうグラフを設計すればいいのか?」「どのエンティティをノードとして切り出すべきか?」というモデリングの段階で悩むケースは珍しくありません。実は、グラフ埋め込みを用いたAIプロジェクトにおける成功の8割は、この「グラフ設計(データモデリング)」の質によって決まると言っても過言ではありません。

もし、現在のRAGプロジェクトで「ドキュメントを増やしても検索精度が頭打ちになっている」「業界特有の専門知識や社内用語の関連性がうまく反映されない」といった課題に直面しているなら、設計フェーズを一度見直す良い機会です。自社への適用を本格的に検討する際は、外部の知見を取り入れることで導入リスクを大幅に軽減できます。個別のデータ特性に応じた客観的なアドバイスを得ることで、最適なグラフ構造の定義と、既存のAIパイプラインへの統合戦略をより確実なものにすることが可能です。

AIは決して魔法ではありません。しかし、正しい「地図(グラフ構造)」と最新の「推論エンジン」を適切に組み合わせて与えることで、まるで魔法のような精度の高い結果を返してくれます。さあ、次はあなた自身のデータセットで、この新しい検索のアプローチを試してみてください。

グラフ埋め込みでRAGの検索精度は進化する:PythonとNetworkXによる関係性計算の実装ハンズオン - Conclusion Image

コメント

コメントは1週間で消えます
コメントを読み込み中...