K-NRM — 用核函数把交互矩阵变成可微排序信号
是什么
K-NRM(Kernel-based Neural Ranking Model)是 BERT 之前最强的一类神经搜索排序器。日常类比:图书管理员判断一本书和你查询的相关度,先把你的关键词和书里出现的词两两比一遍,再用一组”软抽屉”统计有多少对词靠得很近、有多少对意思接近、有多少对完全无关,最后把这些软统计量交给一个会打分的小函数。
输入是查询 q(几个词)和候选文档 d(几十到几百个词)。K-NRM 给每个 (q, d) 算出一个分数,然后按分数排所有候选文档。
它的核心动作只有三步:
- 算 |q|×|d| 的余弦相似度矩阵 M(每对词一个数)
- 用 K 个 RBF 核把每行的 M 值”软计数”成 K 维向量
- 对每个查询词的 K 维向量取 log 求和,再线性投影成一个标量分数
整条链路对 word embedding 可微,可以端到端 BP。
为什么重要
不理解 K-NRM 这条线,下面这些事都解释不通:
- 为什么 2017 年前的神经 ranker(DRMM 2016)用直方图,2017 年突然换成核——直方图桶边界会断梯度
- 为什么 2018 年 Conv-KNRM 一出来横扫各种 benchmark,但 2019 年 BERT 一来全部被收编
- 为什么 ColBERT 这种 late interaction 路线说自己”继承 K-NRM 衣钵”——它们都是先两两比再聚合
- 为什么搜狗、必应这些工业搜索都用过 K-NRM 当重排层,召回还是 BM25
核心要点
第一步:交互矩阵 M
每个查询词 qi 和每个文档词 dj 都先查表拿到 word embedding,再算余弦相似度。结果是一张 |q| 行 |d| 列的”相似度地图”。完全相同的词那格是 1,毫不相干的词那格接近 0。
第二步:核池化(论文最关键的一招)
如果直接把 M 喂给神经网络会怎样?太大了,而且文档长度变化时形状不一。所以要把每行(一个查询词对所有文档词的相似度)压缩成定长向量。
DRMM 2016 用的办法是直方图——把 [-1, 1] 切成 30 个桶,数每个桶里有几个相似度。问题是当某个相似度从 0.79 变成 0.81 跨过桶边界时,整个梯度断掉了,embedding 学不动。
K-NRM 换了一招:“软抽屉”。每个 RBF 核就是一个抽屉,中心 mu,宽度 sigma。第 k 个抽屉对查询词 i 的软计数是:
K_k(M_i) = sum_j exp(- (M_ij - mu_k)^2 / (2 sigma_k^2))读作:把所有 M_ij 与 mu_k 的距离的高斯权重加起来。距离越近权重越大,但总是大于零——这意味着 M_ij 微动一点,软计数也跟着微动一点,梯度永远不断。
第三步:对数 + 线性
每个查询词得到 K 维软计数向量,对每维取 log,然后把所有查询词的 K 维向量按维加起来变成一个 K 维”文档级特征”,最后过一层 tanh + 线性投影成标量分数。
11 个核怎么选
- mu=1.0、sigma=0.001:这个抽屉只数完全匹配的词(精确 TF)
- mu=0.9、0.7、0.5、…、-0.9(步长 0.2)、sigma=0.1:分别数高度相似、中度相似、不太相似的词
一共 11 个核,11 维特征。
实践案例
案例 1:一个具体的查询
查询是 q = [“机票”, “便宜”],文档片段 d = [“低价”, “机票”, “促销”]。
- M 是 2×3 矩阵
- M[机票, 机票] ≈ 1.0(完全匹配)
- M[机票, 低价] ≈ 0.3(不太相似)
- M[便宜, 低价] ≈ 0.85(近义词,靠 word2vec 学到)
mu=1.0 那个抽屉对”机票”那行数到一颗珠子(精确匹配那对);mu=0.9 抽屉对”便宜”那行数到一颗珠子(“便宜”-“低价”那对)。这两个软计数最后体现在分数里,让这个文档比纯关键词重叠的版本得分更高。
案例 2:从可微回到 embedding
训练时用 pairwise hinge:相关文档的分数应当比不相关文档高至少一个 margin。损失对核值可微 → 对 M 可微 → 对 embedding 可微。embedding 因此学到了”对这个查询场景,‘便宜’和’低价’应该靠得更近”。
这就是 K-NRM 的关键卖点:词向量从通用预训练向 IR 任务专用调整。
案例 3:和 BM25 一起用
直接把 K-NRM 用在百万文档库上做检索是不现实的——|q|×|d| 矩阵每个文档都要算。工程上一律先用 BM25 / Anserini 召回 top 100,再用 K-NRM 重排。这种”召回-精排”两段架构后来被 BERT ranker 沿用至今。
案例 4:为什么 11 个核就够
直觉上你可能会想”用 50 个核覆盖更细的相似度区间一定更好”。论文做了消融实验:
- 只用 mu=1.0 那个精确匹配核 → 退化成 TF-IDF 思路,输给 BM25
- 加上 mu=0.7、0.5 几个核 → NDCG 立刻上来
- 11 个核已经接近饱和,再加更多边际收益接近零
原因是 word embedding 的相似度分布本身就集中在几个段:完全匹配、近义、弱相关、几乎无关。11 个核刚好把这几段各覆盖一两个抽屉。
踩过的坑
- 核中心 mu 不学:论文里的 11 个 mu 是手工设的。后续工作(如 Conv-KNRM)让 mu 也可学,效果更好但训练更慢
- 点击日志监督有噪:搜狗 95M 查询的弱监督让高频查询过拟合,长尾查询 NDCG 涨幅小很多
- 零样本场景反而输 BM25:没有点击日志或人工标注,K-NRM 的 embedding 调不动,靠的就是预训练词向量,相比 BM25 没优势
- 长文档内存爆炸:|q|×|d| 矩阵随文档长度线性增长,超长文档(论文、判决书)必须先截断或分段
适用 vs 不适用场景
适用:
- Web/电商/学术搜索的重排阶段,候选集 < 1000
- 有点击日志或人工相关度标注当监督信号
- 查询词和文档都偏短(几十词以内)
不适用:
- 海量召回阶段——用倒排索引 + BM25
- 没有任何监督信号的场景——K-NRM 比 BM25 差
- 需要 cross-attention 的复杂语义任务——直接上 BERT ranker
- 超长文档检索——matrix 内存撑不住
历史小故事(可跳过)
- 2013 年:微软 DSSM 把查询和文档各自压成向量,算最终余弦——双塔路线起点
- 2016 年:DRMM 提出交互矩阵 + 直方图,是第一个真正”看到”词级匹配的神经 ranker,但桶边界断梯度
- 2017 年:Xiong 当时是 CMU 博士生(Callan 组),把直方图换成 RBF 核解决了可微问题,论文在 SIGIR 2017 上发表后被广泛引用,是 pre-BERT 时代 IR 神经化路线的代表作
- 2018 年:Conv-KNRM 在 K-NRM 前面加 n-gram CNN 学短语向量,进一步提升
- 2019 年之后:BERT ranker 出来横扫整条路线,K-NRM 被收编进 ColBERT 等 late interaction 工作的思想血脉里
学到什么
- 可微 vs 不可微是神经 IR 的胜负手:DRMM 直方图 → K-NRM 核池化是一次思想跃迁
- 召回-精排两段式架构由 K-NRM 这一代固化下来:BM25 召回,神经模型精排
- 弱监督 + 大点击日志第一次在 IR 里跑通,后来被 ColBERT / DPR 等继续用
- embedding 从通用走向任务专用——K-NRM 让 word2vec 的”便宜”和”低价”学得更近
延伸阅读
- 论文 PDF:K-NRM SIGIR 2017
- 代码:github.com/AdeDZY/K-NRM(作者官方实现)
- Conv-KNRM 续作:Convolutional Neural Networks for Soft-Matching N-Grams in Ad-hoc Search, WSDM 2018
关联
- drmm-2016 —— 交互矩阵 + 直方图,K-NRM 的直接对手
- dssm-2013 —— 双塔/向量路线起点,K-NRM 走的是另一条交互路线
- anserini-2017 —— Lucene 上的 BM25 baseline 工具,K-NRM 的”召回阶段”伙伴
- bm25-okapi —— 词袋时代检索的天花板,K-NRM 的对照组
- colbert-v2 —— 后来 late interaction 路线的代表,思想血脉来自 K-NRM
- lambdarank-2006 —— pairwise 排序学习祖先,K-NRM 训练目标的来源
- ranknet-2005 —— pairwise 神经排序最早期,K-NRM 训练 loss 的远亲
反向链接
- anserini-2017 —— Anserini — 把工业搜索引擎 Lucene 改造成学术 IR 实验台
- colbert-2020 —— ColBERT — 让 BERT 检索既准又能扛大规模
- colbert-v2 —— ColBERTv2 — 让向量检索既精又能扛百万文档
- drmm-2016 —— DRMM — 检索里的匹配是相关性不是语义相似
- lambdarank-2006 —— LambdaRank — 跳过定义损失函数,直接把梯度写出来
- ranknet-2005 —— RankNet — 让搜索引擎学会比较两个结果谁更好