【可能是全网最丝滑的LangChain教程】十六、LangChain进阶之Vector Stores

你的朋友会告诉你,你能做到;你的敌人会告诉你,你做不到。但只有你自己知道,你到底能不能做到。

01 介绍

Vector Stores在LangChain框架中是连接语言模型与实际应用数据的关键桥梁,为构建智能化、高效的语言处理应用提供了强大的基础设施支持。

在这里插入图片描述

理解向量存储前需了解向量嵌入(Embeddings)。文本向量化是将文本转换成数值向量的过程,这些向量能够在多维空间中捕捉词语或文档之间的语义相似性。常见的嵌入模型如Word2Vec、BERT、Sentence Transformers等,可以被用来生成这样的向量。

Vector Stores 的用途

  • 高效检索:Vector Stores主要用于存储这些嵌入向量,并支持高效的相似性搜索,使得用户能够根据输入的查询(也是一个向量)快速找到最相关的文档、段落或信息。
  • 语义搜索:由于向量能够表达语义关系,Vector Stores使得基于内容而非关键词的搜索成为可能,大大提高了搜索的准确性和相关性。

Vector Stores 实现方式

LangChain支持多种向量存储后端,包括但不限于:

  • FAISS:Facebook AI Similarity Search,一个高效的相似性搜索库,特别适合大规模的向量数据集。
  • PineconeQdrant:云原生的向量数据库服务,提供了API接口,便于管理和检索向量数据。(这里插一嘴,我们项目用的就是 Qdrant,好用就完事了~😊)
  • Weaviate:一个语义搜索引擎,支持向量搜索和知识图谱管理。
  • Chroma:一个开源的向量数据库,专为机器学习和NLP应用设计。

Vector Stores 功能特性

  • 索引构建:可以为文档集合创建索引,这个过程涉及将文档转换为向量并存储起来,以便后续快速检索。
  • 更新与删除:支持对向量数据的动态管理,包括源文档更新时的向量重计算以及删除不再需要的向量。
  • 检索优化:通过近似最近邻(Approximate Nearest Neighbor, ANN)算法,在保证较高精度的同时,实现了对大规模数据集的高效检索。

Vector Stores 应用场景

  • 问答系统:快速从大量文档中找到与问题最相关的答案。
  • 个性化推荐:基于用户历史行为和偏好生成的向量,来推荐相似或相关的内容。
  • 知识图谱增强:结合向量搜索提高知识图谱节点间链接的发现和查询效率。
  • 文档检索系统:企业内部文档、网页内容的快速语义搜索。

LangChain提供了统一的API接口来与不同的Vector Stores交互,使得开发者无需深入了解每个后端的具体实现细节,即可轻松集成和切换向量存储解决方案,提升了开发效率和灵活性。

02 Vector Store 使用

