这篇重点学什么
本篇重点:先核对查询和文档的编码模型版本、归一化方式与索引是否兼容;换模型或迁移索引后检查检索结果,再用标注样例确定并检验相似度阈值。
开始读正文 ↓学过相近内容?查看哪些可以跳过
两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。
向量与相似度
共同基础点积、范数、余弦与稳定计算统一以数学篇为主读;掌握后不重复做同类数值练习。
另一条路线补充什么?
推导并计算 L1 / L2 / L∞ 范数、点积和余弦,再用相近数相减的例子检查浮点抵消误差,学习稳定计算。
读完这篇,你能做什么
能手算并运行余弦示例,解释为什么跨模型向量不能只凭维度相同就混用。
学习理由、准备要求与知识点
为什么学它
检索会比较向量有多接近。理解距离分数在表示什么,才能选对索引,也避免把“相似”误当成“事实可靠”。
先读这些内容
知识点清单
- 标量、向量与矩阵
- 维度与对应位置
- 点积
- 向量长度与零向量
- 余弦相似度
- 相似度与概率区别
- Embedding 表示
- 模型版本与编码兼容
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
想让程序比较学习笔记是否相关,可以先把文本表示为一组数字,再计算这些表示之间的关系。一个数叫标量,固定位置的一组数叫向量,有行列的数表叫矩阵。向量有多少维,并不天然对应多少个知识点;它能做什么取决于训练方式,单个坐标通常也不能直接翻译成某个词或清楚的含义。
Embedding 会把文本变成固定维度的向量,查询和文档必须采用互相兼容的编码方式。两个模型即使都输出同样长的数组,它们每个位置代表的坐标体系也可能不同。更换模型或预处理方式后,通常应重新生成旧向量,或按版本分开索引;程序能完成点积运算,不代表新查询与旧文档的比较仍有意义。
复习已学内容:需要时展开讲解和代码
基础主读:向量、点积与范数 ↗
点积把相同位置相乘再求和,向量长度是平方和开根号,余弦相似度用点积除以两个长度。它主要比较方向,忽略整体正比例缩放;因此[1,2]与[2,4]余弦为一。相似度不是概率,也不是事实支持程度,仍需用任务标注校准检索阈值。
文中这几个词,先认一认
- 点积
- 把两个同维向量的对应数相乘,再把结果相加。它同时受到向量方向和长度的影响。
- 范数
- 按规定计算向量有多大的方法,本篇使用由勾股定理推广出的欧氏长度。
- 零向量
- 每个位置都是零的向量。它没有方向,因此不能直接代入余弦相似度公式。
02看看它怎么工作
复习已学内容:需要时展开讲解和代码
基础主读:向量、点积与范数 ↗
手算[1,0]与[0,1]:点积为零,长度都为一,余弦为零;与[-1,0]则为负一。负值表示该表示空间中的相反方向,不应直接解释为自然语言逻辑否定。哪些相似度对应相关资料,要通过同一模型、相同任务上的标注数据观察。
直接计算平方可能溢出。例如Double.MAX_VALUE的平方变成无穷,而两个相同巨大向量理论余弦仍是一。算法先分别除以各自最大绝对坐标,再计算点积与长度;因为正比例缩放在余弦分子分母中相互抵消,数学结果保持不变,同时中间数值更稳定。
计算相似度前,先检查向量维度、零向量,以及 NaN 或无穷这类非有限值。NaN 会让比较排序失常,无穷可能让归一化无效,不能等搜不到结果再查。正常浮点计算可能产生很小的舍入误差,最后可以把结果限制在 [-1,1];但这一步不能用来掩盖输入非法或维度错误。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
T["文本与编码版本"] --> E["兼容 Embedding"]
E --> C["维度及有限值检查"]
C --> S["最大绝对值缩放"]
S --> D["点积和范数"]
D --> R["相似度与标注验收"]按顺序看,每一步做了什么
- 固定编码模型、维度、预处理与版本标识。
- 逐向量检查维度、非有限值和零向量。
- 按最大绝对值缩放,再计算点积及长度。
- 得到余弦后按相关标注评估排序与阈值。
- 保存来源版本,模型切换时重建或隔离索引。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · Double.isFinite
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
for (double value : vector) {
// NaN 与正负无穷会破坏距离和排序,进入检索前明确拒绝。
if (!Double.isFinite(value)) throw new IllegalArgumentException("non-finite coordinate");
}
// 输入有效只证明数值可计算,不证明向量来自兼容模型。Double.isFinite区分有限数与NaN、无穷,是数值输入校验的真实JDK入口。零是有限数,所以零向量还需单独检查。
浮点数的范围、精度和特殊值是表示的属性,与Embedding模型优劣无关。输入校验应在公共边界执行,避免某个异常坐标污染整个候选排序。
Java代码负责可验证的向量算术,没有实现Embedding网络训练。真正的编码器如何学习表示,应阅读Embedding课程与模型原始资料;本题只是应用层相似度桥梁。
本例缩放减少平方溢出的风险,但浮点误差仍存在。判断数值测试使用容差,不能要求所有数学等价表达式在所有机器上产生逐位相同的结果。
代码先调用谁,再调用谁
- 读取向量及模型元数据
Double.isFinite校验坐标- 寻找最大绝对坐标
- 归一缩放后累计内积与平方和
- 输出有边界的余弦值
04动手写一个例子
完成主读数学篇的稳定余弦实现后,可以跳过这里的数值算法复习。余弦为负不说明两段文字互相矛盾,相似度也不是答案正确的概率;阈值要用对应模型和任务的标注数据确定并检查。接着检查编码版本与索引迁移;下方批量近邻和量化可按需深入。
基础主读:向量、点积与范数 ↗
复习已学内容:需要时展开讲解和代码
基础主读:向量、点积与范数 ↗
数值稳定的余弦相似度
输入两个等长非空有限实数向量,输出余弦;零向量与非法输入拒绝。Java17完整程序内置容差断言,编译Main.java后执行java Main。
- 输入 1
[1,2]与[2,4]- 输出 1
1
- 输入 2
[1,0]与[0,1]- 输出 2
0
先找两个向量各自最大绝对值ma、mb。若任意最大值为零,则对应向量没有方向,不能通过加一个任意极小常数悄悄定义业务相似度。
将每个坐标分别除以ma或mb,新坐标绝对值不超过一。余弦中的ma和mb在分子、分母抵消,因此缩放保留方向。累计ab、aa、bb,再计算ab/sqrt(aa)/sqrt(bb)。
分两次除法避免先把两个长度相乘引入额外溢出。最后处理舍入误差,测试包括极大坐标和极小非零坐标,确认稳定性策略确实覆盖了容易失败的输入。
为什么这样写是对的处理前i维后,ab是缩放向量前缀点积,aa与bb是对应前缀平方和;完整向量正比例缩放不改变理论余弦。
d为维度,两次线性扫描,只有固定数量累加器;输入数组不被修改。此复杂度不包括生成Embedding的神经网络推理。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
public class Main {
static double cosine(double[] a,double[] b){
if(a==null||b==null||a.length==0||a.length!=b.length)throw new IllegalArgumentException("dimension");
double ma=0,mb=0;
for(int i=0;i<a.length;i++){
if(!Double.isFinite(a[i])||!Double.isFinite(b[i]))throw new IllegalArgumentException("non-finite");
ma=Math.max(ma,Math.abs(a[i]));mb=Math.max(mb,Math.abs(b[i]));
}
if(ma==0||mb==0)throw new IllegalArgumentException("zero vector"); // 零向量没有方向。
double ab=0,aa=0,bb=0;
for(int i=0;i<a.length;i++){
double x=a[i]/ma,y=b[i]/mb; // 正比例缩放避免巨大坐标先平方溢出。
ab+=x*y;aa+=x*x;bb+=y*y;
}
double value=ab/Math.sqrt(aa)/Math.sqrt(bb);
return Math.max(-1,Math.min(1,value)); // 只修正有限计算中的微小舍入越界。
}
static void near(double expected,double got){if(Math.abs(expected-got)>1e-12)throw new AssertionError(got);}
static void bad(double[] a,double[] b){try{cosine(a,b);throw new AssertionError();}catch(IllegalArgumentException expected){}}
public static void main(String[] args){
near(1,cosine(new double[]{1,2},new double[]{2,4}));
near(0,cosine(new double[]{1,0},new double[]{0,1}));
near(-1,cosine(new double[]{1},new double[]{-1}));
near(1,cosine(new double[]{Double.MAX_VALUE},new double[]{Double.MIN_VALUE}));
bad(new double[]{0},new double[]{1});bad(new double[]{Double.NaN},new double[]{1});
bad(new double[]{1},new double[]{1,2});
System.out.println("PASS 7 tests");
}
}这些特殊情况也要试一试
- 零向量不定义余弦,需显式拒绝或由业务规定独立状态。
- 同维度不同模型仍不兼容,算法无法从数字推断模型版本。
- 极大和极小坐标使用缩放,比较结果用数值容差。
进阶选读 · 后续再读:批量近邻与向量量化
场景百万级向量需要减少存储和比较成本。
做法先建立精确搜索作为质量基线,再评估量化与近似索引;量化改变坐标精度,应比较召回与相关排序,不能只验证文件变小。
复杂度精确逐查询O(nd),量化或近似索引的查询成本取决于结构和参数,不保证对任意数据均为O(log n)。
好处与代价低精度和近似搜索节省资源,但可能改变近邻;高风险资料需保留版本和可复核的精确比较通路。
05项目里怎么用
检索入库时应记录模型标识、维度、归一化方式、原文片段ID及版本。查询端核对这些配置,发现不一致应停止或路由到匹配索引。只比较数组长度会让错误结果悄悄出现,这比直接抛维度异常更难定位。
阈值应由实际任务定义。课程标题很短时,相似度分布可能与长篇笔记不同;换语言、换模型或换语料后,原阈值不能直接照搬。准备相关、不相关、相似措辞但事实相反的样本,分别检查漏检与误检,再决定是否增加重排。
向量相似度只帮助选候选,引用是否支持答案还要读原文。两个段落可能主题相似但版本结论相反;对外输出时保留来源、版本与适用条件,而不是展示一个相似度数字就宣称“可信度百分之九十”。
容易踩的坑
- 把余弦相似度乘一百显示为正确概率。
- 混用维度相同但来源模型不同的向量。
- 零向量加极小常数后默默继续排序。
- 直接平方巨大坐标导致无穷与
NaN传播。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 余弦 | 强调方向,适合已按该指标训练的表示 | 忽略长度信息,零向量需处理 | 语义检索的常见配置 |
| 点积 | 保留方向与长度,计算直接 | 长度可能主导排序 | 模型训练明确使用点积 |
| 欧氏距离 | 几何含义直观 | 受尺度与归一化影响 | 编码和索引采用一致欧氏配置 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01余弦一代表什么?
展开参考答案与推导
非零向量同方向;它不是文本必然等义、事实相同或概率百分之百的保证。
02相同维度为什么还不够?
展开参考答案与推导
坐标体系来自具体编码模型及预处理;不同模型的同一位置不必表示兼容信息。
03缩放为什么不改变余弦?
展开参考答案与推导
向量乘正数时,点积与两个长度的乘积获得相同缩放因子,分子分母相消。
换个情况,还会处理吗
01更换Embedding后召回突然下降怎么办?
展开参考答案与推导
核对查询与语料是否同时迁移、模型维度和归一化是否一致,再用固定标注集比较;不要先凭感觉调整阈值。
02相似度很高却引用错误版本怎么办?
展开参考答案与推导
增加版本与权限过滤,检查原文支持;语义相似不能替代版本约束和断言核对。
03排序出现NaN,应在哪里处理?
展开参考答案与推导
在向量进入计算或索引前拒绝非有限坐标,并检查零向量和溢出;不要让比较器把异常值当普通分数。
写代码试一试
01计算反方向向量
- 输入 / 条件
[2,0]与[-3,0]- 输出 / 预期
-1
提示 点积为负。
展开参考答案与推导
点积为-6,长度乘积为6,余弦为-1;不据此推断自然语言逻辑矛盾。
02如何增加对称性测试?
- 输入 / 条件
a=[1,2],b=[3,4]- 输出 / 预期
cos(a,b)约等于cos(b,a)
提示 使用容差比较。
展开参考答案与推导
交换输入应保留点积和范数乘积;若差异超容差,检查维度索引或错误的单侧归一化。
03为什么不能把编码耗时计入O(d)结论?
- 输入 / 条件
文本生成向量后计算余弦- 输出 / 预期
O(d)只覆盖已给定数组
提示 区分接口边界。
展开参考答案与推导
神经网络编码有独立的结构、序列长度和硬件成本;本题输入已是向量,因此不对模型推理给出线性维度承诺。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能手算并运行余弦示例,解释为什么跨模型向量不能只凭维度相同就混用。
本篇重点加练
相似度的业务含义依赖编码版本,重点验证分数解释与升级后的可比性。
- 相似度与概率区别
- 模型版本与编码兼容
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
用三篇笔记各含四个特征的例子区分标量、向量和矩阵;交换其中两个特征位置,解释比较前还需核对哪些契约。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点标量、向量与矩阵 · 维度与对应位置
- 作业 02代码推演
对向量[2,-1,3]、[0,4,1]和零向量手算点积与长度,保留推导和输入约束,自行规定无效输入的返回或异常方式。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点点积 · 向量长度与零向量
- 作业 03原理问答
为两份编码配置建立比较资格表,区分相同维度、相同模型、相同归一化与相同阈值这几种声明各需要什么证据。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点相似度与概率区别 · 模型版本与编码兼容
综合应用
- 作业 04面试场景
两组不同模型给相同文本对打出0.76和0.88的相似度。设计验证方案判断是否能共用阈值,并说明分数能够支持哪些业务结论。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点余弦相似度 · 相似度与概率区别
- 作业 05编程实践
为笔记向量记录增加编码模型、维度、版本与来源,写一个兼容性校验器,测试同维不同版本、旧文档新查询和缺失元数据。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点Embedding 表示 · 模型版本与编码兼容
- 作业 06排错实验
用微型语料模拟索引升级,混入一个旧版本向量并观察检索结果;记录发现不兼容、重建、切换和回滚的证据。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点维度与对应位置 · 余弦相似度 · 模型版本与编码兼容
- 作业 07编程实践
编写离线检索结果审计器,输入文档版本、查询编码版本、相似度与人工相关标签;混入高分不相关和跨版本记录,检验哪些记录可用于阈值评价。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点相似度与概率区别 · 模型版本与编码兼容
- 作业 08面试场景
切换模型后平均相似度提高但用户投诉增多。提出一个区分分数尺度变化与实际质量变化的小样本调查,明确不能直接跨版本合并的统计量。
- 提交内容
- 场景评审记录,包含约束、备选方案、未决信息和验证计划。
- 怎样算完成
- 方案回应题目约束,取舍能对应到可核验的事实。
- 给出失败或条件变化时的检查方式。
练到的知识点相似度与概率区别 · 模型版本与编码兼容
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
在掌握标注与检索评测后,用带人工相关性标签的小样本比较阈值策略,报告样本范围和不能推广到其他模型的限制。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点向量长度与零向量 · 相似度与概率区别 · Embedding 表示
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 反例构造
构造“维度相同且余弦值更大,回答就更可信”的反例案例,给出需要补充核查的编码与业务证据。
- 提交内容
- 具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
- 怎样算完成
- 反例满足原主张所声称的前提,确实针对其结论。
- 说明一个反例能推翻什么、不能进一步证明什么。
练到的知识点相似度与概率区别 · 模型版本与编码兼容
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读教程中文
Google:嵌入入门 (新标签页)↗
先看编码、向量与嵌入空间的图示,建立相似度直觉;涉及训练的部分可在应用练习后再读。
- 实践参考文档英文
Spring AI:Vector Databases (新标签页)↗
阅读向量存储、相似度搜索及元数据过滤的抽象,核对嵌入配置和实际数据库能力。
- 深入参考文档英文
pgvector:向量检索与索引说明 (新标签页)↗
先读距离运算符和精确搜索,再读 HNSW、过滤与索引参数,使用同一数据比较速度和召回。
- 深入参考文档英文
OpenJDK 17 Double 源码 (新标签页)↗
按指定版本定位
Double.isFinite;对照源码分析理解实际调用边界。