尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

贝叶斯神经网络实战解析:BBB与MC Dropout实现不确定性建模

贝叶斯神经网络实战解析:BBB与MC Dropout实现不确定性建模 简介面向希望系统掌握贝叶斯神经网络理论与实践的机器学习开发者尤其适合具备一定深度学习基础、需要量化模型不确定性的读者。压缩包内共十二个文件包含六个Python脚本、四个Jupyter Notebook交互式教程以及说明文档和示例数据总大小约一百六十四KB涵盖贝叶斯后向传播BBB回归与分类、MC dropout回归与分类等典型实现并配有README和中文说明便于按模块循序渐进学习。教程从贝叶斯线性回归延伸到BBB变分推断与MC dropout逐步展示如何将概率建模融入神经网络每个Notebook都配有清晰注释和可视化输出可直接在PyTorch或TensorFlow环境下运行。已有八十七人学习使用通过动手实践可直观理解变分推断、蒙特卡洛采样在神经网络中的应用并可将代码迁移至小样本学习、鲁棒性分析或不确定性预测等真实场景。1. 拆开贝叶斯神经网络教程 zip先看到的是不确定性的价值一个分类器在测试集上跑到 97% 准确率面对它没见过的数据时仍然会给出一个接近 1 的 softmax 置信度。这个置信度并不代表它真的“懂”因为传统神经网络用最大似然训练权重是固定的点估计预测结果天然缺少对自身不确定性的表达。贝叶斯神经网络BNN把权重建模成概率分布用变分推断或 MC Dropout 这类后验近似方法把不确定性变成可直接输出的预测方差。这套《BayesNuronalNetworksTutorial》代码包正是围绕这一点展开BBBBayes by Backprop和 MC Dropout 各配一个回归和一个分类案例代码统一落在 Python PyTorch 上。解压 zip 后会看到 BayesNuronalNetworksTutorial-main 目录里面有 .py 和 .ipynb 两套可执行文件想直接跑实验的人用 .py 就够了想一边写笔记一边改参数则打开 Notebook。适合刚接触概率深度学习的人也适合已经用惯确定性模型、想给线上预测加上可靠性边界的工程师。2. 拆解代码包BBB 与 MC Dropout 两条实现路线2.1 压缩包里的文件到底想讲什么打开 zip 后目录排列很清楚。文件名带bbb的是 Bayes by Backprop1_bbb-regression.py、2_bbb-classification.py及同名.ipynb带mcdropout的是 MC Dropout 方案3_mcdropout-regreesion.py、4_mcdropout-classification.py及同名.ipynb。这里有个小坑3_mcdropout-regreesion.py把 regression 拼成了 regreesion搜索文件时别按正确拼写去找。bbb.py是 BBB 的核心层utils.py负责数据生成和画图README.md是实验说明。文件实现方法任务类型bbb.pyBayes by Backprop 核心模块定义贝叶斯线性层utils.py数据集生成、评价辅助回归/分类通用1_bbb-regression.pyBBB回归2_bbb-classification.pyBBB分类3_mcdropout-regreesion.pyMC Dropout回归4_mcdropout-classification.pyMC Dropout分类另外压缩包内附了一个“如果解压失败请用ara软件解压.txt”这通常出现在中文 zip 包场景某些解压器对中文文件名编码处理不当换用 ara 这类对编码兼容更好的工具即可与代码逻辑无关。2.2 bbb.py 中的重参数化采样BBB 的核心思路是把权重 W 当成随机变量先设定先验 p(W)教程里常见的是N(0, 0.1)再用变分分布 q(W|theta) 去逼近真实后验。q 通常建模为对角高斯theta 就是均值 mu 和标准差 sigma。如果直接从 q 里采样 W梯度无法反传所以bbb.py必须使用重参数化先采eps ~ N(0,1)再算W mu sigma * eps。这一行变换把随机性转移到了 eps 上mu 和 sigma 就有了可导路径。import torch import torch.nn as nn import torch.nn.functional as F class BayesLinear(nn.Module): def __init__(self, in_features, out_features, prior_std0.1): super().__init__() self.in_features in_features self.out_features out_features self.prior_std prior_std # mu 是后验均值rho 经过 softplus 后得到标准差 self.mu nn.Parameter(torch.randn(out_features, in_features) * 0.1) self.rho nn.Parameter(torch.full((out_features, in_features), -3.0)) self.bias_mu nn.Parameter(torch.zeros(out_features)) self.bias_rho nn.Parameter(torch.full((out_features,), -3.0)) def reparameterize(self, mu, rho): sigma torch.log1p(torch.exp(rho)) # softplus保证 sigma 0 eps torch.randn_like(mu) return mu sigma * eps def forward(self, x): weight self.reparameterize(self.mu, self.rho) bias self.reparameterize(self.bias_mu, self.bias_rho) return F.linear(x, weight, bias)rho 初始化为-3.0而不是 0原因在于softplus(-3) ≈ 0.049让模型在训练早期从一个较小的不确定度开始避免随机权重噪声过大导致前向输出完全失真。如果你复现时发现预测曲线忽大忽小优先检查是不是把 rho 直接当标准差用了而没有过softplus。记住网络里真正参与矩阵乘法的weight是采样后的值每次 forward 都会重新采一份权重这就是 BBB 与普通线性层的本质区别。2.3 MC Dropout 近似贝叶斯平均MC Dropout 的工程实现比 BBB 更轻量甚至不需要改网络结构。常规 Dropout 在测试阶段会被关闭MC Dropout 反其道而行在预测阶段保持 Dropout 打开用多次随机前向传播的结果做平均和方差估计。从贝叶斯角度看这等价于对权重后验做了一次蒙特卡洛采样。import numpy as np import torch def mc_dropout_predict(model, x, n_samples100): model.train() # 保持 dropout 激活这是关键 outputs [] with torch.no_grad(): for _ in range(n_samples): outputs.append(model(x).cpu().numpy()) outputs np.stack(outputs) return outputs.mean(axis0), outputs.std(axis0)在教程的3_mcdropout-regreesion.py里模型通常是两层全连接中间夹一个Dropout(0.2)预测阶段反复调用这个函数就能得到每个输入点的均值曲线和标准差带。调用model.train()看起来反直觉但目的就是让 Dropout 继续生效model.eval()会立刻退化成单次确定性输出。需要额外留意的是如果模型里同时有 BatchNorm直接设成 train 会影响 BN 的统计量这种情况下需要单独关闭 BN 的更新或者把 Dropout 层拆出来单独控制。这两条路线分别代表了 BNN 的两种主流近似。BBB 通过显式的变分后验分布建模权重训练成本高但表达能力强MC Dropout 几乎零成本直接从确定性网络改造而来适合快速给现有模型加不确定性。教程代码把两者放在一起本质上是让学习者亲手对比它们的边界。3. 复现 1_bbb_regression.py先验、变分后验与 ELBO 损失3.1 从 utils.py 生成带异方差噪声的回归数据回归任务的样例通常不会用干净的正弦曲线否则不确定性没有发挥空间。用np.linspace(-1, 1, 80)生成自变量再叠加一个与 x 相关的噪声项让中段波动大于两端这样的数据跑完后可以直观看到方差带在不同区域的宽度差异。import numpy as np np.random.seed(42) x np.linspace(-1, 1, 80).reshape(-1, 1) y x * np.sin(4 * x) 0.15 * np.random.randn(*x.shape)这段数据把异方差性注入目标值。如果模型用普通 MSE 训练最终只会得到一条拟合均值曲线而无法表达“中间这段我不确定”的信息。BNN 输出的是后验预测分布均值负责拟合方差负责表达置信区间所以这里的噪声幅度直接决定了最终可视化里不确定性带的下限。3.2 用 ELBO 把负对数似然与 KL 散度拼进损失BBB 的训练目标不是最小化 MSE而是最大化证据下界ELBO。写成最小化形式就是L E_q[ -log p(y|w,x) ] KL(q(w) || p(w))其中第一项是数据拟合项第二项是让变分后验不要离先验太远。对回归问题-log p(y|w,x)等价于高斯分布下的负对数似然实现时直接用 MSE 代替。KL 散度在两个高斯分布之间有解析式前提是知道后验 q 的标准差 sigma也就是softplus(rho)。基于上一章的BayesLinear先给每个层加一个计算 KL 的方法再组装出完整的回归网络。def kl_loss(self, prior_stdNone): if prior_std is None: prior_std self.prior_std q_sigma torch.log1p(torch.exp(self.rho)) prior_sigma torch.full_like(q_sigma, prior_std) var_ratio q_sigma ** 2 / prior_sigma ** 2 t1 2 * torch.log(prior_sigma / q_sigma) t2 self.mu ** 2 / prior_std ** 2 t3 var_ratio - 1 return 0.5 * (t1 t2 t3).sum()这里的 KL 计算直接作用在 mu 和 rho 上不需要额外采样。q_sigma每次 forward 时都从当前rho解算出来保证 KL 与重参数化采样使用的是同一组变分参数。注意t1里对多个维度做求和而t2和t3也是按元素计算后求和这样得到的是整层的标量 KL 值可以直接累加进总损失。接下来组装回归网络class BayesianRegressor(nn.Module): def __init__(self): super().__init__() self.fc1 BayesLinear(1, 64, prior_std0.1) self.fc2 BayesLinear(64, 64, prior_std0.1) self.out BayesLinear(64, 1, prior_std0.1) def forward(self, x): x torch.relu(self.fc1(x)) x torch.relu(self.fc2(x)) return self.out(x)每一层都是贝叶斯线性层激活函数放在采样后的输出上。因为每次 forward 都会重新采样权重所以同一个输入在不同迭代里得到的输出本身就带有随机性这正是模型拟合后验分布的表现。3.3 训练循环中的 KLD 缩放与超参数训练时需要在每个 batch 里累积所有贝叶斯层的 KL然后除以样本数。原因是 KL 是整批数据的近似先验惩罚项而 NLL 是逐样本误差如果不缩放KL 项很容易盖过数据拟合导致训练只优化先验而忽略样本信息。model BayesianRegressor() optimizer torch.optim.Adam(model.parameters(), lr0.01) n_epochs 300 for epoch in range(n_epochs): optimizer.zero_grad() y_pred model(x_tensor) nll torch.mean((y_pred - y_tensor) ** 2) kld 0.0 for layer in model.modules(): if isinstance(layer, BayesLinear): kld kld layer.kl_loss(prior_std0.1) loss nll kld / x_tensor.shape[0] loss.backward() optimizer.step()这里把kld除以训练样本总数是常见做法之一。另一种做法是除以 batch size那样会让 KL 在 Batch 较小时偏大训练更不稳定。教程代码通常会打印每个 epoch 的 loss如果在 loss 下降过程中出现大幅抖动优先怀疑 KL 权重偏大把prior_std调大或降低学习率即可。超参数教程常用值说明prior_std0.1先验标准差控制后验向零收缩的程度rho初始值-3.0约等于初始标准差 0.05学习率0.001 到 0.01Adam 下建议从 0.01 开始KLD 缩放除以样本总数让 KL 与 NLL 处于同一数量级采样次数1训练时每 batch 采一次权重即可训练结束后保留模型用固定输入跑 50 次 forward收集预测均值与标准差就能画出带置信带的回归曲线。如果标准差带在数据稀疏区域明显变宽说明模型成功学到了位置相关的不确定性这是确定性网络做不到的。4. 分类任务中的不确定性两个分类案例的对照4.1 分类 BNN 的似然函数与置信度校准分类任务里的负对数似然通常是交叉熵模型输出 logits经 softmax 后得到类别概率。传统分类器的问题在于softmax 概率并不是校准后的不确定性即使不正确也会给出高置信度。BNN 的分类输出则是对多个概率分布的期望平均可以在置信度上给出额外方差从而告诉使用者结果是否可靠。教程里2_bbb_classification.py和4_mcdropout-classification.py都是做同样的分类任务但采样方式不同。前者是 BBB后者是 MC Dropout对比两个案例能清楚看到实现难度的差异。4.2 从 bbb 分类代码里看后验采样BBB 分类模型的结构与回归几乎一样只是输出维度从 1 变为类别数。在预测阶段需要多次经过模型得到多个 logits然后对 softmax 概率求平均。直接对 logits 求平均再 softmax 是不等价的因为 softmax 不是线性变换。def bbb_classify(model, x, n_samples20): model.eval() # 关闭 dropout只使用权重分布 logits_list [] with torch.no_grad(): for _ in range(n_samples): logits_list.append(model(x)) logits torch.stack(logits_list) probs torch.softmax(logits, dim-1).mean(dim0) return probsn_samples 取 20 通常就够用再增大收益有限反而增加推理耗时。这里的model.eval()不会关闭权重采样因为采样发生在BayesLinear.forward里和 Dropout 无关。如果你在这个阶段发现每次输出几乎一致可以检查是否把 rho 训练成了较小的值导致后验分布退化到近似点估计。4.3 MC Dropout 分类如何完成多次前向传播MC Dropout 分类代码更简洁甚至不需要改动网络结构。只需要在预测阶段让 dropout 保持打开收集 softmax 概率然后计算均值与标准差。def predict_mc_classification(model, x, n_samples50): model.train() probs [] with torch.no_grad(): for _ in range(n_samples): logits model(x) p torch.softmax(logits, dim-1) probs.append(p.cpu().numpy()) probs np.stack(probs) return probs.mean(axis0), probs.std(axis0)PyTorch 的Dropout在训练模式下会自动对激活值做尺度缩放因此不需要再额外乘1/(1-p)。与 BBB 分类相比MC Dropout 预测时每次都共享同一套权重只是随机遮掉部分神经元得到的方差主要来自网络结构的随机性而不是权重后验的随机性。这个差异决定了 MC Dropout 的不确定性刻画能力比 BBB 粗一些但胜在实现成本极低。4.4 两条路线的适用边界对比维度BBBMC Dropout随机性来源权重采样神经元随机失活训练改动需替换贝叶斯层自定义 ELBO原网络基本不动预测开销多次 forward采样权重复用低多次 forward权重固定后验表达能力对角高斯可学习均值与方差隐式近似方差与 dropout 率绑定调参敏感点rho 初始化、KL 权重dropout 率、采样次数推荐场景对不确定性精度要求高的离线分析已上线模型快速加不确定性做这个对照是想强调教程把两个算法放进同一套任务并不是让读者判断谁更好而是让读者意识到同样的预测方差背后对应了完全不同的概率解释。BBB 输出的方差可以对权重视角做合理近似MC Dropout 输出的是随机前向的离散程度。两者都能用但写论文或做模型风险分析时必须区分清楚。5. 验证不确定性的校准度才是 BNN 的临门一脚5.1 用可靠性图检查模型是否过度自信拿到 BNN 的预测概率不能直接认为它天然可信。分类模型输出 0.8 的概率意味着 100 个这样的样本里大约 80 个真阳性这才是校准。用测试集算 reliability diagram 时把预测概率分桶每个桶内比较平均置信度与实际频率。def reliability_curve(y_true, y_prob, n_bins10): bins np.linspace(0, 1, n_bins 1) conf [] acc [] for i in range(n_bins): mask (y_prob bins[i]) (y_prob bins[i 1]) if mask.sum() 0: conf.append(y_prob[mask].mean()) acc.append((y_true[mask] 1).mean()) return conf, acc画图时如果大部分点落在对角线下方说明模型过度自信落在上方则是过度保守。BNN 训练完成后可以先跑这个函数确认校准情况而不是只看 AUC 或 accuracy。对于回归任务则检查每个置信区间的覆盖率例如 95% 标准差带是否真的包含了约 95% 的测试样本。5.2 温度缩放与方差阈值调整当可靠性图显示过度自信时一个不需要重新训练模型的修复方式是温度缩放。先学一个温度参数 T把 logits 除以 T 后再过 softmax使得置信度分布整体变平。BNN 中同样适用尤其适合 MC Dropout 这种训练时没有显式不确定性约束的方法。def temperature_scale(logits, temperature): return torch.softmax(logits / temperature, dim-1)温度 T 大于 1 时概率分布更平滑T 小于 1 时更尖锐。用一个验证集搜索最小化 NLL 的 T 值即可。完成校准后把多次预测的标准差当作风险信号在线上系统里设置阈值例如标准差超过 0.3 的样本转发给人工复审这比单纯看概率阈值更能捕捉分布外输入。实际部署时我通常把 BBB 和 MC Dropout 的预测方差都记录进日志离线定期观察方差分布一旦发现方差带上移就应该考虑数据分布是否已经偏移。本文还有配套的精品资源点击获取
返回列表