pgvectorの二値量子化、中心化の効果はモデルで変わる
pgvectorの二値量子化で、ベクトルから平均を引く「中心化」を試しました。8モデルを比較した結果、e5ではRecall@10が0.496から0.943へ改善し、snowflakeでは0.959から0.907へ低下しました。同じ前処理でも、モデルによって効果が逆になりました。
Vector Search Checkupでは、ベクトルの方向の偏りや、次元の使われ方を確認できます。今回は、その特徴を検索の設定に生かすため、PostgreSQL上で中心化の有無を比較した結果を紹介します。
二値化する前に、平均を引く
二値量子化(BQ)は、ベクトルの各成分を0か1で表す圧縮方法です。pgvectorの公式ドキュメントでは、二値化したベクトルで候補を集め、元のベクトルで順位を計算し直す検索方法が紹介されています。
候補選びに使うハミング距離は、二つのビット列で値が異なる位置の数です。この距離で多めに候補を取り、最後に元のベクトルのコサイン距離で上位を選びます。
今回比較したのは、そのまま二値化する方法と、平均ベクトルを引いてから二値化する方法です。中心化する側では、文書と検索クエリの両方から同じ平均を引きました。pgvector上で中心化した式索引を作成し、検索に使われることも確認できました。
8モデル、各10万件で比較する
pgvector 0.8.7を使い、8モデルそれぞれで10万件を検索対象にしました。クエリには、検索対象に含めない1,000件のベクトルを使っています。中心化に使う平均は、2,000件の標本から求めました。
HNSWの bit_hamming_ops で上位200件を取り、元のベクトルで並べ直して10件を返します。hnsw.ef_search は200に揃え、ベクトルの回転は加えていません。
評価指標のRecall@10は、元のベクトルを全件検索した上位10件を、どれだけ取得できたかの割合です。ここでは、圧縮と候補の絞り込みによる取りこぼしを測ります。
e5は改善し、snowflakeとnomicは低下した
| モデル(実験内の識別名) | 中心化なしのRecall@10 | 中心化ありのRecall@10 |
|---|---|---|
| e5 | 0.496 | 0.943 |
| snowflake | 0.959 | 0.907 |
| bge | 0.961 | 0.971 |
| mxbai | 0.988 | 0.991 |
| qwen3 | 0.976 | 0.976 |
| nomic | 0.951 | 0.937 |
| minilm | 0.937 | 0.945 |
| arcface | 0.572 | 0.560 |
e5では、中心化によって全件検索の上位10件を約94%取得できるようになりました。一方、snowflakeやnomicでは取得率が下がり、残る5モデルの変化はおおむね±0.01程度でした。
この結果から、中心化を一律に適用する方針を見直しました。ベクトルの方向集中度(cos_mean)だけで効果を判定せず、中心化あり・なしの検索結果を比較して選びます。
二値化と近似探索の影響を分けて測る
中心化の効果を調べるには、まず同じ文書とクエリから、中心化あり・なしのビット列を作ります。索引を使わずにハミング距離を全件計算し、同じ件数の候補を元のベクトルで並べ直せば、二値化した候補選択の差を測れます。
次に、その結果をHNSWで候補を選んだ結果と比較します。今回の実験では、索引を使わない厳密なハミング距離の比較でも、e5は改善し、snowflakeは悪化しました。モデルによる効果の違いは、HNSWを外しても残りました。
元のベクトルで並べ直しても、候補から漏れたデータは戻りません。中心化の有無に加え、最初に取る候補数を変えながら、Recallと処理時間を比較することが設定選びにつながります。
PostgreSQLで使える診断へ
PostgreSQLでは、ベクトルの特徴を判定するSQL文や、本格的な拡張にする前の機能づくりを進めています。今回の比較結果を踏まえ、診断では同じ標本で中心化の有無を比べ、その結果を示す方針です。SQLの具体例や処理負荷、式索引の扱いは、別の記事で紹介する予定です。
ベクトル検索 ANNプロジェクトでは、LanceDBの索引や再スコアリングの検証も進めています。実装上の課題や改善提案も、事例ごとに報告していきます。
この一連の研究のきっかけは、1月に取り組んだLSHの実験でした。ベクトルをビット列に変換して検索候補を絞る試みが、今回のPostgreSQLでの量子化の検証につながっていることは、感慨深いです。