贝叶斯网络bayesian_network
算法思想:
贝叶斯网络(Bayesian Network):用有向图表达变量因果关系,再用条件概率做推断。
核心思想
现实里很多变量互相影响,但不必直接建一张巨大的联合概率表
P D I G LS
。贝叶斯网络做两件事:
- 用图结构压缩依赖关系:谁影响谁,一目了然
- 用局部条件概率表(CPD)拼出全局联合分布
对本例:
D(难度) ──┐
├──► G(成绩) ──► L(推荐信)
I(天赋) ──┤
└──► S(SAT)
联合分布可分解为:
P D I G L S =P D P I P G ∣ID P L∣G P S∣I

| 符号 | 含义 | 说明 |
|---|---|---|
| D | Difficulty(考试难度) | 难 / 不难 |
| I | Intelligence(个人天赋) | 一般 / 较好 |
| G | Grade(成绩) | 通常分 3 档 |
| L | Letter(推荐信质量) | 好 / 差 |
| S | SAT(SAT 分数) | 高 / 低 |
- ('D', 'G'):难度影响成绩
- ('I', 'G'):天赋影响成绩
- ('G', 'L'):成绩影响推荐信质量
- ('I', 'S'):天赋影响 SAT 分数
也就是经典的 Student 贝叶斯网络 结构:成绩由难度和天赋共同决定,推荐信只看成绩,SAT 只看天赋。
代码里 BayesianModel(...) 建结构,TabularCPD(...) 填各节点局部概率,add_cpds 把它们装进网络。
对应到代码的三层含义
1. 结构先验(谁依赖谁)
边 ('I','G')、('D','G') 表示:成绩由天赋和难度共同决定;L 只看 G,S 只看 I。
这是建模假设:推荐信不直接看天赋,SAT 不直接看考试难度。
2. 参数(条件概率)
如 G 的 values 是 P G∣ID
:在每一种 ID
下,成绩取 0/1/2 的概率。
没有父节点的 D、I 就是先验 PD
、PI
。
3. 推断(已知部分信息,求未知概率)
letter_infer.query(variables='G', evidence={'I': 1, 'D': 0})
含义:已知"天赋较好、考试不难",求成绩分布 P G∣I=1D=0
。
输出里 G(0)=0.9 就是在该证据下的后验。
这正是贝叶斯思想:
后验*∝* 似然*×* 先验

