机器学习数学推导(九)朴素贝叶斯
Chen Kai BOSS

朴素贝叶斯(Naive Bayes)是最简单也最优雅的概率分类器——它基于贝叶斯定理和条件独立假设,将复杂的联合概率分解为简单的条件概率乘积,从而实现高效的分类学习。尽管"朴素"假设在现实中往往不成立,朴素贝叶斯却在文本分类、垃圾邮件过滤、情感分析等任务中展现出惊人的效果。本章将系统推导朴素贝叶斯的理论基础、参数估计方法、平滑技术与性能分析。

贝叶斯决策论基础

贝叶斯定理与后验概率

贝叶斯定理是概率论的核心公式,描述了如何通过观测数据更新先验信念:

其中: -后验概率( Posterior),观测到后类别的概率 -类条件概率( Class-conditional Likelihood),类别下观测到的概率 -先验概率( Prior),类别的先验分布 -证据( Evidence),的边缘概率

推导:由条件概率定义和全概率公式:

整理即得贝叶斯定理。

贝叶斯最优分类器

对于分类任务,贝叶斯决策论给出最优决策规则:选择后验概率最大的类别:

由于分母对所有类别相同,等价于:

这就是最大后验概率( Maximum A Posteriori, MAP)准则。

理论保证:贝叶斯最优分类器最小化分类错误率:

这是任何分类器能达到的最小误差( Bayes Error)。

判别模型与生成模型

判别模型( Discriminative Model):直接学习 - 例如:逻辑回归、支持向量机 - 优点:专注决策边界,通常性能更好 - 缺点:无法生成样本,不能处理部分观测

生成模型( Generative Model):学习联合分布,再通过贝叶斯定理得到 - 例如:朴素贝叶斯、隐马尔可夫模型 - 优点:可生成样本,处理缺失数据,融入先验知识 - 缺点:需要更多假设,可能有偏差

朴素贝叶斯是典型的生成模型。

朴素贝叶斯分类器

条件独立假设

对于高维特征,类条件概率的参数量随维度指数增长,难以估计。

朴素贝叶斯假设( Naive Bayes Assumption):在给定类别的条件下,各特征相互独立:

直觉解释:类别是"原因",特征是"结果"。假设各个"结果"在原因确定后独立发生。

朴素贝叶斯分类规则

结合贝叶斯定理和条件独立假设:

分类器

为避免数值下溢,实践中使用对数概率:

下图展示了贝叶斯定理的直观理解——如何从先验概率和似然函数得到后验概率,以及条件独立假设的可视化:

Bayes Theorem Visualization

下图对比了真实的特征相关分布(左图)与朴素贝叶斯的条件独立假设(右图)。尽管朴素假设很强,但在实践中朴素贝叶斯往往表现出色,因为分类任务只需要正确的相对概率排序,而不需要精确的概率值:

Gaussian Naive Bayes

参数估计:极大似然法

给定训练集,需要估计:

  • 先验概率 - 类条件概率

先验概率估计

其中是类别的样本数。

类条件概率估计:根据特征类型不同:

离散特征

对于取值集合的离散特征

即:类别中特征取值为的样本比例。

连续特征

假设类条件概率服从高斯分布:

参数估计:

拉普拉斯平滑

零概率问题:若训练集中某个类别未出现特征值,则,导致整个后验概率为 0,无法分类。

拉普拉斯平滑( Laplace Smoothing):为每个计数加上伪计数(通常):

其中是特征的取值个数。

先验概率平滑

其中是类别数。

贝叶斯解释:拉普拉斯平滑等价于在多项分布上引入 Dirichlet 先验的贝叶斯估计。

朴素贝叶斯的三种模型

多项式朴素贝叶斯( Multinomial NB)

适用于离散计数特征,如文本数据的词频。

模型:假设特征是词频向量,表示词出现的次数。类条件概率为多项分布:

其中

下图展示了文本分类中朴素贝叶斯的应用——从词频统计到分类决策的完整流程:

Text Classification

下图展示了拉普拉斯平滑的效果——如何解决零概率问题:

Laplace Smoothing

