Neo4j を VectorDB として使う

Neo4j は v5.13 で Vector search index を実装した。使い方は他の VectorDB と同様で、ベクタライズしたデータを Vector Index に格納する。Cypher クエリを通じて近似解を導出する。

この記事では、Vector Index の使い方について紹介する。具体例では、ノートアプリのバックエンドとして Neo4j を VectorDB として利用する想定で紹介する。"Note" ノードはそれぞれのノートの ID やメタデータを持つ。ノートのタイトルは "Title" ノードに保存する。今回の目玉となる Vector Index を "Abstract" ノードに担当させる。

CREATE
  (note:Note)-[:HAS]->(title:Title {text="Hello, world!"})
  , (note)-[:HAS]->(abstract:Abstract)
RETURN
  note, title, abstract

Neo4j Vector Search data-model

まずは下準備について。db.index.vector.createNodeIndex 命令を利用して、Vector Index を作成する。例えば、ノートアプリの要約を保存する "Abstract" インデックスを、ベクター数 768 で作成するコマンドは以下の通り。embedding プロパティにベクタライズしたデータを保存していく。Similarity Search のアルゴリズムとしては Euclidean と Cosine が現時点では使え、以下の例では Cosine Similarity を指定している。

CALL db.index.vector.createNodeIndex(
  'abstract-embeddings',
  'Abstract',
  'embedding',
  768,
  'cosine'
)

次は書き込みについて。db.create.setNodeVectorProperty 命令を利用して、ベクターデータを格納する。これで読み込みの準備は完了だ。

MATCH
  (abstract:Abstract)<-[:HAS]-(note:Note)-[:HAS]->(title:Title{text="Hello, world!"})
CALL
  db.create.setNodeVectorProperty(
    abstract,
    'embedding',
    [0.312, 0.234, -0.456, ...]
  )
RETURN
  abstract, note, title

ベクターデータをすべての Vector Index に格納したら、最後は読み込みだ。db.index.vector.queryNodes 命令を利用して、Vector Index から近似スコアを導出する。例えば以下の例では、Hello world! I'm LLM app! というタイトルを持つ "Note" ノードに近似した別の "Note" ノードを、Vector Index である "Abstract" ノードのスコア上位三件を取得している。

MATCH (title:Title)<--(:Note)-->(abstract:Abstract)
WHERE title.text = "Hello world! I'm LLM app!"

CALL db.index.vector.queryNodes(
  'abstract-embeddings',
  3,
  abstract.embedding
)
YIELD node AS similarAbstract, score

MATCH (similarAbstract)<--(:Note)-->(similarTitle:Title)
RETURN similarTitle.text AS title, score

以下がクエリ結果の例だ。"Hello world!" を含んだノートが上位スコアで返却されていることがわかる。これは "title" プロパティに格納されたベクターデータの Cosine Similarity を計算し、最もベクトル座標上近い上位三件を取得した結果である。

╒══════════════════════════════════════════════════════════════════╤══════════════════╕
│title                                                             │score             │
╞══════════════════════════════════════════════════════════════════╪══════════════════╡
│"Hello world! I'm LLM app!"                                       │1.0               │
├──────────────────────────────────────────────────────────────────┼──────────────────┤
│"Hello world! Here is a RAG demo."                                │0.8671051263809204│
├──────────────────────────────────────────────────────────────────┼──────────────────┤
│"Here is a RAG demo. I'm going to show you how to create the app."│0.8667137622833252│
└──────────────────────────────────────────────────────────────────┴──────────────────┘
2023-12-03