--- title: "03-Embedding向量化原理与模型选型" created: 2026-05-15 tags: - 博客 --- # Embedding向量化原理与模型选型 假设你是个媒人,手里有1000个单身男女的资料,要给他们配对。 每个人的资料是一段文字描述: ``` 小王:28岁程序员,喜欢打游戏看动漫,宅男一枚,偶尔爬山 小李:26岁设计师,热爱旅行摄影,周末喜欢逛展览 小张:27岁产品经理,喜欢打篮球健身,性格外向 ``` 现在有个新人小美:"25岁,喜欢户外运动和摄影,希望找一个有共同爱好的人" 你怎么快速找出最适合小美的人选? **方法一:关键词匹配** 搜索"户外运动"和"摄影",找到包含这些词的人。 问题是:小王的资料里写的是"爬山",不是"户外运动";小李的资料里写的是"旅行摄影",不是单独的"摄影"。关键词匹配可能漏掉他们。 **方法二:理解语义** "爬山"和"户外运动"意思相近,"旅行摄影"也包含"摄影"的意思。如果能理解这层语义关系,就能找到真正匹配的人。 这就是Embedding要解决的问题——**让机器理解文字的"意思",而不只是匹配字面。** > Embedding的本质 > > Embedding(向量化)是将文字转换为高维数值向量的过程。其核心价值在于:**语义相近的文字,在向量空间中的距离也近**。这使得机器能够通过数学运算来理解和比较文字的"意思",而不仅仅是匹配字面字符。 ## 什么是向量 在讲Embedding之前,先回顾一下高数里学过的向量。 ### 向量就是一串数字 向量可以理解为一个带方向的箭头,用一组数字来表示: - **一维向量**:`[3]`,数轴上的一个点 - **二维向量**:`[3, 4]`,平面上的一个点 - **三维向量**:`[3, 4, 5]`,空间中的一个点 三维以上就不好画图了,但数学上没问题——可以是384维、768维、1536维,这些就叫**高维向量**。 ### 向量能表示什么 在RAG场景里,向量用来表示文字的**语义特征**。 每个维度代表一个"语义方向",比如(这是打比方,实际不是这么简单): - 第1维:是否涉及"人物" - 第2维:是否涉及"动作" - 第3维:是否涉及"时间" - 第4维:是否涉及"技术" - ... 一段文字被转成向量后,每个维度上的数值就表示这段文字在这个"语义方向"上的权重。 ### 向量之间的距离 这是关键点——**语义相近的文字,向量在空间中的距离也近。** ``` "打印机怎么用" → [0.23, -0.45, 0.67, ...] "产品使用方法" → [0.25, -0.42, 0.65, ...] ← 很接近 "今天天气不错" → [-0.89, 0.12, 0.03, ...] ← 差很远 ``` 这就是为什么向量检索能做语义匹配——不是比字面,是比"意思"。 ### 向量空间的可视化 在这种表示下,语义相近的内容会在向量空间中聚集(距离更近)。例如,"猫""狗"等词的向量在空间中会聚在一起,因为它们都是"动物"相关词;而与车辆相关的词则聚成另一簇。 ![[7-Blog/AI技术/RAG详细解析/assets/FkQ3lLvgzcDRWrn1TiK8i4JZdJEH-6f6ca760.png]] 下图展示了更多词汇在向量空间中的分布,可以看到同类概念自然聚集: ![[7-Blog/AI技术/RAG详细解析/assets/FoupCi4e9k8WIU2hCy9IWiWuWdxj-289a9ad4.png]] 通过向量表示,模型可以量化语义相似度——两个内容的向量越接近,它们的含义就越相近。 下图展示了"Animals"(动物类:Wolf、Dog、Cat)和"Fruits"(水果类:Banana、Apple)两个语义簇,当查询"Kitten"时,它会自动落在动物类聚簇附近: ![[7-Blog/AI技术/RAG详细解析/assets/FuCf7xnG_4mZRy53m2-p536kFCQ5-7f6a0a6f.png]] ## Embedding:把文字变成向量 ### Embedding是个翻译官 Embedding的工作就是把文字"翻译"成向量。 ``` 输入:一段文字 输出:一串数字(向量) ``` 这个翻译过程由**Embedding模型**(也叫向量模型)完成。 ![[7-Blog/AI技术/RAG详细解析/assets/Fk24tDLOUERZioGLILbKzVo6Md8M-e6a1c849.png]] ### Embedding模型是怎么训练出来的 简单说,是让模型学习大量的文本,学会"什么样的文字意思相近"。 训练过程大致是: - 准备大量的文本对,标注哪些是相似的,哪些是不相似的 - 让模型把这些文本转成向量 - 调整模型参数,让相似文本的向量距离近,不相似的距离远 - 反复训练,直到模型学会 训练好的模型就能把任意文字转成合适的向量了。 ### 常见的Embedding模型 | 模型 | 维度 | 特点 | | --- | --- | --- | | text-embedding-ada-002 (OpenAI) | 1536 | 效果好,但要调API | | text-embedding-v2 (阿里) | 1536 | 国产,对中文友好 | | bge-large-zh (BAAI) | 1024 | 开源,中文效果好 | | m3e-base | 768 | 开源,轻量级 | | nomic-embed-text | 768 | 开源,支持离线 | 选择建议: - 要效果好:text-embedding-v2 或 OpenAI的模型 - 要私有部署:bge系列或m3e - 要轻量级:m3e-base ## 向量相似度计算 把文字变成向量后,怎么判断两个向量有多"像"? ### 余弦相似度(最常用) 想象两个向量是两根箭头,从原点出发指向不同方向。 余弦相似度看的是这两根箭头的**夹角**: - 夹角越小(指向差不多),越相似 - 夹角90度(垂直),完全不相关 - 夹角180度(方向相反),语义相反 下图直观展示了四种常见的向量距离度量方式,其中余弦相似度关注的是向量夹角: ![[7-Blog/AI技术/RAG详细解析/assets/FsLqOnJhL3JNUFnxfJswelmtIWEM-25682374.png]] 计算公式: ``` 余弦相似度 = (A · B) / (|A| × |B|) ``` 结果范围是[-1, 1]: - 1:完全相同 - 0:不相关 - -1:完全相反 **优点**:只关心方向,不关心长度。即使两段文字长短差很多,只要意思相近,相似度就高。 这也是为什么RAG系统普遍用余弦相似度。 ### 欧几里得距离 就是空间中两点的直线距离。距离越小,越相似。 适合有"量"的概念的场景,比如图像相似度。 ### 点积 ``` A · B = a1×b1 + a2×b2 + ... + an×bn ``` 计算简单,速度快。Transformer的注意力机制用的就是点积。 ### 实际应用中怎么选 绝大部分RAG场景,**用余弦相似度就对了**。 所有主流向量数据库(Milvus、PGVector、Qdrant)都默认支持余弦相似度。 > 相似度计算的选择 > > 余弦相似度是RAG场景的标准选择,原因是它只关注向量的方向(语义倾向),不受向量长度(文本长短)的影响。这对于长短不一的文档块特别重要——一段100字的摘要和一段1000字的详述,只要讲的是同一件事,余弦相似度就能正确识别它们的相关性。 ## Spring AI中的向量化 ### EmbeddingModel接口 Spring AI通过`EmbeddingModel`接口统一封装了不同的向量模型: ```typescript public interface EmbeddingModel { // 单条文本向量化 float[] embed(String text); // 批量向量化 List embed(List texts); // 获取向量维度 int dimensions(); } ``` 不管你用的是OpenAI、阿里云还是本地模型,代码都是一样的。 ### 使用阿里云的向量模型 先加依赖: ``` com.alibaba.cloud.ai spring-ai-alibaba-starter 1.0.0 ``` 配置API Key: ```yaml spring: ai: dashscope: api-key: sk-xxxxx embedding: model: text-embedding-v2 ``` 然后就可以注入使用了: ```java @Service public class EmbeddingService { @Autowired private EmbeddingModel embeddingModel; public float[] getEmbedding(String text) { return embeddingModel.embed(text); } public List getEmbeddings(List texts) { return embeddingModel.embed(texts); } } ``` ### 计算两段文字的相似度 ```java public double calculateSimilarity(String text1, String text2) { float[] vec1 = embeddingModel.embed(text1); float[] vec2 = embeddingModel.embed(text2); return cosineSimilarity(vec1, vec2); } private double cosineSimilarity(float[] a, float[] b) { double dotProduct = 0; double normA = 0; double normB = 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)); } ``` 试一试: ``` double sim1 = calculateSimilarity("打印机怎么用", "产品使用方法"); // 结果约 0.85,相似度高 double sim2 = calculateSimilarity("打印机怎么用", "今天天气真好"); // 结果约 0.12,相似度低 ``` ## 实战:电商商品推荐 来个具体的业务场景——电商平台的商品推荐。 ### 场景描述 用户在搜索框输入"适合夏天穿的裙子",我们要从商品库里找出最相关的商品。 商品库里的商品描述是这样的: ``` 商品1:夏季新款碎花连衣裙,轻薄透气,适合度假穿着 商品2:冬季加厚羽绒服,保暖防寒,适合北方冬天 商品3:春季百搭针织衫,柔软舒适,可搭配各种下装 商品4:夏日清凉吊带裙,纯棉面料,凉爽舒适 ``` ### 传统关键词匹配 搜索"夏天"+"裙子",只能匹配到商品4(包含"裙")。 商品1虽然是"夏季"+"连衣裙",但关键词不完全匹配,可能被漏掉。 ### 向量语义匹配 把所有商品描述转成向量,存起来。 用户搜索时,把搜索词也转成向量,找最相似的。 ```java @Service public class ProductSearchService { @Autowired private EmbeddingModel embeddingModel; @Autowired private VectorStore vectorStore; // 商品入库时,生成向量并存储 public void indexProduct(Product product) { String description = product.getDescription(); Document doc = new Document(description); doc.getMetadata().put("productId", product.getId()); doc.getMetadata().put("productName", product.getName()); doc.getMetadata().put("price", product.getPrice()); vectorStore.add(List.of(doc)); } // 用户搜索时,做语义匹配 public List search(String query, int topK) { List results = vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(topK) .similarityThreshold(0.5) .build() ); return results.stream() .map(doc -> { Long productId = (Long) doc.getMetadata().get("productId"); return productRepository.findById(productId).orElse(null); }) .filter(Objects::nonNull) .collect(Collectors.toList()); } } ``` 搜索"适合夏天穿的裙子",结果: ``` 1. 商品1:夏季新款碎花连衣裙(相似度0.89) 2. 商品4:夏日清凉吊带裙(相似度0.87) 3. 商品3:春季百搭针织衫(相似度0.45) 4. 商品2:冬季加厚羽绒服(相似度0.21) ``` 语义匹配能理解"夏天≈夏季≈夏日"、"裙子≈连衣裙≈吊带裙",比关键词匹配智能多了。 ## 完整Demo:给学习诉求匹配课程路线 上面的商品推荐更偏业务理解,下面补一个真正可以独立跑起来的完整示例。 这次我们不再用“商品搜索”或“退货规则”做例子,而是换成一个更贴近本站内容的场景: - 候选数据不是商品,而是几条不同的学习路线 - 查询语句不是搜索词,而是一段学员的学习诉求 - 先把每条路线的介绍文字转成向量,再把学习诉求转成向量 - 最后用余弦相似度排序,找出最匹配的路线 ### 示例中项目地址 - 项目模块:`ai-example-demo-rag` 运行前,直接改 `main` 方法开头这三个参数即可: ``` String apiKey = "请替换成你的 SiliconFlow API Key"; String apiUrl = "https://api.siliconflow.cn/v1/embeddings"; String modelName = "Qwen/Qwen3-Embedding-8B"; ``` 完整代码如下: ```java package org.javaup.ai.test; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.HashMap; import java.util.Map; /** * 演示如何使用 Embedding 给“学习诉求”和“课程路线”做语义匹配。 * 这里直接调用 SiliconFlow 的 Embedding API,不依赖项目里的 Spring AI 配置。 */ public class EmbeddingLearningPathDemo { public static void main(String[] args) throws Exception { String apiKey = "请替换成你的 SiliconFlow API Key"; String apiUrl = "https://api.siliconflow.cn/v1/embeddings"; String modelName = "Qwen/Qwen3-Embedding-8B"; HttpClient httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(20)) .build(); ObjectMapper objectMapper = new ObjectMapper(); List learningPaths = List.of( new LearningPath( "P001", "Java 高并发与分布式实战", "已经掌握 Spring Boot,希望补齐缓存、消息队列、限流熔断和分布式事务的同学", "Spring Boot、Redis、Kafka、分布式锁、幂等设计、秒杀系统", "能独立设计高并发下单链路,并定位常见性能瓶颈" ), new LearningPath( "P002", "MySQL 与 Redis 性能优化路线", "经常写 SQL,但对索引设计、慢查询治理和缓存一致性还不够熟的同学", "索引优化、执行计划、锁机制、缓存击穿、缓存雪崩、热点 Key", "能系统分析数据库慢查询问题,并设计稳定的缓存策略" ), new LearningPath( "P003", "Spring AI 与 RAG 项目实战", "想用 Java 做 AI 应用,希望掌握文档切块、向量化、检索增强和答案生成流程的同学", "Spring AI、Embedding、向量数据库、Chunk、Prompt、RAG、知识库问答", "能搭建一个完整的智能问答系统,并理解从文本到召回的核心链路" ), new LearningPath( "P004", "前端工程化与交互设计", "需要独立完成管理后台或业务门户,希望系统掌握组件化和工程化实践的同学", "Vue、React、TypeScript、状态管理、构建优化、组件设计", "能设计清晰可维护的前端工程结构,并完成复杂页面开发" ), new LearningPath( "P005", "云原生部署与 DevOps 路线", "负责服务上线和日常运维,希望掌握容器化、CI/CD 和服务可观测性的同学", "Docker、Kubernetes、GitHub Actions、灰度发布、监控告警、日志采集", "能把 Java 服务稳定部署到容器平台,并建立基础运维体系" ), new LearningPath( "P006", "数据分析与可视化入门", "想做报表分析、经营复盘和指标看板,希望提升数据建模与图表表达能力的同学", "数据清洗、指标体系、BI 报表、可视化图表、业务分析、经营复盘", "能围绕业务问题搭建基础分析模型,并输出清晰的数据结论" ) ); String learnerNeed = """ 我已经会一点 Spring Boot,最近想做一个能读取文档、切分文本块、 计算相似度并回答问题的 Java AI 项目,最好还能顺手学会向量检索和 RAG 的完整链路。 """; runSemanticMatch(httpClient, objectMapper, apiKey, apiUrl, modelName, learningPaths, learnerNeed, 3); } private static void runSemanticMatch(HttpClient httpClient, ObjectMapper objectMapper, String apiKey, String apiUrl, String modelName, List learningPaths, String learnerNeed, int topK) throws Exception { List pathPortraits = learningPaths.stream() .map(EmbeddingLearningPathDemo::buildPortrait) .toList(); System.out.println("=== Embedding 学习路线匹配 Demo ==="); System.out.println("Embedding 模型: " + modelName); System.out.println("候选路线数量: " + learningPaths.size()); System.out.println(); System.out.println("学习诉求:"); System.out.println(learnerNeed); List pathVectors = embedTexts(httpClient, objectMapper, apiKey, apiUrl, modelName, pathPortraits); float[] needVector = embedText(httpClient, objectMapper, apiKey, apiUrl, modelName, learnerNeed); System.out.println("向量生成完成,向量维度: " + pathVectors.get(0).length); System.out.println(); System.out.println("--- Top 匹配结果 ---"); List results = new ArrayList<>(); for (int i = 0; i < learningPaths.size(); i++) { LearningPath learningPath = learningPaths.get(i); double similarity = cosineSimilarity(needVector, pathVectors.get(i)); results.add(new MatchResult(learningPath, similarity)); } results.stream() .sorted(Comparator.comparingDouble(MatchResult::similarity).reversed()) .limit(topK) .forEachOrdered(result -> printResult(result, buildPortrait(result.learningPath()))); } private static float[] embedText(HttpClient httpClient, ObjectMapper objectMapper, String apiKey, String apiUrl, String modelName, String text) throws Exception { return embedTexts(httpClient, objectMapper, apiKey, apiUrl, modelName, List.of(text)).get(0); } private static List embedTexts(HttpClient httpClient, ObjectMapper objectMapper, String apiKey, String apiUrl, String modelName, List texts) throws Exception { Map requestBody = new HashMap<>(); requestBody.put("model", modelName); requestBody.put("input", texts); requestBody.put("encoding_format", "float"); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl)) .timeout(Duration.ofSeconds(60)) .header("Authorization", "Bearer " + apiKey) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody))) .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw new IllegalStateException("Embedding API 调用失败,HTTP " + response.statusCode() + ",响应体: " + response.body()); } return parseEmbeddings(objectMapper, response.body()); } private static List parseEmbeddings(ObjectMapper objectMapper, String responseBody) throws IOException { JsonNode root = objectMapper.readTree(responseBody); JsonNode data = root.get("data"); if (data == null || !data.isArray() || data.isEmpty()) { throw new IllegalStateException("Embedding API 响应缺少 data 字段,响应体: " + responseBody); } List vectors = new ArrayList<>(); for (JsonNode item : data) { JsonNode embeddingNode = item.get("embedding"); if (embeddingNode == null || !embeddingNode.isArray()) { throw new IllegalStateException("Embedding API 响应缺少 embedding 字段,响应体: " + responseBody); } float[] vector = new float[embeddingNode.size()]; for (int i = 0; i < embeddingNode.size(); i++) { vector[i] = embeddingNode.get(i).floatValue(); } vectors.add(vector); } return vectors; } private static String buildPortrait(LearningPath learningPath) { return """ 路线编号:%s 路线名称:%s 适合人群:%s 核心关键词:%s 学完之后:%s """.formatted( learningPath.code(), learningPath.title(), learningPath.audience(), learningPath.keywords(), learningPath.outcome() ); } private static void printResult(MatchResult result, String portrait) { LearningPath path = result.learningPath(); System.out.printf("[%s] %s -> 相似度 %.4f%n", path.code(), path.title(), result.similarity()); System.out.println("画像内容:"); System.out.println(portrait); System.out.println(); } private static double cosineSimilarity(float[] left, float[] right) { if (left.length != right.length) { throw new IllegalArgumentException("向量维度不一致,无法计算相似度"); } double dotProduct = 0.0; double leftNorm = 0.0; double rightNorm = 0.0; for (int i = 0; i < left.length; i++) { dotProduct += left[i] * right[i]; leftNorm += left[i] * left[i]; rightNorm += right[i] * right[i]; } if (leftNorm == 0.0 || rightNorm == 0.0) { return 0.0; } return dotProduct / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm)); } private record LearningPath(String code, String title, String audience, String keywords, String outcome) { } private record MatchResult(LearningPath learningPath, double similarity) { } } ``` 运行后,你会得到类似下面这样的排序结果: ```yaml === Embedding 学习路线匹配 Demo === Embedding 模型: Qwen/Qwen3-Embedding-8B 候选路线数量: 6 学习诉求: 我已经会一点 Spring Boot,最近想做一个能读取文档、切分文本块、 计算相似度并回答问题的 Java AI 项目,最好还能顺手学会向量检索和 RAG 的完整链路。 向量生成完成,向量维度: 4096 --- Top 匹配结果 --- [P003] Spring AI 与 RAG 项目实战 -> 相似度 0.8073 画像内容: 路线编号:P003 路线名称:Spring AI 与 RAG 项目实战 适合人群:想用 Java 做 AI 应用,希望掌握文档切块、向量化、检索增强和答案生成流程的同学 核心关键词:Spring AI、Embedding、向量数据库、Chunk、Prompt、RAG、知识库问答 学完之后:能搭建一个完整的智能问答系统,并理解从文本到召回的核心链路 [P001] Java 高并发与分布式实战 -> 相似度 0.4896 画像内容: 路线编号:P001 路线名称:Java 高并发与分布式实战 适合人群:已经掌握 Spring Boot,希望补齐缓存、消息队列、限流熔断和分布式事务的同学 核心关键词:Spring Boot、Redis、Kafka、分布式锁、幂等设计、秒杀系统 学完之后:能独立设计高并发下单链路,并定位常见性能瓶颈 [P005] 云原生部署与 DevOps 路线 -> 相似度 0.3843 画像内容: 路线编号:P005 路线名称:云原生部署与 DevOps 路线 适合人群:负责服务上线和日常运维,希望掌握容器化、CI/CD 和服务可观测性的同学 核心关键词:Docker、Kubernetes、GitHub Actions、灰度发布、监控告警、日志采集 学完之后:能把 Java 服务稳定部署到容器平台,并建立基础运维体系 ``` 这个完整示例背后对应的就是 Embedding 检索的标准步骤: - 先把每条路线描述拼成一段“画像文本” - 调用 Embedding 模型,批量生成候选路线向量 - 再把用户的学习诉求也转成查询向量 - 对查询向量和候选向量逐个计算余弦相似度 - 最后按得分排序,拿到 Top-K 结果 这就是一个最小可用版的语义匹配流程。真实项目里,把“学习路线”换成“知识库 chunk”“FAQ 文档”“商品描述”“岗位画像”,整体链路都是一样的。 ## 离线向量模型 在线API调用有成本,而且涉及数据安全问题。有些场景需要用离线模型。 ### 为什么要用离线模型 - **数据安全**:敏感数据不能传到外部API - **成本控制**:大量调用API费用不低 - **延迟要求**:本地推理延迟更低 - **离线场景**:有些环境没有外网 > 离线模型推荐 > > 涉及敏感数据或有离线要求时,优先考虑BGE系列(智源研究院出品,中文效果优秀)。bge-large-zh效果最好但资源消耗大,bge-small-zh轻量但效果稍弱,bge-base-zh是平衡选择。可以用Ollama一键部署,和在线API的使用方式完全兼容。 ### 常用的离线模型 **BGE系列**(智源研究院) ``` bge-small-zh: 512维,轻量级 bge-base-zh: 768维,平衡型 bge-large-zh: 1024维,效果最好 ``` **M3E系列** ``` m3e-small: 512维 m3e-base: 768维 ``` ### **使用Ollama运行本地模型** Ollama可以方便地运行各种本地模型,包括Embedding模型。 安装Ollama后,拉取向量模型: ``` ollama pull nomic-embed-text ``` Spring AI配置: ```yaml spring: ai: ollama: base-url: http://localhost:11434 embedding: model: nomic-embed-text ``` 代码使用方式和在线API完全一样: ```java @Autowired private EmbeddingModel embeddingModel; public float[] embed(String text) { return embeddingModel.embed(text); } ``` ### **使用ONNX运行模型** 如果不想装Ollama,也可以直接用ONNX Runtime加载模型: ``` org.springframework.ai spring-ai-transformers 1.1.0 ``` ```java @Bean public EmbeddingModel embeddingModel() { return new TransformersEmbeddingModel() .setModelPath("path/to/bge-small-zh"); } ``` ## **向量模型选型建议** ### **考虑因素** | 因素 | 说明 | | --- | --- | | 效果 | 对目标语言(中文/英文)的语义理解能力 | | 维度 | 维度越高表达能力越强,但存储和计算成本也越高 | | 速度 | 推理速度,影响实时性 | | 部署方式 | API调用还是本地部署 | | 成本 | API调用费用或服务器成本 | ### 我的建议 **场景一:快速验证** 直接用阿里云的text-embedding-v2,效果好,接入简单。 **场景二:生产环境(无特殊安全要求)** 继续用云服务的API,稳定可靠,省心。 **场景三:生产环境(有数据安全要求)** 部署bge-large-zh或m3e-base,效果和云服务差不多,数据不出内网。 **场景四:资源有限** 用bge-small-zh或m3e-small,牺牲一点效果换取更低的资源消耗。 ### 不同模型效果对比 以中文语义相似度为例(数据来自网上的评测): | 模型 | MTEB中文平均分 | 维度 | | --- | --- | --- | | text-embedding-v2 (阿里) | 65.2 | 1536 | | bge-large-zh | 64.8 | 1024 | | bge-base-zh | 62.4 | 768 | | m3e-base | 61.5 | 768 | | bge-small-zh | 58.3 | 512 | 可以看到,云服务和开源大模型效果差距不大,可以根据实际情况选择。 ## 常见问题 ### Q1:向量维度越高越好吗? 不一定。 维度高,表达能力强,但: - 存储成本高 - 计算成本高 - 可能过拟合 一般768-1536维就够用了。 ### Q2:不同模型的向量能混用吗? 不能! 不同模型的向量空间是不兼容的。用模型A生成的向量,只能和模型A生成的向量比较。 如果中途换了向量模型,之前的向量都要重新生成。 > 切勿混用不同模型的向量 > > 这是一个高频踩坑点:**不同Embedding模型生成的向量绝对不能混用**。如果在项目中途更换了向量模型(如从text-embedding-ada-002换到bge-large-zh),必须对知识库中所有已有向量进行重新计算,否则检索结果会完全错乱。在项目初期就要选定模型,后期更换代价很高。 ### Q3:中文和英文混合的内容怎么办? 选择对多语言支持好的模型,比如: - bge-m3(支持100+语言) - text-embedding-v2(中英文都不错) ### Q4:向量模型需要微调吗? 大部分场景不需要。 除非你的领域非常垂直,通用模型效果不好,才考虑微调。 微调成本很高,需要准备大量的训练数据。 ## 小结 这篇文章讲了Embedding的核心知识: - **Embedding是什么**:把文字翻译成向量,让机器理解语义 - **向量相似度**:余弦相似度是主流选择 - **向量模型选型**:云服务方便,离线部署安全 - **实战应用**:商品推荐与学习诉求匹配两个例子 Embedding是RAG的核心魔法,理解了它,后面的检索、重排就都好理解了。 下一篇讲向量检索的核心算法——为什么能做到毫秒级检索。