下图对比了朴素贝叶斯与完整贝叶斯分类器的差异——朴素贝叶斯假设特征独立(对角协方差),而完整贝叶斯捕获特征相关性:

Naive vs Full Bayes

参数估计

即:类别中词的频率占总词频的比例。

拉普拉斯平滑

伯努利朴素贝叶斯( Bernoulli NB)

适用于二值特征,如文档中词的有无。

模型,类条件概率为伯努利分布:

其中

参数估计$

与多项式 NB 的区别: - 多项式 NB 考虑词频(出现几次) - 伯努利 NB 只考虑词的存在(出现与否)

对于短文本,伯努利 NB 可能更好;对于长文本,多项式 NB 通常更优。

高斯朴素贝叶斯( Gaussian NB)

适用于连续特征,假设类条件概率为高斯分布(见前文)。

应用场景:传感器数据、生物医学特征等。

参数估计:极大似然估计得到均值_{jk}(见前文)。

文本分类实例

垃圾邮件过滤

问题:给定邮件文本,判断是否为垃圾邮件(二分类)。

特征表示:Bag-of-Words(词袋模型)

  • 词汇表:
  • 文档表示:是词的出现次数

训练: 1. 计算先验概率: 2. 对每个词和类别,计算

预测

示例: - 训练集: 1000 封邮件( 600 垃圾, 400 正常) - 词汇表:{"free", "money", "meeting", "schedule", ...} - 测试邮件:"free money now"

若垃圾邮件中"free"和"money"出现频率远高于正常邮件,则分类为垃圾。

完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import numpy as np
from collections import defaultdict

class NaiveBayes:
def __init__(self, alpha=1.0):
"""
参数:
alpha: 拉普拉斯平滑参数
"""
self.alpha = alpha
self.classes = None
self.class_prior = {}
self.feature_prob = defaultdict(lambda: defaultdict(dict))

def fit(self, X, y):
"""
训练朴素贝叶斯分类器

参数:
X: ndarray, shape (N, d), 离散特征
y: ndarray, shape (N,), 类别标签
"""
N, d = X.shape
self.classes = np.unique(y)
K = len(self.classes)

# 计算先验概率
for c in self.classes:
N_c = np.sum(y == c)
self.class_prior[c] = (N_c + self.alpha) / (N + K * self.alpha)

# 计算类条件概率
for j in range(d):
values = np.unique(X[:, j])
S_j = len(values)

for c in self.classes:
X_c = X[y == c]
N_c = len(X_c)

for val in values:
count = np.sum(X_c[:, j] == val)
# 拉普拉斯平滑
self.feature_prob[j][c][val] = (count + self.alpha) / (N_c + S_j * self.alpha)

return self

def predict_log_proba(self, X):
"""
计算对数后验概率

返回:
log_proba: ndarray, shape (N, K)
"""
N = X.shape[0]
K = len(self.classes)
log_proba = np.zeros((N, K))

for idx, c in enumerate(self.classes):
# 对数先验概率
log_proba[:, idx] = np.log(self.class_prior[c])

# 对数类条件概率
for j in range(X.shape[1]):
for i in range(N):
val = X[i, j]
if val in self.feature_prob[j][c]:
log_proba[i, idx] += np.log(self.feature_prob[j][c][val])
else:
# 未见过的特征值,使用平滑后的最小概率
N_c = sum(y == c)
S_j = len(self.feature_prob[j][c])
log_proba[i, idx] += np.log(self.alpha / (N_c + S_j * self.alpha))

return log_proba

def predict(self, X):
"""预测类别"""
log_proba = self.predict_log_proba(X)
return self.classes[np.argmax(log_proba, axis=1)]

def score(self, X, y):
"""计算准确率"""
return np.mean(self.predict(X) == y)

class GaussianNB:
"""高斯朴素贝叶斯(连续特征)"""
def __init__(self):
self.classes = None
self.class_prior = {}
self.theta = {} # 均值
self.sigma = {} # 标准差

def fit(self, X, y):
N, d = X.shape
self.classes = np.unique(y)

