清水理史の「イニシャルB」

注目の生成AI関連技術「Knowledge Graph」で、桃太郎の物語を視覚化してみよう

グラフ構造のデータを扱うデータベースシステム「Neo4j」によって視覚化した桃太郎の物語

 生成AI、なかでもRAGの分野で「Knowledge Graph」の注目が高まっている。文書などを構造化することで、生成AIの知識として利用する方法だ。

 ベクトル検索方式のRAGと異なり、情報の関係を定義することで、より正確な回答が得られる場合があるのが特徴だ。今回は、Knowledge Graphそのものを直感的に理解するために、CopilotとNeo4jを利用して、「桃太郎」における登場人物の関係やイベントを視覚化してみた。

そもそも「Graph」って何だ?

 筆者もそうだったが、「Graph」と言われて、多くの人が思い浮かべるのはExcelなどで使う棒グラフや折れ線グラフのことだろう。

 一方で、生成AIの分野でも「Graph」という言葉がよく登場する。代表的なのが、MicrosoftのCopilot for Microsoft 365だ。Copilot for Microsoft 365では、メールの要約やTeams会議のまとめなど、Microsoft 365に保存された組織のデータを利用した回答ができるが、このときの参照先として「Microsoft Graph」が利用される。

 棒グラフや折れ線グラフの「Graph」を想像すると、この文脈は少し分かりづらい。

 もちろん、Copilotがメールを要約する際に何かしらの折れ線グラフを参照しているわけではない。ここで使われているのは、データ構造を表す文脈での「Graph」だ。

 例えば、Microsoft Graphの場合であれば、Graph Explorerを利用することで、サンプルデータを見ることができる(サインインすれば自分のデータも参照可能)。具体的には、[ユーザー]の[自分の直属の部下]を選択すると、部下の情報が得られるといった具合だ。

Microsoft Graphのデータを参照できるGraph Explorer

 このように、単にデータが保管されているのではなく、それぞれのデータ間の関係性を考慮して、必要な情報を引き出すことができるのがGraphとなる。

 もう少し、直感的に分かる例を挙げるとすると、SNSなどのフォローの考え方がある。例えば、「清水さんが山田さんのアカウントをフォローしている」ということをGraphで表現すると、次のようになる。

(清水)-[:FOLLOWS]->(山田)

 このように、データ間の関係、方向、重みなどを表現することができるのが「Graph」であり、生成AIなどの知識として活用するために構造化されたGraph構造のデータベースを「Knowledge Graph」と呼ぶと考えてほしい。

 本来は、セマンティックやオントロジーなど、なかなか理解しにくい言葉が飛び交う世界で、専門家の方々からするとツッコミどころも多いと思うが、ここでは、そのあたりは飛ばして、ざっくり説明することにする。

 なお、以前、本コラムでRAGの概要を紹介したが、このとき文書などのもとになる情報をベクトルデータとして扱うと解説した。

 現状は、このベクトル検索が主流なのだが、長い文章や内容が複雑な文書などでは、文書内のキーワードや関連性を定義できるKnowledge Graphを利用したRAGが有利なケースもあり、最近ではベクトル検索とKnowledge Graphを組み合わせたハイブリッドなRAGなども提案されている。

 このあたりも、なかなか難しい世界なので、とりあえず今回は、Knowledge Graphが何かを直感的に理解することを優先しよう。

「桃太郎」を構造化する

 それでは、具体的にGraphを使って文章を構造化してみよう。今回は、青空文庫で公開されている楠山正雄の「桃太郎」を構造化してみる。

青空文庫で公開されている楠山正雄の「桃太郎」を構造化する

 利用するツールは、Copilotのノートブックと「Neo4j」だ。Copilotのノートブックは、もとになる桃太郎の文章をNeo4jに登録するためのクエリ(Cypher Query Language)を生成するために利用する。

 Neo4jは、グラフ構造のデータを格納するためのデータベースシステムだ。クラウド上のサービス、またはセルフホスティングでも利用できるが、今回は無料で利用できる「AuraDB Free」を利用する。なお、詳細な手順は省くが、「Get Started for Free」からGoogleアカウントなどでサインインし、画面の指示に従ってデータベースを構成すれば簡単に利用できる。

 以下、Neo4j を利用する準備が整ったところから、手順に沿って紹介していこう。

▼Neo4j(Aura DB Free)を利用する
Neo4j

