天天看點

faiss技術積累預處理和後期處理  Pre and post processingindex_factory如何選擇合适的索引常見問題彙總

Faiss教程:入門             https://www.cnblogs.com/houkai/p/9316129.html

Faiss教程:基礎             https://www.cnblogs.com/houkai/p/9316136.html

Faiss教程:GPU             https://www.cnblogs.com/houkai/p/9316176.html

Faiss教程:索引(1)         https://www.cnblogs.com/houkai/p/9316155.html

源自:https://waltyou.github.io/Faiss-In-Project/

1、建立一個IDMap的索引   “IDMap,Flat”

2、使用gpu索引 

#使用單個gpu
res = faiss.StandardGpuResources()  # use a single GPU
# 建立一個cpu版的Flat索引
index_flat = faiss.IndexFlatL2(d)
#将cpu版的索引轉換成gpu版  第二個參數用于指定使用那塊GPU裝置
gpu_index_flat = faiss.index_cpu_to_gpu(res, 0, index_flat)

#添加資料方法和cpu相同
pgu_index_flat.add(xb)
print gpu_index_flat.ntotal

#搜尋資料
k = 4 
D, I = gpu_index_flat.search(xq, k)
print I[:5]
print I[-5:]
           

注意:一個gpu可以被多gpu索引共享,隻要它不發生并發請求。

3、使用多個GPU

ngpus = faiss.get_num_gpus()

print "number of GPUs", ngpus

cpu_index = faiss.IndexFlatL2(d)
gpu_index = faiss.index_cpu_to_all_gpus(cpu_index)

gpu_index.add(xb)
print gpu_index.ntotal

k = 4
D, I = gpu_index.search(xq, k)
print I[:5]
print I[-5:]
           
# prepare index
dimensions = 128
INDEX_KEY = "IDMap,Flat"
index = faiss.index_factory(dimensions, INDEX_KEY)
if USE_GPU:
    res = faiss.StandardGpuResources()
    index = faiss.index_cpu_to_gpu(res, 0, index)

id = 0
index_dict = {}
for file_name in image_list:
    ret, sift_feature = calc_sift(sift, file_name)
    if ret == 0 and sift_feature.any():
        # record id and path
        index_dict.update({id: (file_name, sift_feature)})
        ids_list = np.linspace(ids_count, ids_count, num=sift_feature.shape[0], dtype="int64")
        id += 1
        index.add_with_ids(sift_feature, ids_list)
           

預處理和後期處理  Pre and post processing

https://github.com/facebookresearch/faiss/wiki/Pre--and-post-processing

自定義ID

資料的預處理

index_factory

index_factory通過字元串來建立索引,字元串包括三部分:預處理、倒排、編碼。

預處理支援:

  • PCA:PCA64表示通過PCA降維到64維(PCAMatrix實作);PCAR64表示PCA後添加一個随機旋轉。
  • OPQ:OPQ16表示為資料集進行16位元組編碼進行預處理(OPQMatrix實作),對PQ索引很有效但是訓練時也會慢一些。

倒排支援:

  • IVF:IVF4096表示使用粗量化器IndexFlatL2将資料分為4096份
  • IMI:IMI2x8表示通過Mutil-index使用2x8個bits(MultiIndexQuantizer)建立2^(2*8)份的反向索引。
  • IDMap:如果不使用倒排但需要add_with_ids,可以通過IndexIDMap來添加id

編碼支援:

  • Flat:存儲原始向量,通過IndexFlat或IndexIVFFlat實作
  • PQ:PQ16使用16個位元組編碼向量,通過IndexPQ或IndexIVFPQ實作
  • PQ8+16:表示通過8位元組來進行PQ,16個位元組對第一級别量化的誤差再做PQ,通過IndexIVFPQR實作

如:

index = index_factory(128, "OPQ16_64,IMI2x8,PQ8+16"): 處理128維的向量,使用OPQ來預處理資料,16是OPQ内部處理的blocks大小,64為OPQ後的輸出次元;使用multi-index建立65536(2^16)和倒排清單;編碼采用8位元組PQ和16位元組refine的Re-rank方案。

