Home 論文解説: SPLADE v2 — 学習型スパース検索で転置インデックスにセマンティック拡張を導入する手法
投稿
キャンセル

📄 論文解説: SPLADE v2 — 学習型スパース検索で転置インデックスにセマンティック拡張を導入する手法

本記事は SPLADE v2: Sparse Lexical and Expansion Model for Information Retrieval (arXiv:2109.10086) の解説記事です。

論文概要(Abstract)

SPLADE(SParse Lexical AnD Expansion model)v2は、BERTのMasked Language Model(MLM)ヘッドを活用して語彙空間上のスパースベクトルを生成し、従来のBM25転置インデックスと同じインフラで検索可能な学習型スパース検索モデルである。著者ら(Naver Labs Europe)は、FLOPS正則化によるスパース性制御とCross-Encoder Distillationの組み合わせにより、MS MARCO DevでMRR@10 = 36.8、BEIRベンチマーク13データセット平均でnDCG@10 = 50.6を達成したと報告している。

この記事は Zenn記事: セマンティック検索精度を向上させる5つの実装テクニック の深掘りです。

情報源

  • arXiv ID: 2109.10086
  • URL: https://arxiv.org/abs/2109.10086
  • 著者: Thibault Formal, Carlos Lassance, Benjamin Piwowarski, Stéphane Clinchant(Naver Labs Europe)
  • 発表年: 2021
  • 分野: cs.IR

背景と動機(Background & Motivation)

情報検索の手法は大きく2つに分類される。

  1. スパース検索(Lexical Retrieval): BM25に代表される語彙一致ベースの検索。転置インデックスで高速に動作するが、同義語や言い換えに対応できない(vocabulary mismatch問題)
  2. 密検索(Dense Retrieval): DPR、Contrieverに代表されるベクトル検索。意味的類似度を捉えられるが、固有名詞や専門用語の完全一致に弱く、ドメイン外でのゼロショット精度が低下しやすい

SPLADEは、この2つのアプローチの利点を統合することを目指している。具体的には、ニューラルネットワークで語彙を意味的に拡張しつつ、出力をスパースベクトルとして表現することで、既存の転置インデックスインフラ(Elasticsearch、Lucene等)をそのまま利用可能にする。

主要な貢献(Key Contributions)

  • 貢献1: BERTのMLMヘッドを利用した語彙拡張スパースベクトルの生成手法(SPLADEアーキテクチャ)
  • 貢献2: FLOPS正則化によるスパース性と精度のトレードオフ制御機構
  • 貢献3: Cross-Encoder Distillation(MarginMSE損失)によるSPLADE v2の精度向上

技術的詳細(Technical Details)

SPLADEのスパースベクトル生成

SPLADEは、入力テキスト $t$ に対して語彙サイズ $V$(BERTの場合30,522)のスパースベクトルを生成する。各語彙 $j$ に対する重み $w_j$ は以下の式で計算される。
\[w_j(t) = \sum_{i=1}^{|t|} \log\left(1 + \text{ReLU}\left(\mathbf{h}_{i,j}\right)\right)\]

ここで、

  • $t$: テキスト $t$ のトークン数
  • $\mathbf{h}_{i,j}$: $i$ 番目のトークンに対するMLMヘッドの出力のうち、語彙 $j$ に対応するlogit
  • $\text{ReLU}(\cdot)$: 負の値をゼロに切り捨てる活性化関数
  • $\log(1 + \cdot)$: 値の飽和を防ぐ対数変換(saturation function)

この式は、各トークン位置のMLM出力をReLUで非負にし、log変換で緩やかなスケーリングを行った後、トークン方向にsum-poolingしている。ReLUにより大部分の語彙の重みがゼロになるため、結果はスパースベクトルとなる。

FLOPS正則化

スパースベクトルの非ゼロ要素が多すぎると、転置インデックスのポスティングリストが肥大化し、検索速度が低下する。著者らは、FLOPS(FLoating point OPerations per Second)正則化でスパース性を制御する手法を提案している。

\[\mathcal{L}_{\text{FLOPS}} = \sum_{j=1}^{|V|} \bar{a}_j^2\]

ここで $\bar{a}_j$ は語彙 $j$ の平均活性化値(バッチ内の全文書における語彙 $j$ の重みの平均)である。

この正則化は、各語彙の「文書頻度」(Document Frequency)に相当する量を抑制する。直感的には、多くの文書で高い重みを持つ語彙(ストップワード的な語彙)の影響を抑え、識別力のある語彙のみが高い重みを持つようにする。

最終的な損失関数は以下のとおりである。

