Day 23 / 共 68 天 · 阶段 4 RAG 检索增强
embedding 向量化
昨天(Day 22)你把长文档切成了一块块 chunk。今天解决 RAG 最核心的问题:切好的文本块,怎么让电脑"按意思"找出跟问题最相关的那几块?答案是 embedding(嵌入向量)——把文本变成一串数字坐标,意思越接近、坐标越接近。学完你就懂了"语义搜索"的底层魔法;明天(Day 24)学把这些向量存进专门的向量数据库里高效检索。
📍 你在阶段 4(RAG 检索增强 D21-28)的位置
D22 chunking→
D23 embedding→
D24 向量数据库→
D25 检索→
D26 rerank
💡 用一套类比兜住今天(今天全程沿用「一张语义地图」的世界观)
embedding = 给每段文字在一张巨大的"意思地图"上标一个坐标。地图上,意思相近的词句挨在一起("猫""小猫""喵星人"扎堆,"银行""贷款""利息"扎堆);向量 = 这个坐标(一串数字,就是"经纬度",只不过有几百上千个维度);相似度 = 两点在地图上的距离——越近意思越像。今天你学会"把语言变成地图上的坐标",从此电脑能按意思找东西,而不只是按字面。
L01
关键词搜索的天花板
🤔 痛点用户问"怎么把钱取出来",你的知识库里写的是"账户提现流程"——用传统关键词搜索,因为一个字都对不上,直接搜不到!可这俩明明是一个意思。
💡 本质传统搜索是"对字面":查"取钱"就只找含"取钱"二字的。但人类语言里同一个意思有无数种说法。我们真正想要的是"对意思"的搜索——这正是 embedding 要解决的。
📝 举个例子:字面不同、意思相同
这几组,关键词搜索会认为"毫不相关",但人一看就知道是一回事:
• "怎么退款" ↔ "如何申请退货款项"
• "电脑开不了机" ↔ "笔记本按电源键没反应"
• "iPhone" ↔ "苹果手机"
RAG 要能把用户五花八门的问法,都匹配到知识库里那段正确内容——靠字面对齐做不到,靠"对意思"才行。
• "怎么退款" ↔ "如何申请退货款项"
• "电脑开不了机" ↔ "笔记本按电源键没反应"
• "iPhone" ↔ "苹果手机"
RAG 要能把用户五花八门的问法,都匹配到知识库里那段正确内容——靠字面对齐做不到,靠"对意思"才行。
L02
embedding 是什么:把文本变成坐标
🤔 痛点电脑只会算数字,不懂"意思"。要让它"按意思"比较两段话,得先把话变成它能算的东西——变成什么?
💡 本质embedding 就是用一个专门的模型,把一段文本变成一串固定长度的数字(叫向量,比如 1536 个数字)。这串数字就是这段文字在"语义地图"上的坐标。关键在于:意思相近的文本,生成的向量也相近。
图注:同一个 embedding 模型对任何文本都输出等长的向量;"取钱"和"提现"的向量会很接近。
👶 "维度"是啥别怕向量有 1536 个数字,就说它是"1536 维"。你无法在脑子里想象 1536 维空间,没关系——把它当成"一个超级精细的坐标"就行:普通地图 2 维(经纬度)只能表达位置,1536 维能同时表达"话题、情感、领域、语气"等成百上千种细微差别,所以能精准区分意思。
L03
语义地图:意思近的挤在一起
🤔 痛点"意思相近向量就相近"——这话听着玄,到底怎么理解?为什么一串数字能表达"意思"?
💡 本质想象一张巨大的地图,embedding 模型经过海量文本训练,学会了把意思相近的文字放到地图上相近的位置。于是"猫/小猫/喵星人"聚成一片,"银行/存款/利息"聚成另一片。这张地图不是人画的,是模型从数据里"悟"出来的。
图注:真实是几百上千维,这里压到 2 维只为直观。核心就一句:意思近 → 位置近。
👶 小白:这地图是我要自己训练的吗?听起来好难。
👨🏫 老师:完全不用!这些 embedding 模型早有大公司/开源社区训练好了(下面 L06 讲怎么选)。你只要调它们的接口,把文本喂进去、拿向量出来用就行——就像你不用自己发电,插上插座用电即可。今天要掌握的是"怎么用、用来干嘛",不是"怎么造"。
L04
距离 = 相似度:怎么量"有多像"
🤔 痛点有了坐标,怎么让电脑算出"这两段话有多像"?地图上两点的"距离"具体怎么量?
💡 本质最常用的是余弦相似度(cosine similarity):不看两点的绝对距离,而看两个向量"指向的方向"有多一致。方向几乎重合 → 相似度接近 1(很像);方向垂直 → 接近 0(不相关);方向相反 → 接近 -1(相反意思)。
import numpy as np # 数值计算库,处理向量很方便
def cosine(a, b): # 计算两个向量的余弦相似度
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# np.dot 点积;np.linalg.norm 求长度;这就是余弦相似度的定义式
# 假设这是三段话的 embedding(真实是几百维,这里用 3 维演示原理)
v_取钱 = [0.9, 0.1, 0.0]
v_提现 = [0.85, 0.15, 0.05] # 和"取钱"意思接近
v_今天天气 = [0.0, 0.2, 0.95] # 完全不相关
print(cosine(v_取钱, v_提现)) # → 约 0.99,很像!
print(cosine(v_取钱, v_今天天气)) # → 约 0.11,几乎不相关
👶 只需记结论公式看不懂没关系,记住三句话:相似度越接近 1 = 越像;越接近 0 = 越不相关;检索就是"找相似度最高的几段"。实战中向量数据库(明天 Day24)会自动帮你算这个、并极快地找出最像的,你几乎不用手写这段。
📝 举个例子:这就是"语义搜索"的一整条链路
用户问"怎么取钱" → 把问题也 embedding 成向量 → 和知识库里每段的向量算余弦相似度 → 挑出相似度最高的"账户提现流程"那段 → 交给大模型据此作答。RAG 的检索(Day25)本质就是这条链路,embedding 是它的心脏。
L05
动手生成 embedding:一行调用
🤔 痛点原理懂了,真实代码里怎么把一段文本变成向量?很复杂吗?
💡 本质和调聊天模型一样简单(回忆 Day11):调 embedding 接口、把文本传进去、拿回一串数字。一行核心代码。
from openai import OpenAI
client = OpenAI()
resp = client.embeddings.create( # 注意是 embeddings,不是 chat
model="text-embedding-3-small", # 一个常用的 embedding 模型
input="账户提现流程:登录后进入我的钱包……", # 要向量化的文本
)
vector = resp.data[0].embedding # 取出向量(一个很长的数字列表)
print(len(vector)) # → 1536(这个模型输出 1536 维)
print(vector[:5]) # 看前 5 个数:[0.021, -0.013, ...]
# 批量向量化:把 input 传一个列表,一次处理多段(更省调用次数)
docs = ["如何退款", "怎么开发票", "配送要几天"]
resp = client.embeddings.create(model="text-embedding-3-small", input=docs)
vectors = [d.embedding for d in resp.data] # 得到 3 个向量,一一对应
👶 两个关键习惯① 存文档和查询必须用同一个 embedding 模型——不同模型画的是不同的地图,坐标不能混着比。② embedding 也是按 token 计费的(回忆 Day13),不过比聊天模型便宜得多;把海量文档一次性向量化后存起来(明天入库),就不用反复花钱重算了。
L06
怎么选 embedding 模型 & 注意事项
🤔 痛点市面上 embedding 模型一大堆,闭源的、开源的、中文的、英文的……新手该怎么选?会不会踩坑?
💡 本质选型看四点:效果、语言、维度/成本、能否本地部署。没有绝对最好,只有最合适。下面给一个够用的选择框架(具体模型名迭代快,用时以官方文档为准)。
| 类型 | 特点 | 适合场景 |
|---|---|---|
| 闭源 API(如 OpenAI text-embedding 系列) | 开箱即用、效果稳、按量付费 | 快速起步、不想管部署 |
| 开源可本地跑(如 BGE、m3e、GTE 等中文友好模型) | 免费、数据不出本地、可微调 | 数据敏感、量大要省钱 |
| 多语言模型 | 中英混排也能对齐 | 跨语言检索 |
👶 新手怎么选?刚上手:直接用一个闭源 embedding API,别纠结,先把 RAG 跑通。等有了"数据不能外传"或"调用量太大太贵"的真实需求,再换成开源模型本地部署。先能用,再优化。
📝 举个例子:最常见的坑
① 用 A 模型给文档建库、却用 B 模型给查询编码 → 两张地图对不上,检索全错(切记同一模型)。② 中文内容却选了个只擅长英文的模型 → 相似度乱套(选语言匹配的)。③ 忘了 chunking(Day22)直接把整篇长文喂进去 → 向量"糊成一团"抓不住重点。这三个坑,几乎每个新手都会踩一次。
L07
今日小结 + 动手 10 分钟
🧠 今天你应该能回答
- 关键词搜索的根本局限是什么?(只对字面,不对意思)
- embedding 是什么?它把文本变成了什么?(一串数字/向量/语义坐标)
- "意思近 → 向量近"怎么理解?"语义地图"的直觉是什么?
- 怎么量两段文本有多像?(余弦相似度,越接近 1 越像)
- 选 embedding 模型看哪几点?为什么建库和查询必须同一个模型?
✋ 动手 10 分钟:用 embedding 做一个"最小语义搜索"
在 Day11 的环境里(已装 openai/dotenv),装个 numpy,新建 semantic_search.py:
from openai import OpenAI
from dotenv import load_dotenv
import numpy as np
load_dotenv()
client = OpenAI()
def embed(texts): # 把一批文本变成向量
r = client.embeddings.create(model="text-embedding-3-small", input=texts)
return [d.embedding for d in r.data]
def cosine(a, b):
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 一个小知识库(现实中来自 Day22 切好的 chunk)
docs = ["账户提现流程:登录进入我的钱包点提现",
"开具发票:在订单页申请电子发票",
"配送时效:普通快递 3-5 天送达"]
doc_vecs = embed(docs) # 预先把文档都向量化
query = "怎么把钱取出来" # 用户的问题(字面和文档完全不同)
q_vec = embed([query])[0]
# 算问题和每段文档的相似度,从高到低排序
scored = sorted(zip(docs, doc_vecs), key=lambda d: cosine(q_vec, d[1]), reverse=True)
for text, vec in scored:
print(f"{cosine(q_vec, vec):.3f} {text}")
# 你会看到"账户提现流程"相似度最高——尽管它一个"取钱"的字都没有!
这就是语义搜索的雏形:字面不同也能凭"意思"找对。
明日预告 · Day 24:今天你手写了"和每段文档逐个算相似度"——文档只有 3 段还行,可要是有几百万段呢?逐个算会慢到没法用。明天学 向量数据库(FAISS / Milvus / Qdrant / pgvector):专门用来存海量向量、并在毫秒级找出最相似的几条。它是 RAG 从"玩具"走向"能用"的关键基础设施。