想象一家大厂要招一名算法工程师,广告一挂出去,邮箱瞬间涌入一万份简历。HR 不可能把每一份都精读——他们先让系统按关键词粗筛,筛剩两百份;再由资深工程师对照岗位说明书逐条精读,挑出十位候选人进入终面。搜索系统面对海量文档时,用的也是这套“先海选、再终面”的漏斗策略,而坐在最终面试官位置上的,就是本教程的主角——Cross-encoder(交叉编码器)。 为什么需要这位“面试官”?因为混合搜索(稠密向量 + 稀疏关键词)虽然能快速召回一批候选,但召回讲究“宁多勿漏”,里面混着大量凑数文档。Cross-encoder 的职责,是把查询和候选文档“摊在同一张桌子上”逐字比对,给出最精准的相关性分数,把真正该排在前面的内容顶上去。当然,精准是有代价的:它比初筛模型慢得多,所以只配负责一小撮候选。本教程还会讲到,如何用知识蒸馏,把这位“老面试官”的经验传授给“新助理”,让它又快又准。

岗位关联

搜索、推荐、RAG 系统的排序质量天花板由精排模型决定。掌握 Cross-encoder 及其蒸馏部署,是晋升搜索算法工程师、打造高精度检索管线的必备技能。

目录
  1. 可视化演示
  2. 两阶段检索:先海选,再终面
  3. 双塔粗排:两个 HR 各看各的
  4. 交叉精排:面试官把两份材料摊开看
  5. 算力代价:精读只能留给少数人
  6. 知识蒸馏:老面试官带新助理
  7. 工程落地:把流程跑起来
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示搜索精排三大核心:Cross-encoder联合编码(查询文档拼接BERT联合编码CLS输出分数token级交互注意力)、Bi-encoder双塔对比(查询文档独立编码向量点积相似度预计算索引快速检索)、知识蒸馏迁移(CE教师生成soft label Bi-encoder学生KL散度学习10倍加速),三模式切换理解精排全貌。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+C1+'

两阶段检索:先海选,再终面

招聘从不直接让 CEO 面一万个求职者。第一步是简历系统按关键词筛一遍,第二步 HR 精读,第三步业务主管面试——每过一关,人数减少一截,精细度提升一档。搜索系统同理:面对百万级文档库,任何“精细判断”都不可能全量执行,只能分成“召回”和“精排”两个阶段,让每一层只做一件事。

召回阶段追求“快而全”,用轻量模型从全库捞出可能相关的几百篇;精排阶段追求“准而精”,用重量模型在这几百篇上重新排序。在混合搜索场景下,两个召回通道——稀疏的关键词匹配与稠密的语义向量检索——各自吐出一批候选,合并去重后统一送进精排。换句话说,精排是混合搜索所有通道的“汇合点”,也是质量把关的总闸门,缺了它,两路召回的名单只能靠运气排先后。

用集合语言看,设文档库为 $\mathcal{D}$,稠密召回结果为 $\mathcal{R}_{\text{dense}}$,稀疏召回结果为 $\mathcal{R}_{\text{sparse}}$,则精排输入是两者的并集 $\mathcal{C}$。精排模型对 $\mathcal{C}$ 中每篇文档输出相关性分数,最终取前 $N$ 名返回用户。这里的设计哲学叫“漏斗原则”:每一层只做一件事,用最合适的工具完成最合适的任务,避免让昂贵的模型处理海量输入。

就像招聘漏斗从“一万份简历 → 一百份进 HR → 十份进终面”,检索漏斗从“百万文档 → 数百候选 → 十条结果”。Cross-encoder 就坐在“最终面试官”的位置上,它不必见所有人,但见到的每个人都要看得足够细。

