查看全课大纲(第 23 / 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 回传
朴素贝叶斯
约 12 分钟
小象实战讲义 · 机器学习算法精讲
先说名字。“Naive”本是法文词,意思就是”非常单纯”——用现在的话讲,这是”傻白甜”版的贝叶斯;直译太怪,中文就翻成了”朴素贝叶斯”。Bayes 则是 18 世纪的一位英国牧师,他的名字后来被用到了各个地方——讲师的学生里就有人把自家的狗起名叫”贝叶斯”,挺酷。本节会看到:它其实一点也不朴素,只是很 naive。
💡 核心导读
- 贝叶斯定理回顾:从条件概率出发,理解如何将后验概率转化为先验概率与似然的乘积。
- “朴素”假设:掌握“特征条件独立”这一核心假设,理解它如何将高维联合概率分解为低维条件概率的乘积。
- 模型推导:从贝叶斯定理出发,逐步推导出朴素贝叶斯分类器的决策公式。
- 数值技巧:了解在实际计算中,为何常使用对数概率来避免数值下溢问题。
- 应用示例:通过一个简单的二分类例子,直观感受朴素贝叶斯的计算流程。
从贝叶斯定理到分类器
我们首先回顾贝叶斯定理(Bayes’ rule)。假设我们有一个分类问题,类别标签为 (例如, 代表“小狗”, 代表“小猫”)。对于一个具有 个特征的新数据点 ——比如一张图片,讲师算了笔账:一张 的小图,共 400 个像素,再乘上 RGB 3 个颜色通道,就是一个 1200 维的向量。我们想知道它属于类别 的概率 ,即后验概率。
根据贝叶斯定理,这个后验概率可以表示为: 其中:
- 是先验概率(prior),表示在观测到数据前,类别 本身出现的概率。
- 是似然(likelihood),表示在类别 的条件下,观测到数据 的概率。
- 是证据(evidence),对于给定的 ,它是一个与 无关的常数,起到归一化的作用。
在分类任务中,我们的目标是找到使后验概率 最大的那个类别 。由于 对所有类别都是相同的,因此最大化后验概率等价于最大化分子: 这就是最大后验概率(MAP)决策规则。
顺便把分母也写开——这是实践中经常用的形式。以两类为例,由 ,可以把归一化常数展开成两个分子的和:
它长得不如贝叶斯定理原形眼熟,但正因为分母对 不产生任何影响(换哪个类别它都一样),做分类时我们才敢把它整个扔掉、只比分子。
“朴素”假设:特征条件独立
现在问题的核心变成了如何估计似然 。 是一个高维向量,直接估计其联合分布 非常困难,尤其是在数据有限的情况下(这被称为“维数灾难”)。
朴素贝叶斯的核心——“朴素”假设(Naive Assumption)——就是为了解决这个问题而提出的。它假设:在给定类别 的条件下,所有特征 之间是相互独立的。
用数学公式表示这个假设就是:
这个假设非常强,在现实中很少完全成立(例如,一张图片中相邻像素的颜色通常是相关的)。因此我们说这个模型很“天真”(naive)。然而,尽管这个假设过于简化,朴素贝叶斯在许多实际任务(如文本分类)中却表现出了惊人的效果,并且因其计算简单、效率高而广受欢迎。
朴素贝叶斯分类器推导
将“朴素”假设代入我们的 MAP 决策规则中,我们得到朴素贝叶斯分类器的决策公式:
在实际操作中, 和 都是从训练数据中估计得到的:
- 先验概率 :用训练集中类别 的样本数除以总样本数来估计。讲师的例子:训练集里有 10 张小猫、20 张小狗,那么 ,。
- 条件概率 :对于离散特征,用训练集中属于类别 且第 个特征取值为 的样本数,除以属于类别 的样本总数来估计。对于连续特征,通常假设其服从某种分布(如高斯分布),然后估计该分布的参数。
对数概率:避免数值下溢的实用技巧
直接计算多个概率的乘积 会导致数值非常小,可能超出计算机浮点数的表示范围,造成数值下溢。一个标准的解决方案是取对数。由于对数函数是单调递增的,最大化一个正数的乘积等价于最大化其对数之和: 这样就将乘法转换成了加法,有效避免了数值下溢问题,并且计算上也更加高效。
下面,我们通过一个极简的代码示例来演示朴素贝叶斯的核心计算逻辑。我们构造一个只有两个样本、两个二值特征的数据集。
import math
from collections import defaultdict
# 构造一个极简的训练数据:每个样本由特征列表和标签组成
# 特征为二值 [0, 1],标签为 0 或 1
train_data = [
([1, 0], 0), # 样本1,特征为[1,0],属于类别0
([0, 1], 1), # 样本2,特征为[0,1],属于类别1
]
# 手动计算先验概率 P(y) 和条件概率 P(x_i=1 | y) 的对数
# 注意:这是一个演示计算过程的简化版本,条件概率部分含拉普拉斯平滑(见下方公式)。
# 统计各类别样本数
label_count = {0: 0, 1: 0}
for _, label in train_data:
label_count[label] += 1
total_samples = len(train_data)
# 计算先验概率的对数 log P(y)
log_prior = {}
for label, count in label_count.items():
log_prior[label] = math.log(count / total_samples)
# 统计每个类别下,每个特征取值为1的次数
# feature_count[label][feature_index] = count of (x_i == 1)
feature_count = {0: defaultdict(int), 1: defaultdict(int)}
for features, label in train_data:
for i, feat_val in enumerate(features):
if feat_val == 1:
feature_count[label][i] += 1
# 计算条件概率的对数 log P(x_i=1 | y)
# 使用拉普拉斯平滑避免零概率: (count + 1) / (label_count + 2)
log_cond_prob = {0: {}, 1: {}}
for label in [0, 1]:
for i in range(2): # 我们只有两个特征
count = feature_count[label][i]
# 拉普拉斯平滑
prob = (count + 1) / (label_count[label] + 2)
log_cond_prob[label][i] = math.log(prob)
print("先验概率的对数:", log_prior)
print("条件概率 P(x_i=1|y) 的对数:")
for label in [0, 1]:
print(f" 类别 {label}:", dict(log_cond_prob[label]))先验概率的对数: {0: -0.6931471805599453, 1: -0.6931471805599453}
条件概率 P(x_i=1|y) 的对数:
类别 0: {0: -0.40546510810816444, 1: -1.0986122886681098}
类别 1: {0: -1.0986122886681098, 1: -0.40546510810816444}现在,我们实现一个完整的、包含平滑处理的朴素贝叶斯训练和预测函数,并用一个简单的新样本进行测试。
import math
from collections import defaultdict
def train_naive_bayes(data, alpha=1.0):
"""
训练朴素贝叶斯模型(适用于二值特征)。
data: 列表,每个元素为 (特征列表, 标签)
alpha: 拉普拉斯平滑参数
返回:先验概率字典,条件概率字典
"""
label_counts = defaultdict(int)
feature_counts = defaultdict(lambda: defaultdict(int)) # feature_counts[label][feature_idx]
feature_present_counts = defaultdict(int) # 记录每个类别下,特征值为1的总次数(用于平滑分母)
total_samples = len(data)
num_features = len(data[0][0]) if data else 0
# 统计
for features, label in data:
label_counts[label] += 1
for i, feat_val in enumerate(features):
if feat_val == 1:
feature_counts[label][i] += 1
feature_present_counts[label] += 1
# 计算先验概率 log P(y)
log_prior = {}
for label, count in label_counts.items():
log_prior[label] = math.log(count / total_samples)
# 计算条件概率 log P(x_i=1 | y) 并应用拉普拉斯平滑
# P(x_i=1|y) = (count(x_i=1, y) + alpha) / (count(y) + alpha * num_features * 2)
# 这里假设特征为二值,所以可能取值有2种(0和1)。平滑分母加 alpha * 2。
# 更通用的公式是 (count + alpha) / (count(y) + alpha * |V|),其中|V|是特征i的可能取值数。
log_cond_prob = defaultdict(dict)
for label in label_counts:
denominator = label_counts[label] + alpha * 2 # 二值特征,|V|=2
for i in range(num_features):
count = feature_counts[label].get(i, 0)
prob = (count + alpha) / denominator
# 对指数运算的输入进行限幅,防止math.log(0)或极端值
prob = max(min(prob, 1 - 1e-15), 1e-15)
log_cond_prob[label][i] = math.log(prob)
return log_prior, log_cond_prob
def predict_naive_bayes(features, log_prior, log_cond_prob):
"""
使用训练好的模型预测样本标签。
features: 特征列表
返回:预测的标签
"""
best_label = None
best_score = -float('inf')
for label in log_prior:
score = log_prior[label] # 先验项
for i, feat_val in enumerate(features):
# 注意:我们只存储了 P(x_i=1|y) 的对数。
# 对于二值特征,P(x_i=0|y) = 1 - P(x_i=1|y)
if feat_val == 1:
score += log_cond_prob[label][i]
else:
# 计算 log(1 - exp(log_p1)),其中log_p1 = log(P(x_i=1|y))
log_p1 = log_cond_prob[label][i]
# 防止数值问题
p1 = math.exp(log_p1)
p0 = 1 - p1
p0 = max(min(p0, 1 - 1e-15), 1e-15)
score += math.log(p0)
if score > best_score:
best_score = score
best_label = label
return best_label
# 使用与之前相同的训练数据
train_data = [
([1, 0], 0),
([0, 1], 1),
]
# 训练模型
log_prior, log_cond_prob = train_naive_bayes(train_data, alpha=1.0)
print("训练完成。")
print("先验概率对数:", dict(log_prior))
print("条件概率对数结构(类别->特征索引->值):", {k: dict(v) for k, v in log_cond_prob.items()})
# 预测新样本
new_sample = [1, 0] # 特征与第一个训练样本相同
predicted_label = predict_naive_bayes(new_sample, log_prior, log_cond_prob)
print(f"\n新样本 {new_sample} 的预测标签为: {predicted_label}")训练完成。
先验概率对数: {0: -0.6931471805599453, 1: -0.6931471805599453}
条件概率对数结构(类别->特征索引->值): {0: {0: -0.40546510810816444, 1: -1.0986122886681098}, 1: {0: -1.0986122886681098, 1: -0.40546510810816444}}
新样本 [1, 0] 的预测标签为: 0可以看到,对于特征为 [1, 0] 的新样本,模型正确地预测其属于类别0。你可以尝试修改 new_sample 为 [0, 1] 或 [1, 1],观察预测结果。
📝 动手练一练
- 概念辨析:朴素贝叶斯中的“朴素”假设在现实世界中常常不成立,为什么该算法仍然能在许多任务(如垃圾邮件过滤)中取得不错的效果?
👉 点击查看参考答案
“朴素”假设虽然简化了模型,但它也带来了几个好处,这些好处在某些问题领域抵消了假设不成立带来的负面影响:
- 高效性:将高维联合概率估计分解为多个一维概率估计,极大降低了模型复杂度,使得训练和预测速度非常快。
- 数据效率:相对于需要估计完整联合分布的模型,朴素贝叶斯在数据量较少时也能获得相对稳定的概率估计,因为它只需要估计每个特征单独的条件分布。
- 鲁棒性:对于分类任务,我们最终关心的是决策边界。即使概率估计不是绝对精确,只要各类别之间的相对大小关系正确,分类结果就可以是正确的。在许多实际问题(特别是文本分类)中,特征之间的依赖关系可能对区分不同类别的贡献不大,独立性假设带来的误差在可接受范围内。
- 可解释性:模型参数(先验概率和条件概率)具有明确的统计意义,易于理解和解释。
- 代码实践:在上述预测函数
predict_naive_bayes中,我们分别处理了特征值为1和0的情况。如果特征是多值的(例如,取值为a,b,c),训练和预测函数应该如何修改?请描述主要修改思路。
👉 点击查看参考答案
主要修改思路如下:
- 训练阶段:
feature_counts的数据结构需要改变,以记录每个特征在每个类别下取每个特定值的次数。例如,可以使用feature_counts[label][feature_idx][feature_value] = count。- 计算条件概率时,对于每个特征 、每个类别 、每个特征值 ,计算 。
- 拉普拉斯平滑的分母需要调整:
denominator = label_counts[label] + alpha * num_possible_values[i],其中num_possible_values[i]是特征 可能取值的个数。
- 预测阶段:
- 对于待预测样本的每个特征 ,取其实际值 。
- 直接从
log_cond_prob[label][feature_idx][feature_value]中取出对应的对数条件概率,加到得分中即可,无需再区分0和1。
- 关键点:核心是将二值特征的“非0即1”逻辑,推广到为每个特征维护一个可能取值到概率的映射。
本章小结
本节我们深入探讨了朴素贝叶斯分类器。我们从贝叶斯定理出发,引出了最大后验概率决策规则。为了克服高维数据下联合概率估计的难题,我们引入了“特征条件独立”这一核心假设,从而推导出朴素贝叶斯的最终形式。我们还讨论了使用对数概率来避免数值计算问题的实用技巧,并通过手写代码实现了基础的二值特征朴素贝叶斯分类器。
朴素贝叶斯模型简单、高效,且在许多现实任务中表现稳健,是机器学习入门必须掌握的经典算法之一。
📋 行动清单
- 尝试运行讲义中的代码,并修改
new_sample的特征值,观察预测结果的变化。 - 思考一个你熟悉的应用场景(如判断一条新闻属于体育还是科技),用文字描述如何为该场景构建朴素贝叶斯分类器(需要哪些特征?特征如何表示?)。
- 了解朴素贝叶斯的不同变体,如用于连续特征的高斯朴素贝叶斯,以及用于文本处理的多项朴素贝叶斯。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。