欢迎光临
我们一直在努力

【LLM进阶-RAG】4.稀疏向量检索算法

一、BM25稀疏向量检索算法

见混合检索算法章节

二、SPLADE稀疏向量检索算法

传统的稀疏检索(如 BM25)擅长精确匹配但缺乏语义理解;稠密检索(Dense Retrieval,如 DPR)擅长语义匹配但在精确词汇匹配(Exact Match)上表现不佳。SPLADE 的核心思想是:利用预训练语言模型(PLM)的 MLM(Masked Language Modeling)头,在庞大的词表空间(Vocabulary Space)中学习出一个稀疏的向量表示。 这使得它既具备语义扩展能力,又能完美接入传统的倒排索引框架。


a. 端到端架构:从输入到输出

假设我们的输入是一段文本 T = [t_1, t_2, …, t_n](可以是 Query 也可以是 Document),词表大小为 |V|(例如 BERT 的 30522)。

1. 上下文编码 (Contextual Encoding)

首先,文本通过一个预训练语言模型(如 BERT 或 DistilBERT)进行编码,得到每个 Token 的上下文表示(Hidden States):

h

i

=

BERT

(

T

)

i

h_i = \\text{BERT}(T)_i

hi=BERT(T)i

其中,

h

i

R

d

h_i \\in \\mathbb{R}^d

hiRd

是第 i 个 Token 的稠密向量(例如 768 维)。

2. 词表空间投影 (MLM Projection)

接着,SPLADE 复用了预训练模型本身的 MLM Head(带权重的线性层),将每个 Token 的

d

d

d 维稠密向量投影到

V

|V|

V 维的词表空间中,计算出它在整个词表上的激活值(Logits):

l

o

g

i

t

s

i

=

W

M

L

M

transform

(

h

i

)

+

b

M

L

M

logits_i = W_{MLM} \\cdot \\text{transform}(h_i) + b_{MLM}

logitsi=WMLMtransform(hi)+bMLM

这里得到的

l

o

g

i

t

s

i

R

V

logits_i \\in \\mathbb{R}^{|V|}

logitsiRV

这一步是**语义扩展(Expansion)**的关键:即使某个词不在原文本中,如果它在语义上高度相关,MLM 也会在对应维度给出较高的分数。

3. 激活与平滑 (Activation & Smoothing)

为了得到非负且平滑的词汇权重,SPLADE 对 Logits 进行了两次变换:

w

i

j

=

log

(

1

+

ReLU

(

l

o

g

i

t

s

i

j

)

)

w_{ij} = \\log(1 + \\text{ReLU}(logits_{ij}))

wij=log(1+ReLU(logitsij))

  • ReLU: 过滤掉所有负面分数,保证权重的非负性,并初步引入稀疏性。
  • log(1 + x)变换: 防止某些词的权重过大(长尾效应),起到了对 Term Weight 进行平滑的作用(类似于 BM25 中的词频饱和机制)。

此时,w_{ij} 表示第 i 个输入 Token 赋予词表中第 j 个词的权重。

4. 序列池化 (Sequence Pooling)

由于一篇文档或 Query 包含多个 Token,我们需要将所有 Token 维度的预测聚合为一个单一的文档/Query 级向量。SPLADE 使用了 Max-Pooling:

w

j

=

max

i

[

1

,

n

]

w

i

j

w_j = \\max_{i \\in [1, n]} w_{ij}

wj=i[1,n]maxwij

  • 面试考点:为什么用 Max 而不是 Mean 或 Sum?

    使用 Max Pooling 意味着只要文本中任意一个 Token 强烈暗示了词表中的某个词 j,这个词就会成为这篇文档的特征。这完美契合了信息检索中“存在即特征”的思想。

最终,我们得到了一个稀疏向量

w

R

V

w \\in \\mathbb{R}^{|V|}

wRV

其中 w_j 代表词汇 j 在该文本中的重要性权重。


b.检索与打分 (Scoring)

在推理阶段,Query 和 Document 都被编码为

V

|V|

V 维的稀疏向量

w

(

q

)

w^{(q)}

w(q)

w

(

d

)

w^{(d)}

w(d)。它们的相似度打分就是简单的点积(Dot Product):

S

(

q

,

d

)

=

w

(

q

)

w

(

d

)

=

j

V

w

j

(

q

)

w

j

(

d

)