LangChain 中向量数据库的使用基本遵循4步:

  1. document loader(【LangChain进阶教程】六、LangChain进阶之Document loaders
  2. text splitter(【LangChain进阶教程】七、LangChain进阶之Text Splitters
  3. embedding model(【LangChain进阶教程】八、LangChain进阶之Embedding Models
  4. vector store

Similarity search

如果我们使用的OpenAI相关的模型,我们可以这么使用:

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

# 词嵌入模型
# 这里openai_api_key必填,其余的参数选填
embeddings = OpenAIEmbeddings(openai_api_key='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')

# 加载文档
text_loader = TextLoader('./index.txt', encoding='utf-8', autodetect_encoding=True)
raw_documents = text_loader.load()

# 文档切割
text_splitter = CharacterTextSplitter(chunk_size=150, chunk_overlap=80)
documents = text_splitter.split_documents(raw_documents)

# 使用FAISS构建向量数据库
db = FAISS.from_documents(documents=documents, embedding=embedding)

# 查询与 query 相似的docs
query = '生活就想巧克力~'
docs = db.similarity_search(query)

或者我们也可替换成_本地的词嵌入模型_,这里我经常用(项目用)的词嵌入模型是m3e系列:

from langchain_community.vectorstores.utils import DistanceStrategy
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
from transformers.utils import is_torch_cuda_available, is_torch_mps_available
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS

# 词嵌入模型
EMBEDDING_DEVICE = "cuda" if is_torch_cuda_available() else "mps" if is_torch_mps_available() else "cpu"
embedding = HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})

text_loader = TextLoader('./index.txt', encoding='utf-8', autodetect_encoding=True)
raw_documents = text_loader.load()

text_splitter = CharacterTextSplitter(chunk_size=150, chunk_overlap=80)
documents = text_splitter.split_documents(raw_documents)
db = FAISS.from_documents(documents=documents, embedding=embedding, distance_strategy=DistanceStrategy.COSINE)
db.save_local(folder_path='./vector/FAISS.db', index_name='cpm-index')

query = '生活就想巧克力~'
docs = db.similarity_search(query)

"""
No sentence-transformers model found with name D:\models\m3e-base. Creating a new one with MEAN pooling.

[Document(page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\n\n"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)', metadata={'source': './index.txt'}),
 Document(page_content='"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)\n\n"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)', metadata={'source': './index.txt'}),
 Document(page_content='"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': './index.txt'}),
 Document(page_content='"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)', metadata={'source': './index.txt'})]
"""

Similarity search by vector

前面我们通过 similarity_search 做向量相似度查询,也可以_通过 similarity_search_with_score 查询结果并返回相似度”分数“,”分数“越低相似度越高。

docs = db.similarity_search_with_score(query='第一行')

"""
[(Document(page_content='这是第一行', metadata={'source': './custom_loader.txt'}),
  26.530163),
 (Document(page_content='第二行是空白行,这是第三行', metadata={'source': './custom_loader.txt'}),
  80.25705),
 (Document(page_content='"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)', metadata={'source': './index.txt'}),
  217.43489),
 (Document(page_content='"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)', metadata={'source': './index.txt'}),
  220.26076)]
"""

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

embedding_vector = OpenAIEmbeddings().embed_query(query)
docs = db.similarity_search_by_vector(embedding_vector)

这里提一嘴:similarity_search、similarity_search_with_score 的本质还是去调用 similarity_search_by_vector

save_local 和 load_local

如果我们项目使用的是本地向量数据库,那我们就可以指定向量数据库的保存位置,将FAISS索引、docstore和index_to_docstore_id保存到本地磁盘

这样做的好处就是我们可以提前对源数据做向量化处理,后面需要用到的时候直接加载就行,无需再次向量化。通过 save_local 实现:

db = FAISS.from_documents(documents=documents, embedding=embedding)
db.save_local(folder_path='./vector/FAISS.db', index_name='cpm-index')

图片

加载并使用,通过 load_local 实现:

db = FAISS.load_local(folder_path='./vector/FAISS.db', embeddings=embedding, index_name='cpm-index',
                      allow_dangerous_deserialization=True)
docs = db.similarity_search(query='狮子王', k=1)

"""
[Document(page_content='"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)', metadata={'source': './index.txt'})]
"""

Vector merge

有时候我们对元数据做向量化的时候并不是一蹴而就,可能是有源数据的时候就向量化一次,这就可能会导致我们会保存很多 .faiss 文件和 .pkl 文件。

这个时候,我们更希望将多个文件进行合并,通过 merge_from 实现:

db1 = FAISS.from_documents(documents=documents, embedding=embedding)
db2 = FAISS.from_documents(documents=documents, embedding=embedding)

# 合并
db2.merge_from(db1)

Asynchronous operations

向量存储通常作为单独的服务运行,需要一些 IO 操作,因此它们可能会被异步调用。这带来了性能优势,因为您不必浪费时间等待外部服务的响应。如果您使用异步框架(如 FastAPI),这可能也很重要。

LangChain支持对向量存储进行异步操作。所有方法都可以使用其异步对应项进行调用,前缀为 a,意思是 async。

Qdrant 是一个在线向量数据库,它支持所有异步操作,同时提供API和管理端,方便操作,我们项目也在用。

db = await Qdrant.afrom_documents(documents, embeddings, "http://localhost:6333")
query = "What did the president say about Ketanji Brown Jackson"
docs = await db.asimilarity_search(query)

# 或者
embedding_vector = embeddings.embed_query(query)
docs = await db.asimilarity_search_by_vector(embedding_vector)

Maximum marginal relevance search(MMR)

最大边际相关性搜索。

是一种在信息检索和推荐系统中用于挑选结果的策略,旨在平衡查询项与已有结果集的相关性以及结果之间的多样性。简单来说,MMR 不仅仅考虑单个结果与查询的相似度,还力求所选集合中的文档彼此不那么相似,以此来提高信息覆盖的全面性和减少重复信息。

MMR 通过结合两个指标来选择下一个最佳文档:

  • 相关性(Relevance):衡量文档与查询的直接相关程度,通常通过相似度分数表示。
  • 多样性和新颖性(Marginality):衡量新文档相对于已选择集合的新颖程度,鼓励选择能够增加信息多样性的文档。
query = "What did the president say about Ketanji Brown Jackson"
found_docs = await qdrant.amax_marginal_relevance_search(query, k=2, fetch_k=10)

03 相似度计算

LangChain 中有一个 DistanceStrategy枚举类,里面定义了开发者可以选择的相似度计算方式。

class DistanceStrategy(str, Enum):
    """Enumerator of the Distance strategies for calculating distances
    between vectors."""

    EUCLIDEAN_DISTANCE = "EUCLIDEAN_DISTANCE"
    MAX_INNER_PRODUCT = "MAX_INNER_PRODUCT"
    DOT_PRODUCT = "DOT_PRODUCT"
    JACCARD = "JACCARD"
    COSINE = "COSINE"

这些参数代表了不同的相似度计算方法或距离度量方式,常用于机器学习、数据挖掘、信息检索等领域。下面是每个参数的简要说明:

  • EUCLIDEAN_DISTANCE(欧氏距离):说明:这是一种衡量两个点之间直线距离的方法。在多维空间中,两点之间的欧氏距离定义为各维度差值平方和的平方根。应用场景:图像识别、语音识别、聚类算法等。
  • MAX_INNER_PRODUCT(最大内积):说明:指两个向量的元素对应相乘后的结果的最大值。在某些情境下,这可以被看作是一种相似度度量,尤其是在二进制特征向量中寻找匹配项时。应用场景:推荐系统中的用户-物品匹配、信息检索的查询-文档匹配。
  • DOT_PRODUCT(点积):说明:也称为标量积,是两个向量的对应元素相乘后求和的结果。点积的大小可以反映两个向量方向上的相关性,正数表示大致方向相同,负数则相反。应用场景:计算向量之间的相似度、机器学习中的权重更新规则等。
  • JACCARD(杰卡德相似系数):说明:用于比较有限样本集之间的相似性和差异性,定义为两个集合交集的元素个数除以并集的元素个数。应用场景:文本分类、推荐系统的用户兴趣相似度计算、生物信息学中的基因序列比较等。
  • COSINE(余弦相似度):说明:通过计算两个非零向量的夹角余弦值来评估它们的方向性相似度,不受向量长度的影响,仅关注方向。应用场景:文档分类、文本挖掘、搜索引擎中的文档排名等。

这些参数的选择取决于具体的应用场景和需求,例如计算相似度的目的、数据的特性(如稀疏性、维度)等。后面看情况可能会单独开一篇文章单独介绍这块内容,目前我们掌握如何选取和使用即可,如下:

# 省略部分代码...
db = FAISS.from_documents(documents=documents, embedding=embedding, distance_strategy=DistanceStrategy.COSINE)

04 总结

为什么我们在大模型应用开发中需要Vector Store?这里主要是2点原因:

  • 我们不能每次处理源数据的时候都去做一次embedding获取向量,这样不合理且效率不高。
  • 我们通过向量数据库做相似度搜索,将相关数据给到 LLM,让它做输出会更加精确

以上就是本次 Vector Stores 的全部内容,希望能给到你们一些小小的帮助。

如果能帮我点个免费的关注,那就是对我个人的最大的肯定。
在这里插入图片描述

以上内容依据官方文档编写,官方地址:https://python.langchain.com/docs/modules/data_connection/vectorstores

Peace Baby~

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值