【Faiss】源码阅读(四)——IVFPQ更低的内存占用

1. 理解PQ乘积量化

请参考:

  1. https://zhuanlan.zhihu.com/p/114029796
  2. https://www.cnblogs.com/mafuqiang/p/7161592.html

2. 整体思路

这种索引方式比前两种复杂多了。现在还是不够清晰。下面对主要思路做下记录

  1. 训练
  • 训练第一阶段的kmean

    • 随机下采样 100×256个样本
    • 训练kmean
  • 训练PQ量化

    • 随机下采样256×256(256个类别,每个类最多有256个样本)个样本
    • 计算下采样样本和kmean聚类中心的匹配关系/向量差
    • 对向量差矩阵,切分为M段,做聚类(256个中心),共M×256个聚类中心
    • 对M×265个PQ聚类中心和kmean聚类中心求向量差/PQ量化表。

训练主要为了得到PQ量化表

  1. 添加底库
  • 计算query和kmean聚类中心的匹配
  • 计算query和匹配的kmean聚类中心的向量差
  • 用向量差和PQ聚类中心求L2距离做PQ量化
  • 将每个样本的PQ量化值写入倒序索引

主要为了得到每个底库样本的编码信息

  1. 查询
  • 计算query和kmean聚类中心的匹配关系key
  • 计算query和PQ聚类中心的内积
  • 更新当前key kmeans聚类中心的PQ查询表
  • 在key kmean聚类的所有样本中,利用PQ查询表计算距离

3. 具体实现

3.2 添加底库

    if (precomputed_idx) {
        idx = precomputed_idx;
    } else {
        idx_t * idx0 = new idx_t [n];
        del_idx.set (idx0);
        quantizer->assign (n, x, idx0);             // 和第一阶段聚类中心的匹配
        idx = idx0;
    }

    double t1 = getmillisecs ();
    uint8_t * xcodes = new uint8_t [n * code_size]; // code_size=8,PQ的量化编码
    ScopeDeleter<uint8_t> del_xcodes (xcodes);

    const float *to_encode = nullptr;
    ScopeDeleter<float> del_to_encode;

    if (by_residual) {
        to_encode = compute_residuals (quantizer, n, x, idx);       // 采用残差的方式, 计算query和第一阶段聚类中心的 向量差
        del_to_encode.set (to_encode);
    } else {
        to_encode = x;
    }
    pq.compute_codes (to_encode, xcodes, n);        // 根据PQ聚类中心编码

3.3 查询

// 1. 计算query和PQ聚类中心的内积
void init_query_L2 () {
    if (!by_residual) {
        pq.compute_distance_table (qi, sim_table);
    } else if (use_precomputed_table) {
        pq.compute_inner_prod_table (qi, sim_table_2);  // 计算 query和PQ聚类中心的内积,sim_table_2:256*8
    }
}

// 2. 更新PQ查询表
fvec_madd (pq.M * pq.ksub,
         &ivfpq.precomputed_table [key * pq.ksub * pq.M],     // = r_norm + 2tab
         -2.0, sim_table_2,          // sim_table_2:query与PQ聚类中心的内积
         sim_table);                 // sim_table = precomputed_tab -2*sim_table_2

// 3. 计算距离
void scan_list_with_table (size_t ncode, const uint8_t *codes,
                               SearchResultType & res) const
    {
        for (size_t j = 0; j < ncode; j++) {

            float dis = dis0;
            const float *tab = sim_table;       // 256×8维,这里是一种查表的方式,sim_table表示query到底库样本的距离。sim_table采用非对称计算的方式

            for (size_t m = 0; m < pq.M; m++) {
                dis += tab[*codes++];               // 距离 , 需要tab + codes两个东西, codes表示
                tab += pq.ksub;
            }

            res.add(j, dis);            // 用堆存放结果
        }
    }

其他

  • 类的继承太多了,阅读跳转太多,没有面向过程的清晰

你可能感兴趣的:(数据结构与算法)