查看全课大纲(第 24 / 82 节)
- 1.函数与数据泛化
- 2.向量:从几何直觉到高维空间
- 3.矩阵与张量
- 4.特征值与特征向量:矩阵变换的不动方向
- 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
- 6.概率与统计:机器学习的数学基础
- 7.独立性与随机变量:贝叶斯公式与二项分布
- 8.期望、方差与协方差:随机变量的数字画像
- 9.联合概率:当一条轴不够用的时候
- 10.概率分布(一):从伯努利到伽马函数
- 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
- 12.机器学习的模型:找输入与输出的关系
- 13.机器学习的应用
- 14.过拟合
- 15.机器学习的分类
- 16.有监督学习:从房价预测说起
- 17.损失函数
- 18.梯度下降(上):更新规则与学习速率
- 19.梯度下降(2)
- 20.牛顿方法
- 21.KNN简介
- 22.距离测算
- 23.朴素贝叶斯
- 24.朴素贝叶斯的实际应用
- 25.信息熵:决策树构建的度量基础
- 26.信息增益与决策树的构建
- 27.ID3算法
- 28.决策树节点的划分
- 29.CART树
- 30.Bagging
- 31.Boosting:自适应提升算法
- 32.Gradient Boosting
- 33.线性模型:回归与分类的同一副骨架
- 34.最小二乘:把最优参数直接解出来
- 35.最小二乘法的几何与概率视角
- 36.极大似然:给最小二乘一个概率出身
- 37.逻辑回归(1):把线性模型接到分类上
- 38.逻辑回归(2):损失函数与多分类
- 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
- 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
- 41.优化方法(1):坐标下降与前向选择
- 42.优化方法(2):前向策略与最小角回归
- 43.拉格朗日乘子法(1):从无约束到等式约束
- 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
- 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
- 46.最大间隔:把分类问题变成优化问题
- 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
- 48.支持向量:边界上的少数派
- 49.SVM 计算过程:一笔到底算完,再走进核方法
- 50.贝叶斯公式:概率即信念
- 51.隐含变量和概率模型:给看不见的原因建模
- 52.EM 算法:期望与最大化的迭代
- 53.琴生不等式:EM 的数学钥匙
- 54.三个硬币问题:EM 的复盘与衔接
- 55.高斯混合模型:用隐变量解释双峰数据
- 56.隐马尔可夫模型
- 57.隐马尔可夫模型(2):前向算法与动态混合模型
- 58.隐马尔可夫模型在 NLP 中的运用
- 59.EM 算法:把「为什么收敛」推到底
- 60.HMM 在 NLP 中的运用:从分词到机器翻译
- 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
- 62.高维下的高斯模型
- 63.主题模型:从词袋到隐含主题
- 64.Dirichlet 分布(1)
- 65.Dirichlet 分布(2)
- 66.统计学习内容回顾
- 67.K-means 算法
- 68.PCA 降维
- 69.SVD 降维
- 70.从聚类到流形
- 71.局部线性嵌入
- 72.随机邻域嵌入:SNE 与 t-SNE
- 73.无参模型:让数据自己说话
- 74.高斯过程:函数空间里的正态分布
- 75.高斯过程(2):噪声、正则化与核的线性组合
- 76.神经网络简介:从感知器到自编码器
- 77.反向传播:把误差一层层传回去
- 78.卷积神经网络(CNN):让特征自己学出来
- 79.循环神经网络(RNN):给网络装上记忆
- 80.深度学习回顾:静态一类,动态一类
- 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
- 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传
朴素贝叶斯的实际应用
约 33 分钟
小象实战讲义 · 机器学习算法精讲
本节我们将通过一个具体的分类问题和一个经典的文本分类应用——垃圾邮件过滤,来深入理解朴素贝叶斯算法是如何从理论走向实践的。你将看到,即便基于一个“朴素”的独立性假设,该算法依然能在许多现实任务中取得令人惊讶的效果。
💡 核心导读
- 从天气到决策:通过经典的“打网球问题”,学习如何将朴素贝叶斯公式转化为具体的查表计算。
- 文本的向量化表示:了解如何将一封邮件(文本数据)转化为一个高维的特征向量,这是应用机器学习于文本领域的关键一步。
- 垃圾邮件分类实战:剖析如何用朴素贝叶斯构建一个垃圾邮件过滤器,并理解其核心计算步骤。
- 平滑技术的重要性:认识“零概率”问题,并学习拉普拉斯平滑等技巧如何避免计算崩溃。
1. 经典案例:打网球问题
我们从一个经典的机器学习数据集——“打网球问题”开始。这个例子最早出现在 Tom Mitchell 的《Machine Learning》一书中,它清晰地展示了朴素贝叶斯在离散特征分类问题上的应用。
1.1 问题描述
我们有一份过去14天的天气记录作为训练数据。每一天有四个天气特征:
- Outlook:天气概况,取值为
Sunny(晴)、Overcast(多云)、Rain(雨)。 - Temperature:温度,取值为
Hot(热)、Mild(温和)、Cool(凉)。 - Humidity:湿度,取值为
High(高)、Normal(正常)。 - Wind:风力,取值为
Strong(强)、Weak(弱)。
每一天还有一个目标值(标签):
- Play Tennis:是否去打网球,取值为
Yes或No。
在14天的数据中,有9天去打网球(Yes),5天没去(No)。
现在,给定一个新的一天,其特征为: Outlook=Sunny, Temperature=Cool, Humidity=High, Wind=Strong
我们的任务是:预测这一天是否会去打网球(Yes 或 No)。
1.2 朴素贝叶斯分类过程
根据朴素贝叶斯公式,对于新实例 ,我们计算其属于类别 的概率正比于: 其中 是特征数。
我们需要分别计算 和 的情况,然后比较大小,或者进一步归一化得到概率。
第一步:计算先验概率
第二步:计算条件概率 这需要通过“查数”从训练数据中统计得到。例如,对于 Outlook=Sunny:
- 在所有
Play=Yes的9天中,Outlook=Sunny出现了2天,所以 。 - 在所有
Play=No的5天中,Outlook=Sunny出现了3天,所以 。
同理,我们可以计算出所有需要的条件概率,汇总如下表:
| 特征/条件 | ||
|---|---|---|
| Outlook=Sunny | 2/9 | 3/5 |
| Temperature=Cool | 3/9 | 1/5 |
| Humidity=High | 3/9 | 4/5 |
| Wind=Strong | 3/9 | 3/5 |
第三步:代入公式计算 对于新实例 :
第四步:决策 比较两个未归一化的值,,因此我们预测这一天 不会 去打网球(No)。
如果需要得到归一化的概率:
可以看到,预测为 No 的概率远大于 Yes。
2. 迈向现实:垃圾邮件过滤
打网球问题展示了朴素贝叶斯的基本机制。现在,我们将其应用到一个更复杂、也更经典的现实问题:垃圾邮件(Spam Email)过滤。
先纠正一个常见误称:垃圾邮件叫 spam email,不叫 rubbish email——spam 这个词有段好玩的来历:它本是英国一部肥皂剧里最受欢迎的午餐肉罐头;剧中有个人进餐馆,发现菜单上所有的菜都是午餐肉——炒午餐肉、蒸午餐肉、凉拌午餐肉、炸午餐肉……everything is spam。后来人们就用 spam 形容那种”塞给你一大堆内容、但全是无意义重复、对你没有用”的邮件——比如群发的”诚邀您参加我们的会议”。它不是被扔掉的垃圾(rubbish),而是”量大而无用”的轰炸。顺带一提,朴素贝叶斯 Naive Bayes 简称 NB——讲师打趣说,这简称本身也是个”好词”。
2.1 问题定义与数据表示
- 输入 :一封电子邮件。
- 输出 :邮件的类别标签。通常 表示正常邮件(Non-Spam), 表示垃圾邮件(Spam)。(标签映射可以互换,不影响算法本质)。
关键挑战:如何将一封文本邮件表示成机器学习算法可以处理的数值向量 ?
我们采用 词袋模型(Bag of Words):
- 构建一个包含 个词的词典(Vocabulary),例如
[“a”, “apple”, “buy”, “the”, “饭”, …]。 - 对于任何一封邮件,我们将其表示为一个长度为 的向量 。
- 向量中的每个元素 表示词典中第 个词在这封邮件中出现的次数(或是否出现,即0或1)。
例如,一封内容为 “buy apple” 的邮件,在词典 [“a”, “apple”, “buy”, “the”] 下的表示可能是 [0, 1, 1, 0]。
这种表示方法完全忽略了词的顺序(语序),只关心词是否出现以及出现的频率,因此被称为“词袋”。
2.2 朴素贝叶斯垃圾邮件分类器
现在,问题转化为:给定一个用高维向量表示的新邮件 ,判断 和 哪个更大。
根据朴素贝叶斯公式: 这里 , 是词典大小。
模型参数估计:
先验概率 :在训练集中,统计垃圾邮件和正常邮件的数量。
条件概率 :这表示在类别 的邮件中,词典第 个词出现次数为 的概率。为了简化,我们常使用伯努利朴素贝叶斯,即只关心词是否出现( 或 )。那么我们需要估计:
- :在所有正常邮件中,第 个词出现过的邮件比例。
- :在所有垃圾邮件中,第 个词出现过的邮件比例。
具体计算时,对于每个词 :
注意:公式中分子加1,分母加2的操作称为 拉普拉斯平滑(Laplace Smoothing) 或 加一平滑。这是为了解决“零概率问题”。
2.3 零概率问题与平滑技术
零概率问题:如果新邮件中出现了一个词 ,而这个词在训练集的某个类别(比如正常邮件)中从未出现过,那么根据最大似然估计,。由于概率是连乘的,这会导致整个 的计算结果变为0,模型就无法做出有效判断。
平滑技术:为了避免零概率,我们在估计概率时给每个计数加上一个小的常数(通常是1)。这相当于假设每个词在任何类别的邮件中都至少“出现”了很小的次数。平滑后的概率估计虽然略有偏差,但保证了数值稳定性,并且通常能提升模型的泛化能力。
除了加一平滑,也可以直接为未出现的词赋予一个极小的固定概率(如 ,约万分之一——词典若有两万个词,加一平滑后的兜底概率也是这个量级)。
3. 算法实现与代码实践
下面,我们用一个极简化的示例,演示如何用纯 Python 标准库实现一个伯努利朴素贝叶斯分类器,用于区分两段文本的类别。
import math
from collections import defaultdict
class BernoulliNaiveBayes:
"""伯努利朴素贝叶斯分类器(极简演示版)"""
def __init__(self, alpha=1.0):
"""
初始化
:param alpha: 平滑参数(拉普拉斯平滑中的加数)
"""
self.alpha = alpha
self.prior = {} # 先验概率 log(P(y))
self.cond_prob = {} # 条件概率 log(P(x_j=1|y))
self.vocab = set() # 词典
def fit(self, X, y):
"""
训练模型
:param X: 文本列表,每个元素是一个字符串
:param y: 类别标签列表
"""
# 1. 构建词典
for doc in X:
self.vocab.update(doc.split())
vocab_list = list(self.vocab)
V = len(vocab_list)
word_to_idx = {word: i for i, word in enumerate(vocab_list)}
# 2. 计算先验概率(取对数)
class_counts = defaultdict(int)
for label in y:
class_counts[label] += 1
total_docs = len(y)
for c, count in class_counts.items():
self.prior[c] = math.log(count / total_docs)
# 3. 计算条件概率(取对数,并应用平滑)
# 初始化计数
doc_count = {c: 0 for c in class_counts} # 各类别文档数
word_count = {c: [0] * V for c in class_counts} # 各类别中词出现的文档数
for doc, label in zip(X, y):
doc_count[label] += 1
words_in_doc = set(doc.split()) # 伯努利模型:只关心是否出现
for word in words_in_doc:
if word in word_to_idx:
word_count[label][word_to_idx[word]] += 1
# 计算 log(P(word|class))
self.cond_prob = {c: [0.0] * V for c in class_counts}
for c in class_counts:
for j in range(V):
# 拉普拉斯平滑: (count + alpha) / (doc_count + 2*alpha)
prob = (word_count[c][j] + self.alpha) / (doc_count[c] + 2 * self.alpha)
self.cond_prob[c][j] = math.log(prob)
# 同时存储未出现的概率 log(1 - P(word|class)),预测时使用
self.cond_prob[c + '_absent'] = [math.log(1.0 - math.exp(lp)) for lp in self.cond_prob[c]]
# 保存映射
self.word_to_idx = word_to_idx
self.classes = list(class_counts.keys())
def predict(self, X):
"""
预测类别
:param X: 文本列表
:return: 预测的类别列表
"""
predictions = []
for doc in X:
words = set(doc.split())
scores = {}
for c in self.classes:
# 分数 = log(P(y)) + Σ log(P(x_j|y))
score = self.prior[c]
for word, idx in self.word_to_idx.items():
if word in words:
score += self.cond_prob[c][idx] # 词出现
else:
score += self.cond_prob[c + '_absent'][idx] # 词未出现
scores[c] = score
# 选择分数最高的类别
predictions.append(max(scores, key=scores.get))
return predictions
# ===== 示例:简单的电影评论情感分类 =====
# 训练数据:简单的正面/负面评论
train_texts = [
"good great wonderful fantastic", # 正面
"bad terrible awful horrible", # 负面
"good nice excellent", # 正面
"poor boring dull", # 负面
]
train_labels = ['pos', 'neg', 'pos', 'neg']
# 测试数据
test_texts = [
"good great", # 应预测为正面
"bad awful", # 应预测为负面
"good bad", # 混合词,看先验和具体概率
]
# 训练和预测
nb = BernoulliNaiveBayes(alpha=1.0)
nb.fit(train_texts, train_labels)
preds = nb.predict(test_texts)
print("训练数据词典:", sorted(nb.vocab))
print("\n测试结果:")
for text, label in zip(test_texts, preds):
print(f" 文本: '{text}' -> 预测: {label}")
# 输出关键概率(取指数恢复概率值)
print(f"\n先验概率:")
for c, logp in nb.prior.items():
print(f" P({c}) = {math.exp(logp):.3f}")
print(f"\n条件概率示例 P(word|pos) 和 P(word|neg):")
sample_words = ['good', 'bad', 'great', 'awful']
for w in sample_words:
if w in nb.word_to_idx:
idx = nb.word_to_idx[w]
p_pos = math.exp(nb.cond_prob['pos'][idx])
p_neg = math.exp(nb.cond_prob['neg'][idx])
print(f" P({w}|pos)={p_pos:.3f}, P({w}|neg)={p_neg:.3f}")训练数据词典: ['awful', 'bad', 'boring', 'dull', 'excellent', 'fantastic', 'good', 'great', 'horrible', 'nice', 'poor', 'terrible', 'wonderful']
测试结果:
文本: 'good great' -> 预测: pos
文本: 'bad awful' -> 预测: neg
文本: 'good bad' -> 预测: pos
先验概率:
P(pos) = 0.500
P(neg) = 0.500
条件概率示例 P(word|pos) 和 P(word|neg):
P(good|pos)=0.750, P(good|neg)=0.250
P(bad|pos)=0.250, P(bad|neg)=0.500
P(great|pos)=0.500, P(great|neg)=0.250
P(awful|pos)=0.250, P(awful|neg)=0.500代码解读与注意事项:
- 伯努利模型:代码实现了伯努利朴素贝叶斯,即特征 表示“词 是否在文档中出现”(0或1)。因此,在计算文档概率时,需要同时考虑词出现 和词未出现 的情况。
- 对数空间计算:概率连乘容易导致数值下溢(结果接近0)。因此,实际计算在对数空间进行,将连乘转化为连加,最后比较对数概率的大小。
- 平滑:
alpha=1.0对应标准的拉普拉斯加一平滑。 - 示例规模:示例数据极小,仅用于演示算法流程。真实垃圾邮件分类需要数万封邮件和数万规模的词典。
4. 中文垃圾邮件分类实战提示
在实际课程中,我们提供了一个完整的中文垃圾邮件分类项目代码包。它包含以下核心部分:
- 数据:约7000封垃圾邮件和7000封正常邮件用于训练,近400封未标注邮件用于测试。
- 文本预处理:使用
jieba库进行中文分词,并移除了“的”、“了”、“呢”等停用词。 - 核心算法:实现了与上述原理一致的朴素贝叶斯分类器,并针对中文特点进行了调整。
- 性能:在测试集上准确率可达约94%。
关键实践步骤:
- 分词:中文需要先分词。例如,“我喜欢机器学习”应分词为
[“我”, “喜欢”, “机器”, “学习”]。 - 特征选择:并非所有词都有区分度。可以过滤掉出现次数太少或太多的词,或使用信息增益等方法选择关键特征。
- 平滑策略:对于只在垃圾邮件里出现、正常邮件中从未见过的词,课程代码直接赋予固定小概率 0.01——这是平滑的另一种形式。
- 环境小坑:代码包里的中文停用词表文件名是中文的,个别系统读不出来时,把它改成任意英文文件名并保持调用路径一致即可。
提示:本课程提供的实战代码包含了更复杂的工程细节。建议学员在理解本节基本原理后,运行并研究该代码,以加深对朴素贝叶斯应用于真实文本任务的理解。
最后想想”朴素”假设在文本上有多离谱:我们说”我喜欢喝——“后面几乎必然跟着水、饮料、可乐、酒,而绝不会是”喝桌子、喝手机、喝电源”——词与词之间显然高度相关。朴素贝叶斯却假设每个词相互独立,这是非常强的假设;但正如课程实战所示,一个”不合理”的假设配上高效的计算,照样能做到约 94% 的准确率。
📝 动手练一练
- 概念辨析:在“打网球问题”中,如果我们计算得到 和 ,我们已经可以做出决策。为什么通常还需要将它们归一化为概率 和 ?在什么场景下归一化是必要的?
👉 点击查看参考答案
参考答案: 归一化在以下场景是必要的:
- 需要输出概率值:当系统不仅需要输出类别,还需要输出属于该类别的置信度或概率时。例如,在垃圾邮件过滤中,我们可能想告诉用户“此邮件有79.5%的可能性是垃圾邮件”,而不仅仅是“这是垃圾邮件”。
- 多分类问题:当类别超过两个时,我们需要比较所有类别的概率,而归一化后的概率之和为1,更直观。
- 模型集成或后续处理:如果朴素贝叶斯的输出要作为其他模型的输入,或者需要与另一个独立模型的输出进行比较、加权平均,那么标准化的概率是必需的。
在仅需要硬分类决策的场景下,比较未归一化的“得分”(正比于概率)已经足够,因为归一化分母相同,不影响大小比较。
- 代码修改:在上一节的代码示例中,我们实现了伯努利模型(只关心词是否出现)。请思考如何修改代码,使其实现多项式朴素贝叶斯(Multinomial Naive Bayes)模型,即特征值 表示词 在文档中出现的次数。你只需要描述
fit和predict方法中核心计算部分需要如何改变即可。
👉 点击查看参考答案
参考答案: 从伯努利模型改为多项式模型,核心变化在于条件概率 的含义和计算方式:
在 fit 方法中:
- 计数改变:不再统计“包含词 的文档数”,而是统计“类别 的所有文档中,词 出现的总次数”。
- 公式改变:条件概率估计变为: 这里 是词典大小, 是平滑参数。
- 存储改变:
cond_prob[c][j]存储的不再是 ,而是 。
在 predict 方法中:
- 计算改变:对于一篇文档,其属于类别 的得分计算变为: 其中 是文档中词 出现的次数。这里是对文档中每一个出现的词,将其对数概率乘以出现次数后累加。注意,多项式模型通常不考虑词未出现的情况(即 的项,因为 乘以0后贡献为0)。
简单来说,伯努利模型是“文档-词”的二维关系(是否出现),而多项式模型是“词”本身在类别中的分布,预测时用词频进行加权。
本章小结
本节我们深入探讨了朴素贝叶斯算法的两个核心应用。从结构清晰的“打网球”决策问题,到高维稀疏的文本分类任务,朴素贝叶斯展现了其独特的魅力:
- 原理直观:基于贝叶斯定理,将后验概率分解为先验概率与条件概率的乘积。
- 实现简单:核心参数(先验和条件概率)可以通过简单的计数和比例计算得到,训练速度极快。
- 效果显著:尽管其“特征条件独立”的假设在现实中往往不成立(尤其是对于文本),但在许多分类任务上,尤其是文本分类,它仍然是强大且高效的基线模型。
朴素贝叶斯的成功也提醒我们,在机器学习中,一个合理的、即使不完全正确的假设,结合高效的计算,往往能带来实用的解决方案。
📋 行动清单
学完本节,你可以立即尝试:
- 运行示例代码:将本节提供的伯努利朴素贝叶斯代码复制到 Python 环境中运行,并尝试修改训练数据,观察预测结果如何变化。
- 分析平滑影响:在示例代码中,将
alpha参数改为0(无平滑)和10(强平滑),重新训练并预测测试数据中的第三句“good bad”,观察预测类别和概率值的变化,理解平滑的作用。 - 构思你的分类器:思考一个你可以用朴素贝叶斯解决的简单二分类问题(例如,根据几个关键词判断一条短信是否是促销信息),并设计出它的特征表示方法。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。