S(q, d) = w{(q)} \\cdot w{(d)} = \\sum_{j \\in V} w_j{(q)} w_j{(d)}

S(q,d)=w(q)w(d)=jVwj(q)wj(d)

因为这两个向量极其稀疏,这个点积计算可以通过**倒排索引(Inverted Index)**高效完成,并且可以直接使用 WAND 等动态剪枝算法加速。


c.损失函数与训练 (Training & Loss) – 面试核心重点!

让模型输出 |V| 维向量容易,但让它输出极度稀疏且效果好的向量很难。SPLADE 的总损失函数由两部分组成:

L

=

L

R

a

n

k

+

λ

L

R

e

g

\\mathcal{L} = \\mathcal{L}_{Rank} + \\lambda \\mathcal{L}_{Reg}

L=LRank+λLReg

1. 排序损失 (

L

_

R

a

n

k

\\mathcal{L}\\_{Rank}

L_Rank)

早期的 SPLADE 使用 In-batch Negatives 的 InfoNCE Loss。但在后期的成熟版本(如 SPLADE-max, SPLADE v2)中,标准做法是使用 知识蒸馏(Knowledge Distillation),特别是 Margin MSE Loss。

通常我们会用一个强大的 Cross-Encoder(如 BERT-base 交叉编码器)作为 Teacher 提供打分

S

_

t

e

a

c

h

e

r

S\\_{teacher}

S_teacher,SPLADE 作为 Student 逼近这个分数差:

L

R

a

n

k

=

1

N

(

q

,

d

+

,

d

)

(

(

S

s

t

u

d

e

n

t

(

q

,

d

+

)

S

s

t

u

d

e

n

t

(

q

,

d

)

)

(

S

t

e

a

c

h

e

r

(

q

,

d

+

)

S

t

e

a

c

h

e

r

(

q

,

d

)

)

)

2

\\mathcal{L}_{Rank} = \\frac{1}{N} \\sum_{(q, d+, d-)} \\left( (S_{student}(q, d+) – S_{student}(q, d-)) – (S_{teacher}(q, d+) – S_{teacher}(q, d-)) \\right)^2

LRank=N1(q,d+,d)((Sstudent(q,d+)Sstudent(q,d))(Steacher(q,d+)Steacher(q,d)))2

2. 稀疏正则化损失 (

L

_

R

e

g

\\mathcal{L}\\_{Reg}

L_Reg)

如果没有正则化,模型倾向于激活词表中的大量词以获取更高的排序精度,这会导致倒排索引变得庞大无比,检索延迟极高。

SPLADE 创造性地引入了 FLOPs 正则化(或者

L

_

1

L\\_1

L_1 正则化,但 FLOPs 效果更好,主要控制索引大小和检索时间)。

FLOPs 正则化的思想是最小化倒排索引预估的计算量。对于一个 Batch(大小为

B

B

B),词表上的平均激活值为:

a

ˉ

j

=

1

B

k

=

1

B

w

j

(

k

)

\\bar{a}_j = \\frac{1}{B} \\sum_{k=1}B w_j{(k)}

aˉj=B1k=1Bwj(k)

FLOPs 损失定义为这些平均激活值的平方和:

L

F

L

O

P

S

=

j

=

1

V

a

ˉ

j

2

\\mathcal{L}_{FLOPS} = \\sum_{j=1}{|V|} \\bar{a}_j^2

LFLOPS=j=1Vaˉj2

  • 面试考点:为什么 FLOPs 正则化有效?

    a

    ˉ

    _

    j

    2

    \\bar{a}\\_j^2

    aˉ_j2 促使模型不要让所有的词都在所有样本里被激活。它惩罚了那些在 Batch 内被频繁激活的“无效高频词”,从而强制模型学习到真正有区分度的稀疏特征。

    实际训练中,Query 和 Document 通常使用不同的正则化系数(

    λ

    _

    q

    \\lambda\\_q

    λ_q

    λ

    _

    d

    \\lambda\\_d

    λ_d),因为 Document 我们希望它多做一点 Expansion,而 Query 我们希望它尽可能稀疏以保证检索速度。


d.总结:面试时如何凸显亮点?

