ベクトルの検索を速くすると、正解を取りこぼすようになります。全部と比べるのをやめて、一部だけを見るためです。
20,000件で測ったところ、50倍速で引いた設定では上位10件のうち6.6件を取りこぼしていました。取りこぼしを0件にすると、速さは6.9倍まで落ちます。
20,000件で実際に測りました。1組だけ見る設定は49.7倍速ですが再現率34.2%、8組見ると100%で6.9倍速です。
ANN再現率がどこまで落ちるのかを、実際に作って測りました。近似の検索は速さと引き換えに正解を取りこぼします。用意したのは64次元のベクトル20,000件です。
ベクトルの生成も検索も本当に実行しています。全探索の上位10件を正解として、近似検索の結果と重なった件数を数えました。所要時間は実行のたびにぶれるので、桁と比で読んでください。
// 近似検索: ベクトルを組に分けておき、問い合わせに近い組だけを見る
function approx(q, nprobe) {
const order = reps.map((r, c) => ({ c, d: dist2(q, r) }))
.sort((a, b) => a.d - b.d).slice(0, nprobe);
const cand = [];
for (const { c } of order) for (const i of buckets[c]) cand.push({ i, d: dist2(q, vectors[i]) });
cand.sort((a, b) => a.d - b.d);
return { ids: cand.slice(0, K).map((s) => s.i), scanned: cand.length };
}
登録 20,000件(64次元)に対して 200回の問い合わせ、上位10件を取る
全探索 1回あたり 5438 マイクロ秒(20,000件すべてと距離を計算)
見る組の数 再現率 取りこぼし 走査した件数 1回あたりの時間 全探索比
1組 34.2% 6.6件 319件 109 μs 49.7倍速
2組 56.6% 4.3件 633件 185 μs 29.4倍速
4組 88.2% 1.2件 1,261件 411 μs 13.2倍速
8組 100.0% 0.0件 2,484件 787 μs 6.9倍速
16組 100.0% 0.0件 4,897件 1602 μs 3.4倍速
32組 100.0% 0.0件 9,933件 3548 μs 1.5倍速
64組 100.0% 0.0件 20,000件 10919 μs 0.5倍速
1行目が極端です。49.7倍速で引ける代わりに、上位10件のうち6.6件が別のものになっています。
この34.2%という数字は、測って初めて分かります。検索としては10件がきちんと返ってきます。エラーも出ません。
返ってきた10件が正解の10件と違うことは、全探索の結果と突き合わせない限り気づけません。
見る組を増やすと再現率は上がります。1組で34.2%、2組で56.6%、4組で88.2%。ここまでは1組増やすごとに大きく伸びます。
8組で100%に届きました。速さは6.9倍まで落ちますが、取りこぼしなしで7倍速なら十分に実用です。
最後の行を見てください。全部の組を見ると0.5倍速、つまり全探索より遅いという結果でした。
組の代表点と比べる手間と、候補を並べ替える手間が余分に乗るためです。絞り込みをやめた近似検索は、全探索の劣化版になります。
4組から8組の間に、取りこぼしなしで済む設定がある。
You can add an index to use approximate nearest neighbor search, which trades some recall for speed.原文pgvector/pgvector README この内容の有効期限2027-02-18
正解のうち何件を取れたかの割合です。全探索の結果を正解として、近似検索の結果と重ねて数えます。
ANN再現率は、近似の検索が、正解のうち何件を取れたかを示す割合です。上位10件を取る設定なら、10件中の一致数になります。
何を正解とするかが要点です。登録されている全件と距離を計算して並べた結果が正解になります。
pgvectorの説明でも、既定では厳密な最近傍の検索を行い、完全な再現率が得られるとされています。索引を作らなければ全探索、という整理です。
全探索は件数に比例して遅くなります。20,000件で5.4ミリ秒でしたから、100万件なら単純計算で270ミリ秒です。1回の検索としては重すぎます。
そこで見る範囲を絞ります。同じ文書でも、索引を足せば近似の検索になり、再現率をいくらか差し出して速さを得ると説明されています。
見落とされやすい点があります。索引を足すと、同じ問い合わせでも以前と違う結果が返ります。pgvectorも、通常の索引と違ってこの点が起きると明記しています。
ですから索引を足す変更は、性能の調整ではなく答えが変わる変更として扱う必要があります。
測ってみて分かったのは、全探索の結果を先に保存しておかないと、後から再現率を測れないということでした。索引を足した後では、比べる相手がありません。編集部では、代表的な問い合わせ200件ぶんの正解を先に取っておく形にしています。
By default, pgvector performs exact nearest neighbor search, which provides perfect recall.原文pgvector/pgvector README この内容の有効期限2027-02-18
用途で決めます。決めたら、その下限を満たす設定のうちいちばん速いものを選んでください。
ANN再現率をどこまで落とすかは、取りこぼした1件が何を意味するかで決まります。技術の問題ではありません。
4番目がいちばん多い状態です。測っていなければ、34.2%で動いていても気づけません。
設定の調整はやることが決まっています。pgvectorの説明でも、値を大きくすれば再現率は上がるが、速さと引き換えになるとされています。
前の節の表がその形です。ですから下限を先に決めて、それを満たす中でいちばん速い設定を選ぶという順になります。逆順で調整すると、どこで止めてよいか分かりません。
1度合わせて終わりではありません。データが増えると、同じ設定でも再現率が下がります。1組あたりの件数が増え、絞り込みが粗くなるためです。
ですから正解を保存した問い合わせ集を残しておき、定期的に測り直してください。試験の作り方はシミュレーション評価の記事で扱っています。
既定値のまま使うと、どれだけ落ちているか分からない。
A higher value provides better recall at the cost of speed.原文pgvector/pgvector README この内容の有効期限2027-02-18
同じ課題を持つ会社にとって、動いている設定は「作る時間」を買えるということです。ServiceDockは自作のワークフローやテンプレートを出品できるマーケットプレイスです。手数料や出品の流れは出品者向けページにまとまっています。
出品の仕組みを見る