0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで超手軽に使えるベクトル検索 ― nlp4j-local-search 0.8.0.0

0
Posted at

Pythonで超手軽に使えるベクトル検索 ― nlp4j-local-search 0.8.0.0

ベクトル検索を試してみたいと思っても、実際に動かそうとすると意外と準備が大変です。

  • Embeddingモデルを用意する
  • テキストをベクトル化する
  • ベクトルを保存する
  • ベクトル検索エンジンを用意する
  • クエリも同じモデルでベクトル化する
  • 類似ベクトルを検索する

ちょっと試したいだけなのに、検索するまでの準備だけで結構なコードになってしまいます。

そこで nlp4j-local-search 0.8.0.0 では、これらをできるだけ意識せず、普通のPythonコードからベクトル検索を使えるようにしました。

PyPI:

インストール

まず検索ライブラリをインストールします。

pip install nlp4j-local-search

テキストから自動的にEmbeddingを生成する場合は、Embedding用パッケージもインストールします。

pip install nlp4j-local-search-embedding

Embeddingにはデフォルトで intfloat/multilingual-e5-large を利用します。

初回実行時にはモデル(約2.2GB)がHugging Faceからダウンロードされるため、最初だけ少し時間がかかります。

とりあえずベクトル検索してみる

用意するデータは普通のテキストです。

from nlp4j_local_search import SearchEngine

documents = [
    {"id": "1", "body": "京都は日本の古都です"},
    {"id": "2", "body": "東京は日本最大の都市です"},
    {"id": "3", "body": "奈良には歴史的な寺院があります"},
    {"id": "4", "body": "大阪は食文化で有名な都市です"},
    {"id": "5", "body": "北海道は広大な自然が広がっています"},
]

with SearchEngine("ja", embedding=True) as engine:

    for doc in documents:
        engine.add(doc["id"], doc["body"])

    engine.commit()

    for r in engine.search_semantic("日本の古い都", limit=3):
        print(r.id, r.body, r.score)

ポイントは、

SearchEngine("ja", embedding=True)

です。

embedding=True を指定すると、Embeddingモデルがロードされ、add() したテキストが自動的にベクトル化されます。

つまり、

engine.add("1", "京都は日本の古都です")

と書くだけです。

自分でEmbeddingを計算して、

[0.0234, -0.0182, 0.0421, ...]

のようなベクトルを扱う必要はありません。

検索するときも同じです。

engine.search_semantic("日本の古い都")

これだけで検索文字列が自動的にEmbeddingされ、ベクトル検索が実行されます。

「京都」と書かなくても京都が見つかる

通常のキーワード検索では、基本的には文書に含まれる単語を使って検索します。

一方、ベクトル検索では文章の意味的な近さを利用できます。

例えば登録されている文章が、

京都は日本の古都です

だったとしても、

engine.search_semantic("日本の古い都")

のように検索できます。

サンプルではさらに、

queries = [
    "日本の古い都",
    "大きな街",
    "お寺や神社",
]

のような検索を行っています。

for query in queries:
    print(f"=== セマンティック検索: '{query}' ===")

    for r in engine.search_semantic(query, limit=3):
        print(f"[{r.id}] {r.body} (score: {r.score:.4f})")

例えば「お寺や神社」という検索語から、

奈良には歴史的な寺院があります

のような意味的に近い文章を探す、といった使い方ができます。

キーワード検索もそのまま使える

embedding=True にしたからといって、ベクトル検索専用になるわけではありません。

従来のキーワード検索も利用できます。

for r in engine.search("京都", limit=5):
    print(r.id, r.body, r.score)

つまり同じ SearchEngine から、

engine.search("京都")

ならキーワード検索、

engine.search_semantic("日本の古い都")

ならセマンティック検索、

というように使い分けられます。

個人的には、

search()
search_semantic()

とAPIを明確に分けることで、コードを見ただけで「どの種類の検索をしているのか」が分かるようにしています。

フィルター付きベクトル検索もできる

