大模型外挂知识库优化——负样本挖掘篇
📝 通俗解释:在大模型回答用户问题时,需要从知识库中检索相关文档。如果检索模型训练得不好,可能会召回不相关或质量较差的文档。训练检索模型需要"正例"(相关文档)和"负例"(不相关文档),如何挑选好的"负例"就是本文要讨论的问题。
一、为什么需要构建负难样本?
📝 通俗解释:训练检索模型就像让学生做选择题,需要有对的选项(正例)和错的选项(负例)才能学会区分。如果负例太简单(比如明显不相关的文档),模型学不到什么东西;如果负例太难(比如实际上相关但被误判的文档),反而会误导模型学习。
在各类检索任务中,为训练好一个高质量的检索模型,往往需要从大量的候选样本集合中采样高质量的负例,配合正例一起进行训练。
二、负难样本构建方法篇
2.1 随机采样策略(Random Sampling)方法
- 方法:直接基于均匀分布从所有的候选 Document 中随机抽取 Document 作为负例
📝 通俗解释:就像从一个大箱子里随便抓一把文档作为"错误的答案"。这种方法最简单,但效果往往不好。
存在问题:由于无法保证采样得到的负例的质量,故经常会采样得到过于简单的负例,其不仅无法给模型带来有用信息,还可能导致模型过拟合,进而无法区分某些较难的负例样本
梯度影响分析:
对于随机采样方法,由于其采样得到的负例往往过于简单,其会导致该分数接近于零 $$s_n(q, d) \longrightarrow 0$$
进而导致其生成的梯度均值也接近于零, $$\nabla_\theta l(q, d) \longrightarrow 0$$
这样过于小的梯度均值会导致模型不易于收敛。
📝 通俗解释:梯度可以理解为模型"学习的方向和强度"。如果负例太简单,模型计算出的梯度几乎为零,就像考试题目太简单,大家都能答对,老师无法从考试中判断学生真正掌握了多少知识。
2.2 Top-K 负例采样策略(Top-K Hard Negative Sampling)方法
- 方法:基于一稠密检索模型对所有候选 Document 与 Query 计算匹配分数,然后直接选择其中 Top-K 的候选 Document 作为负例
📝 通俗解释:先用一个小模型对所有文档打分,选出得分最高但不是正例的那些文档作为"负例"。这些负例是模型目前最容易混淆的,属于"难题"。
优点:可以保证采样得到的负例是模型未能较好区分的较难负例
存在问题:很可能将潜在的正例也误判为负例,即假负例(False Negative)。如果训练模型去将该部分假负例与正例区分开来,反而会导致模型无法准确衡量 Query-Document 的语义相似度。
梯度影响分析:
由于其很容易采样得到语义与正例一致的假负例,其会导致正负样本的右项 $\nabla_\theta s_n(q, d)$ 值相似,但是左项符号相反,这样会导致计算得到的梯度方差很大,同样导致模型训练不稳定。
📝 通俗解释:梯度方差大意味着模型训练时"方向不稳定",一会儿往左一会儿往右,就像开车时方向盘抖动,很难稳定前进。
2.3 困惑负样本采样方法 SimANS 方法
📝 通俗解释:SimANS 的核心思想是选择"中等难度"的负例——既不太简单(学不到东西),也不太难(可能是假负例),就像考试中的"中等难度题"最有助于提高水平。
动机:在所有负例候选中,与 Query 的语义相似度接近于正例的负例可以同时具有较大的梯度均值和较小的梯度方差,是更加高质量的困惑负样本
方法:对与正例语义相似度接近的困惑负例样本进行采样
采样方法特点:
- 与 Query 无关的 Document 应被赋予较低的相关分数,因为其可提供的信息量不足
- 与 Query 很可能相关的 Document 应被赋予较低的相关分数,因为其可能是假负例
- 与正例语义相似度接近的 Document 应该被赋予较高的相关分数,因为其既需要被学习,同时是假负例的概率相对较低
Figure 4: An example of the dense embedding distribution of a query with its positive document, too easy, too hard and ambiguous negatives. media/image1.png
📝 通俗解释:这张图展示了向量空间中的分布。中心是查询query $q$,周围分布着正例文档(圆圈)和各类负例(三角形、方块、菱形)。"模棱两可的负例"(蓝色三角形)最难区分,是最佳的训练样本。
- 困惑样本采样分布:
通过以上分析可得,在该采样分布中,随着 Query 与候选 Document 相关分数 $s(q, d_i)$ 和与正例的相关分数 $s(q, d_+)$ 的差值的缩小,该候选 Document 被采样作为负例的概率应该逐渐增大。故可设计采样分布如下所示:
$$ p_i \propto \exp(-\alpha(s(q, d_i) - s(q, d^+) - \beta)^2), \forall d_i \in \mathcal{D}^- $$
其中:
- $\alpha$ 为控制该分布密度的超参数
- $\beta$ 为控制该分布极值点的超参数
- $d^+ \in \mathcal{D}^+$ 是一随机采样的正例样本
- $\mathcal{D}^-$ 是 Top-K 的负例
通过调节 $K$ 的大小,我们可以控制该采样分布的计算开销。
📝 通俗解释:这个公式做的事情是:计算每个负例与正例的相似度差值,差值越小(越接近正例),被选中的概率越高。参数 $\alpha$ 和 $\beta$ 用来调节"难度偏好"。
- SimANS 算法伪代码:
Algorithm 1: The algorithm of SimANS.
Input: Queries and their positive documents {(q, D+)}, document pool D, pre-learned dense retrieval model M
1. Build the ANN index on D using M.
2. Retrieve the top-k ranked negatives D̃- for each query with their relevance scores {s(q, di)} from D.
3. Compute the relevance scores of each query and its positive documents {s(q, D+)}.
4. Generate the sampling probabilities of retrieved top-k negatives {pi} for each query using Eq. 3.
5. Construct new training data {(q, D+, D̃-)}.
6. while M has not converged do
7. Sample a batch from {(q, D+, D̃-)}.
8. Sample ambiguous negatives for each instance from the batch according to {pi}.
9. Optimize parameters of M using the batch and sampled negatives.
10. end2.4 利用对比学习微调方式构建负例方法
📝 通俗解释:对比学习是一种训练向量化模型的常用方法,核心思想是"相似的靠得近,不相似的离得远"。就像把人按兴趣分组,喜欢相同东西的人站得近,不相关的人站得远。
对比学习是优化向量化模型的常用训练方法:
- 目的:优化向量化模型,使其向量化后的文本,相似的在向量空间距离近,不相似的在向量空间距离远。文档召回场景下,做对比学习(有监督)需要三元组(问题,文档正例,文档负例)
📝 通俗解释:文档正例是和问题密切相关的文档片段,文档负例是和问题不相关的文档片段。训练时,模型要学会把正例拉近、负例推远。
- 构建方法:如果是随机出来的话,完全可以用同一个 batch 里,其他问题的文档正例当作某一个问题的文档负例,如果想要效果好,还需要有比较大的 batch size
📝 通俗解释:比如一个batch里有10个问题,每个问题有1个正例文档。那么问题1的正例文档可以作为问题2、3、...10的负例。这样不需要额外准备负例,省时省力。
- 损失函数(基于批内负样本的交叉熵损失):
$$ \ell_i = - \log \frac{e^{\text{sim}(q_i, d_i^+) / \tau}}{\sum_{j=1}^{N} e^{\text{sim}(q_i, d_j^+) / \tau}} $$
其中:
- $q$、$d$ 分别表示问题和文档正例对应的向量
- $\tau$ 为温度系数
- $sim$ 函数可以是 cosine 相似度或者点积
📝 通俗解释:这个公式的意思是,让正例的相似度得分尽量高,负例的得分尽量低,通过对数损失来优化。温度系数 $\tau$ 用来调节"惩罚力度"。
- 实现方法:
q_reps = self.encode(query) # 问题矩阵 维度 (B1, d)
d_reps = self.encode(doc) # 文档矩阵 维度 (B2, d)
score = torch.matmul(q_reps, d_reps.transpose(0, 1)) # 计算相似度矩阵 维度: (B1, B2)
scores = scores / self.temperature
target = torch.arange(scores.size(0), device=scores.device, dtype=torch.long)
# 考虑文档负例不仅来自于 batch 内其他样本的文档正例,也可能人工的给每个样本构造一些文档负例
target = target * (q_reps.size(0) // d_reps.size(0))
loss = cross_entropy(scores, target) # 交叉熵损失函数- 注意事项:
📝 通俗解释:不同任务用的prompt不同,如果把不同任务的样本混在一起训练,模型可能会"偷懒"——根据prompt内容来区分正负例,而不是真正理解语义。所以最好同一个batch里只放同一种任务的样本,或者一个任务训练一个模型。
2.5 基于批内负采样的对比学习方法
- 本质:随机选取文档负例,如果能针对性的,找到和文档正例比较像的文档负例(模型更难区分这些文档负例),加到训练里,是助于提高对比学习效果的。就好比我们只有不断的做难题才能更好的提高考试水平
📝 通俗解释:这其实是2.4方法的进阶版。2.4是用batch内其他问题的正例做负例,这里是主动去找"更像"的负例(可能是同一篇文章的其他段落),增加训练难度。
- 论文方法:在文档向量空间找到和文档正例最相近的文档片段当作文档负例,训练向量化模型。模型更新一段时间后,刷新文档向量,寻找新的文档负例,继续训练模型
📝 通俗解释:这是一个"动态"的过程。随着模型训练,它的理解能力在提升,所以之前找不到的"难负例"现在可能能被找到了,需要定期更新负例库。
参考论文:
- 【1】Approximate nearest neighbor negative contrastive learning for dense text retrieval
- 【2】Contrastive learning with hard negative samples
- 【3】Hard negative mixing for contrastive learning
- 【4】Optimizing dense retrieval model training with hard negatives
- 【5】SimANS: Simple Ambiguous Negatives Sampling for Dense Text Retrieval
2.6 相同文章采样方法
- 思路:文档正例所在的文章里,其他文档片段当作难负例,毕竟至少是属于同一主题的,和随机样本想起来比较难区分
📝 通俗解释:比如一篇文章有多个段落,正例是关于"如何做蛋糕"的段落,那同一篇文章里关于"蛋糕历史"的段落就是"同文章负例"。它们有一定相关性,但不完全回答用户问题,是中等难度的负例。
- 存在问题:实际应用场景下,如果数据质量不高(脏数据),难例挖掘用处可能不大
2.7 LLM 辅助生成软标签及蒸馏
📝 通俗解释:这个方法很直接——让大模型来判断哪些文档片段对回答问题最有帮助,然后用这些信息来指导检索模型的训练。
- 方法:根据用户问题召回的相关文档片段最终是要为 LLM 回答问题服务的,因此 LLM 认为召回的文档是否比较好很重要。以下介绍的方法是 bge2 提出的。
对于向量化模型的训练,可以让 LLM 帮忙生成样本的辅助标签,引导向量化模型训练。辅助标签的生成可用如下公式表示:
$$ r_{C|O} = \prod_{i=1}^{|O|} \text{LLM}(o_i | C, O_{:i-1}) $$
在已知 LLM 需要输出的标准答案下,分别将问题和各个文档片段 $C$ 放入 LLM 的 prompt 中,看 LLM 生成标准答案的概率 $r$ 大小,当作辅助标签。$r$ 越大,表示起对应的文档片段对生成正确答案的贡献越大,也就越重要。
📝 通俗解释:这个公式做的事情是:给LLM一个文档片段,让它尝试生成标准答案。如果能正确生成,说明这个文档片段有用,贡献值r就高。
存在问题:
- 打标要求有点太高
- 很多实际应用场景,无法拿到 LLM 回答的标准答案,同时对每个问题的候选文档片段都计算一个 r,开销比较大
优化方法:
利用以上 LLM 生成的标签以及 KL 散度,对模型进行优化。$P$ 为某个问题 $q$ 对应的候选文档片段 $p$ 的集合,$e$ 表示向量,$\langle \cdot,\cdot \rangle$ 表示相似度操作,$w$ 是对所有候选文档 $p$ 对应的辅助标签值 $r$ 经过 softmax 变换后的值。本质是,如果 LLM 认为某个文档片段越重要,给它的优化权重越大。
$$ \min \sum_{\mathcal{P}} -w_i \cdot \log \frac{\exp(\langle e_q, e_p \rangle / \tau)}{\sum_{p' \in \mathcal{P}} \exp(\langle e_q, e_{p'} \rangle / \tau)} $$
为了进一步稳定蒸馏效果,还可以对候选文档片段根据 $r$ 进行排序,只用排名靠后的样本进行优化。
📝 通俗解释:这个公式的意思是,根据LLM给出的重要性权重来训练检索模型。权重高的文档片段,模型要尽量让它们的向量与问题更接近。同时,只用排名靠后(LLM认为不重要的)的样本来优化,可以避免模型"偷懒"。
辅助知识
附一:梯度计算方法
以稠密检索常用的 BCE loss 为例,正例与采样的负例在计算完语义相似度分数后,均会被 softmax 归一化,之后计算得到的梯度如下所示:
$$ \nabla_\theta l(q, d) = \begin{cases} (s_n(q, d) - 1) \nabla_\theta s_n(q, d) & \text{if } d \in \mathcal{D}^+ \ s_n(q, d) \nabla_\theta s_n(q, d) & \text{if } d \in \mathcal{D}^- \end{cases} $$
📝 通俗解释:这个公式描述了如何计算梯度。对于正例,目标是让分数接近1(减去1);对于负例,目标是让分数接近0(保持原值)。梯度的大小决定了模型参数更新的幅度。
注:$s_n(q, d)$ 表示经过 softmax 归一化后的语义相似度分数
致谢
感谢 AiGC 面试宝典提供的优质内容
(注:文中图片路径为 media/image1.png,请根据实际部署情况调整)