资讯详情

资讯详情

互信息与贝叶斯网络在交通事故严重程度特征选择与结构学习中的应用

简介基于互信息贝叶斯网络的交通事故严重程度分析.docx 是一份面向交通安全研究领域的技术分析文档聚焦省际客运行业事故严重程度评估问题。资源以2014—2019年上海省际客运事故数据为对象针对传统回归模型处理非线性不足、问卷调查主观性强等局限引入CACC离散算法对样本进行最优区间划分并提出改进互信息方法构造先验网络再结合贝叶斯网络结构学习与参数学习系统揭示人、车、路、环境等因素对事故严重程度的综合影响。文档论证严谨包含完整的建模方法、公式推导与步骤说明适合交通安全科研人员、行业管理部门及相关专业学生参考可帮助读者掌握基于贝叶斯网络与互信息理论的事故数据分析路径。资源包共含1个docx文件大小约344KB内容精炼且结构清晰便于研读与复用。目前已有96人学习下载对于关注省际客运安全和小样本事故数据挖掘的读者具有较高参考价值。1. 互信息贝叶斯网络凭什么比Logistic回归更适合事故严重程度分析做事故严重程度分析多数团队起步都是 Logistic 回归拿到交警台账后先跑一轮相关分析挑几个显著的变量去拟合。用互信息贝叶斯网络做交通事故严重程度分析主要解决两个问题一是特征与结果之间往往不是线性关系传统的 Pearson 相关容易漏掉“酒驾到一定程度后严重程度陡增”这类分段效应而互信息不假设单调关系能把隐藏关联捞出来二是贝叶斯网络把变量关系画成一张有向图训完之后还能回答“能见度差同时没系安全带的死亡概率是多少”这样的反向查询而不是只给一个黑盒预测。我会完整走一遍互信息特征选择、网络结构学习、参数估计和避坑清单适合手里有事故台账、想在预测之外做解释性分析的从业者。2. 先用互信息特征选择筛掉一半特征计算、阈值与可视化2.1 从熵到互信息为什么它比相关系数更能找出非线性关联互信息建立在熵的概念上。某个变量的熵 H(X) 表示它的不确定性两个变量同时出现的联合熵 H(X, Y) 表示整体不确定性。互信息定义为 I(X;Y) H(X) H(Y) − H(X, Y)它回答一个很实际的问题知道了 X 之后Y 的不确定性减少多少。两个变量完全独立时MI 为零只要有任意形式的依赖MI 就大于零。这个“任意形式”是关键。事故数据里最常见的非线性是阈值效应饮酒量超过一定界限严重程度快速上升年龄与事故风险呈倒 U 形。Pearson 相关系数把这些关系平均掉算出来往往是零点几的弱相关而互信息不会漏。另一个特性是互信息对变量的单调变换不敏感。车速和严重程度之间如果是对数关系普通相关分析要先取对数才能看见而互信息直接算就能得到较高值。做互信息特征选择时这就是它比其他过滤式指标更稳的原因不需要预设函数形式也不要求字段同尺度。天气、时段、酒精、车速这些字段类型完全不同放进同一个互信息框架里可以统一排序。需要注意互信息度量的是统计关联不是因果。酒驾与死亡严重程度互信息很高只能说明二者强相关不能说明酒驾“导致”死亡。因果方向要放到后面的网络结构和业务先验里解决。2.2 用 Python 计算互信息矩阵并设定筛选阈值落地时我一般先把事故台账读进来对严重程度做有监督编码。重伤和死亡在分析上经常合并为同一个高风险类别如果不合并后续贝叶斯网络的条件概率表会非常稀疏。下面的代码把严重程度映射为 0/1/2再把类别特征做标签编码连续特征做分位数分箱。读入数据这一步假定已经完成缺失值删除和明显异常值截断。import pandas as pd import numpy as np from sklearn.feature_selection import mutual_info_classif from sklearn.preprocessing import LabelEncoder df pd.read_csv(accident.csv, encodinggbk) # 把严重程度重编码0 仅财产损失1 轻伤2 重伤死亡 severity_map {仅财产损失: 0, 轻伤: 1, 重伤: 2, 死亡: 2} df[severity] df[severity_text].map(severity_map) cat_features [weather, road_type, light, alcohol, fatigue] cont_features [age, speed, visibility] feature_cols [] for col in cat_features: df[col _code] LabelEncoder().fit_transform(df[col].astype(str)) feature_cols.append(col _code) # 连续特征先按分位分箱避免极端值干扰互信息估计 for col in cont_features: df[col _disc] pd.qcut(df[col], q4, labelsFalse, duplicatesdrop) feature_cols.append(col _disc) X df[feature_cols] # discrete_featuresauto唯一值数量少的列按离散特征估计 mi mutual_info_classif(X, df[severity], discrete_featuresauto, random_state42, n_neighbors3) mi_df pd.DataFrame({feature: feature_cols, mi: mi}).sort_values(mi, ascendingFalse) print(mi_df)这段代码输出一张按互信息降序排列的特征表。discrete_featuresauto会让 sklearn 根据每列唯一值数量自动判断离散或连续分箱后唯一值少于 10 的列会走离散估计n_neighbors只对连续列起作用通常取 35。random_state必须固定否则同样的特征每次跑出的 MI 不一样结果没法写进报告。连续特征在这里先分箱再算 MI是因为后面贝叶斯网络必须用离散变量统一在预处理阶段完成。pd.qcut(q4)表示按四分位数分成 4 箱duplicatesdrop是处理车速这种有大量 0 值的列让重复分位点合并成更少箱。阈值怎么定我常用置换检验而不是拍脑袋取一个固定数字。把严重程度这一列随机打乱重新计算所有特征的互信息取每一轮的最大值迭代 200 次后看 95% 分位数。真实特征如果互信息低于这个值它和随机打乱后的最大 MI 没有显著差别直接删掉。rng np.random.RandomState(7) null_max [] for _ in range(200): y_shuffled rng.permutation(df[severity].values) null_max.append(mutual_info_classif(X, y_shuffled, discrete_featuresauto, random_state0).max()) threshold np.percentile(null_max, 95) print(置换检验阈值, threshold)置换次数 200 次是性能和稳定性的折中。数据量上万可以降到 100 次数据只有几百条时建议加到 500 次。阈值由零分布计算不随业务拍脑袋。实际操作中如果 200 次置换跑完阈值只有 0.01而排序表里前几个特征 MI 在 0.2 以上说明这批特征非常强如果前几名 MI 都在 0.05 以下那整个数据集的信息量较弱后续贝叶斯网络的解释空间也有限。这个判断比单个阈值更实用。2.3 离散化和数据分箱互信息计算前最容易翻车的一步互信息本身可以处理连续变量但事故台账里的连续字段偏度通常很大车速大量为 0年龄又集中在 2050 岁。直接用原始数值做 k 近邻估计容易出现局部近邻全是同一类型MI 被高估。折中方案是提前分箱等频分箱保证每个箱样本量接近箱数一般取 35。箱数太少会丢掉非线性箱数太多则每个箱样本量不足条件概率表里出现大量零频后续参数学习也要处理。分箱之后还有一个容易被忽略的检查交叉表的单元格频数。互信息估算在小样本、稀疏格子上会系统性偏大。比如“雾天”只有 20 条记录其中 15 条是死亡事故计算出来的 MI 会很高但它只是小样本噪声。检查方式是对任意两个离散列做交叉表统计频数小于 5 的格子数量。from itertools import combinations def sparse_check(df, a, b): ct pd.crosstab(df[a], df[b]) n_sparse (ct.values 5).sum() return n_sparse, ct.values.min() feature_candidates feature_cols [severity] for a, b in combinations(feature_candidates, 2): n_sparse, min_cell sparse_check(df, a, b) if n_sparse 0: print(f{a} x {b}: 稀疏格 {n_sparse} 个最小频次 {min_cell})发现有稀疏格时常见做法是把低频类别合并进“其他”或者用出现频次低于 20 的样本直接剔除。这一步必须在互信息计算之前完成否则选出来的特征列表不稳定换一批数据结果就变后面贝叶斯网络结构也会跟着翻车。这也是整个流程里最容易被跳过的一道工序。3. 用互信息矩阵搭贝叶斯网络结构从图剪枝到参数学习3.1 结构学习路线先算 MI 再定向还是直接用评分搜索贝叶斯网络要解决两件事结构哪些变量之间有边和参数条件概率表。结构学习有两条主流路线基于约束的算法和基于评分的搜索。基于约束的算法用条件独立性检验拆图典型代表是 PC 算法基于评分搜索的算法在候选 DAG 空间里爬山用 BIC 或 BDeu 评分衡量每个图对数据的拟合程度。互信息在这个阶段的价值体现在约束算法里。检验 X 和 Y 在给定 Z 时是否条件独立用的统计量就是条件互信息。离散数据下卡方检验与互信息有换算关系2n 乘以互信息近似服从卡方分布所以许多资料里直接用卡方检验代替但卡方检验对零频格子很敏感而互信息阈值检验对非线性更稳健。实操中我一般不会全程手动做条件独立性检验而是先用互信息特征选择把节点压缩到 1015 个然后用评分搜索精修结构。变量太多时评分搜索不仅慢还容易陷在局部最优里变量少而精时爬山的图相对可控。还要说明一点评分搜索找到的箭头方向完全来自对联合分布的拟合不代表因果方向。严重程度分析里业务上必然有先后的关系——先有天气、时段再有驾驶行为最后才是事故结果。这些先验必须通过起点图或边约束注入算法否则模型很可能学出“严重程度 → 是否酒驾”这种反常识结构。3.2 在 pgmpy 里走一遍结构学习和参数估计我复现这套流程时用 pgmpy因为它的 HillClimbSearch、BayesianEstimator 和 VariableElimination 接口齐全适合从结构学习一路推到推理。from pgmpy.models import BayesianNetwork from pgmpy.estimators import HillClimbSearch, BicScore, BayesianEstimator from pgmpy.inference import VariableElimination # selected_cols 来自上一章互信息筛选后的特征注意全部转成字符串 model_data df[selected_cols [severity]].astype(str) hc HillClimbSearch(model_data) dag hc.estimate(scoring_methodBicScore(model_data), max_indegree3) print(学习到的边, dag.edges())这段代码最关键的一点是 model_data 里所有列在送入之前必须转成字符串。pgmpy 要求离散变量整数编码在部分版本里会被当作连续值处理直接报错或得到空图。max_indegree3限制每个节点最多 3 个父节点事故数据里如果保留十几个特征不限制父节点数会学出非常宽的条件概率表参数学习时绝大多数格子频次为 0。结构学习出来之后参数学习用贝叶斯估计而不是极大似然。极大似然在零频格子上给出概率 0后续推理一查证据就出 0 概率这在事故分析里不可接受。用 BDeu 先验给每个格子注入等效样本量model BayesianNetwork(dag.edges()) model.fit(model_data, estimatorBayesianEstimator, prior_typeBDeu, equivalent_sample_size20) infer VariableElimination(model)equivalent_sample_size是虚拟样本数20 意味着每个条件概率格子在真实计数基础上额外获得 20 个虚拟样本的平滑。样本少于 2000 条时我会调到 30 或 50防止条件概率表里出现过度自信的小数样本上万则用 1020让先验不至于压过真实数据。3.3 用模型回答「什么情况下最容易发生死亡事故」贝叶斯网络训练完用途不是只做预测而是把证据组合起来反查后验概率。下面这个循环对几组典型场景做推断scenarios [ {alcohol_code: 1, weather_code: 2, fatigue_code: 1}, {alcohol_code: 0, weather_code: 2, fatigue_code: 1}, {alcohol_code: 1, weather_code: 3, fatigue_code: 0}, ] for ev in scenarios: q infer.query(variables[severity], evidenceev) print(ev, q.values)每个场景输出一个长度为 3 的概率数组对应 0/1/2 三个严重程度级别的后验概率。业务上通常只看最后一类的比例酒后加恶劣天气组合如果让死亡概率从基线 0.03 升到 0.15那这个组合交互效应就值得写进报告。分析这类结果时措辞要说“在该网络结构和当前数据下条件概率为多少”不要说“这个因素导致了多少概率提升”。证据组合比单变量拉升更有价值因为事故很少由单因素造成。如果场景数量比较多不要手写字典可以直接遍历原始数据里的真实组合只保留在训练数据中出现频次超过 20 的组合否则推断结果会被稀疏格子的噪声主导。这些后验概率可以直接输出成表格作为解释性分析的附件。4. 严重程度建模的必调参数与避坑清单四条真实翻车记录4.1 五个关键参数与它们的作用范围参数代码位置常见取值调整方向互信息筛选阈值置换检验 95 分位或经验阈值0.030.08样本量小取大值否则易混入噪声特征连续特征分箱数pd.qcut 的 q35箱数多则非线性细节多但零频风险增大最大父节点数HillClimbSearch max_indegree24特征多或样本少时压到 2BDeu 等效样本量BayesianEstimator equivalent_sample_size1050样本越少取值越大平滑越强交叉验证折数KFold 的 n_splits5 或 10结构不稳定就加折并多次重复参数之间的联动比单参数更重要。分箱数提高到 6最大父节点数就要相应降到 2 或 3否则条件概率表的格子数呈指数增长。互信息阈值放宽之后选入更多特征BDeu 等效样本量也要往上调否则少量样本撑不起更宽的网络。这不是单点调参而是一组参数一起动。4.2 现象互信息虚高选进一堆噪音特征现象打印互信息排序表前几名里出现“事故编号”“接警序号”这类 ID 字段或者随机生成一列放进特征列表它的 MI 排序也不低。原因互信息估计在稀疏交叉表上会系统性偏大。事故编号每个取值只出现一次和严重程度列一起形成的交叉表里每个格子频次都极低熵估计严重偏差。小样本的罕见类别同样如此数据里“雾天”如果只有 20 条记录其中 15 条是死亡事故这个 MI 高得吓人但换一年数据大概率变天。解决在算互信息之前先做两道检查。第一特征列里凡是基数等于或接近样本量的列直接排除比如报案号、事故编号。第二用 2.3 的 sparse_check 检查每个候选特征与 severity 的交叉表把所有存在频次小于 5 的格子的特征列合并类别或剔除。做完这两步再跑置换检验阈值选出的特征才稳定。4.3 现象网络只预测“轻伤”这一类现象用贝叶斯网络做分类测试集上的整体 Accuracy 挺高但打开混淆矩阵发现死亡事故几乎全部预测成轻伤。准确率被多数类抬上去模型对高风险样本没有任何分辨能力。原因事故数据里轻伤占绝大多数死亡和重伤可能只有百分之几。BIC 评分优化的是整个联合分布的拟合少数类贡献在评分里被天然稀释。贝叶斯网络把每个变量都建模了多数类先验在各个条件概率表里都占主导最后推理自然倾向多数类。解决第一步评估指标换 balanced_accuracy 和 macro-F1不要再看 Accuracy。第二步把严重程度重编码成二分类把“重伤 死亡”合并为高风险而不是坚持训练三分类模型。第三步如果仍需三分类在训练数据里对高风险样本做加权复制让条件概率表中高风险类的计数不再被淹没。我的经验是事故严重程度分析用二分类高风险/低风险稳定性和可解释性都明显好于三分类。4.4 现象贝叶斯网络结构的方向明显反常识现象评分搜索输出的结构里出现了“severity → alcohol_code”这类边。从业务上无论如何解释不通酒驾发生在事故之前严重程度是结果箭头的因果关系反了。原因评分搜索只比较图的拟合优度BIC 在某个方向条件下参数数量不同可能导致更低分于是决定翻转箭头。互信息本身也没有方向性它只能告诉你两边有关联不能告诉你谁先谁后。把因果方向直觉直接交给评分函数学到反常识结构是非常常见的“玄学”不是代码 bug。解决用业务先验固定起点图。天气、时段、道路类型在时间线上必然早于事故结果把这些关系构造成一个初始贝叶斯网络让爬山从这个起点开始搜索而不是从空图乱跳。start BayesianNetwork([ (weather_code, severity), (alcohol_code, severity), (fatigue_code, severity), ]) dag hc.estimate(scoring_methodBicScore(model_data), max_indegree3, startstart)start 参数决定搜索的初始 DAG爬山算法会在其邻域内调整边而不是从空图任意翻转。想强约束方向时可以把某个边的两个方向分别做成候选跑两轮对比每轮的 BIC 分数选分数低且业务可解释的结果。网络学习完成后还要人工过一遍所有边的方向任何一条不能解释的边都要做同样的替换重训而不是直接接受黑盒输出。5. 从预测到解释结构稳定性验证与动态贝叶斯网络升级路径5.1 用交叉验证重算结构找出真正稳定的边单次爬山学到的 DAG 可能严重依赖训练样本。事故台账换一年数据边的方向和存在性都可能大变。验证稳定性的具体做法是把数据切成 5 折每一折单独重跑一次结构学习统计每条边出现的频率。出现频率低于 80% 的边不写进结论。from sklearn.model_selection import KFold from collections import Counter edge_counts Counter() kf KFold(n_splits5, shuffleTrue, random_state42) for train_idx, _ in kf.split(model_data): train_data model_data.iloc[train_idx] hc HillClimbSearch(train_data) dag hc.estimate(scoring_methodBicScore(train_data), max_indegree3) edge_counts.update(dag.edges()) stable_edges [e for e, c in edge_counts.items() if c 4] print(稳定边, stable_edges)如果某些边出现频率在 23 次之间说明它们对样本敏感报告里最好不要提方向频繁翻转的边要单独标记。模型推理结果也一样同一组证据在不同折模型上算出的后验概率如果标准差超过 0.05这个数值不能当作稳定结论。少数类的条件概率尤其容易波动标准差通常更大这时要考虑合并类别或者增加平滑强度。5.2 有轨迹或时序数据时把静态网络升级为动态贝叶斯网络如果事故数据里有多阶段信息比如接警、出警、现场救援、送医的耗时和状态变化静态贝叶斯网络会把时间信息压缩成一张条件概率表丢失“状态随时间恶化”的结构。此时可以把网络扩展为动态贝叶斯网络把每个变量拆成多个时间片t 时刻的严重程度通过转移边影响 t1 时刻的严重程度各阶段观测变量挂在对应时间片上。常见做法是先分别学习每个时间片的内部结构再固定时间片间的转移边转移边只从早到晚不允许反向。这类模型能模拟“救援延误如何让轻伤转危重”的过程输出比静态网络多一张转移概率表。升级代价是结构学习复杂度显著增加变量数和时间片数一多搜索空间会迅速膨胀。我的建议是只有样本量超过 5000 且确实有多阶段记录时才值得上动态贝叶斯网络否则静态网络加后验查询已经足够支撑解释分析。我自己在事故数据上反复踩过几次坑之后养成一个习惯结构学习完成之后先看边稳定性再看推理结果绝不跳过这一步直接输出。把交叉验证的结构颤抖当作常规检验比任何调参技巧都管用。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →