想象你走进一家叫“知味轩”的中餐馆,对服务员喊了一声“来一份红烧肉”。后厨有一摞菜谱,每张菜谱都可能被端出来。大厨先按“这道菜和红烧肉沾不沾边”排个先后:写满红烧肉细节的菜谱排前面,只是沾边的排后面。这是基础相关度。接着,老板老王在旁边做“人情调分”:某道菜是招牌菜,加 2 分;某家食材供应商离得远,扣 0.5 分;今天会员日,会员爱吃的菜再加 1 分。最后,所有分数加在一起,决定了服务员把哪道菜先端上桌。 Elasticsearch(简称 ES)的搜索打分机制,干的就是这件事。默认情况下,ES 用 BM25 算法给每个文档算“基础相关度分”打底的菜谱吻合度,就像大厨的基础评价;而 function_score 是老板手里的“特权批注单”,让你在基础分上叠加业务规则——比如销量、好评、距离、时间、甚至一段自定义脚本。当搜索排序需要同时兼顾“相关”和“业务意图”时,function_score 就是最趁手的工具。

岗位关联

对搜索工程师来说,掌握 function_score 是进阶排序的必修课。它让你从“只能排相关度”升级为“用业务规则操控排序”,在电商、外卖、招聘等真实场景中直接决定用户体验。

目录
  1. 可视化演示
  2. 打分机制概览
  3. BM25算法
  4. function_score基础
  5. 脚本评分
  6. 衰减函数
  7. 混合检索编排
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

动态演示ES打分的核心机制,通过源头/管道/节点/终端四种模式展示关键概念与交互流程。

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

ES打分:ES打分 ES打分机制概述与BM25评分 function_score查 核心机制可视化 ES打分整体架构与关键组件

打分机制概览

你走进“知味轩”点了一份红烧肉,后厨不是乱端菜的。大厨扫一眼菜谱,先看它和“红烧肉”三个字有多少重合:做法步骤里反复出现“五花肉”“冰糖”“酱油”的菜谱优先;只写了一句“本店有盖浇饭”的菜谱靠后。这一轮叫“相关度初筛”。紧接着老板老王凑过来,在几份菜谱上写了批注:招牌菜加 2 分、今天的推荐加 1 分、供应商离得远的减 0.5 分。大厨的基础印象加上老板的批注,才构成最终的上菜顺序。

在 ES 里,每个文档就是一张菜谱,你的查询词就是客人的点单。ES 返回结果时会给每个文档算一个 _score 字段,数值越大排得越靠前。默认的 _score 来自 Lucene 的 BM25 算法,它只关心“查询词和文档内容的相关程度”,相当于大厨师出身的“标准口味”。但实际搜索里,我们还要照顾销量、好评率、距离、会员等级这些业务因素——这时就要用 function_score 查询,在相关度之外追加“业务调分”。

整体看,ES 的排序分数可以拆成两层。第一层是查询词带来的基础相关度分,第二层是各种业务函数产生的调分。两者合起来,得到最终排序分:

所以,默认排序只是后厨的“标准菜单”,只能回答“谁和红烧肉最像”;function_score 则是老板手里的“特权批注单”,让你把商业规则翻译成排序顺序。想设计一套好的搜索体验,必须同时理解这“基础分”和“调分”两套牌。

total(D)=squery(D)+Δ(D)\displaystyle \text{total}(D) = s_{\text{query}}(D) + \Delta(D)
这个公式说明:排序不是凭空来的,而是“基础相关度 + 业务加权的总和”。公式中的 D 代表一个候选文档,s_query 是它和查询词的相关度,Δ 是老板额外加的批注分。
  1. 文档 D候选文档,就是那些可能被搜出来的菜谱。
    total(D)\text{total}(D)
  2. 基础相关度查询词与文档内容的匹配得分,由 BM25 等算法算出。
    squery(D)s_{\text{query}}(D)
  3. 业务调分function_score 里所有自定义函数产生的加分或减分。
    Δ(D)\Delta(D)

BM25算法

大厨的基础分是怎么打出来的?他不会只看“菜谱里有没有红烧肉”就下结论,而是会考虑三件事:这个词在这份菜谱里出现了多少次?这个词在所有菜谱里是否常见?这本菜谱是厚厚一本还是薄薄一页?要是同一道词在一本 500 页的百科全书中出现 3 次,和一页小抄里出现 3 次,后者的“专注度”显然更高。

