01 基础概念篇
适合谁 :有 Java 后端开发经验,想要转型 AI 应用的工程师
本章收获:建立 Embedding 的直观理解,掌握模型选型方法,学会文本分块策略,能用 Spring AI 调用 Embedding 模型
01 引入:为什么关键词搜索不够用了?
想象你在一家大型电商平台工作,产品部门提出需求:「提升商品搜索的准确率」。
现有的搜索系统是传统的关键词匹配------用户输入"适合跑步的轻便鞋",系统去数据库里找包含"跑步"、"轻便"、"鞋"的商品。看起来没问题,但实际体验很差:
| 用户输入 | 传统搜索能匹配吗? | 问题 |
|---|---|---|
| "跑鞋,透气款" | ✅ 能 | 勉强 |
| "运动时穿的鞋子" | ⚠️ 部分 | 缺少"跑鞋"关键词 |
| "马拉松训练鞋" | ❌ 不能 | 没有"轻便"、"透气" |
| "夜跑安全鞋" | ❌ 不能 | 完全没有"跑步" |
根本原因 :传统搜索依赖精确的字面匹配,无法理解语义。
💡 生活案例:你去图书馆找书,按书名关键词检索 vs 问图书管理员"我要一本讲量子力学入门的书",后者能找到更多你真正需要的书,因为理解了你意图。
这就是 Embedding(向量化) 要解决的问题------把文字变成计算机能理解的"数字指纹",让语义相似的内容在向量空间中靠得更近。
02 基础概念:什么是 Embedding?
2.1 从离散到连续:为什么传统方式不够用?
先把这件事用你熟悉的 Java 代码来理解。
2.1.1 传统方式:词袋模型(Bag of Words)
想象有一个词汇表,里面只有 5 个词:["我", "喜欢", "编程", "代码", "足球"]
词袋模型把文本变成这样:
java
// 词汇表(维度 = 词汇表大小)
String[] vocabulary = {"我", "喜欢", "编程", "代码", "足球"};
// "我喜欢编程" → 统计每个词出现的次数
int[] bagOfWords = {1, 1, 1, 0, 0};
// 含义:第0位"我"出现1次,第1位"喜欢"出现1次,第2位"编程"出现1次,后两位为0
// "我喜欢代码" →
int[] bagOfWords2 = {1, 1, 0, 1, 0};
问题在哪里?
| 问题 | 说明 |
|---|---|
| 稀疏 | 词汇表有 10 万个词时,向量有 10 万个维度,但大部分是 0 |
| 无语义 | "编程"和"代码"在向量空间里的距离 = √((1-0)² + (0-1)²) = √2,和"编程"与"足球"的距离一样! |
| 无法处理未登录词 | 词汇表里没有的词,直接丢弃 |
💡 生活案例:就像你只能用"是/否"来描述一个水果------甜?否。酸?是。红?是。你用 5 个布尔值能区分苹果、梨、橙子吗?远远不够。
2.1.2 Embedding 方式:稠密向量
Embedding 的做法完全不同。它不是统计词频,而是把文本映射到一个连续的向量空间:
java
// 同样的词汇表,但用 4 维稠密向量表示(实际是 768 维)
float[] chengXu = {0.23f, -0.45f, 0.89f, 0.12f}; // "编程"
float[] daiMa = {0.21f, -0.42f, 0.87f, 0.15f}; // "代码"
float[] zuQiu = {-0.81f, 0.33f, -0.12f, 0.67f}; // "足球"
// 计算相似度(余弦相似度)
// "编程" vs "代码" ≈ 0.99(几乎一样!语义相近)
// "编程" vs "足球" ≈ -0.35(完全不一样)
关键区别:
词袋模型: Embedding:
维度 = 词汇表大小(数万) 维度 = 固定值(768/1024)
大部分是 0(稀疏) 几乎全是非零值(稠密)
只计数,无语义 每个维度捕捉一种语义特征
"编程"和"足球"一样远 "编程"和"代码"很近
2.1.3 一张图看清区别
#mermaid-svg-93TnZLAVFaGdUCyn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-93TnZLAVFaGdUCyn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-93TnZLAVFaGdUCyn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-93TnZLAVFaGdUCyn .error-icon{fill:#552222;}#mermaid-svg-93TnZLAVFaGdUCyn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-93TnZLAVFaGdUCyn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-93TnZLAVFaGdUCyn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-93TnZLAVFaGdUCyn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-93TnZLAVFaGdUCyn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-93TnZLAVFaGdUCyn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-93TnZLAVFaGdUCyn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-93TnZLAVFaGdUCyn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-93TnZLAVFaGdUCyn .marker.cross{stroke:#333333;}#mermaid-svg-93TnZLAVFaGdUCyn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-93TnZLAVFaGdUCyn p{margin:0;}#mermaid-svg-93TnZLAVFaGdUCyn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-93TnZLAVFaGdUCyn .cluster-label text{fill:#333;}#mermaid-svg-93TnZLAVFaGdUCyn .cluster-label span{color:#333;}#mermaid-svg-93TnZLAVFaGdUCyn .cluster-label span p{background-color:transparent;}#mermaid-svg-93TnZLAVFaGdUCyn .label text,#mermaid-svg-93TnZLAVFaGdUCyn span{fill:#333;color:#333;}#mermaid-svg-93TnZLAVFaGdUCyn .node rect,#mermaid-svg-93TnZLAVFaGdUCyn .node circle,#mermaid-svg-93TnZLAVFaGdUCyn .node ellipse,#mermaid-svg-93TnZLAVFaGdUCyn .node polygon,#mermaid-svg-93TnZLAVFaGdUCyn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-93TnZLAVFaGdUCyn .rough-node .label text,#mermaid-svg-93TnZLAVFaGdUCyn .node .label text,#mermaid-svg-93TnZLAVFaGdUCyn .image-shape .label,#mermaid-svg-93TnZLAVFaGdUCyn .icon-shape .label{text-anchor:middle;}#mermaid-svg-93TnZLAVFaGdUCyn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-93TnZLAVFaGdUCyn .rough-node .label,#mermaid-svg-93TnZLAVFaGdUCyn .node .label,#mermaid-svg-93TnZLAVFaGdUCyn .image-shape .label,#mermaid-svg-93TnZLAVFaGdUCyn .icon-shape .label{text-align:center;}#mermaid-svg-93TnZLAVFaGdUCyn .node.clickable{cursor:pointer;}#mermaid-svg-93TnZLAVFaGdUCyn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-93TnZLAVFaGdUCyn .arrowheadPath{fill:#333333;}#mermaid-svg-93TnZLAVFaGdUCyn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-93TnZLAVFaGdUCyn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-93TnZLAVFaGdUCyn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-93TnZLAVFaGdUCyn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-93TnZLAVFaGdUCyn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-93TnZLAVFaGdUCyn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-93TnZLAVFaGdUCyn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-93TnZLAVFaGdUCyn .cluster text{fill:#333;}#mermaid-svg-93TnZLAVFaGdUCyn .cluster span{color:#333;}#mermaid-svg-93TnZLAVFaGdUCyn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-93TnZLAVFaGdUCyn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-93TnZLAVFaGdUCyn rect.text{fill:none;stroke-width:0;}#mermaid-svg-93TnZLAVFaGdUCyn .icon-shape,#mermaid-svg-93TnZLAVFaGdUCyn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-93TnZLAVFaGdUCyn .icon-shape p,#mermaid-svg-93TnZLAVFaGdUCyn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-93TnZLAVFaGdUCyn .icon-shape .label rect,#mermaid-svg-93TnZLAVFaGdUCyn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-93TnZLAVFaGdUCyn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-93TnZLAVFaGdUCyn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-93TnZLAVFaGdUCyn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Embedding
'我喜欢编程'
神经网络
0.23, -0.45, 0.89, ...
768个维度
全部有意义
词袋模型 TF-IDF
'我喜欢编程'
词汇表: 10万词
0, 0, ..., 1, 0, ..., 1
10万个维度
99% 是 0
📌 一句话总结:词袋模型是"数词频",Embedding 是"理解语义"。
2.2 核心直觉:语义相似 = 向量距离近
用一张图理解:
#mermaid-svg-vfBlPrl8K1TvZtHq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vfBlPrl8K1TvZtHq .error-icon{fill:#552222;}#mermaid-svg-vfBlPrl8K1TvZtHq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vfBlPrl8K1TvZtHq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vfBlPrl8K1TvZtHq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vfBlPrl8K1TvZtHq .marker.cross{stroke:#333333;}#mermaid-svg-vfBlPrl8K1TvZtHq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vfBlPrl8K1TvZtHq p{margin:0;}#mermaid-svg-vfBlPrl8K1TvZtHq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vfBlPrl8K1TvZtHq .cluster-label text{fill:#333;}#mermaid-svg-vfBlPrl8K1TvZtHq .cluster-label span{color:#333;}#mermaid-svg-vfBlPrl8K1TvZtHq .cluster-label span p{background-color:transparent;}#mermaid-svg-vfBlPrl8K1TvZtHq .label text,#mermaid-svg-vfBlPrl8K1TvZtHq span{fill:#333;color:#333;}#mermaid-svg-vfBlPrl8K1TvZtHq .node rect,#mermaid-svg-vfBlPrl8K1TvZtHq .node circle,#mermaid-svg-vfBlPrl8K1TvZtHq .node ellipse,#mermaid-svg-vfBlPrl8K1TvZtHq .node polygon,#mermaid-svg-vfBlPrl8K1TvZtHq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vfBlPrl8K1TvZtHq .rough-node .label text,#mermaid-svg-vfBlPrl8K1TvZtHq .node .label text,#mermaid-svg-vfBlPrl8K1TvZtHq .image-shape .label,#mermaid-svg-vfBlPrl8K1TvZtHq .icon-shape .label{text-anchor:middle;}#mermaid-svg-vfBlPrl8K1TvZtHq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vfBlPrl8K1TvZtHq .rough-node .label,#mermaid-svg-vfBlPrl8K1TvZtHq .node .label,#mermaid-svg-vfBlPrl8K1TvZtHq .image-shape .label,#mermaid-svg-vfBlPrl8K1TvZtHq .icon-shape .label{text-align:center;}#mermaid-svg-vfBlPrl8K1TvZtHq .node.clickable{cursor:pointer;}#mermaid-svg-vfBlPrl8K1TvZtHq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vfBlPrl8K1TvZtHq .arrowheadPath{fill:#333333;}#mermaid-svg-vfBlPrl8K1TvZtHq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vfBlPrl8K1TvZtHq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vfBlPrl8K1TvZtHq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vfBlPrl8K1TvZtHq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vfBlPrl8K1TvZtHq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vfBlPrl8K1TvZtHq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vfBlPrl8K1TvZtHq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vfBlPrl8K1TvZtHq .cluster text{fill:#333;}#mermaid-svg-vfBlPrl8K1TvZtHq .cluster span{color:#333;}#mermaid-svg-vfBlPrl8K1TvZtHq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vfBlPrl8K1TvZtHq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vfBlPrl8K1TvZtHq rect.text{fill:none;stroke-width:0;}#mermaid-svg-vfBlPrl8K1TvZtHq .icon-shape,#mermaid-svg-vfBlPrl8K1TvZtHq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vfBlPrl8K1TvZtHq .icon-shape p,#mermaid-svg-vfBlPrl8K1TvZtHq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vfBlPrl8K1TvZtHq .icon-shape .label rect,#mermaid-svg-vfBlPrl8K1TvZtHq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vfBlPrl8K1TvZtHq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vfBlPrl8K1TvZtHq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vfBlPrl8K1TvZtHq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 语义相近
语义相近
语义相近
语义无关
'跑鞋'
'运动鞋'
'马拉松鞋'
'笔记本电脑'
'MacBook'
在向量空间中:
- "跑鞋"、"运动鞋"、"马拉松鞋" → 聚在一团
- "笔记本电脑"、"MacBook" → 聚在另一团
- 两团之间距离很远
2.3 Embedding 模型的本质
Embedding 模型 = 一个神经网络,输入文本,输出固定维度的向量:
输出向量 Embedding 模型 用户输入 输出向量 Embedding 模型 用户输入 #mermaid-svg-1te4ZOLAYyXyO5PV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1te4ZOLAYyXyO5PV .error-icon{fill:#552222;}#mermaid-svg-1te4ZOLAYyXyO5PV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1te4ZOLAYyXyO5PV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1te4ZOLAYyXyO5PV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1te4ZOLAYyXyO5PV .marker.cross{stroke:#333333;}#mermaid-svg-1te4ZOLAYyXyO5PV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1te4ZOLAYyXyO5PV p{margin:0;}#mermaid-svg-1te4ZOLAYyXyO5PV .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1te4ZOLAYyXyO5PV text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-1te4ZOLAYyXyO5PV .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1te4ZOLAYyXyO5PV .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-1te4ZOLAYyXyO5PV .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-1te4ZOLAYyXyO5PV .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-1te4ZOLAYyXyO5PV #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-1te4ZOLAYyXyO5PV .sequenceNumber{fill:white;}#mermaid-svg-1te4ZOLAYyXyO5PV #sequencenumber{fill:#333;}#mermaid-svg-1te4ZOLAYyXyO5PV #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-1te4ZOLAYyXyO5PV .messageText{fill:#333;stroke:none;}#mermaid-svg-1te4ZOLAYyXyO5PV .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1te4ZOLAYyXyO5PV .labelText,#mermaid-svg-1te4ZOLAYyXyO5PV .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-1te4ZOLAYyXyO5PV .loopText,#mermaid-svg-1te4ZOLAYyXyO5PV .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-1te4ZOLAYyXyO5PV .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1te4ZOLAYyXyO5PV .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-1te4ZOLAYyXyO5PV .noteText,#mermaid-svg-1te4ZOLAYyXyO5PV .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-1te4ZOLAYyXyO5PV .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1te4ZOLAYyXyO5PV .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1te4ZOLAYyXyO5PV .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1te4ZOLAYyXyO5PV .actorPopupMenu{position:absolute;}#mermaid-svg-1te4ZOLAYyXyO5PV .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-1te4ZOLAYyXyO5PV .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1te4ZOLAYyXyO5PV .actor-man circle,#mermaid-svg-1te4ZOLAYyXyO5PV line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-1te4ZOLAYyXyO5PV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Tokenizer → Encoder → Pooling "我喜欢编程"0.23, -0.45, 0.89, ... (768维)返回向量
主流模型维度对照:
| 模型 | 维度 | 上下文长度 | 特点 |
|---|---|---|---|
| OpenAI text-embedding-ada-002 | 1536 | 8191 token | 英文强,经典模型 |
| OpenAI text-embedding-3-small | 1536/512/256 | 8191 token | 可自定义维度,性价比高 |
| BGE-M3 | 1024 | 8192 token | 中英文双语,多功能 |
| Cohere embed-multilingual-v3 | 1024 | 512 token | 93种语言 |
| 通义 text-embedding-v4 | 1024/768/256 | 8192 token | 阿里,中文优化 |
2.4 维度到底是什么?为什么不同模型维度不同?
⚠️ 这是最容易混淆的概念。很多人看到"768维"、"1024维"、"1536维"就以为是某种神秘数字,其实它有明确的物理含义。
2.4.1 维度的本质:每个维度 = 一个特征轴
想象你在描述一个人,可以用这些特征来刻画:
身高 → 维度1
体重 → 维度2
年龄 → 维度3
职业 → 维度4
...
Embedding 的维度同理------每个维度代表一个潜在的语义特征轴,模型在训练过程中自动学习出这些轴的含义:
维度1 → 可能与"人工 vs 自然"相关
维度2 → 可能与"正式 vs 口语"相关
维度3 → 可能与"正面 vs 负面"情感相关
维度4 → 可能与"具体 vs 抽象"相关
...
维度768 → 某种更细微的语义区分
💡 生活案例:你描述一个水果,用2个特征(甜度、酸度)可以区分苹果和柠檬;但要用10个特征(甜度、酸度、硬度、颜色、大小、香气、季节、产地、用途、价格)才能区分苹果、梨、橙子、葡萄、香蕉......维度越多,描述的"颗粒度"越细。
2.4.2 维度由模型架构决定
维度 = Transformer 模型的 hidden_size(隐藏层大小):
#mermaid-svg-BU1YbkR4hzkc5Px4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BU1YbkR4hzkc5Px4 .error-icon{fill:#552222;}#mermaid-svg-BU1YbkR4hzkc5Px4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BU1YbkR4hzkc5Px4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .marker.cross{stroke:#333333;}#mermaid-svg-BU1YbkR4hzkc5Px4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BU1YbkR4hzkc5Px4 p{margin:0;}#mermaid-svg-BU1YbkR4hzkc5Px4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .cluster-label text{fill:#333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .cluster-label span{color:#333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .cluster-label span p{background-color:transparent;}#mermaid-svg-BU1YbkR4hzkc5Px4 .label text,#mermaid-svg-BU1YbkR4hzkc5Px4 span{fill:#333;color:#333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .node rect,#mermaid-svg-BU1YbkR4hzkc5Px4 .node circle,#mermaid-svg-BU1YbkR4hzkc5Px4 .node ellipse,#mermaid-svg-BU1YbkR4hzkc5Px4 .node polygon,#mermaid-svg-BU1YbkR4hzkc5Px4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BU1YbkR4hzkc5Px4 .rough-node .label text,#mermaid-svg-BU1YbkR4hzkc5Px4 .node .label text,#mermaid-svg-BU1YbkR4hzkc5Px4 .image-shape .label,#mermaid-svg-BU1YbkR4hzkc5Px4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-BU1YbkR4hzkc5Px4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BU1YbkR4hzkc5Px4 .rough-node .label,#mermaid-svg-BU1YbkR4hzkc5Px4 .node .label,#mermaid-svg-BU1YbkR4hzkc5Px4 .image-shape .label,#mermaid-svg-BU1YbkR4hzkc5Px4 .icon-shape .label{text-align:center;}#mermaid-svg-BU1YbkR4hzkc5Px4 .node.clickable{cursor:pointer;}#mermaid-svg-BU1YbkR4hzkc5Px4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .arrowheadPath{fill:#333333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BU1YbkR4hzkc5Px4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BU1YbkR4hzkc5Px4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BU1YbkR4hzkc5Px4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BU1YbkR4hzkc5Px4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BU1YbkR4hzkc5Px4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BU1YbkR4hzkc5Px4 .cluster text{fill:#333;}#mermaid-svg-BU1YbkR4hzkc5Px4 .cluster span{color:#333;}#mermaid-svg-BU1YbkR4hzkc5Px4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BU1YbkR4hzkc5Px4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BU1YbkR4hzkc5Px4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-BU1YbkR4hzkc5Px4 .icon-shape,#mermaid-svg-BU1YbkR4hzkc5Px4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BU1YbkR4hzkc5Px4 .icon-shape p,#mermaid-svg-BU1YbkR4hzkc5Px4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BU1YbkR4hzkc5Px4 .icon-shape .label rect,#mermaid-svg-BU1YbkR4hzkc5Px4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BU1YbkR4hzkc5Px4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BU1YbkR4hzkc5Px4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BU1YbkR4hzkc5Px4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CLS Token
Mean Pooling
模型架构决定维度
输入文本
Tokenizer
Token IDs: 101, 2345, 789, ...
Transformer Encoder
Hidden State: h1, h2, ..., hN
Pooling
输出向量: 768维 / 1024维 / 1536维
bert-base: 768
bert-large: 1024
text-embedding-ada-002: 1536
| 模型 | 维度 | 底层架构 | 说明 |
|---|---|---|---|
| all-MiniLM-L6-v2 | 384 | TinyBERT | 最轻量,适合快速原型 |
| BGE-base | 768 | BERT-base | 中文基础模型 |
| BGE-large | 1024 | BERT-large | 精度更高,但更慢更大 |
| text-embedding-ada-002 | 1536 | GPT-style encoder | OpenAI 经典模型 |
| text-embedding-3-small | 1024 | 自定义 | 可选 256/512/1024 |
2.4.3 维度对系统的实际影响
| 维度大小 | 存储成本 | 检索速度 | 精度(召回率) | 适用场景 |
|---|---|---|---|---|
| 384(MiniLM) | 100万向量 × 384 × 4B ≈ 1.5GB | 快 | ⭐⭐⭐ 中等 | 原型验证、小规模数据 |
| 768(BGE-base) | 100万 × 768 × 4B ≈ 3GB | 中等 | ⭐⭐⭐⭐ 较好 | 生产环境主流选择 |
| 1024(BGE-large) | 100万 × 1024 × 4B ≈ 4GB | 较慢 | ⭐⭐⭐⭐⭐ 优秀 | 对精度要求高的场景 |
| 1536(Ada-002) | 100万 × 1536 × 4B ≈ 6GB | 最慢 | ⭐⭐⭐⭐⭐ 优秀 | 英文为主的生产环境 |
⚠️ 关键认知 :维度不是越高越好,而是够用就好。维度增加会带来:
- 存储成本线性增长
- 检索延迟增加(距离计算次数 = 维度数)
- 但精度提升存在边际效应,超过一定值后收益递减
2.4.4 同一文本在不同维度下的表现
文本:"Spring Boot 3.x 如何配置数据源"
BGE-base (768维) → [0.023, -0.451, 0.892, ...] (768个浮点数)
BGE-large (1024维) → [0.023, -0.451, 0.892, 0.015, ...] (1024个浮点数,多了256个更精细的特征)
Ada-002 (1536维) → [0.023, -0.451, 0.892, 0.015, ...] (1536个浮点数,更细粒度)
这三个向量描述的是同一批语义信息,只是粒度不同。低维是"粗略素描",高维是"高清照片"。
2.4.5 选型建议
#mermaid-svg-sZfM5Tkk3KqtKKmB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sZfM5Tkk3KqtKKmB .error-icon{fill:#552222;}#mermaid-svg-sZfM5Tkk3KqtKKmB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sZfM5Tkk3KqtKKmB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .marker.cross{stroke:#333333;}#mermaid-svg-sZfM5Tkk3KqtKKmB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sZfM5Tkk3KqtKKmB p{margin:0;}#mermaid-svg-sZfM5Tkk3KqtKKmB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .cluster-label text{fill:#333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .cluster-label span{color:#333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .cluster-label span p{background-color:transparent;}#mermaid-svg-sZfM5Tkk3KqtKKmB .label text,#mermaid-svg-sZfM5Tkk3KqtKKmB span{fill:#333;color:#333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .node rect,#mermaid-svg-sZfM5Tkk3KqtKKmB .node circle,#mermaid-svg-sZfM5Tkk3KqtKKmB .node ellipse,#mermaid-svg-sZfM5Tkk3KqtKKmB .node polygon,#mermaid-svg-sZfM5Tkk3KqtKKmB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sZfM5Tkk3KqtKKmB .rough-node .label text,#mermaid-svg-sZfM5Tkk3KqtKKmB .node .label text,#mermaid-svg-sZfM5Tkk3KqtKKmB .image-shape .label,#mermaid-svg-sZfM5Tkk3KqtKKmB .icon-shape .label{text-anchor:middle;}#mermaid-svg-sZfM5Tkk3KqtKKmB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sZfM5Tkk3KqtKKmB .rough-node .label,#mermaid-svg-sZfM5Tkk3KqtKKmB .node .label,#mermaid-svg-sZfM5Tkk3KqtKKmB .image-shape .label,#mermaid-svg-sZfM5Tkk3KqtKKmB .icon-shape .label{text-align:center;}#mermaid-svg-sZfM5Tkk3KqtKKmB .node.clickable{cursor:pointer;}#mermaid-svg-sZfM5Tkk3KqtKKmB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .arrowheadPath{fill:#333333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sZfM5Tkk3KqtKKmB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sZfM5Tkk3KqtKKmB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sZfM5Tkk3KqtKKmB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sZfM5Tkk3KqtKKmB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sZfM5Tkk3KqtKKmB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sZfM5Tkk3KqtKKmB .cluster text{fill:#333;}#mermaid-svg-sZfM5Tkk3KqtKKmB .cluster span{color:#333;}#mermaid-svg-sZfM5Tkk3KqtKKmB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sZfM5Tkk3KqtKKmB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sZfM5Tkk3KqtKKmB rect.text{fill:none;stroke-width:0;}#mermaid-svg-sZfM5Tkk3KqtKKmB .icon-shape,#mermaid-svg-sZfM5Tkk3KqtKKmB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sZfM5Tkk3KqtKKmB .icon-shape p,#mermaid-svg-sZfM5Tkk3KqtKKmB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sZfM5Tkk3KqtKKmB .icon-shape .label rect,#mermaid-svg-sZfM5Tkk3KqtKKmB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sZfM5Tkk3KqtKKmB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sZfM5Tkk3KqtKKmB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sZfM5Tkk3KqtKKmB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} < 10万向量
10万 ~ 1000万
> 1000万
快速迭代/原型
生产级精度
开始选型
业务规模?
384维 MiniLM ✅
精度要求?
768维 + PQ压缩
768维 BGE-base ✅
1024维 BGE-large ✅
Java 团队推荐起步配置:BGE-base(768维)+ HNSW 索引,兼顾精度与性能。后续根据效果再升级到大维度模型。
03 深入理解:相似度度量 --- 为什么存在三种?
3.1 问题的本质:我们到底想比较什么?
拿到两个向量 A 和 B,要判断它们是否"相似"。但"相似"在不同场景下含义不同:
场景一:文本语义搜索
"编程" → [0.23, -0.45, 0.89, ...] (短文本,向量长度可能较小)
"这是一段关于编程的长篇文章..." → [0.23, -0.45, 0.89, ...] (长文本,向量长度可能较大)
→ 我们希望只看方向,不关心长度 → 用余弦相似度
场景二:图片特征检索
"红色苹果" → [0.8, 0.1, 0.3, ...]
"黄色香蕉" → [0.1, 0.9, 0.2, ...]
→ 我们希望看绝对距离 → 用欧氏距离
场景三:推荐系统(用户-物品矩阵)
用户A对物品X的评分向量 vs 用户B对物品X的评分向量
→ 我们希望计算点积,衡量"共同兴趣强度"
📌 一句话总结:度量方式的选择取决于业务场景------你想比较的是"方向"还是"距离"?
3.2 三种度量的几何意义
#mermaid-svg-CjMyl3k2iObGsdmS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CjMyl3k2iObGsdmS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CjMyl3k2iObGsdmS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CjMyl3k2iObGsdmS .error-icon{fill:#552222;}#mermaid-svg-CjMyl3k2iObGsdmS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CjMyl3k2iObGsdmS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CjMyl3k2iObGsdmS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CjMyl3k2iObGsdmS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CjMyl3k2iObGsdmS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CjMyl3k2iObGsdmS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CjMyl3k2iObGsdmS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CjMyl3k2iObGsdmS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CjMyl3k2iObGsdmS .marker.cross{stroke:#333333;}#mermaid-svg-CjMyl3k2iObGsdmS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CjMyl3k2iObGsdmS p{margin:0;}#mermaid-svg-CjMyl3k2iObGsdmS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CjMyl3k2iObGsdmS .cluster-label text{fill:#333;}#mermaid-svg-CjMyl3k2iObGsdmS .cluster-label span{color:#333;}#mermaid-svg-CjMyl3k2iObGsdmS .cluster-label span p{background-color:transparent;}#mermaid-svg-CjMyl3k2iObGsdmS .label text,#mermaid-svg-CjMyl3k2iObGsdmS span{fill:#333;color:#333;}#mermaid-svg-CjMyl3k2iObGsdmS .node rect,#mermaid-svg-CjMyl3k2iObGsdmS .node circle,#mermaid-svg-CjMyl3k2iObGsdmS .node ellipse,#mermaid-svg-CjMyl3k2iObGsdmS .node polygon,#mermaid-svg-CjMyl3k2iObGsdmS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CjMyl3k2iObGsdmS .rough-node .label text,#mermaid-svg-CjMyl3k2iObGsdmS .node .label text,#mermaid-svg-CjMyl3k2iObGsdmS .image-shape .label,#mermaid-svg-CjMyl3k2iObGsdmS .icon-shape .label{text-anchor:middle;}#mermaid-svg-CjMyl3k2iObGsdmS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CjMyl3k2iObGsdmS .rough-node .label,#mermaid-svg-CjMyl3k2iObGsdmS .node .label,#mermaid-svg-CjMyl3k2iObGsdmS .image-shape .label,#mermaid-svg-CjMyl3k2iObGsdmS .icon-shape .label{text-align:center;}#mermaid-svg-CjMyl3k2iObGsdmS .node.clickable{cursor:pointer;}#mermaid-svg-CjMyl3k2iObGsdmS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CjMyl3k2iObGsdmS .arrowheadPath{fill:#333333;}#mermaid-svg-CjMyl3k2iObGsdmS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CjMyl3k2iObGsdmS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CjMyl3k2iObGsdmS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CjMyl3k2iObGsdmS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CjMyl3k2iObGsdmS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CjMyl3k2iObGsdmS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CjMyl3k2iObGsdmS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CjMyl3k2iObGsdmS .cluster text{fill:#333;}#mermaid-svg-CjMyl3k2iObGsdmS .cluster span{color:#333;}#mermaid-svg-CjMyl3k2iObGsdmS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CjMyl3k2iObGsdmS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CjMyl3k2iObGsdmS rect.text{fill:none;stroke-width:0;}#mermaid-svg-CjMyl3k2iObGsdmS .icon-shape,#mermaid-svg-CjMyl3k2iObGsdmS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CjMyl3k2iObGsdmS .icon-shape p,#mermaid-svg-CjMyl3k2iObGsdmS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CjMyl3k2iObGsdmS .icon-shape .label rect,#mermaid-svg-CjMyl3k2iObGsdmS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CjMyl3k2iObGsdmS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CjMyl3k2iObGsdmS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CjMyl3k2iObGsdmS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 点积
方向 × 长度
两者都考虑
适合推荐系统
欧氏距离
只关心距离
向量长度不同有影响
适合空间几何
余弦相似度
只关心方向
向量长度不同没关系
适合文本检索
3.3 余弦相似度 --- 为什么是文本检索的首选?
3.3.1 公式推导
cos(θ) = (A · B) / (||A|| × ||B||)
其中:
A · B = Σ(ai × bi) ← 点积
||A|| = √(Σai²) ← 向量的长度(L2范数)
||B|| = √(Σbi²) ← 向量的长度
3.3.2 为什么长文本和短文本比较时,余弦比欧氏更合理?
java
// 假设 Embedding 模型输出已归一化(长度为1)
float[] shortText = {0.3f, 0.4f, 0.5f, 0.6f}; // 短文本,"编程"
float[] longText = {0.3f, 0.4f, 0.5f, 0.6f}; // 长文本,同样的语义
// 余弦相似度 = 1.0(完全相同的方向)✅
// 欧氏距离 = 0(完全相同的位置)✅
// 但如果向量未归一化:
float[] shortText2 = {0.3f, 0.4f, 0.5f, 0.6f}; // 长度 ≈ 0.97
float[] longText2 = {0.6f, 0.8f, 1.0f, 1.2f}; // 长度 ≈ 1.93(值放大了2倍)
// 余弦相似度仍然 = 1.0(方向相同)✅
// 欧氏距离 = √(0.3²+0.4²+0.5²+0.6²) ≈ 0.97(距离很大)❌
⚠️ 关键认知:大多数 Embedding 模型输出已经 L2 归一化,此时余弦相似度 ≈ 点积。但如果不归一化,余弦比欧氏更适合文本检索。
3.3.3 取值范围的含义
| 余弦值 | 含义 | 示例 |
|---|---|---|
| 1.0 | 完全相同方向 | "编程" vs "代码" |
| 0.8~0.9 | 高度相似 | "跑鞋" vs "运动鞋" |
| 0.5~0.7 | 中度相似 | "跑鞋" vs "运动鞋品牌" |
| 0.0 | 无关 | "编程" vs "篮球" |
| -0.5~-0.8 | 完全相反 | 反义词(少见) |
3.4 欧氏距离 --- 什么时候用它?
3.4.1 公式
d(A, B) = √(Σ(ai - bi)²)
3.4.2 与余弦的本质区别
#mermaid-svg-2QT4RWiscbJtZ5sC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2QT4RWiscbJtZ5sC .error-icon{fill:#552222;}#mermaid-svg-2QT4RWiscbJtZ5sC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2QT4RWiscbJtZ5sC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2QT4RWiscbJtZ5sC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2QT4RWiscbJtZ5sC .marker.cross{stroke:#333333;}#mermaid-svg-2QT4RWiscbJtZ5sC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2QT4RWiscbJtZ5sC p{margin:0;}#mermaid-svg-2QT4RWiscbJtZ5sC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2QT4RWiscbJtZ5sC .cluster-label text{fill:#333;}#mermaid-svg-2QT4RWiscbJtZ5sC .cluster-label span{color:#333;}#mermaid-svg-2QT4RWiscbJtZ5sC .cluster-label span p{background-color:transparent;}#mermaid-svg-2QT4RWiscbJtZ5sC .label text,#mermaid-svg-2QT4RWiscbJtZ5sC span{fill:#333;color:#333;}#mermaid-svg-2QT4RWiscbJtZ5sC .node rect,#mermaid-svg-2QT4RWiscbJtZ5sC .node circle,#mermaid-svg-2QT4RWiscbJtZ5sC .node ellipse,#mermaid-svg-2QT4RWiscbJtZ5sC .node polygon,#mermaid-svg-2QT4RWiscbJtZ5sC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2QT4RWiscbJtZ5sC .rough-node .label text,#mermaid-svg-2QT4RWiscbJtZ5sC .node .label text,#mermaid-svg-2QT4RWiscbJtZ5sC .image-shape .label,#mermaid-svg-2QT4RWiscbJtZ5sC .icon-shape .label{text-anchor:middle;}#mermaid-svg-2QT4RWiscbJtZ5sC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2QT4RWiscbJtZ5sC .rough-node .label,#mermaid-svg-2QT4RWiscbJtZ5sC .node .label,#mermaid-svg-2QT4RWiscbJtZ5sC .image-shape .label,#mermaid-svg-2QT4RWiscbJtZ5sC .icon-shape .label{text-align:center;}#mermaid-svg-2QT4RWiscbJtZ5sC .node.clickable{cursor:pointer;}#mermaid-svg-2QT4RWiscbJtZ5sC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2QT4RWiscbJtZ5sC .arrowheadPath{fill:#333333;}#mermaid-svg-2QT4RWiscbJtZ5sC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2QT4RWiscbJtZ5sC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2QT4RWiscbJtZ5sC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2QT4RWiscbJtZ5sC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2QT4RWiscbJtZ5sC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2QT4RWiscbJtZ5sC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2QT4RWiscbJtZ5sC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2QT4RWiscbJtZ5sC .cluster text{fill:#333;}#mermaid-svg-2QT4RWiscbJtZ5sC .cluster span{color:#333;}#mermaid-svg-2QT4RWiscbJtZ5sC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2QT4RWiscbJtZ5sC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2QT4RWiscbJtZ5sC rect.text{fill:none;stroke-width:0;}#mermaid-svg-2QT4RWiscbJtZ5sC .icon-shape,#mermaid-svg-2QT4RWiscbJtZ5sC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2QT4RWiscbJtZ5sC .icon-shape p,#mermaid-svg-2QT4RWiscbJtZ5sC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2QT4RWiscbJtZ5sC .icon-shape .label rect,#mermaid-svg-2QT4RWiscbJtZ5sC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2QT4RWiscbJtZ5sC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2QT4RWiscbJtZ5sC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2QT4RWiscbJtZ5sC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 欧氏距离
比位置
向量A
直线距离
√(ΣΔ²)
余弦相似度
只比方向
向量A
夹角θ
cos(θ)
3.4.3 反例:什么时候欧氏距离会出错?
java
// 场景:图片特征检索
float[] redApple = {0.9f, 0.1f, 0.1f}; // 红色为主
float[] greenApple = {0.1f, 0.9f, 0.1f}; // 绿色为主
float[] banana = {0.1f, 0.1f, 0.9f}; // 黄色为主
// 欧氏距离:
// redApple vs greenApple = √(0.8²+0.8²+0) ≈ 1.13(相近)✅
// redApple vs banana = √(0.8²+0.1²+0.8²) ≈ 1.14(相近)❌ 应该更远!
// 余弦相似度:
// redApple vs greenApple ≈ 0.19(不相似)✅
// redApple vs banana ≈ 0.19(不相似)✅
💡 结论 :欧氏距离在高维空间中容易失效(所有点距离都趋近于相同),文本检索基本不用欧氏距离。
3.5 点积 --- 为什么推荐系统用它?
3.5.1 公式
score(A, B) = A · B = Σ(ai × bi)
3.5.2 与余弦的关系
当向量已归一化(||A|| = ||B|| = 1)时:
点积 = 余弦相似度
当向量未归一化时:
点积 = 余弦相似度 × ||A|| × ||B||
= 方向相似度 × 向量长度
3.5.3 为什么推荐系统偏好点积?
java
// 用户-物品评分矩阵
// 用户A: [5, 4, 3, 0, 0] → 喜欢电影1、2、3,没看过4、5
// 用户B: [4, 5, 4, 0, 0] → 喜欢电影1、2、3,没看过4、5
// 用户C: [1, 1, 1, 5, 5] → 喜欢电影4、5,没看过1、2、3
float[] userA = {5, 4, 3, 0, 0};
float[] userB = {4, 5, 4, 0, 0};
float[] userC = {1, 1, 1, 5, 5};
// 点积:
// userA · userB = 5×4 + 4×5 + 3×4 + 0 + 0 = 47 ✅ 高度相似
// userA · userC = 5×1 + 4×1 + 3×1 + 0 + 0 = 12 ✅ 几乎不相似
// 余弦相似度(归一化后):
// cos(A, B) ≈ 0.99 ✅
// cos(A, C) ≈ 0.27 ✅
📌 关键洞察:点积同时考虑了"兴趣方向"和"兴趣强度"。用户A和用户B不仅喜欢的电影类型相同(方向),而且评分都很高(强度大)。
3.6 选型决策树
#mermaid-svg-5JGiaZpAZhgJmj21{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5JGiaZpAZhgJmj21 .error-icon{fill:#552222;}#mermaid-svg-5JGiaZpAZhgJmj21 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5JGiaZpAZhgJmj21 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5JGiaZpAZhgJmj21 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5JGiaZpAZhgJmj21 .marker.cross{stroke:#333333;}#mermaid-svg-5JGiaZpAZhgJmj21 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5JGiaZpAZhgJmj21 p{margin:0;}#mermaid-svg-5JGiaZpAZhgJmj21 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5JGiaZpAZhgJmj21 .cluster-label text{fill:#333;}#mermaid-svg-5JGiaZpAZhgJmj21 .cluster-label span{color:#333;}#mermaid-svg-5JGiaZpAZhgJmj21 .cluster-label span p{background-color:transparent;}#mermaid-svg-5JGiaZpAZhgJmj21 .label text,#mermaid-svg-5JGiaZpAZhgJmj21 span{fill:#333;color:#333;}#mermaid-svg-5JGiaZpAZhgJmj21 .node rect,#mermaid-svg-5JGiaZpAZhgJmj21 .node circle,#mermaid-svg-5JGiaZpAZhgJmj21 .node ellipse,#mermaid-svg-5JGiaZpAZhgJmj21 .node polygon,#mermaid-svg-5JGiaZpAZhgJmj21 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5JGiaZpAZhgJmj21 .rough-node .label text,#mermaid-svg-5JGiaZpAZhgJmj21 .node .label text,#mermaid-svg-5JGiaZpAZhgJmj21 .image-shape .label,#mermaid-svg-5JGiaZpAZhgJmj21 .icon-shape .label{text-anchor:middle;}#mermaid-svg-5JGiaZpAZhgJmj21 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5JGiaZpAZhgJmj21 .rough-node .label,#mermaid-svg-5JGiaZpAZhgJmj21 .node .label,#mermaid-svg-5JGiaZpAZhgJmj21 .image-shape .label,#mermaid-svg-5JGiaZpAZhgJmj21 .icon-shape .label{text-align:center;}#mermaid-svg-5JGiaZpAZhgJmj21 .node.clickable{cursor:pointer;}#mermaid-svg-5JGiaZpAZhgJmj21 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5JGiaZpAZhgJmj21 .arrowheadPath{fill:#333333;}#mermaid-svg-5JGiaZpAZhgJmj21 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5JGiaZpAZhgJmj21 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5JGiaZpAZhgJmj21 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5JGiaZpAZhgJmj21 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5JGiaZpAZhgJmj21 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5JGiaZpAZhgJmj21 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5JGiaZpAZhgJmj21 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5JGiaZpAZhgJmj21 .cluster text{fill:#333;}#mermaid-svg-5JGiaZpAZhgJmj21 .cluster span{color:#333;}#mermaid-svg-5JGiaZpAZhgJmj21 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5JGiaZpAZhgJmj21 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5JGiaZpAZhgJmj21 rect.text{fill:none;stroke-width:0;}#mermaid-svg-5JGiaZpAZhgJmj21 .icon-shape,#mermaid-svg-5JGiaZpAZhgJmj21 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5JGiaZpAZhgJmj21 .icon-shape p,#mermaid-svg-5JGiaZpAZhgJmj21 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5JGiaZpAZhgJmj21 .icon-shape .label rect,#mermaid-svg-5JGiaZpAZhgJmj21 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5JGiaZpAZhgJmj21 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5JGiaZpAZhgJmj21 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5JGiaZpAZhgJmj21 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
文本语义检索
图片/音频特征
推荐系统
开始选型
数据已归一化?
点积 ≈ 余弦,选哪个都行
业务场景?
余弦相似度 ✅
欧氏距离 ✅
点积 ✅
统一用余弦
3.7 Java 实现:三种度量对比
java
public class SimilarityMetrics {
/**
* 余弦相似度:只比较方向
*/
public static double cosineSimilarity(float[] a, float[] b) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
/**
* 欧氏距离:比较绝对距离
*/
public static double euclideanDistance(float[] a, float[] b) {
double sum = 0.0;
for (int i = 0; i < a.length; i++) {
double diff = a[i] - b[i];
sum += diff * diff;
}
return Math.sqrt(sum);
}
/**
* 点积:同时考虑方向和强度
*/
public static double dotProduct(float[] a, float[] b) {
double sum = 0.0;
for (int i = 0; i < a.length; i++) {
sum += a[i] * b[i];
}
return sum;
}
// 测试
public static void main(String[] args) {
float[] vec1 = {0.3f, 0.4f, 0.5f, 0.6f}; // "编程"
float[] vec2 = {0.31f, 0.41f, 0.51f, 0.61f}; // "代码"(相似)
float[] vec3 = {-0.5f, 0.2f, 0.1f, -0.3f}; // "足球"(无关)
System.out.println("余弦相似度:");
System.out.println(" 编程 vs 代码 = " + cosineSimilarity(vec1, vec2)); // ≈ 0.999
System.out.println(" 编程 vs 足球 = " + cosineSimilarity(vec1, vec3)); // ≈ -0.13
System.out.println("欧氏距离:");
System.out.println(" 编程 vs 代码 = " + euclideanDistance(vec1, vec2)); // ≈ 0.02
System.out.println(" 编程 vs 足球 = " + euclideanDistance(vec1, vec3)); // ≈ 1.14
System.out.println("点积:");
System.out.println(" 编程 vs 代码 = " + dotProduct(vec1, vec2)); // ≈ 0.94
System.out.println(" 编程 vs 足球 = " + dotProduct(vec1, vec3)); // ≈ -0.11
}
}
运行结果:
余弦相似度:
编程 vs 代码 = 0.999995 ✅ 高度相似
编程 vs 足球 = -0.129999 ✅ 几乎不相关
欧氏距离:
编程 vs 代码 = 0.019999 ✅ 很近
编程 vs 足球 = 1.140175 ✅ 很远
点积:
编程 vs 代码 = 0.940001 ✅ 高相关
编程 vs 足球 = -0.110000 ✅ 负相关
04 工程实践:Spring AI 获取 Embedding
4.1 Maven 依赖
xml
<!-- Spring AI OpenAI 兼容实现 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<!-- BGE-M3 可通过兼容接口调用 -->
4.2 配置文件 application.yml
yaml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY} # 或使用兼容端点
base-url: https://api.openai.com # 或自定义端点
embedding:
options:
model: text-embedding-3-small # 或 bge-m3
dimensions: 1024
4.3 Java 代码示例
java
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class EmbeddingController {
@Autowired
private EmbeddingModel embeddingModel;
/**
* 获取单条文本的 Embedding 向量
*/
@GetMapping("/api/embedding")
public List<Double> getEmbedding(@RequestParam String text) {
float[] vector = embeddingModel.embed(text);
return List.of(ArrayUtil.toObject(vector));
}
/**
* 批量获取 Embedding(性能更优)
*/
@GetMapping("/api/embedding/batch")
public List<List<Double>> batchEmbedding(@RequestParam List<String> texts) {
List<float[]> vectors = embeddingModel.embed(texts);
return vectors.stream()
.map(ArrayUtil::toObject)
.toList();
}
/**
* 计算两条文本的余弦相似度
*/
@GetMapping("/api/similarity")
public double similarity(@RequestParam String text1, @RequestParam String text2) {
float[] v1 = embeddingModel.embed(text1);
float[] v2 = embeddingModel.embed(text2);
return cosineSimilarity(v1, v2);
}
private double cosineSimilarity(float[] v1, float[] v2) {
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
for (int i = 0; i < v1.length; i++) {
dotProduct += v1[i] * v2[i];
norm1 += v1[i] * v1[i];
norm2 += v2[i] * v2[i];
}
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
}
4.4 调用示例
bash
# 获取单条 Embedding
GET /api/embedding?text=我喜欢编程
# 批量获取
GET /api/embedding/batch?texts=编程&texts=代码&texts=足球
# 计算相似度
GET /api/similarity?text1=跑鞋&text2=运动鞋
# 返回:0.87(高度相似)
GET /api/similarity?text1=跑鞋&text2=笔记本电脑
# 返回:0.12(几乎不相关)
05 总结
| 要点 | 说明 |
|---|---|
| Embedding 是什么 | 将文本映射为固定维度的稠密向量,保留语义信息 |
| 核心直觉 | 语义相似 → 向量距离近 → 余弦相似度高分 |
| 度量方式 | 文本检索优先用余弦相似度 |
| 主流模型 | 英文选 OpenAI,中文选 BGE-M3 / 通义 |
| Spring AI | 通过 EmbeddingModel 接口统一调用,兼容 OpenAI 协议 |
📌 面试金句:Embedding 的本质是把离散的符号(文字)映射到连续的空间,让计算机能"理解"语义关系,而不是机械匹配字面。