朴素贝叶斯算法详解与实战
在众多机器学习分类算法中,朴素贝叶斯(Naive Bayes) 虽然名字听起来“简单”,实则是一种极具实用价值的模型。它基于概率理论构建,结构清晰、实现容易,常被用作初学者入门的经典范例。本文将深入剖析其核心原理,并结合西瓜数据集进行逐步推导与实现,帮助你彻底掌握这一经典算法。
核心优势
- 简洁高效:逻辑直观,计算开销小,是学习分类任务的理想起点
- 理论扎实:建立在贝叶斯定理之上,具备严谨的概率统计基础
- 运行快速:训练和预测阶段均表现优异,适用于实时场景
- 适应小样本:即使训练数据有限,仍能保持良好的分类性能
- 易于实现:代码实现简明,适合快速原型开发与教学演示
典型应用场景
- 文本分类:如垃圾邮件识别、情感倾向判断
- 医疗辅助诊断:疾病风险评估、病情初步筛查
- 推荐系统:用户行为偏好建模与预测
- 基础分类问题:例如本文使用的西瓜品质判断任务
数学原理概述
朴素贝叶斯基于贝叶斯定理进行概率推理:
P(Y|X) = \frac{P(X|Y)P(Y)}{P(X)}
其中各项含义如下:
- P(Y|X):后验概率,表示在已知特征 X 的条件下,样本属于类别 Y 的概率
- P(X|Y):似然,即在类别 Y 中观察到特征 X 的可能性
- P(Y):先验概率,反映类别 Y 在整体中的分布频率
- P(X):证据因子,为特征 X 出现的总概率,通常作为归一化常数处理
该算法被称为“朴素”的原因在于其强假设——所有特征在给定类别下相互独立。这一假设使得联合概率可分解为各特征条件概率的乘积:
P(X|Y) = P(x|Y) × P(x|Y) × … × P(x|Y)
从而大大简化了高维特征下的概率计算过程。
西瓜数据集案例分析
我们采用经典的西瓜数据集(共17条记录),包含以下特征信息:
| 特征 |
类型 |
说明 |
| 色泽 |
离散 |
青绿 / 乌黑 / 浅白 |
| 根蒂 |
离散 |
蜷缩 / 稍蜷 / 硬挺 |
| 敲声 |
离散 |
浊响 / 沉闷 / 清脆 |
| 纹理 |
离散 |
清晰 / 稍糊 / 模糊 |
| 脐部 |
离散 |
凹陷 / 稍凹 / 平坦 |
| 触感 |
离散 |
硬滑 / 软粘 |
| 密度 |
连续 |
数值型(范围0.0~1.0) |
| 含糖率 |
连续 |
数值型(范围0.0~1.0) |
| 好瓜 |
标签 |
是 / 否(目标变量) |
针对不同类型特征,采用不同的概率估计方法:
- 离散特征:使用频率统计结合平滑技术
- 连续特征:假设服从高斯分布,利用均值与方差建模
离散特征处理:多项式朴素贝叶斯
适用于离散取值的特征,尤其常见于文本分类中的词频建模。
条件概率计算公式为:
P(x|y) = (Ny + α) / (Ny + αn)
参数解释:
- P(x|y):在类别 y 下,特征 x 出现的概率
- Ny:类别 y 的样本中,特征 x 出现的次数
- Ny:类别 y 所有样本中所有特征的总出现次数
- n:特征总数
- α:平滑参数,常用拉普拉斯平滑(α=1)避免零概率问题
连续特征处理:高斯朴素贝叶斯
假设连续型特征在每个类别下服从正态分布。
概率密度函数表达式为:
P(x|y) = \frac{1}{\sqrt{2\pi\sigma_y^2}} \exp\left(-\frac{(x_i - \mu_y)^2}{2\sigma_y^2}\right)
相关参数定义:
- μy:类别 y 下特征 x 的均值
- σy:类别 y 下特征 x 的标准差
- x:待预测样本的当前特征值
参数估计方式:
- 均值 μy = (1/Ny) × Σx(y) (j 从 1 到 Ny)
- 方差 σy = (1/Ny) × Σ(x(y) - μy) (j 从 1 到 Ny)
代码实现部分
import pandas as pd
import numpy as np

