amazon-knn 原理

 
knn 插件原理 
  
利用 Lucene的 Codec 扩展机制。Codec 可以理解为 Lucene 索引文件格式的一种协议,用户只要实现对应的写入/读取的业务流程,即可自定义正排、倒排、StoreFields 等不同索引的具体实现。包装扩展了Lucene 的 Latest Codec,当向量数据写入es的某个字段时,前期流程跟原生的流程一致,先放入 indexBuffer 中;等内部发起 Refresh 时,调用底层的 mnslib 库,构建出向量索引。
 
 
 
 
 
查询的时候,由于向量索引和原生索引一样都是 Segment 粒度生成,所以只要实现向量 Segment 对应的 Weight 和 Scorer即可。具体的,当查询到了 BuildScorer 阶段,我们利用底层 mnslib 库加载当前 Segment的向量索引文件,通过 Native 方法查询出TopN的 id 和 Score 后,通过docID和分数生成当前 Segment 的 Scorer,交给indexSearcher继续执行上层的求交/求并操作即可。
 
 
 
插入过程:
在整个HNSW(Hierarchical Navigable Small World分层的可导航小世界)的insert的过程中包含以下几个部分。
   
 
 
5.初始化当前最近邻集合W,初始化固定节点ep,获取顶层编号L,获取新插入节点的层l
  1. 对于属于L→l+1的每一层查找出q的最近邻节点。 
  2. 对于lc <- min(L,l)…0的每一层执行以下步骤:
3.1) 每一层查找出最近的efConstruction个节点得到集合M。
3.2) 在每个节点中查找到最近的M个neighbors。
3.2) 将在层lc中的所有neighbors和节点q建立连接。
3.3) 对于neighbors中的每个节点e重新判断一下节点个数,然后减少e节点的邻居节点重新建立连接。
 
 
INSERT(hnsw, q, M, Mmax, efConstruction, mL)
/**
 * 输入
 * hnsw:q插入的目标图
 * q:插入的新元素
 * M:每个点需要与图中其他的点建立的连接数
 * Mmax:最大的连接数,超过则需要进行缩减(shrink)
 * efConstruction:动态候选元素集合大小
 * mL:选择q的层数时用到的标准化因子
 */
Input:
multilayer graph hnsw,
new element q,
number of established connections M,
maximum number of connections for each element per layer Mmax,
size of the dynamic candidate list efConstruction,
normalization factor for level generation mL
/**
 * 输出:新的hnsw图
 */
Output: update hnsw inserting element q
 
W ← ∅  // W:现在发现的最近邻元素集合
ep ← get enter point for hnsw
L ← level of ep
/**
 * unif(0..1)是取0到1之中的随机数
 * 根据mL获取新元素q的层数l
 */
l ← ⌊-ln(unif(0..1))∙mL⌋
/**
 * 自顶层向q的层数l逼近搜索,一直到l+1,每层寻找当前层q最近邻的1个点
 * 找到所有层中最近的一个点作为q插入到l层的入口点
 */
for lc ← L … l+1
    W ← SEARCH_LAYER(q, ep, ef=1, lc)
    ep ← get the nearest element from W to q
// 自l层向底层逼近搜索,每层寻找当前层q最近邻的efConstruction个点赋值到集合W
for lc ← min(L, l) … 0
    W ← SEARCH_LAYER(q, ep, efConstruction, lc)
    // 在W中选择q最近邻的M个点作为neighbors双向连接起来
    neighbors ← SELECT_NEIGHBORS(q, W, M, lc)
    add bidirectional connectionts from neighbors to q at layer lc
    // 检查每个neighbors的连接数,如果大于Mmax,则需要缩减连接到最近邻的Mmax个
    for each e ∈ neighbors
        eConn ← neighbourhood(e) at layer lc
        if │eConn│ > Mmax
            eNewConn ← SELECT_NEIGHBORS(e, eConn, Mmax, lc)
            set neighbourhood(e) at layer lc to eNewConn
    ep ← W
if l > L
    set enter point for hnsw to q
 
 
 
 
 
SEARCH_LAYER(q, ep, ef, lc)
/**
 * 输入
 * q:插入的新元素
 * ep:进入点 enter point
 * ef:需要返回的近邻数量
 * lc:层数
 */
Input:
query element q,
enter point ep,
number of nearest to q elements to return ef,
layer number lc
/**
 * 输出:q的ef个最近邻
 */
Output: ef closest neighbors to q
 
