Day 17:RAG 概念引入 —— Embedding、向量与相似度
欢迎来到第十七天!从今天起,我们正式进入RAG(Retrieval-Augmented Generation,检索增强生成)的世界。RAG 是构建知识密集型 Agent 的核心技术,它让模型能够“查阅”外部知识库,而不是仅仅依赖训练时记住的内容。理解 RAG 的第一步,就是理解Embedding(嵌入)和向量相似度。今天我们将动手调用 Embedding API,计算文本之间的语义相似度,为后续构建完整的 RAG 管道打下基础。
一、今日学习目标
- 理解什么是 Embedding:将文本转化为稠密向量的过程,语义相近的文本在向量空间中距离更近。
- 掌握如何调用 DeepSeek 或其他兼容 API 的 Embedding 接口,将文本转换为向量。
- 学会使用余弦相似度(Cosine Similarity)衡量两个向量之间的相似程度。
- 通过实验观察:语义相近的句子相似度高,语义无关的句子相似度低。
- 了解 Embedding 在 RAG 中的位置:用于检索与用户问题最相关的知识片段。
二、详细实现步骤
步骤 1:理解 Embedding 与向量
大语言模型处理的文本被表示成 Token,但 Token 本身是离散的符号,模型无法直接理解它们之间的语义关系。Embedding 技术通过神经网络将每个 Token 或整段文本映射为一个固定维度的实数向量(例如 1024 维)。这个向量被称为“嵌入向量”,它捕捉了文本的语义特征。
关键性质:
- 语义相近的文本,其嵌入向量在向量空间中彼此靠近(距离小、相似度高)。
- 语义无关的文本,其向量距离远(相似度低)。
- 向量可以进行数学运算,例如
king - man + woman ≈ queen。
在 RAG 中,我们会将知识库中的每个文档片段预先计算成 Embedding 并存入向量数据库。当用户提问时,我们将问题也转换为 Embedding,然后在向量数据库中检索与问题向量最相似的片段,将这些片段作为上下文提供给 LLM,让 LLM 基于这些资料生成回答。
步骤 2:准备环境与调用 Embedding API
DeepSeek 提供 Embedding 接口吗?根据 DeepSeek 官方文档,目前 DeepSeek 的 API 主要提供对话模型(deepseek-chat)和代码模型(deepseek-coder),暂不提供独立的 Embedding 接口。不过我们可以使用其他兼容 OpenAI 格式的 Embedding 服务,例如:
- 智谱 AI (GLM) 的 Embedding 模型:
embedding-2。 - 百度千帆、阿里云通义等也提供 Embedding API。
- 为了教学方便,我们也可以使用一个简单的本地 Embedding 模型(如
sentence-transformers),但需要安装较大的依赖。
为了保持与之前相同的环境(使用 OpenAI SDK),我们选择使用智谱 AI 的 Embedding API,因为它兼容 OpenAI 格式,注册和获取 Key 相对简单。如果你有其他 Embedding 服务,也可以替换。
步骤 2.1:获取智谱 AI 的 API Key
- 访问 智谱AI开放平台 注册账号。
- 在“API Keys”页面创建 API Key,复制保存。
- 智谱的 Embedding 模型名为
embedding-2,接口 Base URL 为https://open.bigmodel.cn/api/paas/v4(兼容 OpenAI 格式)。
步骤 2.2:更新环境变量
在.env文件中添加:
ZHIPU_API_KEY=你的智谱AI_Key步骤 3:调用 Embedding API 将文本转为向量
我们使用openaiSDK 调用智谱的 Embedding 接口。注意:Embedding 接口的路径是/embeddings,但我们需要设置正确的base_url。
新建embedding_demo.py:
importosfromdotenvimportload_dotenvfromopenaiimportOpenAI load_dotenv()# 初始化智谱客户端client=OpenAI(api_key=os.getenv("ZHIPU_API_KEY"),base_url="https://open.bigmodel.cn/api/paas/v4")defget_embedding(text:str)->list:"""调用 Embedding API,返回向量列表"""response=client.embeddings.create(model="embedding-2",input=text)# 提取向量returnresponse.data[0].embedding# 测试text="人工智能正在改变世界"vector=get_embedding(text)print(f"向量维度:{len(vector)}")print(f"向量前10个值:{vector[:10]}")运行脚本,你会看到输出了一个高维向量(智谱embedding-2的维度是 1024)。
步骤 4:计算余弦相似度
余弦相似度衡量两个向量之间的夹角余弦值,范围在 -1 到 1 之间。对于 Embedding 向量,通常使用余弦相似度来衡量语义相似性,值越接近 1 表示越相似。
我们手动实现余弦相似度计算(不依赖额外库,使用纯 Python 或 numpy):
importnumpyasnpdefcosine_similarity(vec1:list,vec2:list)->float:"""计算两个向量的余弦相似度"""v1=np.array(vec1)v2=np.array(vec2)returnfloat(np.dot(v1,v2)/(np.linalg.norm(v1)*np.linalg.norm(v2)))测试几组句子:
sentences=["我喜欢吃苹果","我喜欢吃水果","今天天气很好","人工智能是计算机科学的一个分支"]# 计算第一句与其他句子的相似度base_vector=get_embedding(sentences[0])forsinsentences[1:]:vec=get_embedding(s)sim=cosine_similarity(base_vector,vec)print(f"“{sentences[0]}” 与 “{s}” 的相似度:{sim:.4f}")观察与思考:
- “我喜欢吃苹果” 与 “我喜欢吃水果” 的相似度应该较高(>0.7)。
- “我喜欢吃苹果” 与 “今天天气很好” 的相似度较低(<0.3)。
- 这表明 Embedding 确实捕捉了语义关系。
步骤 5:批量处理与效率考虑
在实际项目中,知识库可能包含成千上万个片段,逐个调用 API 会非常慢。智谱的 Embedding API 支持批量输入(input参数可以是一个字符串数组)。我们可以一次发送多个文本,获得多个向量:
defget_embeddings_batch(texts:list)->list:response=client.embeddings.create(model="embedding-2",input=texts# 列表)# 返回按顺序排列的向量列表return[item.embeddingforiteminresponse.data]# 示例texts=["苹果","香蕉","汽车"]vectors=get_embeddings_batch(texts)fort,vinzip(texts,vectors):print(f"{t}: 维度{len(v)}")批量调用可以大幅减少网络请求次数,提高效率。
步骤 6:简易的向量检索演示
我们已经有了计算相似度的工具,现在模拟一个极简的“检索”过程:从一个文档列表中找出与用户问题最相似的文档片段。
# 模拟知识库documents=["苹果公司发布了新款 iPhone。","香蕉富含钾元素,对心脏有益。","人工智能模型可以生成文本。","汽车轮胎需要定期检查气压。","今天气温 25 摄氏度,适合户外活动。"]# 预先计算所有文档的 Embedding(实际项目中会存入向量数据库)doc_vectors=[get_embedding(doc)fordocindocuments]defretrieve(query:str,top_k:int=2):query_vec=get_embedding(query)similarities=[]fori,doc_vecinenumerate(doc_vectors):sim=cosine_similarity(query_vec,doc_vec)similarities.append((i,sim))# 排序,取相似度最高的 top_k 个similarities.sort(key=lambdax:x[1],reverse=True)results=[]foridx,siminsimilarities[:top_k]:results.append((documents[idx],sim))returnresults# 测试检索query="哪种食物对心脏好?"results=retrieve(query,top_k=2)print(f"查询:{query}")fordoc,siminresults:print(f" 相似度{sim:.4f}-{doc}")运行后,你会看到“香蕉富含钾元素,对心脏有益。”被检索出来,尽管查询中并没有直接出现“香蕉”这个词。这就是语义检索的魅力——即使没有关键词重叠,也能找到相关文档。
三、常见问题与调试
Q1:为什么 DeepSeek 不提供 Embedding API?
→ DeepSeek 目前专注于对话和代码模型,Embedding 服务尚未公开。你可以使用其他提供 Embedding 的厂商,如智谱、OpenAI、Cohere 等。也可以本地运行开源 Embedding 模型(如bge-small-zh),但需要安装sentence-transformers等库,对硬件有一定要求。
Q2:向量维度越高越好吗?
→ 不一定。更高的维度可能捕捉更细微的语义,但也会增加存储和计算成本。目前常用的 Embedding 维度有 768、1024、1536 等,选择时需权衡性能与效果。
Q3:余弦相似度和欧氏距离有什么区别?
→ 余弦相似度关注方向,忽略向量长度;欧氏距离关注绝对距离。在 Embedding 比较中,余弦相似度更常用,因为它对向量长度不敏感(不同 Embedding 模型可能产生不同模长的向量)。
Q4:如何提高 Embedding 的检索准确率?
→ 使用专门针对中文优化的 Embedding 模型,如智谱的embedding-2、BAAI 的bge-large-zh等。此外,对文档进行合理的切分、添加标题或元数据也可以提升检索效果。
Q5:我没有智谱 API,能用 OpenAI 的 Embedding 吗?
→ 可以,OpenAI 提供text-embedding-3-small等模型,但需要能访问 OpenAI 的 API 并付费。替换base_url和model即可。
四、今日总结与作业
今天你完成了:
- ✅ 理解了 Embedding 的概念及其在 RAG 中的核心作用。
- ✅ 学会了调用 Embedding API 将文本转换为向量。
- ✅ 实现了余弦相似度计算,并通过实验验证了语义相似性。
- ✅ 模拟了一个简易的语义检索过程,体会了“语义搜索”相比关键词搜索的优势。
今日作业(必做):
- 使用智谱(或其他)的 Embedding API,计算以下句子两两之间的相似度,并绘制一个相似度矩阵(可以用
print输出表格):- “我喜欢打篮球”
- “我喜欢踢足球”
- “篮球是一项运动”
- “今天下雨了”
- 扩展简易检索示例:将文档列表增加到 10 条(内容自拟),测试多个查询,观察检索结果是否合理。思考如何改进检索效果(例如对文档进行预处理)。
- (思考题)在 RAG 系统中,为什么需要对文档进行切分(chunking)?切分大小对检索和生成有什么影响?请用 100 字以内回答。
明日预告:我们将继续 RAG 的下一步——使用 LangChain 实现文档加载、切分和向量存储,构建一个完整的“本地文档问答机器人”。请确保今天的 Embedding 调用成功,明天我们会直接使用。
有任何问题欢迎随时提问!