Faiss(13):update_vectors分析

1. 说明

update_vectors用于更新索引中的dataset而不需要重新生成和训练的过程,从代码追踪的情况来看,update_vectors()函数只有在IndexIVFFlat索引中有实际的定义,所以只有该类型以及该类型索引的子类有update的功能。本篇文档也以IndexIVFFlat为基础进行分析和实验。

2. 过程分析

2.1 Python接口

上篇文章中说明,faiss的python接口定义都是在swigfaiss.py文件内,update_vectors也一样,如下所示:

# n: 要更新的数据集的大小,如n条列表
# idx: 要更新的数据集的标签列表
# v: 数据集的地址
def update_vectors(self, nv, idx, v):
        return _swigfaiss.IndexIVFFlat_update_vectors(self, nv, idx, v)

上述方法的功能是在index中替换nv条数据向量,其中替换的位置由idx确定,数据向量地址为v。这里的idx是大小的列表,如n条向量组成的数据集,要完整替换所有向量,则idx为[0,1,2,…,n]。

update_vectors在faiss.py的替换方法会省去第一个参数nv,该值直接通过idx的大小获取,如下所示:

def replacement_update_vectors(self, keys, x):
    n = keys.size
    assert keys.shape == (n, )
    assert x.shape == (n, self.d)
    self.update_vectors_c(n, swig_ptr(keys), swig_ptr(x))

测试程序中关于update相关的代码如下:
# 生成更新的dataset
np.random.seed(1024)
ub = np.random.random((nb,d)).astype('float32')
ub[:,0] += np.arange(nb) * 1.0/nb

# 调用update_vectors
index.update_vectors(np.arange(nb), ub)
del ub

程序中现有numpy产生nb条维度为d的数据集,然后使用该数据集ub更新index.

2.2 C++源代码

源代码定义在IndexIVFFlat.cpp文件下

/*
* n: 要更新的向量个数
* new_ids: 新向量在x中的id列表
* x: 新的数据集地址
*/
void IndexIVFFlat::update_vectors (int n, idx_t *new_ids, const float *x)
{
    // 1. 检查direct_map是否已经打开
    FAISS_THROW_IF_NOT (maintain_direct_map);
    // 2. 要更新的索引必须是训练过的
    FAISS_THROW_IF_NOT (is_trained);
    
    std::vector<idx_t> assign (n);
    // 3. 找出最接近查询x的1个向量的索引集合,即n*1个向量
    quantizer->assign (n, x, assign.data());

    // 依次对每个向量进行替换操作
    for (size_t i = 0; i < n; i++) {
        idx_t id = new_ids[i];
        FAISS_THROW_IF_NOT_MSG (0 <= id && id < ntotal,
                                "id to update out of range");
        { // remove old one
            int64_t dm = direct_map[id];          // dm是索引中第id个向量的映射值
            int64_t ofs = dm & 0xffffffff;        // 取dm的低32bits, 向量的最大偏移
            int64_t il = dm >> 32;                // 取dm的高32bits, list_no
            size_t l = invlists->list_size (il);  // 获取编号为il的向量的大小, code_size
            
            // 如果 ofs != l -1,说明direct_map与inverted_list中的向量对不上,那么需要调整invlists中的vectors
            if (ofs != l - 1) { // move l - 1 to ofs
                int64_t id2 = invlists->get_single_id (il, l - 1);      // 生成新的向量id
                direct_map[id2] = (il << 32) | ofs;                     // 将新的id的映射值替换原有映射值
                invlists->update_entry (il, ofs, id2,
                                        invlists->get_single_code (il, l - 1));
            }
            invlists->resize (il, l - 1);
        }
        { // insert new one
            int64_t il = assign[i];
            size_t l = invlists->list_size (il);
            int64_t dm = (il << 32) | l;
            direct_map[id] = dm;
            invlists->add_entry (il, id, (const uint8_t*)(x + i * d));
        }
    }
}

2.3 主要变量及成员

maintain_direct_map
maintain_direct_map是定义在struct IndexIVF中的一个bool型变量,用于描述是否开启映射以直接访问元素,若开启会使能reconstruct()函数。默认为"False",所以要使用update功能需在IndexIVF.cpp的IndexIVF构造函数中改为"True"。

direct_map
direct_map的数据定义为std::vector<idx_t> direct_map; 是一个用于存放索引中每个已添加向量的映射值的容器,该容器内元素为int64_t类型,其中高32bits表示向量在invertedList中的编号,低32bits表示该向量的大小。
当maintain_direct_map标志为设置时,在add过程中会将每个向量的映射地址push_back到本容器中。

invlists
invilists是父类IndexIVF的一个成员指针,类型为struct InvertedLists,其中存放了索引的实际数据。其包含的关键内容如下:

size_t nlist: number of possible key value
size_t code_size: code size per vector in bytes
std::vector<std:vector<idx_t>> ids : Inverted list for indexed
std::vector<std::vector<uint8_t>> codes : binary codes

ids和codes分别是一个二维的可变长容器,codes存放的是原dataset中的向量,这个比较好理解,一条向量就按行存放在一个vector<uint8_t>中其中向量编号由list_no即行号表示。

关键是这里的ids,既然是id号,那么我理解这里按行号与codes里面一一对应就好了,实际不然,这里仍然采用一个二维容器的方式,行号虽然对应,每一行里面存放一个offset用于表示codes的size大小。

3. 总结

因为IndexIVFFlat对原有向量集进行倒序划分,不是简单地一条一条平铺在索引中,所以在更新之前需要找到要更新的向量。这里采用最接近替换,即会先在index中搜索出与新向量集最接近的n个向量(n为新的向量集大小)。

要使用update功能,需要在创建索引时打开"maintain_direct_map"flag,这样会对每个向量进行映射以记录向量位置,方便后续替换。

IndexIVFFlat的方法主要存放在quantizer中,而数据主要保存在InvertedLists成员中。IndexIVFFlat的主要操作用于管理这两个类。

Note

  1. direct_map和invilists内ids的对应关系仍然没有完全理清,需要后续继续研究。
  2. invilists中ids和codes的关系,以及存放内容如何,需要继续研究
  3. invlists->update_entry和invlists->add_entry 的具体操作过程如何?
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翔底

您的鼓励将是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值