\[\mathcal{L} = \mathcal{L}_{\text{rank}} + \lambda_q \cdot \mathcal{L}_{\text{FLOPS}}^{(q)} + \lambda_d \cdot \mathcal{L}_{\text{FLOPS}}^{(d)}\]

ここで $\lambda_q, \lambda_d$ はクエリ側・文書側のFLOPS正則化の強度を制御するハイパーパラメータである。

Distillation(SPLADE v2の改良点)

SPLADE v2では、Cross-Encoder(例: cross-encoder/ms-marco-MiniLM-L-12-v2)のスコアを教師信号として使用するMarginMSE損失を導入している。

\[\mathcal{L}_{\text{MarginMSE}} = \text{MSE}\left(s_\text{student}(q, d^+) - s_\text{student}(q, d^-), \; s_\text{teacher}(q, d^+) - s_\text{teacher}(q, d^-)\right)\]

ここで $d^+, d^-$ はそれぞれ正例・負例文書、$s_\text{student}$ はSPLADEのスコア、$s_\text{teacher}$ はCross-Encoderのスコアである。スコアの差分(マージン)を教師信号にすることで、絶対スコアではなく相対的なランキングを学習する。

アルゴリズム

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import torch
import torch.nn as nn
from transformers import AutoModelForMaskedLM, AutoTokenizer


class SPLADEEncoder(nn.Module):
    """SPLADE v2 Sparse Encoder

    BERTのMLMヘッドを利用して語彙空間上のスパースベクトルを生成する。

    Args:
        model_name: BERTベースのMLMモデル名
    """

    def __init__(self, model_name: str = "naver/splade-cocondenser-ensembledistil"):
        super().__init__()
        self.model = AutoModelForMaskedLM.from_pretrained(model_name)
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)

    def forward(
        self,
        input_ids: torch.Tensor,
        attention_mask: torch.Tensor,
    ) -> torch.Tensor:
        """テキストをスパースベクトルにエンコードする。

        Args:
            input_ids: トークンID (batch_size, seq_len)
            attention_mask: アテンションマスク (batch_size, seq_len)

        Returns:
            スパースベクトル (batch_size, vocab_size)
        """
        outputs = self.model(
            input_ids=input_ids, attention_mask=attention_mask
        )
        logits = outputs.logits  # (batch_size, seq_len, vocab_size)

        # ReLU + log(1+x) 変換
        activated = torch.log1p(torch.relu(logits))

        # attention_maskでパディングトークンを除外
        activated = activated * attention_mask.unsqueeze(-1)

        # トークン方向にmax-pooling(sum-poolingも可能)
        sparse_vec, _ = torch.max(activated, dim=1)  # (batch_size, vocab_size)

        return sparse_vec


def compute_flops_loss(sparse_vectors: torch.Tensor) -> torch.Tensor:
    """FLOPS正則化損失を計算する。

    Args:
        sparse_vectors: スパースベクトル (batch_size, vocab_size)

    Returns:
        FLOPS正則化損失(スカラー)
    """
    # バッチ内の平均活性化値
    mean_activation = sparse_vectors.mean(dim=0)  # (vocab_size,)
    # L2ノルムの2乗
    flops_loss = torch.sum(mean_activation ** 2)
    return flops_loss

実装のポイント(Implementation)

FLOPS正則化パラメータのチューニング: $\lambda_q$ と $\lambda_d$ はスパース性と精度のトレードオフを直接制御する。著者らの実験では、$\lambda_d = 1e\text{-}4$ 〜 $3e\text{-}4$ が精度と効率のバランスが良好とされている。$\lambda_d$ を大きくするとスパース性が増し検索が高速化するが、精度が低下する。ドメインごとの調整が推奨される。

転置インデックスとの互換性: SPLADEの出力はスパースベクトル(語彙ID → 重み)であるため、Elasticsearch、Apache Solr、PyTerrierなどの既存転置インデックスエンジンで直接利用できる。SPLADEの語彙はBERTのWordPieceトークナイザと一致するため、語彙IDをそのままインデックスのキーとして使用する。

日本語への適用: SPLADEはBERTのWordPiece語彙に依存するため、日本語テキストの処理にはmBERT(multilingual BERT)ベースのモデルが必要になる。2026年2月時点で日本語特化のSPLADEモデルは選択肢が限られるが、naver/splade-cocondenser-ensembledistil(多言語対応)が現実的な選択肢である。

エンコード速度: SPLADEのエンコードはBERTのフルフォワードパスに加えてMLMヘッドの計算が必要であるため、Bi-Encoder比で2-3倍の計算時間がかかる。大規模コーパスのインデックス構築時はGPUバッチ処理が必須である。

