Embedding 与向量

Embedding、向量的初步认识

一、机器不懂文字,只懂数字

问题从哪里来

大家很自然地知道:

●”苹果”和”香蕉”都是水果,比较接近

●”苹果”和”汽车”完全不相关

●”我很开心”和”我非常高兴”意思几乎一样

但计算机存储文字只是一串字符,它怎么知道两段文字”意思接近”?

传统字符串匹配:

1
2
3
"苹果".equals("苹果")  // true,完全相同
"苹果".equals("香蕉") // false,字面不同
// 但"苹果"和"水果"之间的语义关联,字符串比较完全表达不出来

关键词搜索:

1
2
3
SELECT * FROM docs WHERE content LIKE '%退款%'
-- 能找到包含"退款"的文档
-- 但找不到只写了"申请返还货款"的文档(意思一样,词不一样)

解决思路

如果能把每段文字转化成一组数字,让意思相近的文字对应数字也相近,问题就解决了。

这组数字叫向量(Vector),把文字转成向量的过程叫 Embedding(嵌入 /ɪmˈbedɪŋ/)

二、向量:用坐标描述语义

从二维空间理解

img

假设用两个维度来描述词语:

●横轴:是否是食物(0~1)

●纵轴:是否是生物(0~1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
               是生物
1

猫 ● │ ● 人
狗 ● │

─────────────────┼─────────────────
不是食物 │ 是食物
│ ● 苹果
│ ● 香蕉
汽车 ● │
桌子 ● │
0
不是生物

在这个空间里,”苹果”和”香蕉”很接近(都是食物,都不是生物),”猫”和”狗”很接近,”苹果”和”汽车”很远。

向量就是在高维空间里的一个坐标点,坐标相近意味着语义相近。

真实的 Embedding 向量

真实的 Embedding 不止 2 个维度,通常是 768、1536 甚至 3072 个维度。每个维度捕捉语言的某种抽象特征(不一定有具体含义)。

1
2
3
4
5
"苹果" → [0.23, -0.87, 0.45, 0.12, -0.33, ..., 0.67]  // 1536 个数字

"香蕉" → [0.21, -0.84, 0.48, 0.15, -0.30, ..., 0.71] // 和苹果非常接近

"汽车" → [-0.55, 0.32, -0.18, 0.89, 0.44, ..., -0.23] // 和苹果差异很大

不只是词,一整段话也可以转成一个向量:

1
2
3
4
"如何申请退款?"         → [0.23, 0.45, -0.12, ...]
"退款流程是怎样的?" → [0.25, 0.43, -0.10, ...] ← 非常接近
"产品质量有问题怎么办?" → [0.20, 0.50, -0.08, ...] ← 比较接近
"苹果手机怎么截图?" → [-0.34, 0.12, 0.67, ...] ← 差异很大

这就是语义搜索的基础:找的不是关键词匹配,而是语义相近的内容

三、相似度:两个向量有多近

衡量两个向量的相似程度,最常用的是余弦相似度

不用理解公式,只需要知道结果:

余弦相似度 含义
1.0 完全相同
0.9+ 非常相似
0.7~0.9 比较相似
0.5~0.7 有一定关联
0~0.5 关联性弱
负数 语义相反

直觉理解:想象两个箭头从原点出发,箭头方向越接近,余弦相似度越高,和箭头的长度无关。

1
2
3
4
"退款申请"  vs  "申请退款"    → 0.97(近义表达)
"退款申请" vs "如何退货" → 0.82(同一场景)
"退款申请" vs "产品使用方法" → 0.31(不同话题)
"退款申请" vs "今天天气如何" → 0.08(完全无关)

搜索过程

有了向量和相似度,语义搜索的过程就很清晰了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
① 把所有文档段落转成向量,存入向量数据库
"第一章 产品介绍..." → [0.23, 0.45, ...]
"第二章 退款政策..." → [0.67, -0.12, ...]
"第三章 使用说明..." → [-0.34, 0.78, ...]

② 用户提问,把问题也转成向量
"怎么退款?" → [0.65, -0.10, ...]

③ 计算问题向量与所有文档向量的相似度
vs 第一章:0.31
vs 第二章:0.94 ← 最高!
vs 第三章:0.22

④ 取出相似度最高的段落,塞进 Prompt 给 LLM

四、Embedding 模型

怎么生成

Embedding 向量不是人工设计的,由专门的 Embedding 模型生成。和对话模型不同,Embedding 模型不生成文字,只生成向量:

1
2
3
// 概念示意
float[] vector = embeddingModel.embed("如何申请退款?");
// 返回:[0.23, 0.45, -0.12, 0.78, ...]

主流 Embedding 模型

模型 提供商 维度 特点
text-embedding-3-small OpenAI 1536 性价比高,英文效果好
text-embedding-3-large OpenAI 3072 精度更高,价格较贵
text-embedding-v3 阿里通义 1024 中文效果好,国内可用
BGE-M3 北京智源 1024 开源,多语言,可本地部署
nomic-embed-text Nomic 768 开源,可用 Ollama 本地跑

选型建议:纯中文场景选通义 text-embedding-v3 或 BGE-M3;英文为主选 OpenAI text-embedding-3-small;需要本地部署选 BGE-M3 + Ollama。

一个关键原则:同进同出

查询时使用的 Embedding 模型,必须和存储时用的是同一个。

不同模型的向量空间完全不同,就像用厘米标的尺和用英寸标的尺,两者的数字无法直接比较。

1
2
3
4
5
6
7
错误做法:
存储时:用 OpenAI 的模型生成向量存入数据库
查询时:用通义的模型生成问题向量来搜索
结果:搜出来完全不相关的内容

正确做法:
存储和查询使用完全相同的 Embedding 模型

有人换了 Embedding 模型之后发现搜索结果全乱了,就是这个问题。

五、向量数据库

为什么需要专门的数据库

img

向量搜索需要对大量向量做相似度计算,这和传统数据库的索引结构完全不同:

1
2
3
4
5
6
7
传统数据库:B+ 树索引,擅长精确匹配和范围查询
SELECT * WHERE id = 123 ← 极快
SELECT * WHERE score > 90 ← 很快
找"语义相似"的向量 ← 不支持,或极慢

向量数据库:ANN 索引(近似最近邻),专门为相似度搜索优化
找最相似的前 10 个向量 ← 极快(毫秒级,即使有百万条数据)

主流向量数据库对比

数据库 部署方式 特点 适合场景
PGVector 本地 / 云 PostgreSQL 扩展,熟悉 SQL 入门首选,已有 PG 的项目
Milvus 本地 / 云 专用向量库,性能强,功能全 生产级大规模场景
Qdrant 本地 / 云 Rust 编写,性能好,易用 中小规模生产场景
Chroma 本地 轻量,适合开发测试 快速原型

学习路径建议:PGVector 入门 → Milvus 进阶。模块五会详细讲。

向量数据库不是万能的

向量数据库擅长”找语义相近的内容”,但有局限:

1
2
3
4
5
6
7
适合问:
"有没有关于退款政策的内容?" → 语义检索
"这个问题的答案在哪段文档里?" → 语义检索

不适合问:
"找出2024年12月的所有订单" → 用普通数据库
"统计每个类别的文档数量" → 用普通数据库

实际系统中,向量数据库和关系型数据库往往并存,各司其职。

六、用一个完整例子串联

场景:公司内部 HR 政策问答机器人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
【离线准备阶段】

HR 上传了 5 份文件:
员工手册.pdf、差旅报销制度.pdf、绩效考核办法.pdf ...

系统处理:
① 解析 PDF 提取文字
② 切成小段(每段约 500 字)
③ 每段用 Embedding 模型转成向量
④ 向量 + 原文存入向量数据库

向量数据库里现在有:
ID=1, 向量=[0.23,...], 原文="差旅报销范围包括:交通费..."
ID=2, 向量=[0.67,...], 原文="报销时限:出差结束后30天内..."
ID=3, 向量=[0.45,...], 原文="住宿标准:一线城市不超过500元..."

【在线查询阶段】

员工问:"出差住酒店最多能报多少钱?"

① 问题 Embedding:[0.44, ...]
② 向量数据库搜索:
ID=1 相似度 0.71
ID=2 相似度 0.68
ID=3 相似度 0.93 ← 最相关!
③ 取出 ID=3 原文注入 Prompt
④ LLM 回答:"根据公司差旅报销制度,住宿标准为:
一线城市(北京、上海、广州、深圳)不超过500元/晚,
其他城市不超过350元/晚。"