坑挖太多了,已經沒時間填。那就再挖一個吧

faiss是為稠密向量提供高效相似度搜索和聚類的框架。由Facebook AI Research研發。 具有以下特性。

1、提供多種檢索方法

2、速度快

3、可存在記憶體和磁碟中

4、C++實現,提供Python封裝呼叫。

5、大部分演算法支援GPU實現

#簡單的cosine_similarity的計算

import

faiss

from

faiss

import

normalize_L2

import

numpy

as

np

def

cosine_similar

():

‘’‘

cosine_similarity

use

:return:

’‘’

d

=

64

# dimension

nb

=

105

# database size

#主要是為了測試不是歸一化的vector

training_vectors

=

np

random

random

((

nb

d

))

astype

‘float32’

*

10

print

‘just compare with skearn’

from

sklearn。metrics。pairwise

import

cosine_similarity

#主要是為了與sklearn 比較結果

ag

=

cosine_similarity

training_vectors

fe

=

np

sort

ag

axis

=

1

print

‘normalize_L2’

normalize_L2

training_vectors

print

‘IndexFlatIP’

index

=

faiss

IndexFlatIP

d

index

train

training_vectors

print

index

print

‘train’

print

index

is_trained

print

‘add’

print

index

index

add

training_vectors

print

‘search’

D

I

=

index

search

training_vectors

[:

100

],

5

print

I

[:

5

])

# 表示最相近的前5個的index

print

D

[:

5

])

# 表示最相近的前5個的相似度的值

cosine_similar

()

FAISS 用法

FAISS 用法

結論

:sklearn 和 FAISS的IndexFlatIP模式的計算結果是一模一樣的,IndexFlatIP這個模式就是精確的暴力的計算模式。

效率上,實際應用,embedding後向量計算相似度,在200w條的資料中,計算每一條前100最相似的鄰居,全量計算肯定是不合適的,如果全量計算要等10h+才能出最後的結果,所以是分批計算,每次只計算若干條,經試驗:計算1W條160s左右,計算2W條329s,計算1k條24s左右,經過測算1w條算一次是比較合適的,具體時間如下圖:

FAISS 用法

上面暴力算是精度高了,但是search time 也邊長了,因此引入倒排版的相似度計算:

建立IndexIVFFlat時需要指定一個其他的索引作為量化器(quantizer)來計算距離或相似度。

這裡同使用

IndexFlatL2

對比,在

add

方法之前需要先訓練。

下面簡述示例中的幾個引數。

faiss。METRIC_L2

: faiss定義了兩種衡量相似度的方法(metrics),分別為

faiss。METRIC_L2

faiss。METRIC_INNER_PRODUCT

。一個是歐式距離,一個是向量內積(餘弦相似度)。

nlist

:聚類中心的個數

k

:查詢最相似的k個向量

index。nprobe

:查詢聚類中心的個數,預設為1個

#加速版的cosine_similarity的計算

def IndexIVFFlat():

d = 64 # dimension

nb = 100005 # database size

np。random。seed(1234) # make reproducible

training_vectors= np。random。random((nb, d))。astype(‘float32’)*10

normalize_L2(training_vectors)

nlist = 1000 # 聚類中心的個數

k = 50 #鄰居個數

quantizer = faiss。IndexFlatIP(d) # the other index,需要以其他index作為基礎

index = faiss。IndexIVFFlat(quantizer, d, nlist, faiss。METRIC_INNER_PRODUCT)

# by default it performs inner-product search

assert not index。is_trained

index。train(training_vectors)

assert index。is_trained

index。nprobe = 300 # default nprobe is 1, try a few more

index。add(training_vectors) # add may be a bit slower as well

t1=time。time()

D, I = index。search(training_vectors[:100], k) # actual search

t2 = time。time()

print(‘faiss kmeans result times {}’。format(t2-t1))

# print(D[:5]) # neighbors of the 5 first queries

print(I[:5])

IndexIVFFlat()

