Faiss流程與原理分析

 一、Faiss簡介

  Faiss是Facebook AI團隊開源的針對聚類和類似性搜索庫,爲稠密向量提供高效類似度搜索和聚類,支持十億級別向量的搜索,是目前最爲成熟的近似近鄰搜索庫。它包含多種搜索任意大小向量集(備註:向量集大小由RAM內存決定)的算法,以及用於算法評估和參數調整的支持代碼。Faiss用C++編寫,並提供與Numpy完美銜接的Python接口。除此之外,對一些核心算法提供了GPU實現。相關介紹參考《Faiss:Facebook 開源的類似性搜索類庫python

 二、Faiss安裝

  參考《faiss_note/1.Install faiss安裝.ipynb》,此文是對英文版本的翻譯,便於查看。git

      基於本機環境,採用了anaconda進行安裝,這也是faiss推薦的方式,facebook研發團隊也會及時推出faiss的新版本conda安裝包,在conda安裝時會自行安裝所需的libgcc, mkl, numpy模塊。github

      針對mac os系統,能夠先安裝Homebrew(mac下的缺失包管理,比較方便使用)。算法

  安裝anaconda的命令以下所示:數據庫

#安裝anaconda包
brew cask install anaconda
#conda加入環境變量
export PATH=/usr/local/anaconda3/bin:"$PATH"
#更新conda
conda update conda
#先安裝mkl
conda install mkl
#安裝faiss-cpu
conda install faiss-cpu -c pytorch
#測試安裝是否成功
python -c "import faiss」

  備註:mkl全稱Intel Math Kernel Library,提供通過高度優化和大量線程化處理的數學例程,面向性能要求極高的科學、工程及金融等領域的應用。MKL是一款商用函數庫(考慮版權問題,後續能夠替換爲OpenBLAS),在Intel CPU上,MKL的性能要遠高於Eigen, OpenBLAS和其性能差距不是太大,但OpenBLAS提供的函數相對較少,另外OpenBLAS的編譯依賴系統環境。dom

 三、Faiss原理及示例分析

  3.1 Faiss核心算法實現

  Faiss對一些基礎的算法提供了很是高效的失效機器學習

  • 聚類Faiss提供了一個高效的k-means實現
  • PCA降維算法
  • PQ(ProductQuantizer)編碼/解碼

  3.2 Faiss功能流程說明

       經過Faiss文檔介紹能夠了解faiss的主要功能就是類似度搜索。以下圖所示,以圖片搜索爲例,所謂類似度搜索,即是在給定的一堆圖片中,尋找出我指定的目標最像的K張圖片,也簡稱爲KNN(K近鄰)問題。函數

  爲了解決KNN問題,在工程上須要實現對已有圖庫的存儲,當用戶指定檢索圖片後,須要知道如何從存儲的圖片庫中找到最類似的K張圖片。基於此,咱們推測Faiss在應用場景中具有添加功能和搜索功能,有了添加相應的修改和刪除功能也會接踵而來,從上述分析看,Faiss本質上是一個向量(矢量)數據庫。性能

      對於數據庫來講,時空優化是兩個永恆的主題,即在存儲上如何以更少的空間來存儲更多的信息,在搜索上如何以更快的速度來搜索出更準確的信息。如何減小搜索所需的時間?在數據庫中很最多見的操做即是加各類索引,把各類加速搜索算法的功能或空間換時間的策略都封裝成各類各樣的索引,以知足各類不一樣的引用場景。學習

 3.3 組件分析

      Faiss中最經常使用的是索引Index,然後是PCA降維、PQ乘積量化,這裏針對Index和PQ進行說明,PCA降維從流程上均可以理解。

  3.3.1索引Index

      Faiss中有兩個基礎索引類Index、IndexBinary,下面咱們先從類圖進行分析。

  下面給出Index和IndexBinary的類圖以下所示:

  Faiss提供了針對不一樣場景下應用對Index的封裝類,這裏咱們針對Index基類進行說明。

  基礎索引的說明參考:Faiss indexes涉及方法解釋、參數說明以及推薦試用的工廠方法建立時的標識等。

      索引的建立提供了工廠方法,能夠經過字符串靈活的建立不一樣的索引。

