返回文章列表

向量相似性搜尋高效系統建構實務

本文深入探討如何運用 FAISS 和 OpenAI 嵌入技術,建構高效的向量相似性搜尋系統。從實務角度出發,涵蓋了關鍵技術的選型考量、程式碼範例、效能最佳化策略以及實際應用案例,並解析常見錯誤和解決方案,提供臺灣工程師在向量搜尋領域的實戰。

人工智慧 軟體開發

在現今資料爆炸的時代,如何快速且精準地從海量資料中找到所需資訊成為關鍵挑戰。向量相似性搜尋技術因其高效性和準確性,已成為解決此問題的利器。本文將以臺灣工程師的視角,深入剖析如何利用 FAISS 和 OpenAI 嵌入技術,建構一套高效的向量搜尋系統。過程中,我們將探討技術選型的考量、程式碼實作細節、效能調校技巧,並分享實際應用案例與常見錯誤的解決方案。

首先,我們選擇 FAISS 作為向量搜尋的核心引擎,主要考量其在處理大規模向量資料時的卓越效能和可擴充套件性。此外,FAISS 支援多種索引策略,允許我們根據實際需求彈性調整搜尋精確度和速度。搭配 OpenAI 的嵌入模型,能將文字資訊轉化為高維向量,捕捉語義關聯,進一步提升搜尋的精準度。

from langchain.document_loaders import TextLoader
from langchain.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import CacheBackedEmbeddings
import os

os.environ["OPENAI_API_KEY"] = "您的 OpenAI 金鑰"

underlying_embeddings = OpenAIEmbeddings()
# 使用本地檔案系統快取嵌入向量,避免重複計算,提升效能
store = LocalFileStore("./cache/")
cached_embedder = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings, store, namespace=underlying_embeddings.model
)

# 載入檔案資料
raw_documents = TextLoader("./sample_data/The Art of Money Getting.txt").load()
# 將檔案分割成較小的區塊,方便向量化處理
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
documents = text_splitter.split_documents(raw_documents)
# 使用 FAISS 建立向量資料函式庫
db = FAISS.from_documents(documents, cached_embedder)

# 執行相似性搜尋
query = "如何致富?" # 臺灣用語
results = db.similarity_search(query, k=3)

# 顯示搜尋結果
for result in results:
    print(result.page_content)

內容解密:

這段程式碼首先初始化 OpenAI 嵌入模型,並利用本地檔案系統快取嵌入向量,有效避免重複計算,提升效能。接著,載入檔案資料並使用 CharacterTextSplitter 將其分割成小區塊,以便向量化處理。然後,利用 FAISS 建立向量資料函式庫,並執行相似性搜尋,最後顯示搜尋結果。

在實際應用中,我們曾遇到向量維度過高導致搜尋效率低下的問題。為瞭解決這個問題,我們嘗試了幾種降維方法,例如 PCA 和 t-SNE,最終選擇了 PCA,因為它在保持資訊完整性的同時,能有效降低向量維度,顯著提升搜尋速度。

import faiss

# ... (先前的程式碼) ...

# 使用 PCA 降維
dimensions = 128  # 設定目標維度
pca = faiss.PCAMatrix(cached_embedder.embed_query("test").shape[0], dimensions)
db.index = faiss.IndexPreTransform(pca, db.index)

內容解密:

這段程式碼展示瞭如何使用 PCA 降維。設定目標維度後,使用faiss.PCAMatrix建立 PCA 矩陣,再使用faiss.IndexPreTransform將 PCA 矩陣應用到 FAISS 索引中,從而達到降維的目的。

此外,我們也發現非同步呼叫向量資料函式庫能進一步提升系統的反應速度。以下是如何使用 Qdrant 執行非同步搜尋的範例:

from langchain.vectorstores import Qdrant
import asyncio

# ... (先前的程式碼) ...

async def async_search(query):
    qdrant = await Qdrant.afrom_documents(documents, cached_embedder)
    docs = await qdrant.asimilarity_search(query)
    return docs[0].page_content

async def main():
    result = await async_search("如何有效管理財務?") # 臺灣用語
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

內容解密:

這段程式碼示範如何使用 Qdrant 進行非同步搜尋。async_search 函式利用 Qdrant.afrom_documents 建立向量資料函式庫,並使用 asimilarity_search 執行非同步搜尋。main 函式則負責呼叫 async_search 並印出結果。

透過以上技巧和實務經驗的分享,希望能幫助臺灣工程師更有效地建構和最佳化向量相似性搜尋系統,進而在各自的領域中發揮其最大價值。

建立高效的向量搜尋系統

在這個例子中,我們將使用 FAISS(Facebook AI Similarity Search)函式庫來實作一個高效的向量搜尋系統。FAISS 是一個由 Facebook 開發的函式庫,旨在提供高效的向量搜尋功能。

FAISS 的優點

