0

0

理解Langchain FAISS相似度搜索:嵌入模型、距离度量与结果解析

DDD

DDD

发布时间:2025-07-30 21:44:12

|

614人浏览过

|

来源于php中文网

原创

理解Langchain FAISS相似度搜索:嵌入模型、距离度量与结果解析

本文深入探讨Langchain中FAISS向量库进行相似度搜索时,如何理解和优化其返回结果。重点分析了嵌入模型选择、距离度量(如余弦相似度和L2距离)对相似度分数的影响,以及normalize_embeddings参数的关键作用。通过实例代码,指导读者正确解读搜索结果,并提供调试与优化策略,确保获得准确高效的向量相似度匹配。

向量嵌入与相似度搜索基础

在现代自然语言处理(nlp)应用中,将文本转化为高维向量(即“嵌入”或“embedding”)是实现语义理解和相似度匹配的关键步骤。这些向量捕捉了文本的语义信息,使得语义上相近的文本在向量空间中距离更近。faiss(facebook ai similarity search)是一个高效的相似度搜索库,它能够快速地在大规模向量集合中查找与查询向量最相似的向量。langchain作为llm应用开发框架,集成了faiss及多种嵌入模型,极大地简化了向量搜索的实现。

当我们在Langchain中使用FAISS进行相似度搜索时,核心任务是找到与给定查询文本语义最接近的文档。这个“接近”程度通常通过计算向量之间的“距离”或“相似度”来衡量,并以一个分数形式返回。

核心概念:距离度量

理解相似度分数的前提是了解其背后的距离度量方法。不同的度量方法对分数的解释截然不同。在向量搜索中,最常用的两种度量是余弦相似度(Cosine Similarity)和L2距离(Euclidean Distance)。

1. 余弦相似度 (Cosine Similarity)

余弦相似度衡量的是两个向量在方向上的接近程度,而非大小。它的计算基于向量夹角的余弦值:

  • 分数范围: 通常在-1到1之间。如果向量经过归一化(即长度为1),则范围在0到1之间。
  • 解释: 分数越高表示两个向量方向越一致,语义越相似。1.0表示完全相同,0表示不相关,-1表示完全相反。
  • 应用场景: 适用于文本相似度匹配,因为它不受文本长度或词频的影响,只关注语义方向。
  • 与 normalize_embeddings=True 的关联: 当嵌入模型配置中设置 encode_kwargs={'normalize_embeddings': True} 时,通常意味着生成的嵌入向量会被归一化。在FAISS中,对归一化向量执行内积(dot product)操作,其结果等同于余弦相似度。

2. L2距离 (欧氏距离 Euclidean Distance)

L2距离是多维空间中两点之间的直线距离。

  • 分数范围: 0到无穷大。
  • 解释: 分数越低表示两个向量距离越近,语义越相似。0.0表示完全相同。
  • 应用场景: 适用于需要严格衡量向量空间中“物理”距离的场景。
  • FAISS默认: FAISS在没有明确指定距离度量或当向量未归一化时,可能默认采用L2距离。

Langchain FAISS中的相似度搜索与分数解读

在使用Langchain的FAISS.similarity_search_with_score()方法时,返回的分数解释取决于底层的距离度量。

考虑以下使用BGE(BAAI/bge-large-zh-v1.5)嵌入模型,并设置了normalize_embeddings=True的示例:

from langchain_community.embeddings import HuggingFaceBgeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document

# 1. 配置嵌入模型
model_name = "BAAI/bge-large-zh-v1.5"
model_kwargs = {'device': 'cuda'} # 根据实际设备调整,如'cpu'
encode_kwargs = {'normalize_embeddings': True} # 启用归一化,通常与余弦相似度匹配

model = HuggingFaceBgeEmbeddings(
    model_name=model_name,
    model_kwargs=model_kwargs,
    encode_kwargs=encode_kwargs,
    cache_folder="../model/", # 模型缓存路径
)

# 2. 准备文档并创建FAISS索引 (这里假设已有一个FAISS数据库)
# 假设我们有一个文档列表
docs = [
    Document(page_content='无纸化发送失败?'),
    Document(page_content='凭证打包失败?'),
    Document(page_content='edi发送不了?'),
    # ...更多文档
]
# 如果是首次创建,可以使用 FAISS.from_documents(docs, embeddings)
# db = FAISS.from_documents(docs, model)
# db.save_local('../dataset/bge_faiss_db', index_name='index')

# 3. 加载FAISS数据库
db = FAISS.load_local('../dataset/bge_faiss_db', embeddings=model, index_name='index')

# 4. 执行相似度搜索
query = '无纸化发送失败?'
res = db.similarity_search_with_score(query, k=3)

print("搜索结果 (BGE模型, normalize_embeddings=True):")
for doc, score in res:
    print(f"(Document(page_content='{doc.page_content}'), {score})")

# 示例输出:
# (Document(page_content='无纸化发送失败?'), 0.9069208)
# (Document(page_content='凭证打包失败?'), 0.57983273)
# (Document(page_content='edi发送不了?'), 0.5719995)

分数解读: 在这个例子中,查询字符串与数据库中的一个文档完全相同,但返回的相似度分数是0.9069208,而非完美的1.0。这可能让一些用户感到困惑,误认为分数“低”。然而,由于normalize_embeddings=True,FAISS通常会使用余弦相似度(或等效的内积)进行计算。对于余弦相似度而言,0.9069208是一个非常高的匹配分数,表明两者语义高度一致。未达到完美的1.0可能是由于浮点数计算精度、嵌入模型在处理完全相同文本时产生的微小差异,或是FAISS内部处理的细微误差。因此,对于余弦相似度,0.9以上的得分通常被认为是强匹配。