index = faiss.index_factory(d,"PCA32,IVF100,PQ8 ")

  該字符串的含義爲:使用PCA算法將向量降維到32維, 劃分紅100個nprobe (搜索空間), 經過PQ算法將每一個向量壓縮成8bit。

  其餘的字符串能夠參考上文給出的Faiss indexes連接中給出的標識。

 3.3.1.1索引說明

  此部分對索引id進行說明,此部分的理解是基於PQ量化及Faiss建立不一樣的索引時選擇的量化器而來,可能會稍有誤差,不影響對Faiss的使用操做。

      默認狀況,Faiss會爲每一個輸入的向量記錄一個次序id,也能夠爲向量指定任意咱們須要的id。部分索引類(IndexIVFFlat/IndexPQ/IndexIVFPQ等)有add_with_ids方法,能夠爲每一個向量對應一個64-bit的id,搜索的時候返回此id。此段中說明的id從個人角度理解就是索引。(備註:id是long型數據,全部的索引id類型在Index基類中已經定義,參考類圖中標註,typedef long idx_t;    ///< all indices are this type)

      示例:

import numpy as np
import faiss                   # make faiss available

# 構造數據
import time
d = 64                           # dimension
nb = 1000000                      # database size
nq = 1000000                       # nb of queries
np.random.seed(1234)             # make reproducible
xb = np.random.random((nb, d)).astype('float32')
xb[:, 0] += np.arange(nb) / 1000.
xq = np.random.random((nq, d)).astype('float32')
xq[:, 0] += np.arange(nq) / 1000.

# 爲向量集構建IndexFlatL2索引,它是最簡單的索引類型,只執行強力L2距離搜索
index = faiss.IndexFlatL2(d)   # build the index
# #此處索引是按照默認方式,即faiss給的次序id爲主
# #能夠添加咱們須要的索引方式,因IndexFlatL2不支持add_with_ids方法,須要藉助IndexIDMap進行映射,代碼以下
# ids = np.arange(100000, 200000)  #id設定爲6位數整數,默認id從0開始,這裏咱們將其設置從100000開始
# index2 = faiss.IndexIDMap(index)
# index2.add_with_ids(xb, ids)
#
# print(index2.is_trained)
# # index.add(xb)                  # add vectors to the index
# print(index2.ntotal)
# k = 4   # we want to see 4 nearest neighbors
# D, I = index2.search(xb[:5], k) # sanity check
# print(I)     # 向量索引位置
# print(D)     # 類似度矩陣

print(index.is_trained)
index.add(xb)                  # add vectors to the index
print(index.ntotal)
k = 4   # we want to see 4 nearest neighbors
# D, I = index.search(xb[:5], k) # sanity check
# # print(xb[:5])
# print(I)     # 向量索引位置
# print(D)     # 類似度矩陣

D, I = index.search(xq, 10)     # actual search
# xq is the query data
# k is the num of neigbors you want to search
# D is the distance matrix between xq and k neigbors
# I is the index matrix of k neigbors
print(I[:5])                   # neighbors of the 5 first queries
print(I[-5:]) # neighbors of the 5 last queries

#從index中恢復數據,indexFlatL2索引就是將向量進行排序
# print(xb[381])
# print(index.reconstruct(381))

 3.3.1.2索引選擇

      此部分沒作實踐驗證,對Faiss給的部分說明進行翻譯過來做爲後續咱們使用的一個參考。

      若是關心返回精度,可使用IndexFlatL2,該索引能確保返回精確結果。通常將其做爲baseline與其餘索引方式對比,以便在精度和時間開銷之間作權衡。不支持add_with_ids,若是須要,能夠用「IDMap」給予任意定義id。

      若是關注內存開銷,可使用「..., Flat「的索引,"..."是聚類操做,聚類以後將每一個向量映射到相應的bucket。該索引類型並不會保存壓縮以後的數據,而是保存原始數據,因此內存開銷與原始數據一致。經過nprobe參數控制速度/精度。

      對內存開銷比較關心的話,能夠在聚類的基礎上使用PQ成績量化進行處理。

  3.3.1.3檢索數據恢復

      Faiss檢索返回的是數據的索引及數據的計算距離,在檢索得到的索引後須要根據索引將原始數據取出。

      Faiss提供了兩種方式,一種是一條一條的進行恢復,一種是批量恢復。

  給定id,可使用reconstruct進行單條取出數據;可使用reconstruct_n方法從index中回批量復出原始向量(備註:該方法從給的示例看是恢復連續的數據(0,10),若是索引是離散的話恢復數據暫時還沒作實踐)。

  上述方法支持IndexFlat, IndexIVFFlat (須要與make_direct_map結合), IndexIVFPQ(須要與make_direct_map結合)等幾類索引類型。

  3.3.2PCA降維

      具體的算法流程沒有進行深刻的瞭解,能夠參考看:《PCA 降維算法詳解 以及代碼示例》,待後續算法學習中在進行深刻了解。

      基於3.2節中對Faiss流程的說明,簡要說下對Faiss中PCA的理解。

      PCA經過數據壓縮減小內存或者硬盤的使用以及數據降維加快機器學習的速度。從數據存儲的角度,圖片處理中經過PCA能夠將圖片從高維空間(p維)轉換到低維空間(q維, 其中p > q ),其具體操做即是是將高維空間中的圖片向量(n*p)乘以一個轉換矩陣(p*q),獲得一個低維空間中的向量(n*q)。

  爲了使得在整個降維的過程當中信息丟失最少,咱們須要對待轉換圖片進行分析計算獲得相應的轉換矩陣(p*q)。也就是說這個降維中乘以的轉換矩陣是與待轉換圖片息息相關的。回到咱們的Faiss中來,假設我指望使用PCA預處理來減小Index中的存儲空間,那在整個處理流程中,除了輸入搜索圖庫外,我必須多輸入一個轉換矩陣,可是這個轉換矩陣是與圖庫息息相關的,是能夠由圖庫數據計算出來的。若是把這個轉換矩陣當作一個參數的話,咱們能夠發現,在Faiss的一些預處理中,咱們會引入一些參數,這些參數又沒法一開始由人工來指定,只能經過喂樣原本訓練出來,因此Index中須要有這樣的一個train() 函數來爲這種參數的訓練提供輸入訓練樣本的接口。

  3.3.3Product quantization(乘積量化PQ)

      Faiss中使用的乘積量化是Faiss的做者在2011年發表的論文,參考:《Product Quantization for Nearest Neighbor Search

      PQ算法能夠理解爲首先把原始的向量空間分解爲m個低維向量空間的笛卡爾積,並對分解獲得的低維向量空間分別作量化。便是把原始D維向量(好比D=128)分紅m組(好比m=4),每組就是D∗=D/m維的子向量(好比D∗=D/m=128/4=32),各自用kmeans算法學習到一個碼本,而後這些碼本的笛卡爾積就是原始D維向量對應的碼本。用qj表示第j組子向量,用Cj表示其對應學習到的碼本,那麼原始D維向量對應的碼本就是C=C1×C2×…×Cm。用k∗表示子向量的聚類中心點數或者說碼本大小,那麼原始D維向量對應的聚類中心點數或者說碼本大小就是k=(k∗)m。

      示例參考《實例理解product quantization算法》。

相關文章
相關標籤/搜索