手动实现机器学习实例——朴素贝叶斯,以西瓜分类为例
介绍
在机器学习的众多算法中,朴素贝叶斯(Naive Bayes) 可能是最"朴素"却又最"强大"的分类算法之一。它不仅有着坚实的数学基础,还因其简单高效的特点成为机器学习入门的首选算法。今天,我将带大家深入理解朴素贝叶斯的原理,并通过西瓜数据集的实战案例,详细展示每一步的计算过程,让算法不再"神秘"。
1.特点:
简单却高效:机器学习的"入门王道"
理论基础扎实:基于贝叶斯定理,有坚实的概率论支撑
计算速度快:训练和预测都非常高效,适合实时应用
对小数据集友好:即使样本量小,也能取得不错的效果
算法实现简单:代码量少,逻辑清晰,特别适合新手入门
2.应用:
文本分类:垃圾邮件过滤、情感分析
医疗诊断:疾病预测、风险评估
推荐系统:用户行为预测
基础分类任务:如我们的西瓜数据集
3.公式原理:
朴素贝叶斯的数学原理基于贝叶斯定理:
P(Y∣X)=P(X∣Y)P(Y)P(X) P(Y|X) = \frac{P(X|Y)P(Y)}{P(X)} P(Y∣X)=P(X)P(X∣Y)P(Y)
其中:
- P(Y∣X)P(Y|X)P(Y∣X):后验概率(在已知特征 XXX 的情况下,属于类别 YYY 的概率)
- P(X∣Y)P(X|Y)P(X∣Y):似然(在类别 YYY 下,特征 XXX 出现的概率)
- P(Y)P(Y)P(Y):先验概率(类别 YYY 出现的概率)
- P(X)P(X)P(X):证据(特征 XXX 出现的总概率)
朴素贝叶斯的"朴素"之处在于假设所有特征条件独立:
P(X∣Y)=P(x1∣Y)⋅P(x2∣Y)⋅…⋅P(xn∣Y) P(X|Y) = P(x_1|Y) \cdot P(x_2|Y) \cdot \ldots \cdot P(x_n|Y) P(X∣Y)=P(x1∣Y)⋅P(x2∣Y)⋅…⋅P(xn∣Y)
这使得原本复杂的联合概率计算,简化为多个简单概率的乘积。
西瓜例子
1. 数据准备:
我们使用经典的西瓜数据集(共17个样本),包含以下特征:
| 特征 | 类型 | 说明 |
|---|---|---|
| 色泽 | 离散 | 青绿/乌黑/浅白 |
| 根蒂 | 离散 | 蜷缩/稍蜷/硬挺 |
| 敲声 | 离散 | 浊响/沉闷/清脆 |
| 纹理 | 离散 | 清晰/稍糊/模糊 |
| 脐部 | 离散 | 凹陷/稍凹/平坦 |
| 触感 | 离散 | 硬滑/软粘 |
| 密度 | 连续 | 数值型(0.0~1.0) |
| 含糖率 | 连续 | 数值型(0.0~1.0) |
| 好瓜 | 标签 | 是/否(目标) |
对于离散特征用频率估计,连续特征用高斯分布
1. 离散特征处理方法
多项式朴素贝叶斯(Multinomial Naive Bayes)
适用于离散特征,特别是文本分类中的词频统计。
条件概率计算公式:
P(xi∣y)=(Nyi+α)/(Ny+αn)
P(x_i|y) = (N_{y_i} + α) / (N_y + αn)
P(xi∣y)=(Nyi+α)/(Ny+αn)
参数说明:
P(xi∣y)在类别y下特征xi出现的概率P(x_i|y) 在类别 y 下特征 x_i 出现的概率P(xi∣y)在类别y下特征xi出现的概率
Nyi在类别y的样本中特征xi出现的次数N_{y_i}在类别 y 的样本中特征 x_i 出现的次数Nyi在类别y的样本中特征xi出现的次数
Ny:类别y的所有样本中所有特征出现的总次数N_y:类别 y 的所有样本中所有特征出现的总次数Ny:类别y的所有样本中所有特征出现的总次数
n:特征总数n:特征总数n:特征总数
α:平滑参数(α=1为拉普拉斯平滑)α:平滑参数(α=1 为拉普拉斯平滑)α:平滑参数(α=1为拉普拉斯平滑)
2. 连续特征处理方法
高斯朴素贝叶斯(Gaussian Naive Bayes)
假设连续特征服从正态分布。
概率密度函数:
P(xi∣y)=1/√(2πσy2)×exp(−(xi−μy)2/(2σy2))P(x_i|y) = 1 / √(2πσ_y²) × exp(-(x_i - μ_y)² / (2σ_y²))P(xi∣y)=1/√(2πσy2)×exp(−(xi−μy)2/(2σy2))
参数说明:
μy:类别y下特征xi的均值μ_y:类别 y 下特征 x_i 的均值μy:类别y下特征xi的均值
σy:类别y下特征xi的标准差σ_y:类别 y 下特征 x_i 的标准差σy:类别y下特征xi的标准差
xi:待预测样本的特征值x_i:待预测样本的特征值xi:待预测样本的特征值
参数估计公式:
μy=(1/Ny)×Σ(xj(y))(j从1到Ny)μ_y = (1/N_y) × Σ(x_j^(y)) (j从1到N_y)μy=(1/Ny)×Σ(xj(y))(j从1到Ny)
σy2=(1/Ny)×Σ(xj(y)−μy)2(j从1到Ny)σ_y² = (1/N_y) × Σ(x_j^(y) - μ_y)² (j从1到N_y)σy2=(1/Ny)×Σ(xj(y)−μy)2(j从1到Ny)
2.代码实现:
import pandas as pd
import numpy as np
# 数据
data = {
'编号': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
'色澤': ['青绿', '乌黑', '乌黑', '青绿', '浅白', '青绿', '乌黑', '乌黑', '乌黑', '青绿', '浅白', '浅白', '青绿',
'浅白', '乌黑', '浅白', '青绿'],
'根蒂': ['蜷缩', '蜷缩', '蜷缩', '蜷缩', '蜷缩', '稍蜷', '稍蜷', '稍蜷', '稍蜷', '硬挺', '硬挺', '蜷缩', '稍蜷',
'稍蜷', '稍蜷', '蜷缩', '蜷缩'],
'敲声': ['浊响', '沉闷', '浊响', '沉闷', '浊响', '浊响', '浊响', '浊响', '沉闷', '清脆', '清脆', '浊响', '浊响',
'沉闷', '浊响', '浊响', '沉闷'],
'纹理': ['清晰', '清晰', '清晰', '清晰', '清晰', '清晰', '稍糊', '清晰', '稍糊', '清晰', '模糊', '模糊', '稍糊',
'稍糊', '清晰', '模糊', '稍糊'],
'脐部': ['凹陷', '凹陷', '凹陷', '凹陷', '凹陷', '稍凹', '稍凹', '稍凹', '稍凹', '平坦', '平坦', '平坦', '凹陷',
'凹陷', '稍凹', '平坦', '稍凹'],
'触感': ['硬滑', '硬滑', '硬滑', '硬滑', '硬滑', '软粘', '软粘', '硬滑', '硬滑', '软粘', '硬滑', '软粘', '硬滑',
'硬滑', '软粘', '硬滑', '硬滑'],
'密度': [0.697, 0.774, 0.634, 0.608, 0.556, 0.403, 0.481, 0.437, 0.666, 0.243, 0.245, 0.343, 0.639, 0.657, 0.360,
0.593, 0.719],
'含糖率': [0.460, 0.376, 0.264, 0.318, 0.215, 0.237, 0.149, 0.211, 0.091, 0.267, 0.057, 0.099, 0.161, 0.198, 0.370,
0.042, 0.103],
'好瓜': ['是', '是', '是', '是', '是', '是', '是', '是', '否', '否', '否', '否', '否', '否', '否', '否', '否']
}
df = pd.DataFrame(data)
# 提取特征和标签
features_discrete = ['色澤', '根蒂', '敲声', '纹理', '脐部', '触感']
features_continuous = ['密度', '含糖率']
label = '好瓜'
X_discrete = df[features_discrete]
X_continuous = df[features_continuous]
y = df[label]
# 确保离散列是字符串类型
for col in features_discrete:
df[col] = df[col].astype(str)
class MixedNaiveBayesWithDetail:
def __init__(self):
self.priors = {}
self.discrete_probs = {}
self.continuous_params = {}
def fit(self, X_discrete, X_continuous, y):
total_samples = len(y)
for c in y.unique():
self.priors[c] = np.sum(y == c) / total_samples
# 离散特征条件概率
for col in X_discrete.columns:
self.discrete_probs[col] = {}
for val in X_discrete[col].unique():
self.discrete_probs[col][val] = {}
for c in y.unique():
count = np.sum((X_discrete[col] == val) & (y == c))
prob = count / np.sum(y == c)
self.discrete_probs[col][val][c] = prob
# 连续特征高斯参数
for col in X_continuous.columns:
self.continuous_params[col] = {}
for c in y.unique():
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)
def _gaussian_pdf(self, x, mean, var):
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
# 创建并训练模型
nb = MixedNaiveBayesWithDetail()
nb.fit(X_discrete, X_continuous, y)
# 测试样本:测1(即训练集第1个样本)
new_sample = {
'色澤': '青绿',
'根蒂': '蜷缩',
'敲声': '浊响',
'纹理': '清晰',
'脐部': '凹陷',
'触感': '硬滑',
'密度': 0.697,
'含糖率': 0.460
}
discrete_input = pd.Series([new_sample[f] for f in features_discrete], index=features_discrete)
cont_input = pd.Series([new_sample[f] for f in features_continuous], index=features_continuous)
pred = nb.predict_with_detail(discrete_input, cont_input)
3.运行结果:

更多推荐
所有评论(0)