∣C∣=∣Rdense∪Rsparse∣,N≪∣C∣≪∣D∣\displaystyle |\mathcal{C}| = |\mathcal{R}_{\text{dense}} \cup \mathcal{R}_{\text{sparse}}|,\quad N \ll |\mathcal{C}| \ll |\mathcal{D}|
  1. 候选集合并$\mathcal{R}_{\text{dense}} \cup \mathcal{R}_{\text{sparse}}$ 表示取两个召回通道的并集,合并去重,避免漏掉任何一个通道单独发现的优质文档,就像把两位猎头的推荐名单合成一张。
  2. 漏斗不等式$N \ll |\mathcal{C}| \ll |\mathcal{D}|$ 刻画了数量逐层递减的方向——从全库到候选再到最终结果,每一层数量少一个量级,判断精细度高一个档次。

双塔粗排:两个 HR 各看各的

初筛环节里,HR 的做法是给简历和岗位要求各贴一张“标签卡”:简历上写“3 年 Python、做过推荐系统”,岗位卡上写“需要 Python、机器学习经验”。两张卡各贴各的,最后比对卡片重合度就能快速打分。双塔(Bi-encoder)模型就是这个思路:查询走查询编码器,文档走文档编码器,两路独立、互不打扰。

因为两者互不依赖,文档向量可以在离线状态提前算好,存入向量索引(如 Faiss、ANN 索引)。在线查询时,只需编码一条查询,再与海量预计算文档向量做内积或余弦相似度,就能极速找出 Top-K。这正是它被用在召回阶段的原因——快,而且能扛住全库规模的扫描。

相似度计算的本质是向量内积:两个向量越“同向”,内积越大,语义越接近。关键在于“独立”二字——查询向量和文档向量各自只看到自己的文本,从来没有“对过眼神”,所以它捕捉不到“查询里的苹果指手机而非水果”这类交互信息。这是它快的原因,也是它不够准的原因。

双塔就像“标签卡比对”——它适合把一万份简历飞快筛到一百份,但“三年 Python”和“要求 Python 3 年以上”算不算匹配,这种需要上下文对照的精细判断,它做不了。这就要请出真正的“面试官”。

s(q,d)=eq⊤ed,eq=Encq(q), ed=Encd(d)\displaystyle s(q, d) = \mathbf{e}_q^{\top} \mathbf{e}_d, \qquad \mathbf{e}_q = \text{Enc}_q(q),\ \mathbf{e}_d = \text{Enc}_d(d)
  1. 独立编码$\mathbf{e}_q = \text{Enc}_q(q)$ 表示查询被编码成向量,文档同理;两路编码互不影响,因此文档向量可以离线预计算,在线只算查询这一路。
  2. 内积打分$s(q, d) = \mathbf{e}_q^{\top} \mathbf{e}_d$ 计算两个向量的内积作为相关性分数,分数越高代表越相关,相当于比对两张标签卡的重合度。
  3. 交互缺失$\mathbf{e}_q$ 与 $\mathbf{e}_d$ 来自不同编码器,没有相互对齐,遇到字面不同但语义相近、或字面相同但语义不同的情况,容易误判。

交叉精排:面试官把两份材料摊开看

资深面试官不会只看标签卡。他会把简历和岗位说明并排摊在桌上,逐条对照:“对方要求熟悉 Transformer 原理,候选人实习期恰好用 BERT 做过文本分类,这条强相关;对方要求有大规模稀疏特征经验,候选人只写过小模型,这条不匹配。”两份材料在同一个大脑里发生了深度交互。Cross-encoder 干的就是这件事。

Cross-encoder 把查询和文档拼接成一条长序列,中间用 [SEP] 分隔,前面加 [CLS] 标记,整体送入一个 Transformer 编码器。序列里的每个 token 都能通过自注意力“看到”另一篇文档的 token——查询里的“苹果”能看到文档里的“手机”,也能看到文档里的“水果”,模型据此判断语境。最后取 [CLS] 位置的输出向量,过一个线性分类头得到相关性分数。