以下数据集包含了西瓜的多个属性,用于判断是否为好瓜。数据中的特征分为离散型和连续型两类,分别进行处理以构建混合朴素贝叶斯模型。
原始数据结构如下:
从数据中提取出离散特征与连续特征,其中离散特征包括:色泽、根蒂、敲声、纹理、脐部、触感;连续特征包括:密度、含糖率。标签列为“好瓜”,表示该样本是否为优质西瓜。
具体特征划分如下:
- 离散特征(分类属性):色澤、根蒂、敲声、纹理、脐部、触感
- 连续特征(数值属性):密度、含糖率
- 目标标签:好瓜
在建模前,确保所有离散特征列的数据类型为字符串,以便正确统计各类别的出现频率。
[此处为图片2]
构建一个支持混合特征类型的朴素贝叶斯分类器——MixedNaiveBayesWithDetail。该类初始化时定义了先验概率、离散特征的条件概率以及连续特征的高斯分布参数存储结构。
训练过程包含以下步骤:
- 计算每个类别(是/否)的先验概率,即该类样本占总样本的比例。
- 对每一个离散特征,统计其在不同类别下的条件概率。采用频次估计方法,计算某特征值在给定类别下出现的概率。
- 对于连续特征,假设其服从高斯分布,计算每个类别下各连续特征的均值与方差,作为后续概率密度计算的依据。
在拟合模型时,首先遍历所有类别,计算先验分布。接着针对每个离散特征列,建立嵌套字典结构,记录每种取值在各个类别中的条件概率。
[此处为图片3]
对于连续特征部分,在每个类别下分别计算“密度”和“含糖率”的均值与标准差,并保存至 continuous_params 字典中,供预测阶段使用。
整个模型基于贝叶斯公式进行联合概率建模,结合离散特征的多项式模型与连续特征的高斯模型,实现对混合类型数据的有效分类。
# 创建并训练模型
nb = MixedNaiveBayesWithDetail()
nb.fit(X_discrete, X_continuous, y)
# 测试样本:测1(即训练集第1个样本)
new_sample = {
'色澤': '青绿',
'根蒂': '蜷缩',
'敲声': '浊响',
'纹理': '清晰',
'脐部': '凹陷',
'触感': '硬滑',
'密度': 0.697,
'含糖率': 0.460
}
def _gaussian_pdf(self, x, mean, var):
"""
计算给定均值和方差下,x处的高斯概率密度函数值。
若方差为0,则退化为确定性情况。
"""
if var == 0:
return 1.0 if x == mean else 1e-10
coeff = 1.0 / np.sqrt(2 * np.pi * var)
exponent = -0.5 * ((x - mean) ** 2) / var
return coeff * np.exp(exponent)
def predict_with_detail(self, X_discrete_row, X_continuous_row):
print("=" * 60)
print("???? 开始预测样本:")
for col in X_discrete_row.index:
print(f" {col}: {X_discrete_row[col]}")
for col in X_continuous_row.index:
print(f" {col}: {X_continuous_row[col]:.3f}")
print("-" * 60)
log_probs = {}
probs = {}
for c in self.priors.keys():
prior = self.priors[c]
log_prior = np.log(prior)
log_prob = log_prior
prob = prior
print(f"\n类别: '{c}'")
print(f" 先验概率 P({c}) = {prior:.4f} (log = {log_prior:.4f})")
# 处理离散特征
for col in X_discrete_row.index:
val = X_discrete_row[col]
if val in self.discrete_probs[col] and c in self.discrete_probs[col][val]:
p = self.discrete_probs[col][val][c]
else:
p = 1e-10 # 拉普拉斯平滑或未登录项处理
log_p = np.log(p)
log_prob += log_p
prob *= p
print(f" P({col}={val} | {c}) = {p:.4f} (log = {log_p:.4f})")
# 处理连续特征
for col in X_continuous_row.index:
x = X_continuous_row[col]
mean, var = self.continuous_params[col][c]
p = self._gaussian_pdf(x, mean, var)
log_p = np.log(p)
log_prob += log_p
prob *= p
print(f" P({col}={x:.3f} | {c}) = {p:.6f} (log = {log_p:.4f})")
print(f" → 高斯分布参数: μ={mean:.4f}, σ?={var:.4f}")
log_probs[c] = log_prob
probs[c] = prob
print(f" 联合概率 P(样本|{c})P({c}) = {prob:.2e} (log = {log_prob:.4f})")
print("\n" + "=" * 60)
print(" 最终比较(使用对数概率避免下溢):")
for c in log_probs:
print(f" log P({c} | 样本) ∝ {log_probs[c]:.4f}")
best_class = max(log_probs, key=log_probs.get)
print(f"\n 预测结果: '{best_class}'")
return best_class
[此处为图片2]
data_c = X_continuous.loc[y == c, col]
mean = data_c.mean()
var = data_c.var(ddof=0) # 使用总体方差,与教材定义保持一致
self.continuous_params[col][c] = (mean, var)
cont_input = pd.Series([new_sample[f] for f in features_continuous], index=features_continuous)
discrete_input = pd.Series([new_sample[f] for f in features_discrete], index=features_discrete)
pred = nb.predict_with_detail(discrete_input, cont_input)
运行结果如下所示:
