一句话结论 :文字能被向量表示,不是因为机器"理解"了语言,而是因为在一个设计好的预测任务 下,"上下文相似 → 向量相似"这一约束被强行写进了模型的训练目标里。向量只是这个约束的副产品。
技术栈 :Python / gensim / sentence-transformers / faiss / PyTorch / numpy
适用读者 :有编程基础、了解神经网络基本概念,但想弄清"词向量为什么有效"的开发者
读完你能拿到 :分布假说的准确含义、Word2Vec 的数学动机、语义关系为何变成几何关系、静态与动态向量的本质区别、如何评估向量好坏 、主流 embedding 模型选型对比 、从内存检索到 faiss 索引的两版 demo 、RAG 中向量的完整用法 、两张配图的绘图提示词
本文不追求术语完备,重点是把"为什么能 work"讲清楚。公式只保留必要的几个,且都配了直觉解释。
一、先澄清两个容易混的概念
标题里容易望文生义的两个词,先定义清楚:
- 向量模型 / 向量表示 :把离散的符号(词、句子、文档)映射为连续的低维实数向量。
- 语言模型(LM):给定上下文,预测下一个 token 的概率分布(GPT 系)或填充被遮蔽的 token(BERT 系)。
它们的关系不是"谁表示谁",而是配合:
- 向量模型负责 "把语言变成数字"(表示);
- 语言模型负责 "在数字上做计算"(预测/生成)。
所以本文的问题准确表述是:为什么语言可以被表示成向量,并且这种表示有意义?
二、为什么不能直接用 one-hot
机器只能算数。最直接的编码是 one-hot:
king=1,0,0,...,0,queen=0,1,0,...,0 \text{king} = 1,0,0,\\dots,0, \quad \text{queen} = 0,1,0,\\dots,0 king=1,0,0,...,0,queen=0,1,0,...,0
问题有两个:
- 维度爆炸:词表有多大,向量就有多长。10 万词表 → 每个词 10 万维,且极其稀疏。
- 无法表达相似性 :任意两个不同词的 one-hot 内积恒为 0,余弦相似度恒为 0。"猫"和"狗"的距离,和"猫"和"量子力学"的距离完全一样。
第 2 点才是致命的。语言的价值恰恰在于词与词语义上的远近关系 ,而 one-hot 把这种结构完全抹掉了。
所以我们需要一种编码 :维度可接受(比如 100~1000 维)、稠密、并且让语义相近的词在空间里离得近。
三、理论地基:分布假说
整个向量表示体系,建立在一句 1957 年的话上(Firth):
You shall know a word by the company it keeps.
你通过一个词周围的词来认识它。
这就是分布假说(Distributional Hypothesis):
上下文相似的两个词,语义大概率相近。
例子:
- "我喝了一杯咖啡"
- "我喝了一杯拿铁"
"咖啡"和"拿铁"出现在极其相似的上下文里("我喝了一杯..."),所以它们语义相近。反过来,"咖啡"周围很少出现"微分方程",所以它和"微积分"应该离得远。
分布假说把"语义"这个抽象的东西,转成了一个可操作的问题:
只要我能统计出哪些词出现在相似的上下文里,我就能让它们的向量相近。
这句话是后面所有方法的总纲。
四、Word2Vec:用一个"假任务"逼出真向量
4.1 核心 trick:预测任务只是手段
Word2Vec 提出时最反直觉的一点是:
我要的是词向量 ,但我训练的任务却是预测上下文。
两条经典路线:
| 模型 | 输入 | 预测目标 |
|---|---|---|
| CBOW | 上下文词 | 中心词 |
| Skip-gram | 中心词 | 上下文词 |
以 Skip-gram 为例,目标是最大化:
max∑t∑j∈windowlogP(wt+j∣wt) \max \sum_{t} \sum_{j \in \text{window}} \log P(w_{t+j} \mid w_t) maxt∑j∈window∑logP(wt+j∣wt)
直觉解释 :让每个词,都能根据它自己,把它周围窗口内出现的词预测得越准越好。
其中 P(wt+j∣wt)P(w_{t+j} \mid w_t)P(wt+j∣wt) 通常用 softmax:
P(o∣c)=exp(vo⊤uc)∑w∈Vexp(vw⊤uc) P(o \mid c) = \frac{\exp(\mathbf{v}_o^\top \mathbf{u}c)}{\sum{w \in V} \exp(\mathbf{v}_w^\top \mathbf{u}_c)} P(o∣c)=∑w∈Vexp(vw⊤uc)exp(vo⊤uc)
直觉解释 :中心词 ccc 和候选词 ooo 的向量点积越大,说明它们越"搭",预测概率就越高。分母是对全词表归一化。
4.2 关键洞察:为什么这能产生有意义的向量?
直觉推导:
- 模型想把"咖啡"的上下文预测得准;
- 而"咖啡"和"拿铁"的上下文高度重叠;
- 于是模型必须让它们在做预测时表现相似;
- 要让两个词"表现相似",最省事的办法是------让它们的向量本身就相似。
结论 :预测目标是一个"约束"。它不直接优化"语义相近",但它间接强迫了"上下文相近 → 向量相近"。这正是分布假说的数学落地。
一句话:向量不是被"设计"出来的,而是被"逼"出来的。
4.3 训练效率:负采样
上面那个 softmax 要对全词表求和,10 万词表根本算不动。实际用的是 Negative Sampling:
把"预测正确上下文词"转成二分类------正样本是真实上下文词,负样本是从噪声分布里采样的若干词。
L=logσ(vo⊤uc)+∑k=1KEwk∼Pnlogσ(−vwk⊤uc) \mathcal{L} = \log \sigma(\mathbf{v}o^\top \mathbf{u}c) + \sum{k=1}^{K} \mathbb{E}{w_k \sim P_n} \left \\log \\sigma(-\\mathbf{v}_{w_k}\^\\top \\mathbf{u}_c) \\right L=logσ(vo⊤uc)+k=1∑KEwk∼Pnlogσ(−vwk⊤uc)
直觉解释 :不用对 10 万词算概率了,只要让真实搭配 的点积变大、随机抽来的词 点积变小即可。把百万级 softmax 变成几十次二分类,这是让 Word2Vec 能单机训练的关键工程技巧。
五、为什么语义关系变成了几何关系
这是很多人第一次接触词向量时最震撼的地方:
vking−vman+vwoman≈vqueen \mathbf{v}{\text{king}} - \mathbf{v}{\text{man}} + \mathbf{v}{\text{woman}} \approx \mathbf{v}{\text{queen}} vking−vman+vwoman≈vqueen
为什么加减法 能对应语义推理?
核心解释 :训练目标只约束了"共现相似 → 向量相似",但当关系在语料中稳定复现 时,向量空间会自发地把这种关系编码成方向。
具体来说:
- "man → woman" 这个语义方向(性别变换),在很多对词里重复出现(king→queen, actor→actress, brother→sister...);
- 为了让这些相似关系同时被满足 ,模型倾向于让"性别方向"在所有词对中大致是同一个方向向量;
- 于是"king 减去男性、加上女性"就落到了"queen"附近。
关键理解 :这不是模型"懂"了性别,而是数据里的规律在低维空间中被压缩成了一种线性结构。
语义被编码成了方向与距离。这就是"向量能表示语言"的核心机制。
六、静态向量的局限:一词一向量
Word2Vec 有个明显缺陷:每个词只有一个向量。
- "苹果很好吃" vs "苹果发布了新手机"
- "银行"(river bank)vs "银行"(financial bank)
多义词被强行压成同一个点,语义空间里必然产生冲突。
根本原因 :Word2Vec 的向量是训练完成后固定查表的 ,它看不到当前句子,也就无法根据上下文调整。
要解决这个问题,必须让向量随上下文变化------这就引出了下一代的表示方式。
七、从静态到动态:语言模型内部涌现的向量
7.1 核心转变
ELMo / BERT / GPT 提供的是上下文相关的表示:
同一个词,在不同句子里得到不同的向量。
"苹果"在"水果"语境下和"手机"语境下,向量不同------因为它的向量是整个上下文前向计算的结果,而不是查表得到的。
7.2 机制:为什么 LM 内部会涌现语义向量
以 BERT 为例,训练任务是 Masked Language Model (MLM):随机遮住一些词,让模型预测。
LMLM=−∑i∈maskedlogP(wi∣context) \mathcal{L}{\text{MLM}} = -\sum{i \in \text{masked}} \log P(w_i \mid \text{context}) LMLM=−i∈masked∑logP(wi∣context)
直觉解释 :把句子挖几个空,让模型填。要填对空,模型必须先"看懂"整句上下文------而这个"看懂"的内部状态,就是上下文向量。
这里发生了和 Word2Vec 一样的事,但更彻底:
- Word2Vec:用"局部窗口共现"逼出静态向量;
- BERT/GPT:用"整句理解"逼出动态向量。
向量不再是人设计的特征,而是模型理解语言过程中"顺带"产生的中间产物。
7.3 一个重要的观点
到这里可以说一句关键的:
语言模型不是"直接理解文字",而是先把文字映射进一个高维的"意义空间",再在这个空间里做计算。
- 表示(embedding)是离散 → 连续的桥梁;
- 语言模型的所有"理解"和"生成",都发生在向量空间里;
- 今天所有的语义检索、RAG、向量数据库,本质都是在复用这一层表示。
八、如何评估向量表示的好坏
训练完了,怎么知道好不好?评估分两类,内外分工不同。
8.1 内在评估(Intrinsic):只看向量本身
不接下游任务,直接测向量空间的语义性质。快、便宜、便于调参,但和真实效果未必强相关。
| 方法 | 做法 | 指标 |
|---|---|---|
| 相似度任务 | 给词对打分(如"咖啡-拿铁"相似度),与人标注比较 | Spearman 相关系数 |
| 类比任务 | a - b + c = ?,看最近邻是否是目标词 |
准确率(Accuracy) |
| 聚类 / 可视化 | 语义相近的词是否聚在一起 | 目测 / 轮廓系数 |
类比准确率是最常被引用的指标,用一句话概括就是:
对每一道题
a : b :: c : ?,算出向量 va−vb+vc\mathbf{v}_a - \mathbf{v}_b + \mathbf{v}_cva−vb+vc,看离它最近的词(且不是 a/b/c 本身)是不是正确答案 d,统计做对的比例。
典型类比集合:man:woman :: king:? → queen。
缺点 :类比结果对语料、超参、词频极其敏感,容易"过拟合"到特定数据集,不能单独作为落地依据。
8.2 外在评估(Extrinsic):接下游任务看效果
把向量当作特征,喂给真实任务,用任务指标衡量。
| 下游任务 | 常用指标 |
|---|---|
| 文本分类 | F1 / Accuracy |
| 命名实体识别 | F1(实体级) |
| 语义相似度回归 | Spearman / Pearson 相关 |
| 检索 / RAG | Recall@K、MRR、nDCG |
| 问答 | EM / F1 |
结论:内在评估用来快速迭代,外在评估才是最终标准。
一句话:内在评估回答"向量空间合不合理",外在评估回答"业务上到底管不管用"。
8.3 一个实用的经验顺序
- 训练多个版本(不同维度 / 窗口 / 算法);
- 先用内在评估快速筛掉明显差的;
- 挑出前几名,接入真实下游任务复测;
- 以外在评估结果做最终选型。
8.4 常见陷阱
- 只看类比准确率:容易选到"数据上漂亮、任务上拉胯"的模型;
- 忽略领域差异:通用语料训练的词向量,直接用到医疗/法律场景往往大幅掉点;
- 忽略 OOV:未见词无法表示,需要子词(subword)机制或字符级兜底;
- 忽略相似度指标的语义:反义词共现强,余弦相似度可能也很高(如"热/冷"),要按任务选指标。
九、主流 Embedding 模型选型对比
实际项目里,你大概率不会自己训 Word2Vec,而是直接用现成的 Embedding 模型。下面是选型全景。
9.1 三代模型对比
| 代际 | 代表 | 表示粒度 | 上下文相关 | 典型场景 |
|---|---|---|---|---|
| 一代:静态词向量 | Word2Vec / GloVe / FastText | 词 | ❌ | 老项目、算力极度受限、可解释性要求高 |
| 二代:上下文表示 | ELMo / BERT | 词/句 | ✅ | NLU 任务(分类、NER)的中间层 |
| 三代:专用句子向量 | Sentence-BERT / BGE / GTE / E5 | 句/段 | ✅ | 检索、聚类、RAG、语义匹配 |
9.2 静态词向量
| 模型 | 特点 | 适合 |
|---|---|---|
| Word2Vec | 最经典、训练快 | 教学、小语料、基线 |
| GloVe | 基于全局共现矩阵 | 有大规模语料、追求稳定 |
| FastText | 引入子词,解决 OOV | 有大量罕见词、形态丰富语言(如中文、德语) |
选型建议 :中文场景、词表外问题严重,优先考虑 FastText 或直接上三代模型。
9.3 句子向量模型(重点)
| 模型 | 维度 | 中文支持 | 特点 |
|---|---|---|---|
| Sentence-BERT | 384~768 | 一般 | 句子向量的开创者,结构清晰 |
| BGE (BAAI) | 512~1024 | ✅ 很好 | 中英文检索强,社区活跃,中文首选之一 |
| GTE | 768~1024 | ✅ 很好 | 阿里出品,多语言、检索效果好 |
| E5 | 768~1024 | ✅ | 微软出品,需加 query:/passage: 前缀 |
| M3E | 768 | ✅ 很好 | 中文优化,轻量 |
| OpenAI text-embedding-3 | 可变 | ✅ | 效果稳、免运维,但收费 + 数据出境 |
| Qwen-Embedding | 1024+ | ✅ 很好 | 国产、可私有化、与 Qwen 生态配套 |
9.4 一个实用的选型决策树
数据能不能出内网?
├─ 不能(政务/金融/医疗)
│ └─ 选开源可私有化:BGE/GTE/M3E/Qwen-Embedding
└─ 能
├─ 预算充足、追求省心 → OpenAI text-embedding-3
└─ 预算有限/要求可控 → 开源模型自部署(BGE 系列)
任务是什么?
├─ 检索/RAG → 选专门优化检索的(BGE、E5、GTE)
├─ 聚类/去重 → 选通用语义模型(Sentence-BERT、M3E)
└─ 只是教学/基线 → Word2Vec/FastText 就够
9.5 选型时的几个坑
| 坑 | 说明 |
|---|---|
| 维度越大越好 | 错。检索场景下维度越高,索引越大、越慢,收益递减。 |
| 模型越大越好 | 错。小模型(如 BGE-small)在很多任务上已接近大模型,且快得多。 |
| 忽略指令前缀 | E5 等模型必须加 query: / passage: 前缀,否则效果大打折扣。 |
| query 和 doc 用同一处理 | 检索场景通常需非对称处理(查询短、文档长),要按模型文档来。 |
| 不测就用 | 通用基准(MTEB)排名 ≠ 你的领域效果,必须用自己数据测。 |
选型金律 :别信榜单,信你自己的评测集。 用 200~500 条真实样本建一个小评测集,比看任何排行榜都准。
十、动手(一):内存版语义检索
先用最朴素的方式------一次矩阵乘法------把语义检索跑通。
python
# pip install sentence-transformers numpy
from sentence_transformers import SentenceTransformer
import numpy as np
# ---------- 1. 加载一个中文友好的 embedding 模型 ----------
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
# ---------- 2. 准备一个小"知识库" ----------
docs = [
"如何申请办理居住证",
"身份证丢失后如何补办",
"公积金提取需要哪些材料",
"社保卡怎么激活和使用",
"户口迁移的办理流程",
"车辆年检需要准备什么",
]
# ---------- 3. 把文档编码成向量(离线做一次即可) ----------
doc_embeddings = model.encode(docs, normalize_embeddings=True)
# ---------- 4. 用户提问 ----------
query = "我的身份证没了,怎么重新办?"
query_embedding = model.encode(
"为这个句子生成表示以用于检索相关文章:" + query,
normalize_embeddings=True,
)
# ---------- 5. 余弦相似度(已归一化,直接点积) ----------
scores = doc_embeddings @ query_embedding
# ---------- 6. 排序并输出 Top-K ----------
top_k = 3
ranked = np.argsort(-scores)[:top_k]
print(f"查询:{query}\n")
for i, idx in enumerate(ranked, 1):
print(f"Top{i} score={scores[idx]:.4f} | {docs[idx]}")
预期输出(示意):
查询:我的身份证没了,怎么重新办?
Top1 score=0.xxxx | 身份证丢失后如何补办
Top2 score=0.xxxx | 户口迁移的办理流程
Top3 score=0.xxxx | 如何申请办理居住证
这段代码揭示了三件事:
- "身份证没了"能匹配到"身份证丢失" ------向量捕捉的是语义,不是关键词字面;
normalize_embeddings=True之后,点积就等于余弦相似度;- 检索的核心就是一次矩阵乘法 + 一次排序。
但内存版有硬伤 :所有向量都堆在内存里,每次查询都要和全部 文档算一遍。文档上万后,延迟和内存都会爆。这就引出了下一节的 faiss。
十一、动手(二):faiss 版语义检索(工业级索引)
faiss 是 Facebook 开源的向量相似度检索库 ,核心价值是:用索引结构加速"最近邻搜索",避免全量比对。
11.1 思路转变:从"算全部"到"查索引"
| 内存版 | faiss 版 | |
|---|---|---|
| 检索方式 | 全量点积 | 近似最近邻(ANN) |
| 复杂度 | O(N) | 近似 O(log N) 或更优 |
| 适用规模 | 千级以内 | 百万级、千万级 |
| 召回率 | 100% | 可调(精度换速度) |
11.2 代码:从内存矩阵升级到 faiss 索引
python
# pip install sentence-transformers faiss-cpu numpy
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
# ---------- 1. 模型与语料 ----------
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
docs = [
"如何申请办理居住证",
"身份证丢失后如何补办",
"公积金提取需要哪些材料",
"社保卡怎么激活和使用",
"户口迁移的办理流程",
"车辆年检需要准备什么",
]
# 示意:实际场景这里是几千上万条
doc_embeddings = model.encode(docs, normalize_embeddings=True)
doc_embeddings = np.asarray(doc_embeddings, dtype="float32")
dim = doc_embeddings.shape[1]
# ---------- 2. 构建 faiss 索引 ----------
# IndexFlatIP = 内积索引;向量已归一化,内积等价于余弦相似度
index = faiss.IndexFlatIP(dim)
# 如需百万级、可接受一定精度损失,可改用 IVF 索引:
# quantizer = faiss.IndexFlatIP(dim)
# index = faiss.IndexIVFFlat(quantizer, dim, nlist=100)
# index.train(doc_embeddings)
# index.nprobe = 10 # 查询时探测的桶数,越大越准越慢
index.add(doc_embeddings)
print(f"索引中向量数:{index.ntotal}")
# ---------- 3. 查询 ----------
query = "我的身份证没了,怎么重新办?"
query_embedding = model.encode(
"为这个句子生成表示以用于检索相关文章:" + query,
normalize_embeddings=True,
)
query_embedding = np.asarray([query_embedding], dtype="float32")
# ---------- 4. 检索 Top-K ----------
top_k = 3
scores, indices = index.search(query_embedding, top_k)
print(f"\n查询:{query}\n")
for rank, (score, idx) in enumerate(zip(scores[0], indices[0]), 1):
print(f"Top{rank} score={score:.4f} | {docs[idx]}")
输出与内存版一致,但检索路径完全不同 :faiss 内部用索引结构跳过了大量明显不相关的向量,不需要逐条比对。
11.3 faiss 索引怎么选
| 索引类型 | 速度 | 精度 | 适用规模 | 说明 |
|---|---|---|---|---|
IndexFlatIP / IndexFlatL2 |
慢 | 100% | 万级以内 | 暴力精确检索,最准 |
IndexIVFFlat |
快 | 可调 | 十万~千万 | 分桶检索,nprobe 控制精度 |
IndexHNSWFlat |
很快 | 高 | 十万~千万 | 图索引,查询快、建索引慢 |
IndexIVFPQ |
极快 | 中等 | 亿级 | 向量压缩,省内存、损精度 |
11.4 从 demo 到生产还差什么
- 向量持久化 :
faiss.write_index(index, "docs.index"),避免每次重建; - ID 映射 :faiss 只返回下标,需要自己维护
下标 → 文档 ID的映射(或用IndexIDMap); - 增量更新:faiss 索引不支持随意增删,需要重建或分层管理;
- 分片 + 合并:超大规模要按业务分片检索,再归并结果;
- 混合检索 :工业界常用 向量检索 + BM25 关键词检索,再做重排(Reranker),效果远好于单一向量检索。
一句话 :内存版是"全量比对",faiss 是"索引查找"。从 demo 到生产,性能优化、更新策略、混合检索才是真正的工作量。
十二、RAG 里的向量是怎么用的
到这里,前面的所有铺垫终于汇成一个当下最热的落地场景 :RAG(Retrieval-Augmented Generation,检索增强生成)。
12.1 RAG 要解决的核心问题
大模型有两个硬伤:
- 知识过时 / 缺失:训练数据截止后的事它不知道;
- 幻觉:不知道也会硬编。
RAG 的思路很直接:回答前,先去"知识库"里找出相关内容,塞进提示词,让模型基于检索到的事实来答。
向量,就是"找出相关内容"这一步的核心技术。
12.2 RAG 的完整链路
┌─────────── 离线建库(一次) ───────────┐
│ 文档 → 切分(chunk) → Embedding → 向量库 │
└───────────────────────────────────────┘
│
用户提问 ──► 提问向量化 ──► 向量检索 Top-K ──► 拼进提示词 ──► LLM 生成答案
│
(可选:重排 rerank、混合检索)
拆成五步:
| 步骤 | 做什么 | 用到的技术 |
|---|---|---|
| ① 切分 | 把长文档切成小段(chunk) | 按长度/语义/标题切 |
| ② 编码 | 每段编码成向量,存入向量库 | Embedding 模型 + faiss/Chroma/Milvus |
| ③ 检索 | 用户提问同样编码,找 Top-K 相似段 | 第 9~11 节讲的全部内容 |
| ④ 重排(可选) | 对候选段做精细排序 | Cross-Encoder / Reranker |
| ⑤ 生成 | 把检索到的内容 + 问题喂给 LLM | 大模型 |
12.3 最小可用的 RAG 主流程(示意)
python
# pip install sentence-transformers faiss-cpu numpy openai
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
# ---------- 0. 准备 LLM(以 OpenAI 兼容接口为例,可替换为任意本地模型) ----------
# 若使用本地模型,可换成 ollama / vllm / transformers 的调用方式
from openai import OpenAI
llm_client = OpenAI(base_url="https://api.openai.com/v1", api_key="sk-xxx") # 换成你的 key
# ---------- 1. 文档切分(chunking) ----------
# 真实场景:读文件 → 按段落/标题/固定长度切分,这里用固定长度 + 重叠演示
raw_docs = [
"居住证是中国一些城市针对非当地户籍人员推出的证件,持证人可享受当地部分公共服务。"
"申请居住证通常需要提供身份证、居住证明(如租房合同或房产证)、近期照片等材料。"
"办理流程一般包括:提交材料、窗口审核、制证发证,全程约 15 个工作日。",
"身份证丢失后应立即挂失,可通过线上平台或派出所办理。"
"补办需要本人携带户口簿到户籍所在地派出所,填写《居民身份证申领登记表》。"
"补办期间可申领临时身份证,有效期 3 个月,与正式身份证具有同等法律效力。",
"公积金提取需要满足特定条件,如购房、租房、离职、退休等。"
"提取材料一般包括身份证、银行卡、相关证明(购房合同/租房发票/离职证明)。"
"可通过当地公积金管理中心 APP 或柜台办理,审核通过后 3~5 个工作日到账。",
]
def chunk_text(text, chunk_size=80, overlap=20):
"""按字符长度切分,带重叠,避免切断语义。"""
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
chunks = []
for i, doc in enumerate(raw_docs):
for j, chunk in enumerate(chunk_text(doc)):
chunks.append({"doc_id": i, "chunk_id": j, "text": chunk})
print(f"切分后共 {len(chunks)} 个 chunk")
# ---------- 2. 向量化 + 建索引 ----------
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
chunk_texts = [c["text"] for c in chunks]
embeddings = model.encode(chunk_texts, normalize_embeddings=True)
embeddings = np.asarray(embeddings, dtype="float32")
index = faiss.IndexFlatIP(embeddings.shape[1])
index.add(embeddings)
print(f"向量库中 chunk 数:{index.ntotal}")
# ---------- 3. 检索 ----------
def retrieve(query, top_k=3):
q_vec = model.encode(
"为这个句子生成表示以用于检索相关文章:" + query,
normalize_embeddings=True,
)
q_vec = np.asarray([q_vec], dtype="float32")
scores, idxs = index.search(q_vec, top_k)
return [(chunks[i], float(scores[0][rank])) for rank, i in enumerate(idxs[0])]
# ---------- 4. 拼接提示词 ----------
def build_prompt(query, retrieved):
context = "\n".join(f"- {c['text']}" for c, _ in retrieved)
return (
"请仅根据下列资料回答问题,资料中没有的信息不要编造。\n\n"
f"资料:\n{context}\n\n"
f"问题:{query}"
)
# ---------- 5. 调用 LLM 生成答案 ----------
def llm_generate(prompt):
resp = llm_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.2,
)
return resp.choices[0].message.content
# ---------- 6. 完整流程 ----------
def rag_answer(query, top_k=3):
retrieved = retrieve(query, top_k)
prompt = build_prompt(query, retrieved)
answer = llm_generate(prompt)
return answer, retrieved
# ---------- 运行示例 ----------
query = "身份证丢了怎么补办?需要多久?"
answer, retrieved = rag_answer(query)
print(f"\n查询:{query}\n")
print("检索到的资料:")
for c, score in retrieved:
print(f" score={score:.4f} | {c['text'][:30]}...")
print(f"\nLLM 回答:\n{answer}")
# 预期输出(示意):
# 查询:身份证丢了怎么补办?需要多久?
#
# 检索到的资料:
# score=0.xxxx | 身份证丢失后应立即挂失,可通过线上平台...
# score=0.xxxx | 居住证是中国一些城市针对非当地户籍人员...
# score=0.xxxx | 公积金提取需要满足特定条件,如购房、租房...
#
# LLM 回答:
# 身份证丢失后应立即挂失,可通过线上平台或派出所办理。
# 补办需要本人携带户口簿到户籍所在地派出所,填写《居民身份证申领登记表》。
# 补办期间可申领临时身份证,有效期 3 个月。全程约需 15 个工作日。
12.4 RAG 里最容易踩的坑
| 坑 | 说明 | 对策 |
|---|---|---|
| 切分太粗/太细 | 太粗召回不相关,太细丢上下文 | 按语义/标题切,或重叠切分 |
| 只做向量检索 | 专有名词、编号类查询召回差 | 加 BM25 混合检索 |
| 不做重排 | Top-K 里有噪声 | 加 Reranker 精排 |
| embedding 模型不对口 | 通用模型在垂直领域掉点 | 换领域模型或微调 |
| 提示词没约束 | 模型仍会编 | 明确"仅依据资料回答" |
| Top-K 拍脑袋定 | 太大引入噪声,太小漏信息 | 用评测集调 |
12.5 回到本文的主线
把 RAG 拆开看,你会发现它本质上是本文所有内容的工程化组合:
- 分布假说 → 决定了 embedding 能不能抓到语义;
- 动态向量(第七节)→ 决定了同一句话在不同语境下能被正确理解;
- 评估(第八节)→ 决定你选的检索方案到底靠不靠谱;
- 选型(第九节)→ 决定你用哪个 embedding 模型;
- faiss 索引(第十一节)→ 决定这套系统能不能上规模。
一句话 :RAG 不神秘,它只是把"文字 → 向量 → 相似度检索 → 喂给语言模型"这条链路工业化 了。理解了向量,就理解了 RAG 的地基。
十三、把整个逻辑串起来
文字(离散符号)
│ ① 为什么不能 one-hot:无法表达相似性
▼
Distributional Hypothesis:上下文相似 → 语义相近(理论地基)
│ ② 怎么落地
▼
Word2Vec:用"预测上下文"这个假任务,
逼出"语义相近 → 向量相近"的约束
│ ③ 副产品
▼
语义关系变成几何关系:king - man + woman ≈ queen
│ ④ 局限
▼
静态向量无法区分多义词
│ ⑤ 升级
▼
BERT/GPT:用"整句理解"逼出上下文相关的动态向量
│ ⑥ 评估
▼
内在评估筛、外在评估定,最终以下游任务为准
│ ⑦ 选型
▼
按"能否出内网 + 任务类型 + 预算"三维决策
│ ⑧ 落地(检索)
▼
内存点积 → faiss 索引 → 混合检索 + 重排
│ ⑨ 应用(RAG)
▼
切分 → 编码 → 检索 → 重排 → 生成
十四、写给开发者的几句话
如果你要动手或选型,记住这几个判断:
- 向量不是"理解",是"压缩":它把共现规律压进低维空间,别过度拟人化。
- 分布假说决定了向量的上限:语料里没体现的关系,向量里不会有。
- 静态 vs 动态,是选型第一分水岭:同词多义场景,静态向量基本不够用。
- 维度不是越高越好:100~300 维(词)/ 512~1024 维(句)常够用,过高只增加成本。
- 相似度指标用余弦,不用欧氏:方向比长度更重要。
- 评估要内外结合:别拿类比准确率当最终交付标准,下游任务说了算。
- 选型别信榜单,信自己的评测集:MTEB 排名 ≠ 你的领域效果。
- 规模上来了必须用向量索引:内存点积只适合 demo,生产用 faiss/Milvus。
- 纯向量检索不够,要混合检索 + 重排:这是 RAG 效果的关键。
- 先把最小 RAG 跑通,再谈优化:链路通了,优化才有方向。
十五、小结
回到最初的问题------为什么一串文字能变成一个向量?
答案不在"机器学会了语言",而在:
- 分布假说提供了理论依据:上下文相似则语义相近;
- 预测任务提供了实现手段:用"假任务"逼出"真约束";
- 低维空间提供了表达载体:语义关系被压缩成方向与距离;
- 语言模型把这一切推向极致:向量随上下文动态生成;
- 评估体系让"表示的好坏"可被度量;
- 选型方法让理论能落到项目里;
- 向量索引让语义检索能上规模;
- RAG 让这一切变成一个能真正解决问题的系统。
向量是语言被"数字化"后的形态。语言模型不是跳过了这一步,而是把这一步做到了更深。而 RAG,则是把这一步工业化了。
附一:可运行的 Word2Vec 最小示例
python
# pip install gensim
from gensim.models import Word2Vec
sentences_raw = [
"我 喝 了 一杯 咖啡",
"我 喝 了 一杯 拿铁",
"我 喝 了 一杯 美式",
"咖啡 的 味道 很 苦",
"拿铁 加 了 牛奶",
"他 喝 了 一杯 咖啡",
"她 点 了 一杯 拿铁",
]
corpus = [s.split() for s in sentences_raw]
model = Word2Vec(
sentences=corpus,
vector_size=100,
window=3,
min_count=1,
sg=1, # 1 = Skip-gram, 0 = CBOW
negative=5,
epochs=200,
)
print("咖啡 ->", model.wv["咖啡"][:5], "...")
print("cos(咖啡, 拿铁) =", model.wv.similarity("咖啡", "拿铁"))
print("最像『咖啡』的词:", model.wv.most_similar("咖啡"))
# 内在评估:类比任务
analogies = [("男人", "女人", "国王", "女王")]
def analogy_accuracy(model, pairs, topn=1):
correct = 0
for a, b, c, d in pairs:
try:
preds = [w for w, _ in model.wv.most_similar(
positive=[a, c], negative=[b], topn=topn)]
if d in preds:
correct += 1
except KeyError:
continue
return correct / len(pairs) if pairs else 0.0
print("类比准确率(示意语料下结果无意义):", analogy_accuracy(model, analogies))
注:示意语料太小,类比效果出不来。要复现 "king - man + woman ≈ queen",需要百万级以上的真实语料。
附二:几个常见误区(自查用)
| 误区 | 纠正 |
|---|---|
| "向量模型 = 语言模型" | 两者是配合关系:一个负责表示,一个负责计算 |
| "词向量理解了语义" | 它只是压缩了共现统计,不理解、也不推理 |
| "维度越高效果越好" | 超过一定维度后收益递减,反而增加成本和检索延迟 |
| "余弦相似度高就等于同义" | 只是分布相近,反义词也可能高(如"热"/"冷"共现强) |
| "Word2Vec 类比一定成立" | 只在关系稳定复现的语料上成立,且有噪声 |
| "内在评估高 = 落地效果好" | 两者不一定相关,最终必须看下游任务 |
| "榜单排名高就用它" | MTEB 等榜单是通用基准,你的领域效果必须自己测 |
| "内存点积就能上生产" | 规模一上来延迟就爆,必须用向量索引 |
| "RAG = 向量检索" | 还有切分、重排、混合检索、提示词约束,缺一不可 |
附三:常见问题排查(FAQ)
把前面几节最容易踩的坑,整理成 6 个高频问题,每个都给出排查步骤 + 可运行的代码。
Q1:faiss 索引构建时内存不足怎么办?
排查步骤:
- 先算一下理论占用:
向量数 × 维度 × 4 字节(float32)。100 万条 × 768 维 ≈ 3 GB,这还只是原始向量; - 检查是否用了
IndexFlatIP------它是全量存储,最占内存; - 换用压缩索引 (
IndexIVFPQ)或降维(PCA)来压内存; - 实在不行,改用磁盘索引 (
IndexIVFPQ+write_index)或分片。
代码示例:
python
# pip install faiss-cpu numpy
import faiss
import numpy as np
# 模拟 100 万条、768 维的向量
n, dim = 1_000_000, 768
xb = np.random.random((n, dim)).astype("float32")
# 方案 A:暴力索引(最准,但最占内存)
flat = faiss.IndexFlatIP(dim)
flat.add(xb)
print(f"IndexFlatIP 占用约 {flat.ntotal * dim * 4 / 1024**3:.2f} GB")
# 方案 B:IVF + PQ 压缩(内存降到约 1/8,精度略降)
nlist = 100
quantizer = faiss.IndexFlatIP(dim)
ivf_pq = faiss.IndexIVFPQ(quantizer, dim, nlist, m=16, nbits=8) # m=16 → 每向量 16 字节
ivf_pq.train(xb)
ivf_pq.add(xb)
ivf_pq.nprobe = 10
print(f"IndexIVFPQ 占用约 {ivf_pq.ntotal * 16 / 1024**3:.2f} GB")
# 方案 C:先 PCA 降维到 256 维再建索引
pca = faiss.PCAMatrix(dim, 256)
pca.train(xb)
xb_low = pca.apply(xb)
flat_low = faiss.IndexFlatIP(256)
flat_low.add(xb_low)
print(f"PCA(256) + Flat 占用约 {flat_low.ntotal * 256 * 4 / 1024**3:.2f} GB")
结论 :内存不够时,优先
IndexIVFPQ(压缩),其次 PCA 降维;两者可叠加。
Q2:检索结果相关性差,如何调优?
排查步骤:
- 先看召回:把 Top-K 打印出来,人工判断是不是"该来的没来";
- 调切分:chunk 太大 → 语义被稀释;太小 → 上下文丢失。试试 200~500 字 + 20% 重叠;
- 换 embedding 模型:通用模型在垂直领域掉点,换领域模型或加指令前缀;
- 加混合检索:向量 + BM25 关键词,专有名词/编号类查询召回会明显变好;
- 加重排(Reranker):用 Cross-Encoder 对 Top-K 精排,去掉噪声。
代码示例(混合检索 + 重排):
python
# pip install sentence-transformers rank-bm25 jieba
import numpy as np
from sentence_transformers import SentenceTransformer, CrossEncoder
from rank_bm25 import BM25Okapi
import jieba
docs = [
"如何申请办理居住证",
"身份证丢失后如何补办",
"公积金提取需要哪些材料",
"社保卡怎么激活和使用",
"户口迁移的办理流程",
"车辆年检需要准备什么",
]
query = "我的身份证没了,怎么重新办?"
# ---------- 1. 向量检索 ----------
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
doc_vecs = model.encode(docs, normalize_embeddings=True)
q_vec = model.encode("为这个句子生成表示以用于检索相关文章:" + query, normalize_embeddings=True)
vec_scores = doc_vecs @ q_vec
vec_rank = np.argsort(-vec_scores)[:5]
# ---------- 2. BM25 关键词检索 ----------
tokenized_docs = [list(jieba.cut(d)) for d in docs]
bm25 = BM25Okapi(tokenized_docs)
bm25_scores = bm25.get_scores(list(jieba.cut(query)))
bm25_rank = np.argsort(-bm25_scores)[:5]
# ---------- 3. 融合(简单加权) ----------
# 归一化后加权求和,权重可调
vec_norm = vec_scores / (vec_scores.max() + 1e-9)
bm25_norm = bm25_scores / (bm25_scores.max() + 1e-9)
fusion = 0.7 * vec_norm + 0.3 * bm25_norm
fusion_rank = np.argsort(-fusion)[:5]
# ---------- 4. 重排(Cross-Encoder) ----------
reranker = CrossEncoder("BAAI/bge-reranker-base")
candidates = [docs[i] for i in fusion_rank]
pairs = [(query, d) for d in candidates]
rerank_scores = reranker.predict(pairs)
final_rank = np.argsort(-rerank_scores)
print("融合检索 + 重排后的 Top-K:")
for rank, idx in enumerate(final_rank, 1):
print(f"Top{rank} score={rerank_scores[idx]:.4f} | {candidates[idx]}")
结论 :相关性差,先调切分和模型,再加混合检索 + 重排,效果提升最明显。
Q3:中文场景下 embedding 模型选哪个效果最好?
排查步骤:
- 先看数据能否出内网:不能 → 只能选开源可私有化模型;
- 看任务类型:检索/RAG 选专门优化检索的,聚类/去重选通用语义模型;
- 看中文支持:优先 BGE / GTE / M3E / Qwen-Embedding;
- 别信榜单,用自己数据测:建 200~500 条评测集,跑 Recall@K。
代码示例(用自己数据快速对比两个模型):
python
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer
import numpy as np
# 你的评测集:query → 期望命中的 doc
eval_set = [
("身份证丢了怎么补办", "身份证丢失后如何补办"),
("公积金怎么提取", "公积金提取需要哪些材料"),
("社保卡怎么激活", "社保卡怎么激活和使用"),
]
def recall_at_k(model_name, eval_set, k=1):
model = SentenceTransformer(model_name)
docs = list({d for _, d in eval_set})
doc_vecs = model.encode(docs, normalize_embeddings=True)
hits = 0
for q, gold in eval_set:
q_vec = model.encode("为这个句子生成表示以用于检索相关文章:" + q, normalize_embeddings=True)
scores = doc_vecs @ q_vec
top_idx = np.argsort(-scores)[:k]
if gold in [docs[i] for i in top_idx]:
hits += 1
return hits / len(eval_set)
for name in ["BAAI/bge-small-zh-v1.5", "shibing624/text2vec-base-chinese"]:
print(f"{name} Recall@1 = {recall_at_k(name, eval_set):.2f}")
结论 :中文检索场景,BGE 系列是稳妥首选;但最终要用自己的评测集在 2~3 个候选里实测,别只看榜单。
Q4:RAG 回答出现幻觉如何缓解?
排查步骤:
- 提示词约束:明确"仅依据资料回答,资料没有的就说不知道";
- 降低温度 :
temperature=0.1~0.2,减少自由发挥; - 提高检索质量:Top-K 里混入不相关内容,模型就会编------先按 Q2 调检索;
- 加"不知道"兜底:检索分数低于阈值时,直接拒绝回答;
- 引用溯源:让模型输出时标注依据的 chunk,便于人工核查。
代码示例(带阈值兜底 + 引用溯源):
python
# 接 12.3 的 rag_answer,这里只改生成部分
def rag_answer_safe(query, top_k=3, score_threshold=0.5):
retrieved = retrieve(query, top_k) # 复用 12.3 的 retrieve
# 1. 分数兜底:最高分太低 → 不答
best_score = max(s for _, s in retrieved)
if best_score < score_threshold:
return "抱歉,知识库中没有找到足够相关的资料,无法回答。", retrieved
# 2. 提示词强约束 + 要求引用
context = "\n".join(f"[{i}] {c['text']}" for i, (c, _) in enumerate(retrieved))
prompt = (
"请仅根据下列资料回答问题。\n"
"规则:\n"
"1) 资料中没有的信息,一律回答'资料中未提及';\n"
"2) 回答末尾用 [编号] 标注依据的资料。\n\n"
f"资料:\n{context}\n\n"
f"问题:{query}"
)
answer = llm_generate(prompt) # 复用 12.3 的 llm_generate
return answer, retrieved
结论 :幻觉缓解三板斧------提示词约束 + 低温度 + 检索质量 ,再加分数兜底 和引用溯源做双保险。
Q5:向量维度选择有什么经验值?
排查步骤:
- 词向量(Word2Vec/FastText):100~300 维,教学/基线用 100 就够;
- 句子向量(BGE/GTE/E5):512~1024 维,模型已定,一般不用自己选;
- 自己训练/降维时 :先跑 256/512/768 三档,用评测集对比,选收益不再增长的那档;
- 维度越高:索引越大、检索越慢、内存越贵,收益递减。
代码示例(对比不同维度下的检索效果):
python
# pip install sentence-transformers faiss-cpu numpy
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
# 用 PCA 把 768 维压到不同维度,对比 Recall@K
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
docs = [
"如何申请办理居住证",
"身份证丢失后如何补办",
"公积金提取需要哪些材料",
"社保卡怎么激活和使用",
"户口迁移的办理流程",
"车辆年检需要准备什么",
]
queries = ["身份证丢了怎么办", "公积金怎么提", "社保卡怎么激活"]
doc_vecs = model.encode(docs, normalize_embeddings=True).astype("float32")
q_vecs = model.encode(queries, normalize_embeddings=True).astype("float32")
for target_dim in [128, 256, 512, 768]:
if target_dim < 768:
pca = faiss.PCAMatrix(768, target_dim)
pca.train(doc_vecs)
doc_low = pca.apply(doc_vecs)
q_low = pca.apply(q_vecs)
else:
doc_low, q_low = doc_vecs, q_vecs
index = faiss.IndexFlatIP(target_dim)
index.add(doc_low)
hits = 0
for q, gold in zip(q_vecs, docs):
scores, idxs = index.search(q.reshape(1, -1), 1)
if docs[idxs[0][0]] == gold:
hits += 1
print(f"维度 {target_dim}: Recall@1 = {hits / len(queries):.2f}")
结论 :句子向量直接用模型默认维度;自己降维时,找到"再降就明显掉点"的临界维度即可,别盲目追求高维。
Q6:如何评估 RAG 系统的整体效果?
排查步骤:
- 分模块评估:检索(Recall@K / MRR / nDCG)和生成(忠实度 / 答案质量)分开测;
- 建评测集 :50~200 条
(query, 期望chunk, 参考答案); - 检索评估:看 Top-K 里有没有期望 chunk;
- 生成评估:看答案是否忠实于检索到的资料(有无幻觉)、是否回答了问题;
- 自动化 + 人工抽检:先用脚本跑指标,再人工抽 20~30 条看质量。
代码示例(检索 + 生成两段式评估):
python
# pip install sentence-transformers faiss-cpu numpy
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
# ---------- 评测集:query → 期望命中的 chunk ----------
eval_set = [
("身份证丢了怎么补办", "身份证丢失后如何补办"),
("公积金怎么提取", "公积金提取需要哪些材料"),
("社保卡怎么激活", "社保卡怎么激活和使用"),
]
# ---------- 建库 ----------
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
all_docs = list({d for _, d in eval_set})
doc_vecs = model.encode(all_docs, normalize_embeddings=True).astype("float32")
index = faiss.IndexFlatIP(doc_vecs.shape[1])
index.add(doc_vecs)
# ---------- 1. 检索评估:Recall@K / MRR ----------
def evaluate_retrieval(k=1):
recall, mrr = 0.0, 0.0
for q, gold in eval_set:
q_vec = model.encode("为这个句子生成表示以用于检索相关文章:" + q, normalize_embeddings=True)
q_vec = np.asarray([q_vec], dtype="float32")
scores, idxs = index.search(q_vec, k)
hits = [all_docs[i] for i in idxs[0]]
if gold in hits:
recall += 1
mrr += 1.0 / (hits.index(gold) + 1)
n = len(eval_set)
return recall / n, mrr / n
recall, mrr = evaluate_retrieval(k=3)
print(f"检索评估:Recall@3 = {recall:.2f},MRR = {mrr:.2f}")
# ---------- 2. 生成评估:忠实度(答案是否基于检索到的资料) ----------
def evaluate_generation(answers, retrieved_chunks):
"""简化版:检查答案中的关键句是否出现在检索到的 chunk 里。"""
faithful = 0
for ans, chunks in zip(answers, retrieved_chunks):
# 把答案按句号切分,看每句是否能在某个 chunk 中找到子串
sentences = [s for s in ans.replace("\n", "").split("。") if s]
hit = sum(1 for s in sentences if any(s[:10] in c for c in chunks))
if hit / len(sentences) >= 0.8: # 80% 句子有依据 → 算忠实
faithful += 1
return faithful / len(answers)
# 示意:answers 来自你的 LLM,chunks 来自检索结果
# answers = ["身份证丢失后应立即挂失...", ...]
# retrieved_chunks = [[c1, c2, c3], ...]
# print(f"生成评估:忠实度 = {evaluate_generation(answers, retrieved_chunks):.2f}")
结论 :RAG 评估要检索和生成分开看------检索差先调 Q2,生成差先调 Q4;用 50~200 条评测集持续回归,别靠感觉。
一句话 :FAQ 的 6 个问题,本质是内存、检索、选型、幻觉、维度、评估 六个维度的工程兜底。先跑通最小链路,再按这六步逐项排查,RAG 就能从"能跑"走向"好用"。
附三:两张配图的绘图提示词
图 1:one-hot vs 稠密向量对比示意
绘图提示词(中文,含英文标注):
技术博客配图,扁平矢量风格,横向对比布局,白色背景,简洁现代科技感。
左侧标题:"One-hot 编码",展示一个极长、极稀疏的条形向量:一整排灰色小方块中只有 1 个是深蓝色高亮,右侧标注文字 "100,000 维 · 稀疏 · 任意两词相似度 = 0"。
右侧标题:"稠密向量 (Word Embedding)",展示一个短而密的彩色向量条:约 12 个方块,颜色渐变(蓝→紫→橙),右侧标注文字 "300 维 · 稠密 · 语义相近则距离近"。
中间一个大大的对比箭头,标注 "降维 + 保留语义"。
底部一行小字:"离散的文字 → 连续的意义空间"。
配色:深蓝 #1f4e79、紫色 #6a4c93、橙色 #e07a5f、浅灰 #eaeaea。字体清晰,无 3D,无阴影。
图 2:词向量的几何关系示意(king - man + woman ≈ queen)
绘图提示词(中文):
技术博客配图,二维散点示意图,白色背景,扁平矢量风格。
用箭头在平面坐标上画出四个词的位置与向量关系:
点 "man" 与点 "woman" 之间画一个红色箭头,标注 "性别方向";
点 "king" 与点 "queen" 之间画一个同样的红色箭头(方向一致);
用蓝色向量表示 "king → man" 的方向,用绿色向量表示 "woman → queen";
在最上方用公式标注:"king − man + woman ≈ queen"。
四个词点用圆点 + 标签表示,字体清晰。整体简洁,配色:深蓝 #1f4e79、红色 #c0392b、绿色 #2e8b57。
关键视觉:两组词对之间的箭头互相平行,直观体现"语义关系 = 空间中同一方向"。
图 2 的核心视觉是两条平行箭头------这是让读者"秒懂"语义即方向的关键。
图 3(新增):RAG 完整链路示意
绘图提示词(中文):
技术博客配图,左右两部分布局的流程示意图,扁平矢量风格,白色背景,简洁专业。
上半部分标注 "离线建库(一次)":一串流程方块,从左到右依次为 "文档 → 切分 chunk → Embedding → 向量库",方块用深蓝色 #1f4e79,箭头为灰色。
下半部分标注 "在线问答(每次请求)":流程为 "用户提问 → 提问向量化 → 向量检索 Top-K → 拼接提示词 → LLM 生成答案",用橙色 #e07a5f 突出"向量检索 Top-K"这一核心步骤。
中间用一个虚线箭头把"向量库"和"向量检索 Top-K"连起来,标注"复用同一套向量"。
底部一行小字:"RAG 的地基 = 向量表示 + 相似度检索"。
整体布局清晰,箭头方向一致,字体明确,无 3D、无阴影。
标签 :# 自然语言处理 # Word2Vec # 词向量 # Embedding # 大模型 # BERT # RAG # 向量检索 # faiss # 表示学习 # Python
免责声明:本文为原理讲解,示例语料与代码为示意,数据与结论仅用于说明机制;实际效果请以自有评测集为准。
这一版把最后两块补齐了:
- 十一、faiss 版语义检索:从内存点积升级到工业级索引,含索引选型表 + 从 demo 到生产的差距清单;
- 十二、RAG 里的向量怎么用 :完整链路 + 最小主流程 + 踩坑表,并回扣前文所有知识点------点明 RAG 就是本文内容的工程化组合。
文章现在是一条完整主线:
原理(分布假说)→ 机制(W2V/BERT)→ 度量(评估)→ 选型(embedding 模型)→ 检索(内存/faiss)→ 应用(RAG)
每个环节都有代码可跑,技术向读者能一路从"为什么"读到"怎么用",还把最热的 RAG 串了进来------CSDN 上这类"原理打底 + 工程落地"的长文,收藏率通常都不错。
至此这篇可以定稿了。如果之后还想拓展,有两个自然的"续篇"方向:① embedding 模型微调实战 (如何用领域数据提升检索效果)、② RAG 效果调优专题(切分策略、混合检索、rerank 对比)。要写的时候随时叫我。