faiss(1):簡介 安裝 與 原理

1. 簡介

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

參考論文:https://arxiv.org/pdf/1702.08734.pdf

源碼地址:https://github.com/huaxz1986/faiss

2. Anaconda安裝Faiss

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

  • Ubuntu安裝anaconda
#安裝anaconda包
brew cask install anaconda
#conda加入環境變量
export PATH=/usr/local/anaconda3/bin:"$PATH"
  • anaconda安裝Faiss
#更新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的編譯依賴系統環境。

3. Faiss原理

  • 核心算法的改進

Faiss對一些基礎的算法提供了非常高效的改進:

  1. 聚類Faiss提供了一個高效的k-means實現
  2. PCA降維算法
  3. PQ(ProductQuantizer)編碼/解碼
  • Faiss功能流程說明

faiss的主要功能就是相似度搜索。如下圖所示,以圖片搜索爲例,所謂相似度搜索,便是在給定的一堆圖片中,尋找出我指定的目標最像的K張圖片,也簡稱爲KNN(K近鄰)問題。當然,隨着word2vec、doc2vec、img2vec、video2vec的出現,向量化描述已經成爲最大的趨勢。這也是Faiss最受歡迎的主要原因,大大提升了Query的效率。

爲了解決KNN問題,在工程上需要實現對已有圖庫的存儲,當用戶指定檢索圖片後,需要知道如何從存儲的圖片庫中找到最相似的K張圖片。基於此,可以推測Faiss在應用場景中會逐漸添加數據庫查詢的一些功能,例如增改刪查,從上圖來看,Faiss本質上是一個向量(矢量)數據庫。

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

3.1 組件原理分析

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

  • Index原理

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

Faiss還提供了針對不同場景下應用對Index的封裝類:

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

索引的創建提供了工廠方法,可以通過字符串靈活的創建不同的索引。

// 使用PCA算法將向量降維到32維, 劃分成100個nprobe (搜索空間), 通過PQ算法將每個向量壓縮成8bit
index = faiss.index_factory(d,"PCA32,IVF100,PQ8 ")
  • 索引說明

索引id是基於PQ量化及Faiss創建不同的索引時選擇的量化器而來,可能會稍有偏差,不影響對Faiss的使用操作。      

默認情況,Faiss會爲每個輸入的向量記錄一個次序id,也可以爲向量指定任意我們需要的id。部分索引類有add_with_ids方法,可以爲每個向量對應一個64-bit的id,搜索的時候返回此id。

import numpy as np
import faiss

# 構造數據
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距離搜索
#此處索引是按照默認方式,即faiss給的次序id爲主
index = faiss.IndexFlatL2(d)  
# IndexFlatL2不支持add_with_ids方法,需要藉助IndexIDMap進行映射
# id設定爲6位數整數,默認id從0開始,將其設置從100000開始
# ids = np.arange(100000, 200000)  
# 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)                        # 相似度矩陣
  • 索引選擇
  1. 關注返回精度,可以使用IndexFlatL2,該索引能確保返回精確結果。一般將其作爲baseline與其他索引方式對比,以便在精度和時間開銷之間做權衡。不支持add_with_ids,如果需要,可以用“IDMap”給予任意定義id。

  2. 關注內存開銷,可以使用“..., Flat“的索引,"..."是聚類操作,聚類之後將每個向量映射到相應的bucket。該索引類型並不會保存壓縮之後的數據,而是保存原始數據,所以內存開銷與原始數據一致。通過nprobe參數控制速度/精度。對內存開銷比較關心的話,可以在聚類的基礎上使用PQ量化進行處理。

  • 檢索數據恢復

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

  1. 條條恢復。 給定id,可以使用reconstruct進行單條取出數據;

  2. 批量恢復。使用reconstruct_n方法從index中回批量復出原始向量

上述方法支持IndexFlat, IndexIVFFlat, IndexIVFPQ等幾類索引類型。

  • PCA降維

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

爲了使得在整個降維的過程中信息丟失最少,我們需要對待轉換圖片進行分析計算得到相應的轉換矩陣(p*q)。也就是說這個降維中乘以的轉換矩陣是與待轉換圖片息息相關的。

對於Faiss,假設期望使用PCA預處理來減少Index中的存儲空間,那在整個處理流程中,除了輸入搜索圖庫外,我必須多輸入一個轉換矩陣,但是這個轉換矩陣是與圖庫息息相關的,是可以由圖庫數據計算出來的。如果把這個轉換矩陣看成一個參數的話,可以發現,在Faiss的一些預處理中,我們會引入一些參數,這些參數又無法一開始由人工來指定,只能通過訓練出來,所以Index中需要有這樣的一個train() 函數來爲這種參數的訓練提供輸入訓練樣本的接口。

  • Product quantization(乘積量化PQ)

此處Facebook引用了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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章