欢迎光临
我们一直在努力

概率模型与 NLP 基础:贝叶斯/朴素贝叶斯/文本分类/语言模型入门

文章目录

    • 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.0010.00099+0.009990.000999%

仅有 9%。这是贝叶斯定理最反直觉的一面——当先验概率(患病率)极低时,即使是高精度的检测,阳性结果的预测价值也很低。

三个核心概念

先验(Prior)

P

(

C

)

P(C)

P(C):在观察到任何数据之前,对类别概率的初始估计。在垃圾邮件过滤中,先验是"历史上约 30% 的邮件是垃圾邮件"。

似然(Likelihood)

P

(

X

C

)

P(X|C)

P(XC):在假设类别为

C

C

C 的条件下,观察到当前数据

X

X

X 的概率。“如果这是垃圾邮件,出现’免费’这个词的概率是多少?”

后验(Posterior)

P

(

C

X

)

P(C|X)

P(CX):观察到数据

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(CX)似然

P(XC)×先验

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(XC)=P(x1,x2,,xpC)

这是一个

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(XC)=j=1pP(xjC)

联合概率变成了各特征概率的乘积,参数量从指数级降到线性级。

"朴素"假设下为什么还能管用?

这是很多教程含糊带过的关键问题。答案有两个层次:

理论层面:分类任务只需要正确的排序,不需要精确的概率值。即使各特征确实不独立,只要它们的依赖关系对不同类别的方向影响相似(即相关性不改变哪个类别的后验更大),分类结果仍然正确。

经验层面:文本中的词确实存在依赖("人工"后面跟"智能"的概率远高于随机),但这种依赖在"垃圾邮件"和"正常邮件"两个类别内通常是对称的——同样的词依赖模式在两类中方向类似,因此相减后影响减弱。

什么时候假设会失效:

  • 特征依赖关系在不同类别间不对称时(某词组合只在某一类出现)
  • 特征之间存在强共线性,且共线性方向跨类别不一致时
  • 使用概率输出做后续决策时(此时概率值本身需要准确,不只需要排序正确)

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(xjC)=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(xjC)(θ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^(xjC)=count(C)+α×Vcount(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(XC)=j=1pP(xjC)xj×(1P(xjC))1xj

核心区别: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(CX),不管数据是怎么生成的,只关心如何区分类别。

代表:逻辑回归、SVM、神经网络、决策树。

生成模型(Generative):学习

P

(

X

C

)

P(X|C)

P(XC)

P

(

C

)

P(C)

P(C),建模数据的生成过程,通过贝叶斯定理得到

P

(

C

X

)

P(C|X)

P(CX)

代表:朴素贝叶斯、高斯混合模型(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(XC),因此理论上可以采样生成属于某类别的新样本——这在数据增强场景有独特价值。


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 万样本):

模型Accuracy训练时间增量更新
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 文本特征进阶),这里先建立全局路线感。


小结

朴素贝叶斯是一个被严重低估的模型。它不够"新",但它能做到很多新模型做不到的事:毫秒级训练、增量更新、在极少数据下仍然有效、自然的概率解释框架。

真正理解朴素贝叶斯,需要回答三个问题:"朴素"假设何时成立何时失效(不是随机的,有规律可循);三种变体的选型依据(不只是"试试看");以及概率输出如何校准(没有校准的概率是虚假的置信度)。这三点串起来,才算真正懂了这个模型。


如果这篇文章对理解概率模型有帮助,欢迎点赞、收藏,这对创作的激励不亚于最直接的鼓励。技术写作不易,每一个赞都是继续深挖的动力。有问题欢迎在评论区讨论。

赞(0)
未经允许不得转载:171主机测评 » 概率模型与 NLP 基础:贝叶斯/朴素贝叶斯/文本分类/语言模型入门
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址