嵌入与检索 · 人工智能基础
AI 助手是怎么找到合适的文档来回答你的问题的?
课程目录为什么需要它
语言模型只知道它训练时见过的东西:不知道你们公司的退款政策,也不知道上周的改动。检索增强生成(RAG)的办法是:先查找相关文档,再把它们放进提示词里。查找靠的是嵌入:把文本变成向量,让意思相近的文本挨得近。这和第 4 课的「找最近的邻居」是同一个想法,只是换了一种衡量「近」的方式。
直觉理解
嵌入模型把一段文本变成一串数字,也就是一个向量。意思相近的文本,得到的向量指向相近的方向。本课用编出来的、只有 3 个数的小向量,好让你看清发生了什么;真实的嵌入由模型生成,数字要多得多,而且单独的某个数字并没有可读的含义。
要比较方向,用余弦相似度:
cosine(a, b) = (a · b) / (|a| × |b|)a · b(点积)把对应位置的数相乘再加起来;|a| 是向量的长度。结果是:方向相同为 1,毫不相关为 0,方向相反为 −1。除以长度很重要:一篇长文档不应该仅仅因为向量长就胜出。
RAG 三步走:
- 把问题变成嵌入向量
- 按余弦相似度给文档排序,保留前 k 个,并丢掉低于最低分的
- 把它们放进提示词:「请根据这些文档回答:…… 问题:……」
定义
嵌入
A vector produced by a model so that texts with similar meaning are close together.由模型生成的向量,让意思相近的文本彼此靠近。
余弦相似度
How closely two vectors point the same way: the dot product divided by both lengths.两个向量方向有多一致:点积除以两者的长度。
检索增强生成
Retrieval-augmented generation: find relevant documents and give them to the language model with the question.检索增强生成:先找到相关文档,再把它们和问题一起交给语言模型。
示例
余弦相似度
动手试试cat 和 kitten 指向同一个方向;car 指向别处。
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
return dot / (math.hypot(*a) * math.hypot(*b))
cat = [0.9, 0.8, 0.1]
kitten = [0.85, 0.75, 0.2]
car = [0.1, 0.2, 0.95]
print(round(cosine(cat, kitten), 3))
print(round(cosine(cat, car), 3))0.996 0.293
- L4对应位置相乘再相加:点积。
- L5向量的长度,√(x² + y² + z²)。
- L10非常接近 1:意思几乎一样。
- L11低得多:不相关。
先检索,再写提示词
动手试试为一个问题给文档排序,然后拼出提示词。
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
return dot / (math.hypot(*a) * math.hypot(*b))
docs = {
"Refund policy": [0.9, 0.1, 0.0],
"Shipping times": [0.1, 0.9, 0.1],
"Reset your password": [0.0, 0.2, 0.9],
}
question = "How do I get my money back?"
q = [0.8, 0.2, 0.1]
ranked = sorted(docs, key=lambda t: cosine(q, docs[t]), reverse=True)
print(ranked)
prompt = f"Answer using this document: {ranked[0]}\nQuestion: {question}"
print(prompt)['Refund policy', 'Shipping times', 'Reset your password'] Answer using this document: Refund policy Question: How do I get my money back?
- L13问题的嵌入向量(这里是编的;实际由模型生成)。
- L14最相似的排在前面。
- L16在真实系统里,放进去的是文档的正文,提示词会被发送给语言模型。
常见错误
只用点积,不除以长度
长文档的向量也长,会赢下每一次搜索。用余弦相似度,或者先把向量归一化到长度 1。
无论如何都取前 k 个
如果没有相关的文档,前 k 个仍然会被返回,模型就会根据错误的文档作答。设定一个最低分。
混用不同的嵌入模型
不同模型生成的向量处在不同的空间里。问题和文档要用同一个模型来嵌入。
小测验
1. 这段代码会输出什么?
a = [1, 0]
b = [0, 1]
c = [2, 0]
dot = lambda u, v: sum(x * y for x, y in zip(u, v))
print(dot(a, b), dot(a, c))查看答案
答案:A
a 和 b 没有共同的方向 (0);c 和 a 方向相同但长一倍 (2)。
2. 为什么用余弦相似度而不是原始的点积给文档排序?
查看答案
答案:A
否则长向量只因为长就能胜出。
3. RAG 给语言模型提供了什么?
查看答案
答案:A
检索发生在提问的时候;模型本身没有改变。
动手练习
3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。
- 预测输出长度为一
准确写出它打印的内容。
- 改需求什么都能赢的 FAQ
一篇又长又啰嗦的 FAQ 页面向量很大,每次搜索都是它赢。把
score改成余弦相似度,让只有方向起作用。 - 从零写检索
写一个
retrieve(query, docs, k, min_score)。docs是一个从标题映射到向量的字典。返回最多k个标题组成的列表:- 只要和
query的余弦相似度大于等于min_score的文档 - 最相似的排前面;分数相同时按标题的字母顺序
向量不会全是零。你还需要自己写一个
cosine函数。 - 只要和
本课术语
| 术语 | 中文 | 英文定义 |
|---|---|---|
| Embedding | 嵌入 | A vector that places similar meanings close together. |
| Cosine similarity | 余弦相似度 | Dot product divided by both lengths. |
| RAG | 检索增强生成 | Retrieving documents and adding them to a model's prompt. |