v ← ep  // v:设置访问过的元素 visited elements
C ← ep  // C:设置候选元素 candidates
W ← ep  // W:现在发现的最近邻元素集合
// 遍历每一个候选元素,包括遍历过程中不断加入的元素
while │C│ > 0
    // 取出C中q的最近邻c
    c ← extract nearest element from C to q
    // 取出W中q的最远点f
    f ← get furthest element from W to q
    if distance(c, q) > distance(f, q)
        break
    /**
     * 当c比f距离q更近时,则将c的每一个邻居e都进行遍历
     * 如果e比w中距离q最远的f要更接近q,那就把e加入到W和候选元素C中
     * 由此会不断地遍历图,直至达到局部最佳状态,c的所有邻居没有距离更近的了或者所有邻居都已经被遍历了
     */
    for each e ∈ neighbourhood(c) at layer lc
        if e ∉ v
            v ← v ⋃ e
            f ← get furthest element from W to q
            if distance(e, q) < distance(f, q) or │W│ < ef
                C ← C ⋃ e
                W ← W ⋃ e
                // 保证返回的数目不大于ef
                if │W│ > ef
                    remove furthest element from W to q
return W
 
 
 
搜索过程:
从固定的enter节点进入,在顶层开始检索。在每一层检索到唯一一个最近邻然后作为下一层入口节点。最后在底层检索top K个最相似节点。
 
SELECT_NEIGHBORS_HEURISTIC(q, C, M, lc, extendCandidates, keepPrunedConnections)
/**
 * 输入
 * q:查询的点
 * C:候选元素集合
 * M:需要返回的数目
 * lc:层数
 * extendCandidates:指示是否扩展候选列表的标志
 * keepPrunedConnections:指示是否添加丢弃元素的标志
 */
Input:
base element q,
candidate elements C,
number of neighbors to return M,
layer number lc,
flag indicating whether or not to extend candidate list extendCandidates,
flag indicating whether or not to add discarded elements keepPrunedConnections
/**
 * 输出:探索得到M个元素
 */
Output: M elements selected by the heuristic
 
R ← ∅ // 记录结果
W ← C  // W:候选元素的队列
if extendCandidates  // 通过邻居来扩充候选元素
    for each e ∈ C
        for each e_adj ∈ neighbourhood(e) at layer lc
            if e_adj ∉ W
                W ← W ⋃ e_adj
Wd ← ∅  // 丢弃的候选元素的队列
/**
 * 这里是关键,他的意思就是:
 * 候选元素队列不为空且结果数量少于M时,在W中选择q最近邻e
 * 如果e和q的距离比e和R中的其中一个元素的距离更小,就把e加入到R中,否则就把e加入Wd(丢弃)
 * 可以理解成:如果R中存在点r,使distance(q,e)<distance(q,r),则加入点e到R
 */
while │W│ > 0 and │R│ < M
    e ← extract nearest element from W to q
    if e is closer to q compared to any element from R
        R ← R ⋃ e
    else
        Wd ← Wd ⋃ e
/**
 * 如果设置keepPrunedConnections为true,且R不满足M个,那就在丢弃队列中挑选最近邻填满R为M个
 */
if keepPrunedConnections
    while │Wd│ > 0 and │R│ < M
        R ← R ⋃ extract nearest element from Wd to q
return R
 
 
 
 
 
 
 
    K-NN-SEARCH(hnsw, q, K, ef)
/**
 * 输入
 * hnsw:q插入的目标图
 * q:查询元素
 * K:返回的近邻数量
 * ef:动态候选元素集合大小
 */
Input:
multilayer graph hnsw, query element q,
number of nearest neighbors to return K,
size of the dynamic candidate list ef
/**
 * 输出:q的K个最近邻元素
 */
Output: K nearest elements to q
 
W ← ∅  // W:现在发现的最近邻元素集合
ep ← get enter point for hnsw
L ← level of ep
/**
 * 自顶层向倒数第2层逼近搜索,每层寻找当前层q最近邻的1个点赋值到集合W
 * 取W中最接近q的点作为底层的入口点,以便时搜索的时间成本最低
 */
for lc ← L … 1
    W ← SEARCH_LAYER(q, ep, ef=1, lc)
    ep ← get nearest element from W to q
// 从上一层得到的ep点开始搜索底层获得ef个q的最近邻
W ← SEARCH_LAYER(q, ep, ef, lc=0)
return K nearest elements from W to q
 
 
 
 
 

posted @ 2021-02-22 15:03  王南辉  阅读(174)  评论(0)    收藏  举报