在网络里体现为:证据固定后,沿图用 CPD 做变量消除(VariableElimination),算出目标变量的后验分布。
和"朴素贝叶斯"的区别
| 本 notebook(贝叶斯网络) | 朴素贝叶斯 | |
|---|---|---|
| 结构 | 可任意有向无环图 | 通常"类别→特征",特征间条件独立 |
| 目标 | 任意变量间的概率推断 | 多为分类 Py∣x ![]() |
| 表达力 | 可建模更复杂因果链 | 假设更强、更简单 |
一句话总结
先把因果/依赖画成图,再给每个节点一张小概率表;有了证据后,按图做概率传播,得到你关心变量的后验。
这比直接枚举所有变量组合更省参数,也更符合"局部依赖、全局推断"的贝叶斯建模思路。
代码实战:
# 导入pgmpy相关模块
from pgmpy.factors.discrete import TabularCPD
from pgmpy.models import BayesianModel
letter_model = BayesianModel([('D', 'G'),
('I', 'G'),
('G', 'L'),
('I', 'S')])
# 学生成绩的条件概率分布
grade_cpd = TabularCPD(
variable='G', # 节点名称
variable_card=3, # 节点取值个数
values=[[0.3, 0.05, 0.9, 0.5], # 该节点的概率表
[0.4, 0.25, 0.08, 0.3],
[0.3, 0.7, 0.02, 0.2]],
evidence=['I', 'D'], # 该节点的依赖节点
evidence_card=[2, 2] # 依赖节点的取值个数
)
# 考试难度的条件概率分布
difficulty_cpd = TabularCPD(
variable='D',
variable_card=2,
values=[[0.6], [0.4]]
)
# 个人天赋的条件概率分布
intel_cpd = TabularCPD(
variable='I',
variable_card=2,
values=[[0.7], [0.3]]
)
# 推荐信质量的条件概率分布
letter_cpd = TabularCPD(
variable='L',
variable_card=2,
values=[[0.1, 0.4, 0.99],
[0.9, 0.6, 0.01]],
evidence=['G'],
evidence_card=[3]
)
# SAT考试分数的条件概率分布
sat_cpd = TabularCPD(
variable='S',
variable_card=2,
values=[[0.95, 0.2],
[0.05, 0.8]],
evidence=['I'],
evidence_card=[2]
)
# 将各节点添加到模型中,构建贝叶斯网络
letter_model.add_cpds(
grade_cpd,
difficulty_cpd,
intel_cpd,
letter_cpd,
sat_cpd
)
# 导入pgmpy贝叶斯推断模块
from pgmpy.inference import VariableElimination
# 贝叶斯网络推断
letter_infer = VariableElimination(letter_model)
# 天赋较好且考试不难的情况下推断该学生获得推荐信质量的好坏
prob_G = letter_infer.query(
variables=['G'],
evidence={'I': 1, 'D': 0})
print(prob_G)
输出结果,
WARNING:root:Replacing existing CPD for G
WARNING:root:Replacing existing CPD for D
WARNING:root:Replacing existing CPD for I
WARNING:root:Replacing existing CPD for L
WARNING:root:Replacing existing CPD for S
Finding Elimination Order: : 100%|██████████████████████████████████████████████████████| 2/2 00:00\<00:00, 668.95it/s
Eliminating: L: 100%|███████████████████████████████████████████████████████████████████| 2/2 00:00\<00:00, 285.72it/s
+------+----------+
| G | phi(G) |
+======+==========+
| G(0) | 0.9000 |
+------+----------+
| G(1) | 0.0800 |
+------+----------+
| G(2) | 0.0200 |
+------+----------+
其他注意,
TabularCPD维度必须和节点取值个数、父节点配置对齐。
对这段 grade_cpd:
variable='G', variable_card=3 # G 有 3 种取值 → 必须 3 行
evidence='I', 'D', evidence_card=2, 2 # I×D = 2×2=4 种组合 → 必须 4 列
所以 values 的形状固定为:
variable_card × evidence_card*=3×4*

对应关系
| 列0 | 列1 | 列2 | 列3 | |
|---|---|---|---|---|
| 父节点组合 | I=0,D=0 | I=0,D=1 | I=1,D=0 | I=1,D=1 |
| G=0 | 0.3 | 0.05 | 0.9 | 0.5 |
| G=1 | 0.4 | 0.25 | 0.08 | 0.3 |
| G=2 | 0.3 | 0.7 | 0.02 | 0.2 |
pgmpy 中列顺序是:evidence 里靠后的变量变化最快(这里 D 先变)。
还要满足概率约束
每一列是"在该父节点取值下,G 的条件分布",所以每列求和必须为 1:
- 列0:0.3+0.4+0.3=1
- 列1:0.05+0.25+0.7=1
- ...
若改成 2×5、4×4 等与 variable_card / evidence_card 不一致的形状,TabularCPD 会报错;列和不等于 1 也不合法。
朴素贝叶斯分类器naive_bayes
算法思想:
朴素贝叶斯分类器(Naive Bayes),分两段:手写离散版 + sklearn 高斯版。
在讲什么
给定特征 x 1 , x2
,预测类别 y∈ +1-1
。依据贝叶斯公式:
P y∣x ∝P y P x∣y

**"朴素"**假设:特征在给定类别下相互独立,于是
P x∣y =P x 1 ∣y P x 2 ∣y

选后验最大的 y
作为预测结果。
代码结构
1. 数据(李航《统计学习方法》表 4.1)
- x1:取值 1/2/3
- x2:S/M/L
- y:-1 / 1
2. nb_fit:训练 = 数频率
- class_prior:Py

- prior(特征, 取值, 类别):P x i=v∣y

3. predict:对测试样本算各类后验并取最大
例如 X_test = {x1:2, x2:'S'},对每个类别 c
计算:
P c ⋅P x 1 =2∣c ⋅P x 2 =S∣c

结果预测为 -1。
4. 对比 sklearn
用 GaussianNB 在鸢尾花上做连续特征版本(特征按高斯分布建模),准确率约 94.7%。
和 bayesian_network.ipynb 的区别
| naive_bayes.ipynb | bayesian_network.ipynb | |
|---|---|---|
| 任务 | 分类:Py∣x ![]() |
一般概率推断 |
| 结构 | 类别 → 各特征(特征间条件独立) | 自定义因果图 |
| 典型用途 | 文本分类、简单判别 | 因果建模、条件查询 |
一句话:用频率估计先验和条件概率,在特征独立假设下做贝叶斯分类。
代码实战:
import numpy as np
import pandas as pd
### 构造数据集
### 来自于李航统计学习方法表4.1
x1 = [1,1,1,1,1,2,2,2,2,2,3,3,3,3,3]
x2 = ['S','M','M','S','S','S','M','M','L','L','L','M','M','L','L']
y = [-1,-1,1,1,-1,-1,-1,1,1,1,1,1,1,1,-1]
df = pd.DataFrame({'x1':x1, 'x2':x2, 'y':y})
df.head()
| x1 | x2 | y | |
|---|---|---|---|
| 0 | 1 | S | -1 |
| 1 | 1 | M | -1 |
| 2 | 1 | M | 1 |
| 3 | 1 | S | 1 |
| 4 | 1 | S | -1 |
这是李航《统计学习方法》表 4.1 的离散分类小例子:15 条样本,2 个特征,1 个标签。
字段含义
| 列 | 含义 | 取值 |
|---|---|---|
| x1 | 特征 1 | 1 / 2 / 3(三类离散值) |
| x2 | 特征 2(常理解为尺寸) | S / M / L(小/中/大) |
| y | 类别标签 | +1 或 -1(两类) |
每一行是一个样本,例如第一行:
x1=1, x2=S, y=-1
表示:特征1取1、特征2为S时,类别是 -1。
完整数据长什么样
把三个列表按位置对齐后就是:
| 编号 | x1 | x2 | y |
|---|---|---|---|
| 1 | 1 | S | -1 |
| 2 | 1 | M | -1 |
| 3 | 1 | M | +1 |
| 4 | 1 | S | +1 |
| 5 | 1 | S | -1 |
| 6 | 2 | S | -1 |
| 7 | 2 | M | -1 |
| 8 | 2 | M | +1 |
| 9 | 2 | L | +1 |
| 10 | 2 | L | +1 |
| 11 | 3 | L | +1 |
| 12 | 3 | M | +1 |
| 13 | 3 | M | +1 |
| 14 | 3 | L | +1 |
| 15 | 3 | L | -1 |
df.head() 只显示前 5 行,所以你会看到前几条都是 x1=1。
怎么理解它的作用
这不是真实业务数据,而是为了手算朴素贝叶斯准备的教具:
- 特征都是离散的,方便数频次算 Py
、P x i∣y 
- 样本很少(15 条),便于对照书上公式验算
- 后面测试样本 {x1:2, x2:'S'},就是问:给定这两个特征值,更可能属于 +1 还是 -1
直观理解:
已知一些"特征组合 → 类别"的历史样本,学出概率规律,再给新组合做分类。
为什么其中编号1和编号4的数据,x1,x2一样,y却不一样呢?
因为这是有噪声/不确定的真实分类数据,不是"一个特征组合只能对应一个标签"的规则表。
同是 (x1=1, x2=S),数据里同时出现了:
- y = -1(如第 1、5 条)
- y = +1(如第 4 条)
说明:同样的特征,历史上两类都出现过,只是频率不同。
为什么要这样设计
若每种 (x1,x2) 都只对应唯一 y,那就变成查表,不必学概率了。
朴素贝叶斯正是处理这种同特征、不同标签的情况:
看频率,例如对 (1,S):
- 出现 -1 更多 → P y=-1∣x1=1x2=S
更大 - 出现 +1 较少 → 也可能,但概率更小
预测时选更可能的那一类,而不是要求特征组合绝对无冲突。
直观理解
就像:
- 同样"发烧 + 咳嗽",有人是感冒,有人是流感
- 不能说这个症状只能对应一种病,只能说更偏向某一种
所以 1,S,-1 和 1,S,+1 同时存在是正常的:特征相同,标签可不同;算法学的是概率倾向,不是硬规则。
X = df[['x1', 'x2']]
y = df[['y']]
def nb_fit(X, y):
classes = y[y.columns[0]].unique()
class_count = y[y.columns[0]].value_counts()
class_prior = class_count/len(y)
prior = dict()
for col in X.columns:
for j in classes:
p_x_y = X[(y==j).values][col].value_counts()
for i in p_x_y.index:
prior[(col, i, j)] = p_x_y[i]/class_count[j]
return classes, class_prior, prior
这段是 nb_fit 的训练核心:用频率估计两类概率。
在算什么
- 类先验 Py

