机器学习Bayesian

贝叶斯网络bayesian_network

算法思想:

贝叶斯网络(Bayesian Network):用有向图表达变量因果关系,再用条件概率做推断。

核心思想

现实里很多变量互相影响,但不必直接建一张巨大的联合概率表

P D I G LS 。贝叶斯网络做两件事:

  1. 用图结构压缩依赖关系:谁影响谁,一目了然
  2. 用局部条件概率表(CPD)拼出全局联合分布

对本例:

D(难度) ──┐

├──► G(成绩) ──► L(推荐信)

I(天赋) ──┤

└──► S(SAT)

联合分布可分解为:

P D I G L S =P DP IP G ∣IDP L∣GP 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 yP x∣y

**"朴素"**假设:特征在给定类别下相互独立,于是

P x∣y =P x 1 ∣yP 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 的训练核心:用频率估计两类概率。

在算什么

  1. 类先验 Py
  2. 特征条件概率 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 σ 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 数据本质上属于这类)
相关推荐
TaoMetrix7 小时前
TaoMetrix:AI 硬件创业公司如何做好原型制造
人工智能·制造
智圣新创017 小时前
面向多级组织协同场景 高校第二课堂一站式管理中枢落地全场景实操答疑
大数据·人工智能
cd_949217217 小时前
AI纹理和Substance Painter手绘纹理哪个更适合游戏资产制作?
人工智能·游戏·substance painter
sali-tec7 小时前
C# 基于OpenCv的视觉工作流-章106-差值追踪
图像处理·人工智能·opencv·算法·计算机视觉
Shockang7 小时前
用 AI 打造高品质 Web 应用
人工智能
l1258657 小时前
# LangGraph Memory机制深度解析:短期记忆与长期记忆的工程实践
前端·人工智能·python·langchain·bootstrap
手写码匠7 小时前
Dify 多 Agent 工具权限与安全沙箱实战:让智能体“有能力,但不越权“
人工智能·深度学习·算法·aigc
ZGIAI7 小时前
ZGI Skill Loop:给工具调用设边界
人工智能·架构
黎阳之光7 小时前
打破堆场感知黑盒:黎阳之光视频孪生,构建港口码头网格化透明管控新体系
大数据·人工智能·算法·安全·数字孪生
ZGIAI7 小时前
ZGI 工作区权限:用户为何看不到资源
人工智能·架构