mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-12 02:13:36 +00:00
Fix distance conversion in IndexBinaryFromFloat for non-L2 metrics (#5549)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5549 `IndexBinaryFromFloat::search()` converted the wrapped float index's distances back to Hamming distances with a hardcoded `round(df[i] / 4.0)`, which is only correct for `METRIC_L2`. For any other metric the returned distances were silently wrong. `binary_to_real()` maps bits to `{-1, +1}`, so the relation between the float distance and the Hamming distance depends on the metric: - `METRIC_L2` (squared): `float_dist = 4 * hamming` - `METRIC_INNER_PRODUCT`: `float_dist = d - 2 * hamming` - `METRIC_L1`: `float_dist = 2 * hamming` Branch on `index->metric_type` and apply the matching inverse. `METRIC_L2` remains the default/fallback, so existing behavior is unchanged. Each mapping is monotonic in the Hamming distance, so the result order the wrapped index produces is already correct and needs no reordering. Reported in https://github.com/facebookresearch/faiss/issues/5546. ___ Differential Revision: D118160087 fbshipit-source-id: f2ce2b97ebfd47461346508f9878b1af7e321b12
This commit is contained in:
committed by
meta-codesync[bot]
parent
af9eeffcf7
commit
718002f7cf
@@ -9,9 +9,11 @@
|
||||
|
||||
#include <faiss/IndexBinaryFromFloat.h>
|
||||
|
||||
#include <faiss/MetricType.h>
|
||||
#include <faiss/impl/FaissAssert.h>
|
||||
#include <faiss/utils/utils.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
namespace faiss {
|
||||
@@ -67,8 +69,27 @@ void IndexBinaryFromFloat::search(
|
||||
binary_to_real(bn * d, x + b * code_size, xf.get());
|
||||
|
||||
index->search(bn, xf.get(), k, df.get(), labels + b * k);
|
||||
for (int i = 0; i < bn * k; ++i) {
|
||||
distances[b * k + i] = int32_t(std::round(df[i] / 4.0));
|
||||
// binary_to_real maps bits to {-1, +1}, so the float distance relates
|
||||
// to the Hamming distance differently for each metric. In all cases the
|
||||
// mapping is monotonic, so the result order needs no adjustment.
|
||||
switch (index->metric_type) {
|
||||
case METRIC_INNER_PRODUCT:
|
||||
for (int i = 0; i < bn * k; ++i) {
|
||||
distances[b * k + i] =
|
||||
int32_t(std::round((d - df[i]) / 2.0));
|
||||
}
|
||||
break;
|
||||
case METRIC_L1:
|
||||
for (int i = 0; i < bn * k; ++i) {
|
||||
distances[b * k + i] = int32_t(std::round(df[i] / 2.0));
|
||||
}
|
||||
break;
|
||||
case METRIC_L2:
|
||||
default:
|
||||
for (int i = 0; i < bn * k; ++i) {
|
||||
distances[b * k + i] = int32_t(std::round(df[i] / 4.0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,44 @@ class TestIndexBinaryFromFloat(unittest.TestCase):
|
||||
|
||||
np.testing.assert_allclose((D_ref / 4.0).astype("int32"), D)
|
||||
|
||||
def test_index_from_float_inner_product(self):
|
||||
d = 256
|
||||
nt = 0
|
||||
nb = 1500
|
||||
nq = 500
|
||||
(xt, xb, xq) = make_binary_dataset(d, nb, nt, nq)
|
||||
|
||||
index_ref = faiss.IndexBinaryFlat(d)
|
||||
index_ref.add(xb)
|
||||
|
||||
index = faiss.IndexFlat(d, faiss.METRIC_INNER_PRODUCT)
|
||||
index_bin = faiss.IndexBinaryFromFloat(index)
|
||||
index_bin.add(xb)
|
||||
|
||||
D_ref, I_ref = index_ref.search(xq, 10)
|
||||
D, I = index_bin.search(xq, 10)
|
||||
|
||||
np.testing.assert_array_equal(D_ref, D)
|
||||
|
||||
def test_index_from_float_l1(self):
|
||||
d = 256
|
||||
nt = 0
|
||||
nb = 1500
|
||||
nq = 500
|
||||
(xt, xb, xq) = make_binary_dataset(d, nb, nt, nq)
|
||||
|
||||
index_ref = faiss.IndexBinaryFlat(d)
|
||||
index_ref.add(xb)
|
||||
|
||||
index = faiss.IndexFlat(d, faiss.METRIC_L1)
|
||||
index_bin = faiss.IndexBinaryFromFloat(index)
|
||||
index_bin.add(xb)
|
||||
|
||||
D_ref, I_ref = index_ref.search(xq, 10)
|
||||
D, I = index_bin.search(xq, 10)
|
||||
|
||||
np.testing.assert_array_equal(D_ref, D)
|
||||
|
||||
def test_wrapped_quantizer(self):
|
||||
d = 256
|
||||
nt = 150
|
||||
|
||||
Reference in New Issue
Block a user