本記事は、情報検索・検索技術Advent Calendar 2025 の15日目の記事です。

はじめに

ビズリーチの検索基盤グループで機械学習エンジニアをしている渡會です。

近年、ベクトル検索エンジンとニューラルネットワークを組み合わせたセマンティック検索が急速に普及しています。その中でも、キーワード検索の解釈性とニューラルネットワークの表現力を兼ね備えたSparse Vector Search(疎ベクトル検索) への注目が高まっています。

これまで私たちのチームでは、日本語特化のSparse Embeddingモデルであるlight-spladeの公開や、Qdrantを用いたDense Vector Searchの活用事例など、検索領域における企業と求職者のマッチング機会を創出するための課題解決の取り組みを発信してきました。

本記事では、これらの知見を踏まえた応用編として、Vector DatabaseであるQdrantのSparse Vector機能とSPLADEモデルを組み合わせた、実践的な検索システムの実装例を紹介します。

アジェンダ

  1. Sparse Vector Searchの基礎: Inverted IndexとSparse Vectorの仕組み
  2. 構成要素の紹介: SPLADE (モデル) とQdrant (Vector Database)
  3. アーキテクチャ: 検索処理の全体像とデータフロー
  4. 実装: light-spladeとQdrantを用いた具体的なコード例
  5. 実用化のポイント: 導入に向けた検討事項

1. Sparse Vector Searchの基礎

ベクトル検索は、Dense Vector(密ベクトル)を用いたセマンティック検索が主流ですが、厳密なキーワード一致や、学習データに含まれない専門用語の検索においては課題が残ります。

例えば、人材マッチングにおいて、ニュアンスが似ているだけの求人やスキルがヒットしてはミスマッチにつながるため、条件に合致するキーワードをピンポイントで捉えたいケースなどがこれに当たります。

そこで、これらの弱点を補いつつキーワード検索の強みを活かせるSparse Vector(疎ベクトル)検索が再び注目されています。まずはその仕組みを整理します。

1-1. Sparse Vector(疎ベクトル)とは

Sparse Vectorとは、多くの要素がゼロで構成されるベクトルのことです。 実際は数万次元になりますが、簡略化した例として、12次元のベクトルで表現すると以下のようになります。

例: [0, 0, 3.5, 0, 0, 0, 1.2, 0, 0, 0, 0, 4.7] (ほとんどが0で、特定の要素のみが値を持ちます)

Sparse Vectorの特徴として以下が挙げられます。

  1. 解釈可能性: 各次元が「単語(語彙)」に対応するため、どの単語が重要視されたかが人間にも理解しやすい。
  2. 現実の反映: ドキュメントに関連する語彙のみが反映されるため、情報の表現として自然です。
  3. 親和性: 従来の全文検索技術である「転置インデックス」をそのまま活用できる。

1-2. Inverted Index(転置インデックス)による高速化

Inverted Index(転置インデックス)は、キーワード検索エンジンで広く用いられるデータ構造です。以下の図のように、各単語(特徴量)に対して、その単語が出現するドキュメントIDのリストを保持します。

inverted-index
Inverted Index

Inverted Indexの特徴として以下が挙げられます。

  1. 構造: 各特徴量(単語/次元)に対し、「その値が非ゼロであるドキュメントIDのリスト」を保持します。
  2. 検索: クエリに含まれる非ゼロの要素に対応するドキュメントのみをリストから取得し、スコア計算を行います。これにより、全件走査を回避し、高速な検索を実現します。

Sparse Vector Searchは、多くの要素がゼロである特性を利用し、Inverted Indexを用いて実装されます。これにより、キーワード検索の高速性とニューラルネットワークの表現力を両立できます。

2. 構成要素の紹介

今回は、Sparse Vector Searchを実現するために以下の2つの技術を採用します。

これらの技術について簡単に紹介します。

2-1. SPLADE (Sparse Lexical AnD Expansion)

SPLADE は、Neural Retrievalモデルの一種で、BERTベースのアーキテクチャを用いてSparse Vectorを生成します。 以下の図は、SPLADEを使った検索の概要を示しており、入力テキストをSparse Vectorに変換してInverted Indexで検索する仕組みを表しています。