for c in self.classes:
X_c = X[y == c]
self.class_prior[c] = len(X_c) / N
self.theta[c] = np.mean(X_c, axis=0)
self.sigma[c] = np.std(X_c, axis=0) + 1e-9 # 避免除零

return self

def _gaussian_prob(self, x, mean, std):
"""高斯概率密度"""
exponent = -((x - mean) ** 2) / (2 * std ** 2)
return np.exp(exponent) / (np.sqrt(2 * np.pi) * std)

def predict_log_proba(self, X):
N = X.shape[0]
K = len(self.classes)
log_proba = np.zeros((N, K))

for idx, c in enumerate(self.classes):
log_proba[:, idx] = np.log(self.class_prior[c])
for j in range(X.shape[1]):
prob = self._gaussian_prob(X[:, j], self.theta[c][j], self.sigma[c][j])
log_proba[:, idx] += np.log(prob + 1e-9)

return log_proba

def predict(self, X):
log_proba = self.predict_log_proba(X)
return self.classes[np.argmax(log_proba, axis=1)]

def score(self, X, y):
return np.mean(self.predict(X) == y)

# 文本分类示例
class MultinomialNB:
"""多项式朴素贝叶斯(词频特征)"""
def __init__(self, alpha=1.0):
self.alpha = alpha
self.classes = None
self.class_prior = {}
self.feature_log_prob = {}

def fit(self, X, y):
"""
参数:
X: ndarray, shape (N, d), 词频矩阵
y: ndarray, shape (N,), 类别标签
"""
N, d = X.shape
self.classes = np.unique(y)

for c in self.classes:
X_c = X[y == c]
N_c = len(X_c)

# 先验概率
self.class_prior[c] = N_c / N

# 类条件概率(词的概率分布)
word_counts = np.sum(X_c, axis=0) # 每个词的总频次
total_count = np.sum(word_counts) # 总词数

# 拉普拉斯平滑
self.feature_log_prob[c] = np.log((word_counts + self.alpha) / (total_count + d * self.alpha))

return self

def predict_log_proba(self, X):
N = X.shape[0]
K = len(self.classes)
log_proba = np.zeros((N, K))

for idx, c in enumerate(self.classes):
# 对数先验
log_proba[:, idx] = np.log(self.class_prior[c])
# 对数似然: sum_{j} x^{(j)} * log(theta_j)
log_proba[:, idx] += X @ self.feature_log_prob[c]

return log_proba

def predict(self, X):
log_proba = self.predict_log_proba(X)
return self.classes[np.argmax(log_proba, axis=1)]

def score(self, X, y):
return np.mean(self.predict(X) == y)

# 示例使用
if __name__ == '__main__':
# 离散特征示例
print("=== 离散特征朴素贝叶斯 ===")
X_discrete = np.array([
[0, 1, 0],
[1, 1, 1],
[1, 0, 0],
[0, 1, 1],
[1, 1, 0],
[0, 0, 1]
])
y_discrete = np.array([0, 1, 1, 0, 1, 0])

nb = NaiveBayes(alpha=1.0)
nb.fit(X_discrete, y_discrete)
print(f"训练准确率: {nb.score(X_discrete, y_discrete):.4f}")
print(f"预测: {nb.predict(np.array([[1, 1, 0], [0, 0, 0]]))}")

# 连续特征示例
print("\n=== 高斯朴素贝叶斯 ===")
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42
)

gnb = GaussianNB()
gnb.fit(X_train, y_train)
print(f"测试准确率: {gnb.score(X_test, y_test):.4f}")

# 文本分类示例(词频)
print("\n=== 多项式朴素贝叶斯(模拟文本数据)===")
# 模拟词频矩阵: 5 个文档, 10 个词
X_text = np.array([
[3, 5, 0, 1, 0, 0, 2, 0, 0, 0], # spam
[0, 1, 2, 4, 3, 5, 0, 0, 0, 0], # ham
[4, 6, 0, 2, 0, 0, 3, 1, 0, 0], # spam
[0, 0, 3, 5, 4, 6, 0, 0, 0, 1], # ham
[5, 7, 0, 1, 0, 0, 4, 2, 0, 0], # spam
])
y_text = np.array([1, 0, 1, 0, 1]) # 1: spam, 0: ham

