ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于八度分析的飞行员绩效仿真建模:压力、认知负荷与任务表现量化方法

基于八度分析的飞行员绩效仿真建模:压力、认知负荷与任务表现量化方法 做飞行员绩效仿真这事我在项目里折腾了挺长时间。标题里提到的八度分析其实有个历史背景GNU Octave 和 MATLAB 在语法上高度兼容很多做数据分析和仿真的团队习惯把用 Octave 跑数据建模简称为八度分析我这套代码最初也是在 Octave 环境里调通再迁移到 MATLAB 上跑的两边都能用。整个项目的核心就是把战斗机飞行员的生理状态和任务环境转化成可量化的输入通过数学模型估算压力水平、认知负荷最后映射到任务表现用来回答一个很难靠真实飞行实验回答的问题到底哪些因素对飞行员表现影响最大它们之间怎么互相作用。我会把整套建模思路、代码结构、参数设计和踩过的坑都梳理出来对做仿真建模或者数据分析的朋友应该有不少参考价值。1. 项目背景与研究思路1.1 为什么用仿真替代真实飞行实验战斗机飞行员表现研究如果在真实环境中做成本极高。一架战机起降的燃油消耗、地勤保障、空域协调再加上生理监测设备的佩戴和数据采集单个架次的花费动辄数十万元。更麻烦的是真实飞行中很难做到变量控制你没法让同一个飞行员在同一种天气、同一种任务复杂度下反复飞十遍也没法精准制造睡眠不足高难度空战这种组合条件来做对照实验。仿真方案的价值就在这。把飞行员视作一个人-机-环境系统里的核心节点用数学模型描述输入变量心率、睡眠、任务、经验、环境对中间状态压力、认知负荷的影响再映射到输出任务表现就可以在几分钟内跑出几百种组合场景。这本质上是一种基于代理agent-based的蒙特卡洛式仿真只是我们的代理是一个带生理参数的飞行员模型。这套方案还有个附加好处它能把很多文献里的经验公式整合进来。比如睡眠剥夺对认知能力的影响曲线、心率变异性与应激水平的关系、任务复杂性对工作记忆负荷的叠加效应这些在学术论文里都有现成的回归系数直接拿来做模型参数比拍脑袋设参数靠谱得多。1.2 输入输出指标的内在逻辑我设计的模型边界是六个输入、三个输出。先说输入。心率Heart Rate反映飞行员当前生理激活状态既受体力消耗影响也受情绪压力和焦虑水平的驱动。睡眠质量Sleep Quality反映前夜休息的恢复效果直接影响认知资源的初始储备量。任务复杂性Task Complexity反映任务需求本身对注意力和工作记忆的占用从简单的编队飞行到多目标拦截分值跨度很大。经验Experience反映飞行员的技能自动化程度和抗干扰能力通常用飞行总小时数表示。环境Environment包括温度、噪声、光照、过载等外部条件这里统一折算成一个环境压力贡献值。三个输出是压力Stress、认知负荷Cognitive Load、整体任务表现Overall Performance。其中压力和认知负荷是中介变量也就是它们既受输入直接影响又反过来影响任务表现。这种输入→中介→输出的模型结构是这类研究最核心的分析框架它不像一个简单的黑箱回归模型只能看到输入对输出的直接作用而是能展示影响路径的传导链条。1.3 模型结构选择在建模结构上我对比了三条路线纯线性回归、结构方程模型风格的分层回归、以及带非线性项的加权融合模型。最后选了第三条路线原因很实际——前两条路线对数据量的要求比较高而我们的研究对象是个体级别的飞行员样本量本身有限。带非线性项的加权融合模型可以手动控制参数把文献里的经验系数直接嵌进去可解释性更强也方便后期做敏感性分析时逐个参数调优。整体模型路径如下环境、任务复杂性、心率 → 压力任务复杂性、经验、睡眠质量 → 认知负荷压力、认知负荷、经验 → 任务表现2. 核心建模细节与参数推导2.1 压力子模型的数学表达压力是生理激活和心理评价共同作用的结果。我用的是一个带饱和效应的公式[ Stress StressBase k_1 \cdot HR_{norm} k_2 \cdot Env_{norm} k_3 \cdot TC_{norm} k_4 \cdot HR_{norm} \cdot Env_{norm} ]这里所有输入变量都先做归一化处理映射到 0 到 1 区间目的是消除量纲影响。压力基值设为 0.2相当于一个飞行员的静息压力水平。交叉项 (HR \times Env) 用来模拟高心率遇上恶劣环境的协同放大效应这个在真实场景里很明显一个飞行员心率 120 的时候如果座舱温度还能到 40 摄氏度应激反应不是简单相加而是成倍往上走。具体参数我是这样定的(k_1 0.4)(k_2 0.2)(k_3 0.15)(k_4 0.25)。这个组合意味着心率是压力的最大单项贡献者同时环境与前两项的交互系数也相当可观。归一化的细节值得展开说。心率原始值范围我取静息状态 60 到极限状态 180归一化公式是[ HR_{norm} \frac{HR - 60}{180 - 60} ]这样心率 120 就对应 0.5。环境因素我做的是复合指标把温度、过载、噪声三项按权重叠加比如温度超过 30 度就按比例计入过载超过 5G 就开始显著加分噪声超过 100 分贝也计入。最后复合出来的环境分再除以一个最大可能值完成归一化。2.2 认知负荷子模型与经验缓冲认知负荷的建模核心是谁都知道的规律任务越复杂需要的认知资源越多经验越多处理同样任务消耗的资源越少睡眠不足可用认知资源池变小。我用的公式是[ CL \frac{TC_{effect}}{Skill Recovery} ]其中 (TC_{effect}) 是任务复杂性的有效需求值Skill 是经验转化的技能系数Recovery 是睡眠质量转化的恢复系数。[ TC_{effect} TC_{norm}^{1.3} ]这里给任务复杂性加了 1.3 次方的非线性项用来模拟任务难度的非均匀增长任务复杂性从 0.3 涨到 0.7 带来的认知需求增幅远大于从 0.1 涨到 0.5 的增幅接近实战中的体感。经验转化公式是[ Skill 0.5 \frac{Experience_{hours}}{2000} \times 0.5 ]这个公式的逻辑是最低技能水平 0.5新飞行员拥有 2000 飞行小时的老手可以达到 1.0 的最大技能系数。中间线性过渡好理解也好调参。睡眠恢复系数[ Recovery 0.3 \frac{SleepScore}{100} \times 0.7 ]睡眠质量用百分制100 分对应 1.0 的完全恢复0 分对应 0.3 的最低恢复底线。注意这里我没有让恢复系数降到 0因为即使一夜没睡人的基本认知功能也不可能完全归零这是对真实生理底线的尊重。最终认知负荷会被截断在 0 到 1.5 之间大于 1 表示认知资源已经被超额占用这时候任务表现会急剧恶化。2.3 任务表现模型的综合映射任务表现是整个系统的最终输出我把它建模成压力和认知负荷的函数再叠加经验的调节作用[ Perf PerfBase - w_1 \cdot Stress - w_2 \cdot CL w_3 \cdot (Skill - 0.5) ]其中 (PerfBase) 设为 0.9代表一个中等经验飞行员在理想状态下的基础表现。(w_1 0.25)(w_2 0.35)(w_3 0.30)最后一项经验修正可以把表现分值拉高或压低 0.15 个点。同时我还加了一个约束条件当认知负荷超过 1 时表现额外乘以一个衰减系数[ Perf_{final} Perf \times \max(0.5, 1.5 - CL) ]这个设计模拟的是认知超载时的断崖式绩效下降。很多仿真模型用线性公式计算表现结果在极端条件下曲线平滑得不像话——现实是飞行员认知过载时表现会断崖下跌这个非线性项非常关键。2.4 为什么心率、睡眠、环境要走不同路径有个容易忽略的设计细节三个输出之间存在非对称的依赖结构。压力主要被心率、环境、任务复杂性驱动认知负荷主要被任务复杂性、经验、睡眠驱动任务表现则同时受压力和认知负荷的双重压制。这意味着睡眠质量几乎不直接影响压力而是通过认知资源间接影响表现环境因素几乎不直接影响认知负荷而是通过压力路径起作用。这种路径分离是从文献里总结出来的。睡眠剥夺研究的经典结论就是睡眠不足主要损害的是前额叶功能也就是工作记忆和注意力控制而不是直接触发应激反应。环境因素如高温、噪声则更多通过自主神经系统的激活来放大应激感受。把这两条路径分开建模模型的行为会更贴近真实。3. Matlab/Octave 代码架构与实现3.1 模块划分整套代码我拆成了五个文件这是我从这个项目里学到的第一个经验用函数式结构别用巨型脚本。pilot_performance_sim.m主函数负责参数设置、场景遍历和数据汇总normalize_input.m输入变量归一化模块stress_model.m压力子模型cognitive_load_model.m认知负荷子模型performance_model.m任务表现子模型主函数代码框架如下function results pilot_performance_sim() %% 基础参数设置 T 30; % 仿真时长分钟 dt 1; % 时间步长秒 nSteps T * 60 / dt; % 总步数 % 六维输入 pilot.HR 75; % 静息心率 bpm pilot.sleepQuality 85; % 睡眠质量 0-100 pilot.taskComplexity 0.6; % 任务复杂因子 0-1 pilot.experienceHours 1200; % 飞行经验小时数 pilot.env.temp 28; % 座舱温度 ℃ pilot.env.noise 92; % 座舱噪声 dB pilot.env.G 3.2; % 平均过载 G % 归一化 [normData, params] normalize_input(pilot); % 逐秒递推仿真 perfArray zeros(nSteps, 1); stressArray zeros(nSteps, 1); clArray zeros(nSteps, 1); for t 1:nSteps % 心率随时间动态变化模拟任务负荷变化 normData.HR dynamic_heart_rate(normData.HR, t, params); [S(t), CL(t), P(t)] sim_step(normData, params); end end3.2 归一化模块详解归一化模块是所有后续模型的基础也是最容易出错的地方。我写的是这样function [normData, params] normalize_input(pilot) % 心率归一化60bpm→0180bpm→1 params.HR_min 60; params.HR_max 180; normData.HR (pilot.HR - params.HR_min) / (params.HR_max - params.HR_min); normData.HR clip(normData.HR, 0, 1); % 睡眠质量 0-100 → 0-1 normData.sleep pilot.sleepQuality / 100; % 任务复杂性 0-1 直接使用 normData.TC pilot.taskComplexity; % 经验小时数归一化0-3000小时 params.exp_max 3000; normData.exp min(pilot.experienceHours / params.exp_max, 1); % 环境复合指标 % 温度贡献超过25℃开始线性增加40℃达到1.0 tempContrib clip((pilot.env.temp - 25) / 15, 0, 1); % 噪声贡献超过80dB开始计入110dB达到1.0 noiseContrib clip((pilot.env.noise - 80) / 30, 0, 1); % 过载贡献3G以下忽略5G以上饱和 gContrib clip((pilot.env.G - 3) / 2, 0, 1); % 加权合并 normData.env 0.4 * tempContrib 0.35 * noiseContrib 0.25 * gContrib; params struct(HR_min, params.HR_min, HR_max, params.HR_max, ... exp_max, params.exp_max, ... weights, [0.4, 0.35, 0.25]); end这里有个坑环境复合指标的三个分项权重之和是 1.0但各个分项不会同时达到峰值所以合并后的环境压力值在绝大多数场景下小于 0.5这是正常的。如果某个环境的极端组合让值接近 1压力模型里的交互项会迅速起效不会让环境压力被低估。3.3 核心模型函数实现压力子模型function Stress stress_model(normData, params) HR normData.HR; Env normData.env; TC normData.TC; StressBase 0.2; k1 0.4; k2 0.2; k3 0.15; k4 0.25; Stress StressBase k1 * HR k2 * Env k3 * TC k4 * HR .* Env; Stress clip(Stress, 0, 1); end认知负荷子模型function CL cognitive_load_model(normData, params) TC normData.TC; exp normData.exp; sleep normData.sleep; TC_effect TC^1.3; Skill 0.5 exp * 0.5; Recovery 0.3 sleep * 0.7; CL TC_effect / (Skill Recovery); CL clip(CL, 0, 1.5); end任务表现子模型function Perf performance_model(Stress, CL, normData, params) PerfBase 0.9; w1 0.25; w2 0.35; w3 0.30; Skill 0.5 normData.exp * 0.5; Perf PerfBase - w1 * Stress - w2 * CL w3 * (Skill - 0.5); % 认知过载惩罚 if CL 1.0 penalty max(0.5, 1.5 - CL); Perf Perf * penalty; end Perf clip(Perf, 0, 1); end3.4 动态心率与时间演化上面的代码只是单步计算如果要做完整仿真还需要让心率随时间动态变化。我的做法是给心率加一个自回归项和一个任务驱动项function HR_next dynamic_heart_rate(HR_now, t, params) % 基础恢复系数 recovery 0.98; % 任务驱动项在特定时间段内模拟高负荷任务 taskBoost 0; if t 60 t 120 taskBoost 0.05; % 高负荷任务段 end % 随机波动 noise 0.01 * randn(); HR_next recovery * HR_now taskBoost noise; HR_next clip(HR_next, 0, 1); end这套设计虽然简单但已经能产生足够丰富的动态行为心率会随着任务节奏波动而不是固定在一个值上这让后续的压力、表现曲线都有了呼吸感。4. 仿真设计与数据分析流程4.1 多场景正交实验设计最核心的分析方法论是把输入变量组合成正交实验矩阵批量跑仿真然后对产出数据做统计归因。我用的是全因子设计的简化版五个关键维度各取三个水平心率静息75bpm、中度激活120bpm、高度激活160bpm睡眠质量差40分、中70分、好90分任务复杂性低0.3、中0.6、高0.9经验新手300小时、中等1200小时、老手2500小时环境良好综合分0.2、恶劣综合分0.8全组合数是 (3^5 243) 个场景每个场景跑 30 分钟仿真数据量完全可控。主函数里用嵌套循环生成场景矩阵levels [75, 120, 160; 40, 70, 90; 0.3, 0.6, 0.9; 300, 1200, 2500; 0.2, 0.5, 0.8]; results []; for i1 1:3 for i2 1:3 for i3 1:3 for i4 1:3 for i5 1:3 pilot.HR levels(1, i1); pilot.sleepQuality levels(2, i2); pilot.taskComplexity levels(3, i3); pilot.experienceHours levels(4, i4); pilot.envScore levels(5, i5); % 记录稳态输出 [S, CL, P] run_single_simulation(pilot, 30); results(end1, :) [pilot.HR, pilot.sleepQuality, ... pilot.taskComplexity, pilot.experienceHours, ... pilot.envScore, mean(S), mean(CL), mean(P)]; end end end end end4.2 敏感性分析哪个变量最重要跑完 243 个场景最重要的分析是从结果中分离出单个变量的影响权重。我用了一个比较朴素但有效的分析方法计算每个输入与表现指标的 Spearman 秩相关系数因为仿真数据往往不是线性的皮尔逊相关系数容易低估强非线性关系。在我的实验结果里任务复杂性对认知负荷和任务表现的回归系数最大其次是睡眠质量。经验因素在低复杂度场景下影响不大但在高复杂度场景下表现得像救命的稻草——这正是经验缓冲效应的直接证据。环境因素在大多数常规场景下影响权重排在后面但一旦进入高温高过载的极端组合它的交互效应会瞬间放大压力让表现断崖下跌。把结果可视化时我画了三张核心图第一张是输入-表现的平行坐标图第二张是压力-认知负荷-表现的散点矩阵第三张是睡眠质量×任务复杂性对表现的热力图。热力图那个发现很有意思在低睡眠质量区域表现对任务复杂性的梯度非常陡说明睡眠差的时候任务复杂性的杀伤力被放大。4.3 结果解读的注意事项仿真结果会被质疑你们的数据是编的——这个必须承认但仿真模型的意义从来不是预测真实值而是揭示关系方向和相对大小。我在写分析报告时会强调模型输出的绝对值没有意义有意义的是参数量级和排序关系。5. 常见问题与排错锦囊5.1 Octave 和 Matlab 的兼容性坑代码开头写了要兼容 Octave 和 MATLAB实际执行中最大的差异在绘图和函数句柄上。Octave 的图形后端渲染效果差一些但数据计算完全一致。有个容易踩的坑是clip函数MATLAB 在 R2022a 之后才原生支持Octave 早期版本没有。我的做法是手写一个function out clip(x, lo, hi) out min(max(x, lo), hi); end这版代码在任何环境都能跑。5.2 认知过载惩罚导致表现跳变仿真中会出现表现值突然跌到 0.5 的情况初次跑的时候我以为是 bug后来定位到是认知负荷超过 1.0 触发衰减项。这个跳变本身是合理的模型行为但在画图时如果直接连点曲线会出现明显的断崖容易被误读为代码问题。建议在可视化时单独用标记点标注过载时段。5.3 随机种子问题我在动态心率模块里用了randn()如果不设置种子每次跑仿真结果都不一样。做正交实验时必须先执行rng(42)固定随机种子否则敏感性分析的结果会因为随机波动产生偏差。5.4 参数范围越界的处理模型里所有输入变量经过归一化后都应落在 0 到 1 之间但经验变量在实战数据里可能超过 3000 小时环境温度也可能超过 40 度。一定要在归一化模块里做截断不能依赖模型函数内部的 clip否则中间运算可能出现负数或者超过预期区间导致压力值出现负值这种离谱结果。5.5 数据可视化的颜色映射性能热力图建议用parula色带而不是jet因为 jet 色带在暗色背景下会给人错误的高低错觉这在人因工程图表规范里是有明确定论的。一个小细节但发表论文时审稿人可能专门看这个。6. 扩展方向这个模型最大的扩展空间在群体仿真。现在代码只能模拟单飞行员但空战是编队行为僚机之间的信息共享、指挥引导压力、甚至长机对僚机的信任度都会反向影响飞行员的认知负荷。把模型改成多智能体版本加入通信延迟和任务协同就能分析编队结构对整体任务表现的影响那才真正到了战术层面。另一个扩展点是把静态的睡眠质量扩展成动态睡眠模型也就是加入连续多天的睡眠债累积和恢复过程。长时间飞行任务中飞行员的睡眠债务是逐日累积的这会改变认知负荷基线。我这个版本的模型里睡眠质量是每次飞行前的一个输入参数如果要支持连续任务仿真需要改成状态变量每天结束后更新睡眠债第二天再作为输入进入模型。从数据分析的角度这套模型还能往机器学习方向走一步把 243 组仿真数据作为训练集训练一个轻量级神经网络再跟机理模型做对比。机理模型的好处是可解释性神经网络的优点是对非线性交互的表征能力更强两边对同一个场景给出预测值差异最大的地方往往就是机理模型考虑不周的地方这种模型对撞的分析方法在复杂系统研究里越来越常用。个人经验上这类仿真项目最后写报告时最容易被领导或甲方问的问题是这个结论怎么落地。我的建议是不要在输出层面硬解释把模型倒过来用。给定一个目标表现阈值反推需要在睡眠质量、任务分配上做出什么调整这样的反向仿真才是决策者真正关心的东西。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进