它是一個能使開發者快速搜索相似多媒體文件的算法庫。而該領域一直是傳統的搜索引擎的短板。借助Faiss,Facebook 在十億級數據集上創建的最鄰近搜索(nearest neighbor search),比此前的最前沿技術快 8.5 倍,並創造出迄今為止學術圈所見最快的、運行於 GPU 的 k-selection 算法。Facebook 人工智能實驗室(FAIR) 借此創造了數個世界紀錄,包括在十億高維矢量上的構建的、世界最快的 k-nearest-neighbor 圖。
相似性搜索的本質
傳統數據庫由包含符號信息的結構表組成。比方說,一個圖像集,會用每行放一張索引照片的列表來表示。每一行都包含諸如圖像標識和描述語句等信息。每一行也可與其他表格的條目關聯,比如照片與人名列表相關聯。
雷鋒網獲知,很多AI 工具都會產生高維矢量,比如像 word2vec 這樣的文本嵌入工具,以及用深度學習訓練的 CNN 描述符(deors)。這些表示比固定的符號表示更加強大靈活,至於原因,本文將為大家解釋。但是,用 SQL 來檢索的傳統數據庫並沒有適配這些新型表示。首先,海量的新多媒體流創造了數十億的矢量。其次,而且更重要的是,找到相似的相似的條目意味着找到相近的高維矢量。而對於當下的標准檢索語言,這是極度低效、甚至無法實現的。
如何使用矢量表示?
讓我們假設你有一張某建築的影像,比方說某城市的禮堂照片,但你忘記這是哪一個城市的了。然后,你希望找到圖片庫中該建築的所有照片。該情況下,SQL 中常用的 key/value 檢索並沒有幫助——因為你已經忘了這是哪個城市。
這就輪到相似性搜索派上用場。由於設計,圖像的矢量表示會對相似圖像生成相近的矢量。在這里,相近矢量被定義為在歐幾里得空間相鄰的矢量。
另一項矢量表示的應用是分類。想象下你需要一個分類器,來判別圖片庫中哪一個圖片代表了菊花。分類器的訓練是一個開發者們都比較熟悉的過程:算法把菊花和非菊花的圖像作為輸入。若果分類器是線性的,它會輸出一個分類矢量,后者帶有一項重要屬性:它的向量點積(Dot Product)和圖像矢量在一起,能反映出該圖像包含菊花的概率。然后對向量點積和圖片庫中的所有條目進行計算。最后 return 有最高概率值的圖像。這種檢索是一種“最大內積”搜索。
所以,對於相似性搜索和分類,我們需要以下操作:
給定檢索矢量,return 在歐幾里得距離上最接近這個矢量的數據庫對象列表。
給定檢索矢量,return 有最高向量點積的數據庫對象列表。
一個額外的挑戰,是 Facebook 想要在一個大尺度上執行這些操作。這里,“大尺度”指的是數十億的矢量。
軟件包
現有的軟件工具,不足以完成上述數據庫搜索操作。傳統的 SQL 數據庫系統可用性不高,因為它們是為 hash-based searches 或 1D interval searches 而優化。OpenCV 等工具包里包含的相似性搜索功能,在擴展性上的限制非常大。針對“小”數據集的相似性搜索算法庫也是這么個情況(比如,一百萬個矢量)。其他工具包大多是學術研究的產物,為發表的論文而開發,用來在特定設定下展示效果。
Faiss 是一個打破上述限制的算法庫。它的優點有:
提供數個相似性搜索方法。這些方法針對不同使用情況,提供了跨度很大的功能取舍。
為內存的使用和速度而優化。
為相關索引方法提供了最前沿的 GPU 執行方案。
相似性搜索評估
一旦這些矢量被學習機提取出來(從圖像、視頻、文本文件或其他渠道),它們就已經可以被輸入進相似性搜索庫。
Facebook 有一個作為參照的“暴力”算法——它會完整地照章計算所有的相似性,然后 return 最相似元素的列表。這提供了一個黃金結果參照列表。值得注意的是,高效得執行該暴力算法不容易實現,而且經常影響系統其它部分的效果。
如果我們願意犧牲一部分精確度,相似性搜索的速度可以有數個數量級的提升。當然,這會導致相對參照結果的一點偏移。舉個例子,對圖像相似性搜索的第一和第二個結果進行交換,或許不會有什么區別,因為它們很可能都是某個給定檢索的正確答案。加速搜索意味着要對數據集進行一些預處理,Facebook 把這成為索引。
這使 Facebook 確定了三大研究方向:
速度。找到十個最相似的矢量需要多久?希望花費的時間比暴力算法要少。不然的話,索引就沒有任何意義。
內存使用。該方法需要多少 RAM?比原始矢量多還是少? Faiss 只支持在 RAM 上搜索,因為其他磁盤數據庫的速度要慢數個數量級。即便是 SSD 也太慢。
精確度。返回的結果列表與暴力搜索結果差多少?精確度能通過計算檢索數量,在結果列表中先返回最鄰近單位評估;或是衡量 10 個最先返回的最鄰近單位的平均 fraction (該方法被稱之為 10-intersection)。
Facebook 一般會衡量在給定內存使用情況下,速度和精確度之間的權衡。Faiss 專注於壓縮原始矢量的方法,因為它們是擴展到十億級矢量數據集的唯一途徑。當需要索引的矢量有十億個之多,每一個會占用 32 左右的字節,這些矢量會占用極大的內存空間。
許多索引算法庫針對的是百萬左右的矢量,Facebook 的工程師們把這成為小規模。舉個例子,nmslib 就包含這方面極其高效的算法,它比 Faiss 更快但是會占用遠遠更多的內存空間。
十億個矢量的評估
工程世界中,並沒有針對這個大小數據集的基准。因此,Facebook 基於研究結果進行評估。
精確度在 Deep1B 上進行評估,它是包含十億張圖片的圖像庫。每一個圖像都已經被 CNN 處理過,CNN 中的其中一個 activation map,會被作為圖像 描述符(deor)。這些矢量可以與歐幾里得距離進行比較,來量化這些圖像之間的相似度。
Deep1B 包含一個比較小的檢索圖像庫。真實的相似性搜索結果,由處理了這些圖像的暴力算法提供。因此,如果我們運行一個搜索算法,我們就可以評估結果中的 1-recall@1。
選擇索引
由於評估,我們把內存使用限制在 30 GB。該內存限制指導我們進行索引方法和參數的選擇。在 FAISS,索引方法用字符串來表示;在這個例子中是OPQ20_80,IMI2x14,PQ20。
該字符串代表了應用於矢量的預處理步驟 (OPQ20_80) 。一個 selection mechanism (IMI2x14) 來指示數據庫應該如何被分割,以及一個編碼部分(encoding component PQ20)來指示用 product quantizer (PQ) 編碼的矢量,生成 20 字節的代碼。因此,內存的占用(包括了 overheads)在 30GB 以下。
這聽起來太技術流,因此 Faiss 的文件會向開發者提供指導:如何根據需要選擇最恰當的索引類型。
索引類型確定之后,就可以開始索引。FAISS 算法庫對這十億個矢量進行處理,並把他們放入索引。索引能夠存在硬盤,或者立即使用,對索引的搜索、additions/removals 可被交錯插入。
在索引中搜索
當索引就緒后,一系列 search-time 的參數可設為針對此方法進行調整。由於評估需要,我們用單線程進行搜索。由於內存占用已經被限制住,我們需要在精確度和搜索時間之間進行權衡、優化。舉個例子,這意味着能對 1-recall@1 40% 的最不可能搜索時間設置參數。
幸運的是,Faiss 配有自動調參機制,能掃描參數空間,並對提供了最佳操作點(operating points)的那些進行。這意味着給定精確度情況下的最優潛在搜索時間,或者反過來,給定搜索時間的最優精確度。在 Deep1B 上,操作點可用折線圖的形式進行可視化。
這幅圖上,我們可讀出,獲取 40% 的 1-recall@1,有少於每矢量 2 ms的檢索時間。如果把檢索時間放寬到 0.5 ms,我們可以達到 30%。2 ms 的檢索時間,意味着單核的 500 QPS(queries per second )。
若把該結果與圈內最先進的研究相比,即 Babenko 和 Lempitsky 在 CVPR 2016 上發表的論文:“Efficient Indexing of Billion-Scale Datasets of Deep Deors”。該論文介紹了 Deep1B 數據集。但他們需要 20 ms 來獲取 45% 的 1-recall@1。
用 GPU 處理十億級數據集
當前,許多研究努力集中於 GPU 的執行上。在原生多 GPU 支持下,這能夠產生相當不錯的單機性能。GPU 執行可被看做是對應 CPU 的替代,你其實不需要理解 CUDA API 來挖掘 GPU 的性能。 Faiss 支持所有 2012 年之后發布的英偉達顯卡(開普勒,計算能力 3.5+)。
我們希望把 roofline model 作為指南,它指出開發者應盡量使內存帶寬或者浮點單位飽和。Faiss 單 GPU 的速度一般比 CPU 快五到十倍。新的帕斯卡架構 GPU 硬件,比如英偉達 P100,使之快了 20 倍有余。
一些展示性能的數字:
合適的索引,一個簡單暴力的 k-nearest-neighbor 圖(k = 10),基於 YFCC100M 數據集中 9500 萬圖像的128D CNN 描述符,0.8 的 10-intersection,用四路上代泰坦(Maxwell Titan X)只需要 35 分鍾即可建成,包含索引創建時間。
十億矢量的 k-nearest-neighbor 圖已經即將成為現實。開發者可以在 Deep1B 數據集上創建強力的 k-nearest-neighbor 圖(k = 10),0.65 的 10-intersection,在四路 Maxwell 泰坦支援下需要 12 個小時。若是 0.8 的 10-intersection,八路帕斯卡 P100-PCIe GPU,也是 12 個小時。更低質量的圖可在五小時內用泰坦創建完成。
其他部分也達到了驚人性能。比方說,創建上述 Deep1B 索引需要 6710 萬 120-dim 矢量,用 k-means 聚類生成 262,144 個幾何中心。這在 25 E-M 次迭代下,需要四路泰坦 (12.6 tflop/s of compute) 花 139 分鍾進行處理。注意聚類的訓練集不需要與 GPU 顯存匹配,因為數據是按需即時導入 GPU 的,而不會影響性能。
底層技術
FAIR(Facebook 人工智能實驗室) 自 2015 年着手開發 Faiss。這是建立在過去的許多研究結論和大量技術攻關的基礎上。對於 Faiss,Facebook 選擇專注於對幾項基礎技術進行優化。尤其在 CPU 方面,Facebook 大量利用了:
多線程以充分利用多核性能並在多路 GPU 上進行並行搜索。
BLAS 算法庫通過 matrix/matrix 乘法進行高效、精確的距離計算。沒有 BLAS,高效的強力執行很難達到最優狀態。 BLAS/LAPACK 是唯一一個 Faiss 必須的前提軟件。
機器 SIMD 矢量化和 popcount 被用於加速孤立矢量的距離計算。
在 GPU 方面
對於從前的相似性搜索 GPU 執行,k-selection(尋找 k-minimum 或 maximum 因子)一直存在性能問題。這是因為普通的 CPU 算法(比如 heap selection)並不適用於 GPU。對於 Faiss GPU,Facebook 設計了學術圈迄今為止最快的小型 k-selection 算法(k <= 1024)。所有中間狀態都完全保存在寄存器中,進一步提升了速度。它能夠將輸入數據以 single pass 進行 k-select,運行於潛在峰值性能的 55%,取決於峰值 GPU 顯存帶寬。由於其狀態只存儲在注冊表中,並可與其他 kernels 融合,使它成為超級快的 exact 和 approximate 搜索引擎。
研究領域的許多注意力被放到了高效的 tiling 策略,和面向 approximate 搜索的 kernels 執行。多 GPU 支持用過粉碎或復制數據來提供。開發者並不會受單 GPU 顯存大小的限制。半精度浮點支持 (float16) 也有提供,可在支持的 GPU 上進行完整 float16 運算,或者更早 GPU 架構所提供的的中級 float16 存儲。我們發現 float16 這樣的編碼矢量能在幾乎不損失精度的前提下進行加速。
簡而言之,持續的 overhead 因素會在執行中起到作用。Faiss 做了許多關注工程細節的痛苦工作。
上手 Faiss
Faiss 用 C++ 實現,支持 Python。想要上手的各位,請到 GitHub 獲取 Faiss,進行編譯,然后把 Faiss 模塊導入到 Python。Faiss 與 numpy 能做到完美的整合,包括需借助 numpy 陣列來實現的所有功能 (in float32)。
GitHub 地址:https://github.com/facebookresearch/faiss
詳情請訪問:https://code.facebook.com/posts/1373769912645926/faiss-a-library-for-efficient-similarity-search/