|
Faiss简介
!pip install faiss-cpu
FAISS(Facebook AI Similarity Search)
是一个由Facebook AI Research开发的库,用于高效地处理和搜索大规模的向量数据。
在许多AI和机器学习应用中,我们经常需要处理高维度的向量数据,
并需要快速地 找到与给定向量最相似的向量 。
例如,
在推荐系统中,我们可能需要找到与用户的兴趣向量最相似的商品向量;
在自然语言处理中,我们可能需要找到与给定词向量最相似的词向量。
这种操作被称为“最近邻搜索”。
全部代码
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv(filename="env.txt"))
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAI
from langchain_openai import OpenAIEmbeddings
!echo "肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸功能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。">/tmp/a.txt
!echo "from langchain_community.document_loaders import TextLoader">>/tmp/a.txt
!echo "from langchain_community.vectorstores import FAISS">>/tmp/a.txt
loader = TextLoader('/tmp/a.txt')
documents = loader.load()
# 准备文本分割器
text_splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=0)
# 将大文档分成小块
texts = text_splitter.split_documents(documents)
print(texts[0].page_content) # str
#选择嵌入模型
#embeddings = OpenAIEmbeddings(deployment='text-embedding-ada-002')
#model = OpenAIEmbeddings(model="text-embedding-3-large")
embeddings = OpenAIEmbeddings()
#将文本保存到FAISS 向量数据库中
db = FAISS.from_documents(texts, embeddings)
#大小写影响不大
docs = retriever.invoke("FAISS")
print("\n\n".join([x.page_content[:200] for x in docs[:2]]))
FAISS
TextLoader
能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。
#大小写影响不大
docs = retriever.invoke("FAISS")
print("\n\n".join([x.page_content[:200] for x in docs[:2]]))
FAISS
TextLoader
docs = retriever.invoke("呼吸")
print("\n\n".join([x.page_content[:200] for x in docs[:2]]))
肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸功
能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。
|
|
FAISS.from_documents
FAISS.from_documents(
documents: 'List[Document]',
embedding: 'Embeddings',
**kwargs: 'Any',
)
Docstring:
Return VectorStore initialized from documents and embeddings.
#将文本保存到FAISS 向量数据库中
db = FAISS.from_documents(texts, embeddings)
# 准备文件搜索器
retriever = db.as_retriever()
#返回一个文档数组,其元素按向量相似度从高到低排列
docs = retriever.invoke("faiss")
print("\n\n".join([x.page_content[:200] for x in docs[:3]]))
FAISS.from_texts
FAISS.from_texts(
texts: 'List[str]',
embedding: 'Embeddings',
metadatas: 'Optional[List[dict]]' = None,
ids: 'Optional[List[str]]' = None,
**kwargs: 'Any',
) -> 'FAISS'
Docstring:
Construct FAISS wrapper from raw documents.
This is a user friendly interface that:
1. Embeds documents.
2. Creates an in memory docstore
3. Initializes the FAISS database
db_text =FAISS.from_texts(texts, embeddings)
retriever_text = db_text.as_retriever()
docs = retriever_text.invoke("FAISS")
print("\n\n".join([x.page_content[:200] for x in docs[:2]]))
|
from dotenv import load_dotenv, find_dotenv _ = load_dotenv(find_dotenv(filename="env.txt")) from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.document_loaders import TextLoader from langchain_community.vectorstores import FAISS from langchain_openai import OpenAI from langchain_openai import OpenAIEmbeddings # 准备文本分割器 text_splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=10,length_function = len) text=""" 肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸功能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。 from langchain_community.document_loaders import TextLoader from langchain_community.vectorstores import FAISS """ texts = text_splitter.split_text(text) texts ['肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸', '病邪犯肺,影响其呼吸功能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。', 'from langchain_community.document_loaders import', 'import TextLoader', 'from langchain_community.vectorstores import', 'import FAISS'] # 准备文本分割器 text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=10,length_function = len) text=""" 肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸功能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。 from langchain_community.document_loaders import TextLoader from langchain_community.vectorstores import FAISS """ texts = text_splitter.split_text(text) texts ['肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸功能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。', 'from langchain_community.document_loaders import TextLoader', 'from langchain_community.vectorstores import FAISS']
db_text =FAISS.from_texts(texts, embeddings)
retriever_text = db_text.as_retriever()
docs = retriever_text.invoke("FAISS")
print("\n\n".join([x.page_content[:200] for x in docs[:2]]))
FAISS
TextLoader
|
|
from_embeddings:覆盖,新加载的向量会覆盖内存数据库中的向量
# 定义一个长文本。
text = """
肺与美容的关系
肺主气主呼吸
肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸功能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。
肺主宣发与肃降
肺主宣发是指肺气向上升宣和向外布散的功能。
若肺气不能宣发而凝滞,清阳不升,则见胸满、鼻塞、咳喘、面色晦暗;
浊气不能肃降,也能引起呼吸短促、喘促、咳痰等症状;
肺与大肠相表里,肺失肃降,则大肠传导失常,肠道实热,表里失和,面部就易出黄褐斑或生痤疮、酒渣鼻。
肺主皮毛通调水道
肺主皮毛,是指肺脏通过它的宣发作用把水谷精微输布于皮毛,以滋养周身皮肤、毛发、肌肉,也能温分肉、充皮肤、肥腠理、司开阖,防御外邪入侵。肺气足则皮肤滋润光滑有弹性,毫毛浓密光泽等;肺气虚,不能行气与津液以温养毫毛,毫毛之营养不足,就会憔悴枯槁,皮肤干燥,毛发暗淡枯稿,面色淡白;卫外不固则易发风疹过敏等症;肺热上熏则发痤疮、酒渣鼻、皮炎等症。
肺开窍于鼻
鼻是肺呼吸的通道,所以称“鼻为肺窍”。肺与大肠相表里,肺失肃降,则大肠传导失常,粪便排出不畅。临床上,肺失清肃,则大便困难;大肠实热,又引起肺气不利而咳喘胸满;表里失和,患者面部就易出黄褐斑或生痤疮。
"""
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator = "\n",
chunk_size = 200,
chunk_overlap = 50
)
texts = text_splitter.split_text(text)
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
embeddings = OpenAIEmbeddings()
#1. Embeds documents.
#2. Creates an in memory docstore
#3. Initializes the FAISS database
faiss = FAISS.from_texts(texts, embeddings)
embeddings = OpenAIEmbeddings() text_embeddings = embeddings.embed_documents(texts) text_embedding_pairs = zip(texts, text_embeddings) type(text_embeddings) list len(text_embeddings[0]) 1536 #1. Embeds documents. #2. Creates an in memory docstore #3. Initializes the FAISS database faiss = FAISS.from_embeddings(text_embedding_pairs, embeddings) |
|
|
|
Retriever(检索器)
在使用FAISS(Facebook AI Similarity Search)库进行大规模向量搜索时,
通常会遇到直接使用FAISS的API进行搜索(如faiss.search()或faiss.knn_query()等)
和将FAISS封装为Retriever(检索器)并通过invoke()方法调用的两种方式。
虽然两者都能实现向量搜索的功能,但将FAISS转化为Retriever后使用,
在某些场景下具有一些明显的优势,主要体现在以下几个方面:
更高的抽象级别和灵活性: 将FAISS封装为Retriever后,可以在更高层次的抽象上操作, 这意味着你可以更容易地与其他组件(如文本处理、数据处理流水线等)集成。 这种封装使得FAISS的使用更加灵活,尤其是在复杂的机器学习或数据科学项目中。 统一的接口: 如果你的项目中使用了多个检索器(如基于Elasticsearch、向量数据库等), 将FAISS也封装为Retriever可以提供一个统一的接口来调用它们。 这有助于简化代码结构,使得在不同检索器之间切换或组合使用变得更加容易。 易于扩展和维护: 随着项目的发展,可能需要添加新的检索器或修改现有的检索逻辑。 将FAISS封装为Retriever后,你可以通过继承或组合的方式轻松地扩展或修改检索逻辑, 而无需修改大量底层代码。 更好的错误处理和日志记录: 在Retriever的实现中,你可以添加自定义的错误处理和日志记录逻辑, 以便更好地监控和调试检索过程。 这对于大型项目或生产环境尤为重要,因为它们需要更高的稳定性和可维护性。 支持异步和并行处理: 某些Retriever实现可能支持异步或并行处理,这可以显著提高检索性能, 尤其是在处理大量查询或大型数据集时。 虽然FAISS本身也支持多线程搜索,但将FAISS封装为Retriever后, 你可以更容易地将其集成到支持异步或并行处理的框架中。 与框架和库的兼容性: 在某些情况下,将FAISS封装为Retriever可能是为了满足特定框架或库的要求。 例如,在构建基于Jina AI的神经网络搜索系统时, 将FAISS作为后端搜索引擎并封装为Retriever是推荐的做法,因为这样可以更容易地与其他Jina组件集成。
综上所述,将FAISS转化为Retriever后使用的主要优势在于提高了
抽象级别、灵活性、可扩展性、可维护性以及与其他组件的兼容性。
然而,这也取决于你的具体需求和项目上下文。
如果项目规模较小且不需要复杂的集成和扩展,直接使用FAISS的API可能更为简单直接。
|
|
|
|
|
|
|
|
|
index存储与加载
import faiss
import numpy as np
# 创建一些随机数据
np.random.seed(42)
data = np.random.rand(1000, 64).astype('float32') # 假设有1000个64维的向量
# 创建一个平面索引(IndexFlatL2),使用L2距离
index = faiss.IndexFlatL2(64)
# 将数据添加到索引中
index.add(data)
# 保存索引到文件
faiss.write_index(index, "my_index.index")
# 从文件中加载索引
loaded_index = faiss.read_index("my_index.index")
# 验证加载的索引是否包含相同的数据(这里仅作为示例,通常不会直接比较索引对象)
# 实际上,你应该通过查询来验证索引的有效性
query = np.random.rand(1, 64).astype('float32')
dis, ind = loaded_index.search(query, 5) # 查询最相似的5个向量
dis
array([[6.099057 , 6.650052 , 6.803897 , 6.9097624, 6.952726 ]],
dtype=float32)
ind
array([[502, 606, 234, 85, 162]])
query
array([[0.15736598, 0.02078138, 0.32708594, 0.8243158 , 0.01537996,
0.10943393, 0.80685484, 0.3058517 , 0.9382135 , 0.38024414,
0.79670393, 0.25878984, 0.06183124, 0.8815545 , 0.92729646,
0.70809966, 0.7700678 , 0.00712762, 0.7538235 , 0.4721964 ,
0.45279047, 0.54006994, 0.27121022, 0.6962885 , 0.29558888,
0.5586066 , 0.13692723, 0.67507565, 0.18961276, 0.17041399,
0.02428316, 0.12178577, 0.23257025, 0.22230937, 0.32767144,
0.05441642, 0.935198 , 0.11917588, 0.7019639 , 0.53507316,
0.5527888 , 0.10551683, 0.24321494, 0.7798813 , 0.46368647,
0.45706782, 0.5459118 , 0.43380588, 0.80613595, 0.7487052 ,
0.3170042 , 0.8120811 , 0.5186064 , 0.6277368 , 0.86617583,
0.47599325, 0.75626034, 0.62808734, 0.42674494, 0.7890484 ,
0.10515912, 0.5812202 , 0.04641076, 0.15616913]], dtype=float32)
data[ind]
确保在调用write_index之前,你的索引已经包含了所有需要的数据,包括向量和可能的ID映射等。
read_index加载的索引将是一个与原来内存中相同的索引对象的副本,所以你可以直接使用它来进行搜索等操作。
FAISS索引持久化功能依赖于磁盘I/O,因此可能会比内存操作慢一些。但在需要节省内存或持久化索引以供后续使用时,这是一个非常有用的特性。
综上所述,FAISS通过write_index和read_index函数提供了索引的持久化功能,允许你将内存中的索引结构及其向量数据保存到磁盘上,并在需要时重新加载它们。
|
|
index注意事项
索引类型:
示例中使用了IndexFlatL2,这是一种简单的索引类型,
适用于小规模数据集或作为其他更复杂索引(如IndexIVFFlat、IndexIVFPQ)的基准。
对于大规模数据集,建议使用更高效的索引类型。
数据规模:
当处理大规模数据集时,索引的保存和加载可能会消耗较多的磁盘空间和时间。
因此,在实际应用中,需要根据具体情况选择合适的索引类型和持久化策略。
异常处理:
在保存和加载索引时,应添加适当的异常处理逻辑以处理可能出现的错误(如文件权限问题、磁盘空间不足等)。
FAISS当数据量大时,应该使用哪种索引
IVF(Inverted File)索引
inverted 英/ɪnˈvɜːtɪd/ 美/ɪnˈvɜːrtɪd/ adj. 反向的,倒转的,颠倒的,(尤指)倒置的 v.(使)倒转,颠倒,倒置
特点:
IVF索引是FAISS中应用最广泛的索引类型之一,
它将向量空间划分为多个子空间(聚类),并为每个子空间构建一个倒排索引(哈希表)。
搜索时,首先根据查询向量找到对应的子空间,然后在该子空间中进行相似度计算和搜索。
这种方法通过减少搜索的空间范围,显著提高了搜索效率。
适用场景:
适用于大规模数据集,能够显著加快搜索速度,同时保持较高的召回率。
PQ(Product Quantization)索引
特点:
PQ索引是一种用于大规模向量量化的索引类型。
它将向量划分为若干个子向量,并对每个子向量进行量化,
然后将量化后的子向量拼接起来形成新的向量表示。
在搜索时,通过比较量化后的向量来计算相似度。
PQ索引通过减小向量的维度,降低了计算和存储的复杂度。
适用场景:
适用于需要量化向量以节省存储空间和计算资源的情况,尤其适用于大规模数据集。
|
|
index只是index:存储的是向量,搜索的输入也是向量,而不是文本 # 定义一个长文本。 text = """ 肺与美容的关系 肺主气主呼吸 肺主呼吸之气、一身之气。肺司呼吸的功能正常,则气道通畅,呼吸调匀,面有色泽。若病邪犯肺,影响其呼吸功能,则出现胸满咳嗽、喘促、呼吸不利、面色紫绀等症状。 肺主宣发与肃降 肺主宣发是指肺气向上升宣和向外布散的功能。 若肺气不能宣发而凝滞,清阳不升,则见胸满、鼻塞、咳喘、面色晦暗; 浊气不能肃降,也能引起呼吸短促、喘促、咳痰等症状; 肺与大肠相表里,肺失肃降,则大肠传导失常,肠道实热,表里失和,面部就易出黄褐斑或生痤疮、酒渣鼻。 肺主皮毛通调水道 肺主皮毛,是指肺脏通过它的宣发作用把水谷精微输布于皮毛,以滋养周身皮肤、毛发、肌肉,也能温分肉、充皮肤、肥腠理、司开阖,防御外邪入侵。肺气足则皮肤滋润光滑有弹性,毫毛浓密光泽等;肺气虚,不能行气与津液以温养毫毛,毫毛之营养不足,就会憔悴枯槁,皮肤干燥,毛发暗淡枯稿,面色淡白;卫外不固则易发风疹过敏等症;肺热上熏则发痤疮、酒渣鼻、皮炎等症。 肺开窍于鼻 鼻是肺呼吸的通道,所以称“鼻为肺窍”。肺与大肠相表里,肺失肃降,则大肠传导失常,粪便排出不畅。临床上,肺失清肃,则大便困难;大肠实热,又引起肺气不利而咳喘胸满;表里失和,患者面部就易出黄褐斑或生痤疮。 """ from langchain.text_splitter import CharacterTextSplitter from langchain_community.vectorstores import FAISS from langchain_openai import OpenAIEmbeddings from dotenv import load_dotenv, find_dotenv _ = load_dotenv(find_dotenv())
def get_embeddings(text):
text_splitter = CharacterTextSplitter(
separator = "\n",
chunk_size = 1000,
chunk_overlap = 50
)
texts = text_splitter.split_text(text)
embeddings = OpenAIEmbeddings()
text_embeddings = embeddings.embed_documents(texts)
return texts,text_embeddings
# get_embeddings(text)
texts,embeddings = get_embeddings(text) import os import faiss import numpy as np
index_path = "my_index.index"
# if os.path.exists(index_path):
# 创建一个平面索引(IndexFlatL2),使用L2距离
dimensions = len(embeddings[0])
index = faiss.IndexFlatL2(dimensions)
# 将数据添加到索引中
index.add(np.array(embeddings))
# 保存索引到文件
faiss.write_index(index, "my_index.index")
# 从文件中加载索引
loaded_index = faiss.read_index("my_index.index")
query = "肺与美容的关系" _,embeddings_query = get_embeddings(query) dis, ind = loaded_index.search(np.array(embeddings_query),k=1) dis array([[0.23161677]], dtype=float32) ind array([[0]]) |
|
|
|
|