如果在面试中被问到“对比一下 Dense Retrieval 和 SPLADE”,你可以从这三个维度降维打击:

  • 精确匹配 (Exact Match) 的保留:稠密检索容易遇到 “Vocabulary Mismatch” 或对专有名词、数字不敏感的问题。SPLADE 由于在词表空间工作,天然保留了 Token 级别的精确匹配能力。
  • 显式的语义扩展 (Explicit Semantic Expansion):传统的 Query Expansion(如 RM3)依赖伪相关反馈,而 SPLADE 利用预训练模型内部蕴含的知识,直接在 MLM 预测中实现了上下文化的词汇扩展。
  • 可解释性 (Interpretability) 与基建复用:DPR 的 768 维向量是黑盒的;而 SPLADE 的输出是具体的词汇权重(比如模型给 “Apple” 和 “iPhone” 分配了高权重),肉眼可读。并且它可以完美复用公司已有的 Elasticsearch/Lucene 倒排索引基建,无需引入昂贵的向量数据库(如 Milvus/Faiss)。

  • e.加深理解的QA问答

    1. MLM head 是不是就是 lm_head 层(一般与 embedding 共享权重)?

    答案是:对的,本质上是同一回事,而且确实共享权重(Weight Tying)。

    • 结构解析:在 Hugging Face 的源码(比如 BertForMaskedLM)中,这个部分通常被称为 cls.predictions。它不仅仅是一个简单的线性层,通常包含:一个全连接层(Dense)

      \\rightarrow

      激活函数(GELU)

      \\rightarrow

      LayerNorm

      \\rightarrow

      最终的预测层(Decoder/lm_head)。

    • 权重共享(Weight Tying):你提到的这一点非常内行。为了减少模型参数量并提升泛化能力,BERT 最终输出到词表大小(例如

      V

      =

      30522

      |V|=30522

      V=30522)的那个线性层的权重矩阵

      W

      _

      d

      e

      c

      o

      d

      e

      r

      R

      v

      o

      c

      a

      b

      _

      s

      i

      z

      e

      ×

      h

      i

      d

      d

      e

      n

      _

      s

      i

      z

      e

      W\\_{decoder} \\in \\mathbb{R}^{vocab\\\\\\_size \\times hidden\\\\\\_size}

      W_decoderRvocab_size×hidden_size,是直接与输入层的词嵌入矩阵(Word Embeddings)共享的(互为转置)。

    • 在 SPLADE 中,我们正是复用了这个预训练好的、具有强大语义映射能力的 lm_head 来生成稀疏向量。
    2. logits 为啥会有负数?

    答案是:因为 logits 是线性层的“原始输出”,没有经过任何将其限制为正数的数学变换(如 Softmax)。

    • 数学推导:最终的预测层是一个线性变换:

      l

      o

      g

      i

      t

      s

      =

      W

      h

      +

      b

      logits = W \\cdot h + b

      logits=Wh+b

      其中,

      h

      h

      h 是经过上一层 LayerNorm 的隐藏状态,包含正数和负数;

      W

      W

      W 是共享的 embedding 权重,也包含正数和负数;偏置

      b

      b

      b 同样可以是负数。它们的点积和加和,自然可能落在

      (

      ,

      +

      )

      (-\\infty, +\\infty)

      (,+) 的任何区间。

    • Logits vs. 概率:在正常的 BERT 预训练中,我们会把这个含有负数的 logits 送进 Softmax 函数:

      p

      _

      i

      =

      e

      l

      o

      g

      i

      t

      s

      _

      i

      e

      l

      o

      g

      i

      t

      s

      _

      j

      p\\_i = \\frac{e^{logits\\_i}}{\\sum e^{logits\\_j}}

      p_i=elogits_jelogits_i。因为有了

      e

      x

      e^x

      ex(指数函数的值域始终大于 0),所以转化后的概率都是正数。

    • SPLADE 的巧妙之处:SPLADE 没有使用 Softmax。如果 logits 为负数,说明模型认为这个词在当前上下文中非常不相关。SPLADE 直接用 ReLU 激活函数(

      max

      (

      0

      ,

      x

      )

      \\max(0, x)

      max(0,x))把所有负数的 logits 全部“一刀切”变成了 0。这就是 SPLADE 向量极其稀疏的根本来源!

    3. SPLADE 有可能出现某个“不存在于 query/chunk 的词”的概率权重不为 0 的情况吗?

    答案是:绝对会,而且这是 SPLADE 算法的“灵魂”和最大卖点!

    这个现象叫做 显式语义扩展(Explicit Semantic Expansion)。

    • 为什么会发生? 因为输入的文本经过 Transformer 层后,得到的隐藏状态

      h

      h

      h 已经融合了上下文的深层语义。当你用这个

      h

      h

      h 去乘以上面提到的 lm_head(也就是词嵌入矩阵)时,模型实际上是在计算当前语义与词表中所有词的“相似度”。

    • 举个生动的例子:

      假设你的输入 Chunk 中有这样一句话:“苹果公司今天发布了新的智能手机”。

      里面并没有出现“iPhone”或“iOS”这两个词。但是,因为预训练模型的知识,这句话对应的隐藏状态

      h

      h

      h 向量,在空间上会和“iPhone”这个词的 Embedding 非常接近。

      因此,当通过 lm_head 计算时,“iPhone”这个词对应的 logits 会非常高(远大于 0)。经过 ReLU 后,它的权重依然不为 0。

    • 最终效果:在输出的

      V

      |V|

      V 维稀疏向量中,“iPhone” 这个维度的权重是非零的。这意味着,即使用户搜 “iPhone”,也能完美召回这篇只写了“智能手机”的文档。它完美解决了传统 BM25 的“词汇不匹配(Vocabulary Mismatch)”问题。

    4.SPLADE既然结合了语义与稀疏检索(关键词)的优势,是不是应用时不需要结合dense检索了

    在追求极致效果的大厂线上系统中,SPLADE 并不能完全替代 Dense(稠密)检索,两者结合(即混合检索 Hybrid Retrieval)依然是目前的业界天花板。

    1. 致命缺陷:词袋模型(Bag-of-Words)丢失了“语序和句法”信息

    无论 SPLADE 的语义扩展有多强,你回想一下它的最后一步操作——序列池化 (Max-Pooling)。

    SPLADE 是把所有 Token 对同一个词的激活值取了最大值(

    w

    j

    =

    m

    a

    x

    (

    w

    i

    j

    )

    w_j = max(w_ij)

    wj=max(wij)),这意味着它完全丢掉了词序和句法结构。

    • 举个极端的例子:

      • Query A: “Dog bites man”(狗咬人)
      • Query B: “Man bites dog”(人咬狗)
    • SPLADE 的表现: 这两句话经过 SPLADE 编码后,生成的稀疏向量几乎是一模一样的!因为它们包含的词相同,扩展出来的词也大同小异,经过 Max-Pooling 后,向量在对应维度上的权重是一样的。

    • Dense 的表现: 稠密检索(如 DPR、BGE)是将整个句子的信息压缩到一个 [CLS] 向量中。基于自注意力机制(Self-Attention),模型能深刻理解主谓宾的区别,“狗咬人”和“人咬狗”的 Dense 向量在空间中会相差非常远。

    结论: Dense 擅长捕捉全局(Global)的句式和深层逻辑,而 SPLADE 本质上依然是个高级的词汇匹配模型,擅长局部(Local)的特征。

    2. 连续空间 vs. 离散词表空间 的表达能力差异
    • SPLADE 被限制在词表内: SPLADE 的输出维度严格等于词表大小(例如 30,522)。如果遇到极其抽象的概念,或者模型根本无法用现有词表里的单个词去完美映射的复杂意图,SPLADE 的表达能力就会受限。
    • Dense 的无限可能: Dense 向量生活在一个连续的实数空间(如 768 维)。在这个连续空间里,向量可以表达极其细腻、抽象、甚至跨语言(Cross-lingual)的语义过渡。例如,将一段代码和一句自然语言映射到同一个连续空间,这是 Dense 擅长的,而 SPLADE 很难做到跨模态/跨语言的严格对齐。
    3. 语义扩展(Expansion)带来的“噪音”问题

    SPLADE 强大的“脑补”能力有时是一把双刃剑。

    • 当 Query 非常简短或存在歧义时,SPLADE 的 MLM 头可能会过度扩展出一些虽然在训练语料库中经常共现、但在当前语境下完全无关的词(即 Query Drift,查询漂移)。
    • 结合 Dense 检索可以起到**“锚定(Anchor)”**的作用,因为 Dense 是从整体语义出发去匹配的,可以有效压制 SPLADE 因过度扩展而召回的噪音文档。

    赞(0)
    未经允许不得转载:171主机测评 » 【LLM进阶-RAG】4.稀疏向量检索算法
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址