RAG 系统核心:深入理解向量相似度匹配与文本向量化
一、文本向量化概念与向量维度选择
1.1 什么是文本向量化?
文本向量化是将非结构化的文本(如句子、段落、文档)转化为低维或高维数值向量的过程。其核心目标是:让计算机通过 “向量距离” 衡量文本语义相似度 —— 向量越接近(距离越小),文本语义越相似。
例如:
- 文本 A:“猫在追老鼠” → 向量 A:[0.23, 0.11, -0.45, ..., 0.67]
- 文本 B:“猫咪追逐耗子” → 向量 B:[0.21, 0.13, -0.42, ..., 0.65]
- 向量 A 与向量 B 的距离极小,计算机可判断两者语义相似。
1.2 向量维度选择:高维 vs 低维
维度类型 | 典型维度范围 | 核心作用 | 优势 | 劣势 | 适用场景 |
低维向量 | 32~256 维 | 平衡性能与精度,适合大规模数据检索 | 1. 计算速度快(相似度算法耗时与维度正相关);2. 存储成本低(128 维向量仅需 512 字节 / 条);3. 抗噪声能力强(维度低时 “维度灾难” 影响小) | 语义表达能力有限,复杂文本(如技术文档)的细节可能丢失 | 1. 百万级以上文档的大规模检索;2. 对响应速度要求高的场景(如实时客服 RAG);3. 文本内容简单(如商品标题、短问答) |
高维向量 | 512~4096 维 | 精准捕捉文本语义细节,适合高质量检索 | 语义表达能力强,可区分细微语义差异(如 “机器学习” vs “深度学习”) | 1. 计算速度慢(768 维向量的相似度计算耗时是 128 维的 6 倍);2. 存储成本高(768 维向量需 3072 字节 / 条);3. 易受 “维度灾难” 影响(高维空间中向量稀疏,距离区分度下降) | 1. 十万级以下文档的小规模检索;2. 对精度要求极高的场景(如学术论文 RAG、医疗文献检索);3. 复杂长文本(如技术手册、法律条文) |
二、向量相似度匹配算法精讲
向量相似度匹配是 RAG 检索阶段的核心 —— 给定 “查询向量”,从向量库中找出 “最相似的 Top-K 个文档向量”。常用算法可分为 “精确匹配” 与 “近似匹配” 两类,前者适合小规模数据,后者适合大规模数据。
1. 精确匹配算法:计算真实距离(无误差)
(1)欧几里得距离(Euclidean Distance)
- 原理:计算两个向量在空间中的直线距离,距离越小相似度越高。
- 公式:对于向量a=(a1,a2,...,an)和b=(b1,b2,...,bn),距离d=√[(a1-b1)²+(a2-b2)²+...+(an-bn)²]。
- 特点:对向量的 “绝对数值” 敏感,适合低维向量。
import ai.djl.modality.ndarray.NDArray;
import ai.djl.modality.ndarray.NDManager;public class SimilarityAlgorithms {// 欧几里得距离(返回距离,值越小越相似)public static float euclideanDistance(float[] vec1, float[] vec2) {if (vec1.length != vec2.length) {throw new IllegalArgumentException("向量维度不一致");}float sum = 0.0f;for (int i = 0; i < vec1.length; i++) {sum += Math.pow(vec1[i] - vec2[i], 2);}return (float) Math.sqrt(sum);}public static void main(String[] args) {// 示例:两个相似文本的向量float[] queryVec = {0.23f, 0.11f, -0.45f, 0.67f}; // 查询向量float[] docVec1 = {0.21f, 0.13f, -0.42f, 0.65f}; // 相似文档向量float[] docVec2 = {0.89f, -0.32f, 0.15f, -0.23f}; // 不相似文档向量System.out.println("查询与doc1的欧氏距离:" + euclideanDistance(queryVec, docVec1)); // 输出:~0.05(小,相似)System.out.println("查询与doc2的欧氏距离:" + euclideanDistance(queryVec, docVec2)); // 输出:~1.3(大,不相似)}
}
(2)余弦相似度(Cosine Similarity)
- 原理:计算两个向量的夹角余弦值,取值范围[-1,1],值越接近 1 表示方向越一致(语义越相似)。
- 公式:cosθ=(a·b)/(|a|×|b|),其中a·b是向量点积,|a|是向量 a 的模。
- 特点:对向量的 “方向” 敏感,对 “长度” 不敏感(如文本长度差异不影响结果),是 RAG 系统的 “首选精确算法”(尤其高维向量)。
// 余弦相似度(返回相似度,值越接近1越相似)
public static float cosineSimilarity(float[] vec1, float[] vec2) {if (vec1.length != vec2.length) {throw new IllegalArgumentException("向量维度不一致");}float dotProduct = 0.0f; // 点积float norm1 = 0.0f; // vec1的模float norm2 = 0.0f; // vec2的模for (int i = 0; i < vec1.length; i++) {dotProduct += vec1[i] * vec2[i];norm1 += Math.pow(vec1[i], 2);norm2 += Math.pow(vec2[i], 2);}// 避免除以0(空向量场景)if (norm1 == 0 || norm2 == 0) {return 0.0f;}return dotProduct / (float) (Math.sqrt(norm1) * Math.sqrt(norm2));
}// 测试
public static void main(String[] args) {float[] queryVec = {0.23f, 0.11f, -0.45f, 0.67f};float[] docVec1 = {0.21f, 0.13f, -0.42f, 0.65f};float[] docVec2 = {0.89f, -0.32f, 0.15f, -0.23f};System.out.println("查询与doc1的余弦相似度:" + cosineSimilarity(queryVec, docVec1)); // 输出:~0.98(接近1,相似)System.out.println("查询与doc2的余弦相似度:" + cosineSimilarity(queryVec, docVec2)); // 输出:~-0.2(负,不相似)
}
2. 近似匹配算法:牺牲少量精度换速度(大规模数据)
当向量库数据量超过 100 万条时,精确算法(O (n) 时间复杂度)会因 “遍历所有向量” 导致检索耗时过高(如 1000 万条向量需秒级响应),此时需用近似匹配算法(O (log n) 时间复杂度)。
(1)FAISS(Facebook AI Similarity Search)
- 原理:通过 “向量索引”(如 IVF_FLAT、IVF_PQ)减少检索时的向量对比数量,核心是 “分桶 + 量化”:
- 分桶:将向量库按聚类分成多个 “桶”(如 100 个桶);
- 检索:先找到与查询向量最接近的几个桶,再在桶内做精确匹配。
- 特点:支持亿级向量检索,响应时间可从秒级降至毫秒级;精度可通过 “桶数量” 调节(桶越多精度越高,速度越慢)。
import com.facebook.faiss.Index;
import com.facebook.faiss.IndexFlatL2;
import com.facebook.faiss.IndexIVFFlat;
import com.facebook.faiss.MetricType;
import com.facebook.faiss.VectorTransform;
import com.facebook.faiss.contrib.IndexScaNN;import java.util.Arrays;public class FAISSExample {public static void main(String[] args) {// 1. 配置参数(向量维度:4,向量库大小:1000条)int dim = 4;int numDocs = 1000;// 2. 生成模拟向量库(1000条4维向量)float[][] docVectors = new float[numDocs][dim];for (int i = 0; i < numDocs; i++) {for (int j = 0; j < dim; j++) {docVectors[i][j] = (float) (Math.random() * 2 - 1); // 随机值:[-1,1]}}// 3. 构建FAISS索引(IVF_FLAT:分桶+精确匹配,适合100万级数据)// 3.1 先创建聚类中心(10个桶)IndexFlatL2 quantizer = new IndexFlatL2(dim);IndexIVFFlat index = new IndexIVFFlat(quantizer, dim, 1</doubaocanvas>
三、面试常见问题 QA
Q1: 为什么在 RAG 中通常使用余弦相似度而不是欧氏距离?
- 余弦相似度,专门看方向的。两篇文章意思越像,它们的向量方向就越一致,这个值就越接近1。它不关心文章写得多长多短。
- 欧氏距离,是计算空间中的直线距离,它既看方向,也看大小。如果一篇文章很长(向量很长),即使它和另一篇意思差不多,它们之间的距离也可能很远,这就不太合理了。
所以,用余弦相似度更能公平地衡量语义上的相似性,不会被文章长度干扰。
Q2: 如何处理高维向量带来的“维度灾难”问题?
A: “维度灾难”指高维空间中所有向量都变得稀疏且距离趋同,导致相似度区分度下降。
- 使用更好的模型: 现代嵌入模型(如 SBERT)经过良好训练,能在高维空间产生区分性强的向量。
- ANN 索引: HNSW、IVF 等算法本身就是为了高效处理高维数据而设计的。
- 降维: 可以使用 PCA 等技术在索引前先降低向量维度,但可能会损失信息,需谨慎评估。
Q3: Faiss 中的 IVF-PQ 索引是什么?
A: 它是一种结合了两种技术的混合索引。
- IVF: 先将向量聚类,搜索时只搜索最近的几个簇,大大缩小搜索范围。比如我们先给图书馆所有的书按主题分好类(比如历史、文学、科学)。你要找一本讲“二战”的书,管理员只会去历史类的书架上找,而不会跑遍全馆。这大大缩小了搜索范围,所以快。
- PQ: 将高维向量切分成多个子段,对每个子段进行量化(创建码本并映射到码字),用压缩后的编码代表原始向量,极大减少内存占用。好比我们存书的时候,不存整本书,而是存一个精简的摘要。这个摘要很小,但能代表这本书的核心内容。这样就能在内存里存下海量的书,所以省地方。
IVF-PQ 在速度和内存效率上做到了极佳的平衡,非常适合超大规模数据集。