BM25 算法就是 ES 的默认大厨。它对每个查询词,结合词频(TF,Term Frequency)、逆文档频率(IDF,Inverse Document Frequency)和文档长度做综合打分。词频高说明文档围绕该词写得多;逆文档频率高说明这个词很稀有,能有效区隔文档;文档长度归一化则避免长文档仅因篇幅大就占便宜。三个信号合起来,得到一个鲁棒的相关度分数:

其中,逆文档频率 IDF 的计算方式是:

BM25 之所以成为 ES 默认,是因为它在“简单”和“有效”之间取得平衡。回到餐厅里,大厨不会因为一份菜谱写了 100 次“红烧肉”就给它打满分——重复 100 次和重复 10 次,在 BM25 眼里差别很小。这就防止了“关键词堆砌”的作弊手段,也保证了短小精悍的菜谱不会被大厚本子欺负。

score(D,Q)=∑i=1nIDF(qi)⋅f(qi,D) (k1+1)f(qi,D)+k1(1−b+b⋅∣D∣avgdl)\displaystyle \text{score}(D,Q) = \sum_{i=1}^{n} \text{IDF}(q_i) \cdot \frac{f(q_i,D)\,(k_1+1)}{f(q_i,D) + k_1\left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}
IDF(qi)=ln⁡(1+N−n(qi)+0.5n(qi)+0.5)\displaystyle \text{IDF}(q_i) = \ln\left(1 + \frac{N - n(q_i) + 0.5}{n(q_i) + 0.5}\right)
BM25 公式初看很长,但拆开就一句话:一个词的得分 = 这个词的“稀有程度” × 这个词在文档中“重复带来的边际收益”(收益会饱和,且受文档长度调节)。
  1. 累加求和对查询 Q 里的每个词 q_i 逐个计算贡献,最后加起来。
    ∑i=1n\sum_{i=1}^{n}
  2. 逆文档频率词出现过的文档数越多,IDF 越小,常见词就不值钱。
    IDF(qi)=ln⁡(1+N−n(qi)+0.5n(qi)+0.5)\text{IDF}(q_i) = \ln\left(1 + \frac{N - n(q_i) + 0.5}{n(q_i) + 0.5}\right)
  3. 词频该词在文档 D 中出现的次数,f 越大基础贡献越大。
    f(qi,D)f(q_i,D)
  4. 词频饱和f(q_i,D)(k_1+1) 是分子,配合分母让次数高到一定程度后加分放缓,防止刷屏。
    f(qi,D) (k1+1)f(qi,D)+k1(⋯ )\frac{f(q_i,D)\,(k_1+1)}{f(q_i,D) + k_1(\cdots)}
  5. 文档长度归一化|D|/avgdl 是文档长度与平均长度之比,越长文档,同样的词频要打折扣。
    ∣D∣avgdl\frac{|D|}{\text{avgdl}}
  6. 超参数 k1、bk1 控制词频饱和的快慢,b 控制长度惩罚的力度,两者都能在查询时调整。
    k1,bk_1, \quad b

function_score基础

老王老板从不单独决定上菜顺序,他总会先看大厨的初评,再往上面叠批注。他手里有一张批注单,上面可能写了好几条规则:“招牌菜加 2 分”“新品加 1 分”“距离超过 10 公里的合作店减 0.5 分”。这些规则就是 ES 里 function_score 的 functions 数组,每个函数都可以独立计算出一个“调分值”。

但这里有个关键问题:多个调分值如何合并?是求和、取平均,还是取最大值?这个合并方式由 score_mode 决定。合并之后,调分结果怎样作用于基础查询分?是加起来、乘上去,还是直接替换?这个由 boost_mode 决定。ES 里有一条完整链路:

最常见的写法是:score_mode = sum,把多条业务调分先加总;boost_mode = multiply,再让基础分整体缩放。比如一道菜基础分 5 分,招牌加分 2 分,最终分就是 5 × 2 = 10——注意,乘法会把基础分和调分“绑得更紧”,只加不加项时,基础分高的文档被放大得更明显。相反,如果用 boost_mode = sum,就是 5 + 2 = 7,业务分只作为固定加成。

