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"
...表示首先要對資料集進行聚類處理。聚類後,"Flat"将向量組織到相應桶裡,過程不存在向量壓縮,占用記憶體數和原始資料大小相同。可以修改nproce參數設定探測桶的數量,權衡檢索速度和準确率。
Supported on GPU: yes (but see below, the clustering method must be supported as well)
3、比較重要,使用 "PCARx,...,SQ8"
"PCARx,...,SQ8"
如果存儲整個向量太過昂貴,這類索引會執行兩類操作:
- 使用PCA進行降維,x為降維後的向量次元
- 使用标量量化,原向量每個次元都會映射到1Byte
是以該索引輸出向量占用x Bytes的記憶體。
Supported on GPU: no
4、非常重要,使用 "OPQx_y,...,PQx"
"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,..."
"...,IVFx,..."
x為聚類中心點的數量,取值大小在【4*sqrt(N), 16*sqrt(N)】之間,其中N是資料集的大小。該索引隻是使用k-means做向量聚類,需要使用30*x到256*x的向量做訓練,越多越好。引支援使用GPU
1M - 10M: "...,IMI2x10,..."
"...,IMI2x10,..."
IMI也是使用K-means方法,生成2^10個中心點,不同的是IMI分别對向量的前半部分和後半部分進行聚類處理。這樣生成桶的數量變成2^(2*10)個。訓練時需要大概64*2^10向量。引不支援使用GPU
10M - 100M: "...,IMI2x12,..."
"...,IMI2x12,..."
和上面類似,隻是将10變成12。
100M - 1B: "...,IMI2x14,..."
"...,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
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