WARNING
🧪 Beta公测版本提示:教程主体已完成,正在优化细节,欢迎大家提Issue反馈问题或建议。
ml02 贝叶斯决策理论 — demo.py 代码详解
运行方式
cd docs/ml/classic/bayesian-decision/code
python demo.py代码逐段详解
第1步:GaussianBayesClassifier 类 — 三种协方差假设
class GaussianBayesClassifier:
def __init__(self, cov_type='full'):
self.cov_type = cov_type # 'isotropic', 'shared', 'full'这个分类器假设各类别的数据服从多元高斯分布:
三种 cov_type 对应不同的协方差假设:
isotropic:(各类别共享,各向同性)。这是最简单的假设,只需要估计 1 个参数( )+ 各类别的均值。决策边界是各类别均值连线的垂直平分线——等价于最近均值分类器。 shared:(各类别共享同一个协方差矩阵)。需要估计 个协方差参数。决策边界是线性超平面——等价于 LDA。 full:每个类别有自己独立的。需要估计 个协方差参数。决策边界是二次曲面——等价于 QDA。
参数越少(isotropic)→ 偏差越高,方差越低。参数越多(full)→ 偏差越低,方差越高。这正是经典的 Bias-Variance 权衡。
第2步:fit() — 参数估计
def fit(self, X, y):
# priors: P(omega_j) = n_j / n (MLE)
self.priors_[idx] = len(X_c) / len(X)
# means: mu_j = mean of class j (MLE)
self.means_[idx] = np.mean(X_c, axis=0)这里使用了最大似然估计(Maximum Likelihood Estimation, MLE):
- 先验概率的 MLE 就是各类别的频率:
- 均值的 MLE 就是样本均值:
- 协方差矩阵的 MLE 取决于
cov_type的选择
对于 full 协方差,代码中加了微小的正则化:
cov_c = np.cov(X_c.T, bias=False)
cov_c += 1e-6 * np.eye(n_features) # 防止奇异这个
第3步:_log_likelihood() — 对数似然计算
def _log_likelihood(self, X):
for idx in range(n_classes):
diff = X - mu
if self.cov_type == 'isotropic':
mahal = np.sum(diff ** 2, axis=1) / sigma2
else:
mahal = np.sum((diff @ cov_inv) * diff, axis=1)
log_gauss = -0.5 * (d * np.log(2 * np.pi) + log_det + mahal)对数高斯密度的公式:
其中第三项
向量化技巧:np.sum((diff @ cov_inv) * diff, axis=1) 批量计算了
diff @ cov_inv得到矩阵,每一行是 - 再逐元素乘以
diff,得到 sum(axis=1)得到每行的标量结果
第4步:predict_proba() — 后验概率与 Log-Sum-Exp
def predict_proba(self, X):
log_joint = log_lik + log_prior
log_evidence = logsumexp(log_joint, axis=1, keepdims=True)
log_posteriors = log_joint - log_evidence
return np.exp(log_posteriors)后验概率的计算需要先用贝叶斯公式归一化:
如果直接计算,分子和分母都涉及 exp(log_joint),当 log_joint 非常负时,exp 会下溢到 0。解决方法是在对数空间中做归一化:
log_joint = log_lik + log_prior:在对数空间中计算分子log_evidence = logsumexp(log_joint):( scipy.special.logsumexp内部做了数值稳定性处理——先减最大值再 exp)log_posteriors = log_joint - log_evidence:(对数中的除法 = 减法)
最后才 np.exp() 得到实际的后验概率值。
第5步:最小风险决策
def minimum_risk_predict(posteriors, loss_matrix):
for i in range(n_classes):
for j in range(n_classes):
risks[:, i] += loss_matrix[i, j] * posteriors[:, j]
return np.argmin(risks, axis=1)条件风险的计算公式为:
loss_matrix[i, j] 表示当真实类别为
演示中使用的不对称损失矩阵:
loss_asymmetric = np.array([[0, 5], # 将 class 1 误判为 class 0: 代价 5
[1, 0]]) # 将 class 0 误判为 class 1: 代价 1当 FN(漏诊)代价远大于 FP(误报)代价时,分类器会更倾向于将样本判为"有病的"那个类别。
第6步:ROC 曲线绘制
fpr, tpr, thresholds = roc_curve(y_test, y_score)
roc_auc = auc(fpr, tpr)sklearn.metrics.roc_curve 通过变化决策阈值,返回不同 (FPR, TPR) 点。原理:
- 按模型输出的后验概率从高到低排序
- 逐个将阈值设为每个样本的得分,计算此时的 FPR 和 TPR
- 连接所有点得到 ROC 曲线
AUC 使用梯形积分计算:sklearn.metrics.auc(fpr, tpr)。AUC 的概率解释:随机抽取一个正样本和一个负样本,正样本的得分高于负样本的概率。
关键概念速查表
| 概念 | 数学形式 | 代码位置 | 关键说明 |
|---|---|---|---|
| 贝叶斯定理 | predict_proba() | 先验 + 似然 → 后验 | |
| 高斯密度 | _log_likelihood() | 假设各类别为正态分布 | |
| 马氏距离 | _log_likelihood() 中 mahal | 协方差校正的距离 | |
| 各向同性协方差 | cov_type='isotropic' | 1 参数,决策面为直线 | |
| 共享协方差 | cov_type='shared' | 类似 LDA,线性决策面 | |
| 独立协方差 | cov_type='full' | 类似 QDA,二次决策面 | |
| Log-Sum-Exp | predict_proba() | 数值稳定归一化 | |
| 条件风险 | minimum_risk_predict() | 不对称损失 | |
| ROC 曲线 | FPR vs TPR(变阈值) | plot_roc_auc_eer() | 分类器排序能力 |
| AUC | auc(fpr, tpr) | 正样本得分 > 负样本得分的概率 | |
| EER | FPR = FNR 时的错误率 | 代码中 eer_idx | 安全与便利平衡点 |
源码位置
clone 后打开(相对仓库根目录):
docs/ml/classic/bayesian-decision/code/demo.py