超平面多維近似向量查找工具annoy使用總結
需求:我有800萬的中文詞向量,我想要查詢其中任意一個向量對應的最鄰近的向量是哪些。通常情況下如果向量集比較小的話,幾十萬個向量(幾個G這種),我們都可以用gensim的word2vec來查找,但是880萬有16個G,加到內存中就爆炸了,而且gensim中的查找屬於暴力搜索,即全都遍歷比較余弦相似度來進行查找,因此幾百萬的詞向量查找起來就很慢了。這裏我需要用更快速的工具來進行查找,找到了兩個工具,一個是facebook的faiss包另一個是annoy包。Faiss只能部署在linux,而且看著好復雜,各種索引類型啥的,估計夠我研究一陣,索性使用了annoy包;
關於annoy包的使用方法參考這兩個網址足夠:
1、https://github.com/spotify/annoy
2、https://markroxor.github.io/gensim/static/notebooks/annoytutorial.html
1是官方文檔,寫的非常簡單,但是我剛開始沒有認真看,所以走了很多彎路;2是一個notebook實踐案例,基於gensim的,我就是被這個版本給坑了。。。這裏面有很多說道,首先我先說一下代碼邏輯,其實很簡單,首先是讀取你的帶 word和vec的txt向量文件作為model:
1 model = gensim.models.KeyedVectors.load_word2vec_format(‘D:\\describe\\dic\\synonyms_vector.txt‘,binary=False,unicode_errors=‘ignore‘) 2 annoy_index = AnnoyIndexer(model, 100) # 生成索引 3 fname = ‘synonyms_txt_index‘ 4 annoy_index.save(fname) # 將索引文件保存到硬盤
代碼說明
這四行代碼目的是
1、加載model
2、對model進行聚類計算
3、建立一個二叉樹集合的索引(樹的數量為100),
4、將索引保存到硬盤
接下來我們就可以根據建立的這個索引來查找近似向量了:
1 annoy_index2 = AnnoyIndexer() # 初始化索引2 annoy_index2.load(fname) 3 annoy_index2.model = model
這三行就是來加載索引,值得註意的是這裏的model就是之前最開始加載的txt文件對應的model
接下來問題來了,執行
1 word = ‘人民’ 2 vector1 = model[word] 3 approximate_neighbors = model.most_similar([vector1], topn=30, indexer=annoy_index2)
這裏想要查詢“人民”對應的前30個相近詞,通過加載索引來查詢,可是最終的查詢速度跟我沒建立索引之前的暴力搜索(即word2vec自帶搜索)是一樣的,但是如果我在這個加載索引之前先進行一個詞的暴力搜索,然後再對其他的詞進行加載索引搜索,速度就會快出很多倍,這讓我百思不得其解,最後沒辦法我就先用暴力搜索先搜索一個詞,然後剩下的詞都用annoy搜索,這樣速度還是很快的;
但是我還是想弄明白到底怎麽回事,於是我去官網問作者,作者就說了一句,你需要進行整數映射,(而且應該是非負整數),靠!!!其實官網寫的明明白白:
a.add_item(i, v) adds item i (any nonnegative integer) with vector v. Note that it will allocate memory for max(i)+1 items.
也就是說我的txt文件格式需要如下這種格式:
1 vecor
2 vecor
而不是開頭是漢語單詞以及對應的vector,最後再做一個integer到word的映射字典即可;
接下來我對作者給出的github上的版本進行了驗證,代碼如下:
from annoy import AnnoyIndex import random f = 100 t = AnnoyIndex(f) dict = {} with open(‘C:\\Users\Administrator\Desktop\synonyms\\synonyms_vector.txt‘,‘r‘,encoding=‘utf-8‘) as f: count = 0 for line in f: result = line.split() if len(result)>10: count+=1 word = result[0] dict[count] = word vector = list(map(eval, result[1:])) # 需要將txt中的str格式vec轉化為float格式 t.add_item(count, vector) t.build(10) t.save(‘C:\\Users\Administrator\Desktop\synonyms\\test.ann‘) u = AnnoyIndex(100) u.load(‘C:\\Users\Administrator\Desktop\synonyms\\test.ann‘) simi_id = u.get_nns_by_item(880, 20,include_distances=True) id = simi_id[0] score = simi_id[1] # print(simi_id) # for i,j in zip(id,score): # print(dict[i]) # print(0.5*(abs(1-j))+0.5) result =[(dict[i],0.5*(abs(1-j))+0.5) for i,j in zip(id,score)] 輸出結果(result): [(‘投資‘, 1.0), (‘融資‘, 0.6934992074966431), (‘投資者‘, 0.6180032193660736), (‘投資額‘, 0.6166684031486511), (‘房地產‘, 0.6127455532550812), (‘外資‘, 0.6104367673397064)]
這裏面我需要指出幾點需要註意的地方:
1、需要將txt中的str格式vec轉化為float格式,否則會報錯;
2、我建立了一個字典映射,這樣能夠最後從查詢到的近似向量對應的id值查詢到映射的詞;
3、最後那個0.5*(abs(1-j))+0.5是余弦相似度歸一化的計算公式,作者程序中建立的距離索引以及最後查找返回值都是默認angular模式(即j的值),也即是余弦相似度,即angular=1-cosin,且其值域為[0,2](因為cosin值域為[-1,1])。但是我們通常只需要求取cosin的絕對值,即其值域應該位於[0,1]。所以我先用1-angular 獲取cosin 然後再求取絕對值,最後再進行歸一化((1+余弦相似度)/2)即可。
4、對於上面代碼求取結果我和word2vec的most_similar對比了一下,近似度基本一致,前三位精度完全一致,說明最後的余弦相似度求取向量相似度就是按照我上面說的方法來進行的;
最後附上余弦相似度計算方法參考網址:余弦相似度python實現
PS:關於annoy加載索引還需要註意一點,索引文件路徑必須是英文路徑,否則程序就會提示查找不到文件,望註意!!!
超平面多維近似向量查找工具annoy使用總結