FAISS 有以下幾個優點:

  • 高效的向量儲存和檢索:FAISS 設計用於高效地處理大規模向量搜尋。它提供了最佳化的演算法來儲存和檢索高維向量,使其非常適合於涉及大型資料集的應用。
  • 可擴充套件性:FAISS 可以擴充套件到數百萬甚至數十億個向量,並確保您的應用程式在資料集增長時保持高效能。
  • 多樣性:FAISS 支援各種索引策略,因此您可以根據具體需求選擇最適合的索引方法。例如,您可以在快速近似最近鄰居搜尋和精確搜尋之間進行選擇。
  • CPU 基礎:faiss-cpu 套件針對 CPU 使用進行了最佳化,因此它是 GPU 資源有限或不可用的環境中的良好替代方案。這確保您可以在廣泛的硬體組態上佈署和執行您的應用程式。

安裝必要的函式庫

要建立向量搜尋系統,我們需要安裝以下函式庫:

from langchain.document_loaders import TextLoader
from langchain.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import CacheBackedEmbeddings

設定 OpenAI API 金鑰

為了生成嵌入,我們需要設定 OpenAI API 金鑰:

import os

os.environ["OPENAI_API_KEY"] = "您的 OpenAI 金鑰"

建立 OpenAI 嵌入例項

接下來,我們建立一個 OpenAI 嵌入例項:

underlying_embeddings = OpenAIEmbeddings()

建立本地檔案儲存例項

然後,我們建立一個本地檔案儲存例項來快取嵌入:

store = LocalFileStore("./cache/")

建立 CacheBackedEmbeddings 例項

這是最重要的一步,我們建立一個 CacheBackedEmbeddings 例項,並包裝我們之前建立的 OpenAI 嵌入。我們將嵌入儲存在本地檔案儲存中以進行快取。我們使用名稱空間引數並將其指向底層嵌入的模型名稱:

cached_embedder = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings, store, namespace=underlying_embeddings.model
)

建立資訊檢索系統

現在,我們需要載入檔案:

raw_documents = TextLoader("./sample_data/The Art of Money Getting.txt").load()

一旦載入完成,我們就可以使用文字分割器將檔案分割成塊:

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)

圖表翻譯:

內容解密:

上述程式碼展示瞭如何使用 FAISS 和 OpenAI 嵌入建立一個高效的向量搜尋系統。首先,我們匯入必要的函式庫,包括 TextLoaderFAISSOpenAIEmbeddingsCharacterTextSplitterCacheBackedEmbeddings。然後,我們設定 OpenAI API 金鑰並建立一個 OpenAI 嵌入例項。接下來,我們建立一個本地檔案儲存例項來快取嵌入,並建立一個 CacheBackedEmbeddings 例項來包裝底層嵌入。最後,我們載入檔案,分割檔案,並建立資訊檢索系統。

文字分割和向量儲存

文字分割是一種將大型檔案分割成小塊的過程,以便於搜尋和檢索。這裡,我們使用 text_splitter 將原始檔案分割成小塊,並建立一個向量儲存來儲存這些小塊。

documents = text_splitter.split_documents(raw_documents)
db = FAISS.from_documents(documents, cached_embedder)

相似性搜尋

相似性搜尋是一種查詢與給定查詢最相似的檔案的過程。這裡,我們使用 db.similarity_search 方法來查詢與給定查詢最相似的檔案。

query = "What advice does the author give about getting rich?"
results = db.similarity_search(query, k=3)

非同步呼叫向量儲存

非同步呼叫向量儲存可以提高搜尋效率。這裡,我們使用 Qdrant 來建立一個非同步向量儲存。

db = await Qdrant.afrom_documents(documents, embeddings)

搜尋結果

搜尋結果可以透過 db.asimilarity_search 方法來獲得。這裡,我們使用 docs[0].page_content 來存取第一個搜尋結果的內容。

query = "What advice did the author give about money"
docs = await db.asimilarity_search(query)
print(docs[0].page_content)

檢索器

檢索器是一種用於從向量儲存中檢索檔案的工具。這裡,我們介紹了幾種不同的檢索器,包括 Vectorstore RetrieverParentDocument RetrieverMulti-vector RetrieverSelf-Query RetrieverContextual Compression Retriever

from langchain.retrievers import VectorstoreRetriever
retriever = VectorstoreRetriever(documents_vectorstore)
query = "What are the main benefits of using a RAG?"
relevant_docs = retriever.get_relevant_documents(query)

圖表翻譯:

內容解密:

上述程式碼展示瞭如何使用 langchainFAISS 來實作文字分割、向量儲存和相似性搜尋。同時,也介紹瞭如何使用 Qdrant 來建立一個非同步向量儲存,並使用 db.asimilarity_search 方法來獲得搜尋結果。最後,介紹了幾種不同的檢索器,包括 Vectorstore RetrieverParentDocument RetrieverMulti-vector RetrieverSelf-Query RetrieverContextual Compression Retriever

深入解析向量搜尋技術:打造高效能應用

