博客 / 詳情

返回

向量數據庫入坑指南:使用 Faiss 實現一個最簡單的向量檢索功能 (二)

上一篇內容中,我們瞭解了什麼是 Faiss,以及如何將文本內容轉換為向量數據。本篇文章中,我們來使用 Faiss 實現向量檢索功能。

使用 Faiss 實現最簡單的向量檢索功能

接下來,我們將使用 Faiss 實現一個小功能,針對哈利波特小説全集內容,接觸向量檢索技術,完成相似內容搜索的功能。與我們使用 “CTRL+F” 或者把數據倒入 MySQL,使用 “%LIKE%” 去進行全文匹配不同,我們的工具性能,將會遠遠高於一般的檢索方式。

為了能夠得到“快到飛起”的執行效率,在使用 Faiss 查詢大量數據之前,我們首先需要和其他追求效率的數據庫軟件一樣,為數據建立索引,我們先來看看最簡單的平面索引:IndexFlatL2

藉助平面索引,完成基礎的相似內容查詢功能

Faiss 中最簡單的索引,便是沒有使用任何花哨技巧(壓縮、分區等)的平面索引:IndexFlatL2。當我們使用這種索引的時候,我們查詢的數據會和索引中所有數據進行距離計算,獲取它們之間的 L2 距離(歐幾里得距離)。因為它會盡職盡責的和所有數據進行比對,所以它是所有索引類型中最慢的一種,但是也是最簡單和最準確的索引類型,同時,因為類型簡單,也是內存佔用量最低的類型。而它採取的遍歷式查找,也會被從業者打趣稱之為“暴力搜索”。

查詢數據都在空間中的進歐式距離計算

在上文中,我們已經準備好了 768 維度的高維向量數據,接下來,我們就用這些數據來建立我們的“第一堆向量數據”的索引:

import faiss

dimension = sentence_embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(sentence_embeddings)

將我們數據的維度信息傳遞給 faiss.IndexFlatL2 函數,建立一個空的索引容器,然後使用 index.add(sentence_embeddings) 將我們在之前處理好的向量數據灌入這個索引容器中。

執行完畢上面的代碼之後,我們執行 index.ntotal 來查看索引的數據是否正確:

# >>> index.ntotal
60028

確認所有數據都被索引之後,我們來寫一段最簡單的程序,來進行查詢,為了演示“相似性檢索”,而不是“關鍵詞匹配”,我們來搜索一個離譜的原文肯定沒有的內容“哈利波特猛然睡醒”:

topK = 5
search = model.encode(["哈利波特猛然睡醒"])
D, I = index.search(search, topK)
df['sentence'].iloc[I[0]]

執行程序之後,我們將能夠看到比較符合預期的神奇結果:

# >>> topK = 5
# >>> search = model.encode(["哈利波特猛然睡醒"])
# >>> D, I = index.search(search, topK)
# >>> df['sentence'].iloc[I[0]]
38216  “我很好先生”哈利結巴的説擦着臉上的汗“我剛才只是睡着了做了個惡夢。
37890  “我很好先生”哈利結巴的説擦着臉上的汗“我剛才只是睡着了做了個惡夢。
8009   那天晚上哈利失眠了。
13996  最後哈利精疲力盡的爬上牀猛拉他的四柱大牀的蚊帳堵住射進來的一道月光翻身躺了進去而且幾乎立即覺...
45306  羅恩立刻就進入了夢鄉但是哈利在上牀之前從行李箱裏翻出了他的那本《高級魔藥製備》。
Name: sentence, dtype: object

雖然沒有完全匹配關鍵詞,但是我們想要的內容還是被程序找到了。我們每天都在使用的搜索引擎背後的眾多技術之一,也包括類似的向量檢索(未來有機會的話,我們聊聊語義搜索)。

進一步瞭解向量檢索的細節

我知道有一些同學,在驚歎上面這加起來不到 10 行的代碼的效果之餘,體驗之後依舊對於“向量”的感知是零。為了讓大家清清楚楚的“入坑”,我們先來展開聊聊上面的程序的含義。

topK = 5
search = model.encode(["哈利波特猛然睡醒"])
D, I = index.search(search, topK)
df['sentence'].iloc[I[0]]

