ナレッジグラフを構築してみた
Neo4jで始めるナレッジグラフ作成ガイド
はじめに
皆さんはインターネットで情報を検索するとき、関連する情報が繋がって表示される経験はありませんか?これは「ナレッジグラフ」という技術によるものです。ナレッジグラフは、データ同士の関係性を可視化し、情報をより理解しやすくするための強力なツールです。本記事では、初心者の方にもわかりやすいように、ナレッジグラフの基本概念から、その構築方法、そしてNeo4jを使った実践的な手法までを解説します。
ナレッジグラフについて
概念
ナレッジグラフとは、情報(ノード)とその関係性(エッジ)をグラフ構造で表現したものです。例えば、人間関係を示す家系図や、駅と路線で繋がった鉄道路線図をイメージすると分かりやすいでしょう。
重要性
大量のデータが溢れる現代、情報同士の関係性を理解することは非常に重要です。ナレッジグラフを用いることで、データ間の関連性を直感的に把握でき、新たな洞察や発見につなげることができます。
応用範囲
- 検索エンジン:ユーザーの意図を理解し、関連情報を提供
- 医療:症状と疾患、治療法の関係性を分析
- SNS:ユーザー同士の関係や興味を把握し、コンテンツを推薦
ナレッジグラフ使用例・比較
- Googleナレッジグラフ:検索結果に人物や場所、物事の詳細情報を表示
- Facebook Graph:ユーザーの友人関係や興味を分析し、広告やコンテンツを最適化
これらは従来のリレーショナルデータベースと比較して、複雑な関係性を効率的に扱える点で優れています。
ナレッジグラフ構築サイトの紹介・比較
代表的な構築ツール
- Wikidata:ウィキペディアのデータを構造化し、誰でも編集可能
- DBpedia:ウィキペディアの情報を抽出し、機械が読み取れる形式で提供
比較
ツール名 | 特徴 | 利点 |
---|---|---|
Wikidata | オープンな共同編集プラットフォーム | 多言語対応、豊富なデータ |
DBpedia | ウィキペディアからの情報抽出 | 自動化されたデータ収集、機械学習に適用可能 |
これらのツールを使うことで、大規模なナレッジグラフを容易に構築・利用できます。
Neo4jの解説・使用言語の解説
Neo4jとは
Neo4jは、世界的に利用されているグラフデータベース管理システムです。ノードとエッジを直感的に扱え、大量のデータでも高速にクエリを実行できます。
使用言語:Cypher
Neo4jでデータを操作する際には、**Cypher(サイファー)**というクエリ言語を使用します。これは、グラフ構造を視覚的に表現でき、学習コストが低いのが特徴です。
例:ノードの作成
CREATE (a:Person {name: 'Alice'})
例:関係の作成
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
CREATE (a)-[:KNOWS]->(b)
Neo4jの使い方説明
基本的な操作
データの投入
- プロジェクトの作成:新しいプロジェクトを作成します。
- データベースの作成:プロジェクト内で新しいグラフデータベースを作成します。
- Cypherクエリの実行:データベースを起動し、Cypherクエリを実行します。
例:人物と関係性の追加
CREATE (alice:Person {name: 'Alice'})
CREATE (bob:Person {name: 'Bob'})
CREATE (alice)-[:FRIENDS_WITH]->(bob)
データのクエリ
例:友人関係の検索
MATCH (person:Person)-[:FRIENDS_WITH]->(friend)
RETURN person.name, friend.name
実践例:簡単な家系図の作成
- 家族ノードの作成
CREATE (john:Person {name: 'John'})
CREATE (jane:Person {name: 'Jane'})
CREATE (emma:Person {name: 'Emma'})
- 関係性の定義
CREATE (john)-[:MARRIED_TO]->(jane)
CREATE (john)-[:PARENT_OF]->(emma)
CREATE (jane)-[:PARENT_OF]->(emma)
- クエリの実行:子供を持つ人物の検索
MATCH (parent:Person)-[:PARENT_OF]->(child:Person)
RETURN parent.name, child.name
結果
Neo4jをいじるには、cypher言語を習得しないといけないのか・・・むずいです。
なんとか、私の知る言語でいじれないかなぁと努力したのが以下です。
Google ColaboratoryでNeo4jを操作するガイド
必要な環境の準備
まずは、作業に必要なアカウントやツールを準備しましょう。
- Googleアカウント:Colabを使用するために必要です。
- Neo4jアカウント:Neo4j AuraDB Freeを利用するために作成します。
Neo4j AuraDB Freeのセットアップ
1. Neo4jアカウントの作成
- **Neo4j AuraDB Free**のページにアクセスします。
- 画面右上の「Start Free」ボタンをクリックします。
- 必要な情報(メールアドレス、パスワードなど)を入力してアカウントを作成します。
- 登録したメールアドレスに確認メールが届くので、リンクをクリックして認証を完了します。
2. 無料データベースの作成
- アカウント作成後、Neo4j Auraのダッシュボードにアクセスします。
- 「Create a Free Database」ボタンをクリックします。
- データベースの名前(例:
mydb
)を入力します。 - リージョンを選択します。日本に近いリージョンを選ぶと接続が速くなります。
- 「Create」をクリックしてデータベースを作成します。数十秒で準備が完了します。
3. 接続情報の取得
- 作成したデータベースの「Connect」ボタンをクリックします。
- 接続情報が表示されます。
-
Bolt URL(例:
neo4j+s://xxxx.databases.neo4j.io
) -
Username:
neo4j
- Password:自動生成されたパスワード(コピーしてメモしておきます)
-
Bolt URL(例:
- Importantと表示されるパスワードは一度しか表示されないため、必ず控えてください。
Colabでのライブラリインストール
1. Colabノートブックの作成
- **Google Colaboratory**にアクセスします。
- 「新しいノートブック」をクリックします。
2. Neo4jドライバのインストール
最初のセルに以下のコマンドを入力し、Shift + Enterで実行します。
!pip install neo4j
- これにより、PythonからNeo4jに接続するためのドライバがインストールされます。
PythonからNeo4jへの接続
1. 接続設定の記述
新しいセルを作成し、以下のコードを入力します。先ほど控えた接続情報を反映させます。
from neo4j import GraphDatabase
# 接続情報の設定
uri = "neo4j+s://<YOUR_BOLT_URL>"
username = "neo4j"
password = "<YOUR_PASSWORD>"
# ドライバの作成
driver = GraphDatabase.driver(uri, auth=(username, password))
-
<YOUR_BOLT_URL>
:取得したBolt URLを貼り付けます。 -
<YOUR_PASSWORD>
:取得したパスワードを貼り付けます。
2. ドライバのテスト
接続が正しく行えるかを確認します。
# ドライバのテスト
with driver.session() as session:
greeting = session.run("RETURN 'Hello, Neo4j!' AS message").single()
print(greeting["message"])
- 正常に接続されていれば、
Hello, Neo4j!
と表示されます。
参考リンク
Google ColaboratoryでNeo4jを操作する:コード解説と実践ガイド
コードの解説と実行
それでは、基本的なcypher言語をpython版のコードに置き換えながら、基本的なNeo4j動作をColab上で確認していきます。
コード1:Rosaがベルリンに住んでいるデータの作成
- Personノード(name: 'Rosa')とPlaceノード(city: 'Berlin', country: 'DE')を作成します。
- RosaがBerlinにsince: 2020から住んでいることを示すLIVES_INリレーションシップを作成します。
CREATE (:Person { name: 'Rosa' })-[:LIVES_IN {since: 2020}]->(:Place {city: 'Berlin', country: 'DE'})
Colabでの実行方法
PythonコードでCypherクエリを実行します。
def create_rosa(tx):
tx.run("""
CREATE (:Person { name: 'Rosa' })-[:LIVES_IN {since: 2020}]->(:Place {city: 'Berlin', country: 'DE'})
""")
with driver.session() as session:
session.execute_write(create_rosa)
結果の確認
def get_rosa(tx):
result = tx.run("""
MATCH (p:Person { name: 'Rosa' })-[:LIVES_IN]->(place:Place)
RETURN p.name AS person, place.city AS city, place.country AS country
""")
return result.single()
with driver.session() as session:
record = session.execute_read(get_rosa)
print(f"{record['person']} lives in {record['city']}, {record['country']}")
-
出力
Rosa lives in Berlin, DE
コード2:データベース内のノードとリレーションシップの削除
- すべてのノードを削除しようとしますが、リレーションシップが存在する場合はエラーになります。
- LIVES_INリレーションシップを削除します。
- DETACH DELETEを使用して、すべてのノードとそのリレーションシップを削除します。
MATCH (n)
DELETE n
MATCH ()-[r:LIVES_IN]->()
DELETE r
MATCH (n)
DETACH DELETE n
Colabでの実行方法
エラーを避けるため、適切な順序で実行します。
def delete_all(tx):
# まず、すべてのリレーションシップを削除
tx.run("MATCH ()-[r]->() DELETE r")
# 次に、すべてのノードを削除
tx.run("MATCH (n) DELETE n")
with driver.session() as session:
session.execute_write(delete_all)
または、DETACH DELETE
を使用して一度に削除します。
def detach_delete_all(tx):
tx.run("MATCH (n) DETACH DELETE n")
with driver.session() as session:
session.execute_write(detach_delete_all)
結果の確認
def count_nodes(tx):
result = tx.run("MATCH (n) RETURN count(n) AS count")
return result.single()["count"]
with driver.session() as session:
node_count = session.execute_read(count_nodes)
print(f"Total nodes in the database: {node_count}")
-
出力
Total nodes in the database: 0
コード3:ロンドンに住むFredとKarlのデータの作成
- Placeノード(city: 'London', country: 'UK')を作成またはマッチします。
- Personノード(name: 'Fred'とname: 'Karl')を作成またはマッチします。
- FredとKarlがLondonに住んでいることを示すLIVES_INリレーションシップを作成します。
MERGE (london:Place { city: 'London', country: 'UK' })
MERGE (fred:Person { name: 'Fred' })
MERGE (fred)-[:LIVES_IN]->(london)
MERGE (karl:Person { name: 'Karl' })
MERGE (karl)-[:LIVES_IN]->(london)
Colabでの実行方法
def create_london_residents(tx):
tx.run("""
MERGE (london:Place { city: 'London', country: 'UK' })
MERGE (fred:Person { name: 'Fred' })
MERGE (fred)-[:LIVES_IN]->(london)
MERGE (karl:Person { name: 'Karl' })
MERGE (karl)-[:LIVES_IN]->(london)
""")
with driver.session() as session:
session.execute_write(create_london_residents)
結果の確認
def get_london_residents(tx):
result = tx.run("""
MATCH (p:Person)-[:LIVES_IN]->(place:Place { city: 'London' })
RETURN p.name AS person
""")
return [record["person"] for record in result]
with driver.session() as session:
residents = session.execute_read(get_london_residents)
print(f"Residents of London: {residents}")
-
出力
Residents of London: ['Fred', 'Karl']
コード4:ベルリンに住む人物の取得
- city: 'ベルリン', country: 'DE'のPlaceに住むPersonノードをマッチします。
- 該当するPersonノードを返します。
MATCH (p:Person)-[:LIVES_IN]->(:Place { city: 'ベルリン', country: 'DE' })
RETURN p
Colabでの実行方法
ベルリンの表記を'Berlin'
に修正します(前述のコードではcity: 'Berlin'
としています)。
def get_berlin_residents(tx):
result = tx.run("""
MATCH (p:Person)-[:LIVES_IN]->(:Place { city: 'Berlin', country: 'DE' })
RETURN p.name AS person
""")
return [record["person"] for record in result]
with driver.session() as session:
residents = session.execute_read(get_berlin_residents)
print(f"Residents of Berlin: {residents}")
-
出力
Residents of Berlin: ['Rosa']
コード5:Rosaの友達の友達を取得(2ステップの友人関係)
- name: 'Rosa'のPersonノードから、2ステップのFRIENDリレーションシップを辿って到達できるPersonノードをマッチします。
- 該当する友達の友達(fof)を返します。
MATCH (:Person { name: 'Rosa' })-[:FRIEND*2..2]->(fof:Person)
RETURN fof
Colabでの実行方法
まず、FRIEND
リレーションシップを持つデータを作成します。
def create_friendships(tx):
# Rosa、Alice、Bobのノードを作成
tx.run("MERGE (rosa:Person { name: 'Rosa' })")
tx.run("MERGE (alice:Person { name: 'Alice' })")
tx.run("MERGE (bob:Person { name: 'Bob' })")
# RosaとAliceの間にFRIENDリレーションシップを作成
tx.run("""
MATCH (rosa:Person { name: 'Rosa' })
MATCH (alice:Person { name: 'Alice' })
MERGE (rosa)-[:FRIEND]->(alice)
""")
# AliceとBobの間にFRIENDリレーションシップを作成
tx.run("""
MATCH (alice:Person { name: 'Alice' })
MATCH (bob:Person { name: 'Bob' })
MERGE (alice)-[:FRIEND]->(bob)
""")
with driver.session() as session:
session.execute_write(create_friendships)
次に、クエリを実行します。
def get_friends_of_friends(tx):
result = tx.run("""
MATCH (:Person { name: 'Rosa' })-[:FRIEND*2..2]->(fof:Person)
RETURN fof.name AS friend_of_friend
""")
return [record["friend_of_friend"] for record in result]
with driver.session() as session:
fof = session.execute_read(get_friends_of_friends)
print(f"Rosa's friends of friends: {fof}")
-
出力
Rosa's friends of friends: ['Bob']
コード6:Rosa自身を除く、Rosaの友達の友達を取得
- name: 'Rosa'のPersonノードから、2ステップのFRIENDリレーションシップを辿って到達できるPersonノードをマッチします。
- WHERE句でRosa自身を除外します。
- 該当する友達の友達(fof)を返します。
MATCH (rosa:Person{ name: 'Rosa' })-[:FRIEND*2..2]->(fof:Person)
WHERE rosa <> fof
RETURN fof
Colabでの実行方法
既にデータは作成済みなので、クエリのみ実行します。
def get_friends_of_friends_excluding_self(tx):
result = tx.run("""
MATCH (rosa:Person{ name: 'Rosa' })-[:FRIEND*2..2]->(fof:Person)
WHERE rosa <> fof
RETURN fof.name AS friend_of_friend
""")
return [record["friend_of_friend"] for record in result]
with driver.session() as session:
fof = session.execute_read(get_friends_of_friends_excluding_self)
print(f"Rosa's friends of friends (excluding self): {fof}")
-
出力
Rosa's friends of friends (excluding self): ['Bob']
コード7:ベルリンの居住者から1〜2ステップ以内の友人を取得
- city: 'Berlin'のPlaceに住むPersonノードをマッチします。
- そのPersonノードから、1〜2ステップのFRIENDリレーションシップを辿って到達できる他のPersonノードをマッチします。
- WHERE句で元のPerson自身を除外します。
- 該当する友人(f)を返します。
MATCH (:Place { city: 'Berlin' })<-[:LIVES_IN]-(p:Person)<-[:FRIEND*1..2]-(f:Person)
WHERE f <> p
RETURN f
Colabでの実行方法
友人関係を拡張します。
def create_berlin_residents_and_friends(tx):
# ベルリンのノードを作成
tx.run("MERGE (berlin:Place { city: 'Berlin', country: 'DE' })")
# Rosaを作成し、ベルリンに住む
tx.run("MERGE (rosa:Person { name: 'Rosa' })")
tx.run("""
MATCH (rosa:Person { name: 'Rosa' })
MATCH (berlin:Place { city: 'Berlin', country: 'DE' })
MERGE (rosa)-[:LIVES_IN]->(berlin)
""")
# Rosaの友人Aliceを作成
tx.run("MERGE (alice:Person { name: 'Alice' })")
tx.run("""
MATCH (rosa:Person { name: 'Rosa' })
MATCH (alice:Person { name: 'Alice' })
MERGE (rosa)-[:FRIEND]->(alice)
""")
# Aliceの友人Bobを作成
tx.run("MERGE (bob:Person { name: 'Bob' })")
tx.run("""
MATCH (alice:Person { name: 'Alice' })
MATCH (bob:Person { name: 'Bob' })
MERGE (alice)-[:FRIEND]->(bob)
""")
# Bobの友人Charlieを作成し、ロンドンに住む
tx.run("MERGE (charlie:Person { name: 'Charlie' })")
tx.run("MERGE (london:Place { city: 'London', country: 'UK' })")
tx.run("""
MATCH (bob:Person { name: 'Bob' })
MATCH (charlie:Person { name: 'Charlie' })
MERGE (bob)-[:FRIEND]->(charlie)
""")
tx.run("""
MATCH (charlie:Person { name: 'Charlie' })
MATCH (london:Place { city: 'London', country: 'UK' })
MERGE (charlie)-[:LIVES_IN]->(london)
""")
with driver.session() as session:
session.execute_write(create_berlin_residents_and_friends)
クエリを実行します。
def get_friends_near_berlin(tx):
result = tx.run("""
MATCH (:Place { city: 'Berlin' })<-[:LIVES_IN]-(p:Person)
MATCH (p)-[:FRIEND*1..2]->(f:Person)
WHERE f <> p
RETURN DISTINCT f.name AS friend
""")
return [record["friend"] for record in result]
with driver.session() as session:
friends = session.execute_read(get_friends_near_berlin)
print(f"Friends within 1-2 steps of Berlin residents: {friends}")
-
出力
Friends within 1-2 steps of Berlin residents: ['Alice', 'Bob']
コード8:Fredの名前を'Freddy'に変更(文字列の連結)
- name: 'Fred'のPersonノードをマッチします。
- apoc.atomic.concat関数を使用して、nameプロパティに'dy'を連結します。
- 変更前の値(oldValue)と変更後の値(newValue)を返します。
MATCH (p: Person { name: 'Fred' })
CALL apoc.atomic.concat(p, 'name', 'dy')
YIELD oldValue, newValue
RETURN oldValue, newValue
解説
-
目的:
Fred
の名前に'dy'
を追加して、Freddy
にします。apoc.atomic.concat
はAPOCプラグインの機能です。
Colabでの実行方法
注意: Neo4j AuraDB FreeではAPOCプラグインの一部機能が制限されています。このため、このコードは直接実行できません。
しかし、SET
を使って同様の操作が可能です。
def rename_fred(tx):
result = tx.run("""
MATCH (p:Person { name: 'Fred' })
WITH p, p.name AS oldValue
SET p.name = p.name + 'dy'
RETURN oldValue, p.name AS newValue
""")
return result.single()
with driver.session() as session:
record = session.execute_write(rename_fred)
print(f"Old name: {record['oldValue']}, New name: {record['newValue']}")
-
出力
Old name: Fred, New name: Freddy
コード9:現在の日時をエポック秒から日数に変換
- datetime().epochSecondsで現在のエポック秒を取得します。
- apoc.date.convert関数を使用して、秒数を日数に変換します。
- 結果をoutputInDaysとして返します。
RETURN apoc.date.convert(datetime().epochSeconds, 'seconds', 'days') AS outputInDays
Colabでの実行方法
APOCプラグインが使用できない場合、Cypherの標準機能で代替します。
def get_current_date_in_days(tx):
result = tx.run("""
RETURN duration.between(date('1970-01-01'), date()).days AS outputInDays
""")
return result.single()["outputInDays"]
# 5. セッション内で関数を実行
with driver.session() as session:
days = session.execute_read(get_current_date_in_days)
print(f"Days since Unix epoch: {days}")
-
出力
Days since Unix epoch: <日数が表示されます>
終わりに
これをベースに、RAGを用いた機械学習に発展していきたいです。
Discussion