Production Deployment Guide

AWS実装パターン(コスト最適化重視)

SPLADEは転置インデックスを使用するため、OpenSearch/Elasticsearchとの相性が良い。

トラフィック量別の推奨構成:

規模月間リクエスト推奨構成月額コスト主要サービス
Small~3,000 (100/日)Serverless$80-200Lambda + OpenSearch Serverless
Medium~30,000 (1,000/日)Hybrid$400-900ECS Fargate + OpenSearch Service
Large300,000+ (10,000/日)Container$2,000-5,000EKS + OpenSearch + ElastiCache

Small構成の詳細 (月額$80-200):

  • Lambda: クエリエンコード ($30/月)
  • SageMaker Serverless: SPLADEモデル推論 ($50/月)
  • OpenSearch Serverless: スパース検索 ($50/月、2 OCU最小)
  • S3: モデルアーティファクト ($5/月)

Large構成の詳細 (月額$2,000-5,000):

  • EKS: コントロールプレーン ($72/月)
  • EC2 Spot (GPU): SPLADEエンコード用 g5.xlarge ($400/月)
  • OpenSearch Service: 3ノードクラスタ、転置インデックス ($1,200/月)
  • ElastiCache: クエリスパースベクトルのキャッシュ ($50/月)

コスト削減テクニック(SPLADE特有):

  • 転置インデックス使用のためベクトルDB不要 → コスト削減
  • FLOPS正則化でスパース性を高め、インデックスサイズを削減
  • 文書エンコードはオフラインバッチ処理(Spot Instances活用)
  • クエリエンコードの結果をキャッシュ(同一クエリパターンの再利用)

コスト試算の注意事項:

  • 上記は2026年2月時点のAWS ap-northeast-1(東京)リージョン料金に基づく概算値です
  • OpenSearch Serviceのコストはインスタンスタイプ・ノード数により大きく変動します
  • 最新料金は AWS料金計算ツール で確認してください

Terraformインフラコード

Small構成 (Serverless): Lambda + OpenSearch Serverless

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
48
49
50
51
52
53
54
55
# --- IAMロール ---
resource "aws_iam_role" "splade_lambda" {
  name = "splade-lambda-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action    = "sts:AssumeRole"
      Effect    = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
    }]
  })
}

# --- Lambda関数(クエリエンコード) ---
resource "aws_lambda_function" "splade_query" {
  filename      = "lambda.zip"
  function_name = "splade-query-encoder"
  role          = aws_iam_role.splade_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.12"
  timeout       = 30
  memory_size   = 2048  # BERTモデルのためメモリ多め

  environment {
    variables = {
      MODEL_NAME          = "naver/splade-cocondenser-ensembledistil"
      OPENSEARCH_ENDPOINT = aws_opensearchserverless_collection.splade.collection_endpoint
      FLOPS_LAMBDA        = "0.0002"
    }
  }
}

# --- OpenSearch Serverless ---
resource "aws_opensearchserverless_collection" "splade" {
  name = "splade-index"
  type = "SEARCH"  # スパース検索は通常のSEARCHタイプ
}

# --- CloudWatch アラーム ---
resource "aws_cloudwatch_metric_alarm" "splade_latency" {
  alarm_name          = "splade-encode-latency"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "Duration"
  namespace           = "AWS/Lambda"
  period              = 300
  statistic           = "p95"
  threshold           = 3000
  alarm_description   = "SPLADEクエリエンコードのP95が3秒超過"

  dimensions = {
    FunctionName = aws_lambda_function.splade_query.function_name
  }
}

運用・監視設定

CloudWatch Logs Insights クエリ:

1
2
3
4
5
6
7
-- SPLADEスパースベクトルの非ゼロ要素数モニタリング
fields @timestamp, query_nonzero_count, doc_avg_nonzero_count
| stats avg(query_nonzero_count) as avg_q_nnz,
        avg(doc_avg_nonzero_count) as avg_d_nnz,
        pct(query_nonzero_count, 99) as p99_q_nnz
  by bin(1h)
| filter avg_q_nnz > 100  -- 閾値超過で検索速度低下の兆候

