公式对比普通 AC 的目标函数J(π)∑tE[rt] 只看奖励SAC 的目标函数J(π)∑tE[rtαH(π(⋅|st))] 奖励 α × 熵。其中 H 就是熵。如果 α0SAC 就变成了普通的连续空间 AC 算法。通俗对比普通 AC智能体像个死记硬背的学生。如果它发现往左能拿 10 分往右拿 9 分它会永远、固执地只往左走。即便左边的路偶尔塌陷它也不管。SAC智能体像个富有探索精神的探险家。它发现往左拿 10 分往右拿 9 分它会想虽然左边高分但右边也挺有意思的我也得经常去转转。它追求的是条条大路通罗马而不是死磕一条路。2.4. 三者的联系与进化我们可以把这三者的演进看作是Q-Learning只有大脑记录分数的表没有身体。在连续空间动作有无穷多种可能里它没法找最大值 maxQ。Actor-Critic给大脑配了身体。大脑Critic评估价值身体Actor直接输出动作。不用再费劲去求 max 了Actor 直接告诉你该做什么。SACSoft Actor-Critic给这个组合加了灵魂熵。不仅要拿分还要动作多样化不要死板。用一个类比总结Q-Learning就像你在玩扫雷。你学习每一格如果点开大概率有多少分数。你最后选分数最高的那格点。Actor-Critic就像导演和演员。演员Actor练习表演导演Critic在旁边说这一段演得好多保持那一段太浮夸少来。演员不看剧本的分数只听导演的。SAC导演Critic跟演员Actor说你不仅要演得好还得有自己的风格熵别老是演得跟模板一模一样多尝试点即兴发挥。0x03 网络结构本节我们来看看SAC 的特色细节。3.1 SAC 到底有几个网络在一个标准的 SAC 实现中通常有以下几个神经网络Actor 网络1 个输出动作的分布μ,σ。Critic 网络2 个即 Q1 和 Q2。为什么要两个为了解决过度估计问题。如果只有一个 Q它会像个爱吹牛的人把得分估得太高。两个 Q 取最小值就能压住这种吹牛。Target Critic 网络2 个即 Qtarget1 和 Qtarget2。它们是 Q1Q2 的影子更新得非常慢平滑更新。这是为了让训练目标更稳定。Temperature (自动熵调节) 网络1个自动调节策略熵的目标值确保温度参数 ≥ 目标熵。3.2 目标函数 J(π) 与损失函数在 RL 中我们确实希望最大化 J(π)即累积奖励。但神经网络优化工具如 PyTorch/TensorFlow通常只能最小化一个损失函数Loss。因此会设置 Loss −J(π)。3.2.1 普通 AC 的损失函数Critic最小化均方误差 MSE(Q(s,a), Target)。Actor通常使用策略梯度Policy Gradient让能够获得高 Q 值的动作出现的概率变大。3.2.2 SAC 的损失函数Critic LossMSE(Q(s,a),rγ(minQtarget(s,a)−αlogπ(a∣s)))Actor LossEa∼π[αlogπ(a∣s)−minQ(s,a)]Alpha Loss−α⋅E[logπ(a∣s)Htarget]3.3 Critic评论家Critic 的目标是预测未来的总收益。在 SAC 的实现中通常会维护两个 Q 网络Clipped Double-Q。SAC 的 CriticQRγE[未来能拿的分未来动作的熵]Q(s,a)←rγEa′∼π(⋅|s′)[Q(s′,a′)−αlogπ(a′|s′)]3.3.1 Critic 公式里的「Soft Value软价值」[Q(s′,a′)−αlogπ(a′|s′)] 这个括号里的东西我们称之为 Soft Value软价值。Q(s′,a′)下一步能拿到的奖励预期。−αlogπ(a′|s′)下一步动作的随机性熵奖励。−logP 在信息论里就是「惊奇度」期望的惊奇度就是熵。含义Critic 现在不只是在预测钱奖励它还在预测 钱 自由度熵。它告诉智能体「去那个奖励又高、选择又丰富的地方」。3.3.2 Critic 到底该不该考虑熵既然提到了熵我们就看看Critic 为何要考虑熵。如果 Critic 只预测奖励而 Actor 却在追求奖励熵。这会导致什么结果Critic 会对 Actor 说你刚才那个动作太随机了虽然奖励高但我不看好你。Actor 会说可我的目标就是要随机啊这会导致驴唇不对马嘴两者无法协作。所以SAC 的 Critic 更新公式是 Q(s,a)←rγEa′∼π(⋅|s′)[Q(s′,a′)−αlogπ(a′|s′)]。这里的 −αlogπ(a′|s′) 其实就是熵的体现。如果智能体在下一步动作 a′ 的概率非常高非常确定−logπ 会变得很小如果动作很随机−logπ 会变大。这意味着SAC 的 Critic 实际上是在评估这步动作不仅现在好而且能保证以后有更多的选择余地。3.3.3 训练 Critic 时要不要更新 Actor实际上在代码实现中训练 Critic 时Actor 是禁止动弹的。原因Critic 的目标是「预测准确」。它要预测的是当前 Actor 表现如何。如果 Critic 一边在学预测Actor 一边在变Critic 就会像在追一个移动的靶子永远练不准。做法我们计算 Target 时会用到 Actor 输出的概率 π但我们只传导梯度给 Critic 的参数。这叫解耦。在每一轮训练中我们其实是分两步走的第一步练 CriticActor 站着不动我们要让 Critic 学会评价当前这个 Actor 的水平。计算 Loss 时我们会用到 Actor 的输出 π但我们设置actor.requires_grad False或者只是不把 Actor 的参数放进优化器。结果只有 Critic 的权重变了Actor 没变。第二步练 ActorCritic 坐着当评委现在 Critic 已经练好了它能准确判断动作的好坏了。我们让 Actor 跑一遍计算 Lossαlogπ−Q。此时计算梯度并且只更新 Actor 的参数。结果Actor 变聪明了它学会了如何让评委Critic给自己打高分。总结在整个大循环里Actor 当然要更新但在训练 Critic那个具体的子步骤里Actor 是不动的。3.4 Actor演员Actor Loss 如下$$\text{Loss}{{\text{Actor}}} \mathbb{E}\left[ \alpha \log \pi(a|s) - Q(s, a) \right]$$这个 Loss 的两部分为−Q(s,a)最小化这个就是在最大化 Q优化奖励。αlogπ(a|s)最小化这个就是在让 π(a|s) 变小因为 log 是增函数。π 越小分布就越平、越随机即最大化熵。这就好比一个教练Loss同时盯着运动员的「速度」和「花哨程度」。如果速度慢了教练扣分如果动作单一了教练也扣分。当调用loss.backward()时梯度会穿过 Q 网络但 Q 的参数被冻结不更新一直回传到 Actor 网络输出 μ 和 σ 的那一层。在这个 Loss 里Q(s,a) 的值决定了梯度的大小和方向但我们只用它来告诉 Actor往这边调整你的 μ 和 σ能让 Q 变得更大。3.4.1 Actor Loss 的直观平衡LossaE[−a变量⋅(logπ(a|s)¯H误差)]这是一个标量损失函数Scalar Loss。¯H 是你的目标。比如你希望动作保持一定的随机性。当 Loss 减小时αlogπ(a|s) 减小 → logπ 趋向更负的值 → π 变小 → 分布变宽、越随机熵优化。博弈平衡如果 π 缩得太小太随机Q 值可能会下降如果 π 太集中熵损失会变大。α 这个权重决定了最终平衡点在哪里。场景 A: 太确定了。log π 很大 (接近 0), 导致 (log π \bar{H}) 变成正数。为了让 Loss 减小, a 必须增大。后果: a 变大后, 在 Actor 的 Loss 中, 熵的权重增加了。Actor 会被教导: 别管奖励了, 先给我变随机点! 场景 B: 太乱了。a 会减小, 让 Actor 专心去拿奖励。3.4.2 特色目标函数Maximize E [ 奖励 α × 熵 ]。如果 α0智能体会陷入死磕一条路的死胡同。熵确保了智能体在追求高分的同时保持条条大路通罗马的鲁棒性。为什么 σ→0熵就没了直观理解σ 代表不确定性。如果 σ0意味着智能体 100% 确定只做一个动作。既然完全确定就没有随机性不确定性熵自然就是 0甚至在连续空间定义下趋向负无穷。数学公式高斯分布的微分熵公式是 (1/2)ln(2πeσ2)。当 σ→0 时这个值趋向 −∞。为什么 log π 越大, 熵就越小? 我们要先搞清楚概率 π 的范围: 它在 [0, 1] 之间。动作非常确定比如智能体 99% 的概率选动作 A。此时 π(A|s) ≈ 1, 那么 log π ≈ log 1 0。动作非常随机比如有 100 个动作, 智能体每个都选, 概率 π ≈ 0.01。此时 log π ≈ log 0.01 -4.6。结论: 在负数世界里, 0 是最大的。所以 log π 越接近 0 (越大), 说明概率越集中, 熵 (即 -log π 的平均值) 就越小。3.5 小结Actor和Critic使用高度相似但不完全相同的网络架构。主要区别在于Critic需要额外输入动作信息这符合Actor-Critic算法的理论设计。相似点都使用相同的编码器视觉编码器可共享都使用MLP主干网络hidden_dims配置相同默认[256, 256]都支持多设备并行通过ensemble机制关键差异特性ActorCritic输入仅观测observations观测 动作[obs_enc, actions]输出动作分布参数mean, std标量Q值网络结构独立输出均值和标准差单一输出层激活函数最后一层通常激活通常线性输出0x04 实现SERL 的网络设计选择如下4.1 异同Actor和Critic使用高度相似但不完全相同的网络架构。主要区别在于Critic需要额外输入动作信息这符合Actor-Critic算法的理论设计。相似点都使用相同的编码器视觉编码器可共享都使用MLP主干网络hidden_dims配置相同默认[256, 256]都支持多设备并行通过ensemble机制关键差异特性ActorCritic输入仅观测observations观测 动作[obs_enc, actions]输出动作分布参数mean, std标量Q值网络结构独立输出均值和标准差单一输出层激活函数最后一层通常激活通常线性输出4.2 网络定义Actor 网络如下class Policy(nn.Module): encoder: Optional[nn.Module] # 视觉编码器 network: nn.Module # MLP主干网络 action_dim: int def __call__(self, observations, temperature1.0): if self.encoder is None: obs_enc observations else: obs_enc self.encoder(observations, traintrain, stop_gradientTrue) outputs self.network(obs_enc, traintrain) means nn.Dense(self.action_dim)(outputs) stds nn.Dense(self.action_dim)(outputs) # 标准差参数 return TanhMultivariateNormalDiag(locmeans, scale_diagstds)Critic 网络定义如下class Critic(nn.Module): encoder: Optional[nn.Module] # 视觉编码器 network: nn.Module # MLP主干网络 def __call__(self, observations, actions, trainFalse): if self.encoder is None: obs_enc observations else: obs_enc self.encoder(observations) inputs jnp.concatenate([obs_enc, actions], -1) # 关键差异 outputs self.network(inputs, traintrain) value nn.Dense(1)(outputs) # 输出Q值 return jnp.squeeze(value, -1)4.3 SAC 网络架构SACAgent 算是 SERL Agent 系统的基础所以我们从它看起。class SACAgent(flax.struct.PyTreeNode):其总体信息如下组件输入网络结构输出参数共享Actor图像观测编码器MLP[256,256]动作分布(μ,σ)编码器可共享Critic图像动作编码器Ensemble MLP[256,256]×2Q值编码器可共享Temperature无Lagrange乘数标量温度独立参数4.3.1 核心组件Actor (Policy) 网络内部结构编码器将图像编码为特征向量MLP主干[256, 256]全连接层输出层均值和标准差各一个全连接层分布TanhMultivariateNormalDiagpolicy_def Policy( encoderencoders[actor], # 视觉编码器 networkMLP(**policy_network_kwargs), # 默认 [256, 256] action_dimactions.shape[-1], tanh_squash_distributionTrue, std_parameterizationuniform, )Critic内部结构编码器与Actor相同或独立Ensemble MLP默认2个独立的Critic网络输入拼接编码特征和动作[obs_enc, actions]输出标量Q值critic_backbone partial(MLP, **critic_network_kwargs) # [256, 256] critic_backbone ensemblize(critic_backbone, critic_ensemble_size)( namecritic_ensemble ) critic_def partial( Critic, encoderencoders[critic], # 可与Actor共享编码器 networkcritic_backbone )
SAC vs. 普通 Actor-Critic(AC)
公式对比普通 AC 的目标函数J(π)∑tE[rt] 只看奖励SAC 的目标函数J(π)∑tE[rtαH(π(⋅|st))] 奖励 α × 熵。其中 H 就是熵。如果 α0SAC 就变成了普通的连续空间 AC 算法。通俗对比普通 AC智能体像个死记硬背的学生。如果它发现往左能拿 10 分往右拿 9 分它会永远、固执地只往左走。即便左边的路偶尔塌陷它也不管。SAC智能体像个富有探索精神的探险家。它发现往左拿 10 分往右拿 9 分它会想虽然左边高分但右边也挺有意思的我也得经常去转转。它追求的是条条大路通罗马而不是死磕一条路。2.4. 三者的联系与进化我们可以把这三者的演进看作是Q-Learning只有大脑记录分数的表没有身体。在连续空间动作有无穷多种可能里它没法找最大值 maxQ。Actor-Critic给大脑配了身体。大脑Critic评估价值身体Actor直接输出动作。不用再费劲去求 max 了Actor 直接告诉你该做什么。SACSoft Actor-Critic给这个组合加了灵魂熵。不仅要拿分还要动作多样化不要死板。用一个类比总结Q-Learning就像你在玩扫雷。你学习每一格如果点开大概率有多少分数。你最后选分数最高的那格点。Actor-Critic就像导演和演员。演员Actor练习表演导演Critic在旁边说这一段演得好多保持那一段太浮夸少来。演员不看剧本的分数只听导演的。SAC导演Critic跟演员Actor说你不仅要演得好还得有自己的风格熵别老是演得跟模板一模一样多尝试点即兴发挥。0x03 网络结构本节我们来看看SAC 的特色细节。3.1 SAC 到底有几个网络在一个标准的 SAC 实现中通常有以下几个神经网络Actor 网络1 个输出动作的分布μ,σ。Critic 网络2 个即 Q1 和 Q2。为什么要两个为了解决过度估计问题。如果只有一个 Q它会像个爱吹牛的人把得分估得太高。两个 Q 取最小值就能压住这种吹牛。Target Critic 网络2 个即 Qtarget1 和 Qtarget2。它们是 Q1Q2 的影子更新得非常慢平滑更新。这是为了让训练目标更稳定。Temperature (自动熵调节) 网络1个自动调节策略熵的目标值确保温度参数 ≥ 目标熵。3.2 目标函数 J(π) 与损失函数在 RL 中我们确实希望最大化 J(π)即累积奖励。但神经网络优化工具如 PyTorch/TensorFlow通常只能最小化一个损失函数Loss。因此会设置 Loss −J(π)。3.2.1 普通 AC 的损失函数Critic最小化均方误差 MSE(Q(s,a), Target)。Actor通常使用策略梯度Policy Gradient让能够获得高 Q 值的动作出现的概率变大。3.2.2 SAC 的损失函数Critic LossMSE(Q(s,a),rγ(minQtarget(s,a)−αlogπ(a∣s)))Actor LossEa∼π[αlogπ(a∣s)−minQ(s,a)]Alpha Loss−α⋅E[logπ(a∣s)Htarget]3.3 Critic评论家Critic 的目标是预测未来的总收益。在 SAC 的实现中通常会维护两个 Q 网络Clipped Double-Q。SAC 的 CriticQRγE[未来能拿的分未来动作的熵]Q(s,a)←rγEa′∼π(⋅|s′)[Q(s′,a′)−αlogπ(a′|s′)]3.3.1 Critic 公式里的「Soft Value软价值」[Q(s′,a′)−αlogπ(a′|s′)] 这个括号里的东西我们称之为 Soft Value软价值。Q(s′,a′)下一步能拿到的奖励预期。−αlogπ(a′|s′)下一步动作的随机性熵奖励。−logP 在信息论里就是「惊奇度」期望的惊奇度就是熵。含义Critic 现在不只是在预测钱奖励它还在预测 钱 自由度熵。它告诉智能体「去那个奖励又高、选择又丰富的地方」。3.3.2 Critic 到底该不该考虑熵既然提到了熵我们就看看Critic 为何要考虑熵。如果 Critic 只预测奖励而 Actor 却在追求奖励熵。这会导致什么结果Critic 会对 Actor 说你刚才那个动作太随机了虽然奖励高但我不看好你。Actor 会说可我的目标就是要随机啊这会导致驴唇不对马嘴两者无法协作。所以SAC 的 Critic 更新公式是 Q(s,a)←rγEa′∼π(⋅|s′)[Q(s′,a′)−αlogπ(a′|s′)]。这里的 −αlogπ(a′|s′) 其实就是熵的体现。如果智能体在下一步动作 a′ 的概率非常高非常确定−logπ 会变得很小如果动作很随机−logπ 会变大。这意味着SAC 的 Critic 实际上是在评估这步动作不仅现在好而且能保证以后有更多的选择余地。3.3.3 训练 Critic 时要不要更新 Actor实际上在代码实现中训练 Critic 时Actor 是禁止动弹的。原因Critic 的目标是「预测准确」。它要预测的是当前 Actor 表现如何。如果 Critic 一边在学预测Actor 一边在变Critic 就会像在追一个移动的靶子永远练不准。做法我们计算 Target 时会用到 Actor 输出的概率 π但我们只传导梯度给 Critic 的参数。这叫解耦。在每一轮训练中我们其实是分两步走的第一步练 CriticActor 站着不动我们要让 Critic 学会评价当前这个 Actor 的水平。计算 Loss 时我们会用到 Actor 的输出 π但我们设置actor.requires_grad False或者只是不把 Actor 的参数放进优化器。结果只有 Critic 的权重变了Actor 没变。第二步练 ActorCritic 坐着当评委现在 Critic 已经练好了它能准确判断动作的好坏了。我们让 Actor 跑一遍计算 Lossαlogπ−Q。此时计算梯度并且只更新 Actor 的参数。结果Actor 变聪明了它学会了如何让评委Critic给自己打高分。总结在整个大循环里Actor 当然要更新但在训练 Critic那个具体的子步骤里Actor 是不动的。3.4 Actor演员Actor Loss 如下$$\text{Loss}{{\text{Actor}}} \mathbb{E}\left[ \alpha \log \pi(a|s) - Q(s, a) \right]$$这个 Loss 的两部分为−Q(s,a)最小化这个就是在最大化 Q优化奖励。αlogπ(a|s)最小化这个就是在让 π(a|s) 变小因为 log 是增函数。π 越小分布就越平、越随机即最大化熵。这就好比一个教练Loss同时盯着运动员的「速度」和「花哨程度」。如果速度慢了教练扣分如果动作单一了教练也扣分。当调用loss.backward()时梯度会穿过 Q 网络但 Q 的参数被冻结不更新一直回传到 Actor 网络输出 μ 和 σ 的那一层。在这个 Loss 里Q(s,a) 的值决定了梯度的大小和方向但我们只用它来告诉 Actor往这边调整你的 μ 和 σ能让 Q 变得更大。3.4.1 Actor Loss 的直观平衡LossaE[−a变量⋅(logπ(a|s)¯H误差)]这是一个标量损失函数Scalar Loss。¯H 是你的目标。比如你希望动作保持一定的随机性。当 Loss 减小时αlogπ(a|s) 减小 → logπ 趋向更负的值 → π 变小 → 分布变宽、越随机熵优化。博弈平衡如果 π 缩得太小太随机Q 值可能会下降如果 π 太集中熵损失会变大。α 这个权重决定了最终平衡点在哪里。场景 A: 太确定了。log π 很大 (接近 0), 导致 (log π \bar{H}) 变成正数。为了让 Loss 减小, a 必须增大。后果: a 变大后, 在 Actor 的 Loss 中, 熵的权重增加了。Actor 会被教导: 别管奖励了, 先给我变随机点! 场景 B: 太乱了。a 会减小, 让 Actor 专心去拿奖励。3.4.2 特色目标函数Maximize E [ 奖励 α × 熵 ]。如果 α0智能体会陷入死磕一条路的死胡同。熵确保了智能体在追求高分的同时保持条条大路通罗马的鲁棒性。为什么 σ→0熵就没了直观理解σ 代表不确定性。如果 σ0意味着智能体 100% 确定只做一个动作。既然完全确定就没有随机性不确定性熵自然就是 0甚至在连续空间定义下趋向负无穷。数学公式高斯分布的微分熵公式是 (1/2)ln(2πeσ2)。当 σ→0 时这个值趋向 −∞。为什么 log π 越大, 熵就越小? 我们要先搞清楚概率 π 的范围: 它在 [0, 1] 之间。动作非常确定比如智能体 99% 的概率选动作 A。此时 π(A|s) ≈ 1, 那么 log π ≈ log 1 0。动作非常随机比如有 100 个动作, 智能体每个都选, 概率 π ≈ 0.01。此时 log π ≈ log 0.01 -4.6。结论: 在负数世界里, 0 是最大的。所以 log π 越接近 0 (越大), 说明概率越集中, 熵 (即 -log π 的平均值) 就越小。3.5 小结Actor和Critic使用高度相似但不完全相同的网络架构。主要区别在于Critic需要额外输入动作信息这符合Actor-Critic算法的理论设计。相似点都使用相同的编码器视觉编码器可共享都使用MLP主干网络hidden_dims配置相同默认[256, 256]都支持多设备并行通过ensemble机制关键差异特性ActorCritic输入仅观测observations观测 动作[obs_enc, actions]输出动作分布参数mean, std标量Q值网络结构独立输出均值和标准差单一输出层激活函数最后一层通常激活通常线性输出0x04 实现SERL 的网络设计选择如下4.1 异同Actor和Critic使用高度相似但不完全相同的网络架构。主要区别在于Critic需要额外输入动作信息这符合Actor-Critic算法的理论设计。相似点都使用相同的编码器视觉编码器可共享都使用MLP主干网络hidden_dims配置相同默认[256, 256]都支持多设备并行通过ensemble机制关键差异特性ActorCritic输入仅观测observations观测 动作[obs_enc, actions]输出动作分布参数mean, std标量Q值网络结构独立输出均值和标准差单一输出层激活函数最后一层通常激活通常线性输出4.2 网络定义Actor 网络如下class Policy(nn.Module): encoder: Optional[nn.Module] # 视觉编码器 network: nn.Module # MLP主干网络 action_dim: int def __call__(self, observations, temperature1.0): if self.encoder is None: obs_enc observations else: obs_enc self.encoder(observations, traintrain, stop_gradientTrue) outputs self.network(obs_enc, traintrain) means nn.Dense(self.action_dim)(outputs) stds nn.Dense(self.action_dim)(outputs) # 标准差参数 return TanhMultivariateNormalDiag(locmeans, scale_diagstds)Critic 网络定义如下class Critic(nn.Module): encoder: Optional[nn.Module] # 视觉编码器 network: nn.Module # MLP主干网络 def __call__(self, observations, actions, trainFalse): if self.encoder is None: obs_enc observations else: obs_enc self.encoder(observations) inputs jnp.concatenate([obs_enc, actions], -1) # 关键差异 outputs self.network(inputs, traintrain) value nn.Dense(1)(outputs) # 输出Q值 return jnp.squeeze(value, -1)4.3 SAC 网络架构SACAgent 算是 SERL Agent 系统的基础所以我们从它看起。class SACAgent(flax.struct.PyTreeNode):其总体信息如下组件输入网络结构输出参数共享Actor图像观测编码器MLP[256,256]动作分布(μ,σ)编码器可共享Critic图像动作编码器Ensemble MLP[256,256]×2Q值编码器可共享Temperature无Lagrange乘数标量温度独立参数4.3.1 核心组件Actor (Policy) 网络内部结构编码器将图像编码为特征向量MLP主干[256, 256]全连接层输出层均值和标准差各一个全连接层分布TanhMultivariateNormalDiagpolicy_def Policy( encoderencoders[actor], # 视觉编码器 networkMLP(**policy_network_kwargs), # 默认 [256, 256] action_dimactions.shape[-1], tanh_squash_distributionTrue, std_parameterizationuniform, )Critic内部结构编码器与Actor相同或独立Ensemble MLP默认2个独立的Critic网络输入拼接编码特征和动作[obs_enc, actions]输出标量Q值critic_backbone partial(MLP, **critic_network_kwargs) # [256, 256] critic_backbone ensemblize(critic_backbone, critic_ensemble_size)( namecritic_ensemble ) critic_def partial( Critic, encoderencoders[critic], # 可与Actor共享编码器 networkcritic_backbone )