Skip to main content
Facebook AI 相似度搜索(Faiss) 是一个用于高效相似度搜索和密集向量聚类的库。它包含可在任意大小向量集合中进行搜索的算法,甚至可处理无法全部装入内存的向量集。它还提供用于评估和参数调优的辅助代码。 参见 The FAISS Library 论文。
Faiss 文档 使用此集成前,请通过 pip install -qU langchain-community 安装 langchain-community 本 Notebook 展示如何使用 asyncioFAISS 向量数据库相关的功能。 LangChain 实现了同步和异步两种向量存储函数。 同步版本请参见此处
pip install -qU  faiss-gpu # 适用于 CUDA 7.5+ 支持的 GPU。
# 或
pip install -qU  faiss-cpu # CPU 安装版本
我们希望使用 OpenAIEmbeddings,因此需要获取 OpenAI API Key。
import getpass
import os

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

# 如需在不使用 AVX2 优化的情况下初始化 FAISS,请取消注释以下行
# 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()  # 序列化 FAISS 索引
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
db = FAISS.deserialize_from_bytes(
    embeddings=embeddings, serialized=pkl, asynchronous=True
)  # 加载索引

合并

您也可以合并两个 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}")
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 应为 docstore 中的 id。
db.delete([db.index_to_docstore_id[0]])
True
# 现在已不存在
0 in db.index_to_docstore_id
False