語義向量召回之ANN檢索
語義向量召回之ANN檢索
本篇文章主要介紹KDD 2020 Applied Data Science Track Papers中的一篇來自Facebook的語義檢索文章,Embedding-based Retrieval in Facebook Search。關於樣本、模型、訓練等細節可以參考:負樣本為王:評Facebook的向量化召回演算法。本文重點關注其 工程實踐 上的經驗,也藉此機會對基於PQ量化的近似近鄰搜尋 (ANN) 的原理做個梳理。
架構總覽
首先從總體上預覽一下paper提出的EBR系統的架構 (Embedding-based Retrieval System):
EBR系統架構圖示意圖
-
左上角是 查詢處理模組 ,是雙塔模型中的query-side embedding model
-
右上角 索引構建模組 ,是用document embedding model推斷doc embedding後構造向量索引。
構造過程中,先拓展doc的metadata,加入doc embedding,並匯入doc的正排索引中 (比如用於ranking的特徵);
同時,通過向量量化技術來降低索引儲存和計算距離代價,並將 量化的結果 存在倒排索引中。
這一部分也是paper中關注的重點,下文會重點介紹。
-
左側中間部分是 檢索模組 ,拿到query embedding後,可以通過精心為語義召回設計的 NN運算元(Near Neighbor Operator) 計算距離並進行 語義召回 。
召回的過程中,既支援原來的布林匹配召回,也支援向量語義召回,還支援混合召回。
這部分下文也會重點介紹。
-
左下角是 排序模組 ,得到召回結果後,再經過排序模組進行實時排序,排序時會利用到 embedding特徵 。
paper中涉及工程實現細節的主要是 索引構建模組 和 檢索模組 。下文我會先介紹索引構建模組,這裡頭涉及很多向量量化的概念,比如PQ量化、粗糙量化、殘差量化等,我會先介紹一些量化的背景知識,核心的索引過程和搜尋過程,然後介紹線上檢索模組,認識基於構建好的向量索引,線上是 如何運轉 和 實現召回 的;接著探討paper中提到的工程優化點和調參經驗。最後,做個總結。
索引構建模組
基於Product Quantization的近似最近鄰搜尋,核心的一些問題預覽一下:
-
問題描述,解決什麼問題?
-
傳統方法存在什麼問題?
-
什麼是向量量化?為什麼要量化?量化場景下距離怎麼計算?
-
什麼是乘積量化?為什麼要乘積量化?
-
什麼是粗糙量化+殘差量化?為什麼要殘差量化?
-
索引過程?搜尋過程
問題描述
給定D維向量和集合,需要找到與距離最短的 k 個最近鄰。距離的衡量可以是歐式距離、餘弦距離等。
暴力搜尋問題
如果以最粗暴的方法進行窮舉搜尋,構造 距離矩陣 的複雜度為:。從距離矩陣中查詢到 k個最近鄰 ,最小堆,則複雜度為。
舉個例子:假設,則構造的距離矩陣包含個元素,假設每個距離值32bit,至少佔用1600TB空間,構建距離矩陣的時間是量級,查詢Top-K搜尋時間是量級。
向量量化
所謂向量量化,就是將原來無限的空間對映到一個有限的 向量集合 中,其中是一個自然數。將這個從到集合的函式記為,則,在資訊理論中稱為codebook。即:通過來 近似 代表。
聚類量化
最常用的就是k-means聚類,通過聚類後的 聚類中心向量 來近似 量化 原始的向量。 聚類中心的個數 即為 codebook大小 。因為量化的存在, 任意兩個向量之間的距離 可以通過對應的 量化向量的距離 進行近似,也就是聚類中心向量的距離。因為聚類中心的個數 小了 很多,故計算距離的複雜度也下降了很多。(顯然,這種方式太粗糙了,誤差很大。除非聚類數非常大,極端情況下,聚類數等於樣本數時,每個樣本一個聚類簇,此時無誤差,但是沒有起到減少計算複雜度的目的)
聚類量化的結果:產出聚類中心向量的過程對應train的過程的一部分。 量化後,每個向量都可以用 聚類簇中心下標ID 來 標識 ,根據ID可以獲取聚類簇的中心向量。
如上圖所示,N個向量,通過聚類量化產生多個聚類中心,每個向量屬於某個聚類簇中,那麼就用該聚類簇對應的中心向量來量化該向量,可以用聚類簇中心對應的下標ID來表示,比如:C1量化vec1。
Faiss中對應的實現是 IndexIVFFlat。
乘積量化
動機: 即:PQ量化,很多時候我們向量不同部分之間的 分佈不同 的,比如下圖(3段向量),因此可以考慮對 向量分段 ,並分別進行 分塊量化 。這個只是直覺原因,本質原因下文講。
分塊量化也可以採取聚類量化來實現,則 分塊聚類中心的個數 即為 分塊codebook 的大小。相當於在這個方法下,對每個向量,有【 m個分塊向量】 來量化它,即: 【m個分塊聚類中心向量】 。 示意圖如下:
PQ量化過程示意圖
如上圖所示,將原始向量等分為m組分塊向量,每組都進行聚類量化,那麼每個向量就有m個分塊聚類中心向量來表示,比如vec1用, ...,共m個向量來量化。
乘積量化的好處 : 假設每個子codebook大小一樣,記做 ,排列組合一下,那麼相當於能表達的向量空間容量是這麼大, ,但是隻需要 的codebook空間,這也是乘積量化大幅度降低空間佔用的本質原因。 PQ量化原論文中給出的經驗取值是:
,即:分成8塊,每個分塊的codebook大小為256,對應的向量空間大小為約,能夠表達的向量個數足夠大了。
乘積量化結果:個分塊聚類中心向量。 每個向量可以用m個 分塊聚類簇中心下標 ID來標識,所有ID連起來稱為 code 。假設每塊的聚類中心個數為256,則需要8bits,即1byte標識某分塊下哪個聚類中心,m塊則需要m bytes,即code大小為m bytes。
粗糙量化+殘差量化
核心思想: 層次化量化,這個也是Faiss中PQ索引的實現方式。其中粗糙量化使用 聚類量化 ,用劃分到的 粗糙聚類簇的中心向量 來 粗粒度 量化該向量,該結果存在較大的誤差;接著對殘差結果進行 細粒度 乘積量化。這樣的話,誤差就小了。
1. 總體上,每個向量先進行粗糙量化劃分到某個粗糙聚類簇裡,1個向量對應1個粗糙聚類簇標識,通常稱為粗糙量化ID;
2. 然後計算殘差向量,即:向量-聚類簇中心向量,再對該殘差向量進行分塊,並進行細粒度分塊殘差量化。殘差量化的時候,每一塊對應一個細粒度聚類簇,1個向量M塊,則對應M個細粒度聚類簇標識,通常稱為殘差量化code。
3. 為什麼用殘差量化?原始向量可能會有特別大的分佈差異/不平衡,也就是說可能聚類後,不同聚類簇分佈得非常分散,每個簇所擁有的樣本數極度不平衡。但是通過殘差化後,即:每個樣本向量減去所屬的聚類簇中心向量後,殘差向量之間的差異就不太大了,然後再對殘差向量進行量化,就能更精確的近似原向量。
過程: 具體而言,向量庫先構造一個小規模codebook,量化器為。這個就是所謂的 粗糙量化,或者稱為粗糙聚類 。接著,每個向量y都會有一個殘差。具體而言:記殘差量化步驟的量化器為,則 y 可以通過來表示。
其中,是粗糙量化結果,是殘差量化結果。
這樣的話,【 查詢向量x】和y之間的距離 :
-
term 2 與查詢向量x 無關, 可以提前計算好;
-
term 1 求x和y的粗糙量化向量的歐式距離,最多計算次,為粗糙聚類中心個數。
-
term 3 求x和y的殘差量化向量的內積,遍歷所有簇的時候,最多計算次,為分塊聚類中心向量。
對應Faiss中的實現是 IndexIVFPQ。
做個小結,量化的結果:
聚類量化 | 乘積量化 | 粗糙量化+殘差量化 |
---|---|---|
k個聚類簇中心下標id | m x k* 個分塊聚類中心下標組成的 code | k個聚類簇中心下標ID,m x k* 個分塊聚類中心下標組成的 code |
也就是說,為了表示每個doc的 量化結果 ,可以為doc可以新增兩種 結構化欄位 : 粗糙量化id, 殘差量化code,用於實時檢索使用。
倒排索引
索引結構
建立從粗粒度聚類中心id 到 doc的對映關係,其中doc的資訊包括:向量id, 向量的殘差量化code。每個doc通過粗糙聚類中心id和殘差量化code就能知道原始向量如何對映到量化向量。
整個倒排索引不需要儲存原始向量本身,索引結構儲存的內容:
-
儲存標識: 粗糙量化id , doc id 和殘差量化 code。
空間佔用很小。m bytes 殘差量化code,即:code_size或者pq_bytes。這個數越大,那麼細粒度聚類簇越大,則精度越高。
基於id,可以找到對應的粗粒度量化向量 (共k個) ; 基於code 可以找到對應 細粒度殘差量化向量 (共 m x k* 個)。
索引構建過程
搜尋場景中,y可以理解為doc。