- 特征条件概率 P x i=v∣y

算完后供后面 predict 使用。
逐行理解
classes = yy.columns\[0].unique()
取出所有类别,这里是 -1, 1。
class_count = yy.columns\[0].value_counts()
class_prior = class_count/len(y)
- class_count:每个类有多少条(如 -1 有 6 条,+1 有 9 条)
- class_prior:P y = 该类别数*/* 总样本数

例如 P -1=6/15=0.4
,P +1=9/15=0.6 
prior = dict()
for col in X.columns: # 遍历特征:x1, x2
for j in classes: # 遍历类别:-1, 1
p_x_y = X(y==j).valuescol.value_counts()
for i in p_x_y.index:
prior(col, i, j) = p_x_yi / class_countj
含义:对每个「特征 + 取值 + 类别」,算
P x col =i∣y=j = 该类中该取值出现次数该类总样本数

键写成 (特征名, 特征值, 类别),例如:
- ('x1', 1, -1): 0.5 → 在 y=-1
时,x1=1
的概率是 0.5 - ('x2', 'S', 1): 0.111... → 在 y=+1
时,x2=S
的概率约 1/9
小例子
假设只看 y=-1(共 6 条),其中 x2:
- S 出现 3 次 → P x2=S∣y=-1=3/6=0.5

- M 出现 2 次 → 2/6≈0.333

- L 出现 1 次 → 1/6≈0.167

这正是代码里 prior('x2','S',-1) 等值的来源。
返回值
| 返回 | 含义 |
|---|---|
| classes | 类别列表 |
| class_prior | Py ![]() |
| prior | 所有 P x i∣y ![]() |
一句话:数每个类有多少、每个特征值在各类里出现多少次,把频率当成概率存起来。
classes, class_prior, prior = nb_fit(X, y)
print(classes, class_prior, prior)
{('x1', 1, -1): 0.5, ('x1', 2, -1): 0.3333333333333333, ('x1', 3, -1): 0.16666666666666666, ('x1', 3, 1): 0.4444444444444444, ('x1', 2, 1): 0.3333333333333333, ('x1', 1, 1): 0.2222222222222222, ('x2', 'S', -1): 0.5, ('x2', 'M', -1): 0.3333333333333333, ('x2', 'L', -1): 0.16666666666666666, ('x2', 'L', 1): 0.4444444444444444, ('x2', 'M', 1): 0.4444444444444444, ('x2', 'S', 1): 0.1111111111111111}
X_test = {'x1': 2, 'x2': 'S'}
classes, class_prior, prior = nb_fit(X, y)
def predict(X_test):
res = []
for c in classes:
p_y = class_prior[c]
p_x_y = 1
for i in X_test.items():
p_x_y *= prior[tuple(list(i)+[c])]
res.append(p_y*p_x_y)
return classes[np.argmax(res)]
print('测试数据预测类别为:', predict(X_test))
测试数据预测类别为: -1
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=0)
gnb = GaussianNB()
y_pred = gnb.fit(X_train, y_train).predict(X_test)
print("Accuracy of GaussianNB in iris data test:",
accuracy_score(y_test, y_pred))
Accuracy of GaussianNB in iris data test: 0.9466666666666667
GaussianNB 不是另一种算法,而是朴素贝叶斯的一种具体实现。
联系
共同点都是朴素贝叶斯框架:
y = argmaxy P y i P x i ∣y

并假设:给定类别后,各特征相互独立。
前面手写的 nb_fit / predict,和后面的 GaussianNB,都在算这件事。
区别:差别在 P x i∣y
怎么建模
| 前面手写版 | GaussianNB | |
|---|---|---|
| 全称 | 离散/多项型朴素贝叶斯思路 | 高斯朴素贝叶斯 |
| 特征类型 | 离散(1/2/3,S/M/L) | 连续数值(鸢尾花花瓣长度等) |
P x i∣y ![]() |
数频次:计数类样本数![]() |
假设服从高斯分布 N μ y,i σ y,i2 ![]() |
| 适用场景 | 类别特征、文本词频等 | 连续特征分类 |
GaussianNB 对每个类别、每个特征估计均值和方差,再用正态密度算 P x i∣y
:
P x i ∣y = 1 2π σ y,i exp*(* - x i - μ y,i 2 2 σ y,i 2 )

一句话
- 朴素贝叶斯:一类算法(独立假设 + 贝叶斯决策)
- GaussianNB:其中"特征按高斯分布建模"的那一版
手写部分适合表格离散数据;GaussianNB 适合鸢尾花这类连续特征。sklearn 里还有 MultinomialNB、BernoulliNB,分别对应计数特征、0/1 特征,同属朴素贝叶斯家族。具体区别如下,
| 对比项 | GaussianNB | MultinomialNB | BernoulliNB | CategoricalNB |
|---|---|---|---|---|
| 中文名 | 高斯朴素贝叶斯 | 多项式朴素贝叶斯 | 伯努利朴素贝叶斯 | 类别型朴素贝叶斯 |
| 特征类型 | 连续实数 | 非负计数 / 频次 | 二值 0/1 | 离散类别(整数编码) |
P x i∣y 假设 |
高斯分布 N μ σ2 ![]() |
多项式分布 | 伯努利分布 | 类别分布(按类别取值计概率) |
| 典型输入 | 长度、面积、传感器读数 | 词频 TF、词袋计数 | 词是否出现(二值化) | 颜色=红/蓝、尺码=S/M/L |
| 典型场景 | 鸢尾花、连续特征分类 | 文本分类(TF/Count) | 短文本、稀疏二值特征 | 表格离散特征分类 |
| 特征取值示例 | 5.1, 3.5, 1.4 | 词A出现3次, 词B出现0次 | 词A出现=1, 词B出现=0 | x1=2, x2=0(类别编号) |
| 与本 notebook 关系 | 后半段鸢尾花用的就是它 | 思想接近词频版 NB | 更强调"出现/未出现" | 最接近前半段手写离散 NB |
| sklearn 常见用法 | GaussianNB() | MultinomialNB() | BernoulliNB() | CategoricalNB() |
怎么选(很短):
- 连续数值 → GaussianNB
- 词频/计数 → MultinomialNB
- 只关心有没有 → BernoulliNB
- 有限个离散类别 → CategoricalNB(你前面手写的 x1/x2 数据本质上属于这类)







假设