datawhale RAG技术全栈指南 202509 第3次作业
目录
Task03:索引构建(第三章)
第一节 向量嵌入vector embedding
给AI装上一副“语义眼镜”:走进向量嵌入的神奇世界
一、Embedding是什么?AI的“语义翻译官”
二、Embedding如何助力RAG?扮演“最强大脑图书管理员”
三、Embedding技术的进化之旅:从“死记硬背”到“融会贯通”
四、如何为你的AI项目挑选“最佳翻译官”?
总结
第二节 多模态嵌入(Multimodal Embedding)
打破模态墙:当AI学会“融会贯通”
一、为什么要“多模态”?打破信息的“巴别塔”
二、CLIP模型:图文理解的“跨界大师”
三、新一代多模态模型:更强大、更全面(以BGE-M3为例)
代码示例
1 环境准备
手动下载方案(推荐)
作业1:
🔍 结果详细分析
第三节 向量数据库
向量数据库:AI的“超级记忆宫殿”
一、为什么需要它?从“小书柜”到“国家图书馆”的挑战
二、它如何工作?建造宫殿的“智能地图”
三、主流产品介绍:不同特色的“记忆宫殿”
作业2:
第四节 Milvus介绍及多模态检索实践
认识Milvus:AI时代的“超级图书馆管理员”
一、快速体验:用Docker一键启动你的“迷你图书馆”
二、核心概念:理解“图书馆”的运作方式
三、核心技术:“超级检索”的魔法背后
四、高级检索技巧:让搜索更智能
✅ 解决方案:在本地 Windows 安装 Docker Desktop
第五节 索引优化
让RAG更聪明:两种索引优化策略
一、句子窗口检索:精准定位+完整语境
二、结构化索引:先筛选再搜索
为什么理解原理比死记工具更重要?
Task03:索引构建(第三章)
第一节 向量嵌入vector embedding
给AI装上一副“语义眼镜”:走进向量嵌入的神奇世界
嘿,同学们!想象一下,如果AI能真正“读懂”我们的话,而不是仅仅匹配关键词,那该多酷?比如,你问“怎么照顾一只宠物猫?”,它能精准地找出讲“猫咪饲养指南”的文章,而不是给你一堆关于“猫科动物分类”的论文。这背后的魔法,就来自于一项叫做向量嵌入(Embedding) 的核心技术。今天,我们就来揭开它的神秘面纱!
一、Embedding是什么?AI的“语义翻译官”
简单来说,Embedding就是一位超级“翻译官”,它的任务是把人类世界复杂的信息(文字、图片、声音),翻译成AI能理解的“数学语言”——也就是一串数字组成的向量。
- 原始信息:比如“猫”这个词,或者一张猫咪的图片。
- Embedding翻译官:一个训练有素的AI模型。
- 输出结果:一个向量,就像一组独一无二的“坐标”,比如
[0.16, 0.29, -0.88, ...]
。这个坐标定义了一个词或一张图在AI大脑这个“超级语义宇宙”中的位置。
这个“语义宇宙”的神奇法则:物以类聚!
Embedding最厉害的地方在于,它构建的“语义宇宙”是有逻辑的!意思相近的东西,它们的“坐标”在宇宙中就会离得很近;意思不相关的东西,坐标就会离得很远。
- “猫”和“狗”(都是宠物)的坐标会靠得很近。
- “猫”和“汽车” 的坐标就会非常遥远。
- 我们通过计算余弦相似度(可以理解为比较两个坐标的“方向”是否一致)这样的数学工具,就能量化这种“语义亲密度”。
二、Embedding如何助力RAG?扮演“最强大脑图书管理员”
还记得我们之前聊过的RAG吗?它就是让AI在回答问题前,先从一个巨大的知识库(比如公司文档、百科全书)里查找相关资料。而Embedding,就是这个过程中的 “最强大脑图书管理员”。
它的工作流程超级高效:
- 整理图书(离线索引):知识库里的所有文章,都会被切分成小块。然后,Embedding翻译官为每一块内容生成一个唯一的“坐标”(向量),并存入一个特殊的“坐标图书馆”(向量数据库)。
- 理解问题(在线查询):当你提出一个问题时,同一位Embedding翻译官也会为你的问题生成一个“坐标”。
- 精准找书(语义检索):这位图书管理员会迅速在你的问题“坐标”和图书馆里所有内容的“坐标”之间进行计算,找出那些和你的问题“方向”最一致、位置最接近的几块内容。
- 递交资料(生成答案):找到的这些最相关的内容,会作为参考材料递给像ChatGPT这样的大语言模型,让它生成一个准确又靠谱的答案。
所以,Embedding的质量至关重要! 一个优秀的Embedding模型,就像是一个知识渊博、理解力超强的图书管理员,能精准把握你问题的深层含义。而一个差劲的模型,则可能给你找来回一堆不相关的“垃圾信息”,导致AI给出错误的答案。
三、Embedding技术的进化之旅:从“死记硬背”到“融会贯通”
这项技术也不是一天练成的,它经历了一场有趣的进化:
- 第一代:静态词嵌入(像背字典)
- 代表:Word2Vec(2013年)
- 特点:每个词都有一个固定的向量,就像字典里每个词都有一个固定的解释。
- 缺陷:无法理解一词多义!比如“苹果”这个词,无论是在“苹果手机”还是“吃苹果”里,它的向量都是一样的,AI无法区分差异。
- 第二代:动态上下文嵌入(学会联系上下文)
- 代表:BERT(2018年)
- 革命:基于Transformer架构的BERT模型诞生了!它生成的向量是动态的,会根据一个词在句子中的具体含义来决定。
- 效果:在“我想买苹果”和“这个苹果很甜”中,“苹果”会得到两个不同的向量,AI终于能分清你指的是公司还是水果了!
- 第三代:为RAG量身定制(成为专业领域专家)
- 新要求:为了在医疗、法律等专业领域也能表现出色,Embedding模型需要具备领域自适应能力。同时,还要能处理长文档、图片等多类型信息。
四、如何为你的AI项目挑选“最佳翻译官”?
面对成千上万个Embedding模型,该怎么选呢?别慌,这里有一份“选秀指南”:
- 查看“英雄榜”:MTEB排行榜
这是一个权威的模型评测网站,就像“模型世界杯”的积分榜。你可以根据综合得分、模型大小(越大通常越强,但也越吃硬件)、支持的语言(一定要选支持中文的!)和能处理的文本长度来快速筛选。 - 明确你的“选秀标准”
- 核心任务:如果你是做RAG,重点看模型在“检索”这项任务上的排名。
- 硬件条件:你的电脑或服务器能带动多大的模型?要量力而行。
- 文本长度:你的文档通常有多长?要确保模型的“处理长度”上限够用。
- 组织“终极试镜”
公开榜单只是参考,真正的考验在你的业务场景里。最好的方法是:- 准备考题:收集一些你们业务中真实的问题和标准答案。
- 让候选模型实战:让几个初步选中的模型在你的“考题”上跑一跑,看谁找资料最准、最快。
- 选出冠军:通过对比测试,最终选出最适合你项目的那个“最强翻译官”!
总结
看,Embedding这项技术就像是为AI戴上了一副神奇的“语义眼镜”,让它能真正“看见”语言的深层含义。从简单的“背字典”到复杂的“理解上下文”,它的进化推动着AI变得越来越智能。现在,无论是你手机里的语音助手,还是能和你畅聊的AI,背后都有这位无声的“翻译官”在默默工作。
第二节 多模态嵌入(Multimodal Embedding)
打破模态墙:当AI学会“融会贯通”
想象一下,如果AI能像我们人类一样,同时用眼睛看、用耳朵听、用大脑理解,那该多强大?现代AI的一项重要突破,正是让模型从只理解文字,进化到能统一理解图像、声音、视频等多种信息。这项技术就像教AI学会了“融会贯通”,而它的核心秘诀,就藏在多模态嵌入(Multimodal Embedding) 之中。
一、为什么要“多模态”?打破信息的“巴别塔”
我们之前学过,Embedding能把文字变成AI懂的向量坐标。但如果AI只懂文字,那它就像一个只能听广播、却不能看电视的人,理解的世界是不完整的。
现实世界的信息是多模态的:我们既看图片、视频,也听声音、读文字。问题来了:传统的文本Embedding和图像Embedding,是在两个完全不同的坐标系里工作的。这就像中文和摩斯密码,互不相通,中间隔着一堵厚厚的 “模态墙”。
- 你问AI:“找一张红色汽车的照片。”
- AI的困境:它有一个强大的“文本坐标系”来理解“红色汽车”这个词,也有一个强大的“图像坐标系”来识别图片内容。但它不知道这两个坐标系如何对应!它无法将文字世界的“红色汽车”与图片世界中的那辆红色汽车联系起来。
多模态嵌入的目标,就是担任一位“宇宙级的地图绘制师”,为所有不同类型的数据建立一個统一的、共享的语义坐标系! 在这张新地图上,“一只奔跑的狗”这段文字的坐标,会和一张真实小狗奔跑的图片的坐标紧紧挨在一起。这样,AI就能实现真正的跨模态检索和理解。
二、CLIP模型:图文理解的“跨界大师”
在打破“模态墙”的征程中,OpenAI发布的CLIP模型是一个里程碑式的存在。它的设计非常巧妙。
CLIP的“双子星”架构
CLIP模型内部有两位“专家”:
- 图像编码器:专门负责“看”图片,将任何图像转换成一个向量。
- 文本编码器:专门负责“读”文字,将任何一段描述转换成一个向量。
最关键的是,CLIP通过训练,让这两位专家使用同一套坐标系统!它们输出的向量在同一个空间里可以直接比较。
CLIP的“拉郎配”学习法:对比学习
CLIP是如何学会给图文配对的呢?它使用了一种叫对比学习的方法。这个过程就像一个高效的“配对游戏”:
- 输入:一批(比如1万张)图片和它们对应的文字描述。
- 目标:模型的任务是,拉近正确图文对的向量距离(比如“猫的图片”和“一只猫”的描述),同时推远所有错误配对的向量距离(比如“猫的图片”和“一辆汽车”的描述)。
- 结果:经过在海量互联网数据上玩这个游戏,CLIP最终学会了将语义相关的图片和文字自动“拴”在一起。
CLIP的超级能力:“零样本”分类
这种训练方式给了CLIP一项酷炫的超能力——零样本识别。比如,你给它一张狗的图片,再给它几个选项的文字描述:“一张狗的照片”、“一张猫的照片”、“一辆汽车的照片”,它不需要事先学习过如何识别狗,就能通过计算图片向量与每个文本向量的相似度,选出最匹配的“一张狗的照片”。这为AI的通用性打开了新的大门。
三、新一代多模态模型:更强大、更全面(以BGE-M3为例)
在CLIP之后,科学家们不断推出更强大的模型。其中,由北京智源研究院推出的BGE-M3就是一个非常出色的“多面手”。它的名字里的“M3”就代表了它的三大核心本领:
- 多语言:天生就能理解超过100种语言。你用中文问“红色汽车”,它不仅能找到英文标注“red car”的图片,还能找到法文、德文标注的图片。
- 多功能:一个模型,多种用法。它既支持CLIP那样的密集检索(比较整个向量的相似度),也支持更精细的多向量检索(比较图像多个局部的向量),甚至还能同时进行传统的关键词匹配,适应各种复杂场景。
- 多粒度:既能处理短短的图片标题,也能处理长达几千字的长篇文档,胃口特别好。
BGE-M3的“火眼金睛”:网格嵌入
BGE-M3在“看图”的方式上也有创新。CLIP通常把一整张图片当作一个整体来理解。而BGE-M3采用了一种更精细的 “网格嵌入” 法。
- 它把一张图片像棋盘一样分成许多个小格子,然后对每个小格子进行独立编码。
- 这样做的好处是,模型能更好地捕捉图片中的局部细节。比如,一张“草地上有只猫和一只狗”的图片,模型不仅能理解整张图,还能分别定位到猫和狗所在的位置,理解能力更上一层楼!
从CLIP到BGE-M3,多模态嵌入技术正朝着更统一、更智能的方向飞速发展,让AI离真正“理解”我们丰富多彩的世界越来越近。
代码示例
1 环境准备
步骤1:安装 visual_bge 模块
# 进入 visual_bge 目录
cd code/C3/visual_bge# 安装 visual_bge 模块及其依赖
pip install -e .# 返回上级目录
cd ..
步骤2:下载模型权重
# 运行模型下载脚本
python download_model.py
手动下载方案(推荐)
-
访问 Hugging Face 页面:
-
打开 https://huggingface.co/BAAI/bge-visualized/tree/main
-
点击
Visualized_base_en_v1.5.pth
文件右侧的下载按钮
-
-
下载
pip install modelscope
下载完整模型库
modelscope download --model BAAI/bge-base-en-v1.5
修改程序如下:
# 01_bge_visualized.py
# 功能:使用本地加载的 Visualized_BGE 模型进行多模态嵌入与相似度计算
# 作者:Datawhale All-in-RAG
# 说明:所有模型均从本地加载,避免网络请求和 SSL 证书问题import torch
from visual_bge.visual_bge.modeling import Visualized_BGE# ==============================
# 📁 模型路径配置(全部使用相对路径)
# ==============================
# BGE 文本编码器:指向你复制到项目 models/ 下的 bge-base-en-v1.5
BGE_MODEL_PATH = "../../models/bge-base-en-v1.5"# 多模态适配权重:你已有的 Visualized_BGE 微调权重
VISUAL_BGE_WEIGHT_PATH = "../../models/bge/Visualized_base_en_v1.5.pth"# 初始化模型(完全离线)
model = Visualized_BGE(model_name_bge=BGE_MODEL_PATH, # 本地 BGE 模型路径model_weight=VISUAL_BGE_WEIGHT_PATH # 本地多模态权重路径
)
model.eval()# ==============================
# 🧪 编码测试:文本、图像、图文融合
# ==============================
with torch.no_grad():text_emb = model.encode(text="datawhale开源组织的logo")img_emb_1 = model.encode(image="../../data/C3/imgs/datawhale01.png")multi_emb_1 = model.encode(image="../../data/C3/imgs/datawhale01.png", text="datawhale开源组织的logo")img_emb_2 = model.encode(image="../../data/C3/imgs/datawhale02.png")multi_emb_2 = model.encode(image="../../data/C3/imgs/datawhale02.png", text="datawhale开源组织的logo")# ==============================
# 🔍 相似度计算(余弦相似度,因向量已归一化,直接点积)
# ==============================
sim_1 = img_emb_1 @ img_emb_2.T
sim_2 = img_emb_1 @ multi_emb_1.T
sim_3 = text_emb @ multi_emb_1.T
sim_4 = multi_emb_1 @ multi_emb_2.Tprint("=== 相似度计算结果 ===")
print(f"纯图像 vs 纯图像: {sim_1.item():.4f}")
print(f"图文结合1 vs 纯图像: {sim_2.item():.4f}")
print(f"图文结合1 vs 纯文本: {sim_3.item():.4f}")
print(f"图文结合1 vs 图文结合2: {sim_4.item():.4f}")# ==============================
# 📊 嵌入向量信息分析
# ==============================
print("\n=== 嵌入向量信息 ===")
print(f"多模态向量维度: {multi_emb_1.shape}")
print(f"图像向量维度: {img_emb_1.shape}")
print(f"多模态向量示例 (前10个元素): {multi_emb_1[0][:10].cpu().numpy()}")
print(f"图像向量示例 (前10个元素): {img_emb_1[0][:10].cpu().numpy()}")
显示结果如下:
=== 相似度计算结果 ===
纯图像 vs 纯图像: 0.8318
图文结合1 vs 纯图像: 0.8291
图文结合1 vs 纯文本: 0.7627
图文结合1 vs 图文结合2: 0.9058=== 嵌入向量信息 ===
多模态向量维度: torch.Size([1, 768])
图像向量维度: torch.Size([1, 768])
多模态向量示例 (前10个元素): [ 0.03599895 -0.00321014 -0.03766303 0.02401761 0.01403442 0.03398741
0.01475963 0.0292121 0.00599695 -0.01447793]
图像向量示例 (前10个元素): [ 0.04065671 -0.06057012 -0.00366566 0.00731354 0.03045561 0.03183692
0.01319521 0.04422256 -0.03800168 -0.02704623]
作业1:
尝试把代码中的部分文本替换一下,比如将datawhale开源组织的logo
替换为blue whale
看看结果有什么不同。
# hk1_compare.py
import torch
from visual_bge.visual_bge.modeling import Visualized_BGE# 模型路径(保持不变)
model = Visualized_BGE(model_name_bge="../../models/bge-base-en-v1.5",model_weight="../../models/bge/Visualized_base_en_v1.5.pth"
)
model.eval()# 图像路径
img1_path = "../../data/C3/imgs/datawhale01.png"
img2_path = "../../data/C3/imgs/datawhale02.png"# 两种文本描述
text_original = "datawhale开源组织的logo"
text_new = "blue whale"with torch.no_grad():# 原始文本text_emb_orig = model.encode(text=text_original)multi_emb_1_orig = model.encode(image=img1_path, text=text_original)multi_emb_2_orig = model.encode(image=img2_path, text=text_original)# 新文本text_emb_new = model.encode(text=text_new)multi_emb_1_new = model.encode(image=img1_path, text=text_new)multi_emb_2_new = model.encode(image=img2_path, text=text_new)# 纯图像(不变)img_emb_1 = model.encode(image=img1_path)img_emb_2 = model.encode(image=img2_path)# 计算相似度
def sim(a, b):return (a @ b.T).item()print("=== 使用文本: 'datawhale开源组织的logo' ===")
print(f"图文1 vs 图像1: {sim(multi_emb_1_orig, img_emb_1):.4f}")
print(f"图文1 vs 文本: {sim(multi_emb_1_orig, text_emb_orig):.4f}")
print(f"图文1 vs 图文2: {sim(multi_emb_1_orig, multi_emb_2_orig):.4f}")print("\n=== 使用文本: 'blue whale' ===")
print(f"图文1 vs 图像1: {sim(multi_emb_1_new, img_emb_1):.4f}")
print(f"图文1 vs 文本: {sim(multi_emb_1_new, text_emb_new):.4f}")
print(f"图文1 vs 图文2: {sim(multi_emb_1_new, multi_emb_2_new):.4f}")print("\n=== 跨文本对比(关键!)===")
print(f"用'blue whale'描述的图文1 vs 原始文本: {sim(multi_emb_1_new, text_emb_orig):.4f}")
print(f"用'blue whale'描述的图文1 vs 'blue whale'文本: {sim(multi_emb_1_new, text_emb_new):.4f}")
显示结果:
=== 使用文本: 'datawhale开源组织的logo' ===
图文1 vs 图像1: 0.8291
图文1 vs 文本: 0.7627
图文1 vs 图文2: 0.9058=== 使用文本: 'blue whale' ===
图文1 vs 图像1: 0.9218
图文1 vs 文本: 0.7572
图文1 vs 图文2: 0.8719=== 跨文本对比(关键!)===
用'blue whale'描述的图文1 vs 原始文本: 0.4644
用'blue whale'描述的图文1 vs 'blue whale'文本: 0.7572
🔍 结果详细分析
✅ 1. 图文1 vs 图像1:0.8291 → 0.9218(显著提升)
- 现象:用
"blue whale"
作为文本提示时,融合向量(multi_emb)与原始图像向量(img_emb)的相似度大幅提高。 - 原因推测:
- “datawhale开源组织的logo” 是一个非常具体、抽象的描述,包含“开源组织”“logo”等非视觉语义,模型难以将其与像素内容对齐;
- “blue whale” 是一个具象的、视觉相关的词,即使图像不是真实蓝鲸,模型也可能从 logo 的蓝色、鲸鱼形状中提取到相关特征;
- 因此,“blue whale” 与图像的视觉语义更匹配,导致多模态融合时更“信任”图像,向量更靠近图像侧。
💡 这说明:文本提示的质量直接影响多模态对齐效果。越具象、越视觉化的文本,越容易与图像融合。
✅ 2. 图文1 vs 文本:0.7627 → 0.7572(基本不变)
- 两种文本下,融合向量与对应文本的相似度都约在 0.75~0.76。
- 说明模型在两种情况下都较好地编码了文本语义,没有明显偏向。
✅ 3. 图文1 vs 图文2:0.9058 → 0.8719(略有下降)
- 两张不同 logo 图像,在使用
"datawhale..."
时相似度更高(0.9058),因为文本强调了“同一个组织的 logo”,增强了语义一致性; - 而
"blue whale"
是通用描述,无法区分两张 logo 的“同源性”,所以相似度略降。
📌 这体现了文本在跨样本对齐中的作用:特定文本能拉近同类样本,通用文本则弱化这种关系。
✅ 4. 跨文本对比:0.4644 vs 0.7572(巨大差异)
- 用
"blue whale"
生成的图文向量,与原始文本"datawhale..."
的相似度只有 0.46(很低); - 但与
"blue whale"
文本的相似度是 0.7572(高)。 - 结论:多模态向量强烈依赖输入文本,换文本就等于换语义空间!
🔑 关键洞见:
Visualized_BGE 的多模态嵌入 = f(图像, 文本)
改变文本,就改变了整个嵌入的语义方向。
🧠 总结:文本提示(Prompt)的重要性
文本类型 | 特点 | 对多模态融合的影响 |
---|---|---|
具体、抽象文本<br>(如 “datawhale开源组织的logo”) | 包含非视觉语义(“开源”“组织”) | 可能与图像对齐较差,但能增强同类样本一致性 |
具象、视觉化文本<br>(如 “blue whale”) | 描述颜色、形状、物体类别 | 与图像特征更匹配,提升图文一致性,但泛化性强、区分度弱 |
💡 实践建议
-
如果你做图像检索:
- 使用视觉相关的关键词(如 “blue whale icon”, “blue whale logo”)效果可能更好;
- 避免加入“开源组织”这类非视觉信息。
-
如果你做语义对齐:
- 确保文本与图像内容语义一致;
- 可尝试多种 prompt,选相似度最高的。
第三节 向量数据库
向量数据库:AI的“超级记忆宫殿”
想象一下,AI模型就像一个学识渊博的“大脑”,它通过Embedding技术,把书本里的每一段知识都变成了一个独特的“思维坐标”。但是,当这个大脑读完了整个图书馆,拥有了数亿个“思维坐标”时,一个新问题出现了:如何在眨眼间,从这数亿个坐标里,找到与你的问题最相关的几个?
这个难题,就需要向量数据库来解决。它就像为AI建造的一个 “超级记忆宫殿” ,专门负责高效地存储和快速查找这些海量的向量坐标。
一、为什么需要它?从“小书柜”到“国家图书馆”的挑战
你可以把简单的向量存储想象成一个小书柜。当你的书(向量)只有几十本时,你一眼就能找到想要的那本。但当你的书多到像一座国家图书馆(数亿向量)时,你再一本一本地去找,恐怕一辈子都找不到。
传统数据库(如MySQL)就像一位严谨的“档案管理员”,它擅长的是:“请精确地找出编号为A-1024的档案”。这种精确匹配是它的强项。
但AI需要的是:“请找出所有和‘宇宙探索’这个主题最相似的10本书。” 这是一个相似性搜索问题。让传统数据库去做这件事,就像让档案管理员用尺子去量所有书籍封面颜色与“蓝色”的相似度,效率极低,根本无法完成。
而向量数据库,就是一位专门训练出来的“最强大脑图书检索员”。它的核心价值就在于:能在数十亿的向量中,实现毫秒级的相似性搜索,快速为你找到最相关的信息。
“超级记忆宫殿”的五大本领:
- 光速检索:利用特殊的“索引地图”(如HNSW算法),实现近似最近邻搜索,速度极快。
- 海量存储:专门为存储成百上千维的向量而优化,轻松管理千亿级别的“知识坐标”。
- 混合查询:不仅能按语义找,还能结合条件筛。例如:“找出与‘新能源汽车’相关,且是2023年之后的文档。”
- 弹性伸缩:采用分布式架构,就像宫殿可以随时加盖新的侧殿,数据量增大时只需增加服务器即可。
- 无缝集成:能轻松与LangChain、LlamaIndex等AI应用框架连接,方便开发者构建智能应用。
二、它如何工作?建造宫殿的“智能地图”
向量数据库能如此高效,关键在于它使用了特殊的“索引”技术。这就像给巨大的记忆宫殿绘制了一张智能地图,而不是一本死板的目录。
常见的几种“绘图术”包括:
- 基于图的方法(如HNSW):像一张复杂的地铁线路图。站点就是向量,相似的向量之间有“线路”直连。搜索时,就像从某个站点出发,沿着最快线路迅速抵达目标站点群。这是目前最流行、性能最好的方法之一。
- 基于量化的方法(如Faiss的PQ):像把世界地图按大洲、国家、省市进行分层分区。搜索时,先定位到大概区域(比如“北美洲”),再在这个区域里精细查找,大大缩小了搜索范围。
- 基于哈希的方法(如LSH):像给向量发“会员卡”,相似的向量会得到相同或相似的“卡号”(哈希值)。搜索时,只需要比较“卡号”就能快速找到相似的群体。
三、主流产品介绍:不同特色的“记忆宫殿”
市场上有许多各具特色的向量数据库,你可以根据你的“藏书量”和“查找需求”来选择:
- Pinecone:五星级“托管式智慧图书馆”
- 特点:完全托管,无需自己操心服务器和维护。高可用、低延迟,开箱即用。
- 适合:追求稳定、省心、有企业级需求的大型应用。
- Milvus:可自由设计的“开源图书馆蓝图”
- 特点:开源、功能强大、支持分布式部署,社区活跃。可以把它建成非常庞大的系统。
- 适合:需要高度自定义、处理超大规模数据的技术团队。
- Qdrant:高性能的“Rust动力跑车”
- 特点:用Rust语言编写,性能极佳,资源利用率高。
- 适合:对性能和资源消耗非常敏感的应用。
- Weaviate:自带AI的“智能知识图谱库”
- 特点:不仅存储向量,还内置了AI模块,原生支持多模态和GraphQL查询,更像一个智能知识库。
- 适合:希望快速构建复杂AI应用,尤其是涉及知识图谱的场景。
- Chroma:轻量便捷的“个人书房”
- 特点:极其轻量,安装简单,几行代码就能用。与LangChain等工具集成度极高。
- 适合:初学者、原型开发、小规模项目。如果你想快速体验RAG,这是最好的起点。
如何选择你的“宫殿”?
- 如果你是学生或初学者,想快速搭建一个demo,强烈推荐从 Chroma 开始,它最简单。
- 如果你要为公司构建一个严肃的、大规模的生产系统,那么应该评估 Pinecone(省心)或 Milvus(可控)。
- 如果你对性能有极致要求,可以试试 Qdrant。
总之,向量数据库是RAG系统中承上启下的关键一环,它将AI的“知识”妥善保管,并能随时响应我们的“召唤”,是让AI真正变得有用的强大基石。
第一步:确认环境已安装 faiss-cpu
python -c "import faiss; print(faiss.__version__)"
安装库:
modelscope download --model BAAI/bge-small-zh-v1.5
运行修改后的程序:
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings # 新路径
from langchain_core.documents import Document# 1. 准备文本
texts = ["张三是法外狂徒","FAISS是一个用于高效相似性搜索和密集向量聚类的库。","LangChain是一个用于开发由语言模型驱动的应用程序的框架。"
]
docs = [Document(page_content=t) for t in texts]# 2. 加载本地模型(注意路径分隔符)
local_model_path = r"E:\Datawhale\All in rag 202509\code\all-in-rag-main\models\bge-small-zh-v1___5"
embeddings = HuggingFaceEmbeddings(model_name=local_model_path)# 3. 构建并保存 FAISS 索引
vectorstore = FAISS.from_documents(docs, embeddings)
save_path = "./faiss_index_store"
vectorstore.save_local(save_path)
print(f"FAISS index has been saved to {save_path}")# 4. 加载索引并查询
loaded_vectorstore = FAISS.load_local(save_path,embeddings,allow_dangerous_deserialization=True
)query = "FAISS是做什么的?"
results = loaded_vectorstore.similarity_search(query, k=1)print(f"\n查询: '{query}'")
print("相似度最高的文档:")
for doc in results:print(f"- {doc.page_content}")
显示结果:
FAISS index has been saved to ./faiss_index_store
查询: 'FAISS是做什么的?'
相似度最高的文档:
- FAISS是一个用于高效相似性搜索和密集向量聚类的库。
索引创建实现细节:
通过深入 LangChain 源码,可以发现索引创建是一个分层、解耦的过程,主要涉及以下几个方法的嵌套调用:
-
from_documents
(封装层):- 这是我们直接调用的方法。它的职责很简单:从输入的
Document
对象列表中提取出纯文本内容 (page_content
) 和元数据 (metadata
)。 - 然后,它将这些提取出的信息传递给核心的
from_texts
方法。
- 这是我们直接调用的方法。它的职责很简单:从输入的
-
from_texts
(向量化入口):- 这个方法是面向用户的入口。它接收文本列表,并执行关键的第一步:调用
embedding.embed_documents(texts)
,将所有文本批量转换为向量。 - 完成向量化后,它并不直接处理索引构建,而是将生成的向量和其他所有信息(文本、元数据等)传递给一个内部的辅助方法
__from
。
- 这个方法是面向用户的入口。它接收文本列表,并执行关键的第一步:调用
-
__from
(构建索引框架):- 一个内部方法,负责搭建 FAISS 向量存储的“空框架”。
- 它会根据指定的距离策略(默认为 L2 欧氏距离)初始化一个空的 FAISS 索引结构(如
faiss.IndexFlatL2
)。 - 同时,它也准备好了用于存储文档原文的
docstore
和用于连接 FAISS 索引与文档的index_to_docstore_id
映射。 - 最后,它调用另一个内部方法
__add
来完成数据的填充。
-
__add
(填充数据):- 真正执行数据添加操作的核心。它接收到向量、文本和元数据后,执行以下关键操作:
- 添加向量: 将向量列表转换为 FAISS 需要的
numpy
数组,并调用self.index.add(vector)
将其批量添加到 FAISS 索引中。 - 存储文档: 将文本和元数据打包成
Document
对象,存入docstore
。 - 建立映射: 更新
index_to_docstore_id
字典,建立起 FAISS 内部的整数 ID(如 0, 1, 2...)到我们文档唯一 ID 的映射关系。
- 添加向量: 将向量列表转换为 FAISS 需要的
- 真正执行数据添加操作的核心。它接收到向量、文本和元数据后,执行以下关键操作:
作业2:
- LlamaIndex默认会将数据存储为透明可读的JSON格式,运行03_llamaindex_vector.py文件,查看保存的json文件内容。
修改程序如下:# 03_llamaindex_vector.py from llama_index.core import VectorStoreIndex, Document, Settings from llama_index.embeddings.huggingface import HuggingFaceEmbedding# 1. 配置全局嵌入模型(使用本地路径!) Settings.embed_model = HuggingFaceEmbedding(model_name="../../models/bge-small-zh-v1___5", # ✅ 关键修改trust_remote_code=False,device="cpu" )# 2. 创建示例文档 texts = ["张三是法外狂徒","LlamaIndex是一个用于构建和查询私有或领域特定数据的框架。","它提供了数据连接、索引和查询接口等工具。" ] docs = [Document(text=t) for t in texts]# 3. 创建索引并持久化到本地 index = VectorStoreIndex.from_documents(docs) persist_path = "./llamaindex_index_store" index.storage_context.persist(persist_dir=persist_path) print(f"LlamaIndex 索引已保存至: {persist_path}")
显示如下:
LlamaIndex 索引已保存至: ./llamaindex_index_store
运行下面的程序 view_index1.py
:
import json
import os# 指定索引目录
persist_dir = "./llamaindex_index_store"# 查看所有 JSON 文件
for filename in os.listdir(persist_dir):if filename.endswith(".json"):print(f"\n=== {filename} ===")with open(os.path.join(persist_dir, filename), "r", encoding="utf-8") as f:data = json.load(f)print(json.dumps(data, indent=2, ensure_ascii=False))
显示结果:
第四节 Milvus介绍及多模态检索实践
认识Milvus:AI时代的“超级图书馆管理员”
想象一下,AI大脑里装着数亿本书的知识向量,怎样才能在瞬间找到最相关的答案?这就需要一位专业的“图书管理员”——Milvus。它不是一个普通的数据库,而是一个专门为海量向量数据打造的超级搜索引擎,能轻松处理十亿甚至百亿级别的向量检索。
一、快速体验:用Docker一键启动你的“迷你图书馆”
想快速体验Milvus?最简单的方式就是使用Docker。这就像在你的电脑上快速搭建一个“迷你图书馆”。
第一步:准备“地基”(安装Docker)
确保你的电脑已经安装了Docker和Docker Compose。它们就像是建造图书馆所需的工具和蓝图。
第二步:下载“施工图纸”
打开终端,执行以下命令,下载Milvus的配置文件(docker-compose.yml
):
bash
# Mac/Linux 用户 wget https://github.com/milvus-io/milvus/releases/download/v2.5.14/milvus-standalone-docker-compose.yml -O docker-compose.yml# Windows 用户 (PowerShell) Invoke-WebRequest -Uri "https://github.com/milvus-io/milvus/releases/download/v2.5.14/milvus-standalone-docker-compose.yml" -OutFile "docker-compose.yml"
第三步:“开工建造”
在配置文件所在目录,运行一句魔法般的命令:
bash
docker compose up -d
Docker会自动下载所需镜像并启动服务。稍等片刻,你的“迷你Milvus图书馆”就建好了!你可以通过 docker ps
命令查看运行中的服务。
二、核心概念:理解“图书馆”的运作方式
要用好Milvus,需要理解它的几个核心概念,它们就像一个现代化图书馆的管理体系:
-
Collection(集合) = 整个图书馆
-
这是最高级别的容器,相当于一个完整的图书馆,用于存放某一类数据的所有信息。
-
-
Partition(分区) = 图书馆里的不同区域
-
比如“科技区”、“文学区”、“历史区”。把书分门别类放在不同区域,找起来就快得多。Milvus也通过分区来提升检索效率。
-
-
Schema(模式) = 图书的编目规则
-
规定每本书需要登记哪些信息:书名(主键)、作者(元数据)、内容摘要(向量)等。一个好的Schema是高效检索的基础。
-
-
Entity(实体) = 一本具体的书
-
就是一条具体的数据,包含它的各种属性和对应的向量。
-
-
Alias(别名) = 图书馆的“临时指示牌”
-
这是一个非常聪明的设计!比如图书馆要装修升级,你可以悄悄把新书搬到“新馆”,然后把入口的“综合阅览室”指示牌(别名)从“旧馆”指向“新馆”。读者完全感觉不到变化,就能享受到更新的资源。这实现了数据的无缝平滑升级。
-
三、核心技术:“超级检索”的魔法背后
Milvus之所以能如此快地找到信息,关键在于它使用了特殊的“索引”技术。这就像给图书馆绘制了不同的“智能检索地图”。
1. FLAT索引:最“老实”的检索员
-
工作方式:拿到一本书的查询请求后,会跑遍图书馆的每一个书架,逐一对比,确保找到最匹配的那本。
-
优点:结果100%准确。
-
缺点:速度太慢,只适合藏书量很少的小书吧。
2. IVF系列索引:聪明的“分区管理员”
-
工作方式:先把所有书按主题分成几百个区域(比如“人工智能区”、“科幻小说区”)。当你来找书时,它先判断你的书属于哪个主题区,然后只在这个区域内进行精细查找。
-
优点:大大缩小搜索范围,速度和精度的平衡之选。
-
适用:大多数通用场景。
3. HNSW索引:拥有“空间跳跃”能力的超能力者
-
工作方式:它建立了一个多层的“快速通道网络”。最顶层是稀疏的骨干网,用于快速定位到大区域;越往下,网络越密集,用于精确定位。检索时从上至下,快速逼近目标。
-
优点:速度极快,尤其擅长高维数据。
-
缺点:占用的“脑容量”(内存)很大。
-
适用:对速度要求极高的实时应用,如聊天机器人。
4. DiskANN索引:管理“超大型图书馆”的专家
-
工作方式:当图书馆太大,无法把所有书的信息都放在手边(内存)时,DiskANN擅长利用高速书架(SSD硬盘)来组织图书,虽然单次取书稍慢一点,但能管理远超内存容量的海量藏书。
-
适用:数据量达到百亿级别的超大规模场景。
四、高级检索技巧:让搜索更智能
除了基本的找相似,Milvus这位“超级管理员”还有很多高级技能:
-
过滤检索:“请帮我找与这幅画风格相似的作品,但只要文艺复兴时期的。”——先按条件过滤,再找相似。
-
范围检索:“请找出所有与标准样本相似度超过90% 的产品。”——用于质检、人脸门禁等场景。
-
多向量混合检索:同时根据“文章语义向量”和“关键词向量”进行搜索,然后智能合并结果,让检索既准确又全面。
-
分组检索:搜索“机器学习”时,确保前10个结果来自10本不同的书籍,避免结果都集中在某一本书里,保证答案的多样性。
通过这些强大的功能组合,Milvus让我们能够构建出真正智能、高效的AI应用,是处理海量向量数据时不可或缺的利器。
✅ 解决方案:在本地 Windows 安装 Docker Desktop
步骤 1:下载并安装 Docker Desktop
- 访问官网:https://www.docker.com/products/docker-desktop/
- 下载 Docker Desktop for Windows
- 安装时勾选:
- ✅ Use WSL 2 instead of Hyper-V(推荐)
- ✅ Install required Windows components for WSL 2
- 安装完成后重启电脑
💡 需要 Windows 10/11 64位,且启用 WSL2(Docker Desktop 会自动配置)。
由于windows系统不支持,待做。
第五节 索引优化
让RAG更聪明:两种索引优化策略
想象一下,你的AI助手就像一个正在写论文的学生。如果参考资料太零碎(比如只有孤立的句子),它可能无法理解整体语境;但如果资料太冗长(比如整章内容),它又可能抓不住重点。如何解决这个矛盾?本节介绍两种让RAG系统变聪明的索引优化方法。
一、句子窗口检索:精准定位+完整语境
1. 核心思路:小而精的检索,大而全的生成
这就像查字典时的聪明做法:
- 检索时:先快速找到包含目标单词的那一行(精准定位)
- 理解时:再看这个单词所在的整个例句段落(完整语境)
具体实现步骤:
步骤1:建立"句子级"索引库
- 将文档拆分成单个句子,每个句子作为一个独立单元存入向量数据库
- 同时,为每个句子记录它的"上下文窗口"(前后各3个句子),作为隐藏的参考资料
步骤2:智能检索与扩展
- 用户提问时,系统先找出最相关的单个句子
- 然后自动将该句子的上下文窗口(前后各3句)一起送给AI生成答案
效果对比:
当询问"大西洋环流有什么风险?"时:
- 普通方法:可能找到一段泛泛而谈的气候变化内容
- 句子窗口法:精准定位到讨论AMOC的具体段落,给出详细、专业的回答
这种方法确保了答案既准确又丰富,就像让AI先找到靶心再看清整个靶子。
二、结构化索引:先筛选再搜索
当知识库变得很大(比如几百个PDF)时,新问题出现了:如何快速找到真正相关的资料?
传统方法的局限:
在整个知识库中搜索,就像在杂乱的大仓库里找一颗特定螺丝——效率低下且容易找错。
解决方案:给资料贴"智能标签"
1. 元数据过滤:先缩小范围
- 为每个文档块添加标签:文件名、日期、章节、作者等
- 检索时先按标签筛选,再在筛选结果中搜索
示例场景:
问:"2023年第二季度财报中关于AI的内容"
系统先筛选:年份=2023
+ 类型=财报
+ 季度=Q2
然后在筛选出的少量文档中精准搜索
2. 递归检索:智能路由查询
对于更复杂的情况(如包含多个表格的Excel文件),可以使用"递归检索":
python
# 简化示例:智能路由到正确的数据表
1. 顶层索引:包含各表格的摘要描述
2. 用户提问:"1994年哪部电影评分人数最少?"
3. 系统先判断:这个问题应该由"1994年电影表"回答
4. 然后将问题路由到对应的表格查询引擎
5. 最终得到精准答案:"《燃情岁月》"
安全提示: 虽然有些工具能自动生成代码查询表格,但存在安全风险。更稳妥的做法是使用元数据过滤等安全方法。
为什么理解原理比死记工具更重要?
学习这些技术时,有人可能会问:为什么不只学一个现成框架?
这好比学做菜:
- 只学框架 = 死记菜谱,食材一变就不会做了
- 理解原理 = 掌握烹饪逻辑,有什么食材都能做出美味
我们的学习哲学:
- 原理优先:理解"为什么"比记住"怎么用"更重要
- 灵活应变:真实业务千变万化,原理让你能随机应变
- 安全第一:知道工具的风险,才能选择更安全的方案
当你真正理解了RAG的工作原理,无论遇到什么新工具、新场景,你都能快速掌握并做出明智的选择。
记住:好的AI助手不是死记硬背的"书呆子",而是懂得如何高效查找、智能理解的"聪明学生"。通过这些优化策略,你的RAG系统就能变得更加精准和可靠。
修改程序如下:
import os
import pandas as pd
from dotenv import load_dotenv
from llama_index.core import VectorStoreIndex, Document, Settings
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.vector_stores import MetadataFilters, ExactMatchFilter
from llama_index.llms.deepseek import DeepSeek
from llama_index.embeddings.huggingface import HuggingFaceEmbeddingload_dotenv()# ---------------- 模型配置(完全离线) ----------------
Settings.llm = DeepSeek(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"))
Settings.embed_model = HuggingFaceEmbedding(model_name=r"E:\Datawhale\All in rag 202509\code\all-in-rag-main\models\bge-base-en-v1.5"
)# ---------------- 1. 加载与预处理数据 ----------------
excel_file = r"E:\Datawhale\All in rag 202509\code\all-in-rag-main\data\C3\excel\movie.xlsx"
xls = pd.ExcelFile(excel_file)summary_docs, content_docs = [], []
print("开始加载和处理 Excel 文件...")
for sheet_name in xls.sheet_names:df = pd.read_excel(xls, sheet_name=sheet_name)# 清洗:把“1234人评价”→1234if '评分人数' in df.columns:df['评分人数'] = df['评分人数'].astype(str).str.replace('人评价', '').str.strip()df['评分人数'] = pd.to_numeric(df['评分人数'], errors='coerce').fillna(0).astype(int)year = sheet_name.replace('年份_', '')summary_docs.append(Document(text=f"This sheet contains movie information for the year {year}, including title, director, rating, number of reviewers, etc.",metadata={"sheet_name": sheet_name}))content_docs.append(Document(text=df.to_string(index=False),metadata={"sheet_name": sheet_name}))print("数据加载和处理完成。\n")# ---------------- 2. 构建索引 ----------------
summary_index = VectorStoreIndex(summary_docs)
content_index = VectorStoreIndex(content_docs)
print("摘要索引和内容索引构建完成。\n")# ---------------- 3. 查询函数 ----------------
def query_safe_recursive(query_str: str):print("--- 开始执行查询 ---")print(f"查询: {query_str}")# 路由:找最相关年份表print("\n第一步:在摘要索引中进行路由...")summary_retriever = VectorIndexRetriever(index=summary_index, similarity_top_k=1)nodes = summary_retriever.retrieve(query_str)if not nodes:return "抱歉,未能找到相关的电影年份信息。"matched_sheet = nodes[0].node.metadata['sheet_name']print(f"路由结果:匹配到工作表 -> {matched_sheet}")# 检索:在该年份表内精确匹配print("\n第二步:在内容索引中检索具体信息...")content_retriever = VectorIndexRetriever(index=content_index,similarity_top_k=1,filters=MetadataFilters(filters=[ExactMatchFilter(key="sheet_name", value=matched_sheet)]))response = RetrieverQueryEngine.from_args(content_retriever).query(query_str)print("--- 查询执行结束 ---\n")return response# ---------------- 4. 执行查询 ----------------
if __name__ == "__main__":query = "1994年评分人数最少的电影是哪一部?"answer = query_safe_recursive(query)print(f"最终回答: {answer}")
显示结果如下:
开始加载和处理 Excel 文件...
数据加载和处理完成。摘要索引和内容索引构建完成。
--- 开始执行查询 ---
查询: 1994年评分人数最少的电影是哪一部?第一步:在摘要索引中进行路由...
路由结果:匹配到工作表 -> 年份_1994第二步:在内容索引中检索具体信息...
--- 查询执行结束 ---最终回答: 1994年评分人数最少的电影是《燃情岁月》,评分人数为176801。