ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

互信息与贝叶斯网络:小样本事故严重度分析全流程实践

互信息与贝叶斯网络:小样本事故严重度分析全流程实践 简介基于互信息贝叶斯网络的交通事故严重程度分析文档面向交通安全研究人员、数据分析师及省际客运行业管理者。资源以上海市2014—2019年省际客运事故数据为对象针对传统回归模型处理非线性问题受限、问卷数据主观性强等不足引入CACC有监督离散算法对有限且分布不均的样本进行有效划分并提出改进互信息方法结合交叉验证来排序变量关联度进而构造多个先验网络进行贝叶斯结构学习从人、车、路、环境四个维度综合剖析事故严重程度的影响因素。文档完整呈现建模流程、公式推导、算法执行步骤及最优模型比选思路有助于读者深入理解基于互信息的贝叶斯网络在交通安全分析中的实际应用为制定针对性行业安全策略提供科学的数据支撑。资源为单个docx文档大小344KB已有96人学习下载适合具备一定统计或机器学习基础、需要事故致因分析参考的研究者与从业者。1. 互信息贝叶斯网络省际客运事故严重程度分析怎么做才不踩坑省际客运的事故数据有个特点看着不少真洗干净能用的不多。这篇资源里用到的上海 2005—2019 年省际客运事故数据原始 790 条清洗后剩下 741 条。这么小的样本量还要从人、车、路、环境里找出哪些因素真正影响事故严重程度用传统 Logit 回归很容易陷入非线性拟合不良的泥潭问卷数据又主观。这篇论文的方法路线很直接先用 CACC 有监督离散算法把连续变量切成合理区间再用改进的互信息方法做特征相关性排序、构造先验网络最后用贝叶斯网络学习结构和参数得到一张可解释的事故致因图。如果你手头也有类似的小样本事故数据、安全评价数据或者想学怎么把互信息特征选择和贝叶斯网络组合成一条可复现的分析流水线这份资源值得仔细拆一遍。2. 数据清洗与变量离散为什么选 CACC 而不是等宽或 CAIM2.1 事故数据变量长什么样建模用到的有效数据是 741 条每条事故记录涉及人员、车辆、道路、环境、后果等多个维度。论文筛选出 14 个变量进入模型其中 12 个离散变量、2 个连续变量。连续变量包括年龄、死亡人数、重伤人数、轻伤人数、财产损失等离散变量则涵盖性别、号牌种类、车辆类型、事故地点、季节、天气、时间、行驶状态、事故类型等。关键在于贝叶斯网络要求变量是离散的。年龄、死亡人数这类连续量不能直接丢进网络里学习必须先做离散化。离散化分有监督和无监督两类等宽离散、Hierarchical 聚类离散属于无监督CAIM、CDD、CACC 属于有监督。无监督离散不考虑目标变量分出来的区间对后续分类没有针对性。CAIM 虽然是有监督但它在评分时用的是类-区间依赖程度的最大值容易忽略部分样本分布导致过拟合。CACC 的评分函数用的是所有类在所有区间上的整体依赖程度如式cacc sqrt(y / (y M))其中 y 由各类样本在每个区间内的占比相对于全局分布计算得到衡量的是变量离散后与目标类之间的整体关联强度对样本分布不平均的数据更友好。这就是论文选 CACC 的理由——样本量小且类别分布不均时CACC 能保留更多有效知识。2.2 用 Python 复现 CACC 离散的核心逻辑论文用 Matlab R2020a 实现 CACC但思路完全可以移植到 Python。算法流程是这样的给定 M 个样本、l 个待离散变量、S 个目标类先把每个变量的取值范围作为初始区间按升序排列计算相邻值的中点作为候选切点迭代尝试划分区间每次划分后计算 CACC 评分如果评分不再提升就输出当前最优区间划分。论文里 M741l7S3三个目标类分别是“死亡事故”“受伤事故”“财产损失事故”最大区间数限制为 5。import numpy as np import pandas as pd from itertools import combinations def cacc_score(data, target, intervals): 计算 CACC 评分 data: 待离散变量列 target: 目标类列 intervals: 当前区间边界列表 M len(data) n len(intervals) - 1 # 区间数 y 0.0 for i in range(n): left, right intervals[i], intervals[i 1] mask (data left) (data right if i n - 1 else data right) bin_total mask.sum() if bin_total 0: return -np.inf for c in np.unique(target): class_in_bin (target[mask] c).sum() if class_in_bin 0: M_plus_r bin_total Mi_plus (target c).sum() y (class_in_bin ** 2) / (Mi_plus * M_plus_r) y M * (y - 1) / np.log2(n) return np.sqrt(y / (y M)) def cacc_discretize(series, target, max_intervals5): 贪心搜索最优区间划分 values np.sort(series.values) candidates [(values[i] values[i 1]) / 2 for i in range(len(values) - 1)] best_intervals [np.min(values), np.max(values)] best_score -np.inf for k in range(1, max_intervals): for cut_points in combinations(candidates, k): intervals [np.min(values)] list(cut_points) [np.max(values)] score cacc_score(series.values, target.values, intervals) if score best_score: best_score score best_intervals intervals return best_intervals这段代码保留的是 CACC 的评分核心实际做工程时可以用动态规划替代暴力组合搜索否则数据量稍大运行会非常慢。cacc_score 里最重要的一行是y (class_in_bin ** 2) / (Mi_plus * M_plus_r)它同时考虑了类内样本在该区间的占比和该区间在全局的占比这就是 CACC 比 CAIM 更稳的原因。使用时有几个参数要留意max_intervals 控制区间数上限设太大会让每个区间样本太少条件概率估计不稳定设太小又损失信息论文试下来 5 是合适的你换数据集可以从 4~6 开始扫。2.3 CACC 离散结果长什么样论文里带星号的变量就是经过 CACC 离散的年龄、行驶状态、死亡人数、重伤人数、轻伤人数、财产损失、天气。以年龄为例被切成五段0~27、28~47、48~51、52~54、54。注意年龄段不是等宽的27 岁以下是一档48~51 和 52~54 各占一档说明这几个年龄段在事故严重程度分布上确实有差异CACC 找出了这些差异边界。死亡人数切成 0、1、2、3、3 五档轻伤人数切成 0~7、8~13、14~25、25 四档。天气分为晴、阴、雨、雪/大风/雾四类其中雪、大风、雾合并成一类样本量太少导致没法细分这个合并动作本身就是一种先验知识。拿到离散结果后就可以进入下一步构造先验网络了。3. 互信息与先验网络构造小样本下如何避免边定向翻车3.1 为什么不能直接用传统互信息贝叶斯网络结构学习最怕两件事搜索空间太大导致陷入局部最优以及小样本下网络结构过度拟合。常见缓解办法是给定一个先验网络缩小搜索范围。构造先验网络有三条路纯专家知识、因果推断框架、互信息方法。专家知识主观性强因果推断只支持二值变量DBe 方法需要至少 3000 条样本。这篇论文的数据只有 741 条多值变量居多直接套因果推断或 DBe 都不合适。传统互信息方法有两个痛点一是基于直方图或核密度估计的熵估计在小样本下偏差很大二是互信息算出的是变量间的关联强度但给不出边的方向。论文的解法是用基于 KNN 的改进互信息公式如式I(X,Y)ψ(k)−⟨ψ(mx1)ψ(my1)⟩ψ(M)其中 k 是近邻数mx 和 my 是水平和垂直方向落入 k 邻域的样本点数ψ 是 digamma 函数。这个公式比传统直方图估计在小样本上更稳代价是对 k 值敏感。k 太小系统误差大k 太大统计误差大所以论文引入交叉验证来挑 k。3.2 k 值选择与互信息矩阵计算论文把经过 CACC 离散的数据集按 70%/30% 拆成训练集和测试集计算训练样本到测试样本的欧氏距离建立距离降序矩阵取第 k 个距离作为邻域半径用 k 近邻分类器在测试集上跑分类准确率选出准确率最高的 k。最终结果 k21。这一步我建议你自己做一遍因为 k 的取值直接决定后续 MI 矩阵的数值分布不同数据集最优 k 差得很远。样本量更小可能在 10~15 之间样本更均衡可能到 30 以上。计算出 MI 矩阵后论文把每个变量与“事故类型”之间的互信息值单独拎出来排序形成变量节点序列。排序结果是重伤人数 10.66、死亡人数 10.65、轻伤人数 10.40、财产损失 9.94这四个是结果变量事故地点 10.78、号牌种类 10.64、性别 10.63、季节 10.51、时间 10.31、车辆类型 10.25、天气 9.97、行驶状态 9.67、年龄 9.65这九个是影响因素变量。这里有个容易被忽略的点排序时结果变量排在前面影响因素变量排在后面节点排列顺序会直接影响先验网络的初始拓扑方向不能乱调。3.3 阈值扫描构造先验网络有了互信息排序序列接下来是给边定向。论文的定向策略是从 9.6 到 10.7 以 0.1 为间隔设 12 个阈值对每个阈值把 MI 值大于阈值的影响因素变量节点向结果变量节点连有向边形成一个先验网络。同时设置“全连”“全不连”两个先验作为对照组一共 14 个先验网络。对这 14 个网络分别做贝叶斯结构学习然后用留一法LOO交叉验证测精度。最终在阈值 10.5 时网络精度最优。这意味着只保留 MI ≥ 10.5 的边也就是事故地点、号牌种类、性别、季节、时间、车辆类型与事故类型之间直接相连而天气、行驶状态、年龄的互信息低于阈值不与事故类型直接相连但它们仍可以通过中间节点间接影响结果。这个阈值选择逻辑本质上是特征选择用交叉验证来定阈值比拍脑袋定 0.5 或 0.6 靠谱得多。留一法在样本量不大时是最稳妥的验证方式因为每次只留一条样本模型几乎用到了全部数据评估方差最小。4. 贝叶斯网络建模与模型验证从先验网络到可解释的致因图4.1 GTT 算法与 EM 参数学习选好先验网络后就要做结构学习和参数学习了。论文用 GTTgreedy thick thinning算法分两阶段迭代先是加边阶段在给定先验网络基础上贪心添加能让评分提升的边直到评分不再上升再是减边阶段逐步移除冗余边直到评分达到最优。这种方式的好处是先验网络已经给出了一个大致的拓扑骨架GTT 只需要在局部做微调避免了从零开始搜索导致陷入局部最优的风险。参数学习用的是期望最大化算法即 EM 算法。EM 在贝叶斯网络里的作用是当部分节点存在缺失值时交替执行 E 步用当前参数估计缺失值和 M 步用补齐后的数据更新条件概率参数迭代直到收敛。论文用 GeNIe 3.0 软件完成可视化和 EM 学习最终得到各变量节点的条件概率分布表。这里要强调一个工作习惯结构学习和参数学习要分开保存结果。结构学习得到的是 DAG 拓扑参数学习得到的是每个节点的 CPT条件概率表后续做推理和敏感度分析时这两个文件缺一不可。4.2 模型验证ROC 曲线与命中率模型建完不能直接信论文用 ROC 曲线和 AUC 值做泛化能力验证。AUC 0.5 表示模型比随机猜测好越接近 1 越好。对比实验做得很完整在相同建模方法下把 CACC 离散结果与等宽离散、Hierarchical 聚类离散做比较在相同离散方法下把“互信息先验”与“纯数据先验”“专家知识先验”做比较。最终结果是本文模型的 AUC 均值 0.644588优于所有对照组。这个数字看起来不高但交通事故事故严重程度预测本身就是低区分度问题类别不均衡、噪声大0.64 的 AUC 已经具备实用价值。交叉验证的命中率更直接103 条“死亡事故”命中 102 条512 条“受伤事故”命中 497 条125 条“财产损失事故”命中 121 条整体命中率 97.3%。这个命中率建立在留一法验证基础上说明学习到的条件概率表与真实数据分布高度吻合。但要注意命中率高不等于预测能力强因为在类别不均衡的数据集上模型可以靠预测多数类获得高准确率。所以论文才用 ROC 和 AUC 作为补充指标两者一起看才不会被单一指标带偏。做完验证这个贝叶斯网络就可以用来做推理了。GeNIe 里可以设置某个节点为证据比如把天气状态设为“雪、大风、雾”然后观察“事故类型”节点的后验概率变化这个推理能力是贝叶斯网络比回归模型实用的核心原因——它不止给出系数还能回答“如果某因素变成某种状态结果分布怎么变”。4.3 模型推理的关键应用场景从条件概率表里提取信息要讲究方法。论文的做法是以“死亡事故”“受伤事故”“财产损失事故”三个结果变量为目标项做敏感度分析再对每个影响因素类的后验概率变化做归一化权重计算。最终结果显示“女性驾驶员”“中型客车”“雪大风雾天气”对事故严重程度影响最大。敏感度分析揭示的是节点微小变化对结果的扰动程度权重分析揭示的是每个状态对结果的贡献度。这两个指标不是一回事前者看的是整体敏感性后者看的是具体类别的作用用的时候要区分。5. 避坑指南从数据清洗到建模推理的五个常见翻车点5.1 数据清洗丢失样本过多离散区间分布崩了现象原始 790 条事故数据清洗后只剩 741 条损失接近 6%。如果自己复现时清洗逻辑过于激进比如把含有任一缺失值的记录整条删除样本量可能跌破 700离散时某些区间只剩个位数的样本。原因省际客运事故记录里存在字段缺失和录入错误原始记录质量参差不齐。清洗时没有区分“关键变量缺失”和“非关键变量缺失”一刀切全删。解决我只删除建模必需变量存在缺失的记录比如事故类型、车辆类型、天气这些核心字段对非关键变量用众数或中位数填充后再进入离散流程。清洗后一定要重新统计每个变量的取值分布确保每个离散区间有足够样本一般低于 20 条样本的区间就该考虑合并。5.2 CACC 离散区间数设太大条件概率表稀疏现象离散结果看起来很“精细”年龄被切成七八档但贝叶斯网络学习出的条件概率表里大量单元格是 0 或者极小值推理结果明显不合理。原因max_intervals 设得过大而样本量只有 741 条细分区间后每个区间的样本量不足以支撑稳定的概率估计。这不是 CACC 的问题是超参没调好。解决论文里最大区间数 5 是有道理的。我做类似数据时习惯先扫 3~6 之间的区间数每个候选值都跑一遍后续建模流程看验证集 AUC 的变化选 AUC 最高的配置。别只看离散结果的“精细程度”一切以最终模型性能为准。5.3 互信息 k 值不优化直接默认用 5 或 10现象互信息矩阵算出来后阈值扫描得到的最优网络和论文结果对不上甚至出现边的方向与业务常识完全相反的情况比如“死亡人数导致性别”这种荒唐结构。原因k 值是 KNN 互信息估计的核心参数k 不同MI 值的大小排序就不同直接影响阈值扫描结果和边定向结果。用默认参数不等于用最优参数。解决老老实实跑一遍交叉验证选 k。把样本拆成 70% 训练集和 30% 测试集对每个候选 k 值计算分类准确率画一条 k 值-准确率曲线选曲线峰值对应的 k。这个步骤耗时不大但对结果的稳定性提升非常明显。论文在 741 条样本上选出的最优 k 是 21这个值离默认值很远说明这一步绝对不能省。5.4 阈值扫描步长太粗错过最优先验网络现象阈值从 9.6~10.7 按 0.1 步长扫了 12 个值最优网络在 10.5但如果把步长改成 0.05最优值可能落在 10.35 或 10.45得到更好的 AUC。原因步长太大时真实的峰值可能落在两个扫描点之间。步长太小呢计算量又成倍增加每个阈值都要做一遍结构学习加留一法验证耗不起。解决我一般分两轮扫第一轮用粗步长比如 0.2 确定最优值的大致范围第二轮在该范围内用 0.02~0.05 的细步长加密。第一轮可能跑十几个模型第二轮只需跑几个总耗时可控精度显著提升。同时对照“全连”和“全不连”两个基线网络它们能告诉你先验网络相对随机起点有没有实质增益。5.5 只盯准确率不看类别均衡模型被多数类带偏现象模型总命中率达到 97%但仔细看被正确命中的大部分是“受伤事故”这类样本量大的类别“死亡事故”命中率不足一半。原因“受伤事故”512 条占总量的 69%如果模型把所有测试样本都判为“受伤事故”整体命中率也能接近 70%。论文之所以敢说模型有效是因为用的是留一法交叉验证并汇报每个类别的命中率而不是只看总量。解决评估时必须分别统计每个结果类别的命中率。死亡事故、受伤事故、财产损失事故三类样本量差异极大只有分类别统计才能看出模型对小类别的泛化能力。必要时可以做简单的重采样平衡但样本量只有 741 条时我不建议过采样容易过拟合优先用类别权重或阈值移动来调节。6. 复现这篇模型的完整流程从原始数据到事故致因图如果你想在自己的数据集上复现这套方法我把它拆成六个步骤每一步对应一套明确产出物。第 1 步数据清洗。把原始事故数据导出后先按关键字段完整性过滤再统一变量取值口径。时间字段要统一为 24 小时制并划分四个时段季节按气象学标准划分而不是按农历天气里的“雾”“大风”等低频类别要合并归类。连续变量先保留原始值等离散化处理。第 2 步CACC 有监督离散。以“事故类型”为监督变量对年龄、行驶状态、死亡人数、重伤人数、轻伤人数、财产损失等连续或高基数变量做离散。最大区间数从 4 开始试记录每种区间数下训练集的 CACC 评分选评分最高且区间样本量均匀的配置。产出是每个变量的区间划分表这份表要保存好后续推理阶段需要对新的数据做同样映射。第 3 步互信息计算与 k 值选择。对离散后的全量数据做交叉验证选最优 k再用选定的 k 计算所有变量对的互信息值。产出是 MI 矩阵和一个变量-结果变量 MI 值排序表。这一步是整条流水线里最容易被跳过的但也是决定模型上限的关键因为 MI 矩阵直接决定先验网络的边集合。第 4 步阈值扫描与先验网络选择。把 MI 值从最小值到最大值按 0.1 步长生成一组合适的阈值对每个阈值构造先验网络。同时构造全连、全不连两个基线。每个先验网络都输入贝叶斯网络结构学习算法执行 GTT再用留一法评估精度。选 AUC 或命中率最高的阈值作为最终先验网络。产出是一个最优先验网络 DAG 文件。第 5 步参数学习与模型验证。用 EM 算法在最优网络结构上学习条件概率表。验证时同时看 AUC 和分类别命中率不能只看总命中率。如果 AUC 低于 0.6回头检查离散区间和 k 值大概率是这两步的某个参数没调好。产出是标准的贝叶斯网络模型文件。第 6 步敏感度分析与后验推理。在 GeNIe 或 Python 的 pgmpy 里加载模型对结果变量做敏感度分析找到影响最大的因素节点。再逐个设置证据节点为不同状态观察目标节点后验概率变化。这个阶段才能真正回答“女性驾驶员导致死亡事故的概率变化是多少”“雪天对受伤人数的影响有多大”这类业务问题。# 用 pgmpy 复现贝叶斯网络推理的骨架代码 from pgmpy.models import BayesianNetwork from pgmpy.estimators import BayesianEstimator, HillClimbSearch from pgmpy.inference import VariableElimination # 根据论文的互信息矩阵排序手动指定网络结构 edges [ (事故地点, 事故类型), (号牌种类, 事故类型), (性别, 事故类型), (季节, 事故类型), (时间, 事故类型), (车辆类型, 事故类型), (事故类型, 重伤人数), (事故类型, 死亡人数), (事故类型, 轻伤人数), (事故类型, 财产损失), ] model BayesianNetwork(edges) # 参数学习用贝叶斯估计做平滑避免小样本下出现零概率 model.fit(data, estimatorBayesianEstimator, prior_typeK2) # 推理设置天气为雪/大风/雾观察死亡事故概率变化 infer VariableElimination(model) query_result infer.query(variables[事故类型], evidence{天气: 雪大风雾}) print(query_result)这段代码里edges 列表来自论文阈值 10.5 时选出的最优先验网络。变量名和状态名要根据你实际离散后的数据调整。prior_type 用 K2 是贝叶斯估计里比较通用的平滑先验能避免零概率导致的推理崩溃。若你不需要证据推理只想验证结构可以把 HillClimbSearch 加进来做一次纯数据驱动的结构对比看看与互信息先验给出的边差异在哪。我自己的习惯是先跑一遍纯数据驱动的结构学习作为参照再叠加互信息先验看精度变化。如果互信息先验没有带来提升就需要回到第 2 步和第 3 步检查离散区间划分是否合理、k 值选得对不对。回归模型给的是系数表贝叶斯网络给的是可交互的因果图这个差别在实际汇报时很有用——管理层更愿意看“天气变差会导致死亡概率提高多少”而不是一个带负号的回归系数。从那以后我每次做类似的小样本安全数据分析都强制走一遍 CACC 离散→互信息排序→阈值扫描→结构学习→分类别验证的完整链条不再跳过任何一环。希望这套流程对你复现也有帮助。本文还有配套的精品资源点击获取
返回列表