1、RAG技术流总结


1.1 索引
坦白来说这部分的技术并不是大模型领域的,更像是之前技术在大模型领域的应用;早在2019年我就做过faiss部分的尝试,彼时索引技术已经在互联网领域得到了广泛的应用。
1、大模型需要通过向量化去建立语义理解。
通过将包含高维信息的知识降维到向量空间里,这些知识就变成了一堆数字串;此时,当用户去提问时,先将提问的知识向量化变成一串数字后,再从知识库中通过余弦计算等方式找出和用户提问数字串最相似的信息出来,这就完成了所谓的语义理解。
2、分块能够有效提升检索效率和缓解上下文长度限制。
理想状态下,在检索时将每个信息都遍历一遍肯定就不会漏信息了,但是当信息量大且不能让用户等待过久的时候,还是需要更高效和更具性价比的方式;同时,大模型一次能输入的上下文有长度限制,虽然已经有大模型将上下文长度延伸至了更高量级,但似乎实验证明更大的上下文窗口不一定对检索结果更有效。
而分块技术,则可以理解为将一篇50w字的书籍文档按照段落或者语义等方式划分成n个块。这样,既能够有效解决上下文长度限制问题,同时也对于检索有一定的效率提升;但同时也存在可能会丢失文档的全局结构、不同块之间的前后逻辑等问题(这些问题在陆续通过建立重叠上下块内容、建立块的类似索引结构等方式进行优化)。

