一句话答案:朴素贝叶斯是一种“假设特征互相独立”的概率分类算法,虽“天真”却极高效。10行代码就能实现垃圾邮件识别,准确率超85%!
如果你在搜索:
那么,这篇文章就是为你写的——从数学推导到代码落地,一步不跳。
朴素贝叶斯(Naive Bayes)是基于贝叶斯定理的分类算法,核心思想是:
“已知结果,反推最可能的原因”
比如:
因为它做了一个强假设:
所有特征彼此独立(例如:“免费”和“赢大奖”出现互不影响)
现实中这显然不成立(垃圾邮件常同时出现这两个词),但神奇的是——即使假设错误,分类效果依然很好!
💡 原因:我们只关心“哪个类别概率最大”,不要求概率值绝对准确。只要排序对,结果就对。
我们要计算:

根据贝叶斯定理:

由于 P(X)对所有类别相同,可忽略。目标变为:

再利用“特征独立”假设:

最终决策公式:

邮件ID | 内容(分词后) | 类别 |
|---|---|---|
1 | ["免费", "赢", "大奖"] | 垃圾邮件 |
2 | ["免费", "课程"] | 垃圾邮件 |
3 | ["会议", "安排", "明天"] | 正常邮件 |
4 | ["项目", "进展", "顺利"] | 正常邮件 |
假设使用伯努利模型(只关心词是否出现,不计频次)
所有唯一词:["免费", "赢", "大奖", "课程", "会议", "安排", "明天", "项目", "进展", "顺利"] → 共10个词
使用拉普拉斯平滑(α=1),避免零概率:

分母+2:因为伯努利模型只有“出现/未出现”两种状态
其他词同理,略。
["免费", "会议"]计算两类后验概率(忽略公共分母):
P(垃圾)⋅P(免费∣垃圾)⋅P(会议∣垃圾)=0.5×0.75×0.25=0.09375
P(正常)⋅P(免费∣正常)⋅P(会议∣正常)=0.5×0.25×0.5=0.0625
✅ 结论:0.09375 > 0.0625 → 判定为 垃圾邮件
尽管“会议”通常是正常词,但“免费”的权重更高,模型做出了合理判断。
from sklearn.naive_bayes import BernoulliNB
from sklearn.feature_extraction.text import CountVectorizer
# 数据
texts = [
"免费 赢 大奖",
"免费 课程",
"会议 安排 明天",
"项目 进展 顺利"
]
labels = [1, 1, 0, 0] # 1=垃圾, 0=正常
# 向量化(二值化)
vectorizer = CountVectorizer(binary=True)
X = vectorizer.fit_transform(texts)
# 训练
clf = BernoulliNB(alpha=1.0) # alpha=1 即拉普拉斯平滑
clf.fit(X, labels)
# 预测
new_text = vectorizer.transform(["免费 会议"])
print("预测结果:", "垃圾邮件" if clf.predict(new_text)[0] == 1 else "正常邮件")
# 输出: 垃圾邮件import numpy as np
class NaiveBayes:
def __init__(self):
self.vocab = {}
self.prior = {}
self.cond_prob = {}
def fit(self, docs, labels, alpha=1):
# 构建词汇表
words = set(w for doc in docs for w in doc)
self.vocab = {w: i for i, w in enumerate(words)}
n_vocab = len(self.vocab)
# 统计类别
unique_labels = list(set(labels))
label_count = {l: labels.count(l) for l in unique_labels}
total = len(labels)
# 先验概率
self.prior = {l: count / total for l, count in label_count.items()}
# 条件概率(伯努利)
self.cond_prob = {l: np.zeros(n_vocab) for l in unique_labels}
for doc, label in zip(docs, labels):
doc_vec = np.zeros(n_vocab)
for w in doc:
if w in self.vocab:
doc_vec[self.vocab[w]] = 1
self.cond_prob[label] += doc_vec
# 拉普拉斯平滑
for label in unique_labels:
n_docs = label_count[label]
self.cond_prob[label] = (self.cond_prob[label] + alpha) / (n_docs + 2 * alpha)
def predict(self, doc):
scores = {}
for label in self.prior:
log_prob = np.log(self.prior[label])
for w in doc:
if w in self.vocab:
idx = self.vocab[w]
log_prob += np.log(self.cond_prob[label][idx])
scores[label] = log_prob
return max(scores, key=scores.get)
# 使用
docs = [["免费","赢","大奖"], ["免费","课程"], ["会议","安排","明天"], ["项目","进展","顺利"]]
nb = NaiveBayes()
nb.fit(docs, [1,1,0,0])
print(nb.predict(["免费", "会议"])) # 输出: 1import java.util.*;
public class NaiveBayes {
private Map<String, Integer> vocab = new HashMap<>();
private Map<Integer, Double> prior = new HashMap<>();
private Map<Integer, double[]> condProb = new HashMap<>();
private int vocabSize;
public void fit(List<List<String>> docs, List<Integer> labels, double alpha) {
// 构建词汇表
Set<String> wordSet = new HashSet<>();
for (List<String> doc : docs) {
wordSet.addAll(doc);
}
int idx = 0;
for (String word : wordSet) {
vocab.put(word, idx++);
}
vocabSize = vocab.size();
// 统计标签
Map<Integer, Integer> labelCount = new HashMap<>();
for (int label : labels) {
labelCount.put(label, labelCount.getOrDefault(label, 0) + 1);
}
// 先验概率
int totalDocs = labels.size();
for (int label : labelCount.keySet()) {
prior.put(label, (double) labelCount.get(label) / totalDocs);
}
// 条件概率(伯努利)
for (int label : labelCount.keySet()) {
double[] prob = new double[vocabSize];
int docsWithLabel = labelCount.get(label);
// 统计每个词在该类中出现次数
for (int i = 0; i < docs.size(); i++) {
if (labels.get(i) == label) {
Set<String> docWords = new HashSet<>(docs.get(i));
for (String word : docWords) {
if (vocab.containsKey(word)) {
int wIdx = vocab.get(word);
prob[wIdx] += 1.0;
}
}
}
}
// 拉普拉斯平滑
for (int i = 0; i < vocabSize; i++) {
prob[i] = (prob[i] + alpha) / (docsWithLabel + 2 * alpha);
}
condProb.put(label, prob);
}
}
public int predict(List<String> doc) {
Map<Integer, Double> scores = new HashMap<>();
for (int label : prior.keySet()) {
double logProb = Math.log(prior.get(label));
Set<String> docWords = new HashSet<>(doc);
for (String word : docWords) {
if (vocab.containsKey(word)) {
int wIdx = vocab.get(word);
logProb += Math.log(condProb.get(label)[wIdx]);
}
}
scores.put(label, logProb);
}
// 返回概率最大的标签
return Collections.max(scores.entrySet(), Map.Entry.comparingByValue()).getKey();
}
// 测试
public static void main(String[] args) {
List<List<String>> docs = Arrays.asList(
Arrays.asList("免费", "赢", "大奖"),
Arrays.asList("免费", "课程"),
Arrays.asList("会议", "安排", "明天"),
Arrays.asList("项目", "进展", "顺利")
);
List<Integer> labels = Arrays.asList(1, 1, 0, 0);
NaiveBayes nb = new NaiveBayes();
nb.fit(docs, labels, 1.0); // alpha=1
List<String> testDoc = Arrays.asList("免费", "会议");
System.out.println("预测结果: " + (nb.predict(testDoc) == 1 ? "垃圾邮件" : "正常邮件"));
// 输出: 垃圾邮件
}
}优点 | 缺点 |
|---|---|
✅ 训练快,预测快(O(n)) | ❌ 特征独立假设太强 |
✅ 小样本表现好 | ❌ 无法建模特征交互(如“not good”) |
✅ 对缺失值鲁棒 | ❌ 概率输出可能不准 |
✅ 内存占用小,适合嵌入式 | ❌ 连续特征需假设分布(如高斯) |
本系列将持续更新以下算法,每篇均包含:
即将发布:
朴素贝叶斯用最天真的假设,实现了最实用的分类。它不追求完美,只求快速、稳健、可解释。
记住:在AI世界,有时“简单有效”比“复杂精确”更重要。
现在,你已经能:
相关链接
无论你是想写代码调用 API 的开发者,设计 AI 产品的 PM,评估技术路线的管理者,还是单纯好奇智能本质的思考者,这里都有值得你驻足的内容。 不追 hype,只讲逻辑;不谈玄学,专注可复现的认知。 让我们一起,在这场百年一遇的智能革命中,看得更清,走得更稳 https://cloud.tencent.com/developer/column/107314
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。