优化与调试策略

当相似度搜索结果不符合预期时,可以从以下几个方面进行优化和调试:

VIVA
VIVA

一个免费的AI创意视觉设计平台

下载

1. 选择合适的嵌入模型

不同的嵌入模型在处理特定语言、领域或任务时表现各异。虽然BGE模型在中文领域表现优秀,但并非所有场景都能产生“完美”的1.0余弦相似度。尝试其他嵌入模型,特别是那些经过特定训练或在你的数据集上表现更好的模型,可能会改善结果。

2. 明确距离度量与参数配置

理解你的嵌入模型和FAISS如何交互是关键。

  • normalize_embeddings 参数: 当设置为 True 时,模型会输出归一化向量,这使得余弦相似度成为一个自然的选择。如果你的应用需要严格的“距离”概念(例如,在某个阈值内),并且你期望0.0表示完美匹配,那么L2距离可能更直观。
  • FAISS默认行为: FAISS在内部可以配置不同的索引类型来使用不同的距离度量(如IndexFlatL2代表L2距离,IndexFlatIP代表内积,对于归一化向量等同于余弦相似度)。Langchain在与嵌入模型集成时,会根据嵌入模型的特性和normalize_embeddings参数来选择或配置FAISS索引。

3. 示例代码:使用OpenAIEmbeddings和L2距离

为了对比,我们可以尝试一个通常默认使用L2距离的设置,例如OpenAIEmbeddings(尽管其内部向量也可能被归一化,但FAISS在默认情况下可能将其视为L2距离)。

from langchain_community.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings # 导入OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document

# 1. 配置嵌入模型 (OpenAIEmbeddings通常在FAISS中倾向于L2距离)
# 请确保已设置 OPENAI_API_KEY 环境变量
embeddings = OpenAIEmbeddings()

# 2. 准备文档并创建FAISS索引
# 假设text.txt内容为:
# 无纸化发送失败?
# 凭证打包失败?
# edi发送不了?
# ...
# loader = TextLoader("./text.txt", encoding="utf-8")
# documents = loader.load()
# text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
# docs = text_splitter.split_documents(documents)

# 为了演示,直接创建文档
docs = [
    Document(page_content='无纸化发送失败?', metadata={'source': './text.txt'}),
    Document(page_content='凭证打包失败?', metadata={'source': './text.txt'}),
    Document(page_content='edi发送不了?', metadata={'source': './text.txt'}),
]

db_openai = FAISS.from_documents(docs, embeddings)

# 3. 执行相似度搜索
query = '无纸化发送失败?'
res_openai = db_openai.similarity_search_with_score(query, k=3)

print("\n搜索结果 (OpenAIEmbeddings, L2距离):")
for doc, score in res_openai:
    print(f"(Document(page_content='{doc.page_content}', metadata={doc.metadata}), {score})")

# 示例输出:
# (Document(page_content='无纸化发送失败?', metadata={'source': './text.txt'}), 0.0)
# (Document(page_content='凭证打包失败?', metadata={'source': './text.txt'}), 0.08518691) # 假设的近似值
# (Document(page_content='edi发送不了?', metadata={'source': './text.txt'}), 0.12345678) # 假设的近似值

对比分析: 使用OpenAIEmbeddings时,对于完全相同的查询,返回的分数是0.0。这印证了FAISS在与某些嵌入模型结合时,会默认采用L2距离,而L2距离下0.0代表完美匹配。这种情况下,用户对“完美匹配”的期望得到了满足。

4. 注意事项

  • 浮点精度: 计算机处理浮点数存在精度限制,即使是完全相同的输入,经过复杂的嵌入和距离计算后,也可能产生微小的非零偏差。
  • 模型特性: 不同的嵌入模型对文本的编码方式不同,即使是语义完全相同的文本,其向量表示也可能存在细微差异。
  • 阈值设定: 在实际应用中,不应期望完美匹配总是返回1.0(余弦)或0.0(L2)。更重要的是设定一个合理的相似度阈值,高于该阈值则认为匹配成功。例如,对于余弦相似度,可以设定0.8或0.85为高相似度阈值。

总结

Langchain中FAISS的相似度搜索结果解读,关键在于理解其背后的嵌入模型配置和距离度量方式。

  1. 余弦相似度(通常与normalize_embeddings=True关联):分数越高越好,1.0是理想完美匹配,但0.9以上已是非常高的相似度。
  2. L2距离:分数越低越好,0.0是理想完美匹配。

当遇到“低”相似度分数时,首先应确认所使用的距离度量,并根据其特性来判断分数的实际含义。如果对“完美匹配”的0.0或1.0有强烈的追求,可以尝试更换嵌入模型或调整FAISS的索引类型配置,以匹配期望的距离度量行为。最终,在实际应用中,更重要的是根据业务需求设定合理的相似度阈值,而非一味追求理论上的完美分数。

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

250

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

205

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1435

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

609

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

547

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

539

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

158

2025.07.29

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

77

2025.08.07

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

74

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.2万人学习

React中文开发手册
React中文开发手册

共0课时 | 0人学习

react快速入门
react快速入门

共14课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号