mnb = MultinomialNB(alpha=1.0)
mnb.fit(X_text, y_text)
print(f"训练准确率: {mnb.score(X_text, y_text):.4f}")

# 测试新文档
X_new = np.array([[4, 5, 0, 2, 0, 0, 3, 0, 0, 0]])
print(f"新文档预测: {mnb.predict(X_new)} (1=spam, 0=ham)")

理论分析与性能

条件独立假设的影响

假设不成立时的表现:尽管现实中特征往往相关,朴素贝叶斯仍可能表现良好。原因: 1. 决策鲁棒性:分类只需后验概率的相对大小,不需要精确值 2. 参数估计稳定:独立假设大幅减少参数量,降低方差 3. 偏差-方差权衡:增加偏差,但显著降低方差

理论分析( Domingos & Pazzani, 1997):即使条件独立假设错误,朴素贝叶斯仍可达到最优分类(零-一损失下),只要:$

即后验概率的排序正确,无需绝对值准确。

样本复杂度

优势:参数量为个类别,个特征),远小于联合分布的是特征的取值数)。

样本复杂度:在 PAC 学习框架下,朴素贝叶斯只需个样本达到误差,而无条件独立假设需要

属性重要性与特征选择

互信息:特征与类别的互信息衡量其重要性:

特征选择:保留互信息最大的前个特征,减少噪声和计算量。

Q&A 精选

Q1:为什么称为"朴素"贝叶斯?

A:"朴素"指条件独立假设——假设各特征在给定类别下相互独立。这一假设在现实中往往不成立(如"北京"和"大学"在文档中高度相关),但大幅简化计算,使得模型简单高效。


Q2:朴素贝叶斯与逻辑回归的关系?

A:在二分类、高斯假设下,朴素贝叶斯等价于逻辑回归的特殊情况:

其中权重由朴素贝叶斯的参数解析确定。差异:朴素贝叶斯是生成模型(联合分布),逻辑回归是判别模型(条件分布)。


Q3:拉普拉斯平滑的本质是什么?

A:从贝叶斯观点,拉普拉斯平滑是引入均匀 Dirichlet 先验(, , )对应均匀先验,> 1倾向于稀疏分布。


Q4:如何处理连续特征的非高斯分布?

A: 1. 核密度估计:用非参数方法估计 2. 离散化:将连续特征分箱,转为离散特征 3. 变换:对数变换、 Box-Cox 变换使分布接近高斯


Q5:朴素贝叶斯能输出概率吗?

A:能,但概率值往往不准确(过于极端)。原因:条件独立假设导致对数概率累加,放大偏差。若需要准确概率,考虑概率校准( Platt Scaling 、 Isotonic Regression)。


Q6:多项式 NB 与伯努利 NB 如何选择?

A: - 文档长度长:多项式 NB(词频信息丰富) - 文档长度短:伯努利 NB(有无比频率更重要) - 稀疏数据:伯努利 NB(避免零概率)

实践中,交叉验证选择。


Q7:朴素贝叶斯能处理缺失值吗?

A:能,且很自然:预测时忽略缺失特征,只计算已观测特征的对数概率:


Q8:朴素贝叶斯的时间复杂度?

A: - 训练(遍历数据统计计数) - 预测个类别,个特征)

远快于 SVM 、神经网络,适合大规模文本分类。


Q9:如何可视化朴素贝叶斯的决策边界?

A:对于 2D 连续特征:

1
2
3
4
5
6
7
8
9
10
11
def plot_nb_boundary(nb, X, y):
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
np.linspace(y_min, y_max, 200))
Z = nb.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, alpha=0.3, cmap='viridis')
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', cmap='viridis')
plt.show()

Q10:朴素贝叶斯的优缺点总结?

A: 优点: - 简单高效,易于实现 - 小样本表现良好 - 自然处理多分类 - 可解释性强 - 支持在线学习

缺点: - 条件独立假设往往不成立 - 对特征相关性敏感 - 概率估计不准确 - 无法学习特征交互


✏️ 练习题与解答

练习 1:后验概率计算