実際の検索アプリでは、

全文書から意味的に近いものを検索

するだけでなく、

条件に一致する文書の中から意味的に近いものを検索

したいケースがよくあります。

search_semantic() では filter_query を指定できます。

for r in engine.search_semantic(
    "都市",
    limit=5,
    filter_query="id:1 OR id:2",
):
    print(r.id, r.body, r.score)

この場合、まず

id:1 OR id:2

という条件で検索対象を絞り、その中で「都市」という意味に近い文書をベクトル検索できます。

フィルター部分にはLucene Query Syntaxを利用できます。

そのため、ベクトル検索と通常のフィールド検索を組み合わせた検索も構築できます。

中ではLucene 10.xが動いている

0.8.0.0では、検索エンジンとしてApache Lucene 10.xを利用しています。

Pythonから見ると、

engine.add(...)
engine.search(...)
engine.search_semantic(...)

というシンプルなAPIですが、その裏側ではLuceneのインデックスとベクトル検索機能を利用しています。

そのため、別途Elasticsearch、OpenSearch、Solrなどの検索サーバーを立ち上げる必要はありません。

Dockerで検索サーバーを起動する必要もありません。

PythonプロセスからローカルのLuceneを直接利用します。

イメージとしては、

Python
  |
  +-- nlp4j-local-search
        |
        +-- Embedding
        |     |
        |     +-- multilingual-e5-large
        |
        +-- Apache Lucene 10.x
              |
              +-- Keyword Search
              +-- Vector Search

という非常にシンプルな構成です。

ベクトルDBを準備しなくてもベクトル検索できる

今回やりたかったことの一つがこれです。

「ベクトル検索を試してみたい」

というだけなのに、

  • ベクトルDBを選ぶ
  • サーバーを構築する
  • Dockerを準備する
  • 接続設定をする
  • Embeddingライブラリを選ぶ
  • モデルをロードする
  • ベクトルのdimensionを管理する

といった準備が必要になると、検索そのものを試すまでのハードルが上がります。

nlp4j-local-search では、

pip install nlp4j-local-search
pip install nlp4j-local-search-embedding

として、

with SearchEngine("ja", embedding=True) as engine:

と書けば、そこから普通のPythonコードとしてベクトル検索を始められます。

もちろんEmbeddingを直接扱うこともできる

embedding=True は「テキストを簡単にベクトル検索したい」場合の便利な機能です。

一方、0.8.0.0ではVectorフィールドそのものを扱うこともできます。

つまり、

テキスト
 ↓
nlp4j-local-searchがEmbedding
 ↓
Lucene Vector Search

という使い方だけでなく、

自分で用意したVector
 ↓
Lucene Vector Search

という使い方も可能です。

独自のEmbeddingモデルを使いたい場合や、すでにベクトル化済みのデータを持っている場合にも利用できます。

まとめ

nlp4j-local-search 0.8.0.0では、PythonからLucene 10.xのベクトル検索をかなり手軽に利用できるようになりました。

最小限覚える必要があるのは、ほぼこの3つです。

SearchEngine("ja", embedding=True)

engine.add(id, text)

engine.search_semantic(query)

Embeddingモデルのロード、文書のベクトル化、検索クエリのベクトル化、Luceneへのベクトル登録と検索はライブラリ側で処理します。

ベクトル検索を使うために、大掛かりな検索サーバーやベクトルDBを準備する必要もありません。

「まずPythonでベクトル検索を試してみたい」

という用途であれば、かなり小さなコードから始められるようになったと思います。

from nlp4j_local_search import SearchEngine

with SearchEngine("ja", embedding=True) as engine:
    engine.add("1", "京都は日本の古都です")
    engine.add("2", "東京は日本最大の都市です")
    engine.commit()

    for r in engine.search_semantic("日本の古い都"):
        print(r.body)

ベクトル検索を、普通のPythonの検索処理くらい手軽に。

それが今回の nlp4j-local-search 0.8.0.0 の狙いです。

Links

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?