Skip to main content
Open In ColabOpen on GitHub

Faiss(异步)

Facebook AI Similarity Search (Faiss) is a library for efficient similarity search and clustering of dense vectors. It contains algorithms that search in sets of vectors of any size, up to ones that possibly do not fit in RAM. It also includes supporting code for evaluation and parameter tuning.

See The FAISS Library paper.

Faiss 文档

您需要使用 pip install -qU langchain-community 安装 langchain-community 才能使用此集成

本笔记本展示了如何使用与 FAISS 向量数据库相关的功能,使用了 asyncio。 LangChain 实现了同步和异步向量存储功能。

查看 synchronous 版本 此处

%pip install --upgrade --quiet  faiss-gpu # For CUDA 7.5+ Supported GPU's.
# OR
%pip install --upgrade --quiet faiss-cpu # For CPU Installation

我们想要使用 OpenAIEmbeddings,因此必须获取 OpenAI API 密钥。

import getpass
import os

if "OPENAI_API_KEY" not in os.environ:
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")

# Uncomment the following line if you need to initialize FAISS with no AVX2 optimization
# os.environ['FAISS_NO_AVX2'] = '1'

from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter

loader = TextLoader("../../../extras/modules/state_of_the_union.txt")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

embeddings = OpenAIEmbeddings()

db = await FAISS.afrom_documents(docs, embeddings)

query = "What did the president say about Ketanji Brown Jackson"
docs = await db.asimilarity_search(query)

print(docs[0].page_content)

带分数的相似性搜索

有一些特定于 FAISS 的方法。其中之一是 similarity_search_with_score,它允许您不仅返回文档,还返回查询与它们之间的距离分数。返回的距离分数是 L2 距离。因此,较低的分数更好。

docs_and_scores = await db.asimilarity_search_with_score(query)

docs_and_scores[0]

也可以使用 similarity_search_by_vector 来搜索与给定嵌入向量相似的文档,它接受一个嵌入向量作为参数,而不是字符串。

embedding_vector = await embeddings.aembed_query(query)
docs_and_scores = await db.asimilarity_search_by_vector(embedding_vector)

保存和加载

你也可以保存和加载一个FAISS索引。这很有用,这样你就不用每次使用时都重新创建它了。

db.save_local("faiss_index")

new_db = FAISS.load_local("faiss_index", embeddings, asynchronous=True)

docs = await new_db.asimilarity_search(query)

docs[0]

序列化和反序列化为字节

你可以通过这些函数将FAISS索引进行pickle序列化。如果你使用的是90MB大小的嵌入模型(例如sentence-transformers/all-MiniLM-L6-v2或其他模型),那么生成的pickle文件大小将会超过90MB,因为模型本身的大小也包含在总大小中。为了克服这个问题,可以使用下面的函数。这些函数只对FAISS索引进行序列化,因此文件大小会小得多。这在你希望将索引存储到SQL数据库等场景下非常有用。

from langchain_huggingface import HuggingFaceEmbeddings

pkl = db.serialize_to_bytes() # serializes the faiss index
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
db = FAISS.deserialize_from_bytes(
embeddings=embeddings, serialized=pkl, asynchronous=True
) # Load the index

合并

你也可以合并两个FAISS向量存储。

db1 = await FAISS.afrom_texts(["foo"], embeddings)
db2 = await FAISS.afrom_texts(["bar"], embeddings)
db1.docstore._dict
{'8164a453-9643-4959-87f7-9ba79f9e8fb0': Document(page_content='foo')}
db2.docstore._dict
{'4fbcf8a2-e80f-4f65-9308-2f4cb27cb6e7': Document(page_content='bar')}
db1.merge_from(db2)
db1.docstore._dict
{'8164a453-9643-4959-87f7-9ba79f9e8fb0': Document(page_content='foo'),
'4fbcf8a2-e80f-4f65-9308-2f4cb27cb6e7': Document(page_content='bar')}

与过滤的相似性搜索

FAISS向量存储还支持过滤功能,由于FAISS本身不原生支持过滤,因此需要手动实现。具体操作是先获取多于k个结果,然后再进行过滤。可以根据元数据对文档进行过滤。在调用任何搜索方法时,还可以设置fetch_k参数来指定在过滤前要获取多少个文档。以下是一个小示例:

from langchain_core.documents import Document

list_of_documents = [
Document(page_content="foo", metadata=dict(page=1)),
Document(page_content="bar", metadata=dict(page=1)),
Document(page_content="foo", metadata=dict(page=2)),
Document(page_content="barbar", metadata=dict(page=2)),
Document(page_content="foo", metadata=dict(page=3)),
Document(page_content="bar burr", metadata=dict(page=3)),
Document(page_content="foo", metadata=dict(page=4)),
Document(page_content="bar bruh", metadata=dict(page=4)),
]
db = FAISS.from_documents(list_of_documents, embeddings)
results_with_scores = db.similarity_search_with_score("foo")
for doc, score in results_with_scores:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}, Score: {score}")
API 参考:文档
Content: foo, Metadata: {'page': 1}, Score: 5.159960813797904e-15
Content: foo, Metadata: {'page': 2}, Score: 5.159960813797904e-15
Content: foo, Metadata: {'page': 3}, Score: 5.159960813797904e-15
Content: foo, Metadata: {'page': 4}, Score: 5.159960813797904e-15

现在我们进行相同的查询调用,但只过滤 page = 1

results_with_scores = await db.asimilarity_search_with_score("foo", filter=dict(page=1))
for doc, score in results_with_scores:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}, Score: {score}")
Content: foo, Metadata: {'page': 1}, Score: 5.159960813797904e-15
Content: bar, Metadata: {'page': 1}, Score: 0.3131446838378906

同样的事情也可以用 max_marginal_relevance_search 来完成。

results = await db.amax_marginal_relevance_search("foo", filter=dict(page=1))
for doc in results:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
Content: foo, Metadata: {'page': 1}
Content: bar, Metadata: {'page': 1}

以下是调用 similarity_search 时设置 fetch_k 参数的示例。通常情况下,您希望 fetch_k 参数大于 k 参数。这是因为 fetch_k 参数是过滤前将获取的文档数量。如果将 fetch_k 设置为一个较低的数字,您可能无法获得足够的文档进行过滤。

results = await db.asimilarity_search("foo", filter=dict(page=1), k=1, fetch_k=4)
for doc in results:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
Content: foo, Metadata: {'page': 1}

一些 MongoDB 查询和投影运算符 支持更高级的元数据过滤。当前支持的运算符列表如下:

  • $eq(等于)
  • $neq(不等于)
  • $gt(大于)
  • $lt(小于)
  • $gte(大于或等于)
  • $lte(小于或等于)
  • $in(列表成员资格)
  • $nin(不在列表中)
  • $and(所有条件必须匹配)
  • $or(任何条件必须匹配)
  • $not(条件的否定)

使用高级元数据过滤执行相同的相似性搜索可以按照以下方式进行:

results = await db.asimilarity_search(
"foo", filter={"page": {"$eq": 1}}, k=1, fetch_k=4
)
for doc in results:
print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
Content: foo, Metadata: {'page': 1}

删除

你也可以删除ID。请注意,要删除的ID应该是文档存储中的ID。

db.delete([db.index_to_docstore_id[0]])
True
# Is now missing
0 in db.index_to_docstore_id
False