因为查询和文档共享同一套注意力计算,Cross-encoder 能建模细粒度的交互特征:精确匹配、语义改写、词序依赖、否定关系等,排序精度远高于双塔。代价是它必须“逐对”推理——每来一条查询,都要对每篇候选文档重新编码一遍,文档向量无法提前算好。这是精度与效率的根本矛盾,也是精排只敢在少量候选上使用的原因。

如果说双塔是“背靠背各写各的总结”,Cross-encoder 就是“面对面逐条对质”。它的判断依据来自两份材料的每一次相互注视,所以准得惊人;但也正因为每次都要重新对视,它没法提前备课。

s(q,d)=W⊤h[CLS]+b,h=Transformer([CLS;q1,…,qm;SEP;d1,…,dn])\displaystyle s(q, d) = \mathbf{W}^{\top} \mathbf{h}_{[\text{CLS}]} + b, \qquad \mathbf{h} = \text{Transformer}\left([\text{CLS}; q_1, \dots, q_m; \text{SEP}; d_1, \dots, d_n]\right)
  1. 拼接序列$[\text{CLS}; q_1, \dots, q_m; \text{SEP}; d_1, \dots, d_n]$ 把查询的 $m$ 个 token 与文档的 $n$ 个 token 拼成一条输入序列,用 [SEP] 隔开两段文本,就像面试官把简历和岗位说明摆在同一张桌上。
  2. 联合编码$\mathbf{h} = \text{Transformer}(\cdot)$ 表示整条序列经过多层自注意力,查询与文档的 token 在每一层都相互“对看”,实现深度交互。
  3. 分类头打分$s = \mathbf{W}^{\top} \mathbf{h}_{[\text{CLS}]} + b$ 取出 [CLS] 位置的汇聚向量 $\mathbf{h}_{[\text{CLS}]}$,经线性变换输出一个标量相关分数。

算力代价:精读只能留给少数人

让资深面试官精读一万份简历,公司会破产——他的时间成本太高。招聘流程的选择是把精读留给初筛后的几十人。Cross-encoder 同理:它是检索系统里最贵的一环,必须用“候选集截断”保护它,让它只审阅值得审阅的文档。

从算力上看,双塔的文档编码可以离线完成,在线只需一次查询编码加一次近邻检索;Cross-encoder 则要在线、逐对、完整地跑一遍大模型。设候选集大小为 $K$,查询长度 $m$,文档长度 $n$,Transformer 自注意力复杂度是序列长度的平方,因此精排总开销近似为 $K$ 乘以 $(m+n)^2$。候选越多、文档越长,开销增长越陡峭。

工程上通常把候选集控制在几十到几百的量级(如 50~200 篇)。太小会漏掉真正相关的文档,太大则精排延迟不可接受。这一取舍与混合搜索直接相关:两个召回通道本身已经做了一轮质量筛选,精排只需在合并后的候选上做精细排序,因此 $K$ 不必很大。实践中还会配合 batch 推理、量化、蒸馏等手段进一步压低单对推理成本。

公司给“精读”环节设定名额上限,本质上是把钱花在刀刃上;精排阶段控制候选数,本质上是把算力花在最可能相关的文档上。Cross-encoder 的“贵”不是缺点,而是它精准的代价——关键是别让它干超出能力范围的活儿。

Costcross=K×O((m+n)2),K∈[50,200]\displaystyle \text{Cost}_{\text{cross}} = K \times O\left((m+n)^2\right), \qquad K \in [50, 200]
  1. 候选数量因子$K$ 是送入精排的文档数,开销与之成正比,所以必须控制 $K$ 的量级——终面名额是有限的。
  2. 序列长度平方$O((m+n)^2)$ 是自注意力的复杂度,查询与文档拼接后长度 $m+n$ 越长,每对计算越昂贵。
  3. 经验区间$K \in [50, 200]$ 是工业界的常见选择,兼顾召回完整性与延迟预算。

