Q学习实现空战对抗:Python代码与调参避坑指南
发布时间:2026/9/28 17:19:59来源:尧图网络
简介本资源面向强化学习与空战对抗仿真方向的学习者与研究人员提供一套基于Q学习算法的空战对抗实现方案适合具备一定Python与强化学习基础、希望深入理解智能决策在军事仿真中应用的中高级读者。压缩包共384个文件以276个Python源码为主辅以67个编译缓存、16个XML配置、7份Markdown说明及若干检查点与脚本文件整体约16.28MB结构完整、便于直接运行与二次开发。代码采用参数化编程关键参数可灵活调整编程思路清晰且注释详尽并附赠案例数据支持在Matlab 2014、2019a及2024a环境下复现实验。目前已有91人学习关注。读者可从中获得完整的空战对抗训练框架、Q学习智能体实现细节、训练日志与模型检查点以及可迁移至其他对抗场景的算法思路适合用于课程设计、科研验证与算法对比实验。1. Q学习打空战从一张状态表到能打的拦截决策空战对抗听起来像是要上深度强化学习、要跑几十万帧仿真的大工程但如果你把问题收窄到「二维平面内一对一拦截」Q学习这张状态动作表反而能跑得又快又稳。标题里的 Q学习实现空战对抗附python代码本质就是用一个离散化的状态空间描述双方相对几何关系用一张 Q 表记录「在这个态势下往哪拐、加不加速」的长期收益再靠回合制仿真把这张表迭代到收敛。它解决的不是六自由度空战而是拦截决策这个核心子问题我方如何在一个有限时间内把机头指向对手并保持进入角优势。适合谁适合已经会写 Python、懂一点 numpy想找一个能当天跑通、能可视化、能改奖励函数的强化学习练手项目的人。下面我按「建模 → 训练 → 调参 → 避坑 → 进阶」的顺序把这条链路拆开讲清楚。2. 空战对抗怎么建模成 Q 学习问题状态、动作、奖励三件套Q学习能不能训出来八成取决于建模而不是代码写得多花哨。空战对抗的连续物理量太多直接上原始坐标必然维度爆炸所以第一步是把态势压缩成几个真正决定胜负的量。我一般会先想清楚「飞行员在座舱里看什么」再决定状态里放什么。2.1 状态设计用相对几何量而不是绝对坐标最稳的做法是把我方和敌方的位置、速度都转成相对量。核心几个量距离、我方相对敌方的方位角、敌方相对我方的方位角也就是进入角、双方速度差。这四个量基本能刻画一对一拦截的态势。绝对坐标放进状态只会让 Q 表无谓地膨胀因为同一个相对态势平移到地图任何位置最优动作都一样。离散化是这里的关键动作。连续量必须分桶桶太粗学不精桶太细学不动。我的经验是距离分 6 到 8 档近距、中距、远距各再细分角度分 12 档每 30 度一档速度差分 3 到 5 档。这样状态总数大概在几千到一万量级普通笔记本内存完全扛得住。import numpy as np def discretize_state(dist, my_angle, enemy_angle, speed_diff): # 距离0~10000 米分 8 档每档 1250 米 d_bin min(int(dist / 1250), 7) # 角度-180~180 度每 30 度一档共 12 档 a_bin min(int((my_angle 180) / 30), 11) e_bin min(int((enemy_angle 180) / 30), 11) # 速度差-100~100 m/s分 5 档 s_bin min(max(int((speed_diff 100) / 40), 0), 4) return (d_bin, a_bin, e_bin, s_bin)这段代码把四个连续量映射成一个四元组。参数说明距离上限 10000 米是常见近距空战尺度超出这个范围基本还没进入交战角度用 30 度一档是精度和状态数的折中再细到 15 度状态数翻倍但收益有限速度差 40 m/s 一档对应大约 0.1 马赫够区分能量优势方。注意min和max的夹取不能省仿真里偶尔会飞出边界不夹取会直接索引越界。2.2 动作空间与奖励函数让「咬尾」变成可累积的信号动作空间我一般用离散的 9 个动作左转/直行/右转 乘以 加速/匀速/减速。这样动作数少Q 表更新快也符合飞行员的基本操纵直觉。如果你想让动作更平滑可以扩到 15 个但训练回合数要相应增加。奖励函数是空战 Q 学习最容易翻车的地方。纯稀疏奖励只有击落给 1会让智能体在前期几乎学不到东西因为随机探索很难撞上击落。我的做法是稠密奖励加稀疏奖励叠加用角度优势给过程奖励用距离缩小给引导奖励击落给大额终局奖励。def compute_reward(dist, enemy_angle, done, win): r 0.0 # 角度优势敌机进入角越小越接近正后方被咬奖励越高 r (180 - abs(enemy_angle)) / 180.0 * 0.5 # 距离引导越近越好但避免过早贴脸 r (1.0 - dist / 10000.0) * 0.3 if done and win: r 100.0 elif done: r - 50.0 return r逻辑说明角度项让智能体主动去抢进入角优势这是空战的核心距离项提供早期梯度避免智能体原地打转终局奖励量级要远大于过程奖励否则智能体会满足于「保持优势但不击落」。参数上过程奖励每步控制在 1 以内终局奖励给到 100 量级这个比例能让 Q 值稳定收敛。奖励权重是最需要反复试的部分别指望一次调好。3. 用 Python 把 Q 学习训练循环跑起来从 Q 表到收敛建模定下来之后训练循环本身其实很短。Q学习的核心就一个更新公式难点全在探索策略和收敛判断上。这一章把能直接抄的训练骨架给出来再说清楚每个参数为什么这么设。3.1 Q 表初始化与贝尔曼更新Q 表用字典或者 numpy 数组都行。状态数固定的话用 numpy 数组更快索引就是离散化后的元组展平。更新公式是标准的当前 Q 值向「即时奖励 折扣后的下一状态最大 Q 值」逼近。import numpy as np # 状态空间大小8 * 12 * 12 * 5 STATE_DIM 8 * 12 * 12 * 5 ACTION_DIM 9 Q np.zeros((STATE_DIM, ACTION_DIM)) alpha 0.1 # 学习率 gamma 0.95 # 折扣因子 epsilon 1.0 # 初始探索率 epsilon_min 0.05 epsilon_decay 0.995 def state_index(state): d, a, e, s state return ((d * 12 a) * 12 e) * 5 s def choose_action(idx): if np.random.rand() epsilon: return np.random.randint(ACTION_DIM) return int(np.argmax(Q[idx])) def update(idx, action, reward, next_idx, done): target reward if not done: target gamma * np.max(Q[next_idx]) Q[idx, action] alpha * (target - Q[idx, action])参数说明学习率 0.1 是离散 Q 学习的常用值太大震荡太小收敛慢折扣因子 0.95 表示智能体看重未来约 20 步的收益空战拦截的时间尺度大概就是这个量级探索率从 1.0 线性或指数衰减到 0.05前期必须充分探索后期要稳定利用。done为真时不再加未来收益这是终止状态处理漏了这一步 Q 值会发散。3.2 训练循环与收敛判断训练循环就是「重置环境 → 选动作 → 执行 → 拿奖励 → 更新 → 判断终止」的重复。判断收敛不要只看奖励曲线那个噪声很大。我一般同时看两个指标每 100 回合的平均累计奖励以及平均回合步数。奖励上升且步数下降说明智能体学会了更快结束战斗。def train(env, episodes5000): global epsilon reward_history [] for ep in range(episodes): state env.reset() idx state_index(state) total_reward 0 done False while not done: action choose_action(idx) next_state, reward, done, win env.step(action) next_idx state_index(next_state) update(idx, action, reward, next_idx, done) idx next_idx total_reward reward epsilon max(epsilon * epsilon_decay, epsilon_min) reward_history.append(total_reward) if ep % 100 0: avg np.mean(reward_history[-100:]) print(fepisode {ep}, avg reward {avg:.2f}, epsilon {epsilon:.3f}) return Q逻辑说明每个回合结束才衰减一次探索率这样衰减节奏和回合长度解耦更稳定。打印频率设成 100 回合太密看不清趋势太疏发现不了问题。5000 回合是个起点具体看状态空间大小状态多就多跑。如果 2000 回合后平均奖励还在原地抖别硬跑回去查奖励函数和状态离散化。提示训练前先用随机策略跑几十回合确认环境本身能正常终止、奖励量级合理。环境有 bug 的话Q 学习会把它学成一堆无意义的 Q 值你还以为是算法问题。4. 空战 Q 学习调参避坑那些让训练原地打转的坑这一章是我踩过的坑合集每条都按「现象 → 原因 → 解决」写。空战 Q 学习翻车基本逃不出这几类提前知道能省好几天。4.1 智能体学会原地转圈不进攻现象训练几千回合后智能体每回合都在做匀速直行或小幅转向从不主动接近敌机奖励卡在一个不高不低的平台。原因距离引导奖励给得太弱或者角度奖励的符号搞反了。智能体发现「保持现状」能稳定拿到那点过程奖励而主动接近有风险可能被反咬扣分于是学会了躺平。解决先检查角度奖励方向对不对敌机进入角应该是越小越好。然后把距离引导奖励的权重提上来或者加一个「距离缩小就给正奖励、距离拉大就给负奖励」的差分项。差分项比绝对距离项更能驱动接近行为。4.2 Q 值越来越大直到溢出现象训练到中途 Q 值开始指数增长最后变成 inf 或 nan动作选择完全失效。原因终止状态没处理好或者奖励量级失控。最常见的是done为真时还在加未来收益导致 Q 值自我强化。另一个原因是终局奖励给得太大比如给到 10000配合折扣因子会迅速放大。解决确认更新公式里done分支正确跳过未来收益。终局奖励控制在过程奖励的 100 倍以内。如果已经溢出把 Q 表重置重新训别试图抢救。4.3 状态离散化边界导致索引越界现象训练随机崩溃报IndexError位置在状态索引计算处。原因仿真里距离或角度偶尔超出预设范围比如距离算出来是负数或者超过 10000离散化后索引为负或超上限。解决所有离散化函数里都加min和max夹取别信仿真永远输出合法值。这是血泪经验加两行夹取能省掉半夜排查的功夫。4.4 训练快但实战表现差现象训练时平均奖励很高但拿贪心策略去跑测试智能体表现一塌糊涂。原因探索率衰减太快Q 表只覆盖了部分状态很多状态没被充分访问过贪心时选到没学好的动作。解决放慢探索率衰减保证每个状态至少被访问几十次。可以统计状态访问次数对访问少的状态保持较高探索。另外测试时用固定随机种子多跑几局单局结果说明不了问题。4.5 奖励曲线剧烈震荡看不出趋势现象每回合奖励在正负之间大幅跳动看不出是涨是跌。原因空战初始态势随机性太大有的开局占优有的开局劣势单回合奖励方差天然就大。解决看滑动平均而不是原始曲线窗口取 100 回合。同时把初始态势的随机范围收窄一点先让智能体在相似开局下学稳定再逐步扩大随机性。这是课程学习的思路对空战这种高方差任务特别管用。5. 从能跑到能打Q 表可视化与策略验证的进阶技巧训练收敛只是及格线真正判断这个空战 Q 学习方案值不值得继续投入得看策略能不能解释、能不能泛化。我一般会做两件事把 Q 表在几个典型态势下切片看动作偏好以及用固定态势集做批量验证。先看 Q 表切片。挑几个有代表性的状态比如「中距、我方略占角度优势、速度相当」把该状态下 9 个动作的 Q 值打出来。如果最优动作是「右转加速」这类符合空战直觉的选择说明学到位了如果最优动作是「直行减速」这种反直觉的多半是奖励函数还有问题。这个检查比看奖励曲线直观得多。def inspect_policy(Q, state): idx state_index(state) q_values Q[idx] action_names [左转加速,直行加速,右转加速, 左转匀速,直行匀速,右转匀速, 左转减速,直行减速,右转减速] for name, q in zip(action_names, q_values): print(f{name}: {q:.2f}) best action_names[int(np.argmax(q_values))] print(f最优动作: {best})参数说明state传离散化后的四元组比如(3, 5, 7, 2)表示中距、我方方位中等、敌方进入角偏大、速度略占优。多切几个状态对比能看出策略是否随态势合理变化。再看批量验证。固定一组初始态势比如 50 个不同开局用贪心策略各跑一局统计胜率、平均击落时间和平均剩余能量。这三个指标比单局胜负靠谱得多。胜率高但平均时间很长说明策略保守胜率一般但时间短说明策略激进看你的任务需求取舍。验证指标含义健康范围参考胜率贪心策略击落比例60% 以上算可用平均击落时间从开局到结束的步数越短越主动但别短到靠运气平均剩余能量结束时我方速度余量别为赢把能量耗光最后说一个我自己的习惯每次改奖励函数或离散化粒度都保留一份旧的 Q 表和验证结果新版本必须在这三个指标上不劣于旧版本才采纳。空战 Q 学习的调参很容易「改一处好一处坏一处」没有基线对比就是瞎调。这套流程跑顺之后你会发现 Q 学习在空战拦截这个小场景里比想象中能打得多。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网