文章目录
-
- 1. 贝叶斯定理:先验、似然与后验
-
- 从一个医学诊断问题出发
- 三个核心概念
- 贝叶斯更新的链式直觉
- 2. 朴素贝叶斯的"朴素":条件独立假设为何管用
-
- 独立假设的数学形式
- "朴素"假设下为什么还能管用?
- 3. 三种变体:GaussianNB / MultinomialNB / BernoulliNB
-
- GaussianNB:连续特征,假设正态分布
- MultinomialNB:计数特征,词频/TF-IDF
- BernoulliNB:二值特征,特征"有/无"
- 三种变体选型速查表
- 4. 文本特征工程:分词 → TF-IDF → n-gram
-
- 分词与预处理
- TF-IDF:词的重要性权衡
- n-gram:捕捉局部词序信息
- 5. 文本分类完整 Pipeline
- 6. 概率校准:让概率输出真正可信
-
- 为什么概率校准很重要
- 校准曲线(Reliability Diagram)
- 两种校准方法对比
- 7. 生成模型 vs 判别模型
-
- 两类模型的建模方式
- 选型决策矩阵
- 生成模型的三个独特优势
- 8. 实战:新闻文本多分类(MultinomialNB + 增量更新)
- 9. NLP 的下一步:从词袋到语义表示的演进路线
- 小结
Gmail 的垃圾邮件过滤器至今仍有朴素贝叶斯的身影。不是因为没有更准确的模型——XGBoost 在绝大多数场景下精度更高——而是因为**"极低延迟 + 增量更新 + 概率输出"的组合只有朴素贝叶斯能同时满足**。每封新邮件只需做一次乘法,不用重新训练;新词汇出现时,只需更新对应词的计数,整个模型几毫秒内完成更新。
概率模型的核心价值不在精度,在于**“知道自己有多大把握”**。医疗诊断给出的不是"是/否",而是"患病概率 87%";信用评分模型的输出不是标签,而是违约概率,用于决定授信额度。在这些场景里,模型的概率输出必须可信(校准良好),否则所有后续决策都是建立在虚假数字上的。
本文从贝叶斯定理的直觉出发,深挖朴素假设为什么在大多数情况下"虽朴素但有效",然后走完文本分类的完整工程流程,最后讨论概率校准这个被大多数教程忽略的关键议题。
1. 贝叶斯定理:先验、似然与后验
从一个医学诊断问题出发
某疾病的患病率(先验)是 0.1%。有一种检测方法:患病者 99% 会检测为阳性,健康者 1% 会误检为阳性。
现在某人检测结果为阳性——患病概率是多少?
直觉上很多人会说"99%"。正确答案是:
P
(
患病
∣
阳性
)
=
P
(
阳性
∣
患病
)
×
P
(
患病
)
P
(
阳性
)
P(\\text{患病} | \\text{阳性}) = \\frac{P(\\text{阳性} | \\text{患病}) \\times P(\\text{患病})}{P(\\text{阳性})}
P(患病∣阳性)=P(阳性)P(阳性∣患病)×P(患病)
=
0.99
×
0.001
0.99
×
0.001
+
0.01
×
0.999
≈
0.00099
0.00099
+
0.00999
≈
9
%
= \\frac{0.99 \\times 0.001}{0.99 \\times 0.001 + 0.01 \\times 0.999} \\approx \\frac{0.00099}{0.00099 + 0.00999} \\approx 9\\%
=0.99×0.001+0.01×0.9990.99×0.001≈0.00099+0.009990.00099≈9%
仅有 9%。这是贝叶斯定理最反直觉的一面——当先验概率(患病率)极低时,即使是高精度的检测,阳性结果的预测价值也很低。
三个核心概念
先验(Prior)
P
(
C
)
P(C)
P(C):在观察到任何数据之前,对类别概率的初始估计。在垃圾邮件过滤中,先验是"历史上约 30% 的邮件是垃圾邮件"。
似然(Likelihood)
P
(
X
∣
C
)
P(X|C)
P(X∣C):在假设类别为
C
C
C 的条件下,观察到当前数据
X
X
X 的概率。“如果这是垃圾邮件,出现’免费’这个词的概率是多少?”
后验(Posterior)
P
(
C
∣
X
)
P(C|X)
P(C∣X):观察到数据
X
X
X 后,类别为
C
C
C 的更新概率。这是真正想要的输出。
P
(
C
∣
X
)
⏟
后验
∝
P
(
X
∣
C
)
⏟
似然
×
P
(
C
)
⏟
先验
\\underbrace{P(C|X)}_{\\text{后验}} \\propto \\underbrace{P(X|C)}_{\\text{似然}} \\times \\underbrace{P(C)}_{\\text{先验}}
后验
P(C∣X)∝似然
P(X∣C)×先验
P(C)
(分母
P
(
X
)
P(X)
P(X) 对所有类别相同,可以忽略,只做归一化使用)
贝叶斯更新的链式直觉
#mermaid-svg-jTmxVAGzldI8DPei{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jTmxVAGzldI8DPei .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jTmxVAGzldI8DPei .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jTmxVAGzldI8DPei .error-icon{fill:#552222;}#mermaid-svg-jTmxVAGzldI8DPei .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jTmxVAGzldI8DPei .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jTmxVAGzldI8DPei .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jTmxVAGzldI8DPei .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jTmxVAGzldI8DPei .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jTmxVAGzldI8DPei .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jTmxVAGzldI8DPei .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jTmxVAGzldI8DPei .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jTmxVAGzldI8DPei .marker.cross{stroke:#333333;}#mermaid-svg-jTmxVAGzldI8DPei svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jTmxVAGzldI8DPei p{margin:0;}#mermaid-svg-jTmxVAGzldI8DPei .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-jTmxVAGzldI8DPei .cluster-label text{fill:#333;}#mermaid-svg-jTmxVAGzldI8DPei .cluster-label span{color:#333;}#mermaid-svg-jTmxVAGzldI8DPei .cluster-label span p{background-color:transparent;}#mermaid-svg-jTmxVAGzldI8DPei .label text,#mermaid-svg-jTmxVAGzldI8DPei span{fill:#333;color:#333;}#mermaid-svg-jTmxVAGzldI8DPei .node rect,#mermaid-svg-jTmxVAGzldI8DPei .node circle,#mermaid-svg-jTmxVAGzldI8DPei .node ellipse,#mermaid-svg-jTmxVAGzldI8DPei .node polygon,#mermaid-svg-jTmxVAGzldI8DPei .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jTmxVAGzldI8DPei .rough-node .label text,#mermaid-svg-jTmxVAGzldI8DPei .node .label text,#mermaid-svg-jTmxVAGzldI8DPei .image-shape .label,#mermaid-svg-jTmxVAGzldI8DPei .icon-shape .label{text-anchor:middle;}#mermaid-svg-jTmxVAGzldI8DPei .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jTmxVAGzldI8DPei .rough-node .label,#mermaid-svg-jTmxVAGzldI8DPei .node .label,#mermaid-svg-jTmxVAGzldI8DPei .image-shape .label,#mermaid-svg-jTmxVAGzldI8DPei .icon-shape .label{text-align:center;}#mermaid-svg-jTmxVAGzldI8DPei .node.clickable{cursor:pointer;}#mermaid-svg-jTmxVAGzldI8DPei .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jTmxVAGzldI8DPei .arrowheadPath{fill:#333333;}#mermaid-svg-jTmxVAGzldI8DPei .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jTmxVAGzldI8DPei .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jTmxVAGzldI8DPei .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jTmxVAGzldI8DPei .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jTmxVAGzldI8DPei .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jTmxVAGzldI8DPei .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jTmxVAGzldI8DPei .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jTmxVAGzldI8DPei .cluster text{fill:#333;}#mermaid-svg-jTmxVAGzldI8DPei .cluster span{color:#333;}#mermaid-svg-jTmxVAGzldI8DPei div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jTmxVAGzldI8DPei .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jTmxVAGzldI8DPei rect.text{fill:none;stroke-width:0;}#mermaid-svg-jTmxVAGzldI8DPei .icon-shape,#mermaid-svg-jTmxVAGzldI8DPei .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jTmxVAGzldI8DPei .icon-shape p,#mermaid-svg-jTmxVAGzldI8DPei .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jTmxVAGzldI8DPei .icon-shape .label rect,#mermaid-svg-jTmxVAGzldI8DPei .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jTmxVAGzldI8DPei .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jTmxVAGzldI8DPei .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jTmxVAGzldI8DPei :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
先验 P(C)经验判断'30%是垃圾邮件'
似然 P(X|C)新证据'出现了 免费、中奖'
后验 P(C|X)更新后的判断'90%是垃圾邮件'
下一轮先验先验 = 本轮后验继续更新
再次更新出现了 点击链接…
贝叶斯更新是可以串联的:每次观察到新证据,上一轮的后验就变成下一轮的先验。这种递进式更新的特性,是朴素贝叶斯天然支持增量学习的根本原因。
2. 朴素贝叶斯的"朴素":条件独立假设为何管用
独立假设的数学形式
若样本有
p
p
p 个特征
x
1
,
x
2
,
…
,
x
p
x_1, x_2, \\ldots, x_p
x1,x2,…,xp,完整的似然计算需要:
P
(
X
∣
C
)
=
P
(
x
1
,
x
2
,
…
,
x
p
∣
C
)
P(X|C) = P(x_1, x_2, \\ldots, x_p | C)
P(X∣C)=P(x1,x2,…,xp∣C)
这是一个
p
p
p 维联合分布——对于 10000 维的文本特征,直接计算根本不可行(参数量爆炸)。
朴素假设:在给定类别
C
C
C 的条件下,所有特征相互独立:
P
(
X
∣
C
)
=
∏
j
=
1
p
P
(
x
j
∣
C
)
P(X|C) = \\prod_{j=1}^p P(x_j | C)
P(X∣C)=j=1∏pP(xj∣C)
联合概率变成了各特征概率的乘积,参数量从指数级降到线性级。
"朴素"假设下为什么还能管用?
这是很多教程含糊带过的关键问题。答案有两个层次:
理论层面:分类任务只需要正确的排序,不需要精确的概率值。即使各特征确实不独立,只要它们的依赖关系对不同类别的方向影响相似(即相关性不改变哪个类别的后验更大),分类结果仍然正确。
经验层面:文本中的词确实存在依赖("人工"后面跟"智能"的概率远高于随机),但这种依赖在"垃圾邮件"和"正常邮件"两个类别内通常是对称的——同样的词依赖模式在两类中方向类似,因此相减后影响减弱。
什么时候假设会失效:
- 特征依赖关系在不同类别间不对称时(某词组合只在某一类出现)
- 特征之间存在强共线性,且共线性方向跨类别不一致时
- 使用概率输出做后续决策时(此时概率值本身需要准确,不只需要排序正确)
from sklearn.naive_bayes import MultinomialNB
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
# 20 Newsgroups 数据集:约 18000 条新闻文本,20个类别
newsgroups = fetch_20newsgroups(subset='train', remove=('headers', 'footers', 'quotes'))
X_text = newsgroups.data
y = newsgroups.target
# 验证:即使朴素假设明显被违反,分类效果仍然合理
vectorizer = TfidfVectorizer(max_features=10000)
X = vectorizer.fit_transform(X_text)
nb = MultinomialNB(alpha=0.1)
from sklearn.model_selection import cross_val_score
scores = cross_val_score(nb, X, y, cv=5, scoring='accuracy')
print(f"MultinomialNB 5-fold Accuracy: {scores.mean():.3f} ± {scores.std():.3f}")
# 通常 0.82 左右,对 20 分类任务已属不错
3. 三种变体:GaussianNB / MultinomialNB / BernoulliNB
朴素贝叶斯不是一个模型,而是一类模型——不同变体对特征的概率分布做了不同假设。选错变体,效果会显著下降。
GaussianNB:连续特征,假设正态分布
假设每个特征在每个类别内服从高斯分布,参数为该类别下的样本均值和标准差:
P
(
x
j
∣
C
)
=
1
2
π
σ
j
C
2
exp
(
−
(
x
j
−
μ
j
C
)
2
2
σ
j
C
2
)
P(x_j | C) = \\frac{1}{\\sqrt{2\\pi \\sigma_{jC}^2}} \\exp\\left(-\\frac{(x_j – \\mu_{jC})^2}{2\\sigma_{jC}^2}\\right)
P(xj∣C)=2πσjC2
1exp(−2σjC2(xj−μjC)2)
适用场景:传感器数据、医学指标(身高、体重、血压)、经过归一化的连续特征。 注意:如果特征分布严重偏态(如指数分布),应先做对数变换。
from sklearn.naive_bayes import GaussianNB
from sklearn.datasets import load_iris
X_iris, y_iris = load_iris(return_X_y=True)
gnb = GaussianNB()
scores = cross_val_score(gnb, X_iris, y_iris, cv=5)
print(f"GaussianNB on Iris: {scores.mean():.3f}") # ≈ 0.953
MultinomialNB:计数特征,词频/TF-IDF
假设特征是某事件发生次数的计数。对文本来说,特征是词的出现次数(或 TF-IDF 权重):
P
(
x
j
∣
C
)
∝
(
θ
j
C
)
x
j
P(x_j | C) \\propto (\\theta_{jC})^{x_j}
P(xj∣C)∝(θjC)xj
适用场景:文本分类(词频/TF-IDF)、文档分类、用户行为计数。 不适用:负值特征(如 TF-IDF 归一化后可能出现极端值)、连续特征。
拉普拉斯平滑(Laplace Smoothing) 通过 alpha 参数控制,避免某个词在某类别中从未出现时概率为 0 导致整个后验为 0:
P
^
(
x
j
∣
C
)
=
count
(
x
j
,
C
)
+
α
count
(
C
)
+
α
×
∣
V
∣
\\hat{P}(x_j | C) = \\frac{\\text{count}(x_j, C) + \\alpha}{\\text{count}(C) + \\alpha \\times |V|}
P^(xj∣C)=count(C)+α×∣V∣count(xj,C)+α
alpha=1 是标准拉普拉斯平滑,alpha=0.1 更平衡(数据量大时推荐)。
BernoulliNB:二值特征,特征"有/无"
假设每个特征是 0/1 二值,且对没有出现的特征也给予惩罚:
P
(
X
∣
C
)
=
∏
j
=
1
p
P
(
x
j
∣
C
)
x
j
×
(
1
−
P
(
x
j
∣
C
)
)
1
−
x
j
P(X|C) = \\prod_{j=1}^p P(x_j|C)^{x_j} \\times (1 – P(x_j|C))^{1-x_j}
P(X∣C)=j=1∏pP(xj∣C)xj×(1−P(xj∣C))1−xj
核心区别:MultinomialNB 只关心"出现了什么词",BernoulliNB 同时关心"没出现什么词"。对于短文本(<20 词),词的缺席信息很有价值,BernoulliNB 通常更好;长文本则 MultinomialNB 更合适。
三种变体选型速查表
#mermaid-svg-CwA26E2vNqOL7qcl{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CwA26E2vNqOL7qcl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CwA26E2vNqOL7qcl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CwA26E2vNqOL7qcl .error-icon{fill:#552222;}#mermaid-svg-CwA26E2vNqOL7qcl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CwA26E2vNqOL7qcl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CwA26E2vNqOL7qcl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CwA26E2vNqOL7qcl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CwA26E2vNqOL7qcl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CwA26E2vNqOL7qcl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CwA26E2vNqOL7qcl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CwA26E2vNqOL7qcl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CwA26E2vNqOL7qcl .marker.cross{stroke:#333333;}#mermaid-svg-CwA26E2vNqOL7qcl svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CwA26E2vNqOL7qcl p{margin:0;}#mermaid-svg-CwA26E2vNqOL7qcl .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-CwA26E2vNqOL7qcl .cluster-label text{fill:#333;}#mermaid-svg-CwA26E2vNqOL7qcl .cluster-label span{color:#333;}#mermaid-svg-CwA26E2vNqOL7qcl .cluster-label span p{background-color:transparent;}#mermaid-svg-CwA26E2vNqOL7qcl .label text,#mermaid-svg-CwA26E2vNqOL7qcl span{fill:#333;color:#333;}#mermaid-svg-CwA26E2vNqOL7qcl .node rect,#mermaid-svg-CwA26E2vNqOL7qcl .node circle,#mermaid-svg-CwA26E2vNqOL7qcl .node ellipse,#mermaid-svg-CwA26E2vNqOL7qcl .node polygon,#mermaid-svg-CwA26E2vNqOL7qcl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CwA26E2vNqOL7qcl .rough-node .label text,#mermaid-svg-CwA26E2vNqOL7qcl .node .label text,#mermaid-svg-CwA26E2vNqOL7qcl .image-shape .label,#mermaid-svg-CwA26E2vNqOL7qcl .icon-shape .label{text-anchor:middle;}#mermaid-svg-CwA26E2vNqOL7qcl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CwA26E2vNqOL7qcl .rough-node .label,#mermaid-svg-CwA26E2vNqOL7qcl .node .label,#mermaid-svg-CwA26E2vNqOL7qcl .image-shape .label,#mermaid-svg-CwA26E2vNqOL7qcl .icon-shape .label{text-align:center;}#mermaid-svg-CwA26E2vNqOL7qcl .node.clickable{cursor:pointer;}#mermaid-svg-CwA26E2vNqOL7qcl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CwA26E2vNqOL7qcl .arrowheadPath{fill:#333333;}#mermaid-svg-CwA26E2vNqOL7qcl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CwA26E2vNqOL7qcl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CwA26E2vNqOL7qcl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CwA26E2vNqOL7qcl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CwA26E2vNqOL7qcl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CwA26E2vNqOL7qcl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CwA26E2vNqOL7qcl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CwA26E2vNqOL7qcl .cluster text{fill:#333;}#mermaid-svg-CwA26E2vNqOL7qcl .cluster span{color:#333;}#mermaid-svg-CwA26E2vNqOL7qcl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CwA26E2vNqOL7qcl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CwA26E2vNqOL7qcl rect.text{fill:none;stroke-width:0;}#mermaid-svg-CwA26E2vNqOL7qcl .icon-shape,#mermaid-svg-CwA26E2vNqOL7qcl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CwA26E2vNqOL7qcl .icon-shape p,#mermaid-svg-CwA26E2vNqOL7qcl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CwA26E2vNqOL7qcl .icon-shape .label rect,#mermaid-svg-CwA26E2vNqOL7qcl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CwA26E2vNqOL7qcl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CwA26E2vNqOL7qcl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CwA26E2vNqOL7qcl :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
是,连续实数
否
是
否,偏态
是,词频/TF-IDF
否,二值 0/1
短文本 < 20词
长文本 > 20词
特征类型?
连续型?
分布接近正态?
计数/频率型?
GaussianNB鸢尾花/医学指标传感器数据
先做对数变换再用 GaussianNB
文本长度?
BernoulliNB短文本 / 关键词存在性
MultinomialNB新闻分类/垃圾邮件
4. 文本特征工程:分词 → TF-IDF → n-gram
分词与预处理
中文文本需要显式分词(英文以空格天然分隔)。jieba 是最常用的中文分词库:
import jieba
import jieba.analyse
text = "机器学习是人工智能的一个重要分支,通过数据驱动的方式学习规律"
# 精确模式(适合文本分析)
words = jieba.lcut(text)
print(words)
# ['机器', '学习', '是', '人工智能', '的', '一个', '重要', '分支', ',', …]
# 去停用词(以自定义停用词表为例)
stopwords = {'是', '的', '了', '在', '和', '一个', ',', '。'}
words_clean = [w for w in words if w not in stopwords and len(w) > 1]
print(words_clean)
# ['机器', '学习', '人工智能', '重要', '分支', '通过', '数据', '驱动', '方式', '学习', '规律']
TF-IDF:词的重要性权衡
TF(词频):词在当前文档中出现的频率。高频词不一定重要——“的”"是"在任何文档中都高频。
IDF(逆文档频率):
log
N
1
+
d
f
(
t
)
\\log \\frac{N}{1 + df(t)}
log1+df(t)N,其中
N
N
N 是总文档数,
d
f
(
t
)
df(t)
df(t) 是包含词
t
t
t 的文档数。词在越多文档中出现,IDF 越低——"的"在所有文档里都有,IDF 趋近于 0,权重被自然压制。
TF-IDF
(
t
,
d
)
=
TF
(
t
,
d
)
×
IDF
(
t
)
\\text{TF-IDF}(t, d) = \\text{TF}(t, d) \\times \\text{IDF}(t)
TF-IDF(t,d)=TF(t,d)×IDF(t)
from sklearn.feature_extraction.text import TfidfVectorizer
# 中文需要先分词,sklearn 的分析器默认按空格分割
def chinese_tokenizer(text):
return [w for w in jieba.lcut(text) if len(w) > 1]
vectorizer = TfidfVectorizer(
tokenizer=chinese_tokenizer,
max_features=50000, # 词表大小上限
min_df=2, # 至少出现在 2 篇文档中(过滤极低频词)
max_df=0.95, # 在 95% 以上文档中出现则过滤(高频停用词)
sublinear_tf=True # TF 取 log(1 + tf),抑制高频词的极端值
)
n-gram:捕捉局部词序信息
单词袋(Unigram)完全忽略词序——"好不好"和"不好好"的词袋表示相同。n-gram 通过相邻
n
n
n 个词的组合捕捉局部语义:
- Bigram(2-gram):“机器学习” 而不只是 “机器” + “学习”
- Trigram(3-gram):“不是很好” 的整体语义与单词拆开不同
# 在 TF-IDF 中加入 bigram
vectorizer_ngram = TfidfVectorizer(
ngram_range=(1, 2), # 同时使用 unigram 和 bigram
max_features=100000,
min_df=2,
sublinear_tf=True
)
实践建议:加入 bigram 通常能提升 2-5% 的准确率,但会使特征维度翻倍。trigram 以上收益递减,且会产生大量稀疏特征,谨慎使用。
5. 文本分类完整 Pipeline
#mermaid-svg-LAFORc9FgsO1mD3N{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LAFORc9FgsO1mD3N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LAFORc9FgsO1mD3N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LAFORc9FgsO1mD3N .error-icon{fill:#552222;}#mermaid-svg-LAFORc9FgsO1mD3N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LAFORc9FgsO1mD3N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LAFORc9FgsO1mD3N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LAFORc9FgsO1mD3N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LAFORc9FgsO1mD3N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LAFORc9FgsO1mD3N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LAFORc9FgsO1mD3N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LAFORc9FgsO1mD3N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LAFORc9FgsO1mD3N .marker.cross{stroke:#333333;}#mermaid-svg-LAFORc9FgsO1mD3N svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LAFORc9FgsO1mD3N p{margin:0;}#mermaid-svg-LAFORc9FgsO1mD3N .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LAFORc9FgsO1mD3N .cluster-label text{fill:#333;}#mermaid-svg-LAFORc9FgsO1mD3N .cluster-label span{color:#333;}#mermaid-svg-LAFORc9FgsO1mD3N .cluster-label span p{background-color:transparent;}#mermaid-svg-LAFORc9FgsO1mD3N .label text,#mermaid-svg-LAFORc9FgsO1mD3N span{fill:#333;color:#333;}#mermaid-svg-LAFORc9FgsO1mD3N .node rect,#mermaid-svg-LAFORc9FgsO1mD3N .node circle,#mermaid-svg-LAFORc9FgsO1mD3N .node ellipse,#mermaid-svg-LAFORc9FgsO1mD3N .node polygon,#mermaid-svg-LAFORc9FgsO1mD3N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LAFORc9FgsO1mD3N .rough-node .label text,#mermaid-svg-LAFORc9FgsO1mD3N .node .label text,#mermaid-svg-LAFORc9FgsO1mD3N .image-shape .label,#mermaid-svg-LAFORc9FgsO1mD3N .icon-shape .label{text-anchor:middle;}#mermaid-svg-LAFORc9FgsO1mD3N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LAFORc9FgsO1mD3N .rough-node .label,#mermaid-svg-LAFORc9FgsO1mD3N .node .label,#mermaid-svg-LAFORc9FgsO1mD3N .image-shape .label,#mermaid-svg-LAFORc9FgsO1mD3N .icon-shape .label{text-align:center;}#mermaid-svg-LAFORc9FgsO1mD3N .node.clickable{cursor:pointer;}#mermaid-svg-LAFORc9FgsO1mD3N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LAFORc9FgsO1mD3N .arrowheadPath{fill:#333333;}#mermaid-svg-LAFORc9FgsO1mD3N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LAFORc9FgsO1mD3N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LAFORc9FgsO1mD3N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LAFORc9FgsO1mD3N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LAFORc9FgsO1mD3N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LAFORc9FgsO1mD3N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LAFORc9FgsO1mD3N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LAFORc9FgsO1mD3N .cluster text{fill:#333;}#mermaid-svg-LAFORc9FgsO1mD3N .cluster span{color:#333;}#mermaid-svg-LAFORc9FgsO1mD3N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LAFORc9FgsO1mD3N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LAFORc9FgsO1mD3N rect.text{fill:none;stroke-width:0;}#mermaid-svg-LAFORc9FgsO1mD3N .icon-shape,#mermaid-svg-LAFORc9FgsO1mD3N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LAFORc9FgsO1mD3N .icon-shape p,#mermaid-svg-LAFORc9FgsO1mD3N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LAFORc9FgsO1mD3N .icon-shape .label rect,#mermaid-svg-LAFORc9FgsO1mD3N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LAFORc9FgsO1mD3N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LAFORc9FgsO1mD3N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LAFORc9FgsO1mD3N :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
原始文本语料
预处理去HTML/特殊字符统一大小写/繁简转换
分词英文: 按空格中文: jieba
去停用词+ 过滤低质量词
TF-IDF 向量化+ n-gram 可选
模型训练
模型选择
MultinomialNB速度最快基线
LinearSVC精度通常更高不输出概率
XGBoost/LightGBM精度最高速度慢
评估Accuracy/F1/混淆矩阵
sklearn Pipeline 将整个流程封装,避免训练/测试集的特征泄漏(TF-IDF 的 IDF 只能在训练集上 fit):
from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# 假设中文新闻数据
# texts: list of str(原始文本),labels: list of int(类别)
X_train, X_test, y_train, y_test = train_test_split(
texts, labels, test_size=0.2, stratify=labels, random_state=42
)
# Pipeline 1:MultinomialNB 基线
pipeline_nb = Pipeline([
('tfidf', TfidfVectorizer(ngram_range=(1, 2), max_features=50000,
min_df=2, sublinear_tf=True)),
('clf', MultinomialNB(alpha=0.1))
])
pipeline_nb.fit(X_train, y_train)
y_pred_nb = pipeline_nb.predict(X_test)
print("=== MultinomialNB ===")
print(classification_report(y_test, y_pred_nb))
# Pipeline 2:LinearSVC(精度通常更高)
pipeline_svc = Pipeline([
('tfidf', TfidfVectorizer(ngram_range=(1, 2), max_features=50000,
min_df=2, sublinear_tf=True)),
('clf', LinearSVC(C=1.0, max_iter=1000))
])
pipeline_svc.fit(X_train, y_train)
y_pred_svc = pipeline_svc.predict(X_test)
print("=== LinearSVC ===")
print(classification_report(y_test, y_pred_svc))
6. 概率校准:让概率输出真正可信
为什么概率校准很重要
分类模型的输出概率经常是"不可信的"——模型说"80% 概率",但实际上并不意味着 80 个这样的样本里有 80 个是正类。
朴素贝叶斯的概率输出尤其极端:由于独立性假设被违反,似然值的乘积往往趋向于 0 或 1,导致后验概率呈现双峰分布(要么 99%,要么 1%,很少说"50%")。
医疗/金融场景的要求:概率必须是校准良好(Calibrated) 的——预测概率 70% 的样本集中,实际正例比例应接近 70%。
校准曲线(Reliability Diagram)
from sklearn.calibration import CalibrationDisplay, CalibratedClassifierCV
from sklearn.naive_bayes import MultinomialNB
import matplotlib.pyplot as plt
# 假设二分类任务
nb_raw = Pipeline([
('tfidf', TfidfVectorizer(max_features=10000)),
('clf', MultinomialNB(alpha=0.1))
])
nb_raw.fit(X_train, y_train)
# 校准前
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
CalibrationDisplay.from_estimator(nb_raw, X_test, y_test, n_bins=10, ax=ax1)
ax1.set_title("MultinomialNB(未校准)\\n概率极端化明显")
# Platt Scaling(sigmoid 校准)
nb_platt = CalibratedClassifierCV(
estimator=Pipeline([
('tfidf', TfidfVectorizer(max_features=10000)),
('clf', MultinomialNB(alpha=0.1))
]),
method='sigmoid', # Platt Scaling
cv=5
)
nb_platt.fit(X_train, y_train)
CalibrationDisplay.from_estimator(nb_platt, X_test, y_test, n_bins=10, ax=ax2)
ax2.set_title("MultinomialNB + Platt Scaling\\n概率分布更合理")
plt.tight_layout()
两种校准方法对比
| Platt Scaling(sigmoid) | 用逻辑回归将原始分数映射到概率 | 概率偏离严重,S 形曲线 | 快,但只能修正单调变换 |
| Isotonic Regression | 非参数单调校准,更灵活 | 有足够数据(>1000 样本) | 需要更多数据,可能过拟合 |
实践建议:如果业务上需要使用概率值做决策(如风控授信、医疗分诊),必须进行概率校准。如果只关心分类结果(精确率/召回率),校准可选。
7. 生成模型 vs 判别模型
这是机器学习中的一个根本性区别,但教材里常常一笔带过。
两类模型的建模方式
判别模型(Discriminative):直接学习决策边界
P
(
C
∣
X
)
P(C|X)
P(C∣X),不管数据是怎么生成的,只关心如何区分类别。
代表:逻辑回归、SVM、神经网络、决策树。
生成模型(Generative):学习
P
(
X
∣
C
)
P(X|C)
P(X∣C) 和
P
(
C
)
P(C)
P(C),建模数据的生成过程,通过贝叶斯定理得到
P
(
C
∣
X
)
P(C|X)
P(C∣X)。
代表:朴素贝叶斯、高斯混合模型(GMM)、隐马尔可夫模型(HMM)。
选型决策矩阵
#mermaid-svg-qRj8QHBoppraufEo{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qRj8QHBoppraufEo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qRj8QHBoppraufEo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qRj8QHBoppraufEo .error-icon{fill:#552222;}#mermaid-svg-qRj8QHBoppraufEo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qRj8QHBoppraufEo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qRj8QHBoppraufEo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qRj8QHBoppraufEo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qRj8QHBoppraufEo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qRj8QHBoppraufEo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qRj8QHBoppraufEo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qRj8QHBoppraufEo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qRj8QHBoppraufEo .marker.cross{stroke:#333333;}#mermaid-svg-qRj8QHBoppraufEo svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qRj8QHBoppraufEo p{margin:0;}#mermaid-svg-qRj8QHBoppraufEo .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-qRj8QHBoppraufEo .cluster-label text{fill:#333;}#mermaid-svg-qRj8QHBoppraufEo .cluster-label span{color:#333;}#mermaid-svg-qRj8QHBoppraufEo .cluster-label span p{background-color:transparent;}#mermaid-svg-qRj8QHBoppraufEo .label text,#mermaid-svg-qRj8QHBoppraufEo span{fill:#333;color:#333;}#mermaid-svg-qRj8QHBoppraufEo .node rect,#mermaid-svg-qRj8QHBoppraufEo .node circle,#mermaid-svg-qRj8QHBoppraufEo .node ellipse,#mermaid-svg-qRj8QHBoppraufEo .node polygon,#mermaid-svg-qRj8QHBoppraufEo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qRj8QHBoppraufEo .rough-node .label text,#mermaid-svg-qRj8QHBoppraufEo .node .label text,#mermaid-svg-qRj8QHBoppraufEo .image-shape .label,#mermaid-svg-qRj8QHBoppraufEo .icon-shape .label{text-anchor:middle;}#mermaid-svg-qRj8QHBoppraufEo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qRj8QHBoppraufEo .rough-node .label,#mermaid-svg-qRj8QHBoppraufEo .node .label,#mermaid-svg-qRj8QHBoppraufEo .image-shape .label,#mermaid-svg-qRj8QHBoppraufEo .icon-shape .label{text-align:center;}#mermaid-svg-qRj8QHBoppraufEo .node.clickable{cursor:pointer;}#mermaid-svg-qRj8QHBoppraufEo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qRj8QHBoppraufEo .arrowheadPath{fill:#333333;}#mermaid-svg-qRj8QHBoppraufEo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qRj8QHBoppraufEo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qRj8QHBoppraufEo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qRj8QHBoppraufEo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qRj8QHBoppraufEo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qRj8QHBoppraufEo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qRj8QHBoppraufEo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qRj8QHBoppraufEo .cluster text{fill:#333;}#mermaid-svg-qRj8QHBoppraufEo .cluster span{color:#333;}#mermaid-svg-qRj8QHBoppraufEo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qRj8QHBoppraufEo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qRj8QHBoppraufEo rect.text{fill:none;stroke-width:0;}#mermaid-svg-qRj8QHBoppraufEo .icon-shape,#mermaid-svg-qRj8QHBoppraufEo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qRj8QHBoppraufEo .icon-shape p,#mermaid-svg-qRj8QHBoppraufEo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qRj8QHBoppraufEo .icon-shape .label rect,#mermaid-svg-qRj8QHBoppraufEo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qRj8QHBoppraufEo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qRj8QHBoppraufEo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qRj8QHBoppraufEo :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
少 < 1000
多 > 10000
是,数据流式到来
否,批量训练
是,医疗/金融决策
否,只需分类标签
是
可解释性优先
选型维度
数据量?
生成模型朴素贝叶斯/GMM少量数据也能运作
是否需要增量更新?
生成模型朴素贝叶斯 partial_fit无需重新训练
是否需要精确概率?
判别模型 + 概率校准LR/SVC + CalibratedClassifierCV
精度优先?
判别模型XGBoost / LinearSVC精度通常更高
逻辑回归生成模型也可
生成模型的三个独特优势
优势一:增量学习(Online Learning)
朴素贝叶斯支持 partial_fit——不需要重新读取历史数据,直接用新批次数据更新模型:
from sklearn.naive_bayes import MultinomialNB
import numpy as np
# 模拟流式数据:每次到来一批
nb_online = MultinomialNB(alpha=0.1)
all_classes = np.array([0, 1, 2, 3]) # 必须在第一次 partial_fit 时指定
for batch_X, batch_y in data_stream:
nb_online.partial_fit(batch_X, batch_y, classes=all_classes)
优势二:数据量要求低
生成模型在数据量少时往往比判别模型更稳健——因为它通过先验注入了归纳偏置,不需要大量样本来确定决策边界。
优势三:可以生成样本
生成模型建模了
P
(
X
∣
C
)
P(X|C)
P(X∣C),因此理论上可以采样生成属于某类别的新样本——这在数据增强场景有独特价值。
8. 实战:新闻文本多分类(MultinomialNB + 增量更新)
以 THUCNews(清华大学中文新闻分类数据集)为例,演示从原始文本到多分类预测的完整链路。
import jieba
import numpy as np
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, accuracy_score
from sklearn.calibration import CalibratedClassifierCV
# ===== 步骤 1:文本预处理 =====
def preprocess_chinese(text):
"""分词 + 去停用词"""
# 假设 stopwords_set 已从停用词表加载
tokens = jieba.lcut(text)
return ' '.join([t for t in tokens if len(t) > 1 and t not in stopwords_set])
# ===== 步骤 2:Pipeline 构建 =====
# 基线:MultinomialNB
pipeline_nb = Pipeline([
('tfidf', TfidfVectorizer(
max_features=100000,
ngram_range=(1, 2),
min_df=2,
sublinear_tf=True
)),
('clf', MultinomialNB(alpha=0.1))
])
# 进阶:LinearSVC(精度更高但不直接输出概率)
pipeline_svc = Pipeline([
('tfidf', TfidfVectorizer(
max_features=100000,
ngram_range=(1, 2),
min_df=2,
sublinear_tf=True
)),
('clf', CalibratedClassifierCV(LinearSVC(C=1.0, max_iter=2000), cv=5)
)
])
# ===== 步骤 3:训练与评估 =====
for name, pipe in [('MultinomialNB', pipeline_nb), ('LinearSVC + 校准', pipeline_svc)]:
pipe.fit(X_train_processed, y_train)
y_pred = pipe.predict(X_test_processed)
print(f"\\n=== {name} ===")
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred, target_names=categories))
# ===== 步骤 4:MultinomialNB 增量更新演示 =====
nb_incremental = MultinomialNB(alpha=0.1)
tfidf = TfidfVectorizer(max_features=100000, ngram_range=(1, 2), sublinear_tf=True)
# 全量数据 fit tfidf(实际流式场景需要 HashingVectorizer)
X_all_sparse = tfidf.fit_transform(all_texts_processed)
# 分批增量训练
all_classes = np.unique(y_all)
batch_size = 1000
for start in range(0, len(X_all_sparse.shape[0]), batch_size):
end = start + batch_size
nb_incremental.partial_fit(
X_all_sparse[start:end],
y_all[start:end],
classes=all_classes
)
print(f"\\n增量训练完成: {nb_incremental.class_count_.sum():.0f} 个样本已学习")
典型结果参考(THUCNews 14 分类,10 万样本):
| MultinomialNB | ~90% | < 2s | ✅ 支持 |
| LinearSVC | ~94% | ~15s | ❌ 不支持 |
| XGBoost(TF-IDF) | ~93% | ~3min | ❌ 不支持 |
MultinomialNB 的 90% 精度配合 2 秒训练时间和增量更新能力,在许多实时分类场景中是最优的工程选择,而非只是"入门算法"。
9. NLP 的下一步:从词袋到语义表示的演进路线
词袋模型(TF-IDF)的核心局限:同义词不知道是同义词——"汽车"和"车辆"在词袋里是完全不同的维度,但语义几乎相同。词向量(Word2Vec/FastText)和预训练语言模型(BERT)正是为了解决这个问题。
#mermaid-svg-9h7hTSK5s6McamK8{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9h7hTSK5s6McamK8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9h7hTSK5s6McamK8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9h7hTSK5s6McamK8 .error-icon{fill:#552222;}#mermaid-svg-9h7hTSK5s6McamK8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9h7hTSK5s6McamK8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9h7hTSK5s6McamK8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9h7hTSK5s6McamK8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9h7hTSK5s6McamK8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9h7hTSK5s6McamK8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9h7hTSK5s6McamK8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9h7hTSK5s6McamK8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9h7hTSK5s6McamK8 .marker.cross{stroke:#333333;}#mermaid-svg-9h7hTSK5s6McamK8 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9h7hTSK5s6McamK8 p{margin:0;}#mermaid-svg-9h7hTSK5s6McamK8 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-9h7hTSK5s6McamK8 .cluster-label text{fill:#333;}#mermaid-svg-9h7hTSK5s6McamK8 .cluster-label span{color:#333;}#mermaid-svg-9h7hTSK5s6McamK8 .cluster-label span p{background-color:transparent;}#mermaid-svg-9h7hTSK5s6McamK8 .label text,#mermaid-svg-9h7hTSK5s6McamK8 span{fill:#333;color:#333;}#mermaid-svg-9h7hTSK5s6McamK8 .node rect,#mermaid-svg-9h7hTSK5s6McamK8 .node circle,#mermaid-svg-9h7hTSK5s6McamK8 .node ellipse,#mermaid-svg-9h7hTSK5s6McamK8 .node polygon,#mermaid-svg-9h7hTSK5s6McamK8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9h7hTSK5s6McamK8 .rough-node .label text,#mermaid-svg-9h7hTSK5s6McamK8 .node .label text,#mermaid-svg-9h7hTSK5s6McamK8 .image-shape .label,#mermaid-svg-9h7hTSK5s6McamK8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-9h7hTSK5s6McamK8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9h7hTSK5s6McamK8 .rough-node .label,#mermaid-svg-9h7hTSK5s6McamK8 .node .label,#mermaid-svg-9h7hTSK5s6McamK8 .image-shape .label,#mermaid-svg-9h7hTSK5s6McamK8 .icon-shape .label{text-align:center;}#mermaid-svg-9h7hTSK5s6McamK8 .node.clickable{cursor:pointer;}#mermaid-svg-9h7hTSK5s6McamK8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9h7hTSK5s6McamK8 .arrowheadPath{fill:#333333;}#mermaid-svg-9h7hTSK5s6McamK8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9h7hTSK5s6McamK8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9h7hTSK5s6McamK8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9h7hTSK5s6McamK8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9h7hTSK5s6McamK8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9h7hTSK5s6McamK8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9h7hTSK5s6McamK8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9h7hTSK5s6McamK8 .cluster text{fill:#333;}#mermaid-svg-9h7hTSK5s6McamK8 .cluster span{color:#333;}#mermaid-svg-9h7hTSK5s6McamK8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9h7hTSK5s6McamK8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9h7hTSK5s6McamK8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-9h7hTSK5s6McamK8 .icon-shape,#mermaid-svg-9h7hTSK5s6McamK8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9h7hTSK5s6McamK8 .icon-shape p,#mermaid-svg-9h7hTSK5s6McamK8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9h7hTSK5s6McamK8 .icon-shape .label rect,#mermaid-svg-9h7hTSK5s6McamK8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9h7hTSK5s6McamK8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9h7hTSK5s6McamK8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9h7hTSK5s6McamK8 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
当前位置
词袋 / TF-IDF・无语义・维度爆炸・同义词盲
词向量Word2Vec/FastText・语义空间・固定维度・泛化能力强
预训练语言模型BERT / RoBERTa・上下文感知・迁移学习・最强语义理解
大语言模型GPT/LLaMA・生成能力・零样本/少样本・多任务
适用场景:中短文本快速分类实时流式场景可解释性要求高
适用场景:句子相似度情感分析中等数据量
适用场景:复杂语义理解问答/摘要足够算力
选型建议:
- 文本分类 + 数据量大 + 速度要求高 → TF-IDF + LinearSVC / MultinomialNB
- 文本相似度 / 语义搜索 → Word2Vec / FastText + 余弦相似度
- 复杂 NLP 任务(QA/摘要/情感细粒度分析)→ BERT 微调
- 极少样本 / 零样本 → GPT 系列 API
专栏后续会专门讲词向量和预训练模型的实战应用(#18 文本特征进阶),这里先建立全局路线感。
小结
朴素贝叶斯是一个被严重低估的模型。它不够"新",但它能做到很多新模型做不到的事:毫秒级训练、增量更新、在极少数据下仍然有效、自然的概率解释框架。
真正理解朴素贝叶斯,需要回答三个问题:"朴素"假设何时成立何时失效(不是随机的,有规律可循);三种变体的选型依据(不只是"试试看");以及概率输出如何校准(没有校准的概率是虚假的置信度)。这三点串起来,才算真正懂了这个模型。
如果这篇文章对理解概率模型有帮助,欢迎点赞、收藏,这对创作的激励不亚于最直接的鼓励。技术写作不易,每一个赞都是继续深挖的动力。有问题欢迎在评论区讨论。