IndexIVFFlat這個模式就是Inverted file with exact post-verification,翻譯過來叫倒排檔案,其實是使用K-means建立聚類中心,然後透過查詢最近的聚類中心,然後比較聚類中的所有向量得到相似的向量。

這個模式的Python的 寫法不對的如下:

def

make_index

():

index_sub

=

faiss

IndexFlatL2

10

index

=

faiss

IndexIDMap

index_sub

return

index

# CRASH!

正確的寫法:

def make_index():

index_sub = faiss。IndexFlatL2(10)

index = faiss。IndexIDMap(index_sub)

index_sub。this。disown()

index。own_fields = True

return index

具體原因facebookresearch/faiss

如下2個圖是IndexFlatIP模式的計算結果search time 0。9045s

FAISS 用法

FAISS 用法

暴力算的,結果是準確可靠的

如下2個圖是IndexIVFFlat模式的計算結果,search time 0。0818s

FAISS 用法

FAISS 用法

暴力模式下的第4,7。。。個鄰居(第一個鄰居是自己本身)在倒排模式沒了,速度是快了,但是找最相鄰的鄰居有丟三落四的情況。看相似度結果,能找到的鄰居,相似度都與暴力模式是相同,所以相似度結果是準確的。

對比與結論

:1。找最相鄰的鄰居有丟三落四的情況,2。計算的相似度結果是準確的(不會出現說原來排名靠後的鄰居,現在變成排名靠前)。

效率上,

一些引數的敏感性:

nprobe越大,召回效果越好。

C=10 to give an idea ncentroids = C * sqrt (n)

index。nprobe 改成3,search time 0。0404949s,結果有點慘不忍睹了,第二居的相似度到了0。8851595,0。9以上的直接丟了。

FAISS 用法

暴力模式下的,和前面的圖是一模一樣的,只是複製了一份下來

FAISS 用法

index。nprobe =3的對比暴力模式的,直接丟棄很多

nlist 改成 2500 ,index。nprobe 還是300,search 時間0。0766s

FAISS 用法

這個是nlist 改成 2500

第3個鄰居丟了,往後也丟了好長一段鄰居,難道這nlist ,index。nprobe這兩個有個合適的搭配?

改成nlist500 ,index。nprobe 還是300,search 時間0。107s,結果比最開始的nlist=1000 ,index。nprobe =300還要好,但是到了第二行還是可以看到丟了幾個

FAISS 用法

結果好很多

nlist改成2500 ,index。nprobe改成1500,search 時間0。106s,這種改法比前面的結果還要好,全對。

FAISS 用法

資料總結:

base line :nlist=1000 ,index。nprobe=300,search =0。0818s,

nlist=500 ,index。nprobe=300,search =0。107s,召回效果比baseline好,同時search time 增加。

nlist=1000 ,index。nprobe=3,search time 0。0404949s,結果有點慘不忍睹了,同時search time 下降。

nlist=2500 ,index。nprobe=300,search =0。0766s,召回效果比baseline差好多,同時search time下降。

nlist=2500 ,index。nprobe=1500,search =0。106s,召回效果比baseline好非常多,同時search time上升,也比nlist=500 ,index。nprobe=300召回效果好,時間短。+

結論:

1。index。nprobe 越大,search time 越長,召回效果越好。

2。nlist=2500,不見得越大越好,需要與nprobe 配合,這兩個引數同時大才有可能做到好效果。

3。不管哪種倒排的時間,在search 階段都是比暴力求解快很多,0。9s與0。1s級別的差距。

以上的時間都沒有包括train的時間。也暫時沒有做記憶體使用的比較。

IndexIVFPQ

這個是少記憶體條件下使用的。

歸一化後計算的歐式距離是關於餘弦相似的單調函式,可以認為歸一化後,餘弦相似與歐式距離效果是一致的(歐式距離越小等價於餘弦相似度越大),具體證明如下(一定是L2歸一化後)。

因此可以將

求餘弦相似轉為求歐式距離

,餘弦相似的計算複雜度過高,轉為求歐式距離後,可以藉助KDTree(KNN演算法用到)或者BallTree(對高維向量友好)來降低複雜度。

FAISS 用法