人人都能学编程
English 开始学习
专项 · 人工智能第 6 课 / 共 6 课免费阅读

嵌入与检索 · 人工智能基础

AI 助手是怎么找到合适的文档来回答你的问题的?

课程目录

为什么需要它

语言模型只知道它训练时见过的东西:不知道你们公司的退款政策,也不知道上周的改动。检索增强生成(RAG)的办法是:先查找相关文档,再把它们放进提示词里。查找靠的是嵌入:把文本变成向量,让意思相近的文本挨得近。这和第 4 课的「找最近的邻居」是同一个想法,只是换了一种衡量「近」的方式。

直觉理解

嵌入模型把一段文本变成一串数字,也就是一个向量。意思相近的文本,得到的向量指向相近的方向。本课用编出来的、只有 3 个数的小向量,好让你看清发生了什么;真实的嵌入由模型生成,数字要多得多,而且单独的某个数字并没有可读的含义。

要比较方向,用余弦相似度:

cosine(a, b) = (a · b) / (|a| × |b|)

a · b(点积)把对应位置的数相乘再加起来;|a| 是向量的长度。结果是:方向相同为 1,毫不相关为 0,方向相反为 −1。除以长度很重要:一篇长文档不应该仅仅因为向量长就胜出。

RAG 三步走:

  1. 把问题变成嵌入向量
  2. 按余弦相似度给文档排序,保留前 k 个,并丢掉低于最低分的
  3. 把它们放进提示词:「请根据这些文档回答:…… 问题:……」

定义

嵌入

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
逐步执行
  1. L4对应位置相乘再相加:点积。
  2. L5向量的长度,√(x² + y² + z²)。
  3. L10非常接近 1:意思几乎一样。
  4. 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?
逐步执行
  1. L13问题的嵌入向量(这里是编的;实际由模型生成)。
  2. L14最相似的排在前面。
  3. 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))
  1. A
    0 2
  2. B
    1 1
  3. C
    0 1
查看答案

答案:A

a 和 b 没有共同的方向 (0);c 和 a 方向相同但长一倍 (2)。

2. 为什么用余弦相似度而不是原始的点积给文档排序?

  1. A余弦比较的是方向(意思),忽略长度
  2. B余弦算得更快
  3. C点积只适用于二维
查看答案

答案:A

否则长向量只因为长就能胜出。

3. RAG 给语言模型提供了什么?

  1. A提示词里的相关文档,让它能用上没学过的事实
  2. B新的训练,永久更新它的权重
  3. C更快的生成文本的方式
查看答案

答案:A

检索发生在提问的时候;模型本身没有改变。

动手练习

3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。

  1. 预测输出长度为一

    准确写出它打印的内容。

  2. 改需求什么都能赢的 FAQ

    一篇又长又啰嗦的 FAQ 页面向量很大,每次搜索都是它赢。把 score 改成余弦相似度,让只有方向起作用。

  3. 从零写检索

    写一个 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.