简介:本资源面向强化学习与空战对抗仿真方向的学习者与研究人员,提供一套基于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, episodes=5000): 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(f"episode {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 学习在空战拦截这个小场景里,比想象中能打得多。希望帮到你。
本文还有配套的精品资源,点击获取