题目:某疾病检测,患病率,测试准确率(敏感度),(特异度)。若某人检测呈阳性,求其真正患病的概率

解答

由贝叶斯定理:

计算边缘概率:

因此:

尽管测试准确率很高,但由于患病率很低(先验概率小),阳性预测值仍然较低。这是贝叶斯推理的经典案例。


练习 2:拉普拉斯平滑

题目:文本分类中,正类有100个词,其中"good"出现15次,"bad"出现0次。负类有80个词,"good"出现2次,"bad"出现10次。词汇表大小。使用拉普拉斯平滑()计算

解答

正类中"good"的概率:

正类中"bad"的概率:

没有平滑时,,会导致整个样本的概率为0(零概率问题)。拉普拉斯平滑确保所有词都有非零概率。


练习 3:朴素假设的影响

题目:设二维特征服从联合高斯分布,类1的协方差矩阵为(高度相关)。朴素贝叶斯假设对角协方差。讨论这会如何影响分类决策。

解答

真实分布中高度正相关()。朴素贝叶斯忽略这种相关性,假设二者独立。

影响: - 朴素贝叶斯的决策边界会与真实最优边界偏离 - 对于位于协方差椭圆长轴方向的样本,朴素贝叶斯可能低估其概率 - 但在实践中,只要各类的相关性结构相似,朴素贝叶斯仍可能得到正确的相对排序

这解释了为什么朴素贝叶斯在特征相关的情况下仍常表现良好——分类只需正确的排序,不需要精确的概率值。


练习 4:高斯朴素贝叶斯的决策边界

题目:证明高斯朴素贝叶斯的决策边界是线性的。

解答

对于二分类,决策边界为,即:

取对数:

代入高斯密度:

展开并整理,得到的二次项和一次项。当两类方差相等()时,二次项抵消,决策边界简化为线性函数


练习 5:多项式 vs 伯努利朴素贝叶斯

题目:文档"good good bad",用多项式NB和伯努利NB表示。设正类先验。计算(忽略归一化常数)。

解答

多项式NB(考虑词频):

伯努利NB(只考虑有无):

简化(假设其他词概率可忽略):

多项式NB对重复出现的词("good"出现2次)更敏感,适合词频特征。伯努利NB只关心词是否出现,适合二值特征。

参考文献

  1. Domingos, P., & Pazzani, M. (1997). On the optimality of the simple Bayesian classifier under zero-one loss. Machine Learning, 29(2-3), 103-130.
  2. McCallum, A., & Nigam, K. (1998). A comparison of event models for naive Bayes text classification. AAAI Workshop on Learning for Text Categorization.
  3. Rish, I. (2001). An empirical study of the naive Bayes classifier. IJCAI Workshop on Empirical Methods in AI.
  4. Zhang, H. (2004). The optimality of naive Bayes. FLAIRS Conference.
  5. Manning, C. D., Raghavan, P., & Sch ü tze, H. (2008). Introduction to Information Retrieval. Cambridge University Press. [Chapter 13: Text Classification]
  6. Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. [Section 8.2: Naive Bayes]
  7. Murphy, K. P. (2012). Machine Learning: A Probabilistic Perspective. MIT Press. [Chapter 3: Generative Models for Discrete Data]

朴素贝叶斯以其极简的形式和出人意料的效果,成为机器学习入门的经典算法。从垃圾邮件过滤到情感分析,从医疗诊断到推荐系统,朴素贝叶斯在无数实际应用中证明了"大道至简"的智慧。理解朴素贝叶斯不仅是掌握概率分类的起点,更是通往贝叶斯网络、隐马尔可夫模型等高级概率图模型的基础。

  • 本文标题:机器学习数学推导(九)朴素贝叶斯
  • 本文作者:Chen Kai
  • 创建时间:2021-10-12 09:15:00
  • 本文链接:https://www.chenk.top/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E6%95%B0%E5%AD%A6%E6%8E%A8%E5%AF%BC%EF%BC%88%E4%B9%9D%EF%BC%89%E6%9C%B4%E7%B4%A0%E8%B4%9D%E5%8F%B6%E6%96%AF/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论