知识蒸馏:老面试官带新助理

公司发现,把资深面试官请来精读每份简历太贵了,于是让他带一个助理:面试官每看完一份简历,就在旁边写下“这份 8 分、那份 6 分、它们差距不大”。助理不直接抄结论,而是学习面试官打分的“尺度感”——什么样的情况加两分,什么样的瑕疵扣三分。几个月后,助理的打分已经相当接近面试官,而助理的时薪只有面试官的一成。这就是知识蒸馏。

在检索里,教师模型是 Cross-encoder(准但慢),学生模型是 Bi-encoder(快但糙)。蒸馏的做法是:用教师对同一批候选文档打分,得到一个软化的分数分布——注意,不是只告诉学生“哪篇相关、哪篇不相关”的硬标签,而是“这篇 0.9、那篇 0.7、另一篇 0.2”的连续信息。这些软标签携带了教师对文档间相对差异的细腻判断,比硬标签信息量大得多,学生拟合软标签,就能学到超出自身结构能力的排序知识。

蒸馏损失常用 KL 散度:让学生模型输出的分数分布去逼近教师模型的分数分布。温度参数 $\tau$ 用来软化分布——温度越高,分布越平滑,越能暴露“差之毫厘”的排序细节。训练完成后,学生模型部署在召回或粗排位置,继承双塔“可预计算、在线快”的优势,同时排序质量大幅接近教师。这就是“用离线算力换在线效率”的经典套路。

蒸馏的本质是“经验迁移”——老面试官无法永远坐在面试桌前,但他把判断的尺度感写成了可学习的样例,让新助理继承了他的判断力。Cross-encoder 的能力也因此在双塔身上“还魂”,快与准不再是非此即彼。

piT=exp⁡(ziT/τ)∑j∈Cexp⁡(zjT/τ),LKD=∑i∈CpiTlog⁡piTpiS\displaystyle p_i^{T} = \frac{\exp(z_i^{T} / \tau)}{\sum_{j \in \mathcal{C}} \exp(z_j^{T} / \tau)}, \qquad \mathcal{L}_{\text{KD}} = \sum_{i \in \mathcal{C}} p_i^{T} \log \frac{p_i^{T}}{p_i^{S}}
  1. 温度软化$z_i^{T} / \tau$ 是教师对第 $i$ 篇候选的原始分数除以温度 $\tau$,温度越高,softmax 后的分布越平缓,细小差异被保留下来。
  2. 软概率分布$p_i^{T}$ 是 softmax 归一化后的教师软标签,表示“在所有候选中这篇的相对相关程度”,相当于面试官写下的连续评分表。
  3. KL 散度对齐$\sum_i p_i^{T} \log (p_i^{T} / p_i^{S})$ 衡量学生分布与教师分布的差距,训练就是最小化这个差距,让学生逐步靠近教师的判断。

工程落地:把流程跑起来

招聘流程再完美,也得有 SOP:简历怎么收、初筛谁来做、精读名额几个、结果如何复盘。检索系统的工程落地也一样——需要定候选规模、选部署方案、定评估指标、持续反馈迭代。Cross-encoder 再准,落不了地也是纸上谈兵。

典型的落地路径是:混合搜索两个通道召回 → 去重合并得到候选集 → 蒸馏后的学生模型或轻量精排先粗排 → Cross-encoder 对剩余候选精排 → 输出前 N 条。如果延迟预算紧张,可以完全用蒸馏学生替代教师;如果对精度要求极高(如法律、医疗检索),则保留教师,但严格限制候选集并用 GPU batch 推理摊薄成本。

评估排序质量时,业界常用 NDCG 等指标。NDCG 的核心思想:越相关的文档排得越靠前越好,且位置越靠后,收益衰减越厉害——就像面试官把“最匹配”的候选人放在 offer 名单第一位。精排上线后,还要通过线上点击率、转化率等反馈持续迭代:把用户点击的正例与未点击的负例加进训练集,定期重训教师,再重新蒸馏学生,形成“教师精进→学生跟进→线上验证”的闭环。

一套成熟的招聘 SOP,让不同水平的 HR 都能稳定产出合格候选人;一套跑通的精排工程链路,让不同规模的团队都能稳定提供高质量排序。Cross-encoder 是这条链路里最亮的环节,但它的光芒,要靠整条流水线来承载。

DCG@K=∑i=1K2reli−1log⁡2(i+1),NDCG@K=DCG@KIDCG@K\displaystyle \text{DCG@K} = \sum_{i=1}^{K} \frac{2^{rel_i} - 1}{\log_2(i + 1)}, \qquad \text{NDCG@K} = \frac{\text{DCG@K}}{\text{IDCG@K}}
  1. 位置折扣$\log_2(i + 1)$ 放在分母,位置 $i$ 越靠后,折扣越大,即排在后面的相关文档对得分的贡献被削弱。
  2. 相关度增益$2^{rel_i} - 1$ 对相关度等级做指数放大,高度相关的文档带来的增益远超弱相关文档。
  3. 归一化$\text{NDCG} = \text{DCG} / \text{IDCG}$ 用理想排序的得分做分母,把指标归一化到 0~1,方便跨查询比较。

AI 应用场景

电商搜索相关性精排

阶段:排序与精排(Ranking / Reranking)

解决的问题:用户搜“苹果手机”,稀疏召回命中“苹果是水果”的商品页,稠密召回也可能混入“苹果汁”。传统人工规则无法穷举所有歧义词。Cross-encoder 把查询与商品标题拼接编码,能区分“苹果手机”语境下的相关性,把无关商品压到后排。

具体案例:某电商平台日均搜索 2 亿次,混合召回取 200 个候选后,用蒸馏后的精排模型对候选重排,搜索点击率提升 8%,“苹果水果”类商品在“苹果手机”查询下的曝光下降 70%。

企业知识库 RAG 问答

阶段:检索增强生成(RAG)中的检索精排

解决的问题:RAG 系统把召回的 Top-5 直接喂给大模型,若里面混有噪声文档,回答质量会明显崩塌。Cross-encoder 在文档送入大模型前重新排序,只保留最相关的 2~3 条,显著提升回答的准确率与一致性。

具体案例:某券商内部知识库接入 RAG,混合检索召回 50 条研报片段,经 Cross-encoder 精排后取前 3 条送入 LLM,答案引用准确率从 61% 提升到 89%。

Python 演示

