网络安全政策优化:0-1整数规划与Python实现

发布时间:2026/9/13 2:08:06
网络安全政策优化:0-1整数规划与Python实现 简介这是2025年美赛F题“网络安全政策分析与优化模型”的完整题解资源面向数学建模竞赛参与者、计算机及数据分析相关专业学生也适合作为课程设计、毕业项目或课题前期的政策量化参考。方案以数据驱动方式评估和优化各国网络安全政策有效性核心方法涵盖贝叶斯网络建模、双重差分、合成控制分析、倾向得分匹配、政策成本效益分析等并配有政策仪表板、国家画像、地理空间影响力图、边际削减效果图等可视化输出数据文件覆盖多国网络安全指标、网络犯罪率、劳动力密度等维度源码脚本完整包含数据预处理、模型构建与图表生成流程便于从数据到结论对照理解也方便二次开发与算法替换。整套资料以zip压缩包提供共35个文件包括7个Python脚本、9个CSV数据文件、17张分析图表以及README和说明文档整体仅4.58MB结构清晰代码经测试可运行。目前已有77人学习适合需要快速掌握美赛F题建模流程、复现政策评估实验或进行拓展研究的读者。1. 网络安全政策优化题为什么不是排优先级而是解优化模型美赛F题越来越像“给一堆约束和选项让选手自己定义什么是最优”。网络安全政策问题尤其典型可选的合规措施有十几到上百项每项有实施成本、对风险的抵消能力、对合规分数的贡献措施之间还有依赖和互斥。多数人的直觉是按风险等级排序后往预算里塞这个做法在小样本下能看但只要出现“必须先做A才能做B”“不做C则D成本翻倍”这类条件贪心排序就会给出次优解。正确做法是把政策制定建模成0-1整数规划用Python的线性规划库求全局最优。本文用一组可复现的模拟数据带你走通“数据清洗–多目标建模–源码实现–敏感性验证”的完整链路适合美赛参赛队、负责合规预算的安全工程师以及想用优化模型做决策分析的数据从业者。2. 用Pandas清洗与构造网络安全政策数据数据是模型的地基。F题给的数据往往不是一张干净的Excel而是多个Sheet分别放“措施清单、威胁事件、资产价值表”。直接合并会遇到字段大小写不一致、措施编号重复、Excel里数字被存成文本等问题。我习惯先把所有表拉平统一字段名再做merge。为了演示且可复现下面先用numpy生成一份模拟数据集再用Pandas完成清洗和拼接。2.1 生成可复现的措施与威胁数据集先构建措施清单每条是一类可决策的网络安全控制项例如部署防火墙、启用日志审计、对数据库加密、给员工做培训。每个措施有三个关键属性cost实施成本万元、effectiveness对威胁的缓解比例0到1之间、compliance_score合规基线得分。depends_on记录前置措施比如“启用多因子认证”依赖“升级身份管理系统”。import pandas as pd import numpy as np rng np.random.default_rng(42) n 20 df pd.DataFrame({ measure_id: [fMC{i:02d} for i in range(1, n 1)], name: [f控制措施{i} for i in range(1, n 1)], cost: rng.integers(5, 50, n) * 10, effectiveness: rng.uniform(0.3, 0.95, n), compliance_score: rng.integers(1, 10, n), depends_on: [None] * n }) # 手动添加依赖关系模拟“先做基础措施才能做进阶措施” df.loc[4, depends_on] MC01 df.loc[9, depends_on] MC05 df.to_csv(security_measures.csv, indexFalse) # 威胁事件表每条威胁关联一个可缓解它的措施 threat pd.DataFrame({ threat_id: [fTH{i:02d} for i in range(1, 11)], related_measure: [fMC{i:02d} for i in range(1, 11)], freq: rng.integers(1, 20, 10) }) threat.to_csv(threat_events.csv, indexFalse) print(df.head())seed42保证每次运行结果一致评委复现时不需要额外说明。cost用integers(5, 50) * 10含义是5到50之间的整数乘以10代表5万到50万元后面预算按百万元计算比较直观。effectiveness取0.3到0.95之间的均匀分布现实中来自厂商评测或历史事件统计分析。rng.integers是NumPy 2.x的新写法如果你的环境较老换成rng.randint效果相同。生成后的措施表前几行如下所示。measure_idnamecosteffectivenesscompliance_scoredepends_onMC01控制措施15000.827NaNMC02控制措施21000.453NaNMC03控制措施32300.615NaNMC04控制措施43800.738NaNMC05控制措施51200.584MC012.2 统一字段名、处理缺失值与跨表合并第二张表是威胁事件记录。常见的就是related_measure里混入不可见空格比如MC01和MC01看起来相同合并时却匹配不上。还有depends_on列里有人用“-”表示无依赖、有人用“无”有人直接留空必须统一成空字符串。数值列也可能混入“约500”这样的文本导致sum直接报错。下面的代码处理这几种典型脏数据threat pd.read_csv(threat_events.csv) measure pd.read_csv(security_measures.csv) # 统一列名先去首尾空格再改成小写 threat.columns [str(c).strip().lower() for c in threat.columns] measure.columns [str(c).strip().lower() for c in measure.columns] # 数值列统一转成浮点数无法解析时置为NaN for col in [cost, effectiveness, compliance_score]: measure[col] pd.to_numeric(measure[col], errorscoerce) # 依赖列统一成字符串空值统一为 measure[depends_on] measure[depends_on].fillna().astype(str).str.strip() threat[related_measure] threat[related_measure].str.strip() # 合并威胁频率与措施属性 combined threat.merge( measure[[measure_id, cost, effectiveness, compliance_score]], left_onrelated_measure, right_onmeasure_id, howleft ) print(combined.isna().sum())errorscoerce能把“约500”这类文本直接转成NaN后面用measure.dropna(subset[cost])就能把脏行筛出来。howleft保证威胁记录不丢而关联不上的威胁会得到全空的措施字段这些记录需要回到原始Excel核实。合并前最好加一句assert measure[measure_id].is_unique因为measure_id一旦重复合并后行数会膨胀后面的整数规划也跟着错。提示Pandas合并步骤里出现的“数据不一致”大多不是值本身错而是key的编码、大小写、空格不一致。用df[col].str.len().nunique()检查长度分布往往比肉眼快得多。3. 多目标优化模型从0-1整数规划到线性化求解这一章进入核心如何把“网络安全政策”翻译成数学优化模型。直接选型0-1整数规划是因为现实里的控制措施都有不可分性——你不能“半部署”一套防火墙也不能“30%启用”日志审计。变量只能取0或1。3.1 为什么政策优化必须用0-1整数规划如果只用线性规划连续变量求解结果会说“实施防火墙0.77套”这个答案在代码层面没有问题但在安全政策评审时是荒谬的。0-1变量天然表达这种不可分性模型变成混合整数线性规划MILP。相比按风险排序的贪心算法MILP能统一处理依赖、互斥和预算联动并且PuLP这类开源库就能求到全局最优。措施数量在几百个以内时CBC求解器通常几秒就能解决完全满足美赛场景。3.2 目标函数与约束的构造定义决策变量x_i表示是否实施措施i。目标有三块最大化风险降低、最大化合规得分、最小化总成本。三个目标量纲不一致常见做法是线性加权把风险和合规折算成货币当量。在演示数据里我们让每条威胁只关联一个可缓解它的措施因此风险降低可以写作risk_reduction sum(威胁频率 freq_i × 措施有效性 effectiveness_i × x_i)这个表达式是线性的因为freq_i和effectiveness_i是输入常数只有x_i是变量。如果题目中一条威胁能被多个措施缓解直接累加会重复计算风险降低常见处理办法是把这些措施改成互斥约束即sum(x_i) 1或者人为把它们拆成“组合方案包”让每个包成为一个新决策项。约束包括预算约束、依赖约束和可选的政策约束预算约束sum(cost_i × x_i) Budget依赖约束若措施i依赖措施j则x_i x_j含义是“选了子项必须先选父项”互斥约束x_i x_j 1用于表示两条措施冲突时只能选一个强制约束x_must 1表示合规基线要求的必选项权重参数w的设定是这题最容易拍脑袋的地方。我一般会把w_cost固定为1这样目标函数的值域以“万元”为基准w_risk设为“每次威胁事件的平均业务损失 × 威胁频率”w_comp则参考“合规不达标可能产生的罚款或商誉损失”。这三个权重不再是随意填的而是对应到真实业务量评委复核时也能理解。3.3 用鲁棒优化处理威胁频率不确定网络安全威胁频率本身是估计值受新漏洞、业务扩展和行业环境变化影响。如果把freq当作单点估计模型会输出一个“精确但脆弱”的方案。更稳妥的是把输入改成区间[freq_min, freq_max]引入不确定集做鲁棒优化。最简单的鲁棒化方法是“悲观-中性-乐观”三情景法把freq分别乘1.2、1.0、0.8得到三个模型分别求解后再取每次都被选中的措施集合。这种集合交集的方式比复杂的对偶模型更直观代码也好写适合在F题中展示鲁棒性分析。第5章会给出具体实现。3.4 用PuLP建立网络安全优化模型的核心代码下面是一段可以直接放进Python脚本里的模型代码。输入是第2章清洗好的df和threat_freq输出是PuLP的模型对象和决策变量字典。import pulp as pl def build_policy_model(df, threat_freq, budget, w_risk, w_cost, w_comp): prob pl.LpProblem(SecurityPolicy, pl.LpMaximize) x pl.LpVariable.dicts(select, df[measure_id], catBinary) cost_total pl.lpSum( df.loc[df[measure_id] mid, cost].iloc[0] * x[mid] for mid in df[measure_id] ) risk_reduction pl.lpSum( threat_freq.loc[mid, freq] * df.loc[df[measure_id] mid, effectiveness].iloc[0] * x[mid] for mid in df[measure_id] ) compliance pl.lpSum( df.loc[df[measure_id] mid, compliance_score].iloc[0] * x[mid] for mid in df[measure_id] ) prob w_risk * risk_reduction - w_cost * cost_total w_comp * compliance prob cost_total budget for _, row in df.iterrows(): parent row[depends_on] if isinstance(parent, str) and parent.strip() ! : prob x[row[measure_id]] x[parent] return prob, x参数含义budget是总预算单位与cost一致w_risk、w_cost、w_comp分别是风险降低、成本和合规得分的权重threat_freq是index为measure_id、包含freq列的DataFrame。代码里用df.loc[...]逐行取属性效率虽然一般但措施数量小于500时完全够用并且比复杂字典推导更容易让评委读懂。依赖约束写在循环里只要depends_on非空就要求x[子项] x[父项]。4. Python源码实现策略筛选、求解与结果解析第3章的build_policy_model只是模型骨架实际参赛时还需要把数据读取、模型构建、求解、结果输出串成一个完整流程。这一章把它封装成一个PolicyOptimizer类让整份源码可以一键运行并输出决策表。4.1 完整的PolicyOptimizer类实现class PolicyOptimizer: def __init__(self, measures_csv, threat_csv, budget, weights): self.df pd.read_csv(measures_csv) self.threat pd.read_csv(threat_csv) # 将威胁表转成以 measure_id 为索引的形式 self.freq self.threat.set_index(related_measure)[[freq]] self.budget budget self.w_risk, self.w_cost, self.w_comp weights self.prob None self.x None def build_model(self): df self.df prob pl.LpProblem(SecurityPolicy, pl.LpMaximize) x pl.LpVariable.dicts(sel, df[measure_id], catBinary) # 三个目标分量 cost_total pl.lpSum(df.loc[i, cost] * x[row[measure_id]] for i, row in df.iterrows()) risk_reduction pl.lpSum( self.freq.loc[mid, freq] * row[effectiveness] * x[mid] for mid, row in df.set_index(measure_id).iterrows() if mid in self.freq.index ) compliance pl.lpSum(row[compliance_score] * x[mid] for mid, row in df.set_index(measure_id).iterrows()) prob self.w_risk * risk_reduction - self.w_cost * cost_total self.w_comp * compliance prob cost_total self.budget # 依赖关系子项被选父项必须被选 for _, row in df.iterrows(): parent row[depends_on] if isinstance(parent, str) and parent.strip() ! : prob x[row[measure_id]] x[parent] self.prob prob self.x x def solve(self): self.prob.solve(pl.PULP_CBC_CMD(msgFalse)) chosen [mid for mid, var in self.x.items() if pl.value(var) and pl.value(var) 0.5] return chosen这个类把数据、预算和权重都作为构造参数传入后续做参数扫描时只需要不断创建新实例。PULP_CBC_CMD(msgFalse)用于关闭求解器日志输出更干净。pl.value(var) 0.5是判断0-1变量取值的常用写法因为浮点求解可能返回0.999999或0.000001。4.2 输出决策结果与预算使用率拿到选中列表后还需要生成一份供业务人员看的结果报告。增加一个report方法def report(self): status pl.LpStatus[self.prob.status] chosen self.solve() sub self.df[self.df[measure_id].isin(chosen)] used_budget sub[cost].sum() risk_reduced sum( self.freq.loc[mid, freq] * row[effectiveness] for _, row in sub.iterrows() for mid in [row[measure_id]] if mid in self.freq.index ) return { status: status, chosen_measures: chosen, used_budget: used_budget, budget_ratio: round(used_budget / self.budget, 4), risk_reduced: round(risk_reduced, 2), compliance_score: sub[compliance_score].sum() }budget_ratio是预算使用率用于判断预算是否被浪费risk_reduced是模型目标函数里的风险降低总量。当status返回Infeasible时多半是预算低于某个强制前置措施的成本或者权重配置让目标函数无界。出现这种情况先检查depends_on里是否有循环依赖再检查df[cost] 0是否全为真。4.3 参数扫描预算和权重怎么影响结果安全政策优化的重点不是跑一次出结果而是观察结果随预算、权重的变化。下面的循环分别用三档预算运行模型并输出选中的措施数量for budget in [300, 500, 700, 900]: opt PolicyOptimizer(security_measures.csv, threat_events.csv, budget, weights(2, 1, 0.5)) opt.build_model() reps opt.report() print(budget, -, len(reps[chosen_measures]), 项措施 预算使用率, reps[budget_ratio])实际运行会看到措施数量并不随预算线性增加因为依赖关系会产生“门槛效果”某些措施未达到预算前高阶措施全部无法被选中。这种非线性正是整数规划的价值所在。权重扫描也有讲究w_risk调大模型会优先选高有效性措施w_comp调大模型会更倾向堆合规分哪怕成本变高。美赛评审时把这三组参数下的方案差异做成表格比单给一个最优解有说服力得多。5. 敏感性分析、鲁棒性与提交指标验证最后一章落到一个具体技巧如何用“三情景鲁棒验证”证明你的方案不是靠运气跑出来的。这一步在美赛评审中非常加分因为它直接回应了网络安全政策面对的最大问题——威胁频率是预测值不是真实值。5.1 悲观-中性-乐观三情景验证将threat_events.csv里的freq分别乘以1.2、1.0、0.8表示威胁发生频率的高估、基准和低估三种情景。每种情景都重新建模求解最后取三个情景解决方案的交集作为“稳健必选措施”scenarios {pessimistic: 1.2, baseline: 1.0, optimistic: 0.8} chosen_sets {} for name, mult in scenarios.items(): opt PolicyOptimizer(security_measures.csv, threat_events.csv, budget500, weights(2, 1, 0.5)) opt.freq[freq] opt.freq[freq] * mult opt.build_model() chosen_sets[name] set(opt.report()[chosen_measures]) robust_measures set.intersection(*chosen_sets.values()) print(三情景下都被选中的措施, sorted(robust_measures))这三条结论可以直接写进论文的鲁棒性章节悲观情景下被迫追加的措施有多少乐观情景下被放弃的措施有多少以及始终入选的稳健措施组合。后者就是政策制定者最应该优先保障的部分。5.2 生成帕累托前沿图并写入附录最后用matplotlib把不同权重下“总成本 vs 风险降低”的点画出来形成帕累托前沿。代码很短但图能直观展示优化模型给出的不是一个点而是一条可决策的边界。import matplotlib.pyplot as plt points [] for w_risk in [0.5, 1, 2, 4, 8]: opt PolicyOptimizer(security_measures.csv, threat_events.csv, budget800, weights(w_risk, 1, 0.5)) opt.build_model() rep opt.report() points.append((rep[used_budget], rep[risk_reduced])) x, y zip(*points) plt.scatter(x, y) plt.xlabel(总成本万元) plt.ylabel(风险降低值) plt.title(网络安全政策优化模型的帕累托前沿) plt.savefig(pareto_frontier.png, dpi150)把这些点对应的措施组合存成CSV放进论文附录然后把robust_measures作为第一推荐方案。评委想复核时只需要重新跑一遍solve()就能在几分钟内生成本文涉及的全部结果不需要额外配置商业求解器。从数据文件到最终图表整条代码链就是一个可复现的优化模型交付物。本文还有配套的精品资源点击获取