1. 从像素到方向盘为什么用DQN啃下赛车游戏这块硬骨头很多人第一次接触深度强化学习都是从打砖块、走迷宫这类离散动作空间的小游戏入手的。动作就那几个状态也简单训练几百轮就能看到智能体学会“往左往右”。但一旦把场景换成赛车游戏事情就完全不一样了——方向盘转角是连续的油门和刹车是连续的速度、位置、朝向角全是连续变量状态空间大到离谱。这时候如果还想着用表格法或者简单的Q-learning去硬解基本等于拿算盘去跑深度学习。我当初选这个Capstone题目就是冲着这个“连续控制”的难点去的。DQN本身是为离散动作空间设计的直接拿来做赛车游戏天然就有一层矛盾要么把连续动作离散化要么换算法。但Capstone的要求是用DQN所以核心工作就变成了——怎么在DQN的框架下把赛车游戏这个连续控制问题“翻译”成DQN能吃的形式同时还得让智能体真的学会开车而不是原地打转或者一头撞墙。这个项目适合谁呢如果你已经学过Q-learning、知道神经网络的基本训练流程、能看懂PyTorch或TensorFlow的简单代码那这个项目就是你的进阶跳板。它不会教你什么是卷积也不会从头讲梯度下降但它会逼着你面对真实项目里最头疼的问题奖励怎么设计、状态怎么表示、训练不稳定怎么办、超参数怎么调。这些东西看论文是看不出来的只有自己跑过、崩过、调过才能真正理解。我做的这个赛车项目环境用的是经典的2D俯视视角赛道车辆有位置、速度、朝向角三个核心状态量动作空间我最终选了5个离散动作左转、右转、直行、加速、刹车。注意这里我把转向和加减速拆成了独立动作而不是组合动作原因后面会详细说。状态表示上我没有直接用原始像素而是用了赛道传感器射线加车辆自身状态向量的混合表示。这个选择直接决定了训练能不能收敛也是我踩坑最多的地方。整篇文章我会按照我实际做项目的顺序来展开先讲清楚DQN在赛车场景下的核心矛盾再讲状态和动作空间怎么设计然后是网络结构和训练流程接着是奖励函数这个“玄学”部分最后是我踩过的坑和调参经验。每一部分我都会给出具体的代码片段和参数你可以直接拿去改改就用。2. 状态表示与动作空间DQN在赛车场景下的第一道坎2.1 为什么不能直接用原始像素很多人一上来就想复刻DQN打Atari的那套做法把游戏画面缩到84x84堆4帧直接喂给卷积网络。我一开始也是这么干的结果训练了整整两天智能体连直线都跑不稳。问题出在哪儿Atari游戏的画面里物体边界清晰、背景简单卷积网络很容易提取出“球在哪里”“挡板在哪里”这种空间特征。但赛车游戏的俯视画面里赛道是灰的、车是小的、背景还有各种纹理卷积网络要花大量容量去学“什么是赛道边缘”而不是“怎么开车”。更致命的是原始像素里不包含速度信息。你看到两帧画面知道车在哪儿但不知道车跑多快。DQN的输入是单帧或堆叠帧堆叠帧虽然能隐式提供一点运动信息但对于赛车这种对速度极其敏感的任务来说远远不够。我试过堆8帧训练速度直接慢到无法接受而且效果提升微乎其微。所以我的结论很明确对于2D赛车游戏用传感器射线加状态向量比用原始像素靠谱得多。这不是偷懒而是把领域知识注入到状态表示里让网络把容量花在决策上而不是花在理解画面上。2.2 传感器射线的设计与实现我最终用的状态表示是一个混合向量包含两部分车辆自身状态和赛道传感器读数。车辆自身状态有5个量横向位置、纵向位置、速度大小、速度方向与车头朝向的夹角、朝向角的正弦和余弦。注意朝向角我用的是sin和cos而不是角度值原因是角度在0度和360度附近有跳变直接输入网络会让网络学得很痛苦。用sin和cos表示就变成了连续平滑的二维向量网络处理起来舒服得多。传感器射线我设置了9条从车头开始均匀分布在车头前方180度范围内每条射线检测到赛道边界的距离归一化到0到1之间。9条射线加上5个自身状态一共14维状态向量。这个维度不算高全连接网络就能处理训练速度也快。import numpy as np def get_state(car, track): # 车辆自身状态 speed car.speed / car.max_speed # 归一化 angle_diff normalize_angle(car.velocity_angle - car.heading) sin_heading np.sin(car.heading) cos_heading np.cos(car.heading) # 传感器射线 ray_angles np.linspace(-np.pi/2, np.pi/2, 9) ray_distances [] for angle in ray_angles: dist cast_ray(car.position, car.heading angle, track) ray_distances.append(dist / track.max_ray_distance) state np.array([ car.x / track.width, car.y / track.height, speed, angle_diff, sin_heading, cos_heading, *ray_distances ], dtypenp.float32) return state这里有个细节射线检测的距离要归一化。如果不归一化距离值可能从0到几百和前面那些0到1的状态量混在一起网络训练时梯度会被大数值主导小数值的特征就学不到了。归一化之后所有输入都在同一量级训练稳定得多。2.3 动作空间离散化的取舍DQN只能处理离散动作这是它的硬伤。赛车游戏里方向盘转角是连续的油门开度也是连续的。怎么离散化直接决定了智能体能不能学会精细操控。我试过三种方案第一种是粗粒度离散左转、右转、直行、加速、刹车5个动作。这是最简单的方案训练快但智能体开起来很“愣”转向是一格一格的没法走平滑的 racing line。第二种是细粒度离散把转向分成7个角度油门分成3个档位组合起来21个动作。动作精细了但动作空间大了4倍多DQN的探索效率急剧下降训练时间翻了好几倍而且很多动作组合在特定状态下是冗余的。第三种是混合方案转向和加减速分开但转向用3个角度左、直、右加减速用3个档位加速、滑行、刹车组合成9个动作。这个方案在精细度和训练效率之间取得了平衡。我最终选了第三种。9个动作网络输出层9个节点训练速度可以接受智能体也能做出比较平滑的过弯动作。这里的关键经验是动作空间不是越大越好DQN的探索效率随动作空间增大而指数下降。在Capstone这种有限时间内要出结果的项目里宁可动作粗一点也要保证能训出来。注意动作离散化之后一定要检查每个动作在物理上是否合理。比如“左转加速”和“左转刹车”在高速下可能导致车辆失控如果环境物理引擎不支持这种组合就要在动作设计时排除掉否则智能体会学到一些莫名其妙的策略。3. 网络结构与训练流程把DQN跑起来只是第一步3.1 全连接网络的层数与宽度选择状态是14维向量动作是9个这个规模用全连接网络就够了不需要上卷积。我一开始用了两层隐藏层每层128个神经元ReLU激活。训练下来发现收敛速度还可以但最终策略不够稳定有时候能跑完整圈有时候跑一半就撞墙。后来我把网络加宽到256个神经元层数加到三层情况有所改善但提升不明显。真正带来质变的是加了Dropout和Layer Normalization。Dropout放在每层全连接之后概率0.1防止过拟合Layer Normalization放在激活函数之前稳定训练过程。这两个改动让训练曲线平滑了很多最终策略的方差也小了。import torch import torch.nn as nn class DQN(nn.Module): def __init__(self, state_dim14, action_dim9): super(DQN, self).__init__() self.net nn.Sequential( nn.Linear(state_dim, 256), nn.LayerNorm(256), nn.ReLU(), nn.Dropout(0.1), nn.Linear(256, 256), nn.LayerNorm(256), nn.ReLU(), nn.Dropout(0.1), nn.Linear(256, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, action_dim) ) def forward(self, x): return self.net(x)这里有个坑Dropout在训练时开启推理时关闭。如果你忘了在推理前调用model.eval()Dropout会继续随机丢弃神经元导致策略随机性很大车开得时好时坏。我当初就被这个坑了两天一直以为是超参数问题后来才发现是忘了切换模式。3.2 经验回放池的大小与采样策略经验回放是DQN的核心组件之一它打破了样本之间的时间相关性让训练更稳定。但回放池的大小很有讲究。太小了比如只存1000条经验样本多样性不够网络容易过拟合到最近的经验上训练会震荡。太大了比如存100万条内存吃不消而且旧经验可能已经过时了采样到旧经验反而拖累训练。我最终用的是10万条经验的回放池。这个大小在16GB内存的机器上完全没问题采样时随机抽64条作为一个batch。采样策略我用的是均匀随机采样没有用优先经验回放。原因很简单优先经验回放虽然理论上更高效但实现复杂而且需要维护每个经验的TD误差计算开销大。在Capstone项目里均匀采样已经足够让智能体学会开车了。from collections import deque import random class ReplayBuffer: def __init__(self, capacity100000): self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size64): batch random.sample(self.buffer, batch_size) states, actions, rewards, next_states, dones zip(*batch) return ( np.array(states, dtypenp.float32), np.array(actions, dtypenp.int64), np.array(rewards, dtypenp.float32), np.array(next_states, dtypenp.float32), np.array(dones, dtypenp.float32) ) def __len__(self): return len(self.buffer)回放池的预热也很重要。我一开始就让智能体边跑边训练结果前几千步网络完全是在拟合随机噪声。后来改成先跑5000步纯随机动作把回放池填到一定规模再开始训练效果好了很多。这个预热阶段相当于给网络提供了一批“初始教材”让它先对状态-动作空间有个大致了解。3.3 目标网络的更新频率DQN有两个网络在线网络和目标网络。在线网络每步都更新目标网络定期从在线网络复制参数。目标网络的作用是稳定TD目标防止训练震荡。更新频率是个关键超参数。我试过每100步更新一次训练很稳定但收敛慢试过每10步更新一次收敛快但训练曲线抖动大。最终我选了每200步更新一次配合学习率1e-4训练曲线比较平滑最终策略也稳定。这里有个经验目标网络更新频率和学习率要配合调整。学习率大目标网络就要更新慢一点否则TD目标变化太快网络追不上学习率小目标网络可以更新快一点加速收敛。我一般先固定学习率然后调更新频率找到训练曲线最平滑的那个点。def update_target_network(online_net, target_net, tau1.0): for target_param, online_param in zip(target_net.parameters(), online_net.parameters()): target_param.data.copy_(tau * online_param.data (1 - tau) * target_param.data)我用的不是硬更新而是软更新tau0.005。软更新每步都做但每次只把目标网络往在线网络挪一小步。这样目标网络的变化更平滑训练更稳定。硬更新虽然简单但每次更新都会造成TD目标的突变容易引起训练震荡。4. 奖励函数设计决定智能体是赛车手还是碰碰车4.1 稀疏奖励的致命问题如果只给智能体一个“到达终点1撞墙-1其他时候0”的稀疏奖励会发生什么答案是智能体永远学不会开车。因为在随机探索阶段它几乎不可能碰巧跑完整圈大部分时候都是撞墙或者原地打转拿到的奖励全是-1或0。网络从这些经验里学不到任何有用的东西因为所有动作的Q值都差不多。我一开始就犯了这个错误训练了5000轮智能体还是只会原地转圈。后来我意识到稀疏奖励在赛车这种长序列任务里基本不可行必须设计稠密奖励让智能体每时每刻都能得到反馈。4.2 稠密奖励的组成与权重我的稠密奖励由四部分组成第一部分是速度奖励。每步给智能体一个正比于速度的奖励鼓励它跑快。但这里有个陷阱如果只奖励速度智能体学会的就是直线加速然后撞墙因为它不知道撞墙的代价。所以速度奖励必须和其他奖励配合。第二部分是赛道中心奖励。每步给智能体一个负比于偏离赛道中心距离的惩罚鼓励它沿着赛道中间跑。这个奖励让智能体学会了沿着赛道走而不是到处乱窜。第三部分是朝向奖励。每步给智能体一个正比于车头朝向与赛道切线方向一致性的奖励鼓励它朝着正确的方向开。这个奖励让智能体学会了过弯时调整车头方向。第四部分是撞墙惩罚。撞墙时给一个大的负奖励并结束当前回合。这个惩罚让智能体学会了避障。def compute_reward(car, track, prev_progress): # 速度奖励 speed_reward car.speed / car.max_speed * 0.1 # 赛道中心奖励 center_dist distance_to_center(car.position, track) center_reward -center_dist / track.width * 0.2 # 朝向奖励 track_angle get_track_angle(car.position, track) heading_diff abs(normalize_angle(car.heading - track_angle)) heading_reward (1 - heading_diff / np.pi) * 0.1 # 进度奖励 progress get_progress(car.position, track) progress_reward (progress - prev_progress) * 1.0 # 撞墙惩罚 if car.collision: return -10.0, True total_reward speed_reward center_reward heading_reward progress_reward return total_reward, False注意进度奖励这一项。我加了“当前进度减去上一步进度”的奖励鼓励智能体不断向前推进。这个奖励很关键因为它给了智能体一个全局的目标感让它知道“往前开是对的”。没有这个奖励智能体可能会学会在原地高速转圈因为速度奖励和朝向奖励都能拿到但进度为零。4.3 奖励权重的调参经验奖励权重的调整是整个项目里最耗时间的部分。我前后调了十几版才找到一组比较平衡的权重。速度奖励权重0.1不能太大否则智能体只顾加速不顾方向。中心奖励权重0.2让智能体有动力回到赛道中间。朝向奖励权重0.1辅助过弯。进度奖励权重1.0这是主导奖励让智能体有全局目标。撞墙惩罚-10足够大让智能体把撞墙当成严重错误。这里有个经验奖励权重的比例比绝对值更重要。你把所有奖励都乘以10训练效果基本不变因为网络学的是相对大小。但如果你把速度奖励权重从0.1调到0.5智能体的行为就会明显偏向加速过弯质量下降。所以调参时先固定一组比例然后微调不要同时改多个权重。注意奖励函数设计完后一定要可视化智能体的行为。我当初调了一组权重训练曲线很好看奖励一直在涨但看回放发现智能体学会了“贴墙跑”——紧贴着赛道边缘高速行驶因为这样速度奖励高中心惩罚也不算太大。后来我把中心惩罚权重加大才把这个行为纠正过来。训练曲线好看不代表策略合理一定要看实际行为。5. 训练不稳定与过估计DQN在赛车任务里的典型病症5.1 训练震荡的排查链路训练不稳定是DQN的常见问题但在赛车任务里尤其严重。我遇到的现象是训练曲线在某个阶段突然崩掉奖励从正变负然后再也涨不回来。排查过程是这样的先看回放发现智能体在崩掉之后学会了“原地转圈”一直转直到回合结束。然后看Q值发现所有动作的Q值都变得很大而且彼此之间差异很小。这说明网络过估计了而且过估计得很均匀导致智能体无法区分好坏动作。根因是TD目标里的max操作。DQN的TD目标是r gamma * max(Q_target(s, a))这个max操作会系统性地高估Q值。在赛车任务里因为状态空间大、奖励稠密过估计问题比Atari游戏更严重。5.2 Double DQN的引入与效果对比解决过估计的标准方案是Double DQN。它的核心改动很小用在线网络选动作用目标网络评估动作值。这样就把“选择”和“评估”解耦了过估计大大减轻。def compute_td_target(rewards, next_states, dones, online_net, target_net, gamma0.99): with torch.no_grad(): # 在线网络选动作 next_actions online_net(next_states).argmax(dim1, keepdimTrue) # 目标网络评估动作值 next_q_values target_net(next_states).gather(1, next_actions).squeeze(1) targets rewards gamma * next_q_values * (1 - dones) return targets改成Double DQN之后训练曲线明显稳定了崩掉的次数少了很多。但也不是完全解决偶尔还是会震荡。后来我又加了梯度裁剪把梯度范数限制在10以内训练才彻底稳定下来。5.3 梯度裁剪与学习率调度梯度裁剪是训练深度强化学习模型的标配。赛车任务里奖励的方差比较大偶尔会出现很大的TD误差导致梯度爆炸。梯度裁剪把梯度范数限制在一个范围内防止参数更新过猛。optimizer.zero_grad() loss nn.MSELoss()(q_values, targets) loss.backward() nn.utils.clip_grad_norm_(online_net.parameters(), max_norm10.0) optimizer.step()学习率调度我也加了。一开始用1e-3训练快但震荡大后来改成1e-4稳定但收敛慢。最终我用的是余弦退火调度从1e-3开始逐渐降到1e-5。这样前期收敛快后期稳定。scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max10000, eta_min1e-5)这套组合拳打下来训练曲线终于变得比较平滑了。智能体从随机乱撞到能跑完整圈大概用了8000个回合。这个数字不算快但考虑到赛车任务的难度我觉得可以接受。6. 从训练到部署模型保存、推理优化与效果评估6.1 模型保存与加载的坑模型保存看起来简单但有几个坑。第一保存时要同时保存网络参数和优化器状态否则恢复训练时优化器的动量信息丢失训练会有一个明显的性能下降。第二保存时要记录当前的训练回合数和探索率否则恢复训练时探索率从头开始会破坏已经学到的策略。def save_checkpoint(online_net, target_net, optimizer, episode, epsilon, path): torch.save({ online_net: online_net.state_dict(), target_net: target_net.state_dict(), optimizer: optimizer.state_dict(), episode: episode, epsilon: epsilon }, path) def load_checkpoint(path, online_net, target_net, optimizer): checkpoint torch.load(path) online_net.load_state_dict(checkpoint[online_net]) target_net.load_state_dict(checkpoint[target_net]) optimizer.load_state_dict(checkpoint[optimizer]) return checkpoint[episode], checkpoint[epsilon]6.2 推理阶段的性能优化训练完之后推理阶段不需要计算梯度可以用torch.no_grad()包裹速度能快不少。另外推理时要把网络设为eval模式关闭Dropout和LayerNorm的训练行为。online_net.eval() with torch.no_grad(): state_tensor torch.FloatTensor(state).unsqueeze(0) q_values online_net(state_tensor) action q_values.argmax(dim1).item()如果要在CPU上跑推理还可以把模型转成ONNX格式用ONNX Runtime加速。我实测下来ONNX Runtime比原生PyTorch在CPU上快大概30%对于实时性要求高的场景很有用。6.3 效果评估的指标与方法评估强化学习模型不能只看训练奖励因为训练奖励是在探索策略下得到的不代表最终策略的性能。我用了三个指标第一个是平均完赛率。跑100个回合看有多少回合能跑完整圈。我的模型最终完赛率是85%左右剩下15%大部分是起步阶段撞墙。第二个是平均完赛时间。跑完整圈的回合里平均用了多少时间。这个指标反映的是策略的精细程度时间越短说明 racing line 走得越好。第三个是撞墙率。每100步里撞墙多少次。这个指标反映的是策略的安全性。def evaluate(model, env, num_episodes100): model.eval() completion_count 0 total_time 0 collision_count 0 total_steps 0 for _ in range(num_episodes): state env.reset() done False steps 0 while not done: action model.select_action(state, epsilon0.0) state, reward, done, info env.step(action) steps 1 if info[collision]: collision_count 1 total_steps steps if info[completed]: completion_count 1 total_time steps completion_rate completion_count / num_episodes avg_time total_time / max(completion_count, 1) collision_rate collision_count / total_steps return completion_rate, avg_time, collision_rate评估的时候要注意一定要用固定的随机种子否则每次评估结果波动很大没法比较不同模型的好坏。我一般跑3次评估取平均值。7. 踩坑实录与调参心得7.1 状态归一化没做好训练直接不收敛这是我踩的第一个大坑。一开始我觉得状态量都是物理量直接输入网络就行了。结果训练了几千轮损失函数一直在震荡Q值要么全正要么全负智能体完全学不到东西。后来我把所有状态量都归一化到0到1之间训练立刻就正常了。原因很简单神经网络对输入尺度很敏感如果输入量有的在0到1有的在0到1000网络的第一层权重会被大数值主导小数值的特征就学不到了。归一化之后所有输入在同一量级网络才能均衡地学习每个特征。注意归一化要用训练集的统计量不能用测试集的。但在强化学习里状态分布是随策略变化的所以最好用运行均值来动态归一化。我用的是一种简化方案根据环境的物理边界做固定归一化比如位置除以赛道宽度速度除以最大速度。这样简单可靠不需要维护运行统计量。7.2 探索率衰减太快智能体陷入局部最优探索率epsilon控制智能体随机探索的概率。我一开始设的是从1.0线性衰减到0.1用了5000步。结果智能体很快就学会了“直行加速”这个局部最优策略因为直行能拿到速度奖励而且不会撞墙赛道是直的。但它永远学不会过弯因为过弯需要先减速再转向这个动作序列在随机探索阶段很难碰巧发现。后来我把衰减周期拉长到50000步并且把最终epsilon从0.1降到0.01。这样智能体有足够的时间去探索各种动作组合最终学会了过弯。这里的关键经验是探索率衰减周期要和任务难度匹配。任务越难需要的探索时间越长。赛车任务里过弯是一个复杂的动作序列需要大量探索才能发现所以衰减周期不能太短。7.3 回放池预热不足前期训练全是噪声前面提到过我一开始让智能体边跑边训练结果前几千步网络完全在拟合随机噪声。后来改成先跑5000步纯随机动作把回放池填到一定规模再开始训练效果好了很多。这个预热阶段相当于给网络提供了一批“初始教材”。如果回放池里只有几百条经验而且都是随机动作产生的网络从这些经验里学不到任何有用的模式。预热到5000条之后回放池里有了一定的多样性网络才能开始学到一些基本规律。7.4 奖励函数调参的“玄学”与科学奖励函数调参是整个项目里最像“玄学”的部分。我前后调了十几版才找到一组比较平衡的权重。但后来我总结出了一些科学的方法第一先固定一组初始权重跑一次训练看回放。如果智能体学会了“贴墙跑”说明中心惩罚太小如果智能体学会了“原地转圈”说明进度奖励太小如果智能体学会了“直线加速然后撞墙”说明撞墙惩罚太小。第二每次只调一个权重其他保持不变。同时调多个权重你根本不知道是哪个起了作用。第三用评估指标来指导调参不要只看训练奖励。训练奖励高不代表策略好要看完赛率、完赛时间、撞墙率这些客观指标。第四奖励权重的比例比绝对值更重要。你把所有奖励都乘以10训练效果基本不变。但如果你把速度奖励权重从0.1调到0.5智能体的行为就会明显偏向加速。7.5 训练时间与硬件配置的平衡赛车游戏的训练时间比Atari游戏长得多因为状态空间大、奖励稠密、动作序列长。我在一台配置了中端GPU的机器上训练跑完8000个回合大概用了6个小时。如果只用CPU时间要翻好几倍。这里有个经验如果你的机器配置有限可以适当减小网络规模、减小回放池大小、减少传感器射线数量。这些都会降低训练时间但也会影响最终性能。我建议先在小规模配置上跑通流程确认代码没问题再放大配置跑完整训练。另外训练过程中要定期保存模型。我一般每500个回合保存一次防止训练崩掉之后从头再来。保存的时候要同时保存优化器状态和探索率方便恢复训练。8. 这个项目还能怎么玩扩展方向与进阶思路8.1 从DQN到连续控制算法DQN的离散动作空间是它的硬伤。如果你想让智能体做出更平滑的操控可以换成DDPG、TD3或SAC这些连续控制算法。这些算法直接输出连续的动作值不需要离散化过弯会更平滑racing line 会更接近人类车手。但换算法不是没有代价的。连续控制算法的训练通常比DQN更不稳定超参数更敏感调试时间更长。如果你时间有限DQN加离散动作已经能跑出不错的效果了。8.2 多传感器融合与视觉输入我用的传感器射线加状态向量的方案优点是训练快、可解释性强。但它的缺点是信息量有限智能体看不到赛道全局只能看到局部。如果你想进一步提升性能可以加入视觉输入用卷积网络提取赛道特征和传感器特征融合。这个方案我在后期尝试过效果有提升但训练时间翻了三倍。而且卷积网络的调参比全连接网络复杂得多需要更多的耐心。如果你要做这个扩展建议先把传感器方案跑通再逐步加入视觉输入。8.3 课程学习与迁移学习课程学习是一种很实用的技巧先让智能体在简单赛道上训练学会基本操控再迁移到复杂赛道上。这样可以大大缩短训练时间因为智能体不需要从零开始探索。我试过在直道赛道上预训练然后迁移到弯道赛道。预训练用了2000个回合迁移后只需要3000个回合就能跑完整圈比直接从零训练快了将近一半。迁移的时候网络的前几层可以冻结只训练后面的层这样能保留预训练学到的底层特征。8.4 多智能体竞速与对抗如果你想让项目更有趣可以加入多个智能体让它们在同一条赛道上竞速。这时候奖励函数要加入相对位置的考量比如“超过前车奖励被后车超过-惩罚”。多智能体训练比单智能体复杂得多因为环境是非平稳的每个智能体的策略都在变化。但这也是一个很有挑战性的扩展方向适合对多智能体强化学习感兴趣的人。我个人在实际操作中的体会是这个Capstone项目最大的价值不在于最终模型跑得多好而在于你亲手经历了深度强化学习从环境搭建、状态设计、奖励工程到训练调参的完整流程。这些经验是看论文和跑Demo学不到的。如果你也在做类似的项目我的建议是先把最简单的版本跑通哪怕性能很差然后再逐步优化。不要一上来就追求完美那样很容易卡在某个细节上最后什么都做不出来。