コスト最適化チェックリスト

  • ~100 req/日 → Lambda + OpenSearch Serverless - $80-200/月
  • ~1000 req/日 → ECS + OpenSearch Service - $400-900/月
  • 10000+ req/日 → EKS + OpenSearch + Cache - $2,000-5,000/月
  • FLOPS正則化パラメータ最適化(スパース性vs精度)
  • 転置インデックス使用(ベクトルDB不要)
  • 文書エンコードはオフラインバッチ(Spot Instances)
  • クエリスパースベクトルのキャッシュ
  • Spot Instances優先(最大90%削減)
  • OpenSearchインスタンスサイズの最適化
  • AWS Budgets: 月額予算設定
  • CloudWatch: エンコードレイテンシ・非ゼロ要素数監視
  • Cost Anomaly Detection有効化
  • 日次コストレポート送信
  • OpenSearchシャード最適化
  • タグ戦略: 環境・プロジェクト別
  • 開発環境: Serverless最小構成
  • BERTモデルのONNX変換(推論高速化)
  • バッチインデックス更新: Step Functionsで夜間実行
  • 未使用インデックスの定期削除
  • Lambda Provisioned Concurrency(コールドスタート回避)

実験結果(Results)

MS MARCO Dev(論文Table 1より)

モデルMRR@10Recall@1000
BM2518.485.7
DocT5Query27.7-
DPR31.095.1
SPLADE v132.295.5
SPLADE v2 (Distil)36.897.9

SPLADE v2はBM25から+18.4ポイント、v1から+4.6ポイントの改善を達成している。著者らは、この改善の主因はCross-Encoder DistillationとFLOPS正則化の最適化であると分析している。

BEIRベンチマーク(論文Table 2より)

モデルnDCG@10 (13データセット平均)
BM2544.0
DPR38.0
ANCE39.7
ColBERTv249.9
SPLADE v2 (ensemble-distil)50.6

著者らの報告では、SPLADE v2はBEIRベンチマークでColBERTv2を上回るnDCG@10を達成している。特にドメイン外のゼロショット評価で強い性能を示しており、BM25の語彙一致の利点をニューラル拡張で補完していることが示唆されている。

スパース性の分析(論文Table 4より)

モデルクエリ平均非ゼロ要素数文書平均非ゼロ要素数
SPLADE v164280
SPLADE v2 (eff-v1)49120
SPLADE v2 (eff-v5)1848

FLOPS正則化の強度を上げることで、非ゼロ要素数を大幅に削減できることが示されている。eff-v5では文書あたり48語のみが非ゼロであり、BM25(通常数百語)より高いスパース性を実現しつつ、nDCG@10は依然としてBM25を上回っている。

実運用への応用(Practical Applications)

SPLADEは以下のシナリオで有効である。

既存Elasticsearchインフラの活用: 多くの企業がBM25ベースの検索にElasticsearchを使用している。SPLADEは転置インデックスと互換性があるため、ベクトルDBを新規導入せずにセマンティック検索を実現できる。インデックスの構造を変更する必要がなく、スパースベクトルの重みをBM25のterm frequencyと同様に扱える。

ハイブリッド検索の構築: SPLADEとDense Retrieverを組み合わせたハイブリッド検索が実用的である。SPLADEが語彙の拡張と完全一致をカバーし、Dense Retrieverが意味的類似度をカバーすることで、相互に弱点を補完できる。

固有名詞・専門用語の検索: Dense Retrieverが苦手とする固有名詞(人名、製品名等)や専門用語の完全一致が必要なケースで、SPLADEの語彙一致特性が有効に機能する。

関連研究(Related Work)

  • DocT5Query (Nogueira et al., 2019): T5モデルで文書に関連するクエリを生成し、文書を拡張する手法。SPLADEとは異なり、外部モデルでの事前処理が必要
  • DeepImpact (Mallia et al., 2021): BERTベースのterm importance推定モデル。SPLADEと同様にスパースベクトルを生成するが、語彙拡張は行わない
  • SPLADE++ (Formal et al., 2022): SPLADE v2をさらに改良し、効率的なエンコーダ蒸留を追加した手法

まとめと今後の展望

SPLADE v2は、BERTのMLMヘッドを活用した語彙拡張と、FLOPS正則化によるスパース性制御を組み合わせた学習型スパース検索手法である。著者らは、MS MARCOでMRR@10 = 36.8、BEIRで平均nDCG@10 = 50.6を達成し、転置インデックスインフラとの互換性を維持しながらBM25を大幅に上回ったと報告している。

実務への示唆として、SPLADEは既存のElasticsearch環境にセマンティック検索機能を追加する最も現実的な手法の一つである。ベクトルDBの新規導入が不要であるため、インフラコストと運用負荷を抑えながら検索品質を向上できる。

今後の研究方向としては、Echo-Mistral-SPLADE(LLMベースのSPLADE)やSentence Transformers v5でのSPLADE対応など、より大規模なモデルでのスパース検索が注目されている。

参考文献

この投稿は CC BY 4.0 でライセンスされています。