我們日常使用的各種 APP 中的許多功能,都離不開相似度檢索技術。比如一個接一個的新聞和視頻推薦、各種常見的對話機器人、保護我們日常賬號安全的風控系統、能夠用哼唱來找到歌曲的聽歌識曲,甚至就連外賣配送的最佳路線選擇也都有着它的身影。
相信很多同學是第一次聽説它,或者只知道它的大名,而不知該如何使用它。本篇文章,我們就來聊聊 faiss,分享這個“黑科技”是如何發揮神奇的“魔法”的。
寫在前面
faiss 是相似度檢索方案中的佼佼者,是來自 Meta AI(原 Facebook Research)的開源項目,也是目前最流行的、效率比較高的相似度檢索方案之一。雖然它和相似度檢索這門技術頗受歡迎,在出現在了各種我們所熟知的“大廠”應用的功能中,但畢竟屬於小眾場景,有着不低的掌握門檻和複雜性。
所以,不要想着一口氣就完全掌握它,咱們一步一步來。
當然,如果你實在懶得了解,希望能夠和寫簡單的 Web 項目一樣,寫幾行 CRUD 就能夠完成高效的向量檢索功能,可以試試啓動一個 Milvus 實例。或者更懶一些的話,可以試着使用 Milvus 的 Cloud 服務,來完成高性能的向量檢索。
瞭解 Faiss 的工作機制和適用場景
在正式使用 faiss 之前,我們需要先了解它的工作機制。
當我們把通過模型或者 AI 應用處理好的數據餵給它之後(“一堆特徵向量”),它會根據一些固定的套路,例如像傳統數據庫進行查詢優化加速那樣,為這些數據建立索引。避免我們進行數據查詢的時候,需要笨拙的在海量數據中進行一一比對,這就是它能夠實現“高性能向量檢索”的秘密。
我們熟知的互聯網企業中比較賺錢的“搜廣推”(搜索、廣告、推薦)業務中,會使用它解決這些場景下的向量召回工作。在這些場景下,系統需要根據多個維度進行數據關聯計算,因為實際業務場景中數據量非常大,很容易形成類似“笛卡爾積”這種變態的結果,即使減少維度數量,進行循環遍歷,來獲取某幾個向量的相似度計算,在海量數據的場景下也是不現實的。
而 Faiss 就是解決這類海量數據場景下,想要快速得到和查詢內容相似結果(Top K 個相似結果),為數不多的靠譜方案之一。
和我們在常見數據庫裏指定字段類型一樣, Faiss 也能夠指定數據類型,比如 IndexFlatL2、IndexHNSW、IndexIVF等二十來種類型,雖然類型名稱看起來比較怪,和傳統的字符串、數字、日期等數據看起來不大一樣,但這些場景將能夠幫助我們在不同的數據規模、業務場景下,帶來出乎意料的高性能數據檢索能力。反過來説,在不同的業務場景、不同數據量級、不同索引類型和參數大小的情況下,我們的應用性能指標也會存在非常大的差異,如何選擇合適的索引,也是一門學問。(下文會提到)
除了支持豐富的索引類型之外,faiss 還能夠運行在 CPU 和 GPU 兩種環境中,同時可以使用 C++ 或者 Python 進行調用,也有開發者做了 Go-Faiss ,來滿足 Golang 場景下的 faiss 使用。
對 Faiss 有了初步認識之後,我們來進行 Faiss 使用的前置準備。
環境準備
為了儘可能減少不必要的問題,本篇文章中,我們使用 Linux 操作系統作為 faiss 的基礎環境,同時使用 Python 作為和 faiss 交互的方式。
在之前的文章中,我介紹過如何準備 Linux 環境 和 Python 環境,如果你是 Linux 系統新手,可以閲讀這篇文章,從零到一完成系統環境的準備:《在筆記本上搭建高性價比的 Linux 學習環境:基礎篇》;如果你不熟悉 Python 的環境配置,建議閲讀這篇文章《用讓新海誠本人驚訝的 AI 模型製作屬於你的動漫視頻》,參考“準備工作”部分,完成 “Conda” 的安裝配置。
在一切準備就緒之後,我們可以根據自己的設備狀況,選擇使用 CPU 版本的 faiss 還是 GPU 版本的 faiss,以及選擇是否要指定搭配固定 CUDA 版本使用:
# 創建一個乾淨的環境
conda create -n faiss -y
# 激活這個環境
conda activate faiss
# 安裝 CPU 版本
conda install -c pytorch python=3.8 faiss-cpu -y
# 或者,安裝 GPU 版本
conda install -c pytorch python=3.8 faiss-gpu -y
# 或者,搭配指定 CUDA 版本使用
conda install -c pytorch python=3.8 faiss-gpu cudatoolkit=10.2 -y
在配置安裝的時候,推薦使用 3.8 版本的 Python,避免不必要的兼容性問題。在準備好環境之後,我們就能夠正式進入神奇的向量數據世界啦。
構建向量數據
前文提到了,適合 faiss 施展拳腳的地方是向量數據的世界,所以,需要先進行向量數據的構建準備。
本文作為入門篇,就先不聊如何對聲音(音頻)、電影(視頻)、指紋和人臉(圖片)等數據進行向量數據構建啦。我們從最簡單的文本數據上手,實現一個“基於向量檢索技術的文本搜索功能”。接下來,我將以我比較喜歡的小説 “哈利波特”為例,你可以根據自己的喜好調整要使用的文本數據。從網絡上下載好要處理為向量的文本數據(txt 文檔)。
簡單針對數據進行 ETL
我這裏的原始 TXT 文檔尺寸是 3 MB 大小,為了減少不必要的向量轉化計算量,我們先對內容進行必要的預處理(數據的 ETL 過程),去掉不必要的重複內容,空行等:
cat /Users/soulteary/《哈利波特》.txt | tr -d ' ' | sed '/^[[:space:]]*$/d' > data.txt
打開文本仔細觀察,數據中有一些行中的文本數據格外長,是由好多個句子組成的,會對我們的向量特徵計算、以及精準定位檢索結果造成影響的。所以,我們還需要進行進一步的內容調整,將多個長句拆成每行一個的短句子。
為了更好的解決句子換行的問題,以及避免將一段人物對話中的多個句子拆散到多行,我們可以使用一段簡單的 Node.js 腳本來處理數據:
const { readFileSync, writeFileSync } = require("fs");
const raw = readFileSync("./hp.txt", "utf-8")
.split("\n")
.map((line) => line.replace(/。/g, "。\n").split("\n"))
.flat()
.join("\n")
.replace(/“([\S]+?)”/g, (match) => match.replace(/\n/g, ""))
.replace(/“([\S\r\n]+?)”/g, (match) => match.replace(/[\r\n]/g, ""))
.split("\n")
.map((line) => line.replace(/s/g, "").trim().replace(/s/g, "—"))
.filter((line) => line)
.join("\n");
writeFileSync("./ready.txt", raw);
我們執行 node . 將文本處理完畢之後,當前文件夾中將出現一個名為 ready.txt 的文本文件。
為了方便後文中,我們更具象的瞭解向量數據庫的資源佔用,我們順手查看下整理好的文本文件佔磁盤空間是多少:
du -hs ready.txt
5.5M ready.txt
使用模型將文本轉換為向量
為了將文本轉換為向量數據,我們需要使用能夠處理文本嵌入的模型。我這裏選擇的模型是來自人大、騰訊 AI Lab、北大(按論文作者順序)聯合推出的《UER: An Open-Source Toolkit for Pre-training Models》預訓練模型。
關於這個預訓練模型的相關資料:
- HuggingFace https://huggingface.co/uer/sbert-base-chinese-nli
- 訓練數據 https://github.com/liuhuanyong/ChineseTextualInference/
想要使用模型,我們需要先安裝一些 Python 的基礎軟件包:
pip install sentence_transformers pandas
在依賴安裝完畢之後,我們可以在終端中輸入 python 來進入 Python 交互式終端,首先將我們準備好的文本文件使用 pandas 解析為 DataFrames 。
import pandas as pd
df = pd.read_csv("ready.txt", sep="#",header=None, names=["sentence"])
print(df)
在執行之後,我們將看到類似下面的結果:
sentence
0 《哈利波特》J.K羅琳
1 第一部 第一章 倖存的男孩
2 住在四號普里懷特街的杜斯利先生及夫人非常驕傲地宣稱自己是十分正常的人。
3 但是他們最不希望見到的就是任何奇怪或神秘故事中的人物因為他們對此總是嗤之以鼻。
4 杜斯利先生是一家叫作格朗寧斯的鑽機工廠的老闆。
... ...
60023 哈利看着她茫然地低下頭摸了摸額頭上閃電形的傷疤。
60024 “我知道他會的。”
60025 十九年來哈利的傷疤再也沒有疼過。
60026 一切都很好。
60027 (全書完)
[60028 rows x 1 columns]
接下來,我們對載入內存的文本進行向量計算,對每一行數據進行“特徵向量抽取”:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('uer/sbert-base-chinese-nli')
sentences = df['sentence'].tolist()
sentence_embeddings = model.encode(sentences)
這個過程會比較久,消耗時間將會和你的電腦性能相關,我這邊使用一台 Zen2 的普通筆記本,大概需要運行接近半個小時,所以這個時間不妨站起來動一動,緩解一天的疲勞。
當數據向量完畢之後,我們可以先執行 sentence_embeddings.shape,看看數據的狀況:
(60028, 768)
執行完畢,我們將看到類似上面的結果,有六萬條文本被向量化為了 768 維的向量數據。
最後
我們已經搞定了“向量數據”,下一篇內容中,我們將一起了解如何使用 Faiss 來實現向量相似度檢索功能。
作者:蘇洋
原文:《向量數據庫入坑指南:聊聊來自元宇宙大廠 Meta 的相似度檢索技術 Faiss》
鏈接:https://zhuanlan.zhihu.com/p/...
如果你覺得我們分享的內容還不錯,請不要吝嗇給我們一些鼓勵:點贊、喜歡或者分享給你的小夥伴!
活動信息、技術分享和招聘速遞請關注:https://zilliz.gitee.io/welcome/
如果你對我們的項目感興趣請關注:
用於存儲向量並創建索引的數據庫 Milvus
用於構建模型推理流水線的框架 Towhee