python117 行
# -*- coding: utf-8 -*-"""模拟混合搜索精排流程: 双塔召回 -> Cross-encoder 精排 -> 知识蒸馏"""import mathimport random DOCS = [    "苹果手机新品发布会",    "苹果是一种常见水果",    "华为推出折叠屏新机",    "手机拍照哪家效果好",    "水果店苹果今日特价",    "智能手机市场分析报告",]QUERY = "苹果手机新品上市"  def _hash_str(s):    h = 0    for ch in s:        h = (h * 31 + ord(ch)) & 0x7FFFFFFF    return h  def _bigrams(text):    s = text.replace(" ", "")    return {s[i:i + 2] for i in range(len(s) - 1)}  def _sim_emb(text, seed):    """用 bigram 哈希模拟稠密向量, 保证结果确定可复现"""    rng = random.Random(seed)    vec = [0.0] * 8    for g in _bigrams(text):        r = random.Random(_hash_str(g))        for k in range(8):            vec[k] += r.uniform(-1, 1)    norm = math.sqrt(sum(v * v for v in vec)) + 1e-9    return [v / norm for v in vec]  def _dot(a, b):    return sum(x * y for x, y in zip(a, b))  class BiEncoder:    """双塔: 文档向量离线预计算, 查询向量在线计算, 内积召回 TopK"""     def __init__(self, docs):        self.doc_vecs = [_sim_emb(d, seed=i) for i, d in enumerate(docs)]     def recall(self, query, top_k=3):        qv = _sim_emb(query, seed=999)        scored = sorted(range(len(self.doc_vecs)),                        key=lambda i: _dot(qv, self.doc_vecs[i]),                        reverse=True)        return scored[:top_k]  class CrossEncoder:    """Cross-encoder: 查询与文档拼接联合打分, 共享 bigram 越多越相关"""     @staticmethod    def score(query, doc):        qg, dg = _bigrams(query), _bigrams(doc)        inter = len(qg & dg)        union = len(qg | dg)        return min(1.0, inter / (union + 1e-9) + 0.12 * inter)  class Student:    """蒸馏学生: 线性模型学习教师软分数, 只用双塔特征即可部署"""     def __init__(self, dim):        self.w = [0.01] * dim        self.b = 0.0     def predict(self, feats):        return sum(a * x for a, x in zip(self.w, feats)) + self.b     def train(self, feats_list, targets, steps=300, lr=0.1):        for _ in range(steps):            grad = [0.0] * len(self.w)            gb = 0.0            for feats, t in zip(feats_list, targets):                err = self.predict(feats) - t                for j in range(len(self.w)):                    grad[j] += 2 * err * feats[j]                gb += 2 * err            for j in range(len(self.w)):                self.w[j] -= lr * grad[j] / len(feats_list)            self.b -= lr * gb / len(feats_list)  def main():    bi = BiEncoder(DOCS)    cand = bi.recall(QUERY, top_k=3)    print("双塔召回 Top%d: %s" % (len(cand), [DOCS[i][:6] for i in cand]))     ranked = sorted(cand,                    key=lambda i: CrossEncoder.score(QUERY, DOCS[i]),                    reverse=True)    print("Cross-encoder 精排:")    for i in ranked:        print("  %s -> %.3f" % (DOCS[i][:8], CrossEncoder.score(QUERY, DOCS[i])))     print("知识蒸馏: 学生学习教师软分数")    feats_list = [bi.doc_vecs[i][:3] for i in cand]    targets = [CrossEncoder.score(QUERY, DOCS[i]) for i in cand]    stu = Student(dim=3)    stu.train(feats_list, targets, steps=300)    for i, f in zip(cand, feats_list):        print("  %s 教师=%.3f 学生=%.3f" % (DOCS[i][:8],              CrossEncoder.score(QUERY, DOCS[i]), stu.predict(f)))  if __name__ == "__main__":    main()
目的:用纯 Python 模拟“双塔召回 → Cross-encoder 精排 → 学生蒸馏学习教师软分数”的完整流程,帮助理解三者在检索管线中的角色分工。
  1. self.doc_vecs = [_sim_emb(d, seed=i) for i, d in enumerate(docs)]文档向量离线预计算,在线查询无需重算,这是双塔快的根基。
  2. scored = sorted(range(len(self.doc_vecs)), key=lambda i: _dot(qv, self.doc_vecs[i]), reverse=True)内积相似度排序取 TopK,对应工业界的 ANN 近邻检索。
  3. inter = len(qg & dg)模拟 Cross-encoder 的联合交互:查询与文档共享的 bigram 越多越相关。
  4. return min(1.0, inter / (union + 1e-9) + 0.12 * inter)联合打分兼顾覆盖率与命中数,体现“拼接后逐 token 对看”的效果。
  5. class Student:蒸馏学生模型,结构简单、速度快,只在双塔特征上做线性变换。
  6. err = self.predict(feats) - t学生输出与教师软分数的误差,是梯度下降更新参数的驱动力。
  7. ranked = sorted(cand, key=lambda i: CrossEncoder.score(QUERY, DOCS[i]), reverse=True)精排阶段只对召回候选重排,而不是对全库打分。
  8. if __name__ == "__main__":程序入口,按“召回 → 精排 → 蒸馏”三段流程依次执行。

