Faiss流程與原理分析

1、Faiss簡介

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

 2、Faiss安裝

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

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

      針對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的編譯依賴系統環境。

 3、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算法》。

 

 

https://www.cnblogs.com/yhzhou/p/10568728.html

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