首页 / 资讯中心 / 文章详情

空战对抗中的Q学习:从Q表到状态离散化与奖励塑形实战解析

空战对抗中的Q学习:从Q表到状态离散化与奖励塑形实战解析 ★ FEATURED ARTICLE
简介面向空战博弈与强化学习研究者这份工程以Q学习算法为核心实现了智能体在空战对抗环境中的自主决策并附完整Python代码。资源共384个文件压缩包大小16.28MB其中包含276个Python脚本、67个pyc编译文件、16个XML环境参数配置、7个Markdown说明文档、6个文本文件以及训练生成的ckpt权重和TensorBoard事件文件从算法实现、环境配置到训练记录一应俱全。已有91人下载学习适合希望快速上手强化学习空战项目的开发者。代码采用参数化编程注释详尽调参方便可直接运行案例数据复现对抗效果Markdown文档梳理了代码结构与使用要点XML文件定义了仿真环境ckpt权重支持加载已训练的模型继续迭代。读者还可借助TensorBoard观察训练曲线深入理解Q学习的收敛过程并在此基础上修改奖励函数、调整探索策略开展更深入的算法对比与改进实验。1. 空战对抗里的Q学习不靠战术公式靠一张Q表打赢缠斗先泼一盆冷水如果你期待用Q学习让无人机像王牌飞行员一样做眼镜蛇机动那大概率会翻车。Q学习擅长的是在状态空间和动作空间都被切碎之后通过反复试错学出一个“查表式”的格斗策略而不是在连续高动态环境中做端到端控制。这个标题里的“空战对抗”指的是简化后的单机对单机博弈双方在同一片空域用一个固定或者半智能的敌机策略让我方通过奖励信号学会咬尾、占位、脱离锁定这些基本动作。适合读这篇的人是手里已经有Python基础、想从零把强化学习的经典算法落进一个看得见博弈过程的仿真场景里并搞清楚“状态怎么做网格化、奖励怎么设计、参数怎么调、为什么训练曲线跑飞”的这些问题的从业者。这里的Q学习不是噱头它是整个博弈闭环里的决策核心把这张Q表喂好比调一堆超参数更能直接改变对抗胜率。2. 空战问题怎么改写成Q学习能吃的状态、动作和奖励2.1 空战对抗为什么适合用Q学习从MDP五元组说起强化学习里最常见的建模方式是马尔可夫决策过程空战对抗的单个决策周期恰好能对应上这个五元组状态、动作、转移概率、奖励、折扣因子。敌我双方每一时刻都有一个联合状态我方根据这个状态选择一个机动动作环境返回新的状态和一个即时奖励。对Q学习来说它不需要知道敌机的真实控制逻辑也不需要显式建模空气动力学模型它只需要“当前长什么样、做了什么、拿到多少分、接下来变成什么样”这就是model-free的核心优势。很多做过工程的人会问空战是连续状态连续动作Q学习不是只能处理离散的吗答案是先把连续空间切成离散网格这也是这个标题里能给出Python代码、并且能在一台普通笔记本上跑起来的原因。状态网格化的代价是精度换来的是可复现和可解释。你不必去对抗复杂的气动模型先在一个二维平面上验证博弈逻辑再把状态维度和网格分辨率逐步加回来。这个简化路线对于研发验证阶段完全够用。2.2 状态空间设计位置、速度、航向和相对几何关系设计状态空间时最容易犯的错是“什么都往里塞”绝对坐标、绝对速度、绝对航向全部进Q表结果状态数量爆炸训练一万个回合都在重复相同轨迹。我一般会优先取“相对状态”因为它天然消除了地图位置带来的冗余也让Q表泛化能力更强。最常用的最小状态集合是相对距离、相对方位角、速度差、高度差如果是二维平台就省掉高度差。下面这个离散化函数可以说明怎么把连续量变成表格索引def discretize_state(rel_dist, rel_angle, speed_diff, grid_size8): dist_bin min(int(rel_dist / 500.0), grid_size - 1) angle_bin int((rel_angle np.pi) / (2 * np.pi / grid_size)) % grid_size speed_bin np.clip(int((speed_diff 100) / 25), 0, grid_size - 1) return dist_bin, angle_bin, speed_bin逻辑上rel_dist的单位是米500米一格超过范围就截断到最后一格rel_angle的取值范围是[-pi, pi]被均匀切成8个扇区对接近正尾后的角度和正头前的角度做了区分speed_diff被限制在-100到100之间每25米/秒一格。这套离散化参数看起来“拍脑袋”但它决定Q表的状态总量8的3次方等于512个状态每个状态有5个动作整张表只有2560个格值训练效率会高很多。2.3 动作空间与奖励塑形赢一局才能拿到奖励太稀疏了空战动作如果做成连续油门加连续舵面Q学习基本学不动。常见做法是把动作离散成几个战术级的基本机动左侧转、右侧转、平飞、加速、减速。方向上如果把三维动作压到二维就只保留航向变化如果后面要加高度维度再加一个爬升/俯冲动作。动作不是越细越好动作数越多每一个动作被采样到的次数越少训练方差就越大。奖励设计是一个典型“玄学”环节。空战最自然的奖励是“击落敌机1被击落-1”但这个奖励太稀疏了可能几百个回合里一次都没打中Q表完全得不到有效梯度。实际工程里需要做奖励塑形也就是把中期过程信号拆出来。下面这段奖励函数是一个可用的起点def compute_reward(prev_dist, now_dist, angle_to_enemy, locked_on): reward 0.0 reward 0.5 * (prev_dist - now_dist) / 100.0 # 接近奖励 if abs(angle_to_enemy) np.pi / 6: reward 0.3 # 进入敌机后向6点钟区域 if locked_on: reward - 0.5 # 被敌机锁定给负反馈 return reward这个函数的核心逻辑是给“逼近”的每一步都发小额正奖励给“进入尾巴区域”发额外奖励给“被锁定”施加惩罚。注意这里没有把击落事件当成唯一奖励而是在击落时额外再加一个大的正奖励否则Q表会倾向于原地绕圈因为绕圈也能靠接近奖励刷分。奖励塑形最怕的是agent找到“刷分漏洞”比如反复横跳获得接近奖励这一点在后面的避坑章节会专门展开。2.4 为什么先选Q表而不是DQN或PPO可解释性和调参成本同类任务里DQN和PPO现在更常见但在这个标题限定“附python代码”、而且是教学和验证导向的场景下经典Q学习反而更合理。Q表是一个完全透明的决策矩阵某个状态下最优动作是什么、Q值是多少可以直接打印出来看而DQN是个黑匣子你看到一个loss数字根本无法判断它是否学会了“咬尾”。PPO虽然训练稳定但要配神经网络、GAE、clip参数调试成本高出一大截。另一个关键点是训练效率。Q表更新是单步时序差分每次交互立刻回传不需要像DQN那样维护经验回放缓冲区。对状态空间只有几百到几千个格子的空战简化环境Q表通常几千个回合就能看到明确趋势如果换成神经网络同样的训练量大概率还在欠拟合。当你把Q表跑明白了再去切DQN你会很清楚哪些问题来自环境建模哪些问题来自网络结构而不是所有锅都甩给“神经网络玄学”。3. 最小可跑的Python实现从飞机运动模型到Q表更新全流程3.1 环境搭建与坐标约定先用NumPy写一个轻量二维对战环不要一上来就装物理引擎空战对抗验证阶段用NumPy手写运动学就够。下面这个环境模拟了二维平面上的追逐飞机用位置、航向、速度三个量描述转向通过最大角速度限制速度通过油门增减。代码里刻意省略了重力、惯性和延迟因为这些因素在当前阶段只会干扰对强化学习逻辑的理解。import numpy as np class AirCombatEnv: def __init__(self, dt0.5, max_turn0.5, max_speed300.0, min_speed80.0): self.dt dt self.max_turn max_turn self.max_speed max_speed self.min_speed min_speed self.my_pos None self.my_heading None self.my_speed None self.enemy_pos None self.enemy_heading None self.enemy_speed None self.reset() def reset(self): angle np.random.uniform(-np.pi, np.pi) dist np.random.uniform(2000, 6000) self.my_pos np.array([0.0, 0.0]) self.my_heading np.random.uniform(-np.pi, np.pi) self.my_speed 150.0 self.enemy_pos self.my_pos np.array([np.cos(angle), np.sin(angle)]) * dist self.enemy_heading np.random.uniform(-np.pi, np.pi) self.enemy_speed 150.0 return self._get_state() def _get_state(self): rel_vec self.enemy_pos - self.my_pos rel_dist np.linalg.norm(rel_vec) rel_angle np.arctan2(rel_vec[1], rel_vec[0]) - self.my_heading return rel_dist, rel_angle, self.my_speed - self.enemy_speed def step(self, my_action, enemy_action): self.my_heading self.max_turn * my_action self.my_heading np.arctan2(np.sin(self.my_heading), np.cos(self.my_heading)) self.my_pos np.array([np.cos(self.my_heading), np.sin(self.my_heading)]) * self.my_speed * self.dt # 敌机用固定策略偏向正对我方 desired np.arctan2(self.my_pos[1] - self.enemy_pos[1], self.my_pos[0] - self.enemy_pos[0]) diff np.arctan2(np.sin(desired - self.enemy_heading), np.cos(desired - self.enemy_heading)) self.enemy_heading 0.3 * np.clip(diff, -1.0, 1.0) self.enemy_pos np.array([np.cos(self.enemy_heading), np.sin(self.enemy_heading)]) * self.enemy_speed * self.dt return self._get_state()my_action这里直接用0、1、2、3、4表示左转、右转、平飞、加速、减速step内部把动作映射成航向角速度变化和油门变化。敌机策略故意设计成“总是朝我机方向转”它虽然不聪明但能逼着Q学习做出连续的规避或占位动作。状态返回的是相对距离、相对角度、速度差三个原始连续量下一节再把它变成Q表的离散索引。3.2 Q表初始化与epsilon-greedy动作选择Q表是一个形状为(state_bins, state_bins, state_bins, n_actions)的NumPy数组。训练开始前全部初始化为0意味着每个动作都没有先验优势。如果想让训练更稳也可以给所有初始值加一个极小的随机噪声比如np.random.rand(*shape) * 0.01这样可以避免一开始因Q值全等导致动作选择完全随机。STATE_BINS 8 N_ACTIONS 5 q_table np.zeros((STATE_BINS, STATE_BINS, STATE_BINS, N_ACTIONS)) epsilon 1.0 epsilon_min 0.05 epsilon_decay 0.9995 def choose_action(state_idx, epsilon): if np.random.rand() epsilon: return np.random.randint(N_ACTIONS) s np.ravel_multi_index(state_idx, (STATE_BINS, STATE_BINS, STATE_BINS)) flat_start s * N_ACTIONS q_flat q_table.reshape(-1) return int(np.argmax(q_flat[flat_start:flat_start N_ACTIONS]) )动作选择的逻辑是三行随机概率小于epsilon就乱飞否则老老实实按当前Q表取最大值。ravel_multi_index的作用是把三维状态索引压平成一维方便从Q表里切片。实际工程中注意不要每次都reshape整个Q表性能会差我只是在这里为了直观把多维索引压成一维然后切片取argmax。你也可以直接写q_table[state_idx]然后argmax效果一样。3.3 Q学习的核心更新时序差分公式怎么落地Q学习的更新公式只有一行核心逻辑def update_q_table(q_table, state_idx, action, reward, next_state_idx, done, alpha0.1, gamma0.9): sa_idx np.ravel_multi_index(state_idx (action,), q_table.shape) ns_idx np.ravel_multi_index(next_state_idx (np.argmax(q_table[next_state_idx]),), q_table.shape) target reward if done else reward gamma * q_table.flat[ns_idx] q_table.flat[sa_idx] alpha * (target - q_table.flat[sa_idx])这里的逻辑是用当前状态的Q值与“即时奖励加上下一状态的最大Q值”做差值再按alpha步长向目标靠近。特别注意更新时使用的target中下一状态的动作是argmax这就是Q学习和SARSA的关键差异——Q学习是off-policy它评估的是“如果下一步也按最优策略走”的价值。另一个细节是“计算target时要用更新前的Q值还是更新后的Q值”。在单步更新里我使用更新前的下一状态Q值时序差分本来就是这个定义如果你在同一回合里连续更新两个状态不要用刚刚更新过的值去算下一个target那会带来偏差。3.4 训练一整个回合把环境、策略、更新串起来训练主循环的结构非常简单重置环境、选动作、执行、拿奖励、更新Q表、进入下一状态。下面是完整的一段训练逻辑每个回合最多跑200步超时强制结束def train(episodes5000, max_steps200): env AirCombatEnv() global epsilon for ep in range(episodes): rel_dist, rel_angle, speed_diff env.reset() done False step 0 total_reward 0.0 prev_dist rel_dist while not done and step max_steps: rel_angle_norm (rel_angle np.pi) % (2 * np.pi) - np.pi state_idx discretize_state(rel_dist, rel_angle_norm, speed_diff) action choose_action(state_idx, epsilon) next_rel_dist, next_rel_angle, next_speed_diff env.step(action, enemy_action1) next_angle_norm (next_rel_angle np.pi) % (2 * np.pi) - np.pi next_state_idx discretize_state(next_rel_dist, next_angle_norm, next_speed_diff) reward compute_reward(prev_dist, next_rel_dist, next_angle_norm, locked_onFalse) if next_rel_dist 300.0: reward 1.0 done True update_q_table(q_table, state_idx, action, reward, next_state_idx, done) rel_dist, rel_angle, speed_diff next_rel_dist, next_rel_angle, next_speed_diff total_reward reward step 1 epsilon max(epsilon_min, epsilon * epsilon_decay) if ep % 500 0: print(fepisode {ep}, total_reward {total_reward:.2f}, epsilon {epsilon:.3f})这段代码把前面几块的逻辑全部粘在一起discretize_state负责把连续量变成索引choose_action负责探索与利用的权衡update_q_table负责学习。enemy_action1表示敌机策略固定朝我方转向距离小于300视为近距相遇给一个击落等价奖励并结束回合。打印的训练曲线用于观察reward是否随训练上升若一直没变化优先检查状态索引有没有越界或者奖励函数是不是恒为0。4. 让Q表跑出咬尾动作的四个参数alpha、gamma、epsilon和网格分辨率4.1 学习率alpha调大了抖动调小了便秘学习率是每个更新步对目标的信任程度。alpha0.1是比较稳妥的起点意味着每次更新只向目标移动10%alpha0.5时学得快但Q值会来回振荡尤其是在奖励函数有噪声的空战环境里。判断alpha是否过大的一个直观信号是训练后期奖励曲线不看趋势光看震荡幅度并且同一个固定策略下连续两个回合的Q表表现差别极大。调alpha有一个工程小技巧不用固定值而是把alpha随训练步数从0.5线性衰减到0.05。前期大学习率快速覆盖状态空间后期小学习率稳定收敛。你可以在训练主循环里按回合数更新alpha或者用一个十分简单的alpha max(0.05, 0.5 * (1 - ep / total_episodes))。需要注意的是alpha和epsilon的衰减速度要错开如果epsilon降太快、alpha还很大Q表会在探索结束时剧烈改动策略导致“看起来收敛实则震荡”。4.2 折扣因子gamma看得远还是看得近gamma决定了agent多大程度上重视未来奖励。空战对抗里一个常见失误是gamma设太低比如gamma0.7导致agent完全只盯着“下一秒能不能靠近”而放弃了绕到敌机尾巴这种需要绕一个大圈才能获得高额奖励的机动。我自己常用的取值是0.9起步如果发现agent动作短视就往上加到0.95或0.99。但gamma不是越大越好。gamma接近1时Q值会趋向于累积总奖励的期望而空战环境每回合长度不一、奖励塑形信号有正有负很容易出现Q值绝对值膨胀使得Q表对奖励函数中的常数偏移特别敏感。判断gamma是否合适的办法是看训练后期不同状态的Q值分布如果所有状态Q值都很大、几乎不分胜负那多半是gamma太高加上奖励塑形信号叠加太多可以试着把即时奖励缩小或者降低gamma。4.3 epsilon探索率从初期的乱飞到最后的高冷空战环境里探索尤其重要因为“咬尾”是一条需要多步连续动作才能走通的轨迹如果探索太少agent只能学到“直线冲向敌人”这种局部最优。初始epsilon1.0然后每回合衰减通常3000到5000个回合后衰减到0.05。衰减系数0.9995意味着每回合只剩原来的99.95%跑5000回合后大约0.08这个节奏和上面代码匹配。探索率最大的坑是衰减和训练长度不匹配。如果你总回合数是1000用0.9995这个衰减训练结束时epsilon还在0.6左右Q表根本没有充分利用如果回合数到20000epsilon已经撞到0.05下限很久后半段完全靠当前策略对意外情况的适应力就弱了。工程里可以加一个简单判断如果训练中后期固定策略对战的胜率不再上升而epsilon还在下降多半是探索已经耗尽。我自己的习惯是把epsilon衰减系数做成可输入参数每次训练完记录最终epsilon避免“因为跑太久导致探索提前停止”。4.4 状态离散化分辨率网格不是越细越好空战的Q学习和扫地机器人最大的不同在于状态空间的高维性非常致命。把距离、角度、速度差都切成8格状态总数512如果贪心一点距离20格、角度16格、速度差8格就变成2560个状态乘上5个动作Q表仍然不算大但每个状态被访问的次数会显著下降。问题的本质是“表格型方法的样本效率”Q表中每个格子的Q值都必须经过多次访问才能收敛。网格越细状态被重复命中的次数越少训练回合数必须同比例增加。更合理的做法是先用粗网格跑通全流程观察哪些状态格子被频繁访问再对热点区域做细分比如距离近(1500米)时距离格子加密距离远时保持粗粒度。在QLearning空战里这比一开始就用高分辨率网格要实用得多。下面是对照表能直接看出分辨率和训练成本的权衡关系参数项粗网格细网格状态维度距离8格/角度8格/速度差8格距离16格/角度12格/速度差8格Q表大小2560个Q值7680个Q值建议回合数3000到500015000以上对咬尾策略的刻画能学到大致占位能学到更精细的切入角风险动作切换粗糙边界处抖动样本不足训练缓慢甚至不收敛这张表里的回合数是经验值不是理论定值。细网格不是不能做而是你必须同步增加探索回合并把epsilon衰减拉长。如果你的训练时间预算不够宁可接受粗网格的次优策略先验证整体博弈逻辑是否正确。5. 空战Q学习训练最常见的5个坑现象、原因与排查路径5.1 坑一Q值收敛了但打不过固定的追踪敌机现象训练奖励曲线稳步上升但拿训练好的Q表去和固定策略敌机打对抗胜率反而不到三成。原因奖励函数里“接近奖励”占了大头agent学会了无限逼近敌人却牺牲了角度优势它每次都正面冲向敌机容易被对方咬住。解决调整奖励权重把“进入敌机后向区域”的奖励从0.3提高到接近奖励的两倍并给正面迎头接近时增加负奖励逼迫它绕行。进一步排查时我习惯把某个episode的轨迹打印出来看看每一帧的相对角度和距离。如果相对角度始终在±30度以内说明agent就是直线冲锋如果角度变化有明显的绕着转的趋势说明筋位在改善。这个坑属于设计题不是bug别急着调学习方法率。5.2 坑二epsilon已经降到0.05动作仍然随机乱跳现象训练进入后期epsilon可能已经触底到0.05但每次用相同状态去查Q表动作却是不一致的。原因Q表里多个动作的Q值完全相同或非常接近argmax在面对几乎相等的值时会受浮点噪声影响来回选中不同动作。这种等值竞争在Q表初始化为全0时特别容易出现因为没被访问到的状态永远是0所有动作都一样“最优”。解决给Q表初始化加入一个极小的随机扰动例如np.random.randn(*q_table.shape) * 1e-4让argmax有明确的选择依据另一个做法是在决策时加入“平局打破”逻辑如果最大值和次大值差距小于0.01就从这两个动作里随机选一个。前者实现简单后者更符合实际博弈中的不确定性但会引入额外参数。5.3 坑三状态维度太多Q表内存和训练时间一起爆炸现象想增加一个高度状态状态从三维变成四维Q表数量直接从几千跳到几万甚至几十万训练1万回合仍然很多格子没被访问。原因离散网格维度是乘法关系每增加一维格子数量指数级增长。解决放弃绝对高度改用一个相对高度差“敌机高度减我机高度”并只分成5~7格同时检查状态间相关性比如速度和高度如果都影响“能量状态”可以合并成一个“能量差”维度。这一步本质上是在做特征工程Q学习没法帮你自动筛选特征。5.4 坑四训练曲线先升后崩前期看起来要成了后期越打越差现象前2000回合总奖励在上涨5000回合后反而持续下滑或者胜率突然跳水。原因最常见的是敌机策略固定不变Q表慢慢记住了固定敌机的具体走位形成过拟合当训练数据中某一类初始位置占多数时agent只对那一片区域有效。解决在训练中让敌机也有一定的随机策略比如每回合以20%概率让其使用另一个转向策略增加状态覆盖范围。另一种原因是epsilon衰减过慢后期仍在大量探索刚学到的策略被乱飞动作反复打断检查epsilon的最终值如果训练到5000回合还在0.2以上就要加快衰减。5.5 坑五奖励一直为正但复盘时看不到任何战术动作现象平均回合奖励是正数可把轨迹打点画出来飞机只是在原地画圈或者往复抖动。原因reward shaping给了“接近奖励”和“角度奖励”但关闭了时间惩罚agent发现抖动能频繁改变角度刷分。解决给每一步加一个小的负时间成本比如-0.01同时检查“进入尾巴区域”的奖励判定是不是只要角度差小于30度就发实际上还应该加一个前置条件敌机处于我机前方一定距离范围内才算有效占位否则绕圈刷角度没有意义。这种bug靠肉眼盯reward曲线看不出来要把动作序列打印出来逐帧看。6. 往工程走一步SARSA、Double Q学习对照验证与胜率评估Q学习跑通只是开了头真正让人信服的是你证明这个策略不是“死记硬背”。我的习惯是同时实现SARSA和Double Q-Learning两个变体固定同一套环境、同一套奖励函数、同一个初始随机种子做三组对照。SARSA和Q学习的差异在更新的target上SARSA用下一步实际选择的动作去计算Q值而不是用下一步的最大Q值。修改量非常小核心更新逻辑如下def update_sarsa(q_table, state_idx, action, reward, next_state_idx, next_action, done, alpha0.1, gamma0.9): sa_idx np.ravel_multi_index(state_idx (action,), q_table.shape) ns_idx np.ravel_multi_index(next_state_idx (next_action,), q_table.shape) target reward if done else reward gamma * q_table.flat[ns_idx] q_table.flat[sa_idx] alpha * (target - q_table.flat[sa_idx])Double Q-Learning则是用两套Q表交替更新从而消除单表更新里“最大值偏差”带来的乐观估计。对空战这种动作收益差别不大、奖励噪声不小的环境Double Q通常比普通Q学习更稳def update_double_q(q_a, q_b, state_idx, action, reward, next_state_idx, done, alpha0.1, gamma0.9): if np.random.rand() 0.5: best_action np.argmax(q_a[next_state_idx]) target reward gamma * q_b[next_state_idx][best_action] q_a[state_idx (action,)] alpha * (target - q_a[state_idx (action,)]) else: best_action np.argmax(q_b[next_state_idx]) target reward gamma * q_a[next_state_idx][best_action] q_b[state_idx (action,)] alpha * (target - q_b[state_idx (action,)])验证方法上每次训练完我会固定Q表跑1000个测试回合统计胜率、平均被击落时间、平均回合奖励三个指标。为了让结果可对比测试回合的初始位置要覆盖不同距离和方位角的组合而不是沿用训练时的随机分布。胜率表的参考意义比训练曲线更大Q学习如果胜率不到五成先保留粗网格方案SARSA胜率更高说明环境本身有探索需求Double Q效果更平说明原算法有方差问题。关于训练可视化我强烈建议画两条曲线一条是几个不同epsilon下的reward曲线一条是Q表的“最大Q值均值”曲线。前者看策略好坏后者看是否收敛。如果最大Q值均值还在持续上涨说明算法没有稳定。画出这个图不需要TensorBoardmatplotlib就可以import matplotlib.pyplot as plt plt.plot(reward_history, labelepisode reward) plt.plot(q_max_history, labelavg max Q) plt.xlabel(episode) plt.legend() plt.title(Q-Learning air combat training curve) plt.show()我的真实教训是第一次跑这个空战demo时我把epsilon衰减设成了0.9955000回合结束时还在0.9附近训练曲线漂亮得像说明书但一测胜率惨不忍睹。后来把衰减改到0.9995胜率才上来。从那以后我养成了在训练结束时print所有超参数最终epsilon的习惯方便复盘。强化学习空战对抗的代码不难难的是知道哪里需要抠细节希望这篇里的踩坑记录能帮你少走几段弯路也希望你把Q表、SARSA和Double Q都跑一遍之后再下结论。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站