从零实现朴素贝叶斯分类器:原理、变体与Python实战 1. 从一封垃圾邮件的判定说起朴素贝叶斯到底在算什么很多人第一次接触朴素贝叶斯是在机器学习课程的分类章节里。公式一摆条件独立假设一讲好像就结束了。但真正到了要拿它解决实际问题的时候脑子里往往只剩一个模糊的印象这玩意儿好像跟概率有关好像能做文本分类但具体怎么算、为什么叫“朴素”、代码写出来为什么跟教科书上的公式对不上号就说不清楚了。我打算换个方式来讲这件事。不从公式开始而是从一个具体的判定场景切入。假设你收到一封邮件标题里出现了“发票”“中奖”“点击链接”这几个词。你要判断它是垃圾邮件还是正常邮件。朴素贝叶斯做的事情非常直接它去统计历史上所有垃圾邮件里这几个词分别出现了多少次再统计正常邮件里它们出现的频率然后比较两个概率值——在“垃圾邮件”这个类别下同时出现这几个词的概率和在“正常邮件”这个类别下同时出现这几个词的概率哪个更大就归到哪一类。听起来很简单对吧但这里有一个关键问题如果要求“同时出现”的联合概率理论上需要统计所有词组合在一起出现的频率。现实中词的数量可能是几万维组合数量是天文数字根本没法统计。朴素贝叶斯做了一个非常大胆的简化假设假设每个词之间是相互独立的。也就是说它认为“发票”出现与否跟“中奖”出现与否没有关系。这个假设在现实中显然不成立——垃圾邮件里这些词往往是结伴出现的——但神奇的是即使假设不成立朴素贝叶斯在很多任务上依然表现不错。这就是“朴素”二字的由来它用了一个过于简单的假设把复杂的联合概率计算拆解成了单个词概率的乘积。代价是理论上不够精确收益是计算量从指数级降到了线性级而且在实际文本分类任务中效果往往超出预期。所以朴素贝叶斯的核心思想可以概括成一句话用贝叶斯定理做分类框架用条件独立假设做计算简化用先验概率和似然概率的乘积做最终决策。理解了这一点后面所有的变体、代码实现、调参技巧都是围绕这个核心展开的。2. 贝叶斯定理在分类任务里到底怎么用2.1 从条件概率到后验概率的推导链路贝叶斯定理本身不复杂公式是这样的P(A|B) P(B|A) * P(A) / P(B)放到分类任务里A是类别标签B是特征向量。我们想知道的是给定一个样本的特征它属于某个类别的概率是多少。写成公式就是P(类别|特征) P(特征|类别) * P(类别) / P(特征)这里面每一项都有明确的含义P(类别)先验概率。在没看任何特征之前这个类别出现的概率有多大。比如历史数据里垃圾邮件占30%那P(垃圾邮件)0.3。P(特征|类别)似然概率。在已知属于某个类别的条件下出现这组特征的概率有多大。P(特征)证据因子。对所有类别来说这个值都一样所以比较不同类别的后验概率时可以忽略它。P(类别|特征)后验概率。这是我们最终要比较的值。因为P(特征)对所有类别相同分类决策就变成了预测类别 argmax [ P(特征|类别) * P(类别) ]到这里都还很清晰。问题出在P(特征|类别)上。特征通常是一个向量比如文本分类里可能是几万个词的存在与否。要计算P(特征|类别)理论上需要统计所有特征组合在某个类别下出现的概率这根本做不到。2.2 条件独立假设带来的计算简化与代价朴素贝叶斯的解法是引入条件独立假设P(特征|类别) P(特征1|类别) * P(特征2|类别) * ... * P(特征n|类别)这个假设的意思是在给定类别的条件下各个特征之间相互独立。用文本分类举例就是假设“发票”这个词出现与否跟“中奖”这个词出现与否没有关系它们各自独立地受到类别的影响。这个假设显然过于简化了。现实中词汇之间是有强关联的比如“机器”和“学习”经常一起出现。但朴素贝叶斯不管这些它强行把它们拆开分别计算每个词在某个类别下的出现概率然后乘起来。这样做的好处是计算量从“需要统计所有组合”变成了“只需要统计每个词单独出现的频率”。对于一个有10万个词的词表前者需要统计的组合数量是2的10万次方后者只需要统计10万个概率值。这个差距是天文数字级别的。代价是概率估计不准确。但因为分类任务只需要比较不同类别的后验概率大小不需要精确的概率值所以即使每个概率都有偏差只要偏差不改变大小关系的排序分类结果就是对的。这也是朴素贝叶斯“假设不成立但效果不错”的根本原因。2.3 先验概率、似然概率与证据因子的角色分工在实际代码实现中这三个部分的处理方式直接决定了模型的性能。先验概率通常用训练数据中各类别的频率来估计。比如1000封邮件里300封是垃圾邮件那P(垃圾邮件)0.3。这个值看起来简单但在类别不平衡的场景下非常关键。如果垃圾邮件只占1%先验概率会严重偏向正常邮件需要通过调整权重或重采样来修正。似然概率是核心计算量所在。对于离散特征比如词出现与否通常用频率估计在某个类别下某个词出现的次数除以该类别下所有词的总出现次数。对于连续特征通常假设服从高斯分布用均值和方差来估计概率密度。证据因子P(特征)在比较时被忽略但如果你需要输出真实的概率值而不是仅仅做分类就需要把它算出来。P(特征) Σ P(特征|类别) * P(类别)对所有类别求和。这三者的分工可以用一个简单的类比来理解先验概率是“在没有证据之前你倾向于相信什么”似然概率是“如果假设成立你看到当前证据的可能性有多大”后验概率是“看到证据之后你应该相信什么”。分类决策就是选后验概率最大的那个类别。3. 三种常见变体多项式、伯努利与高斯朴素贝叶斯不是一个单一的算法而是一个算法家族。根据对P(特征|类别)的建模方式不同主要分为三种变体。选错变体是新手最常踩的坑之一所以这部分值得仔细说。3.1 多项式朴素贝叶斯词频统计的天然搭档多项式朴素贝叶斯假设特征服从多项式分布最典型的应用场景就是文本分类。它关注的是词出现的次数而不是仅仅关注词是否出现。具体来说对于某个类别c某个词w的似然概率估计为P(w|c) (类别c下词w出现的次数 α) / (类别c下所有词的总次数 α * 词表大小)这里的α是平滑参数后面会详细讲。这个公式的核心思想是在某个类别下某个词出现的频率越高它对该类别的“代表性”就越强。多项式朴素贝叶斯适合处理词频特征比如TF-IDF向量化之后的文本数据或者原始的词频矩阵。它在长文本分类任务中表现通常优于伯努利变体因为它利用了词频信息而不仅仅是词的存在性。但要注意一个细节多项式朴素贝叶斯假设词的出现次数服从多项式分布这意味着它隐含地假设文档长度不影响概率。实际上长文档中每个词出现的次数都更多这会导致长文档的似然概率乘积更小。不过因为分类时比较的是不同类别下的相对大小这个影响通常可以接受。3.2 伯努利朴素贝叶斯关注“出现与否”的二值场景伯努利朴素贝叶斯假设特征服从伯努利分布每个特征只有两个取值出现1或不出现0。它关注的是词是否出现而不是出现了多少次。对于某个类别c某个词w的似然概率估计为P(w|c) (类别c下包含词w的文档数 α) / (类别c下的文档总数 2α)注意这里的分母是文档总数不是词的总次数。而且伯努利模型有一个多项式模型没有的惩罚项对于在文档中没有出现的词它也会计算P(未出现|c) 1 - P(w|c)并将其纳入乘积。这意味着伯努利朴素贝叶斯对“缺失特征”也进行了建模。在短文本分类中比如判断一条推文的情感倾向伯努利变体有时比多项式变体更合适因为短文本中词的出现与否比出现次数更有信息量。但伯努利变体有一个明显的缺点对于长文档大量未出现的词会产生很多“惩罚项”导致计算量增加而且这些惩罚项可能淹没真正有区分度的信号。所以在长文本任务中多项式变体通常是更好的选择。3.3 高斯朴素贝叶斯连续特征的分布假设当前的特征不是离散的词频而是连续的数值时比如身高、体重、温度、价格等多项式分布和伯努利分布都不适用了。这时候需要用高斯朴素贝叶斯。高斯朴素贝叶斯假设在给定类别的条件下每个特征服从正态分布P(x|c) (1 / sqrt(2πσ²)) * exp(-(x - μ)² / (2σ²))其中μ和σ²分别是该特征在类别c下的均值和方差从训练数据中估计得到。高斯朴素贝叶斯适合处理连续型特征比如鸢尾花数据集中的花瓣长度、宽度等。它的一个关键假设是每个特征在每个类别下都服从正态分布而且不同特征之间相互独立。在实际使用中高斯朴素贝叶斯对特征分布的正态性有一定要求。如果特征明显偏离正态分布比如严重偏态或存在大量离群值效果可能会打折扣。这时候可以考虑对特征做变换如取对数或者使用核密度估计等非参数方法。三种变体的选择可以用一个简单的决策流程来判断特征类型推荐变体典型场景离散计数词频多项式长文本分类、主题识别二值特征出现/不出现伯努利短文本分类、情感分析连续数值高斯数值型数据的分类4. 平滑处理为什么零概率会让整个模型失效4.1 零概率问题的本质与后果假设你在训练一个垃圾邮件分类器词表里有“发票”“中奖”“会议”“报告”等词。在训练数据中正常邮件里从来没有出现过“中奖”这个词。那么根据频率估计P(“中奖”|正常邮件) 0 / 正常邮件总词数 0现在来了一封新邮件里面同时出现了“中奖”和“会议”。计算它属于正常邮件的似然概率时P(特征|正常邮件) P(“中奖”|正常邮件) * P(“会议”|正常邮件) * ... 0 * ... 0整个乘积变成了零。这意味着无论其他词多么强烈地指向正常邮件只要有一个词在某个类别下没出现过该类别就被完全排除了。这就是零概率问题。它的本质是用频率估计概率时未观测到的事件被赋予了零概率而乘法运算中任何一项为零都会导致整个乘积为零。这个问题在文本分类中尤其严重因为词表通常很大而训练数据有限几乎必然存在某些词在某个类别下没有出现。如果不处理模型会变得非常脆弱泛化能力极差。4.2 拉普拉斯平滑的数学原理与参数选择解决零概率问题的标准方法是平滑。最常用的是拉普拉斯平滑也叫加一平滑。对于多项式朴素贝叶斯平滑后的似然概率估计为P(w|c) (类别c下词w出现的次数 α) / (类别c下所有词的总次数 α * 词表大小)对于伯努利朴素贝叶斯P(w|c) (类别c下包含词w的文档数 α) / (类别c下的文档总数 2α)α就是平滑参数。当α1时就是标准的拉普拉斯平滑。它的效果是给每个词的出现次数都“加一”这样即使某个词在某个类别下从未出现它的概率也不再是零而是一个很小的正数。α的选择是一个需要根据数据调整的超参数α1最常用的默认值适合大多数场景。α1当训练数据充足、词表很大时较小的α可以减少平滑带来的偏差。比如α0.1或0.01。α1当训练数据很少、需要更强的平滑时使用。但α过大会导致所有词的概率趋于均匀丧失区分度。在实际调参时我通常从α1开始然后在验证集上尝试[0.01, 0.1, 0.5, 1, 2, 5]这几个值。对于文本分类任务α在0.1到1之间往往效果最好。如果发现模型对训练数据过拟合可以适当增大α如果发现模型欠拟合可以减小α。4.3 平滑对分类决策的实际影响平滑不仅仅是一个数学上的修补它实际上改变了模型的决策边界。在没有平滑的情况下模型对训练数据中未出现的特征组合会给出极端判断。平滑之后模型变得更加“谨慎”对于没见过的词它不会直接判定为零概率而是给出一个基于平滑参数的小概率值。这带来的实际影响是模型对噪声和罕见词的鲁棒性增强了。比如一封正常邮件里偶然出现了一个通常只在垃圾邮件里出现的词没有平滑时这封邮件可能被误判为垃圾邮件有平滑之后这个单一证据的影响被削弱了模型会综合考虑其他词的信息。但平滑也有代价。如果α设置得过大所有词的概率都会被“拉平”模型失去区分能力退化成只依赖先验概率的基线分类器。所以平滑参数的选择需要在鲁棒性和区分度之间找平衡。5. 用Python从零实现一个朴素贝叶斯分类器5.1 数据准备与特征工程的关键决策在写代码之前有几个关键决策需要先想清楚。第一个决策用什么特征。对于文本分类最直接的方式是词袋模型把每个词作为一个特征。但原始文本需要经过分词、去停用词、词干提取等预处理步骤。分词工具的选择取决于语言英文可以用空格和标点分割中文需要专门的分词工具。去停用词是为了去掉“的”“了”“是”这类高频但无区分度的词。词干提取是把“running”“runs”“ran”归并到“run”减少词表大小。第二个决策用什么向量化方式。词袋模型有两种常见输出词频矩阵和TF-IDF矩阵。词频矩阵直接统计每个词在每篇文档中出现的次数。TF-IDF则在此基础上乘以逆文档频率降低常见词的权重。对于朴素贝叶斯词频矩阵通常就够了因为朴素贝叶斯本身会对高频词赋予较高权重TF-IDF的额外加权有时反而会引入噪声。第三个决策用什么变体。根据特征类型选择多项式、伯努利或高斯。文本分类通常用多项式或伯努利。下面是一个完整的多项式朴素贝叶斯实现从数据准备到模型训练和预测import numpy as np from collections import defaultdict class MultinomialNB: def __init__(self, alpha1.0): self.alpha alpha self.class_prior {} self.feature_prob {} self.classes None self.vocab None def fit(self, X, y): n_samples, n_features X.shape self.classes np.unique(y) self.vocab list(range(n_features)) # 计算先验概率 for c in self.classes: self.class_prior[c] np.sum(y c) / n_samples # 计算似然概率 for c in self.classes: X_c X[y c] # 每个特征在该类别下的总出现次数 feature_count X_c.sum(axis0) # 该类别下所有特征的总出现次数 total_count feature_count.sum() # 拉普拉斯平滑 self.feature_prob[c] (feature_count self.alpha) / (total_count self.alpha * n_features) return self def predict(self, X): predictions [] for x in X: posteriors {} for c in self.classes: # 对数概率避免下溢 log_prior np.log(self.class_prior[c]) log_likelihood np.sum(x * np.log(self.feature_prob[c])) posteriors[c] log_prior log_likelihood predictions.append(max(posteriors, keyposteriors.get)) return np.array(predictions)这段代码有几个值得注意的细节。第一个细节对数概率。在计算后验概率时我没有直接乘概率值而是取对数后相加。这是因为多个小概率相乘会导致数值下溢尤其是特征维度很高时乘积会小到计算机无法表示。取对数之后乘法变成加法数值稳定性大大提高。而且因为对数函数是单调递增的比较对数概率的大小和比较原始概率的大小是等价的。第二个细节拉普拉斯平滑。在计算feature_prob时分子加了alpha分母加了alpha * n_features。这保证了即使某个特征在某个类别下从未出现它的概率也不是零。第三个细节先验概率的计算。直接用类别频率估计没有做平滑。在类别极度不平衡时可以考虑对先验也做平滑但通常不需要。5.2 训练阶段对数概率与向量化计算上面的代码用了循环来计算每个类别的似然概率这在特征维度不高时没问题但如果词表有几十万维循环会非常慢。实际工程中需要用向量化操作来加速。向量化的核心思路是把所有类别的feature_prob存储为一个矩阵形状是(n_classes, n_features)然后用矩阵运算一次性计算所有样本对所有类别的对数似然。class VectorizedMultinomialNB: def __init__(self, alpha1.0): self.alpha alpha def fit(self, X, y): n_samples, n_features X.shape self.classes np.unique(y) n_classes len(self.classes) # 先验概率 self.class_log_prior np.zeros(n_classes) for i, c in enumerate(self.classes): self.class_log_prior[i] np.log(np.sum(y c) / n_samples) # 似然概率矩阵 self.feature_log_prob np.zeros((n_classes, n_features)) for i, c in enumerate(self.classes): X_c X[y c] feature_count X_c.sum(axis0) total_count feature_count.sum() self.feature_log_prob[i] np.log( (feature_count self.alpha) / (total_count self.alpha * n_features) ) return self def predict(self, X): # X shape: (n_samples, n_features) # feature_log_prob shape: (n_classes, n_features) # 计算每个样本对每个类别的对数似然 log_likelihood X self.feature_log_prob.T # (n_samples, n_classes) joint_log_likelihood log_likelihood self.class_log_prior return self.classes[np.argmax(joint_log_likelihood, axis1)]向量化版本的核心是最后一行X self.feature_log_prob.T。这是一个矩阵乘法X的每一行是一个样本的特征向量feature_log_prob.T的每一列是一个类别的对数概率向量。矩阵乘法的结果是一个(n_samples, n_classes)的矩阵第i行第j列表示第i个样本属于第j个类别的对数似然。这个操作在NumPy底层是用C语言实现的比Python循环快几个数量级。对于大规模数据这是必须的优化。5.3 预测阶段如何避免数值下溢前面提到了对数概率避免下溢这里再展开说一下为什么这个问题很严重。假设有1000个特征每个特征的概率是0.1那么乘积是0.1^1000 10^-1000。这个数已经超出了双精度浮点数的表示范围最小正数约为10^-308会直接变成0。一旦变成0所有类别的后验概率都是0分类器就无法做出判断了。取对数之后log(0.1^1000) 1000 * log(0.1) ≈ -2302。这个数在浮点数范围内可以正常比较。但要注意取对数之后原本的乘法变成了加法原本的加法先验概率乘以似然概率变成了log_prior log_likelihood。这个转换是等价的因为log(P(特征|类别) * P(类别)) log(P(特征|类别)) log(P(类别))所以最终比较的是对数后验概率取最大值对应的类别作为预测结果。还有一个细节在计算log_likelihood时我用的是X feature_log_prob.T。这里X是词频矩阵feature_log_prob是对数概率。这个矩阵乘法的含义是对于每个样本把它每个词的对数概率乘以该词的频次然后求和。这等价于Σ count(w) * log(P(w|c))正好是多项式分布的对数似然。5.4 完整可运行示例从文本到分类结果下面是一个完整的示例从原始文本开始经过分词、向量化、训练、预测输出分类结果。from sklearn.feature_extraction.text import CountVectorizer from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report import numpy as np # 模拟数据简单的情感分类 texts [ 这个产品非常好用 质量很棒 推荐购买, 服务态度很好 物流很快 满意, 质量太差了 用了两天就坏了 退货, 客服不理人 东西也不行 差评, 性价比很高 做工精细 喜欢, 发货慢 包装破损 失望, 功能强大 操作简单 好评, 完全不符合描述 浪费钱, 外观漂亮 手感好 值得买, 用了一次就坏了 垃圾产品 ] labels [1, 1, 0, 0, 1, 0, 1, 0, 1, 0] # 1: 正面, 0: 负面 # 向量化 vectorizer CountVectorizer() X vectorizer.fit_transform(texts).toarray() y np.array(labels) # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) # 训练模型 model VectorizedMultinomialNB(alpha1.0) model.fit(X_train, y_train) # 预测 y_pred model.predict(X_test) # 输出结果 print(预测结果:, y_pred) print(真实标签:, y_test) print(classification_report(y_test, y_pred, zero_division0))这个示例虽然数据量很小但完整展示了朴素贝叶斯分类器的使用流程。在实际项目中你需要把模拟数据替换成真实数据把CountVectorizer替换成更适合的向量化工具把alpha调整到合适的值。6. 调参、评估与踩坑实录6.1 平滑参数alpha的调参策略alpha是朴素贝叶斯最重要的超参数。调参的基本思路是在验证集上尝试不同的alpha值选择分类效果最好的那个。但有几个经验性的规律可以参考规律一数据量越大最优alpha越小。当训练数据充足时频率估计已经比较准确不需要太强的平滑。此时alpha可以设得很小比如0.01或0.1。当数据量很少时需要更强的平滑来防止过拟合alpha可以设大一些比如1或2。规律二词表越大最优alpha越小。词表大意味着特征维度高平滑带来的偏差会被放大。如果alpha1而词表有10万维那么分母会加上10万这会导致所有词的概率都被严重压缩。所以对于高维稀疏数据alpha通常设得比较小。规律三类别越不平衡越需要关注先验概率的调整。如果某个类别的样本极少先验概率会很低即使似然概率很高后验概率也可能被压低。这时候可以考虑对先验概率也做平滑或者使用类别权重。在实际调参时我通常用网格搜索在[0.001, 0.01, 0.1, 0.5, 1.0, 2.0, 5.0]这几个值中寻找最优。对于文本分类任务0.1到1.0之间往往是最优区间。6.2 分类评估指标的选择与解读朴素贝叶斯是分类器评估它需要用分类指标。常用的指标有准确率、精确率、召回率、F1值。准确率是最直观的指标预测正确的样本占总样本的比例。但在类别不平衡时准确率会误导人。比如99%的样本是正常邮件1%是垃圾邮件一个把所有邮件都判为正常的分类器准确率是99%但它完全没有识别垃圾邮件的能力。精确率和召回率是一对需要权衡的指标。精确率是“预测为垃圾邮件的样本中真正是垃圾邮件的比例”召回率是“真正是垃圾邮件的样本中被预测为垃圾邮件的比例”。提高精确率通常会降低召回率反之亦然。F1值是精确率和召回率的调和平均综合了两者。在类别不平衡时F1值比准确率更能反映模型的真实性能。对于朴素贝叶斯还有一个需要关注的点它输出的概率值通常不够准确。因为条件独立假设的偏差朴素贝叶斯给出的后验概率往往偏向0或1而不是真实的概率分布。所以如果你需要精确的概率估计朴素贝叶斯可能不是最佳选择。但如果你只需要分类标签它的表现通常足够好。6.3 那些教科书不会告诉你的坑坑一特征独立假设在强相关特征上会翻车。如果两个特征高度相关比如“机器”和“学习”在文本分类中几乎总是一起出现朴素贝叶斯会把它们当作独立证据导致这个组合的权重被重复计算。结果是模型对这个组合过于敏感。解决办法是特征选择时去掉冗余特征或者使用特征加权来降低相关特征的权重。坑二连续特征的高斯假设不成立时效果骤降。高斯朴素贝叶斯假设特征服从正态分布。如果特征明显偏态比如收入分布直接用高斯朴素贝叶斯效果会很差。这时候可以先对特征做变换比如取对数使其接近正态分布。坑三文本分类中词序信息完全丢失。词袋模型把文本当作词的集合完全忽略了词序。“我打你”和“你打我”在词袋模型里是一样的。对于情感分析这种词序重要的任务朴素贝叶斯可能不如考虑词序的模型。但话说回来在很多文本分类任务中词袋模型已经足够好了词序带来的额外信息有限。坑四朴素贝叶斯对无关特征不敏感但对噪声特征敏感。如果特征中有大量与类别无关的噪声朴素贝叶斯会把它们也纳入计算虽然每个噪声特征的影响很小但数量多了之后会累积成显著的偏差。特征选择是缓解这个问题的有效手段。坑五多分类问题中先验概率的估计偏差会被放大。在二分类中先验概率的偏差影响有限。但在多分类中如果某个类别的先验概率被低估即使似然概率很高后验概率也可能排不到第一。对于类别不平衡的多分类问题需要特别关注先验概率的校准。7. 朴素贝叶斯在实际场景中的边界与替代方案朴素贝叶斯不是万能的。它有明确的适用边界超出这个边界效果可能不如其他算法。它最适合的场景是特征维度高、训练数据少、需要快速训练和预测、对概率校准要求不高。文本分类是它的经典应用场景尤其是垃圾邮件过滤、新闻分类、情感分析等任务。在这些任务中朴素贝叶斯通常能给出一个不错的基线而且训练速度极快可以在几秒内处理完几十万条数据。它不太适合的场景是特征之间强相关、需要精确概率估计、特征分布严重偏离假设、数据量极大且计算资源充足。在这些场景下逻辑回归、支持向量机、梯度提升树、神经网络等模型通常表现更好。但即使在这些场景下朴素贝叶斯仍然有一个不可替代的优势它是理解贝叶斯分类框架的最佳入口。它的数学形式简单清晰代码实现短小精悍非常适合用来学习分类算法的基本原理。理解了朴素贝叶斯再去学逻辑回归、贝叶斯网络、隐马尔可夫模型都会容易很多。我在实际项目中的做法是先用朴素贝叶斯跑一个基线看看数据的基本可分性。如果基线效果已经满足需求就直接用如果不够再尝试更复杂的模型。这样既能快速验证数据质量又能为后续模型提供一个性能参照。还有一个实用技巧把朴素贝叶斯的输出概率作为特征输入到其他模型中。比如用朴素贝叶斯对文本进行初步分类把它的后验概率作为新特征再训练一个梯度提升树。这种堆叠stacking的方式有时能结合两者的优势取得比单一模型更好的效果。最后说一个容易被忽略的点朴素贝叶斯的训练和预测速度极快这使得它非常适合在线学习场景。当新数据不断到来时朴素贝叶斯可以增量更新概率估计而不需要重新训练整个模型。只需要更新每个类别的特征计数和总计数就能得到新的概率估计。这个特性在实时分类系统中非常有用。