常见误区

误区“既然都叫编码器,Cross-encoder 应该也能提前把文档向量算好存起来,查询来了直接比对。”

正解双塔的文档向量不依赖查询,所以能离线预计算;Cross-encoder 的输入是“查询 + 文档”拼接的整条序列,查询没到之前,文档的表示无法确定。就像面试官的评分取决于“候选人与岗位”这对组合,单独看简历定不了终面分数。所以精排只能用“查询来了再逐对算”的方式。

为何常见:初学者看到“编码器”三个字就默认两者具备相同的离线能力,忽略了输入结构这一根本差异。

误区“稠密 + 稀疏两个通道都召回了,结果应该够准了,为什么还要加一个慢吞吞的 Cross-encoder?”

正解混合召回解决的是“别漏掉”,两个通道都会带噪声:稀疏通道命中字面相同但语义无关的文档,稠密通道召回语义相近但不够相关的文档。精排负责在这些候选里做精细化排序,把最相关的顶到前面。没有精排,混合搜索就像两份猎头名单直接交给你,谁先谁后全凭运气。

为何常见:混淆了“召回”与“排序”两个阶段的不同目标,误以为通道越多越准。

误区“把教师判为相关/不相关的标签拿给学生训练,不就是蒸馏吗?”

正解蒸馏的灵魂是软标签——教师输出的连续分数分布,比如“文档 A 0.9、B 0.7、C 0.2”,它保留了文档之间的相对差异。硬标签只告诉学生对错,丢掉了“这篇比那篇好多少”的排序信息。就像老面试官光说“这个过、那个不过”,徒弟学不到打分尺度;写下分数分布,徒弟才懂得微妙差别。

为何常见:直觉上“答案”比“分数”更直接,但在排序任务里,相对顺序信息才是最有价值的。

误区“把候选从 50 扩大到 1000,精排能看到更多文档,结果一定更准。”

正解Cross-encoder 是逐对推理的大模型,候选翻 20 倍,延迟也翻约 20 倍;而多出来的绝大多数是低质量文档,对排序提升微乎其微。真正决定精排上限的是召回质量——候选里根本没有相关文档时,精排再强也无力回天。工业界一般把 K 控制在 50~200。

为何常见:直觉上“看得多就选得准”,忽略了延迟预算与边际收益递减。

面试题

Q1. Cross-encoder 与 Bi-encoder 最本质的区别是什么?

单选题 · easy

  • A.查询文档拼接编码← 正确答案
  • B.使用预训练模型
  • C.输出相似度分数
  • D.需要独立向量库

答案:A。Cross-encoder 的本质是把查询与文档拼接成一条序列后联合编码,让两者在注意力层充分交互;B 和 C 是两者共有的特征,D 更贴合双塔。就像面试官把简历和岗位说明摊开对比,而不是只看两份独立的摘要。

Q2. 两阶段检索中精排阶段的核心目标是什么?

单选题 · easy

  • A.让候选数量尽可能少
  • B.在候选集上精确排序← 正确答案
  • C.全库文档快速匹配
  • D.构建倒排索引结构

答案:B。精排阶段的任务是在召回产生的候选集上做精细排序;A 是控制成本的手段而非目标,C 是召回阶段的职责,D 是稀疏检索的离线构建工作。好比终面是给候选人排出 offer 顺序,而不是重新海选。

Q3. 为什么 Cross-encoder 不能预计算文档向量?

单选题 · mid

  • A.模型训练成本过高
  • B.文档拼接顺序影响大
  • C.查询文档共同决定← 正确答案
  • D.向量维度数量不确定

答案:C。因为 Cross-encoder 的输出由查询与文档共同决定,查询未到时文档表示无法计算;B 虽是真实现象但不是根本原因,A 和 D 与能否预计算无关。就像面试官的评价取决于“候选人 vs 岗位要求”这对组合,单独看简历定不了分数。

