接上一篇《七天学完十大机器学习经典算法-06.支持向量机(SVM):分类边界的艺术》
想象你是一位侦探,面对一起案件:房间里有碎玻璃、地毯上有泥土脚印、窗户被撬开。你会如何推断作案者?你可能会想:“有碎玻璃时入室盗窃的概率有多高?留下泥土脚印时盗窃的概率又如何?”——综合这些线索做出判断,正是朴素贝叶斯算法的核心思想!
朴素贝叶斯(Naive Bayes) 是一种基于贝叶斯定理(Bayes' Theorem) 和特征条件独立假设(“朴素”的来源) 的经典机器学习算法。它主要用于分类任务(如垃圾邮件识别、情感分析、疾病诊断),因其实现简单、计算高效、在小规模数据集上表现优异而广受欢迎。
P(A|B) = [P(B|A) * P(A)] / P(B)
P(A|B)
:在事件B发生的条件下,事件A发生的概率(后验概率)。这是我们最想知道的:给定观测到的特征B,数据属于类别A的概率是多少?
P(B|A)
:在事件A发生的条件下,事件B发生的概率(似然)。已知数据属于类别A,观测到特征B的可能性有多大?
P(A)
:事件A发生的先验概率。在没有任何证据的情况下,数据属于类别A的初始可能性有多大?(通常用训练集中类别A的样本比例估计)。
P(B)
:事件B发生的证据概率。观测到特征B的整体可能性有多大?(通常对所有类别是一个常数,用于归一化)。
P(B1, B2, ..., Bn | A) = P(B1|A) * P(B2|A) * ... * P(Bn|A)
。联合概率等于单个条件概率的乘积。
P(A_k | B1, B2, ..., Bn)
。
P(B1, B2, ..., Bn)
对所有类别相同,不影响比较大小,我们只需计算分子:
P(A_k | B1, B2, ..., Bn) ∝ P(A_k) * P(B1|A_k) * P(B2|A_k) * ... * P(Bn|A_k)
A_k
:
预测类别 = argmax_{A_k} [ P(A_k) * Π_{i=1}^{n} P(B_i | A_k) ]
P(A_k)
A_k
的初始信念或概率。
A_k
的样本数占总样本数的比例来计算。
P(A_k) = (训练集中属于A_k的样本数量) / (训练集总样本数量)
P(垃圾邮件) = 300 / 1000 = 0.3
P(非垃圾邮件) = 700 / 1000 = 0.7
P(B_i | A_k)
A_k
的条件下,观察到特征 B_i
取某个特定值(例如,第i个单词“免费”在邮件中出现)的概率。
B_i
的类型(离散 or 连续)和朴素贝叶斯变种的不同,估计方法不同(详见第三节)。
P(B_i=value | A_k) = (训练集中属于A_k类且特征B_i取值为value的样本数量) / (训练集中属于A_k类的样本总数)
P(单词"免费"出现 | 垃圾邮件)
:
P("免费" | 垃圾邮件) = 200 / 300 ≈ 0.6667
P("viagra" | 非垃圾邮件) = 0 / 700 = 0
现在,有一封新邮件包含了单词“viagra”,我们计算它属于非垃圾邮件的概率:
P(非垃圾邮件 | ... "viagra" ...) ∝ P(非垃圾邮件) * ... * P("viagra" | 非垃圾邮件) * ... = 0.7 * ... * 0 * ... = 0
仅仅因为一个在训练集中没出现过的单词,就导致整个后验概率为0,这显然不合理!特别是当特征维度很高时,新样本中很可能包含训练集中未出现过的特征值组合。
α
(通常 α=1
,称为拉普拉斯平滑;α<1
称为Lidstone平滑)。
平滑后的条件概率估计:
P(B_i=value | A_k) = (属于A_k类且B_i=value的样本数 + α) / (属于A_k类的样本总数 + α * N_i)
N_i
是特征 B_i
可能的取值个数(对于二元特征是2,对于有K个类别的离散特征是K)。
P("viagra" | 非垃圾邮件)
,使用拉普拉斯平滑 (α=1
):
N_i=2
)
平滑后 P("viagra" | 非垃圾邮件) = (0 + 1) / (700 + 1 * 2) = 1 / 702 ≈ 0.00142
(不再是0)
朴素贝叶斯的核心思想不变,但根据特征 B_i
的数据类型(离散 or 连续)以及分布假设的不同,主要衍生出三种常用变体:
B_i
表示某个事件(如单词i)发生的次数,并且服从多项式分布(Multinomial Distribution)。
P(B_i = count | A_k) = (N_{ik} + α) / (N_k + α * |V|)
N_{ik}
:在所有属于类别 A_k
的训练样本中,特征 i
(如单词i)出现的总次数。
N_k
:属于类别 A_k
的所有训练样本中,所有特征的总出现次数之和(即所有单词在所有文档中的总词数)。
|V|
:特征的总数(即词汇表的大小)。
α
:平滑参数(通常 α=1
)。
P(单词"免费"出现5次 | 垃圾邮件)
。
N_k = 10000
。
N_{ik} = 500
。
|V| = 5000
。
α=1
。
P("免费"=5 | 垃圾邮件) ≈ (500 + 1) / (10000 + 1 * 5000) = 501 / 15000 ≈ 0.0334
(注意:这里计算的是单词“免费”在垃圾邮件中的整体概率质量,不是指定出现5次的精确概率,但在朴素贝叶斯框架下,我们使用这个估计值来表示特征“免费”的贡献。实际计算 P(B_i|A_k)
时,B_i
是特征 i
的值,在多项式NB中通常直接用这个公式计算该特征的概率)。
B_i
是一个二元变量(例如,单词i在文档中出现=1,未出现=0),并且服从伯努利分布(Bernoulli Distribution)。
P(B_i = 1 | A_k) = (N_{ik} + α) / (N_k + α * 2)
N_{ik}
:在属于类别 A_k
的训练样本中,特征 i
取值为1(例如单词i出现)的样本数量。
N_k
:属于类别 A_k
的训练样本总数。
α
:平滑参数(通常 α=1
)。
P(B_i = 0 | A_k) = 1 - P(B_i = 1 | A_k)
(因为只有0和1两种可能)。
P(单词"免费"出现 | 垃圾邮件)
(伯努利视角)。
N_k = 300
。
N_{ik} = 200
。
α=1
。
P("免费"=1 | 垃圾邮件) = (200 + 1) / (300 + 1 * 2) = 201 / 302 ≈ 0.6656
A_k
的条件下,每个连续特征 B_i
的值服从一个高斯分布(正态分布)。
P(B_i = x | A_k) = (1 / √(2πσ_{ik}^2)) * exp( -(x - μ_{ik})^2 / (2σ_{ik}^2) )
μ_{ik}
:在类别 A_k
的训练样本中,特征 i
的均值。
σ_{ik}^2
:在类别 A_k
的训练样本中,特征 i
的方差。
x
:新样本中特征 i
的取值。
A_k
和每个特征 i
,计算该特征在该类别下的均值和方差。
μ_{ik} = (1 / N_k) * Σ_{x∈A_k} x_i
(类别k下特征i的平均值)
σ_{ik}^2 = (1 / N_k) * Σ_{x∈A_k} (x_i - μ_{ik})^2
(类别k下特征i的方差)
μ_{体温, 流感}
= 38.5°C。
σ_{体温, 流感}^2
= 0.5。
P(体温=39.0 | 流感) = 高斯PDF(39.0; μ=38.5, σ^2=0.5) ≈ 0.4839
(具体数值需代入公式计算)。
问题: 根据邮件内容判断邮件是垃圾邮件(Spam)还是正常邮件(Ham)。
步骤:
TF(词t, 文档d) = (词t在文档d中出现的次数) / (文档d中所有词的总数)
IDF(词t) = log(总文档数 / (包含词t的文档数 + 1))
(+1避免除零)
TF-IDF(t, d) = TF(t, d) * IDF(t)
P(Spam)
和 P(Ham)
。
i
:
P(单词i | Spam) = (所有Spam邮件中单词i的总出现次数 + α) / (所有Spam邮件中所有单词总次数 + α * |V|)
P(单词i | Ham)
(同理)。
P(Spam | 邮件) ∝ P(Spam) * Π_{单词j在邮件中出现} P(单词j | Spam)
P(Ham | 邮件) ∝ P(Ham) * Π_{单词j在邮件中出现} P(单词j | Ham)
Π
连乘中不参与计算(因为 P(单词j未出现 | Class)
在多项式NB中不是显式建模的,实际计算只乘出现单词的条件概率)。
P(Spam | 邮件)
和 P(Ham | 邮件)
中较大的类别作为预测结果。
以下是代码实例:
# Python 实现 (使用scikit-learn)
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
# 1. 加载数据 (假设spam_ham_data是包含邮件文本和标签的DataFrame)
texts = spam_ham_data['text']
labels = spam_ham_data['label'] # 例如 'spam' 或 'ham'
# 2. 划分训练集/测试集
X_train, X_test, y_train, y_test = train_test_split(texts, labels, test_size=0.2, random_state=42)
# 3. 创建Pipeline:组合文本处理和分类器
# 步骤1: 将文本转为词频向量 (CountVectorizer)
# 步骤2: (可选) 将词频转为TF-IDF向量 (TfidfTransformer)
# 步骤3: 应用多项式朴素贝叶斯分类器 (MultinomialNB)
text_clf = Pipeline([
('vect', CountVectorizer(stop_words='english')), # 移除英文停用词
# ('tfidf', TfidfTransformer()), # 启用TF-IDF转换
('clf', MultinomialNB(alpha=1.0)), # 拉普拉斯平滑alpha=1
])
# 4. 训练模型 (拟合训练数据)
text_clf.fit(X_train, y_train)
# 5. 预测测试集
y_pred = text_clf.predict(X_test)
# 6. 评估模型
print("准确率:", accuracy_score(y_test, y_pred))
print("\n分类报告:\n", classification_report(y_test, y_pred))
print("\n混淆矩阵:\n", confusion_matrix(y_test, y_pred))
# 7. (可选) 查看特征重要性 (前N个对垃圾邮件贡献大的词)
feature_names = text_clf.named_steps['vect'].get_feature_names_out()
coefs = text_clf.named_steps['clf'].feature_log_prob_[1] # 假设索引1对应'spam'类
top_spam_words = sorted(zip(coefs, feature_names), reverse=True)[:10]
print("\nTop 10 Spam Words:", [word for _, word in top_spam_words])
问题: 根据花萼长度、花萼宽度、花瓣长度、花瓣宽度(4个连续特征)预测鸢尾花种类(Setosa, Versicolor, Virginica)。
from sklearn.datasets import load_iris
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 1. 加载数据
iris = load_iris()
X = iris.data # (150, 4)
y = iris.target # (150,)
# 2. 划分训练集/测试集 (注意:高斯NB假设特征独立且连续,通常不需要标准化,但标准化有时也能提升性能)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 3. 创建并训练高斯朴素贝叶斯模型
gnb = GaussianNB() # 没有alpha参数,高斯NB没有拉普拉斯平滑
gnb.fit(X_train, y_train)
# 4. 预测测试集
y_pred = gnb.predict(X_test)
# 5. 评估模型
accuracy = accuracy_score(y_test, y_pred)
print(f"测试集准确率: {accuracy:.4f}")
# 6. 查看模型参数 (每个类别的每个特征的均值和方差)
print("类别标签:", gnb.classes_)
print("每个类别的先验概率:", gnb.class_prior_) # 或 np.exp(gnb.class_log_prior_)
print("\n每个类下每个特征的均值 (μ):\n", gnb.theta_) # shape (n_classes, n_features)
print("\n每个类下每个特征的方差 (σ²):\n", gnb.var_) # shape (n_classes, n_features)
问题: 将新闻文章自动分类到不同的主题(如体育、科技、政治、娱乐等)。本质是多类别的文本分类问题。
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score, classification_report
# 1. 加载数据 (选择几个类别示例)
categories = ['sci.space', 'comp.graphics', 'rec.sport.baseball', 'talk.politics.mideast']
newsgroups_train = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes'))
newsgroups_test = fetch_20newsgroups(subset='test', categories=categories, remove=('headers', 'footers', 'quotes'))
X_train = newsgroups_train.data
y_train = newsgroups_train.target
X_test = newsgroups_test.data
y_test = newsgroups_test.target
# 2. 创建Pipeline: TF-IDF向量化 + 多项式NB
text_clf = Pipeline([
('tfidf', TfidfVectorizer(stop_words='english', max_features=10000)), # 限制词汇量
('clf', MultinomialNB(alpha=0.1)), # 调整alpha可能提升效果
])
# 3. 训练模型
text_clf.fit(X_train, y_train)
# 4. 预测测试集
y_pred = text_clf.predict(X_test)
# 5. 评估模型
print("准确率:", accuracy_score(y_test, y_pred))
print("\n分类报告:\n", classification_report(y_test, y_pred, target_names=newsgroups_test.target_names))
O(n*d)
(n样本数,d特征数)。
P(B_i|A_k)
会趋近于在所有类别上相同,对后验概率比较的影响会减弱。当然,过多无关特征还是会降低性能。
P(A_k)
的准确性。如果测试数据的类别分布与训练数据差异很大(如训练集垃圾邮件占30%,测试集占70%),而模型未做相应调整(如通过设置class_prior
参数),性能会下降。
StandardScaler
)。虽然理论上不必须,但有时能提升性能(优化过程数值更稳定)。检查特征分布,严重非正态时谨慎使用。
alpha>0
) 以避免零概率问题。alpha
是一个超参数,可以通过交叉验证调整,通常 alpha=1
是好的起点。
P(多数类)
大)。
class_prior
参数,显式指定符合预期的先验概率(如假设测试集类别均匀分布)。
朴素贝叶斯的效率和简单性使其在众多领域大放异彩:
朴素贝叶斯算法,以其对贝叶斯定理的直观应用和对特征条件独立性的“朴素”简化,成为了机器学习工具箱中一把锋利而轻巧的手术刀。它向我们展示了:
尽管其“朴素”的假设限制了它在某些复杂关系数据上的上限,但作为建立基线模型、处理文本分类任务的首选方案,以及在资源受限环境下的高效解决方案,朴素贝叶斯始终闪耀着独特的光芒。掌握它,不仅是掌握一个算法,更是掌握一种基于概率和证据进行推理的贝叶斯思维方式——一种在充满不确定性的世界中,理性做出决策的宝贵能力。
创作不易,如有收获请点🌟收藏加关注啦!下期预告:《七天学完十大机器学习经典算法-08.K均值聚类:无监督学习的万能分箱术》