OPQ是非常有效的,除非原始資料就具有block-wise的結構如SIFT。

如何選擇合适的索引

一 、是否需要确切結果

是:“Flat”

隻有IndexFlatL2或者IndexFlatIP能夠保證生成确切的結果。通常情況下它是用來生成其他索引的基線的。"Flat"不會壓縮向量,也不會增加額外的開銷。不支援add_with_ids功能,隻是支援順序添加。如果需要使用add_with_ids,可以使用“IDMap, Flat”。Flat的索引不需要訓練,也沒有參數。

二、是否關注記憶體的占用

1、不關心,使用“HNSWx”

如果資料集很小或者記憶體很大,基于圖的方法HNSW是最好的選擇,這種索引方法即快又準。x的取值在[0,64]之間,表示每個向量的連接配接數,x的值越大結果越準确,但是占用記憶體越多。通過設定efSearch參數可以權衡速度和準确度。每個向量會占用記憶體為(d*4 + x*2*4) bytes的記憶體

Supported on GPU: no

2、有點關心,使用

"...,Flat"

...表示首先要對資料集進行聚類處理。聚類後,"Flat"将向量組織到相應桶裡,過程不存在向量壓縮,占用記憶體數和原始資料大小相同。可以修改nproce參數設定探測桶的數量,權衡檢索速度和準确率。

Supported on GPU: yes (but see below, the clustering method must be supported as well)

3、比較重要,使用

"PCARx,...,SQ8"

如果存儲整個向量太過昂貴,這類索引會執行兩類操作:

  • 使用PCA進行降維,x為降維後的向量次元
  • 使用标量量化,原向量每個次元都會映射到1Byte

是以該索引輸出向量占用x Bytes的記憶體。

Supported on GPU: no

4、非常重要,使用

"OPQx_y,...,PQx"

PQx是使用乘積量化器壓縮向量,輸出x Byte的編碼,通常x值小于64,x值越大越準确,檢索速度越快。

 OPQ是對向量進行線性變換預處理,這樣會更容易壓縮,y是輸出次元,要求如下:

  • y必須是x的倍數
  • y最好要小于輸出向量的次元d
  • y最好小于4*x

Supported on GPU: yes (note: the OPQ transform is done in software, but it is not performance critical)

三、需要索引資料集有多大?

這個問題的答案将會決定選擇的聚類算法(即如何設定上面...部分)。資料集會被聚類成桶,搜尋的時候隻有通路一部分的桶。通常情況隻會對資料集的代表性樣本進行聚類,也就是資料的抽樣。這裡會說明抽樣本的最佳大小。

1、少于1M的向量 

"...,IVFx,..."

x為聚類中心點的數量,取值大小在【4*sqrt(N), 16*sqrt(N)】之間,其中N是資料集的大小。該索引隻是使用k-means做向量聚類,需要使用30*x到256*x的向量做訓練,越多越好。引支援使用GPU

1M - 10M: 

"...,IMI2x10,..."

IMI也是使用K-means方法,生成2^10個中心點,不同的是IMI分别對向量的前半部分和後半部分進行聚類處理。這樣生成桶的數量變成2^(2*10)個。訓練時需要大概64*2^10向量。引不支援使用GPU

10M - 100M: 

"...,IMI2x12,..."

和上面類似,隻是将10變成12。

100M - 1B: 

"...,IMI2x14,..."

和上面類似,隻是将10變成14

常見問題彙總

1、IndexIVFFlat或者

IndexIVFScalarQuantizer索引,有修改探測分區數量的api,

index.setNumProbes(128)
nprob = index.getNumProbes(128)
           

而對于通過factory或者faiss.load_index()生成的索引,例如‘ 

OPQ64_256,IVF4096,PQ64

’,如何修改相同的屬性?

答案: 可以通過如下代碼:

nprob = 10

#cpu搜因
faiss.ParameterSpace().set_index_parameter(index, "nprobe", nprob)

