街角有一家老面馆,老板记了一本厚厚的《点单记录》。他有一套朴素经营哲学:想预测顾客点什么面,就翻翻记录——以前被点得最多的面,下一次大概率还是它。这套“以史为鉴”的打分法,很像检索系统里的统计语言模型:给一句查询打分时,假设它出自某篇“文档面馆”的菜单,概率越高越相关。可老板很快撞上尴尬:记录本里从没写过的面,难道顾客就真的不点吗? 答案是把整条街的数据请进来——城里面馆协会发布了一份《城市流行面食报告》:每种面在整座城被点的比例。老板把自家记录和城市报告按某种方式“掺一掺”,给没出现过的面也留一个极小的概率兜底。这个“掺”的动作,就是语言模型里的平滑(Smoothing)。掺法有两个流派:Dirichlet 平滑像“提前备货”——记录越厚越信自己;JM 平滑则像“固定配方”——永远三成信城市、七成信自己。整篇文章就是讲清这两种掺法如何让检索排序从“碰运气”变成“有兜底”。
掌握统计语言模型与平滑方法,你就真正理解了从 TF-IDF 到概率检索模型的进化逻辑:平滑是让排序模型在稀疏数据下依然稳健的关键机制,也是搜索、推荐岗位面试中最常被追问的工程细节。
可视化演示
展示最大似然估计的零概率问题、Jelinek-Mercer线性插值平滑及Dirichlet贝叶斯平滑的自适应特性。切换模式查看不同平滑策略。
本节配有交互动效(进入视口后自动加载,约 3KB)。
语言模型概率
面馆老板想预测下一句点单,他先把顾客的话拆成一碗碗“单点要素”:“牛肉”“二两”“宽面”。他相信,这句点单出现的可能性,等于每样要素各自被点的概率乘起来。要素越常见,整句就越“顺嘴”;要素越冷门,整句就越“拧巴”。这个“连乘”的直觉,正是语言模型的起点。
检索里的统计语言模型干的是同一件事。给定一篇文档 D(老板的记录本),模型给任意词序列分配一个概率。为了让计算可行,采用马尔可夫假设:词与词之间互相独立,只问“单个词出现在这份文档里的可能性有多大”。这种简化版本叫一元语言模型(unigram),也是信息检索中最常用的形式。
一句查询 Q 由 n 个词组成,我们假设它像“从文档 D 里生成”的一样,那么生成概率就是各词概率的连乘。这个 P(Q|D) 就是文档与查询相关度的证据:概率越高,文档越可能是用户要找的。
老板此时信心满满:记录本在手,天下我有。但他马上会在第一笔冷门点单面前傻眼——这就是下一节的“零概率困境”。
- 词序列$w_1, w_2, \ldots, w_n$ 表示把一句话从左到右拆出的 n 个词。
- 连乘符号$\prod_{i=1}^{n}$ 表示把从第 1 个到第 n 个词的概率逐一相乘。
- 概率语义$P(w_i)$ 是词 $w_i$ 在文档模型下出现的概率,连乘结果就是整句话的生成概率。
零概率困境
一个顾客走进来:“来碗螺蛳粉。”老板翻遍整本记录——从没出现过。按“频率即概率”的逻辑,这碗面的概率是 0。可顾客明明就站在面前。记录没写,不代表不可能;只是老板没见过而已。
最大似然估计(MLE)就是这个“只认记录”的逻辑:词在文档里出现的次数,除以文档的总词数。它只回答“见过多少次”,不回答“可能有多大”。问题在于:真实语料极度稀疏,词表动辄几十万,可一篇文档只有几百个词;而且绝大多数词都服从长尾分布——在某个文档里出现一次就顶天了。于是几乎每篇文档都有大量“没见过的词”。
更致命的是连乘的连锁反应。查询 Q 有 5 个词,只要有一个词在某文档中概率为 0,整个 P(Q|D) 立刻塌成 0——“一颗老鼠屎坏了一锅汤”。排序时这些文档直接失去资格,哪怕其余 4 个词高度匹配。这就是为什么平滑不是锦上添花,而是救命稻草。
老板的结论是:不能只信自己的记录本,得把外部信息请进来,给没见过的面一个“说得过去的猜测”。这就引出了背景分布。
- 分子计数$count(w, d)$ 表示词 w 在文档 d 里出现的次数,这是“亲眼所见”的证据。
- 分母长度$|d|$ 表示文档 d 的总词数,用来把次数换算成比例。
- 比值含义次数除以总词数,得到频率,MLE 就把频率直接当成概率。
背景分布
城里面馆协会发布了一份《城市流行面食报告》:全城所有面馆一整年被点的面,按种类统计占比。这份报告就是检索里的“背景模型”(Collection Model)。它不关心某一家面馆的特色,只回答一个朴素问题:这座城市的人,平均意义上更爱吃哪种面?
技术上说,背景模型 $P(w \mid C)$ 在全集语料 C 上统计每个词的出现次数,再除以全集总词数做归一化,得到一个合法的概率分布。它反映的是这个词在整个“城市”里的流行度。这个分布有两个天生的优点:第一,只要词在全集出现过,它的概率就是非零的——天然适合给零频词兜底;第二,统计量来自海量语料,估计稳定,不像单篇文档那样抖得厉害。
在平滑的视角里,文档模型负责“个性”,背景模型负责“常识”。一篇讲机器学习的文档里,“模型”一词频率高,这是个性;而“的”这种词在全集里到处都是,这是常识。平滑要做的,就是把个性和常识按合理比例焊在一起,既不丢掉文档特色,又不至于在没见过的东西上直接翻车。
有了这份“城市报告”,老板开始琢磨掺法:怎么把自家记录和城市比例合成一个最终概率?两个流派应运而生——Dirichlet 与 JM。
- 分子$count(w, C)$ 是词 w 在整个全集语料 C 中出现的总次数。
- 分母求和$\sum_{w'} count(w', C)$ 把词典里所有词 w' 的次数都加起来,得到全集总词数。
- 归一化结果两者相除,把每个词映射到 0 到 1 之间,且所有词的概率之和等于 1。
Dirichlet平滑
Dirichlet 流派的做法像“提前备货”。老板根据城市报告,心里预置了 μ 份“万能基础物料”,并按每种面的流行度把这批物料分到每个品种头上。当记录本里没有“螺蛳粉”时,至少还有城市比例派过来的一点基础分量;而记录本越厚,这批基础物料在总判断里占的比重就越小。
公式上,Dirichlet 平滑把“文档计数 + μ 倍背景概率”除以“文档长度 + μ”。这里的 μ 是 Dirichlet 先验参数,可以理解成伪计数(Pseudo-count)的总规模:μ 越大,越依赖背景;|d| 越大,文档自身的词频越占主导。把公式稍微变形,会看到它本质上是插值:文档权重是 |d|/(|d|+μ),背景权重是 μ/(|d|+μ)。
这个“长度自适应”特性让 Dirichlet 在文档长短不一的搜索引擎里特别吃香:长文档几乎只看自己,短文档多参考城市。工程经验上 μ 常取 1500~2500,具体数值用验证集调。它的理论根基是贝叶斯统计——把背景分布当作先验,把文档词频当作观测,后验就是平滑后的分布。
老板很满意:记录越厚越自信,这是常识的数学化。但另一位老板喜欢更简单的配方——永远三七开,这就是 JM。
- 分子第一项$count(w,d)$ 是文档里词 w 的真实出现次数,代表“自家记录”。
- 分子第二项$\mu P(w \mid C)$ 是按背景流行度注入的伪计数,代表“城市预付款”。
- 分母归一化$|d| + \mu$ 把分子整体缩放到概率范围,保证所有词的概率之和为 1。
- 长度感知当 $|d|$ 远大于 $\mu$ 时,公式趋近于 MLE;当 $|d|$ 很小时,公式趋近于背景分布。
JM平滑
JM 流派(Jelinek-Mercer)的哲学是“固定配方”:不管记录本多厚,永远按某个固定比例混合自家记录与城市报告。这个比例由 λ 控制,取值范围 0 到 1。λ 大,更保守、更靠城市常识;λ 小,更激进、更信自家记录。配方一旦定下,所有文档一视同仁。
公式是标准的线性插值:文档的 MLE 概率乘以 (1-λ),背景概率乘以 λ,两者相加。检索实践中 λ 通常取 0.1~0.3,让文档自身的信号占主导,背景只负责兜底。为什么不能取太大?因为背景分布是“众口一词”的常识,权重一大,所有文档的分数都会被它拉平,区分度就没了。
JM 的优势是简单、好调:一个 λ 走天下,对小语料、短文档很友好,训练时用验证集做网格搜索就能找到不错的取值。它的短板也源于“固定”二字:一篇 10000 词的长文和一篇 50 词的短文,对自家记录的信任度竟然一样,这在文档长短差异大的场景下不太合理。
厨房里两种配方各有拥趸。到底用哪个?取决于你的“面馆”长什么样——文档有多长、查询有多短、数据有多稀疏。
- 文档信任度$(1 - \lambda) P_{ml}(w \mid d)$ 是文档自身 MLE 概率打折后的份额。
- 背景信任度$\lambda P(w \mid C)$ 是背景概率按 λ 比例入账的份额。
- 权重守恒$(1-\lambda) + \lambda = 1$,两路证据按比例瓜分总概率,不会超发。
平滑对比选择
两家面馆,一家做社区生意,顾客需求五花八门、记录本厚薄悬殊;另一家做写字楼快餐,每单都很短、很稳定。前者适合 Dirichlet,后者适合 JM。平滑没有绝对优劣,只有合不合适。
原理上:Dirichlet 的文档权重随 |d| 自适应,理论上等价于对文档模型施加 Dirichlet 先验;JM 则是固定权重的两段混合,本质是 λ 折中。参数上:μ 的量纲与“词次数”绑定,换了语料要跟着平均文档长度重新调;λ 是纯比例,语义更直观,好解释。场景上:文档长度差异大的搜索库(网页、论文库),Dirichlet 往往更稳;查询短、文档均匀的垂直搜索或 FAQ 匹配,JM 的简单更省心。
两者都可以写成统一的插值形式——文档概率乘一个权重 α,背景概率乘 (1-α)。区别只在于 α 怎么定:Dirichlet 让 α 随文档长度变化,JM 让 α 恒为 (1-λ)。实践者常做 A/B 测试或网格搜索,在验证集上比 NDCG、MAP 等指标定胜负;数据极充足时两者差距会缩小,但稀疏场景下选对配方能拉开明显差距。
选好配方只是开始,真正让模型发光的是把它装进检索系统——用“查询似然”给文档排名。
- 统一形式$\alpha_d P_{ml}(w \mid d)$ 与 $(1 - \alpha_d) P(w \mid C)$ 分别代表个人证据与公共证据。
- Dirichlet 特例$\alpha_d = \frac{|d|}{|d| + \mu}$,权重随文档长度动态变化。
- JM 特例$\alpha_d = 1 - \lambda$,权重恒定,与文档长度无关。
检索应用实践
现在老板要把这套哲学变成“今日招牌墙”:顾客报出查询 Q,对所有候选文档算一遍 P(Q|D),从高到低排序。这就是查询似然(Query Likelihood)排序,概率检索模型的代表性做法。它回答的问题是:哪个文档最“像”能生成这句查询的文档?
工程上,概率连乘会越乘越小,直接计算容易下溢成 0,所以工程实现一律取对数:把连乘变成连加,每一项都是 log P(w|D)。log 是单调函数,不改变排序顺序,却把数值从“天文数字级别的下溢”解救到“可累加的负数”。实现时先离线统计好背景词频表,查询到来时只需查表累加,配合倒排索引,性能完全可以支撑线上检索。
和 BM25 对比:BM25 是词频饱和、文档长度归一化的加权求和,属于 TF-IDF 家族;语言模型走的是“文档生成查询”的概率路线。两者都是经典概率模型,Dirichlet 平滑的查询似然在不少数据集上能与 BM25 打得有来有回。尤其是在长尾查询上,平滑的兜底概率让模型不容易因为一个冷门词“全军覆没”,这正是它作为现代排序基线的价值所在。
从一本点单记录,到一份城市报告,再到两种掺法——老板最终明白:好的排序不是记住所有答案,而是给未知留一扇门。这扇门,就是平滑。
- 对数左侧$\log P(Q \mid D)$ 是整句生成概率的对数,防止连乘结果过小。
- 连加右侧$\sum_{w \in Q} \log P(w \mid D)$ 把连乘转换成连加,每个查询词贡献一项。
- 数学依据$\log(a \times b) = \log a + \log b$,取对数把乘法变加法,排序结论不变。
AI 应用场景
网页搜索引擎排序
阶段:检索与排序阶段(Query Likelihood Ranking)
解决的问题:传统 BM25 依赖词频与逆文档频率的加权,遇到冷门词或长查询时,文档只要缺一个词就难以得分。语言模型 + 平滑把“文档是否包含词”升级为“文档生成该词的概率有多大”,零频词也有兜底概率,长查询不会因单个词整体击穿。
具体案例:某电商搜索“降噪头戴式耳机 学生党 平价”,商品标题大多只覆盖部分词。用 Dirichlet 平滑(μ≈2000)计算查询似然后,冷门词“降噪”在标题未出现时仍有小概率兜底,最终排序既保留高相关商品,也不漏掉标题未写全但描述匹配的长尾商品。
推荐系统物品冷启动
阶段:物品召回与打分阶段
解决的问题:新上架商品没有用户行为数据,协同过滤无从下手;但它的标题、类目、标签是有文本的。用一元语言模型为物品建“文本画像”,再求用户查询/兴趣词的生成概率,平滑让冷门词也有非零得分。
具体案例:视频平台新上传的纪录片《深海秘境》,观看量几乎为零。系统用标题和简介建立语言模型,用户搜“深海 纪录片 4K”时,靠 JM 平滑(λ=0.2)兜底,把这篇只有几十词描述的“文档”也纳入候选集,顺利触发冷启动推荐。
客服 FAQ 智能匹配
阶段:语义匹配与答案检索阶段
解决的问题:客服知识库的问题描述往往只有一两句话,MLE 估计极不可靠。平滑借背景分布补充常见词概率,让用户提问与 FAQ 的匹配不因措辞差异而失效。
具体案例:银行客服机器人收到“银行卡丢了怎么办”。FAQ 库中“挂失流程”一文的正文含“卡”“丢失”“冻结”等词,但标题不含“丢”。用背景模型兜底后,查询似然得分不再为 0,机器人能正确命中挂失流程并回复操作指引。
Python 演示
# -*- coding: utf-8 -*-# 统计语言模型 + Dirichlet/JM 平滑的最小实现import math class SmoothedLM: def __init__(self, docs, method="dirichlet", param=2000.0): self.docs = docs self.method = method self.param = param self.coll_count = {} self.coll_total = 0 for doc in docs: for token in doc.split(): self.coll_count[token] = self.coll_count.get(token, 0) + 1 self.coll_total += 1 def coll_prob(self, token): # 背景模型: 词在全集的占比, 天然非零(只要出现过) return self.coll_count.get(token, 0) / self.coll_total def doc_prob(self, doc, token): # 某个词在给定文档中的平滑概率 dl = len(doc.split()) tf = doc.split().count(token) if self.method == "dirichlet": mu = self.param return (tf + mu * self.coll_prob(token)) / (dl + mu) # jm 平滑: 固定权重混合 lam = self.param return (1 - lam) * (tf / dl) + lam * self.coll_prob(token) def score(self, query, doc): # 查询似然: log P(Q|D) = sum log P(w|D) total = 0.0 for token in query.split(): p = self.doc_prob(doc, token) total += math.log(p) if p > 0 else -20.0 return total def search(self, query, top_k=3): ranked = [(self.score(query, doc), idx, doc) for idx, doc in enumerate(self.docs)] ranked.sort(reverse=True) return ranked[:top_k] if __name__ == "__main__": corpus = [ "搜索引擎返回相关的文档结果", "语言模型估计词语序列的概率", "平滑方法解决零概率问题", "推荐系统为用户推荐商品列表", "机器翻译生成目标语言的句子", "信息检索查找用户需要的资料", ] query = "搜索引擎 概率 模型" for method, param in [("dirichlet", 1500.0), ("jm", 0.2)]: lm = SmoothedLM(corpus, method=method, param=param) print("method =", method, " param =", param) for s, idx, doc in lm.search(query): print(" %.4f %s" % (s, doc))
self.coll_count[token] = self.coll_count.get(token, 0) + 1统计全集词频,这是“城市报告”的数据来源return self.coll_count.get(token, 0) / self.coll_total背景概率归一化,保证分布合法且绝大多数词非零return (tf + mu * self.coll_prob(token)) / (dl + mu)Dirichlet 公式:文档计数 + μ 倍背景伪计数return (1 - lam) * (tf / dl) + lam * self.coll_prob(token)JM 公式:固定权重线性插值total += math.log(p) if p > 0 else -20.0查询似然对数求和,零概率时给一个极小的兜底分,防止整体崩溃ranked.sort(reverse=True)按 log 概率从高到低排序,完成检索闭环
常见误区
误区我觉得平滑很简单,就是把每个没见过的词都硬塞一个 0.001 的概率,零概率问题就解决了。
正解平滑不是“均匀撒糖”,而是有方向地“借概率”:从高频词那里匀出一部分,按背景分布的流行度分配给零频词。Dirichlet 用 μ×P(w|C) 的伪计数,JM 用 λ×P(w|C),借多少取决于词在全集的地位,而不是平均主义。
为何常见:很多教程只强调“给零概率一个非零值”,忽略了概率质量守恒——所有词的概率之和必须为 1,只能内部腾挪,不能凭空增发。
误区λ 越大表示背景模型越重要,检索应该更稳健,所以我把 JM 的 λ 直接设成 0.9。
正解λ 越大,文档自身信号越弱,所有文档的分数都会被背景分布拉平,区分度消失。经验上 λ 取 0.1~0.3 检索效果更好;Dirichlet 的 μ 同理,过大会让模型“全信城市、不信自己”。参数要用验证集调,不是越大越好。
为何常见:直觉上“多参考大数据”听起来更稳,但检索要的是区分相关与不相关,过强的背景会抹平文档个性。
误区反正排序只看相对大小,平滑后每篇文档的概率之和是不是 1 无所谓。
正解平滑必须保持概率分布归一化:Σ_w P(w|d) = 1。Dirichlet 和 JM 的公式都满足这一点(分子分母同步放量)。若实现时忘记归一化,长文档天然得分更高,排序会被文档长度绑架。
为何常见:因为大家习惯盯着单个词的概率看,忘了语言模型输出的是一整个概率分布,分布的性质决定了排序的公平性。
误区用了 Dirichlet 或 JM 平滑之后,任何查询词都不会再是零概率了。
正解平滑解决的是“词典内但文档中未见”的零频问题。如果查询词根本不在全集语料里(OOV,词典外词),背景概率 P(w|C) 同样是 0,连平滑也无能为力,还需要分词兜底、词干化或专门的 OOV 处理。
为何常见:把“文档内的零”和“词典外的零”混为一谈,没意识到背景分布也有自己的词表边界。
误区Dirichlet 有贝叶斯理论支撑,又支持长度自适应,无脑选它准没错。
正解理论优雅不等于数据表现更好。文档长度均匀、查询全为短查询的小规模垂直搜索里,JM 常常更简单够用;Dirichlet 的优势在文档长短差异大的场景才明显。选型要看数据分布,并让验证集指标(NDCG、MAP)说了算。
为何常见:大家容易被“更高级的数学”吸引,忽略了工程上“够用就好”的权衡。
面试题
Q1. 统计语言模型的核心任务是什么?
- A.给词序列分配合理概率← 正确答案
- B.对文档进行自动分词
- C.计算两个词的字面距离
- D.删除语料中的停用词
答案:A。语言模型本质是“词序列的概率分布”,它回答“这句话有多像自然语言”。分词是预处理,字面距离是相似度方法,停用词过滤是清洗步骤,都不是语言模型的本职。
Q2. 未平滑的 MLE 模型最致命的问题是什么?
- A.模型训练速度较为缓慢
- B.零概率击穿整句连乘← 正确答案
- C.参数数量急剧膨胀
- D.无法处理中文长文本
答案:B。MLE 对未见词给出概率 0,而整句概率是所有词概率连乘,一个 0 就让整个查询概率归零——像面馆记录本上没写过的面,顾客点了就直接“出局”。性能、参数规模、语言类型都不是 MLE 的核心死穴。
Q3. Dirichlet 平滑中参数 μ 的主要作用是什么?
- A.控制先验伪计数的强度← 正确答案
- B.决定词典规模的大小
- C.改变背景集合的来源
- D.设定查询词项的数目
答案:A。μ 是伪计数总规模,决定背景分布注入多少“预付物料”;μ 越大越相信城市报告。词典规模由语料决定,背景集合来源是设计选择,查询词数目与 μ 无关。
Q4. JM 平滑中,λ 取值偏大意味着什么?
- A.更加信任文档自身词频
- B.更加偏向背景集合分布← 正确答案
- C.完全丢弃背景模型
- D.平滑作用彻底消失
答案:B。JM 是 (1-λ)P_ml + λP(w|C),λ 大则背景权重高,文档自身信号被稀释。λ=1 时完全只看背景,λ=0 时退化为 MLE,所以“完全丢弃背景”和“平滑消失”都对应 λ 偏小而非偏大。
Q5. 在 Dirichlet 平滑下,长文档的排序分数主要取决于什么?
- A.背景集合的全局分布
- B.文档自身的词频统计← 正确答案
- C.其他文档的平均概率
- D.查询词的出现顺序
答案:B。Dirichlet 的文档权重是 |d|/(|d|+μ),文档越长权重越接近 1,公式趋近 MLE 本身。短文档反而更依赖背景。查询词顺序在一元模型里不影响结果,其他文档概率不参与单篇打分。
Q6. 背景模型 P(w|C) 的作用有哪些?
- A.为文档提供兜底概率← 正确答案
- B.反映词在全集的流行度← 正确答案
- C.完全替代文档词频统计
- D.作为零频词的参照分布← 正确答案
答案:ABD。背景分布是“城市报告”,既反映流行度,又是零频词的兜底参照。但它只是辅助信号,不能替代文档自身的词频统计——替代了就没有“个性”了,排序会失去区分度。
Q7. MLE 估计一个词在文档中概率的公式是什么?
- A.词频除以文档总词数← 正确答案
- B.文档数除以词典大小
- C.查询数除以文档总数
- D.词总数乘以出现次数
答案:A。MLE 就是“频率即概率”:count(w,d)/|d|。其余选项的分子分母搭配没有概率意义,属于干扰项。
Q8. 下列关于平滑的说法,哪些是正确的?
- A.Dirichlet 的文档权重随文档长度变化← 正确答案
- B.JM 的混合权重是固定不变的← 正确答案
- C.平滑之后概率总和不再为 1
- D.平滑可以消除一切零概率
答案:AB。Dirichlet 的 α=|d|/(|d|+μ) 随长度变化,JM 的 α=1-λ 恒定,A、B 都对。平滑保持归一化(C 错),且对词典外词 OOV 无能为力(D 错)——背景分布本身也是零。
Q9. 查询似然排序为什么要用 log 概率求和?
- A.把连乘变连加避免下溢← 正确答案
- B.让所有分数都大于零
- C.使模型训练速度更快
- D.消除背景模型的影响
答案:A。n 个小于 1 的概率连乘会指数级缩小,浮点数直接下溢成 0。log 把乘法变加法,数值稳定且不改变排序。log 概率通常是负数,所以“大于零”是错的,它也不影响训练速度或背景模型。
Q10. 搜索引擎落地平滑模型时,哪些做法是必要的?
- A.预计算全集词频统计表← 正确答案
- B.对未见词设计额外兜底← 正确答案
- C.把 λ 调到接近 1 求稳
- D.用验证集网格搜索调参← 正确答案
答案:ABD。背景词频表要离线算好,否则查询时现扫全集代价极高;OOV 词需要分词或词表兜底;参数要用验证集调。λ 接近 1 会让所有文档分数趋同,检索效果反而变差,是典型错误。