1.2 检索Retrieval
当用户提问后,通过检索技术则可以从知识库中召回相关内容块。检索方式将不局限于关键词检索和向量检索,最终的形态一定是多种检索方式的结合和互补。当混合检索结束后,再通过一个Rerank的机制重新对不同渠道的检索结果做一个最终的整合和排序。
1.3 生成(Generation)
将检索得到的前 K 个文本块和用户问题一起送进大模型,让大模型基于给定的文本块来回答用户的问题。
在整个完整的RAG过程中,索引和检索将极大的影响最终生成的质量。其中索引直接决定语义保存的完整度,检索决定提供给大模型的文本质量,值得注意的是检索过程还存在rerank的过程。
在知识库问答,数据越多效果越好吗?中,有道大模型团队提出针对query大连医科大学怎么样,主要原因是第三批加入的某些文档中恰好有大连理工大学 xxx 怎么样?的句子,和 query 大连医科大学怎么样?表面上看起来确实非常像,Embedding 给它打了比较高的分。直接向量检索的情况下因为缺乏语义部分导致
而类似大连医科大学师资介绍这样的片段相关性就稍微低了些。而 LLM 输入 token 有限制,前面两个最相关但是实际并不能回答 query 问题的片段就已经占满了 token 的窗口,只能把他俩送进 LLM 里。结果可想而知,啥都不知道。
RAG知识除了可以参考前面有道团队的介绍,另外也可以参考这一篇:
一文读懂:大模型RAG(检索增强生成)含高级方法
2、transformer总结
在之前的文章中我们有总结过transformer,其实当时配合李沐的视频也是有一定了解的,目前可以配合代码做进一步的了解。
NLP入门:word2vec & self-attention & transformer & diffusion的技术演变
其中QKV是由可学习的权重矩阵和input相乘得到,目的是获得不同隐空间的向量表示;具体在单个隐空间的变换可以参考:超越标准注意力机制:探索深度模型中的多头潜在注意力
2.1模型输入:Embedding + Positional Encoding
我们通常输入一个token 序列(文字已经被分词器变成了 ID,比如 [101, 1342, 129, 102])。
每个 token ID 会被映射成一个向量(embedding),假设维度是 768。
但是,注意力机制本身没有“顺序”概念,所以需要加上位置编码(Positional Encoding)
最常见的是使用 正弦/余弦编码:
P
E
(
p
o
s
,
2
i
)
=
sin
(
p
o
s
1000
0
2
i
d
m
o
d
e
l
)
P
E
(
p
o
s
,
2
i
+
1
)
=
cos
(
p
o
s
1000
0
2
i
d
m
o
d
e
l
)
\begin{aligned} PE(pos, 2i) &= \sin\left(\frac{pos}{10000^{\frac{2i}{d_{model}}}}\right) \\ PE(pos, 2i + 1) &= \cos\left(\frac{pos}{10000^{\frac{2i}{d_{model}}}}\right) \end{aligned}
PE(pos,2i)PE(pos,2i+1)=sin(10000dmodel2ipos)=cos(10000dmodel2ipos)
这可以保证每个位置的向量是唯一的,并且具有距离相关性。
2.2 Self-Attention 机制
基本原理:
对于输入中的每个 token,它会与所有其他 token 进行交互,学习 “我应该注意谁”。
我们会为每个 token 生成三个向量:Query 向量:我要问的问题,Key 向量:我是谁,Value 向量:我携带的信息。
计算过程(点积注意力):
Attention
(
Q
,
K
,
V
)
=
softmax
(
Q
K
T
d
k
)
V
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V
Attention(Q,K,V)=softmax(dkQKT)V
本质上,它们都是从原始的输入词向量(也叫 embedding)线性变换出来的!
假设我们有一句话:“I love machine learning”
这句话被编码成 4 个向量(假设每个维度是 512):
X = [x₁, x₂, x₃, x₄] # 每个 x 是一个词向量(来自 Embedding)
Transformer 里有三组参数矩阵:
W_Q:Query 的权重;W_K:Key 的权重;W_V:Value 的权重
这些是可训练的矩阵,通常形状是 [hidden_size, head_dim]。
每个词向量 x 会分别乘这三个矩阵:
Q = X × W_Q # 得到每个 token 的 Query 向量
K = X × W_K # 得到每个 token 的 Key 向量
V = X × W_V # 得到每个 token 的 Value 向量
模型会通过反向传播算法(back propagation) 计算 loss 对每个参数矩阵的梯度,并对其进行最终的固定。注意每个 token ID 没有独立的 QKV 参数,所有 token 的 embedding 是通过共享的 QKV 线性层转换的。多头注意力里也一样:每个注意力头会有自己的一套 Q/K/V 权重矩阵,但依旧是所有 token 共享该权重。
2.3 多头注意力机制
多头注意力(Multi-Head Attention)确实会使用多个 Q、K、V —— 但是它们都是从同一个输入中计算出来的,只是用不同的参数矩阵**!
多头注意力 = 把注意力机制拆成多个“子注意力”,每个头(head)专注于不同的关系子空间,最后把它们合并在一起。
举个例子:
假设输入维度是 hidden_dim = 512
你要用 num_heads = 8
每个头的维度 = head_dim = 64(因为 512 / 8 = 64)
那么你需要:
每一组都从同一个输入向量 X 中生成一组 Q, K, V,每个头都在不同的子空间中“关注”不同的东西。
3、自然语言如何变换为向量
本章节内容来自大模型整理,主要是讲述自然语言到向量的变换过程。
Token生成的具体步骤可分为以下四个阶段:
3.1、文本预处理阶段
-
格式规范化
去除文本中的特殊符号、标点符号和非标准字符,统一文本格式(如全角转半角)。部分模型会强制转换大小写以提高处理效率(如全部转为小写)。 -
语义单元分割(分词)
通过空格分词将文本切分为基础语义单元,例如英文按单词分割,中文按字/词分割。此阶段需处理语言特性差异,如中文无空格需额外分词处理。
3.2、Tokenization阶段
-
子词拆分策略
采用字节对编码(BPE)算法进行动态合并: -
初始化时将每个字符视为独立Token
统计相邻Token对出现频率,优先合并高频组合(如"ing"+"?“→"ing?”)
重复合并直至达到预设词表规模(如GPT-3词表50,257个) -
词表映射
根据训练完成的词表,将预处理文本转换为整数序列。例如"人工智能"可能拆分为[“人”, “工”, “智能”]对应ID序列。
3.3、向量编码阶段
- 嵌入层转换
通过Embedding层将整数ID转换为高维向量(如768维),每个维度编码特定语义特征。
将Token ID序列转换为向量表示的技术路径可分为以下四类,各类方法在实现机制和应用场景上存在显著差异:
3.3.1、嵌入层映射法(深度学习主流)
预训练嵌入层
在Transformer架构中,Embedding层将每个Token ID映射为高维向量(如768维),通过海量语料训练获得语义编码能力。例如:
# 使用HuggingFace实现
from transformers import AutoModel
model = AutoModel.from_pretrained("bert-base-chinese")
inputs = [101, 3928, 1920, 2110] # Token ID序列
outputs = model(torch.tensor([inputs])).last_hidden_state # 输出768维向量矩阵
动态参数更新
在模型训练过程中,Embedding层的权重通过反向传播持续优化,使相似语义的Token向量在向量空间中距离更近。
3.3.2、词袋模型法(传统NLP方法)
One-Hot编码
生成维度等于词表大小的稀疏向量,仅对应位置置:
# 示例:词表规模10,000
token_id = 3892
one_hot_vector = *10000
one_hot_vector[3892] = 1 # 产生万维稀疏向量:ml-citation{ref="2,4" data="citationList"}
TF-IDF加权
在One-Hot基础上叠加词频-逆文档频率权重
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ["深度学习 改变 世界", "自然语言处理 技术"]
vectorizer = TfidfVectorizer(vocabulary={"深度学习":3892, "改变":6753,...})
tfidf_matrix = vectorizer.fit_transform(corpus) # 获得加权向量:ml-citation{ref="3,8" data="citationList"}
3.3.3、分布式表示法(过渡技术)
Word2Vec转换
通过预训练词向量表进行查表转换:
# 加载预训练词向量
from gensim.models import Word2Vec
model = Word2Vec.load("word2vec.bin")
vector = model.wv[token_id] # 获取300维稠密向量:ml-citation{ref="2,8" data="citationList"}
动态组合策略
对未登录词(OOV)采用子词向量加权平均,例如"ChatGPT"分解为[“Chat”,“GPT”]后求均值。
3.3.4、混合编码法(工业优化方案)
哈希技巧
使用特征哈希降低维度,解决超大词表存储问题:
from sklearn.feature_extraction.text import HashingVectorizer
hasher = HashingVectorizer(n_features=512)
hashed_vector = hasher.transform([token_id_sequence]):ml-citation{ref="3,8" data="citationList"}
量化压缩
对浮点向量进行8-bit量化,减少存储和传输开销:
import numpy as np
original_vector = np.random.randn(768).astype(np.float32)
quantized = np.round(original_vector * 127).astype(np.int8) # 压缩率提升4倍:ml-citation{ref="7" data="citationList"}
方法对比与选型建议
- 位置编码注入
添加位置向量保留序列顺序信息,使模型理解词语间位置关系。
3.4、生成解码阶段
概率预测
通过Transformer层逐Token计算概率分布,例如输入"北京欢迎"后预测下一个Token概率:“你”(0.8)、“您”(0.15)、其他(0.05)。
解码策略选择
- 贪婪解码:直接选择最高概率Token(如选"你")
- 抽样解码:按概率分布随机采样(可能选"您")
- 束搜索:保留多个候选路径降低重复风险
3.5、补充说明
- 模型差异:GPT-3与GPT-4的Token拆分逻辑不同,相同文本可能产生不同Token数量
- 语言差异:中文Token效率低于英文,如"人工智能"需3个Token,而"AI"仅需1个
- 无损转换:通过逆向词表可将Token序列完全还原为原始文本
各阶段处理示例:
原始文本: "ChatGPT-4表现惊艳!"
预处理后: "chatgpt4表现惊艳"
Token序列: ["chat","gpt","4","表","现","惊","艳"](GPT-3)
对应ID: [1536, 2250, 20, 1932, 2106, 9854, 10533]
4、Rerank模型原理
4.1 Cross Encoder
🧠 一、Cross Encoder 核心结构回顾
-
Cross Encoder 的核心思想就是:将 Query 和 Document(或 Answer)拼接成一个输入句对,送入一个 Transformer 模型(如 BERT)中,让它从头到尾判断两者之间的语义匹配程度。
-
输入结构举例(BERT-style): [CLS] Query [SEP] Document [SEP]
例如:
[CLS] 人工智能的应用 [SEP] 人工智能被广泛应用于医疗、教育等领域。 [SEP]
Transformer 模型处理后,输出的是 token-level 的表示,我们取 [CLS] 对应的向量作为句对的整体表示。然后通过一个全连接层(MLP)进行打分,比如:1个输出节点 → 表示相关性分数(Sigmoid),2个输出节点 → 表示二分类(Softmax:相关 vs 不相关)
🏗️ 二、Cross Encoder 的训练方式
🏷️ 1. 数据准备
-
Cross Encoder 训练是监督学习,需要构造 (Query, Document, Label) 三元组。正样本:真实匹配的 Query 和 Document(如人工标注的、用户点击的)。负样本:不相关的 Document(可以从随机采样、BM25召回中挑错的)。
-
比如:Query Document Label
人工智能的应用 AI在医疗和金融中发挥重要作用 1
人工智能的应用 一部2001年的电影《人工智能》 0
📐 2. 常见损失函数
- ✅ 二分类损失(Binary Cross Entropy):
适用于训练模型判断“是否相关”。
loss = -[y * log(p) + (1 - y) * log(1 - p)]
- ✅ 排序损失(Pairwise Ranking Loss / Margin Ranking Loss):
适用于学习排序的优先级。
每个训练样本是:(Query, Pos_Doc, Neg_Doc)。
目标:让正文档的得分高于负文档至少margin的数值。
loss = max(0, margin - score_pos + score_neg)
- ✅ Softmax Loss over candidates(Listwise):
适用于每个 query 有多个候选文档的场景,模型对所有候选进行归一化打分。
🛠️ 三、优化技巧 & 实践经验
-
🧪 1. 预训练模型选择
常用模型:BERT、RoBERTa,MacBERT(中文效果好),DeBERTa(更强的语义建模),multilingual-BERT(多语言),可直接从 HuggingFace 上加载预训练模型进行微调。 -
⚙️ 2. 输入截断与长度控制
因为 Query 和 Document 拼接后容易超出 Transformer 的最大长度(通常为 512),所以:
Query:保留全部(通常很短)
Document:根据重要性截断(如只取前256 tokens)
长文档处理:使用滑窗+投票机制 -
🔁 3. Batch 构造技巧(Pairwise/Triplet)
使用 Pairwise Loss 或 Triplet Loss 时,构造三元组时可以:
每个正样本搭配多个 hard negative(靠近但不相关)
使用 BM25 排名靠前但非点击文档作为负样本
这有助于模型学习更有判别力的语义特征。 -
🧊 4. 模型蒸馏(知识压缩)
Cross Encoder 很准但慢。常见做法是:用 Cross Encoder 精排训练 Bi-Encoder,让它学到“语义判断能力”。得到一个兼顾速度和效果的模型。
🚀 四、部署时的注意事项
- 计算开销大:每一个候选都要重新跑一遍模型,所以只能在候选集较小时使用(比如 Top 50)。
GPU加速:适合 batch inference,减少重复模型加载。
批处理注意pad mask:处理不同长度输入时,注意对 padding 做 attention mask。
✅ 总结一下:
Cross Encoder 是通过联合编码 Query 和 Document,学习它们之间的深层语义关系来进行排序的模型。它的训练依赖标注或构造的正负样本,常用 BERT 类模型作为骨干,在效果上明显优于 Bi-Encoder,但计算成本也更高。
4.2 Bi-Encoder
🧠 一、什么是 Bi-Encoder?
- Bi-Encoder 是一种将两个输入(如 Query 和 Document)分别编码成向量,再通过向量之间的相似度(如余弦相似度)判断它们是否相关的模型结构。它的结构比 Cross Encoder 简单、计算更高效,适合大规模向量检索和实时应用。
⚙️ 二、Bi-Encoder 的工作原理
- 输入:Query(查询文本) Document(候选文本)
编码方式:使用同一个编码器(如 BERT、RoBERTa)或两个参数共享的编码器,将它们分别转换成向量:
Q = Encoder(query)
D = Encoder(document)
相似度计算:
计算 Q 和 D 的相似度作为相关性得分:
score = cosine(Q, D) 或 dot_product(Q, D)
🔍 三、Bi-Encoder 的应用场景
📌 Bi-Encoder 的最大优势是:支持离线编码与高效检索。
🎯 四、训练方式
- 数据准备
跟 Cross Encoder 类似,也需要 (Query, Pos_Doc, Neg_Doc) 的三元组。 - 损失函数
✅ 对比学习损失(Contrastive Loss / Triplet Loss):
目标是:正样本的相似度要高于负样本。
loss = max(0, margin - sim(Q, D_pos) + sim(Q, D_neg))
✅ 多负样本 InfoNCE / In-batch negative:
每个 query 在一个 batch 内使用其他样本的 document 作为负样本。
loss = CrossEntropy(similarity_matrix)
总结:Bi-Encoder 是一种高效的、可扩展的语义匹配结构,它将查询和候选分别编码成向量,用于快速计算相似度,特别适合大规模语义检索场景。