#gpu索引
faiss.GpuParameterSpace().set_index_parameter(gpu_index, "nprobe", nprob)



#或者
faiss.downcast_index(index.index).nprobe = 123


           

2、如何儲存和加載索引?

faiss使用write_index/read_index API儲存和加載索引

#建立索引
d = 1024
index = faiss.index_factory(d, "IVF1024,SQ8")

#儲存索引
faiss.write_index(index, 'IVF1024_SQ8.index')

#加載索引
new_index = faiss.read_index('IVF1024_SQ8.index')
           

如果使用的GPU索引,會稍微麻煩一點:

#建立索引
d= 1024
index = faiss.index_factory(d, "IVF1024,SQ8")

#transfer到GPU裝置上
gpu_id = 0
res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, gpu_id, index)

#訓練模型
.....

#搜尋
....

#儲存模型模型前,需要首先transfer到cpu上
index_cpu = faiss.index_gpu_to_cpu(gpu_index)
faiss.write_index(index_cpu, 'IVF1024_SQ8.index')



#加載模型
new_index = faiss.read_index('IVF1024_SQ8.index')

#如果需要在gpu上使用,需要再次transfer到GPU裝置上
res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, gpu_id, index)

#搜尋
....
           

 3、StandardGpuResources預設使用18%的GPU顯存。如何設定使用顯存大小?

res = faiss.StandardGpuResources()
res.setTempMemory(512 * 1024 * 1024)
           

4、使用omp的多線程

在搜尋之前,添加以下代碼

faiss.omp_set_num_threads(8)
           

并行後的加速效果,由Amdahl's law(阿姆達爾定律)決定,參考連結:

https://zh.wikipedia.org/wiki/%E9%98%BF%E5%A7%86%E8%BE%BE%E5%B0%94%E5%AE%9A%E5%BE%8B

faiss技術積累預處理和後期處理  Pre and post processingindex_factory如何選擇合适的索引常見問題彙總

5、PQ編碼會将子向量編碼成多個位元組?

需要分别說明:

  • ProductQuantizer量化器支援長度nbits=1和長度nbits=16之間的任何編碼,當編碼長度是1-8個bits時将使用1個位元組編碼,當編碼長度為9-16bits時會使用2個位元組,是以如果nbits是8或者16時,記憶體空間是存在浪費的
  • IndexPQ和ProductQuantizer支援相同
  • IndexIVFPQ僅支援8位的量化編碼
  • MultiIndexQuantizer支援最多16位的編碼

'IVF1000,PQ8', 其中PQ8表示原始向量會被壓縮成8個位元組。對于PQx的寫法,生成的子向量都是會被壓縮成8bits, 即一個位元組的。

6、d = 1000 ,使用索引 'IVF1000,PQ16'  提示如下錯誤  “RuntimeError: Error in void faiss::ProductQuantizer::set_derived_values() at ProductQuantizer.cpp:164: Error: 'd % M == 0' failed” ,原因是什麼?

貌似要求輸出向量M值必須能夠被d整除

7、d= 1000, 使用索引“"IVF1024,PQ250"”,提示如下錯誤?

RuntimeError: Error in void faiss::gpu::GpuIndexIVFPQ::verifySettings_() const at GpuIndexIVFPQ.cu:438: Error: 'IVFPQ::isSupportedPQCodeLength(subQuantizers_)' failed: Number of bytes per encoded vector / sub-quantizers (250) is not supported

貌似要求原向量占用位元組數(1000*4) /  生成向量位元組數(250) = 16,這個比值faiss不支援,為什麼呢?

8、"OPQ16_512,IVF1024,PQ64"  提示錯誤

RuntimeError: Error in void faiss::gpu::GpuIndexIVFPQ::verifySettings_() const at GpuIndexIVFPQ.cu:462: Error: 'requiredSmemSize <= getMaxSharedMemPerBlock(device_)' failed: Device 0 has 49152 bytes of shared memory, while 8 bits per code and 64 sub-quantizers requires 65536 bytes. Consider useFloat16LookupTables and/or reduce parameters

繼續閱讀