批注单上的“先合并、再叠加”的顺序,就是 score_mode 和 boost_mode 的职责划分。理解这两层,你就能随心所欲地把老板的任意规则编码进排序逻辑:想让业务分只做小幅修正,用 sum;想让业务分成倍放大,用 multiply。

final(D)=boost_mode(query_score(D), score_mode(f1(D),f2(D),…,fn(D)))\displaystyle \text{final}(D) = \text{boost\_mode}\left(\text{query\_score}(D),\, \text{score\_mode}\big(f_1(D), f_2(D), \ldots, f_n(D)\big)\right)
  1. 单个调分函数f_i(D) 代表第 i 条业务规则,可以是一个常量权重、一段脚本或一个衰减函数。
    fi(D)f_i(D)
  2. 合并调分结果score_mode 把所有 f_i(D) 合成一个数,可取值有 multiply、sum、avg、max、min、first。
    score_mode(f1(D),f2(D),…,fn(D))\text{score\_mode}\big(f_1(D), f_2(D), \ldots, f_n(D)\big)
  3. 基础查询分query_score(D) 是原始的 BM25 相关度分,反映文档和查询词有多匹配。
    query_score(D)\text{query\_score}(D)
  4. 作用方式boost_mode 决定最终分,可选 multiply、sum、replace、avg。最常用的是 multiply(相乘)和 sum(相加)。
    boost_mode(⋅,⋅)\text{boost\_mode}(\cdot, \cdot)

脚本评分

有些业务规则不是一个常数能表达的。比如老王的批注单上写:“今天食材价格高于 100 元的菜,加 2 分;否则加 0.5 分。”这需要现场读取每个文档的价格字段,再做一次判断。更复杂的规则,比如“会员收藏过的标签命中一个加 1 分,收藏超过 3 个额外加 2 分”,就很难用固定 weight 写出来。

ES 为此提供了脚本评分:用内置的 Painless 脚本语言,在打分阶段逐文档执行一小段代码。脚本可以从文档字段取值,也可以访问查询参数,最后返回一个浮点数,作为这个文档的“调分”。这样,打分逻辑就从“静态配置”变成了“动态计算”,业务上再灵活的需求都能塞进去。它对应的核心概念是 function_score 里的 script_score 函数:

脚本评分是一把瑞士军刀,能处理任何可计算的打分规则,但也要小心:它是逐文档执行的,会带来额外性能开销。就像老板不可能在用餐高峰期对每道菜都现场做复杂的数学题——他会把最常用的规则写成固定批注(weight、field_value_factor),只在真正需要时动用脚本。合理的设计是:能用配置解决的就别写脚本,脚本只用来覆盖那些“非它不可”的动态业务逻辑。