向量搜尋技術在現今資料爆炸時代扮演著關鍵角色,從推薦系統到自然語言處理,其應用無所不在。本文將深入探討如何利用 FAISS 這個由 Facebook AI Research 開發的函式庫,在 CPU 環境下建構一個高效能的向量搜尋系統。

FAISS 之所以受到廣泛應用,是因為其具備多項優勢。它能有效率地儲存和檢索高維向量,即使面對龐大的資料集也能保持優異的效能。此外,FAISS 支援多種索引策略,讓開發者能依據特定需求,在搜尋速度和準確度之間取得平衡。更重要的是,FAISS 提供了針對 CPU 最佳化的版本,即使在 GPU 資源受限的環境中,也能發揮其強大功能。

在實際建構向量搜尋系統之前,我們需要先安裝必要的 Python 函式庫,包含用於載入檔案、向量儲存、嵌入生成、文字分割,以及快取嵌入的相關工具。以下程式碼片段展示了這些函式庫的匯入:

from langchain.document_loaders import TextLoader
from langchain.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import CacheBackedEmbeddings

內容解密:

上述程式碼匯入了建構向量搜尋系統所需的 Python 函式庫。TextLoader 用於載入文字檔案,FAISS 則是用於向量儲存和搜尋的核心函式庫。OpenAIEmbeddings 則負責將文字轉換為向量表示,CharacterTextSplitter 將長檔案分割成較小的區塊,而 CacheBackedEmbeddings 則用於快取嵌入結果,避免重複計算,提升效率。

接著,設定 OpenAI API 金鑰,這是使用 OpenAI 嵌入模型的必要步驟:

import os
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"  # 替換為您的 OpenAI API 金鑰

內容解密:

這段程式碼設定了 OpenAI API 金鑰,讓程式可以存取 OpenAI 的服務。記得將 YOUR_OPENAI_API_KEY 替換成您自己的金鑰。

建立嵌入模型例項,並利用本地檔案系統快取嵌入結果,以提升效能:

underlying_embeddings = OpenAIEmbeddings()
store = LocalFileStore("./cache/") # 設定快取路徑
cached_embedder = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings, store, namespace=underlying_embeddings.model
)

內容解密:

這段程式碼首先建立了一個 OpenAIEmbeddings 例項,接著設定本地檔案系統作為嵌入快取的儲存位置,最後利用 CacheBackedEmbeddings 將嵌入模型和快取機制結合,以提升效能並減少 API 呼叫次數。

載入檔案並進行分割,以利後續處理:

loader = TextLoader("./sample_data/The_Art_of_Money_Getting.txt") # 替換為您的檔案路徑
raw_documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
documents = text_splitter.split_documents(raw_documents)

內容解密:

這段程式碼使用 TextLoader 載入指定的文字檔案,然後使用 CharacterTextSplitter 將檔案分割成較小的區塊,每個區塊大小為 1000 個字元,且區塊之間沒有重疊。

最後,利用 FAISS 建立向量資料函式庫,並執行相似度搜尋:

db = FAISS.from_documents(documents, cached_embedder)
query = "如何致富?" # 範例查詢
results = db.similarity_search(query, k=3) # 搜尋最相似的三個檔案

for doc in results:
    print(doc.page_content)

內容解密:

這段程式碼使用 FAISS.from_documents 建立向量資料函式庫,將分割後的文字區塊及其對應的嵌入向量儲存至 FAISS 索引中。接著,它執行相似度搜尋,找出與查詢 如何致富? 最相似的三個檔案,並印出其內容。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 向量相似性搜尋高效系統建構實務

package "NumPy 陣列操作" {
    package "陣列建立" {
        component [ndarray] as arr
        component [zeros/ones] as init
        component [arange/linspace] as range
    }

    package "陣列操作" {
        component [索引切片] as slice
        component [形狀變換 reshape] as reshape
        component [堆疊 stack/concat] as stack
        component [廣播 broadcasting] as broadcast
    }

    package "數學運算" {
        component [元素運算] as element
        component [矩陣運算] as matrix
        component [統計函數] as stats
        component [線性代數] as linalg
    }
}

arr --> slice : 存取元素
arr --> reshape : 改變形狀
arr --> broadcast : 自動擴展
arr --> element : +, -, *, /
arr --> matrix : dot, matmul
arr --> stats : mean, std, sum
arr --> linalg : inv, eig, svd

note right of broadcast
  不同形狀陣列
  自動對齊運算
end note

@enduml

在建構向量搜尋系統時,除了效能考量外,也需注意資料的更新和維護。隨著資料量的增長,如何有效地管理和更新向量資料函式庫將成為一個重要的課題。此外,選擇合適的嵌入模型和索引策略也是影響搜尋效能的關鍵因素,需要根據實際應用場景進行調整。在臺灣的技術社群中,向量搜尋技術的應用也日漸普及,從電商平臺的商品推薦到新聞網站的內容搜尋,都能看到其身影。未來,隨著技術的發展,向量搜尋技術的應用將更加多元化,為各個領域帶來更多創新應用。