STEP 1:Cypherクエリを生成する

 まず、Neo4jにデータを登録するためのクエリを生成する。Neo4jでは、SQLライクの「Cypher」という言語を利用するが、知らなくても問題ない。今回は、全てCopilotに作ってもらう。

 トークン数としてトータルで6000トークン近くあるので、Copilotのノートブックを利用して次のようなプロンプトで依頼する。なお、「## テキスト」として青空文庫の文章を貼り付ける際は、あらかじめルビなど取り除いておくことが望ましい(データベースにルビが反映されてしまう)。

Copilotのノートブックを使ってCypherクエリを生成した

与えられたテキストを以下のルールに従って構造化してください。

## ルール
- 日本語で生成する
- Cypherクエリを生成する
- あなたの知識ではなく、必ず与えられたテキストの内容で生成する
- 文章を章ごとのチャンクに分割して、それぞれのチャンクごとにクエリを生成する
- 次の内容を構造化する
- 登場人物の名前、役割、性格、仕事
- 重要なアイテム
- 登場人物の間の関係
- 物語内のイベント、イベント内での登場人物のふるまい
- 登場人物の感情

## テキスト
桃太郎
楠山正雄


 むかし、むかし、あるところに、おじいさんとおばあさんが……(以下全文貼り付け)

 この結果、生成されたCypherクエリが以下のようなものになる。ノードとして「桃太郎」などのキャラクターを作成し、各データ間のリレーションを作成している。

CREATE (p:Person {name: '桃太郎'})
CREATE (g1:Person {name: 'おじいさん'})
CREATE (g2:Person {name: 'おばあさん'})
CREATE (d:Dog {name: '犬'})
CREATE (m:Monkey {name: '猿'})
CREATE (b:Bird {name: 'きじ'})
CREATE (o:Ogre {name: '鬼の大将'})

CREATE (p)-[:HAS_ROLE]->(:Role {name: '主人公', personality: '勇敢', job: '鬼退治'})
CREATE (g1)-[:HAS_ROLE]->(:Role {name: 'おじいさん', personality: '優しい', job: '山へしば刈り'})
CREATE (g2)-[:HAS_ROLE]->(:Role {name: 'おばあさん', personality: '優しい', job: '川へ洗濯'})
CREATE (d)-[:HAS_ROLE]->(:Role {name: '犬', personality: '忠実', job: '桃太郎の助け'})
CREATE (m)-[:HAS_ROLE]->(:Role {name: '猿', personality: '賢い', job: '桃太郎の助け'})
CREATE (b)-[:HAS_ROLE]->(:Role {name: 'きじ', personality: '視力が良い', job: '桃太郎の助け'})
CREATE (o)-[:HAS_ROLE]->(:Role {name: '鬼の大将', personality: '悪い', job: '鬼が島の支配'})

CREATE (p)-[:HAS_RELATIONSHIP {type: '孝行'}]->(g1)
CREATE (p)-[:HAS_RELATIONSHIP {type: '孝行'}]->(g2)
CREATE (p)-[:HAS_RELATIONSHIP {type: '友情'}]->(d)
CREATE (p)-[:HAS_RELATIONSHIP {type: '友情'}]->(m)
CREATE (p)-[:HAS_RELATIONSHIP {type: '友情'}]->(b)
CREATE (p)-[:HAS_RELATIONSHIP {type: '敵対'}]->(o)

CREATE (e1:Event {name: '桃太郎の誕生', description: '大きな桃から桃太郎が生まれた'})
CREATE (e2:Event {name: '鬼退治の旅', description: '桃太郎が犬、猿、きじと共に鬼退治の旅に出た'})
CREATE (e3:Event {name: '鬼が島の戦い', description: '桃太郎と仲間たちが鬼と戦った'})
CREATE (e4:Event {name: '帰郷', description: '桃太郎が宝物を持って帰郷した'})

CREATE (p)-[:PARTICIPATED_IN]->(e1)
CREATE (g1)-[:PARTICIPATED_IN]->(e1)
CREATE (g2)-[:PARTICIPATED_IN]->(e1)
CREATE (p)-[:PARTICIPATED_IN]->(e2)
CREATE (d)-[:PARTICIPATED_IN]->(e2)
CREATE (m)-[:PARTICIPATED_IN]->(e2)
CREATE (b)-[:PARTICIPATED_IN]->(e2)
CREATE (p)-[:PARTICIPATED_IN]->(e3)
CREATE (d)-[:PARTICIPATED_IN]->(e3)
CREATE (m)-[:PARTICIPATED_IN]->(e3)
CREATE (b)-[:PARTICIPATED_IN]->(e3)
CREATE (o)-[:PARTICIPATED_IN]->(e3)
CREATE (p)-[:PARTICIPATED_IN]->(e4)
CREATE (d)-[:PARTICIPATED_IN]->(e4)
CREATE (m)-[:PARTICIPATED_IN]->(e4)
CREATE (b)-[:PARTICIPATED_IN]->(e4)