sscript(D)={2.0if price(D)>1000.5otherwise\displaystyle s_{\text{script}}(D) =\begin{cases}2.0 & \text{if } \text{price}(D) > 100 \\0.5 & \text{otherwise}\end{cases}
  1. 读字段price(D) 表示从文档中取出“价格”字段,脚本里写作 doc['price'].value。
    price(D)\text{price}(D)
  2. 条件分支当价格大于 100 时走上半支,返回 2.0;否则走下半支,返回 0.5,这就是一个“分档加分”。
    {2.0if price(D)>1000.5otherwise\begin{cases} 2.0 & \text{if } \text{price}(D) > 100 \\ 0.5 & \text{otherwise} \end{cases}
  3. 返回调分脚本计算出的数值会被 score_mode 和其他函数合并,再交给 boost_mode 作用到基础分上。
    sscript(D)s_{\text{script}}(D)

衰减函数

老王的外卖业务最近火了,但他发现一个问题:距离太远的合作餐馆,就算菜再好吃,客人也等不起。他想在排序里加一条规则:“距离越远,加分越少;超过 5 公里直接降到很低。”衰减函数就是为这类场景设计的——它根据数值字段(如距离、时间、价格)计算一个介于 0 和 1 之间的衰减系数。字段值越接近某个“完美点”,系数越接近 1;偏离越多,系数越小。

衰减函数有三种形态:linear(线性衰减)、exp(指数衰减)、gauss(高斯衰减)。它们都要求你指定两个关键参数:origin 是最优值,scale 是衰减幅度控制的“半径”。以最常用的高斯衰减为例,当一个文档的距离值为 v(D) 时,它的调分为:

想象夜晚的一盏路灯:origin 是灯芯正下方,亮度最高;scale 是光照半径,半径越大,远处越亮。如果你的业务希望“3 公里内差异不大、5 公里外快速下降”,就把 scale 调小一些;如果希望“10 公里内都能接受”,就把 scale 调大。衰减函数特别适合地理搜索、价格敏感类推荐和“新鲜度优先”的场景(比如把发布时间当成 origin,让旧内容自动降温)。

gauss(D)=exp⁡(−(v(D)−origin)22⋅scale2)\displaystyle \text{gauss}(D) = \exp\left(-\frac{(v(D) - \text{origin})^2}{2 \cdot \text{scale}^2}\right)
  1. 字段值v(D) 是文档里用于衰减的数值,比如外卖店的距离 3.2 公里。
    v(D)v(D)
  2. 最优值origin 是得分最高的点,当 v(D) 恰好等于 origin 时,衰减系数为 1,不扣分。
    origin\text{origin}
  3. 衰减半径scale 控制曲线宽度,相当于“光照半径”,scale 越大,衰减越平缓。
    scale\text{scale}
  4. 指数衰减整个式子就是高斯函数的指数形式,距离 origin 越远,系数按平方速度下滑。
    exp⁡(−(v(D)−origin)22⋅scale2)\exp\left(-\frac{(v(D) - \text{origin})^2}{2 \cdot \text{scale}^2}\right)

混合检索编排

大厨的口味很稳定,可老王发现,光靠菜谱关键词匹配不够——有时候客人说不清楚菜名,只描述“想吃点家常的、管饱的”。这时让美食 App 的“潮流指数”参与判断,效果更好。这个“潮流指数”就是向量检索:把文档和查询都映射成向量,用相似度衡量语义接近程度。但问题来了:大厨的基础分和潮流指数,该怎么合在一起?

ES 的实践是:先用 BM25 跑一遍词法匹配,再用向量相似度跑一遍语义匹配,最后把两个分数融合。直接相加往往不科学,因为两种分数的量纲和分布完全不同——BM25 可能从 0 到十几,向量余弦相似度通常在 -1 到 1 之间,谁数值大谁就压制对方。所以要先分别归一化,再用一个权重系数 α 平衡:

这就像老板拍板:“这次咱们按七三开——七分听大厨的关键词,三分看潮流指数。”α 的取值要结合业务反复调:对专有名词(如“iPhone 15 评测”)适合加大 BM25 权重;对口语化、同义表达多的查询(如“适合下雨天吃的暖乎乎的东西”)则要加重向量权重。归一化之后,即使未来某一方的分数普遍变高,也不会粗暴地淹没另一方。

total(D)=α⋅slexical(D)max⁡slexical+(1−α)⋅svector(D)max⁡svector\displaystyle \text{total}(D) = \alpha \cdot \frac{s_{\text{lexical}}(D)}{\max s_{\text{lexical}}} + (1-\alpha) \cdot \frac{s_{\text{vector}}(D)}{\max s_{\text{vector}}}
  1. 词项分s_lexical(D) 是 BM25 等词法匹配打出的基础相关度分。
    slexical(D)s_{\text{lexical}}(D)
  2. 向量分s_vector(D) 是语义向量检索给出的相似度分数,代表“意思像不像”。
    svector(D)s_{\text{vector}}(D)
  3. 归一化分别除以各自的最大值,把两套分数都压到 0~1 区间,才能公平比较。
    slexical(D)max⁡slexical,svector(D)max⁡svector\frac{s_{\text{lexical}}(D)}{\max s_{\text{lexical}}}, \quad \frac{s_{\text{vector}}(D)}{\max s_{\text{vector}}}
  4. 平衡系数α 表示你有多信任“关键词匹配”,1−α 表示有多信任“语义匹配”。
    α,1−α\alpha, \quad 1-\alpha

AI 应用场景

电商排序:销量、价格、库存如何参与排名

阶段:排序策略定制

解决的问题:传统搜索只按相关度返回,会把“没人买”的商品和爆款排在一起,转化率低。AI 排序里用 function_score 把销量、好评率、库存状态编码为业务调分,让好卖的商品浮上来。

具体案例:某电商平台搜索“跑步鞋”,BM25 保证结果和“跑步鞋”相关,function_score 叠加“销量指数”和“好评率”作为缩放因子,再对价格偏离用户预期过大的商品施加衰减扣分,最终推荐位置明显更适合转化。

外卖排序:距离与时间衰减

阶段:特征工程与排序融合

解决的问题:同样的“黄焖鸡米饭”,客人当然想要近的、出餐快的。传统做法是把距离过滤掉,但太生硬。用 gauss 衰减函数,把配送距离和预计出餐时间作为 origin 附近的平滑降权信号。

具体案例:外卖 App 里,用户搜“麻辣烫”,系统先按文本相关召回,再用衰减函数对“配送距离超过 3 公里”“出餐时间超过 25 分钟”的商家平滑扣分,保证列表里前排都是真正送得到的店。

招聘检索:语义混合排序

阶段:召回与排序融合

解决的问题:候选人搜“高并发架构师”,简历里可能写“系统性能优化”“分布式稳定性”,关键词不匹配但语义匹配。用 BM25 + 向量检索做混合召回,再用 function_score 把“工作年限”“更新时间”作为加权和衰减因素融入排序。

具体案例:某招聘网站升级职位搜索,BM25 负责匹配硬技能词,向量模型负责理解同义表达,最终用 α=0.6 加权融合,并给“近 7 天更新”的职位加时间衰减加分,候选人求职意愿强的职位排得更前。

Python 演示

python67 行
import math  def idf(n, N):    return math.log(1 + (N - n + 0.5) / (n + 0.5))  def bm25_score(query_terms, term_counts, doc_len, avgdl, df, N, k1=1.2, b=0.75):    score = 0.0    for term in query_terms:        tf = term_counts.get(term, 0)        if tf == 0:            continue        term_idf = idf(df.get(term, 0), N)        denom = tf + k1 * (1 - b + b * doc_len / avgdl)        score += term_idf * tf * (k1 + 1) / denom    return score  def gauss_decay(value, origin, scale):    return math.exp(-((value - origin) ** 2) / (2 * scale * scale))  DOCS = [    {"title": "红烧肉家常做法", "text": "五花肉 冰糖 酱油 料酒 姜片",     "price": 45.0, "distance": 1.2},    {"title": "餐厅招牌红烧肉", "text": "精选五花肉 秘制酱油 慢炖两小时",     "price": 88.0, "distance": 3.4},    {"title": "低卡鸡胸肉沙拉", "text": "鸡胸肉 生菜 沙拉酱 柠檬汁",     "price": 68.0, "distance": 2.1},]  def tokenize(text):    return text.split()  def main():    query_text = "红烧肉 做法 五花肉"    query_terms = tokenize(query_text)    corpus = [" ".join(tokenize(d["text"]) + tokenize(d["title"])) for d in DOCS]    docs_tokens = [tokenize(c) for c in corpus]    N = len(docs_tokens)    doc_lens = [len(t) for t in docs_tokens]    avgdl = sum(doc_lens) / N     df = {}    for tokens in docs_tokens:        for token in set(tokens):            df[token] = df.get(token, 0) + 1     results = []    for idx, doc in enumerate(DOCS):        term_counts = dict((t, docs_tokens[idx].count(t)) for t in set(docs_tokens[idx]))        base = bm25_score(query_terms, term_counts, doc_lens[idx], avgdl, df, N)        boost = 0.0        boost += 2.5 * gauss_decay(doc["distance"], 1.0, 3.0)        boost += 1.0 if doc["price"] < 60 else 0.2        results.append((doc["title"], base, boost, base + boost))     results.sort(key=lambda row: row[3], reverse=True)    for title, base, boost, total in results:        print("%-16s BM25=%6.3f 调分=%6.3f 总分=%6.3f" % (title, base, boost, total))  if __name__ == "__main__":    main()
目的:用纯 Python 模拟 ES 的 BM25 打分和 function_score 调分流程,展示基础相关度与业务加权如何共同决定排序。
  1. term_idf = idf(df.get(term, 0), N)计算该词在整个文档集合里的逆文档频率,词越罕见权重越高。
  2. denom = tf + k1 * (1 - b + b * doc_len / avgdl)对词频做长度归一化,长文档需要更多命中才不吃亏。
  3. score += term_idf * tf * (k1 + 1) / denom累加每个查询词的 BM25 贡献,得到基础相关度分。
  4. boost += 2.5 * gauss_decay(doc["distance"], 1.0, 3.0)用高斯衰减把“距离越近加分越多”的规则变成数值。
  5. results.sort(key=lambda row: row[3], reverse=True)按最后的总分(基础分 + 业务调分)降序排列。

常见误区

误区我看到搜索结果里第一条 `_score` 是 12.8,第二条是 8.1,第一条第 2 个查询里 `_score` 是 9.0,那它一定比第 2 个查询里的第 1 条差。

正解_score 只是当前查询下的相对排序分数,不是一个绝对质量指标。换个查询词,同样的文档可能从 12 分变成 2 分;换个文档集合,IDF 也会变。就像“辣度”在川菜馆的评分体系和粤菜馆里根本没有可比性。

为何常见:接口返回的数值太像“考试成绩”,人天然想用绝对数比较,但打分机制本质是排序工具。

误区我写文章时故意把关键词重复 50 遍,搜索排名应该稳稳第一,但实际效果并不好。

正解BM25 的词频收益是饱和的,出现 10 次和出现 50 次对得分的提升非常小;同时 IDF 会惩罚那些在太多文档里都出现的常见词。堆砌关键词反而显得不自然,被算法自动“降权”。就像一道菜盐放得越多,并不会让人越觉得“咸得高级”。

为何常见:直觉上以为“提到越多越相关”,但 BM25 的设计哲学是“够用就好,过量无效”。

误区反正排序是分数大小决定的,我把业务权重 boost 设为 100,不就把爆款顶上来了吗?

正解boost 过大时,boost_mode: multiply 会把基础相关度彻底淹没,导致明明用户搜“青菜”却出来一篇反复强调“招牌菜”的鸡汤文。调分应该克制,目的是“微调”而不是“重写排序”。就像老板给所有菜都加 100 分,那“招牌”两个字就失去意义了。

为何常见:初学排序容易把“权重”理解为“力大砖飞”,忽略了相关度是排序的地基。

误区我设置 `gauss` 的 `scale: 100`,觉得数值越大衰减越“猛”,结果距离很远的店分数也没降下去。

正解scale 是高斯函数的“尺度”,直观理解是光照半径:scale 越大,亮度衰减越平缓,远处也越亮。想让曲线快速下滑,反而要调小 scale。

为何常见:scale 这个单词缺乏直觉画面,很多人把它误当成“惩罚力度”。

误区既然要混合,那就 `score = bm25_score + vector_score`,一行代码搞定。

正解BM25 的分数量级通常在 0~十几,而向量余弦相似度一般在 -1~1 之间,直接相加时向量分几乎不起作用。必须先把两套分数各自归一化到 0~1,再用权重 α 融合。这就像比较苹果和橘子,先都换成“斤”再谈比例。

为何常见:公式看起来就是加法,容易忽略两套算法各自输出分数的分布差异。

面试题

Q1. BM25 算法里的 k1 参数主要用来控制什么?

单选题 · easy

  • A.调控词频过高后的收益饱和程度← 正确答案
  • B.改变查询分词器切词的粒度
  • C.控制文档索引刷新时间间隔
  • D.指定分页返回结果的条数上限

答案:A。k1 控制词频的饱和曲线:k1 越大,高频词带来的增益持续得更久;k1 越小,词频很快就不再加分。选 B、D 混淆了分词器和分页概念,C 是索引刷新与打分无关。

Q2. 某个词在越来越多的文档中出现,它的 IDF 值会怎样变化?

单选题 · easy

  • A.逆文档频率对应数值会变小← 正确答案
  • B.逆文档频率对应数值会变大
  • C.逆文档频率数值保持不变
  • D.逆文档频率数值无限趋近零

答案:A。IDF 的公式里包含 N - n(q) + 0.5,n(q) 是出现该词的文档数。n 越大,分子越小、分母越大,整体值越小。这符合直觉:越是烂大街的词,区分能力越差,就像“下饭”这种词在每家菜单都有,没有辨识度。

Q3. 在 function_score 查询中,score_mode 支持以下哪些合并多个函数结果的策略?

多选题 · mid

  • A.取所有函数结果相乘← 正确答案
  • B.取所有函数结果求和← 正确答案
  • C.取所有函数结果平均← 正确答案
  • D.取所有函数结果求平方

答案:ABC。score_mode 支持 multiply、sum、avg、max、min、first,没有“平方”(square)。求平方不是一个独立的合并策略,因为取 square 本质上也只是对一个数做变换,不是多结果合并。

Q4. 在 function_score 中,将 boost_mode 设置为 replace 表示什么?

单选题 · mid

  • A.用函数调分覆盖基础查询分← 正确答案
  • B.在基础查询分上加上调分
  • C.用基础查询分覆盖函数调分
  • D.固定分数为查询时的常量

答案:A。replace 的含义是“最终得分直接采用 functions 算出的结果,忽视 query_score”。也就是说,基础相关度完全不参与排序。选项 B 是 sum、C 方向反了、D 是常量 boost 的行为。

Q5. 高斯衰减函数中,当文档字段值恰好等于 origin 时,衰减系数是多少?

单选题 · hard

  • A.衰减系数取得最大值一← 正确答案
  • B.衰减系数已归零失去作用
  • C.衰减系数会变为负数值
  • D.衰减系数恰好等于尺度

答案:A。把 v(D) = origin 代入公式,指数部分为 exp(0) = 1,即不衰减,是最大值。B、C 把衰减和“惩罚”混为一谈,D 混淆了 scale 的定义。衰减函数输出范围恒在 0 到 1 之间。

Q6. 为什么混合检索时,通常要先对 BM25 和向量相似度做归一化再加权融合?

多选题 · mid

  • A.两种分数量纲并不一致← 正确答案
  • B.避免一种分数压制另一种← 正确答案
  • C.归一化能显著提高吞吐量
  • D.保证加权系数合理作用← 正确答案

答案:ABD。BM25 与向量相似度的数值范围和分布差异大,不归一化会导致某一方被淹没或主导,α 也无法表达真实权重。C 错误,归一化属于数值处理,不直接优化查询吞吐。

Q7. 在 ES 的 script_score 评分中,Painless 脚本最终必须返回什么类型的值?

单选题 · easy

  • A.双精度浮点数类型← 正确答案
  • B.可变长字符串类型
  • C.布尔判定值类型
  • D.JSON对象类型

答案:A。script_score 的返回值会被当作该文档的调分参与后续运算,必须是数字,通常是 double。字符串、布尔和对象都不能直接作为分数,ES 会报类型错误。

Q8. 如果想实现“打分随距离增大而平缓均匀地下降”,应该优先选择哪种衰减函数?

单选题 · mid

  • A.线性衰减曲线下降← 正确答案
  • B.指数衰减曲线下降
  • C.高斯衰减曲线下降
  • D.正弦波动曲线下降

答案:A。linear 衰减按线性比例均匀下降,直观且稳定;exp 和 gauss 都是先快后慢的曲线。正弦波动不是衰减函数,无法用于单调惩罚。选线性最适合“均匀平缓”的需求。

Q9. 某次查询使用 function_score,设置 weight = 2 且 boost_mode = multiply,却出现最终分数为 0 的文档。最可能的原因是?

单选题 · hard

  • A.基础查询得分恰好为零← 正确答案
  • B.调分函数权重设置过高
  • C.score_mode 使用了总和
  • D.文档没有进入候选集

答案:A。multiply 是把基础分和调分相乘,基础分为 0 时结果必为 0。B 中权重过高只会放大分数,不会归零;C 中 sum 只是合并多条函数;D 中未进入候选集根本不会出现在结果里。

Q10. 关于 BM25 中的长度归一化参数 b,下列说法正确的有哪些?

多选题 · extreme

  • A.当 b 等于零时忽略文档长度← 正确答案
  • B.当 b 等于一时按长度完全惩罚← 正确答案
  • C.b 越大对长文档惩罚越重← 正确答案
  • D.b 与 IDF 计算互斥无关

答案:ABC。b=0 时分母中长度比例项消失,完全不考虑文档长度;b=1 时长度归一化完全生效,长文档惩罚最大;b 越大惩罚越重。D 错误:b 调节长度因子,IDF 独立计算,两者是不同维度而非“互斥”。