splade
SPLADE Encode & Search

特徴として以下が挙げられます。

以下は、テキストをSPLADEでエンコードした際の例です。(あくまでも例示なので、出力が異なる場合があります。)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
text = "Qdrant is a vector database"
sparse_vector = splade_model.encode(text)
# 出力例 (トークンとスコアのペア)
# {
#   "Qdrant": 2.5,
#   "vector": 1.8,
#   "database": 3.0,
#   "search": 0.5,  # 関連語彙として付与
#   # その他の語彙: 0.0
# }

2-2. Qdrant

Qdrant は、Rustで実装された高機能なオープンソースのベクトル検索エンジン(Vector Database)です。AIアプリケーション向けに設計されており、大規模なベクトルデータを高速に検索・管理するための「プロダクションレディ」な機能を備えています。

主な特徴として以下が挙げられます。

通常のベクトル検索エンジンはHNSWアルゴリズムなどでDense Vectorを扱いますが、QdrantはInverted Indexを持っています。今回はこの機能を利用して、SPLADEで生成したSparse Vectorの格納と検索を行います。

3. アーキテクチャ: SPLADE + Qdrantの検索フロー

SPLADEでベクトル化し、Qdrantで検索を行う全体の流れは以下のようになります。

splade-sparse-retrieval
Sparse Vector Search の全体フロー
  1. インデックス作成
  1. 検索

流れを整理したので、次に実際のコード例を見ていきましょう!

4. 実装例

実際に SPLADEQdrant を組み合わせて実装してみます。 今回は、Sparse Embeddingの生成にlight-spladeを使用します。 light-spladeは、BizReach (Visional) がOSSとして公開しているライブラリです。日本語の事前学習モデルをベースにSPLADEの学習を行っており、以下の特徴があります。

使用するコード、ライブラリ、モデルは以下の通りです。

それでは、実装の詳細を見ていきましょう。

4-1. ヘルパー関数の準備 (encode.py)

まず、SPLADEの出力をQdrantのSparseVector形式(インデックスと値のリスト)に変換する関数を用意します。トークン文字列をtoken2idを使って数値IDに変換している点がポイントです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# encode.py
import torch
from light_splade import SpladeEncoder
from qdrant_client import models

def encode_documents2points(encoder: SpladeEncoder, docs: list[str]) -> list[models.PointStruct]:
    token2id: dict[str, int] = encoder.tokenizer.get_vocab()

    # ドキュメントをSparse Vectorにエンコード
    with torch.inference_mode():
        embeddings: torch.Tensor = encoder.encode(docs)
        sparse_vectors: list[dict[int, float]] = encoder.to_sparse(embeddings)

    # QdrantへUpsertするためのPointStructを作成
    points = []
    for i, (doc, sparse_vector) in enumerate(zip(docs, sparse_vectors)):
        indices = []
        values = []
        # トークンID(indices)とスコア(values)のリストを作成
        for token, value in sparse_vector.items():
            indices.append(token2id[token])
            values.append(value)

        point = models.PointStruct(
            id=i,
            payload={"text": doc},
            vector={"text-sparse": models.SparseVector(indices=indices, values=values)},
        )
        points.append(point)

    return points

def encode_query2vector(encoder: SpladeEncoder, query: str) -> models.SparseVector:
    token2id: dict[str, int] = encoder.tokenizer.get_vocab()

    # クエリをSparse Vectorにエンコード
    with torch.inference_mode():
        query_embedding: torch.Tensor = encoder.encode([query])
        query_sparse_vector: dict[int, float] = encoder.to_sparse(query_embedding)[0]

    indices = []
    values = []
    for token, value in query_sparse_vector.items():
        indices.append(token2id[token])
        values.append(value)

    return models.SparseVector(indices=indices, values=values)

4-2. メイン処理の流れ (main.py)

QdrantクライアントとSPLADEエンコーダを初期化し、コレクションの作成、データの登録(Upsert)、検索の実行を行うメイン処理を実装します。 それでは、メインの処理をステップごとに見ていきましょう。

