介绍

在机器学习的众多算法中,朴素贝叶斯(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(YX)=P(X)P(XY)P(Y)

其中:

  • P(Y∣X)P(Y|X)P(YX):后验概率(在已知特征 XXX 的情况下,属于类别 YYY 的概率)
  • P(X∣Y)P(X|Y)P(XY):似然(在类别 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(XY)=P(x1Y)P(x2Y)P(xnY)

这使得原本复杂的联合概率计算,简化为多个简单概率的乘积。

西瓜例子

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(xiy)=(Nyi+α)/(Ny+αn)
参数说明:
P(xi∣y)在类别y下特征xi出现的概率P(x_i|y) 在类别 y 下特征 x_i 出现的概率P(xiy)在类别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(xiy)=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))(j1Ny)
σ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(j1Ny)

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.运行结果:

在这里插入图片描述

更多推荐