朴素贝叶斯:从条件概率到文本分类
总阅读量 加载中... 次
朴素贝叶斯通过类别先验和类条件分布计算类别后验概率,用于文本分类、垃圾邮件识别等任务。模型对给定类别下的特征作条件独立假设,将高维联合概率的估计转化为多个一维条件分布的估计。
不同变体使用不同的特征分布:多项式模型适合词频等计数数据,伯努利模型适合二元特征,高斯模型适合用正态分布描述的连续特征。训练通常只需要累计计数或估计均值、方差,无需梯度下降。
1. 背景
判断一封邮件是否为垃圾邮件时,“中奖”“优惠”等词语可能提供证据。单个词不能决定类别,但多个词的组合可以改变类别的相对可能性。
设类别为
其中
难点在于估计高维联合分布。即使每个特征只有两个可能值,
2. 模型原理
2.1 条件独立假设
给定类别
因此:
条件独立不等于无条件独立。两个特征可以因为共享类别因素而相关;假设只约束固定类别后的分布。现实文本中的词语即使在同一类别内也经常相关,因此该假设通常是近似。
模型属于生成式分类方法:先估计类别及特征生成机制,再由贝叶斯公式得到分类概率。逻辑回归则直接对类别条件概率建模。
2.2 分类与对数概率
按最大后验概率分类:
连乘许多小概率容易发生浮点数下溢。取对数后,乘积变为求和:
仅比较类别时可以省略共同的归一化项;需要后验概率时,则必须进行归一化。令
归一化后的数值符合模型后验定义,但不保证在真实数据上已经校准。强相关特征可能重复计入相似证据,使模型产生过高置信度。
2.3 类别先验
训练集共有
先验由文档数或样本数估计,不能用该类别的总词数替代。长文档会影响词频统计,但每篇文档在类别样本计数中只贡献一次。
若训练数据经过类别重采样,频率先验反映的是采样后的类别比例。部署类别比例不同时,应根据采样机制和验证数据调整先验,并检查概率校准。
2.4 多项式朴素贝叶斯
(1)词袋与词频
设词表大小为
其中
标准多项式文本分类模型通常不单独建模类别相关的文档长度分布。多项式系数对同一文档的各类别相同,分类时可省略:
(2)计数估计与加性平滑
令
平滑只作用于已定义的词表。预测时遇到词表外词,可以忽略,也可以使用训练时定义好的未知词标记;不能临时扩充词表而不更新分母和模型参数。
2.5 多项式模型的手算示例
考虑已经分词的四篇训练文档:
| 类别 | 文档 |
|---|---|
| 垃圾 | 优惠 优惠 中奖 |
| 垃圾 | 优惠 中奖 |
| 正常 | 会议 项目 |
| 正常 | 会议 会议 项目 |
词表为“优惠、中奖、会议、项目”,
取
| 词语 | 垃圾类概率 | 正常类概率 |
|---|---|---|
| 优惠 |
|
|
| 中奖 |
|
|
| 会议 |
|
|
| 项目 |
|
|
对“优惠 中奖”,两个类别的未归一化分数分别为:
共同的多项式系数已省略。垃圾类相对正常类的分数比为 12,因此后验概率为:
2.6 伯努利朴素贝叶斯
伯努利模型令
若
每个特征是二元分布,因此分母加入
类别得分同时包括出现和未出现的特征:
因此,将词频截断为 0 和 1 后继续使用多项式得分,并不等价于伯努利模型。前者没有纳入词语缺失的证据。
2.7 高斯朴素贝叶斯
连续特征可以在各类别内使用高斯分布:
训练时按类别和特征估计均值与最大似然方差:
使用方差下界或小幅方差平滑可避免常数特征导致除零。极小方差也可能使微小偏移主导类别得分,平滑尺度需要结合数据量纲选择。
| 模型 | 特征类型 | 类条件参数 | 关键区别 |
|---|---|---|---|
| 多项式 | 非负计数 | 各类别的词概率 | 使用重复次数,标准得分省略共同多项式系数 |
| 伯努利 | 二元指示 | 各类别的出现概率 | 同时使用出现和缺失信息 |
| 高斯 | 连续数值 | 各类别各特征的均值与方差 | 使用概率密度,需要处理退化方差 |
词频标准化后得到的负值不适合多项式模型。非负的词频逆文档频率特征有时也用于多项式分类,但不再具有严格的整数词频生成解释。
3. 模型实现
3.1 词频统计与训练
多项式实现只依赖 Python 标准库。输入是已经分词的词语列表,标签为字符串。词表只从训练文档构建,预测时忽略词表外词;空文档或全部为未知词的文档根据类别先验分类。
import math
from collections import Counter
class MultinomialNaiveBayes:
def __init__(self, alpha=1.0):
if not math.isfinite(alpha) or alpha <= 0:
raise ValueError("alpha must be finite and positive")
self.alpha = alpha
self.fitted = False
def fit(self, documents, labels):
self.fitted = False
documents = [list(document) for document in documents]
labels = list(labels)
if not documents or len(documents) != len(labels):
raise ValueError("documents and labels must have equal nonzero length")
self.classes = sorted(set(labels))
self.vocabulary = set(word for document in documents for word in document)
if not self.vocabulary:
raise ValueError("training vocabulary must not be empty")
class_count = Counter(labels)
word_count = {label: Counter() for label in self.classes}
for document, label in zip(documents, labels):
word_count[label].update(document)
self.log_prior = {
label: math.log(class_count[label] / len(labels))
for label in self.classes
}
self.log_word_probability = {}
for label in self.classes:
total = sum(word_count[label].values())
denominator = total + self.alpha * len(self.vocabulary)
self.log_word_probability[label] = {
word: math.log((word_count[label][word] + self.alpha) / denominator)
for word in self.vocabulary
}
self.fitted = True
return self
def log_scores(self, document):
if not self.fitted:
raise RuntimeError("fit must be called before prediction")
counts = Counter(word for word in document if word in self.vocabulary)
return {
label: self.log_prior[label] + sum(
count * self.log_word_probability[label][word]
for word, count in sorted(counts.items())
)
for label in self.classes
}
def predict_proba(self, document):
scores = self.log_scores(document)
maximum = max(scores.values())
weights = {label: math.exp(score - maximum) for label, score in scores.items()}
total = sum(weights.values())
return {label: weight / total for label, weight in weights.items()}
def predict(self, document):
scores = self.log_scores(document)
return max(self.classes, key=lambda label: scores[label])
相同得分下,predict 按排序后的类别顺序返回第一个类别。所有类别在训练集中均有样本,因此频率先验为正;某个类别没有任何词时,加性平滑使该类别的词分布成为均匀分布。
3.2 文本分类示例
train_documents = [
["优惠", "优惠", "中奖"],
["优惠", "中奖"],
["会议", "项目"],
["会议", "会议", "项目"],
]
train_labels = ["垃圾", "垃圾", "正常", "正常"]
model = MultinomialNaiveBayes(alpha=1.0)
model.fit(train_documents, train_labels)
query = ["优惠", "中奖"]
print("prediction:", model.predict(query))
print("probability:", {
label: round(value, 6)
for label, value in model.predict_proba(query).items()
})
print("unknown:", model.predict_proba(["未知词"]))
输出为:
prediction: 垃圾
probability: {'垃圾': 0.923077, '正常': 0.076923}
unknown: {'垃圾': 0.5, '正常': 0.5}
该数据用于核对计数和平滑公式,不构成泛化效果评估。真实任务需要独立的验证集与测试集,并在划分之后构建词表、过滤规则及其他数据依赖的预处理参数。
3.3 计算成本与增量更新
设训练语料总词数为
多项式模型可以通过累加类别文档数和类别词计数实现增量更新。固定词表下,只需重新计算变化类别的分母与词概率;若扩展词表,所有类别的平滑分母都会变化,必须同步更新。模型参数和分词、词表版本需要一起发布。
3.4 评价与适用边界
文本分类可观察各类别精确率、召回率、宏平均调和分数和混淆矩阵,概率输出还可用对数损失与可靠性曲线检查。类别不平衡时,整体准确率可能掩盖少数类表现。
朴素贝叶斯适合参数估计成本低、稀疏高维、需要快速建立基线的场景。其限制主要来自以下方面:
- 相关特征重复提供证据:近义词、重复字段或强关联词组可能导致过度置信,需要结合特征处理和概率校准评估。
- 词袋丢失顺序:“喜欢”和“不喜欢”可能共享词语,词组特征可以补充局部语义,但会增加词表和稀疏性。
- 短文本与未知词:有效证据较少时,先验影响更大;全部未知词的结果仅反映先验,不代表模型理解了文本。
- 训练与部署分布变化:词汇和类别比例漂移都会改变预测质量,需要监控未知词比例和分类别指标。
4. 总结
朴素贝叶斯通过条件独立结构简化类条件分布估计,再结合类别先验完成分类。多项式模型累计词频,伯努利模型统计出现与缺失,高斯模型估计连续特征的类内均值和方差。
可靠实现需要匹配特征分布、正确设置平滑分母,并在对数空间中计算得分。词表边界、类别先验、训练测试划分和概率校准同样决定模型的实际可用性。
5. 参考文献
[1] Bishop C M. Pattern Recognition and Machine Learning. Springer, 2006.
[2] Manning C D, Raghavan P, Schütze H. Introduction to Information Retrieval. Cambridge University Press, 2008.
[3] McCallum A, Nigam K. A Comparison of Event Models for Naive Bayes Text Classification. AAAI Workshop on Learning for Text Categorization, 1998.