第一行,topK 定義了我們要查找多少條最相似的數據,比如這裏我就只想查詢 5 條數據,避免有人説我水文章字數 :D

第二行,我們通過 model.encode 方法,來將要搜索的內容“哈利波特猛然睡醒”編碼為向量(行內人稱這個過程的黑話為“embedding”)

第三行,則是使用我們在前文中構建的 faiss 索引,來查找上面的“文本內容”,以及找到符合預期數量的條數後就停止查找。

如果我們將 “DI” 打印出來,可以看到類似下面的輸出:

# >>> print (D)
[[206.22675 206.22675 212.70087 219.73259 221.30847]]
# >>> print (I)
[[38216 37890  8009 13996 45306]]

前者指的是“數據置信度/可信度”,而後者指的是我們之前數據準備時灌入的文本數據的具體行數。

最後一行,我們使用 df['sentence'].iloc[I[0]] 來利用 pandas 的 DataFrame.iloc 接口,基於查詢結果的行數,找到對應的文本的原文。如果我們使用上面的 “[38216 37890 8009 13996 45306]” 替換 iloc[I[0]] 中的數據,得到的結果也是一樣的:

# >>> df['sentence'].iloc[[38216, 37890, 8009, 13996, 45306]]
38216  “我很好先生”哈利結巴的説擦着臉上的汗“我剛才只是睡着了做了個惡夢。
37890  “我很好先生”哈利結巴的説擦着臉上的汗“我剛才只是睡着了做了個惡夢。
8009   那天晚上哈利失眠了。
13996  最後哈利精疲力盡的爬上牀猛拉他的四柱大牀的蚊帳堵住射進來的一道月光翻身躺了進去而且幾乎立即覺...
45306  羅恩立刻就進入了夢鄉但是哈利在上牀之前從行李箱裏翻出了他的那本《高級魔藥製備》。
Name: sentence, dtype: object

聊完程序的執行過程,我們來看看程序在“面對”的“向量”數據的真身是什麼樣的,以上文中結果中的第一條為例,我們將 38216 這條在 faiss 索引中存儲的數據進行向量重建:

>>> index.reconstruct(38216)
array([ 5.07521369e-02, -3.93364072e-01, -1.19723105e+00, -3.36433440e-01,
        1.06395984e+00,  3.83257926e-01,  1.24985963e-01,  2.79548287e-01,
       -7.02445269e-01,  7.59876966e-01, -5.09731807e-02, -5.78854322e-01,
       -2.41243094e-01, -6.83130026e-01,  2.50904560e-01, -3.06654796e-02,
        1.09606862e+00,  1.76596511e-02,  4.99801673e-02, -1.00713462e-01,
...
...
        7.15905130e-01,  2.10034728e-01,  2.63317943e-01,  7.68652320e-01],
      dtype=float32)
>>> len(index.reconstruct(38216))
768

“array”中的一堆使用科學計數法表示的數據,就是我們的向量數據,通過 len 方法來獲取數據長度,我們能夠確認數據長度為 768,這個數據長度,就是被我們稱呼為維度的神奇數字(可以發揮想象,一個 768 維的立體世界)。

好啦,對於目前的我們來説,瞭解到向量檢索的過程和向量到這個程度就足夠啦。如果你想對 “FLAT” 索引有更多瞭解,可以移步官方開源項目中的[facebookresearch/faiss/blob/main/benchs/bench\_index\_flat.py]() 文件。

最後

和傳統數據庫類似,當我們的數據量越來越大,用户規模越來越大之後,也會遇到性能問題,那麼當相似度檢索性能不夠時,我們該怎麼辦呢?

下一篇內容中,我們將瞭解如何使用針對向量索引優化,來解決檢索性能問題。


作者:蘇洋

原文:《向量數據庫入坑指南:聊聊來自元宇宙大廠 Meta 的相似度檢索技術 Faiss》

鏈接:https://zhuanlan.zhihu.com/p/...


如果你覺得我們分享的內容還不錯,請不要吝嗇給我們一些鼓勵:點贊、喜歡或者分享給你的小夥伴!

活動信息、技術分享和招聘速遞請關注:https://zilliz.gitee.io/welcome/

如果你對我們的項目感興趣請關注:

用於存儲向量並創建索引的數據庫 Milvus

用於構建模型推理流水線的框架 Towhee

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.