CREATE (i1:Item {name: '桃'})
CREATE (i2:Item {name: 'きびだんご'})
CREATE (i3:Item {name: '宝物'})

CREATE (p)-[:HAS_ITEM]->(i1)
CREATE (p)-[:HAS_ITEM]->(i2)
CREATE (p)-[:HAS_ITEM]->(i3)

 なお、残念ながら、今回の出力では、Copilotの判断によって感情部分がスキップされてしまった。

 ChatGPTのGPT-4で、チャンクを500トークンなど細かくすれば、感情を含めたり、さらに詳細な状況まで説明するクエリを作ったりできるが、あまり詳細になると、視覚化しにくくなるので、とりあえずはこれくらいが妥当だろう。

STEP 2:Neo4jに登録する

 Cypherクエリができたら、Neo4jに貼り付けて実行するだけでいい。[Query]タブの[Database information]画面で、[neo4j$]に続けて貼り付け、右側の実行ボタンをクリックすれば、クエリが実行され、データが登録される。

 左側の[Database information]の一覧に、[Nodes]や[Relationships]のラベルが追加されたことが確認できるはずだ。

生成したクエリをNeo4jに貼り付けて実行

STEP 3:桃太郎のリレーションを確認する

 実際のデータを確認してみよう。[Nodes]の一覧で[Person]をクリックすると、登場人物として登録されている[おじいさん][おばあさん][桃太郎]の3つのノードが表示される。

 [桃太郎]を右クリックし、[Expand]を選択すると、周囲に[桃太郎]ノードに接続されているほかのノード(犬、鬼の大将、宝物など)が展開され、矢印でリレーションシップが表示される。

 例えば、[桃太郎]と[鬼の大将]のリレーションシップを示す「HAS_RELATIONSHIP」を選択すると、右側の[type]に[敵対]と表示される。

[Person]で登場人物を参照
[桃太郎]を展開するとリレーションシップがあるノードが表示される

STEP 4:イベントのリレーションを確認する

 続いて、Nodesの一覧から[Event]をクリックすると、[鬼が島の戦い][鬼退治の旅][桃太郎の誕生][帰郷]といったイベントが表示される。

 このうち、[鬼が島の戦い]を右クリックして[Expand]を選択すると、[桃太郎][犬][猿][きじ][鬼の大将]が[PARTICIPATED_IN]というリレーションシップで表示され、戦いに参加していた登場人物を確認できる。

Eventを参照
[鬼が島の戦い]を展開

STEP 5:最短ルートを探す

 最後に、鬼の大将がおじいさんと知り合うためのルートを探してみよう。次のようなクエリを使うと、ノード間の最短パスを検索できる。

MATCH (start:Ogre {name: '鬼の大将'}), (end:Person {name: 'おじいさん'}),
p = shortestPath((start)-[*..15]-(end))
RETURN p

 鬼の大将は、直接、おじいさんと面識がないので、戦った相手である桃太郎におじいさんを紹介してもらう必要がある。Knowledge Graphでは、このようにノードをたどって関係を表すこともできる。

鬼の大将とおじいさんの最短パスを検索

生成AIから使うとなると、まだまだ難しい

 以上、今回は、Neo4jを利用して、Knowledge Graphを体験してみた。物語のような文章が構造化される様子が視覚化されることで、「Graph」の概要をつかめたのではないかと思う。

 実際は、LangchainやLlamaindexを利用して、文書を自動的にNeo4jに格納したり、生成AIを利用してNeo4jのGraphデータベースに対して質問したりするという実装となる。今回の例はシンプルなので、あまり複雑な質問に回答することはできないが(前述した最短パスはLLMがCypherクエリを生成してくれない)、具体的には以下の画面のような使い方になる。

Langchainを利用してGPT-4経由で自然言語からCypherクエリを生成して、自動的にneo4jに接続して問い合わせを実行したところ

 まだまだ利用のハードルは高いので、このあたりを簡単に構築できるローコードツールが登場してくれることを期待したいところだ。

清水 理史

製品レビューなど幅広く執筆しているが、実際に大手企業でネットワーク管理者をしていたこともあり、Windowsのネットワーク全般が得意ジャンル。最新刊「できるWindows 11」ほか多数の著書がある。