-
通過粗糙量化器將向量對映到,即粗糙量化。這樣就知道掛到哪個連結串列上了。
-
計算殘差
-
將殘差量化到,其中包含了 m 個分組,每個分組有對應的一個細粒度聚類簇ID,用1byte表示,則共m bytes,對應code標識。
-
構造一個entry,其中id是doc的標識,code是殘差量化標識。
搜尋過程
搜尋場景中,x可以理解為查詢向量。

-
通過粗糙量化器來量化 查詢向量x ,即:找到離x最近的w個粗糙聚類簇。實際上是用於 限定搜尋的範圍 ,只搜尋w個粗糙聚類簇ID索引下的向量。w是個超引數,即nprobes,量化的結果 對應term 1。
-
選定某個粗糙聚類簇,
-
計算 x 和該粗糙聚類簇下的 k* (預設即256)箇中心點向量的內積。對應term 3,計算時間複雜度 O(m x D/m x k*) = O(Dk*),記錄下來,下一步查表用。
-
遍歷該聚類簇下的doc文件, 計算距離時 ,實際上全是 查表操作 ,term 2是提前算的,term 1粗糙量化時算的,term 3上一步算的。查表時間複雜度實際上是 O(m)
w個粗糙聚類簇,搜尋時間複雜度為 O(w(Dk* + m)), 另外,返回top-k的話,要加上最小堆排序時間。
檢索模組
布林檢索
Facebook在自研的檢索引擎Unicorn中支援了第一代的近鄰搜尋。
首先簡單介紹下Unicorn。Unicorn原本的檢索方式主要以Term布林匹配為主。
-
doc側:以Term詞袋的方式來表示doc,會基於Term的語義來區分名稱空間。Term上還可以新增一些term特定的 payload 資訊。舉例:John living in Seattle會表示成【 text : john and location : seattle】。
-
query側:定義 Term-level 的布林表示式來表示query。舉例:下述query主要返回doc的文字中有john和smithe字眼,並住在seattle和menlo_park的使用者。
向量檢索
為了支援embedding,需要擴充套件 doc和query的表示
-
doc側:新增結構化欄位 key 來拓展doc詞袋錶示,形如:
<key>,比如:key=model-141795009,代表了產出doc embedding model的版本。設定該欄位方便部署多種embedding版本。 -
query側:新增 nn運算元 ,即: nn < key > : radius < radius >
,使用時,通過計算query embedding和 模型產出的doc embedding的距離,來匹配距離在指定radius內的 文件 。radius此處起到 閾值約束 的作用。
索引和計算向量距離時,Facebook將 向量索引和向量線上檢索 通過某種方式轉化成上述已有的 布林檢索語言 ,很巧妙,可以完美融入 現有的布林檢索系統 ,而不需要重新寫一套系統。
先做個對應關係。
布林匹配檢索 | 語義向量檢索 |
---|---|
Term | 粗糙聚類中心ID 標識,Cluster-ID |
Payload | 細粒度聚類中心CODE 標識, Cluster-Code |
doc側:每個doc embedding會被 量化 並轉成一個 term 和一個 payload ,相當於是兩個doc的結構化欄位,完美相容已有的檢索系統Unicorn的設計。
-
term,其實就是用於標識倒排索引中,該doc屬於哪個 粗粒度聚類簇 (用於粗糙聚類量化)
-
payload,用於標識每個分塊向量下的 細粒度聚類簇 (用於殘差量化)
query側:
-
term: (nn) 重寫成和粗糙聚類相關的 term。
重寫規則:計算query embedding和所有粗糙聚類中心距離,選出nprobes個最近的,用粗糙聚類中心ID來標識 (和doc的結構化欄位 Cluster-ID 進行比較),不同聚類中心對應的Term之間的關係就是or的關係。
-
payload: 對query進行殘差量化,得到滿足 radius約束條件 的細粒度聚類中心,用code標識。
重寫規則:對每個粗糙聚類簇,計算query embedding和其細粒度聚類中心的距離,距離 滿足<radius約束 的細粒度聚類中心對應的Code 取值記錄一下 (和doc的 結構化欄位Cluster-Code 進行比較),Code之間是OR關係,但是Code和ID是AND關係。
舉個query改寫的例子:
or ((and( (term(Cluster-ID, '粗糙聚類中心ID-a')),
(or (term(Cluster-Code, '殘差聚類中心ID-a1'), term(Cluster-Code, '殘差聚類中心ID-a3'),...)))
),
(and( (term(Cluster-ID, '粗糙聚類中心ID-b')),
(or (term(Cluster-Code, '殘差聚類中心ID-b1'), term(Cluster-Code, '殘差聚類中心ID-b4'),...)))
),
...
)
另外,作者強調了radius-mode和topk-mode的差異,radius方式的效能和質量更高。radius是一種 受限制 NN搜尋。top-K需要掃描整個索引庫來找 Top-K結果 。radius效能更好,但是需要確定radius值。
正是因為在已有的布林查詢語言上融入目前的EBR語義檢索,使得混合檢索的實現非常方便。特別是在模糊匹配場景、或者query存在錯誤的場景。舉個例子:
調參經驗
模型改進不大的時候,多調調引數,會有奇效。
-
調節粗糙量化聚類簇數量num_cluster和實時查詢時掃描的聚類簇數量nprobe。由於不同聚類中心的向量個數可能存在很不平衡的現象,對於兩個對比實驗(比如對比不同構建索引的方法),即使num_cluster和nprobe設定完全一致,可能兩個實驗掃描的文件數量是不一樣的,因此要監控掃描文件數量,並通過調節這兩個引數,來保證不同對比方法掃描的文件數量一致。
-
多嘗試使用乘積量化:包括原生PQ,OPQ,PQ with PCA等。能夠顯著 降低索引儲存空間儲存複雜度、查詢時間複雜度 。其中, pq_bytes,即殘差量化codes的大小,很重要 。決定了殘差量化分塊聚類中心的個數,個數越大越精確,比如m=8,k*=256時,pq_byte=1byte x 8= 8 byte。paper中建議採用d/4,d是向量的維度數,假設64維度,則d/4=16,是預設值的2倍。
-
多進行 線上調參 ,nprobe, num_clusters,pq_bytes。雖然離線能夠直觀感受perf vs recall之間的tradeoff,但是多部署幾套引數進行線上調整,對於理解效能因素對EBR檢索系統的影響會更加直觀。這對於減少 容量開銷 和 離線引數搜尋成本 很有用。
總結
此次分享主要介紹了Facebook在KDD 2020發表的文章中的工程實踐經驗。首先從全域性總覽其系統架構,然後針對索引構建模組和實時檢索模組展開討論。其中,索引構建模組主要介紹了ANN中 向量量化 方法的背景知識。實時檢索模組主要介紹 系統實現 ,如何將向量檢索通過 query改寫規則 等融入現有的布林檢索系統中。最後介紹了一些 調參的經驗 。
做向量召回的初期可以先重點關注模型層面上的優化,索引上也可以先採用簡單的聚類量化的索引構建方式。當向量召回優化到一定程度時,如果想進一步提升效能和召回率,可以考慮借鑑文中的一些經驗,比如以PQ量化的方式來構建索引,調參等。
參考
KDD 2020: Embedding-based Retrieval in Facebook Search
Faiss基於PQ的倒排索引實現
負樣本為王:評Facebook的向量化召回演算法
Faiss向量召回引擎如何做到快速查詢最近鄰
A library for efficient similarity search and clustering of dense vectors
- 綜述 | GNN金融風控領域業界進展調研
- 推薦中的召回演算法—總結串講
- 召回模型中的負樣本構造
- SIGIR'21騰訊 FORM:元學習 FTRL 動態學習率
- 少數派報告:談推薦場景下的對比學習
- 肝了三個月 | 機器學習與推薦演算法工程師養成計劃
- 點選率預估分析中的問題
- 一文總結微軟研究院Transformer霸榜模型三部曲!
- 淺談推薦系統如何在NLP的肩膀上前進
- 在演算法的道路上,你掌握了什麼思維讓你突飛猛進?
- 【讀書筆記】被討厭的勇氣
- Bootstrap方法在AB TEST中的應用
- 這應該是網上最簡單的元學習入門教程了
- 召回演算法和工程協同優化的若干經驗
- Simnet | 神經網路語義匹配技術
- 冷啟動與多樣性的一把利器
- KDD2021| 工業界搜推廣nlp論文整理
- 多互動注意力網路用於CTR預估中細粒度特徵學習
- 五個工業風滿滿的 Look-alike 演算法
- KDD2021 放榜,6 篇論文帶你瞭解阿里媽媽AI技術