ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从Embedding到RAG:文本向量化与语义检索实战

从Embedding到RAG:文本向量化与语义检索实战 大模型这一轮技术升级把 Embedding 从论文里的概念变成了日常开发组件。凡是碰过 RAG、Agent 记忆、知识库问答、语义搜索第一条技术链路基本都是“文本切块 — 向量化 — 相似度召回 — 上下文注入”。你检索效果好不好、回答准不准很大程度上取决于 Embedding 这一步有没有做对。这次我们把 Embedding 从头到尾拆一遍。先讲清楚它在 LLM 链路里的位置和底层原理再给完整可运行的代码实操包括本地模型向量化、余弦相似度计算、Top-K 语义检索、RAG 最小链路和批量向量化最后把接口 API 和资源占用一并说清楚。标题里提到就业前景所以文章后半部分也会拆解哪些岗位在考察 Embedding、面试怎么问、学习路线怎么排、最容易踩的坑在哪里。全文采用开源模型为主线优先选择 BAAI/bge 系列、text2vec 系列这类可免费下载的模型。这类模型硬件门槛低CPU 也能跑普通办公电脑足以完成学习和原型验证。下面直接进入正题。1. Embedding 是什么大模型应用里的向量“翻译器”把文字变成机器能计算的数字是 NLP 的老问题。早期做法是 one-hot 编码一个词一个 0/1 向量维度巨大而且没有任何语义关系。之后的 Word2Vec、GloVe 解决了“词的稠密向量表示”让语义相近的词在空间中距离更近。而今天的 LLM Embedding可以理解为把这个思路扩展到了“整句、整段、整篇”。一个 Embedding 模型的核心能力输入一段文本输出一个固定维度的数值向量。比如输入“大模型是什么”输出一个 768 维或者 1024 维的浮点向量。不同文本输出的向量长度一致文本越长模型内部先做截断或压缩最终照样汇总成一个固定向量。这个向量不是随机生成的而是模型在大规模语料上预训练学出来的语义坐标。值得强调的是大模型领域里“Embedding”有两条使用路径容易混淆模型内部的 Embedding LayerLLM 接收 token 时先把 token id 映射成隐藏向量这是模型本身结构的一部分。独立的文本 Embedding 模型把句子/文档编码成句向量再用于检索、去重、聚类这是 RAG 应用最常用的一类。本文下面的代码实操主要针对第二类。这也是招聘 JD 里最常出现“熟悉 Embedding 与 RAG”这句话的真实含义。应用方向解决什么问题典型场景语义检索用向量相似度找相关文本知识库问答、企业文档搜索RAG 召回先召回再交给 LLM 生成RAGFlow、客户问答机器人文档去重判断两段文本语义是否接近资讯聚合、数据清洗文本聚类把相似内容分组工单分类、舆情分析Agent 记忆历史会话向量化后按需回忆智能体长期记忆在一套 RAG 系统里Embedding 通常要做两次建库时把所有文档切成块并向量化查询时把用户问题向量化。然后用“问题向量 vs 文档块向量”的相似度排序取出 Top-K 块交给大模型。许多人在调 RAG 效果时只盯着 Prompt却忘了比对召回质量结果大模型写得多好都没用因为喂进去的上下文本来就是错的。2. Embedding 底层原理Token、上下文和语义向量2.1 Tokenizer先把文本拆成模型认识的符号任何文本进入模型前都不是“直接编码”而是先被切分成 token。中文按字或词切分英文按子词切分例如“unbelievable”可能被拆成“un”、“believ”、“able”。这一步由分词器完成输出一串 token id。模型对世界的理解全部建立在 token 序列之上。2.2 Embedding 层一张通过训练得到的查表大模型内部有一张维度为 vocab_size × hidden_size 的嵌入矩阵。每个 token id 去矩阵里查一行得到对应的向量。训练最初向量是随机的模型通过海量文本上的自监督任务不断更新这张表。与此同时位置编码把 token 顺序信息加进去让“我打你”和“你打我”在模型内部表现为不同的向量状态。这一步如果细究就是几乎所有 LLM 预训练前向传播的第一小段。2.3 从静态词向量到上下文向量Word2Vec 时代是静态词向量一个词只有一个固定向量一词多义解决不了。Transformer 结构改变了这一点token 向量经过多层自注意力计算后每个位置的隐藏态都包含了上下文信息的加权融合。同一句话里的“苹果手机”和“吃苹果”虽然底层词表查出来的初始向量一样但经过多轮 Attention 计算在高层表示中已经是两种不同的语义。文本 Embedding 模型就是把这段“高层表示”再压缩成一个句子级向量。常用做法有两种取特殊 token 的向量或对所有 token 向量做平均池化。开源生态里大量使用的 sentence-transformers 库默认走的就是“编码器输出 池化”路线。2.4 相似度计算余弦距离是默认方案有了向量如何判断两段文本相关最常用的是余弦相似度公式可以写成两个向量的内积除以模长的乘积。结果范围在 -1 到 1 之间越接近 1 表示语义越相近。实际工程里通常先对向量做 L2 归一化再直接计算内积效果等价于余弦相似度但检索阶段可以借力向量数据库的点积索引。需要提醒的是不同模型输出的向量空间不同不能拿 A 模型生成的向量去 B 模型的索引里搜索。同一套系统内从建库到查询必须锁定同一个模型版本。模型升级后旧索引要重新生成否则语义空间对不上召回结果会“看似正常但很不准”。3. 开发环境准备安装、选型和验证3.1 创建独立 Python 环境实操阶段建议先建一个独立虚拟环境避免依赖冲突。conda create -n embedding python3.10 -y conda activate embedding pip install sentence-transformers numpy scikit-learn faiss-cpu如果本机有 NVIDIA 显卡且想用 GPU 推理再装对应 CUDA 版本的 PyTorch。CPU 环境直接安装默认版 torch 即可文本向量模型的参数量通常在 1 亿上下CPU 做几十条文本的向量化完全能接受。先小规模验证别一上来就追求大模型。3.2 常见开源文本向量模型模型向量维度主要特点适用场景BAAI/bge-base-zh-v1.5768中文效果好体积小中文 RAG、知识库检索BAAI/bge-large-zh-v1.51024精度更高速度略慢对召回精度要求较高的场景BAAI/bge-m31024多语言、支持多种检索粒度多语言内容、较大规模知识库text2vec-base-chinese768社区常用部署简单中文语义相似度任务各模型的准确维度和最大输入长度要以对应模型仓库的说明为准。大多数 bge 中文模型的输入长度限制在 512 token 左右超长文本必须先切块再向量化。第一次运行模型时代码会自动下载权重并缓存你也可以将模型文件提前放到本地指定目录代码里改用本地路径加载。4. Embedding 代码实操向量生成与语义检索4.1 生成第一批文本向量下面这段代码完成最基本的向量化。from sentence_transformers import SentenceTransformer # 如果本地已缓存模型也可以写成本地绝对路径 model SentenceTransformer(BAAI/bge-base-zh-v1.5) docs [ 大模型是一种基于深度学习的自然语言处理模型, 今天上海天气晴朗适合出门, RAG 通过检索增强大模型的回答能力, 向量数据库适合存储和检索高维向量, ] embeddings model.encode(docs, normalize_embeddingsTrue) print(embeddings.shape) # 输出类似 (4, 768) print(embeddings[0][:5])运行成功后你会得到 4 行、每行 768 维的向量矩阵。normalize_embeddingsTrue 的作用是把向量归一化到单位长度后续计算内积就能直接当作余弦相似度。4.2 用余弦相似度判断语义距离接着写一个函数手动计算两两相似度。import numpy as np def cosine_similarity_matrix(a, b): a: (n, dim), b: (m, dim)返回 (n, m) 相似度矩阵 return np.dot(a, b.T) query 什么是 RAG query_vec model.encode([query], normalize_embeddingsTrue) sims cosine_similarity_matrix(query_vec, embeddings)[0] for idx in np.argsort(sims)[::-1]: print(f相似度 {sims[idx]:.4f} | {docs[idx]})预期结果里“RAG 通过检索增强大模型的回答能力”这段会排在最前面因为它在语义上和问题最接近。你不需要期待模型能“看懂”字面意思它判断的是两段文本在向量空间里的距离。4.3 bge 系模型的查询前缀问题bge 模型对“检索”场景有一个使用习惯查询句子建议加上固定指令前缀例如“为这个句子生成表示以用于检索相关文章”。文档入库句子不需要加前缀查询句子加前缀后检索精度通常更好。query 为这个句子生成表示以用于检索相关文章什么是 RAG query_vec model.encode([query], normalize_embeddingsTrue)这段细节在实际项目中经常被忽略。如果你把 bge 模型接入业务后召回效果明显偏差先检查是否漏了查询指令。换成其他模型时要以该模型的 model card 或官方示例为准不是所有模型都要求前缀。4.4 最小 Top-K 检索函数把上面的逻辑封装成一个函数后续做 RAG 可以直接复用。def search(query, model, doc_embeddings, doc_texts, top_k3, use_query_instructionTrue): if use_query_instruction and bge in str(model).lower(): query f为这个句子生成表示以用于检索相关文章{query} q_vec model.encode([query], normalize_embeddingsTrue) sims np.dot(q_vec, doc_embeddings.T)[0] top_idx np.argsort(sims)[::-1][:top_k] return [(doc_texts[i], float(sims[i])) for i in top_idx]5. 从 Embedding 到 RAG最小可运行文档问答链路5.1 RAG 的整体流程RAG 的核心思路大模型不直接凭空回答而是先从知识库里检索相关材料再把材料拼进上下文最后让模型基于材料生成。Embedding 在其中承担“召回”职责。原始文档 → 切块 → 每块向量化 → 存入向量索引 用户问题 → 向量化 → 与索引做相似度检索 → Top-K 上下文 Top-K 上下文 用户问题 → 大模型生成回答5.2 文档切块切块直接影响检索质量。切得太粗块里噪音多向量语义被稀释切得太细单块信息不完整多条上下文拼接后逻辑断裂。一个通用起步策略中文按字符窗口切分每块约 200 到 300 字块与块之间保留 20 到 50 字重叠。业务复杂时再考虑按段落、标题、语义边界切分。def chunk_text(text, chunk_size250, overlap30): chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) if end len(text): break start end - overlap return chunks raw_text 你的知识库原文可以是一段很长的文档内容…… chunks chunk_text(raw_text) print(len(chunks))切块后把每个块连同标题、来源、页码等元信息一起保存方便后续回溯。5.3 用 FAISS 建立本地向量索引FAISS 是 Meta 开源的高维向量检索库适合本地原型验证。建库时使用点积索引因为我们入库和查询时都已经做了归一化。import faiss import numpy as np chunk_embeddings model.encode(chunks, normalize_embeddingsTrue).astype(float32) dim chunk_embeddings.shape[1] index faiss.IndexFlatIP(dim) index.add(chunk_embeddings) # 查询 query_vec model.encode( [为这个句子生成表示以用于检索相关文章什么是 RAG], normalize_embeddingsTrue ).astype(float32) scores, ids index.search(query_vec, k3) for score, idx in zip(scores[0], ids[0]): print(fscore{score:.4f} | {chunks[idx]})注意 faiss 的 IndexFlatIP 返回的点积值等价于相似度。如果切块数量达到几十万甚至百万级再把 FAISS 换成 Milvus、Qdrant、Elasticsearch 这样的服务型向量数据库。向量索引占用的内存可以按公式估算向量条数 × 向量维度 × 4 字节。例如 100 万条 1024 维向量仅原始向量就接近 4GB这个数字在做容量规划时很有用。5.4 把召回结果拼给大模型并生成回答召回得到 Top-K 后把文本块拼进 Prompt再调用你本地部署的大模型服务。现在很多部署工具都提供 OpenAI 兼容接口调用方式差异不大。import requests context \n\n.join([f[{i1}] {chunks[idx]} for i, idx in enumerate(ids[0])]) prompt f请根据以下资料回答问题。 如果资料里没有相关内容请明确说明资料不足不要编造。 资料 {context} 问题什么是 RAG payload { model: qwen2.5, messages: [ {role: system, content: 你是一个严谨的技术助手。}, {role: user, content: prompt}, ], temperature: 0.2, } resp requests.post(http://127.0.0.1:11434/v1/chat/completions, jsonpayload, timeout120) print(resp.json()[choices][0][message][content])这里用的是本地大模型推理服务的 OpenAI 兼容接口示意实际地址和端口以你部署的服务为准。Retrieval 一旦召回质量过关LLM 生成内容会有明显改善因为知识来源被限制在给定资料里幻觉概率降低。需要特别说明的是RAG 技术可以用于企业知识库问答但请只对你有权处理的文档建立索引和问答。内部资料、个人隐私信息、受版权保护的内容不能未经授权就上传到不受控的云端 API也不能在公开 Demo 中展示。涉及敏感数据的场景优先走本地部署和私有化 Embedding 模型。6. 批量向量化与 API 接口调用6.1 批量向量化脚本实际业务中入库文档往往成千上万条。批量处理前先验证两件事单条文本是否超过模型最大输入长度超长先切块批量数是否会导致内存溢出先小批量测再放大。import json from tqdm import tqdm with open(knowledge_items.json, r, encodingutf-8) as f: items json.load(f) # 假设每条记录有 id, text batch_size 64 output_path knowledge_vectors.jsonl with open(output_path, w, encodingutf-8) as out: for i in tqdm(range(0, len(items), batch_size)): batch items[i:i batch_size] texts [item[text] for item in batch] vectors model.encode(texts, normalize_embeddingsTrue) for item, vec in zip(batch, vectors): record { id: item[id], text: item[text], embedding: vec.tolist(), model: bge-base-zh-v1.5, } out.write(json.dumps(record, ensure_asciiFalse) \n) print(done, output_path)向量以 JSON 行文件保存最方便调试但要留意文件体积768 维 float 转成 JSON 后一条记录可能超过 3KB百万条数据会很臃肿。大规模落地时优先用 numpy 的 .npy、HDF5 或直接写入向量数据库不建议把向量长期保存在文本 JSON 里。6.2 用 FastAPI 暴露 Embedding 服务把向量化能力封装成 HTTP 服务好处是业务端不需要安装 PyTorch 和模型通过接口即可完成文本向量化。以下是一个最小服务示例from fastapi import FastAPI from pydantic import BaseModel from sentence_transformers import SentenceTransformer app FastAPI(titleEmbedding Service) model SentenceTransformer(BAAI/bge-base-zh-v1.5, devicecpu) class EmbedRequest(BaseModel): texts: list[str] normalize: bool True class EmbedResponse(BaseModel): embeddings: list[list[float]] dimension: int app.post(/v1/embeddings, response_modelEmbedResponse) def create_embeddings(req: EmbedRequest): vectors model.encode(req.texts, normalize_embeddingsreq.normalize) return EmbedResponse(embeddingsvectors.tolist(), dimensionvectors.shape[1])启动命令uvicorn app:app --host 0.0.0.0 --port 8000然后测试接口curl -X POST http://127.0.0.1:8000/v1/embeddings \ -H Content-Type: application/json \ -d {texts: [大模型, Embedding 是什么]}返回示例{ embeddings: [ [0.0123, -0.0456, 0.0789], [0.0345, -0.0123, 0.0567] ], dimension: 768 }如果服务只在本机使用建议把监听地址改成 127.0.0.1避免暴露到局域网或公网。开放到团队内部使用时要加鉴权和请求限流防止接口被人刷爆或注入脏数据。6.3 接口调用与批量任务设计接口服务跑通后可以把它接到数据管道里上游是文档解析和切块中间调用 Embedding 服务下游写入向量库。批量任务建议增加“断点续跑”和“失败重试”机制例如每条记录写入结果文件后记录处理状态失败任务放入重试队列。不要等到 10 万条数据跑完才发现第 1000 条之后全部失败那样的任务没有任何可维护性。另外现在主流本地大模型服务框架基本都提供 Embedding 能力入口常见做法包括通过 API 服务加载 bge 等一系列开源模型。不同框架的接口路径和批次限制差异不小接入前先看对应框架的文档。嵌入模型在线服务化之后批量入库和单条查询的并发模型不一样入库阶段走批处理查询阶段走低延迟单条接口这是生产环境的基本划分。7. 模型选择、资源占用与性能观察7.1 先量化后做选型文本向量模型参数量在几千万到几亿之间相比动辄几十亿参数的生成式大模型资源需求小一个数量级。bge-base 这类模型在 CPU 上跑几百条文本一般也不成问题。选择模型先确认业务语言纯中文场景优先中文优化模型中英混合或多语言场景用 bge-m3 这类多语言模型。接着确认维度向量维度越高单条表达能力越强但存储和检索开销也线性上升。7.2 观察显存、内存与耗时的方法无论本地还是服务端运行建议做统一的性能观察启动服务后用nvidia-smi观察显存占用是否稳定。连续编码一批文本记录 CPU 使用率、内存峰值和单批次耗时。分别用 batch_size 为 8、32、64 测试观察吞吐量和延迟变化。跑完长文本后确认是否需要限制 max_seq_length超长文本会导致无效计算和内存上涨。文本向量推理通常要求在秒级完成但不同 CPU、不同批次的差异会很大。不要在别人的博客里抄一个“一秒钟处理多少条”的结论直接写进自己的方案必须用本机真实数据说话。7.3 降低资源占用的常见手段模型升级或大规模入库前可以按顺序做这几件优化用半精度加载减少模型显存控制文本长度在模型最大输入范围内批量编码而不是单条循环归档不用的历史索引并定时合并。还有一个常被忽略的点如果多次运行实验确认没有残留的 Python 服务进程占用显存或端口否则后续实验数据会非常不稳定。8. 常见问题与排查方法问题现象可能原因排查方式解决方案首次运行模型下载失败网络不稳定或权重未完整缓存查看模型缓存目录和报错日志提前下载模型后使用本地路径加载encode 输出的维度与表结构不匹配换了模型但没更新表结构打印 embedding.shape 与建表 schema统一模型版本重建索引或迁移字段检索结果和语义明显无关查询指令缺失或文本超长被截断对比 query 加前缀前后的 Top-K按模型要求加查询前缀文本先切块大批量 encode 时内存/显存过高batch_size 设置过大观察资源监控曲线调小 batch_size必要时用 CPU 分批处理服务端口启动失败端口被占用netstat -ano或lsof -i查看端口换端口或停掉旧进程API 调用返回超时服务端在加载模型或队列拥塞查看服务日志和任务队列服务预加载模型限制并发增加超时时间上线后向量库增长过快没有去重和版本管理统计每个文档块来源入库前去重旧模型向量统一过期重建同一文档多个块重复召回切块重叠过多且缺少去重查看 Top-K 结果来源字段按文档维度去重必要时增加 rerank 环节排查时习惯上从数据到索引再到服务逐层检查。如果 Top-K 结果里根本没有相关块优先怀疑切块和向量化环节如果相关块在里面但排得靠后优先怀疑相似度排序和查询指令如果召回正常但回答很差问题多半出在 Prompt 组装和生成参数上。9. Embedding 学习路线与就业前景9.1 哪些岗位会考察 Embedding从现在的市场招聘信息看接触 Embedding 的岗位大致分成几类LLM 应用工程师日常做 RAG、Agent、知识库问答JD 里高频出现“熟悉向量化、检索增强、Embedding 模型选型”。NLP 算法工程师涉及文本表征、语义相似度、表示学习需要理解模型结构和训练方式。搜索/推荐算法工程师向量召回是通用技术栈协同过滤、双塔模型等都会用到相似度检索。大模型部署与平台开发把推理服务、向量数据库、RAG 框架工程化常涉及 embedding API 对接和批量任务设计。AI 全栈开发技术面试喜欢问 RAG 链路而 RAG 链路第一站就是 Embedding。换句话说Embedding 已经不只是算法岗的知识点后端开发、测试开发接触 AI 应用时也经常被问到。它更像 AI 应用工程里的一项基本功。9.2 面试和实战会问什么面试里的问题通常很直接例如“为什么文本要向量化”“两个句子向量怎么算相似度”“Embedding 模型怎么选”“RAG 召回结果不准怎么调”“向量数据库和传统倒排索引有什么区别”“长文本怎么处理和存”。有些公司还会现场给一段代码要求写文本切块 向量化 Top-K 检索。所以只看原理不够代码实操必须能写出来。做项目时常见的进阶考察点包括如何在召回前做混合检索BM25 关键词 向量检索来互补召回的 k 值对准确率影响多大是否在向量检索后加一层 rerank 模型如何评估召回质量例如 recallk、MRR 这类指标。真正的工程经验往往体现在这些细节上。9.3 建议学习路径第一步掌握基础数学和编码理解向量、内积、范数、余弦相似度能用 numpy 手写相似度计算。第二步跑通一个文本向量模型安装 sentence-transformers用 bge 或 text2vec 生成句子向量观察不同文本的相似度差异。第三步弄懂 RAG 最小链路文本切块、向量化、FAISS 或向量数据库存储、召回、拼接 Prompt、调用 LLM。这一套跑通后你对 Embedding 在应用侧的理解就基本闭环。第四步做检索效果评估构造一批 query 和标准答案统计召回命中率对比不同切块参数、不同 Embedding 模型的效果差异。第五步再进入工程化把 Embedding 模型封装成 API写批量入库 Pipeline接 RAGFlow、向量数据库等成熟组件处理日志、去重、重试、权限和资源监控。这几步里最忌讳的是只收集资料不敲代码。Embedding 是一个动手就能立刻感知效果的方向测试一条文本的相似度只需要几十行代码你不缺一个复杂环境缺的是先把最小链路跑通的耐心。10. 总结与下一步整篇文章围绕一条主线Embedding 是把文本变成向量、把向量变成检索能力的关键一环。底层原理核心包括 tokenizer、嵌入层、上下文建模和相似度计算应用侧核心包括向量生成、Top-K 检索、RAG 链路、批量任务和 API 服务。硬件门槛很低先从 bge-base 这类中文模型开始跑CPU 环境也可以完成学习验证。如果你现在正要搭建一个 RAG 应用第一优先级不是调 Prompt而是先验证召回质量。建议你先做一个最小实验准备 10 到 20 段文档切块后建一个小索引然后用 5 到 10 个真实问题测试 Top-3 是否命中正确答案。这块跑通了再考虑换更好的 Embedding 模型、加深层 rerank 或上向量数据库。下一步值得延伸的方向有三个一是加 rerank 模块对向量召回结果做精排通常能显著提升最终准确率二是做混合检索把关键词匹配和向量检索结合避免向量模型对专有名词、编号类文本召回偏弱的问题三是尝试在垂直领域微调 Embedding 模型用少量业务标注数据做对比学习让向量空间更贴合你自己的场景。Embedding 的知识密度不大但应用链条很长。先把代码跑起来再用数据说话会比只背概念走得快得多。
返回列表