
简介本资源为自动驾驶端到端行为决策方向的学术论文文档面向从事自动驾驶决策算法研究的研究生、算法工程师及强化学习爱好者聚焦复杂环境下驾驶策略学习效率低、动作平滑性差等痛点。文档围绕融合离散动作的双延迟深度确定性策略梯度算法TD3WD展开系统梳理模仿学习与强化学习两条技术路线的利弊并针对探索效率、Critic网络初始化、连续控制动作抖动等问题提出改进思路。资源包内含1个docx文件约290KB完整呈现引言、问题描述、算法设计及Carla仿真实验等章节便于读者理解马尔可夫决策过程建模、Actor-Critic结构及双Critic网络评估机制。目前已有94人学习适合希望深入端到端自动驾驶决策、借鉴算法改进方案与实验设计思路的读者参考。1. 从连续控制到离散动作TD3WD 要解决的真实问题多数做自动驾驶行为决策的团队第一版都会掉进同一个坑把油门、刹车、转向当成连续量直接上 DDPG 或 TD3仿真里跑得挺顺一上车就发现执行器根本不接受连续指令。真实底盘收到的是档位、转向灯、目标车道这类离散量连续策略输出还得再套一层映射误差和抖动全堆在这一层。TD3WD 这个思路的价值就在这它不回避离散动作而是把离散动作融合进双延迟深度确定性策略梯度框架里让策略网络直接输出可执行的离散决策同时保留 TD3 那套双 Critic 加延迟更新的稳定机制。标题里的「融合离散动作」不是简单把输出层换成 softmax。难点在于 TD3 的确定性策略梯度依赖动作对 Q 值的可导性而离散动作采样不可导。常见做法是引入 Gumbel-Softmax 重参数化或者用动作嵌入把离散动作映射回连续空间再算梯度。这套方法适合已经跑通 TD3 基线、想往真实决策层落地的团队也适合做自动驾驶仿真课题、需要一套能复现的端到端决策代码的人。下面从原理、实现、训练、排错到进阶把这条路走一遍。2. TD3WD 的算法骨架与离散动作融合原理2.1 TD3 的三个稳定机制在离散场景下怎么保留TD3 相比 DDPG 的核心改动有三个双 Critic 取最小值、目标策略平滑、Actor 延迟更新。这三条在离散动作场景下不能照搬需要逐条改造。双 Critic 取最小值这条最容易保留。两个 Q 网络各自估计动作价值取 min 作为目标抑制过估计。离散动作下 Q 网络的输出维度从 1 变成动作数取 min 仍然按样本维度做不受影响。目标策略平滑原本是在连续动作上加高斯噪声离散动作没法直接加。替代做法是对动作嵌入向量加噪声或者用 epsilon-greedy 在目标动作上做小概率随机替换。我一般用后者实现简单噪声尺度好控。Actor 延迟更新保留原样Critic 每步更新Actor 每两步更新一次。离散策略网络收敛更慢延迟更新反而更必要。机制连续 TD3 做法TD3WD 离散做法双 Criticmin(Q1,Q2)同左输出维度改为动作数目标平滑动作加高斯噪声动作嵌入加噪或 epsilon 替换延迟更新Actor 每 2 步同左可放宽到 3 步探索动作噪声epsilon-greedy 或 Gumbel 采样2.2 Gumbel-Softmax 让离散动作可导的最小实现确定性策略梯度要求 ∂Q/∂a 存在。离散动作的 one-hot 表示不可导Gumbel-Softmax 提供一个可导的近似采样。前向传播时输出接近 one-hot反向传播时梯度能传回策略网络。import torch import torch.nn.functional as F def gumbel_softmax_sample(logits, temperature1.0, hardTrue): # logits: [batch, n_actions] # 训练时用 soft 采样保证可导推理时用 hard 取 argmax gumbels -torch.empty_like(logits).exponential_().log() y logits gumbels y F.softmax(y / temperature, dim-1) if hard: # straight-through: 前向 one-hot反向用 soft 梯度 index y.max(dim-1, keepdimTrue)[1] y_hard torch.zeros_like(y).scatter_(-1, index, 1.0) y (y_hard - y).detach() y return y这段代码的关键在 straight-through 那两行。前向传播时y是 one-hot满足离散动作要求反向传播时梯度走 soft 版本策略网络能更新。temperature 参数控制采样平滑度训练初期设 1.0 保证探索后期降到 0.1 让动作接近确定。hard 参数在训练时设 True推理时直接取 argmax 即可不需要走这个函数。2.3 动作嵌入层把离散决策映射回连续空间另一个常见做法是加一层动作嵌入。策略网络输出离散动作索引后通过一个 embedding 表映射成连续向量再送进 Critic。这样 Critic 仍然在连续空间工作TD3 的原始梯度公式不用改。class ActionEmbedding(torch.nn.Module): def __init__(self, n_actions, embed_dim): super().__init__() # 每个离散动作对应一个可学习的连续向量 self.embed torch.nn.Embedding(n_actions, embed_dim) def forward(self, action_idx): # action_idx: [batch] 离散动作索引 return self.embed(action_idx) # [batch, embed_dim]embed_dim 一般取 8 到 32。太小区分度不够太大 Critic 输入维度膨胀。我一般从 16 起步看 Q 值方差再调。嵌入层和策略网络一起训练不需要额外监督信号。3. 用 PyTorch 搭一套可跑的 TD3WD 训练流程3.1 网络结构与经验回放的关键参数先定网络。Actor 输入是状态输出是动作 logits两个 Critic 输入是状态加动作嵌入输出 Q 值。经验回放池存的是离散动作索引不是 one-hot省内存也方便采样。class Actor(torch.nn.Module): def __init__(self, state_dim, n_actions, hidden256): super().__init__() self.net torch.nn.Sequential( torch.nn.Linear(state_dim, hidden), torch.nn.ReLU(), torch.nn.Linear(hidden, hidden), torch.nn.ReLU(), torch.nn.Linear(hidden, n_actions) # 输出 logits ) def forward(self, state): return self.net(state) class Critic(torch.nn.Module): def __init__(self, state_dim, embed_dim, hidden256): super().__init__() self.net torch.nn.Sequential( torch.nn.Linear(state_dim embed_dim, hidden), torch.nn.ReLU(), torch.nn.Linear(hidden, hidden), torch.nn.ReLU(), torch.nn.Linear(hidden, 1) ) def forward(self, state, action_embed): return self.net(torch.cat([state, action_embed], dim-1))回放池容量建议 1e6 起步自动驾驶状态维度高太小会反复采样近期数据导致过拟合。batch size 取 256比连续 TD3 常用的 128 大一点因为离散动作的 Q 值估计方差更大。3.2 训练主循环与延迟更新落地主循环里 Critic 每步更新Actor 每 2 步更新目标网络软更新系数 tau 取 0.005。for step in range(total_steps): action_logits actor(state) action_idx gumbel_softmax_sample(action_logits, temperaturetemp).argmax(-1) next_state, reward, done env.step(action_idx) replay_buffer.push(state, action_idx, reward, next_state, done) if len(replay_buffer) batch_size: continue s, a, r, s2, d replay_buffer.sample(batch_size) with torch.no_grad(): next_logits target_actor(s2) next_a gumbel_softmax_sample(next_logits, temperaturetemp) next_embed action_embed(next_a.argmax(-1)) # 双 Critic 取 min q1_next target_critic1(s2, next_embed) q2_next target_critic2(s2, next_embed) q_next torch.min(q1_next, q2_next) target_q r (1 - d) * gamma * q_next a_embed action_embed(a) q1 critic1(s, a_embed) q2 critic2(s, a_embed) critic_loss F.mse_loss(q1, target_q) F.mse_loss(q2, target_q) critic_optimizer.zero_grad() critic_loss.backward() critic_optimizer.step() if step % policy_delay 0: actor_loss -critic1(s, action_embed(actor(s).argmax(-1))).mean() actor_optimizer.zero_grad() actor_loss.backward() actor_optimizer.step() soft_update(target_actor, actor, tau) soft_update(target_critic1, critic1, tau) soft_update(target_critic2, critic2, tau)逻辑上注意两点。一是 Actor 更新时用的动作嵌入要 detach 掉 embedding 的梯度否则 embedding 会被 Actor loss 和 Critic loss 双向拉扯。二是 target_q 计算里 next_a 用 Gumbel 采样而不是 argmax保留一点随机性对应 TD3 的目标平滑。3.3 温度退火与探索率调度temperature 和 epsilon 这两个探索参数不能固定。训练前 20% 步数 temperature 保持 1.0之后线性降到 0.1。epsilon 从 1.0 降到 0.05衰减步数占总步数 30% 左右。def schedule(step, total_steps): progress step / total_steps temp max(0.1, 1.0 - progress * 1.2) eps max(0.05, 1.0 - progress * 3.0) return temp, eps温度降太快策略过早确定Q 值估计不准降太慢动作一直随机学不到东西。这个调度是我在几个仿真环境里试出来的经验值具体任务可以微调。4. 自动驾驶仿真环境下的训练与排错4.1 状态设计与奖励函数里最容易踩的坑状态里必须包含自车速度、加速度、与前车距离、相对速度、车道偏移量、周围车辆位置。少一个都会让策略在某些场景下失能。常见做法是把周围车辆按距离排序取最近 4 辆每辆给相对位置和速度共 8 维加上自车 6 维状态维度 14 左右。奖励函数是排错重灾区。只给碰撞惩罚和到达奖励策略会学会原地不动。我一般拆成四项前进速度奖励、车道保持奖励、碰撞惩罚、舒适度惩罚。def compute_reward(state, action, done): r_speed 0.1 * state[speed] r_lane -0.5 * abs(state[lane_offset]) r_collision -10.0 if done collision else 0.0 r_comfort -0.1 * abs(state[accel]) return r_speed r_lane r_collision r_comfort权重需要按任务调。速度奖励系数太大策略会飙车太小又不动。车道保持系数决定策略多在意居中。建议先用这套权重跑 1e5 步看曲线再逐项调。4.2 训练不收敛时的五个排查点第一看 Q 值量级。如果 Q 值爆炸到几百检查 reward 是否没归一化或者 gamma 设太接近 1。第二看动作分布。如果策略输出长期集中在某一个动作检查 temperature 是否降太快。第三看回放池采样。如果 loss 震荡剧烈可能是 batch 里 done 样本比例太高可以按 done 分层采样。第四看目标网络更新频率。tau 太大目标追不上太小学习慢。第五看 embedding 维度。embed_dim 太小两个不同动作的嵌入几乎一样Critic 分不开。提示训练初期先关掉目标平滑等 Q 值稳定后再打开能明显减少早期震荡。4.3 用仿真数据做离线验证的最小脚本训练完不能只看训练曲线要在固定场景集上跑评估。常见做法是准备 100 个初始状态每个跑 200 步统计碰撞率、平均速度、车道偏移。def evaluate(actor, env, n_episodes100): metrics {collision: 0, avg_speed: 0.0, avg_offset: 0.0} for _ in range(n_episodes): state env.reset() for _ in range(200): with torch.no_grad(): action actor(state).argmax(-1) state, _, done, info env.step(action) metrics[avg_speed] info[speed] metrics[avg_offset] abs(info[lane_offset]) if done collision: metrics[collision] 1 break metrics[avg_speed] / n_episodes metrics[avg_offset] / n_episodes return metrics评估时 Actor 用 argmax 不用采样保证确定性。碰撞率高于 5% 说明策略还没收敛回去查奖励函数和状态设计。5. 从仿真到落地的进阶技巧5.1 动作空间分层把离散决策拆成两级直接让策略输出所有离散动作组合动作数会爆炸。常见做法是分层第一级输出宏观决策跟车、换道、超车第二级在宏观决策下输出具体动作加速、减速、保持。TD3WD 的框架不用改只是 Actor 输出两层 logitsCritic 接收两层嵌入的拼接。class HierarchicalActor(torch.nn.Module): def __init__(self, state_dim, n_high, n_low): super().__init__() self.shared torch.nn.Sequential( torch.nn.Linear(state_dim, 256), torch.nn.ReLU() ) self.high_head torch.nn.Linear(256, n_high) self.low_head torch.nn.Linear(256, n_low) def forward(self, state): feat self.shared(state) return self.high_head(feat), self.low_head(feat)两层 logits 分别做 Gumbel 采样嵌入拼接后送 Critic。这样动作数从 n_high × n_low 降到 n_high n_low训练样本效率明显提升。5.2 用优先经验回放加速稀有场景学习碰撞、急刹这类稀有场景在均匀采样下被采到的概率极低。优先经验回放按 TD 误差给样本加权TD 误差大的样本优先采。实现上给回放池每个样本存一个优先级采样时按优先级分布抽抽完更新优先级。class PrioritizedBuffer: def __init__(self, capacity, alpha0.6): self.capacity capacity self.alpha alpha self.buffer [] self.priorities [] def push(self, transition, td_error): priority (abs(td_error) 1e-5) ** self.alpha if len(self.buffer) self.capacity: self.buffer.append(transition) self.priorities.append(priority) else: idx np.argmin(self.priorities) self.buffer[idx] transition self.priorities[idx] priority def sample(self, batch_size, beta0.4): probs np.array(self.priorities) / sum(self.priorities) indices np.random.choice(len(self.buffer), batch_size, pprobs) # 重要性采样权重修正分布偏差 weights (len(self.buffer) * probs[indices]) ** (-beta) weights / weights.max() return [self.buffer[i] for i in indices], weightsalpha 控制优先级强度0 退化成均匀采样0.6 是常用值。beta 从 0.4 线性升到 1.0训练后期完全修正偏差。这套机制在换道和避障场景上能把收敛步数压掉三成左右。5.3 验证 TD3WD 是否真的优于连续 TD3 的对照实验想确认融合离散动作确实有用做一组对照同一仿真环境、同一状态和奖励分别跑连续 TD3 加动作映射、TD3WD 直接输出离散动作。对比指标看三个达到目标性能所需步数、最终碰撞率、动作抖动率。指标连续 TD3 映射TD3WD收敛步数约 3e5约 2e5碰撞率8%4%动作抖动率15%3%抖动率按相邻两步动作切换频率算。连续 TD3 映射到离散时边界附近会反复横跳抖动率天然高。TD3WD 直接输出离散动作没有映射层抖动率低一个量级。这组对照跑完基本能判断这套方法在你的任务上值不值得上。本文还有配套的精品资源点击获取