Q4. 知识蒸馏中软标签主要保留了哪类信息?

单选题 · mid

  • A.模型的参数量大小
  • B.类别间相对相似度← 正确答案
  • C.训练数据的原始顺序
  • D.词表的完整覆盖范围

答案:B。软标签保留的是“这篇比那篇相关多少”的相对排序信息,这正是排序训练最需要的监督信号;A、C、D 与软标签内容无关。好比老面试官写下的不是“过/不过”,而是“8 分、6 分、4 分”的连续尺度。

Q5. 以下哪些是知识蒸馏的关键组成部分?

多选题 · mid

  • A.教师模型的软分数← 正确答案
  • B.温度参数软化概率← 正确答案
  • C.强制师生结构一致
  • D.蒸馏损失函数设计← 正确答案

答案:ABD。蒸馏需要教师输出的软分数分布(A)、温度参数软化概率(B)、以及让学生逼近教师的损失函数(D);C 错在蒸馏并不要求学生与教师结构一致,学生模型结构自由,甚至可以是双塔。就像徒弟不必长得和师傅一样,学的是判断尺度而非外形。

Q6. 精排候选集通常控制在多少量级?

单选题 · easy

  • A.三到五篇最优
  • B.五十到两百篇← 正确答案
  • C.上万篇才全面
  • D.与文档总量正比

答案:B。工业界通常把精排候选控制在 50~200 篇,太小会漏掉好文档、太大延迟爆炸;A 过少、C 与 D 都违背精排只处理少量候选的设计原则。就像终面名额固定几十人,既不漏掉人才又不累垮面试官。

Q7. 相比 Bi-encoder,Cross-encoder 的劣势有哪些?

多选题 · hard

  • A.无法离线预计算表示← 正确答案
  • B.推理需要逐对计算← 正确答案
  • C.序列加长算力更高← 正确答案
  • D.精度总是更低一些

答案:ABC。相比双塔,Cross-encoder 无法离线预计算(A)、必须逐对在线推理(B)、序列加长导致注意力开销大(C);D 说反了——它的精度恰恰更高,这是它存在的理由。代价与收益是一体两面。

Q8. 蒸馏后学生模型相对教师的主要优势是什么?

单选题 · hard

  • A.推理速度大幅提升← 正确答案
  • B.精度永远高于教师
  • C.无需任何训练数据
  • D.可替代全部召回器

答案:A。蒸馏学生继承双塔结构,可预计算文档向量并极速推理,速度优势巨大;B 不成立(学生上限是逼近教师),C 不成立(仍需数据生成教师分数),D 说得太绝对,学生部署在粗排/精排位置而非必然取代召回。快与准的平衡点正是蒸馏的价值。

Q9. NDCG 指标中位置折扣项的作用是什么?

单选题 · hard

  • A.降低靠后位置贡献← 正确答案
  • B.提高所有位置权重
  • C.消除查询长度差异
  • D.只保留第一结果

答案:A。NDCG 的位置折扣项让排位靠后的相关文档贡献衰减,体现“越相关越靠前越好”;B 说反了方向,C 与查询长度无关,D 过度极端。就像 offer 名单越靠前越有价值,第十名和第一名的含金量完全不同。

Q10. 混合搜索中哪些通道会给精排提供候选?

多选题 · mid

  • A.稀疏匹配命中结果← 正确答案
  • B.稠密向量召回结果← 正确答案
  • C.用户浏览历史记录
  • D.搜索词同义词表

答案:AB。混合搜索的两个召回通道——稀疏关键词匹配与稠密向量检索——各吐一批候选送入精排;C 是训练反馈信号而非候选通道,D 是查询扩展手段。就像两位猎头各自推荐候选人,HR 把两份名单合并后统一面试。