0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RAGの内積検索、正規化漏れで閾値通過が1件から3件

0
Posted at

検索順位が合っていても、RAGで渡す文書の足切りは壊れる。内積をコサイン類似度として使うコードで、クエリの正規化を外すと、閾値0.8を通る候補が1件から3件に増えた。

自分は今回、3本のベクトルで正規化する側を変えながら出力を見ていた。文書側を直すと1位がAに戻る。でも、まだ3件通っているんだよね。先頭の文書だけ確認するテストでは、この差を拾えない。

内積検索を3通りで動かす

検索した文書はスコア0.8以上だけLLMへ渡す。0.8は実験用で、実サービスへの推奨値ではない。

クエリは(2, 0)。文書Aは(1, 0)で同じ向き、BとCは向きも長さも違う。計算を切り分ける人工データで、意味の近さは測っていない。

repro.pyに保存し、python3 repro.pyで実行する。Python 3.14.6で確認済み。標準ライブラリだけで内積を全件計算する。Faiss自体の実行は含まない。

from math import hypot

q = (2.0, 0.0)
docs = {"A": (1.0, 0.0), "B": (3.0, 4.0), "C": (2.0, 3.0)}

def unit(v):
    n = hypot(*v)
    if n == 0:
        raise ValueError("zero vector")
    return tuple(x / n for x in v)

def search(query, rows):
    scores = [(name, sum(x * y for x, y in zip(query, v, strict=True)))
              for name, v in rows.items()]
    return sorted(scores, key=lambda row: row[1], reverse=True)

normalized = {name: unit(v) for name, v in docs.items()}
for label, query, rows in [
    ("raw", q, docs),
    ("docs_only", q, normalized),
    ("both", unit(q), normalized),
]:
    scores = search(query, rows)
    accepted = [name for name, score in scores if score >= 0.8]
    print(label, [(name, round(score, 3)) for name, score in scores],
          "accepted=", accepted)
    assert len(accepted) == (1 if label == "both" else 3)

出力はこうなった。

raw [('B', 6.0), ('C', 4.0), ('A', 2.0)] accepted= ['B', 'C', 'A']
docs_only [('A', 2.0), ('B', 1.2), ('C', 1.109)] accepted= ['A', 'B', 'C']
both [('A', 1.0), ('B', 0.6), ('C', 0.555)] accepted= ['A']

丸めるのは表示だけ。閾値との比較には丸める前の値を使っている。

文書だけ直すと、順位のテストは通る

内積は「クエリの長さ×文書の長さ×コサイン類似度」。未正規化のBは長さが5あるので、向きが完全に一致するAを追い越す。

文書を長さ1にそろえると、残る倍率はクエリの長さだけになる。今回は2倍。この倍率は全候補に共通なので、順位はA、B、Cに戻る。それでもBの0.6は1.2になり、閾値0.8を通ってしまう。

クエリ側も長さ1にそろえると、スコアはAが1.0、Bが0.6、Cが約0.555になる。

Faiss公式の距離仕様でも、コサイン類似度を内積検索で扱うには、登録前の文書と検索前のクエリを両方正規化するとある。今回はその片方を抜いた計算を再現した。

RAGに組み込むときの確認箇所

文書の登録処理とクエリの検索処理に正規化を置く。登録コードを直しても、保存済みの未正規化ベクトルは残る。過去分の長さも確認したい。

ただし、モデルが最初から長さ1のベクトルを返す場合や、検索基盤がコサインを直接計算する場合もある。まず手元の出力と設定を確認する。内積そのものを目的に選んだ検索へ、無条件でこの変更を入れる話でもない。

ゼロベクトルは長さで割れず、ValueError: zero vectorで止める。実際に出たら埋め込み生成側を調べる。今回は有限値の小さなベクトルが対象だ。

おわりに

検索のテストには、上位IDと一緒に閾値通過件数を残しておく。今回ならAが1位という確認に加え、通過が1件というassertが効く。クエリの長さが変わる入力も混ぜれば、件数の変動を追いやすい。LLMへ渡す文書が急に増えたときは、この順でスコアの倍率を確かめたい。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?