Step 1: クライアントとモデルの初期化

Qdrantのクライアントと、日本語対応のSPLADEエンコーダを初期化します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from light_splade import SpladeEncoder
from qdrant_client import QdrantClient, models

from encode import encode_documents2points, encode_query2vector
from utils import show_results

def main() -> None:
    # Initialize Qdrant client and SPLADE encoder
    client = QdrantClient(url="http://localhost:6333")
    encoder = SpladeEncoder(model_path="bizreach-inc/light-splade-japanese-28M")

    collection_name = "sparse_splade_collection"
    docs = [
        "Qdrantは高速なベクトル検索エンジンです",
        "SPLADEはスパース表現を学習します",
        "ベクトル検索の仕組みを理解しましょう",
        "Pythonでベクトル検索エンジンを構築します",
        "QdrantとSPLADEを組み合わせて使います",
    ]

Step 2: コレクションの作成

Qdrantにコレクションを作成します。ここで重要なのはsparse_vectors_configを設定することです。これにより、指定した名前(ここでは text-sparse)でSparse Vectorを格納できるようになります。

1
2
3
4
5
6
7
8
9
    # Create collection
    client.create_collection(
        collection_name=collection_name,
        vectors_config={}, # Dense Vectorを使わない場合は空にする
        sparse_vectors_config={
            "text-sparse": models.SparseVectorParams(),
        },
    )
    print(f"Collection '{collection_name}' created.")

Step 3: データの登録 (Upsert)

ドキュメントをエンコードしてPointStructのリストに変換し、Qdrantに登録(Upsert)します。

1
2
3
4
5
6
7
8
9
    # Document Encode
    points = encode_documents2points(encoder, docs)

    # Upsert points
    client.upsert(
        collection_name=collection_name,
        points=points,
    )
    print(f"Upserted {len(points)} points.\n")

クエリをエンコードし、検索を実行します。(Qdrant v1.7以降で推奨されている query_points APIを使用します。) using パラメータには、検索に使用するベクトルの名前(text-sparse)を指定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    # Query Encode
    query = "ベクトル検索の仕組み"
    query_sparse_vector = encode_query2vector(encoder, query)
    print(f"query: '{query}'\n")

    # Search
    search_result: models.QueryResponse = client.query_points(
        collection_name=collection_name,
        query=query_sparse_vector,
        using="text-sparse",
    )

4-3. 実行結果

上記のコードを実行すると、クエリ「ベクトル検索の仕組み」に対して、関連性の高いドキュメントがスコア順に出力されます。 以下は、実行した際のコンソール出力です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ python main.py
Collection 'sparse_splade_collection' created.
Upserted 5 points.

query: 'ベクトル検索の仕組み'

# results
- id: 2, score: 25.036448, text: ベクトル検索の仕組みを理解しましょう
- id: 3, score: 14.1208515, text: Pythonでベクトル検索エンジンを構築します
- id: 0, score: 5.669049, text: Qdrantは高速なベクトル検索エンジンです

Collection 'sparse_splade_collection' deleted.
DONE
$

このように、light-spladeとQdrantを利用することで、簡単に実装できます!

5. Sparse Vector Search実用化のためのポイント

実装は容易ですが、プロダクション環境で運用するためには以下の点を考慮する必要があります。

  1. 要件の明確化
  1. 評価基盤の整備
  1. モデルのファインチューニング

これらのことを踏まえ、Sparse Vector Searchに限らず、検索システム全体の設計・運用・改善を検討していくことが重要です。

まとめ

最後に一言!

☝️ BizReach
🔍 Information Retrieval
🐐 GOAT (Greatest Of All Time)

と胸を張って言えるように頑張りたいと思います!

参考文献・リンク

ビズリーチでは、新しい仲間を募集しています。

お客様にとって価値あるモノをつくり、働く環境の変革に挑戦する仲間を募集しています。
募集中のポジションやプロダクト組織の詳細は、ぜひキャリア採用サイトをご覧ください。

ビズリーチ採用サイト
渡會 恭平
渡會 恭平

検索 x 機械学習