图解强化学习 |SAC
欢迎来到图解强化学习的世界博客主页卿云阁欢迎关注点赞收藏⭐️留言首发时间2026年4月17日✉️希望可以和大家一起完成进阶之路目录SAC算法网络结构5 个神经网络网络更新代码实现PolicyModel 策略网络经验回放缓冲区ReplayBufferSAC算法SAC算法网络结构5 个神经网络Actor —— 策略网络输出动作分布Critic 1 —— 第一个 Q 评估网络Critic 2 —— 第二个 Q 评估网络Target Critic 1 —— 稳定用的 Q 目标网络Target Critic 2 —— 另一个稳定用的 Q 目标网络再加 1 个可学习参数α (alpha) —— 控制探索强度① Actor输入状态 s输出动作 a、动作的对数概率 log π(a|s) 输入 next_state 对应输出 next_action 和 next_log_prob 输入 state 对应输出 new_action 和log_prob② Critic 1 / Critic 2输入状态 s 动作 a输出一个 Q 值评估这个动作好不好③ Target Critic 1 / Target Critic 2输入下一状态 s 下一动作 a输出目标 Q 值用来训练 Critic为什么要有 Actor策略网络因为 SAC 是 Actor-Critic 架构Actor 负责 “做动作”Critic 负责 “评价动作”如果没有 Actor就没有策略智能体不会动。而且 SAC 的 Actor 是 随机策略输出分布不是固定动作这是为了天然自带探索不用像DDPG 那样手动加噪声。为什么要有 Critic 1 Critic 2两个 Q 网络这是为了解决强化学习千古难题Q 值过估计overestimation biasCritic 总是倾向于把 Q 值越估越高估高了 → Actor 学到错误行为 → 训练崩掉。解决办法用两个网络每次都取更小的那个 Q 值。Qfinalmin(Q1,Q2)这样就能显著抑制高估训练超级稳。为什么还要 Target Critic 1 Target Critic 2主 Critic负责实时更新、逼近目标Target Critic负责提供稳定、不动的标准答案为什么要有 α温度系数、自动学习的熵参数α自动平衡 “奖励最大化” 和 “探索随机性”网络更新Q 网络更新 Critic LossActor 策略更新 Policy LossAlpha 自动更新 Entropy Temperature Loss目标网络软更新代码部分对应公式Q 网络更新LQMSE(Q(s,a),y)Actor 更新LπE[π⋅(αlogπ−minQ)]Alpha 更新Lαα(−H(π)−Htarget)目标网络软更新 ϕtar代码实现PolicyModel 策略网络给定状态生成各个动作的概率输入 state[0.1, 0.2, -0.1, 0.05, 0.02, 0.01, 0, 0]经过网络 → 输出概率[0.05, 0.80, 0.10, 0.05]Q网络模型给定状态和动作样本对估计Q值输入是 8 维向量state [0.1, 0.2, -0.3, 0.05, 0.01, -0.02, 0, 0]输出 每个动作的 Q 值q_values [ 12.3, 45.6, -5.2, 10.1 ]经验回放缓冲区ReplayBuffer初始化__init__max_size最多存多少条经验buffer用队列存储满了自动踢掉最老的添加experience五元组到缓冲区addstate [0.1, 0.2, -0.3, 0.05, 0.02, -0.01, 0, 0] action 1 reward 2.5 next_state [0.1, 0.18, 0.04, -0.28, 0.02, -0.01, 0, 0] done False buffer.add(state, action, reward, next_state, done)sample () —— 随机抽一批经验训练用states, actions, rewards, next_states, dones buffer.sample(64)__len__返回缓冲区数据数量print(len(buffer)) # 输出 1250表示存了1250条经验SAC算法__init__构造函数参数包含环境学习率折扣因子更新目标网络参数缓冲区大小基础参数初始化self.env env # 强化学习环境如LunarLander self.gamma gamma # 折扣因子未来奖励的权重 self.rho rho # 目标网络软更新系数 self.replay_buffer ReplayBuffer(max_sizebuffer_size) # 经验回放池目标熵self.target_entropy -np.log2(env.action_space.n)SAC 希望策略保持一定的随机性探索目标熵公式target_entropy−log(动作数量)例如LunarLander 有 4 个动作target_entropy−log2(4)−2目的让策略不要太贪婪要保持探索。设备选择CPU/GPUself.device torch.device(cuda if torch.cuda.is_available() else cpu)创建模型并将模型移动到指定设备上# 演员网络策略 self.actor PolicyModel(输入维度输出维度).to(self.device) # 两个 Q 网络 self.q1 QValueModel(输入维度输出维度).to(self.device) self.q2 QValueModel(输入维度输出维度).to(self.device) # 两个目标 Q 网络 self.target_q1 QValueModel(输入维度输出维度).to(self.device) self.target_q2 QValueModel(输入维度输出维度).to(self.device)Actor —— 策略网络输出动作概率Q1 —— 第一个 Q 评估网络Q2 —— 第二个 Q 评估网络Target Q1 —— 稳定目标Target Q2 —— 稳定目标for param, target_param in zip(self.q1.parameters(), self.target_q1.parameters()): target_param.data.copy_(param) for param, target_param in zip(self.q2.parameters(), self.target_q2.parameters()): target_param.data.copy_(param)让 Target 网络一开始和主 Q 网络完全一样self.optimizer_actor torch.optim.Adam(self.actor.parameters(), lrlearning_rate) self.optimizer_q1 torch.optim.Adam(self.q1.parameters(), lrlearning_rate) self.optimizer_q2 torch.optim.Adam(self.q2.parameters(), lrlearning_rate)3 个优化器分别更新Actor、Q1、Q2使用模型生成动作概率分布并采样choose_action# 使用模型生成动作概率分布并采样 def choose_action(self, state): # 将状态转换为tensor输入模型 state torch.FloatTensor(np.array([state])).to(self.device) with torch.no_grad(): action_prob self.actor(state) # 生成分布后采样返回动作 c torch.distributions.Categorical(action_prob) action c.sample() return action.item()输入状态 state真实 8 维数字state [0.1, 0.2, -0.3, 0.05, 0.02, -0.01, 0.0, 0.0]转成 PyTorch Tensorstate torch.FloatTensor( [[0.1, 0.2, -0.3, 0.05, 0.02, -0.01, 0.0, 0.0]] )送入 Actor 网络 → 输出概率action_prob self.actor(state)action_prob tensor([[0.1, 0.7, 0.1, 0.1]])动作 010% 动作 170% 动作 210% 动作 310%采样后输出动作1模型更新next_action_prob self.actor(next_states) log_next_prob torch.log(next_action_prob 1e-9)用 Actor 网络计算【下一个状态】的所有动作概率next_states 下一个状态next_states [0.1, 0.2, -0.3, 0.05, 0.02, -0.01, 0, 0]经过 Actor 网络输出4 个动作的概率分布next_action_prob [0.1, 0.6, 0.2, 0.1]log_next_prob log([0.1, 0.6, 0.2, 0.1]) [-2.30, -0.51, -1.61, -2.30]计算目标Q值target_q1 self.target_q1(next_states) target_q2 self.target_q2(next_states) target_q_min torch.min(target_q1, target_q2) min_q_next_target next_action_prob * (target_q_min - alpha * log_next_prob) min_q_next_target torch.sum(min_q_next_target, dim1, keepdimTrue)next_action_prob tensor([[0.1, 0.6, 0.2, 0.1]]) # 动作概率 log_next_prob tensor([[-2.30, -0.51, -1.61, -2.30]]) # log概率 alpha 0.2 # 探索系数 target_q1 tensor([[10, 20, 5, 8]]) # 目标Q1 四个动作 target_q2 tensor([[11, 19, 4, 9]]) # 目标Q2 四个动作target_q_min 取两个 Q 的最小值target_q_min [[10, 19, 4, 8]]计算熵探索项 alpha * log_next_probmin_q_next_target next_action_prob * (target_q_min - torch.exp(self.log_alpha) * log_next_prob)alpha * log_next_prob 0.2 * [-2.30, -0.51, -1.61, -2.30] [-0.46, -0.10, -0.32, -0.46]target_q_min - alpha * log_next_prob[10, 19, 4, 8] - [-0.46, -0.10, -0.32, -0.46] [100.46, 190.10, 40.32, 80.46] [10.46, 19.10, 4.32, 8.46]乘以动作概率next_action_prob * (...) [0.1, 0.6, 0.2, 0.1] * [10.46, 19.10, 4.32, 8.46] [ 0.1 * 10.46 1.046 0.6 * 19.10 11.46 0.2 * 4.32 0.864 0.1 * 8.46 0.846 ][1.046, 11.46, 0.864, 0.846]sum 1.046 11.46 0.864 0.846 14.216最终结果min_q_next_target tensor([[14.216]])计算下一状态的 “期望 Q 值”双 Q 取最小 熵探索奖励 动作概率加权计算TD目标td_target rewards (1 - dones) * self.gamma * min_q_next_targetTD 目标 当前奖励 折扣因子 × 下一状态的期望 Q 值rewards 5.0 # 当前得到的奖励 dones 0 # 回合没结束0False gamma 0.99 # 折扣因子 min_q_next_target 14.216 # 上一步算出来的下一状态Q值td_target 19.07计算Q网络的loss# 计算Q网络的loss q1 self.q1(states) q2 self.q2(states) q1_loss F.mse_loss(q1.gather(1, actions), td_target.detach()).mean() q2_loss F.mse_loss(q2.gather(1, actions), td_target.detach()).mean()q1 tensor([[12, 15, 8, 10]]) # 4个动作的Q值q2 tensor([[13, 14, 7, 11]])q1.gather(1, 1) → 15(15 - 19.07)² ( -4.07 )² 16.56actor网络更新##### actor网络更新 ##### # 计算当前状态的动作概率 action_prob self.actor(states) log_prob torch.log(action_prob 1e-9) # 计算Q值和actor网络的loss q1 self.q1(states) q2 self.q2(states) inside_term torch.exp(self.log_alpha) * log_prob - torch.min(q1, q2) actor_loss torch.sum(action_prob * inside_term, dim 1, keepdim True).mean()已知假设action_prob tensor([[0.1, 0.6, 0.2, 0.1]]) # 动作概率 log_prob tensor([[-2.3, -0.51, -1.6, -2.3]]) # log概率 alpha 0.2 # 探索系数 q1 tensor([[12, 20, 5, 9]]) q2 tensor([[11, 19, 4, 10]])首先看这行代码inside_term torch.exp(self.log_alpha) * log_prob - torch.min(q1, q2)min_q torch.min(q1, q2) min([12,20,5,9], [11,19,4,10]) tensor([[11, 19, 4, 9]])alpha * log_prob 0.2 * [-2.3, -0.51, -1.6, -2.3] [-0.46, -0.102, -0.32, -0.46] inside_term [-0.46, -0.102, -0.32, -0.46] - [11, 19, 4, 9] [-11.46, -19.102, -4.32, -9.46]action_prob * inside_term [0.1, 0.6, 0.2, 0.1] * [-11.46, -19.102, -4.32, -9.46] [ -1.146, -11.461, -0.864, -0.946 ]sum -1.146 -11.461 -0.864 -0.946 -14.417actor_loss -14.417alpha参数更新##### alpha参数更新 ##### # 计算alpha值的loss inside_term -torch.sum(action_prob * log_prob, dim 1, keepdim True) - self.target_entropy alpha_loss (torch.exp(self.log_alpha) * inside_term.detach()).mean()alpha 是控制 “探索强度” 的旋钮这段代码让 alpha 自动变大 / 变小不用人工调参alpha 大 → 策略更随机、更爱探索alpha 小 → 策略更稳定、更爱 exploitaction_prob tensor([[0.1, 0.6, 0.2, 0.1]]) # 动作概率 log_prob tensor([[-2.3, -0.51, -1.6, -2.3]]) # log概率 target_entropy -2.0 # 目标熵-log2(4) -2 alpha 0.2 # 当前探索系数计算当前策略的熵 H (π)-torch.sum(action_prob * log_prob, dim1, keepdimTrue)action_prob * log_prob 0.1*-2.3 -0.23 0.6*-0.51 -0.306 0.2*-1.6 -0.32 0.1*-2.3 -0.23 sum -0.23 -0.306 -0.32 -0.23 -1.086 取负 -entropy 1.086计算 inside_terminside_term 当前熵 - 目标熵inside_term 1.086 - (-2.0) 1.086 2 3.086alpha_lossalpha_loss alpha * inside_termalpha_loss 0.2 * 3.086 0.617