SAC 算法

SAC 算法

  • soft贝尔曼期望
    • 策略更新目标
      • 拉格朗日乘数法
        • 策略迭代
          • [soft-策略价值中 α \alpha α的调节](#soft-策略价值中 α \alpha α的调节)
        • 最优状态
          • [证明Soft 贝尔曼算子同样是压缩映射](#证明Soft 贝尔曼算子同样是压缩映射)
          • [当 α → 0 , V ∗ ( s ) \alpha\rightarrow0,V^{*}(s) α→0,V∗(s)相当于 max ⁡ a Q ( s , a ) \max_{a} Q(s,a) maxaQ(s,a)](#当 α → 0 , V ∗ ( s ) \alpha\rightarrow0,V^{*}(s) α→0,V∗(s)相当于 max ⁡ a Q ( s , a ) \max_{a} Q(s,a) maxaQ(s,a))

soft贝尔曼期望

数学表达式为:

Q ( s t , a t ) = r ( s t , a t ) + γ E s t + 1 V ( s t + 1 ) Q(s_t,a_t)=r(s_t,a_t)+\gamma\mathbb{E}{s{t+1}}V(s_{t+1}) Q(st,at)=r(st,at)+γEst+1V(st+1)

其中,

V ( s t ) = E a ∼ π Q ( s t , a t ) − α log ⁡ ( a t ∣ s t ) = E a ∼ π Q ( s t , a t ) + α H ( π ( ⋅ ∣ s t ) ) V(s_t)=\mathbb{E}{a\sim \pi}Q(s_t,a_t)-\\alpha \\log (a_t\|s_t)=\mathbb{E}{a\sim \pi}Q(s_t,a_t)+\alpha H(\pi(\cdot|s_t)) V(st)=Ea∼πQ(st,at)−αlog(at∣st)=Ea∼πQ(st,at)+αH(π(⋅∣st))

策略更新目标

由于策略的价值定义为:

J ( π ) = E V π ( s ) J(\pi)=\mathbb{E}V\^{\\pi}(s) J(π)=EVπ(s)

因此最优策略目标为:

π ∗ = arg max ⁡ π E V π ( s ) \pi^{*}=\argmax_{\pi}\mathbb{E}V\^{\\pi}(s) π∗=πargmaxEVπ(s)

= arg max ⁡ π E a ∼ π Q ∗ ( s , a ) − α log ⁡ π ( ⋅ ∣ s ) =\argmax_{\pi}\mathbb{E}_{a\sim \pi}Q\^{\*}(s,a)-\\alpha\\log \\pi(\\cdot\|s) =πargmaxEa∼πQ∗(s,a)−αlogπ(⋅∣s)

= arg max ⁡ π ∑ a π ( a ∣ s ) Q ∗ ( s , a ) − α log ⁡ π ( ⋅ ∣ s ) =\argmax_{\pi} \sum_{a}\pi(a|s)Q\^{\*}(s,a)-\\alpha\\log \\pi(\\cdot\|s) =πargmaxa∑π(a∣s)Q∗(s,a)−αlogπ(⋅∣s)

约束为:

∑ a π ( a ∣ s ) = 1 \sum_{a}\pi(a|s)=1 a∑π(a∣s)=1

目标函数第一项: ∑ a π ( a ∣ s ) Q ∗ ( s , a ) \sum_a\pi(a|s)Q^{*}(s,a) ∑aπ(a∣s)Q∗(s,a)这是一个关于 π \pi π的线性函数。线性函数既是凸的也是凹的,不改变凸性。

目标函数第二项: − ∑ a π ( a ∣ s ) log ⁡ π ( ⋅ ∣ s ) -\sum_a\pi(a|s)\log \pi(\cdot|s) −∑aπ(a∣s)logπ(⋅∣s)这是熵函数,严格凹函数。

因此整个目标函数是严格凹函数。

又因为约束条件 ∑ a π ( a ∣ s ) = 1 \sum_{a}\pi(a|s)=1 ∑aπ(a∣s)=1,定义了一个概率单纯形,这是一个凸集。在概率单纯形上最大化一个严格凹函数,是一个标准的凸优化问题。

在凸优化中,只要目标函数是严格凹的、约束是凸的,那么任何满足KKT条件的局部最优解就是全局最优解。拉格朗日乘数法找到的解满足KKT条件,因此它就是全局最优解。我们不需要"先验证凸性再求解",而是"凸性保证了拉格朗日解就是全局最优"。

拉格朗日乘数法

构建拉格朗日算子如下:

L ( π , λ ) = ∑ a π ( a ∣ s ) Q ∗ ( s , a ) − α log ⁡ π ( ⋅ ∣ s ) − λ ( 1 − ∑ a π ( a ∣ s ) ) L(\pi,\lambda)=\sum_{a}\pi(a|s)Q\^{\*}(s,a)-\\alpha\\log \\pi(\\cdot\|s)-\lambda(1-\sum_{a}\pi(a|s)) L(π,λ)=a∑π(a∣s)Q∗(s,a)−αlogπ(⋅∣s)−λ(1−a∑π(a∣s))

对指定某一策略求导数,并令其为0可得:

∂ L ( π , λ ) ∂ π ( a ∣ s ) = Q ( s , a ) − α log ⁡ π ( ⋅ ∣ s ) + π ( a ∣ s ) α π ( a ∣ s ) − λ \frac{\partial L(\pi,\lambda)}{\partial \pi(a|s)}=Q(s,a)-\\alpha\\log \\pi(\\cdot\|s)+\\pi(a\|s)\\frac{\\alpha}{\\pi(a\|s)}-\lambda ∂π(a∣s)∂L(π,λ)=Q(s,a)−αlogπ(⋅∣s)+π(a∣s)π(a∣s)α−λ

∂ L ( π , λ ) ∂ π ( a ∣ s ) = Q ( s , a ) − α log ⁡ π ( a ⋅ ∣ s ) − ( α + λ ) = 0 \frac{\partial L(\pi,\lambda)}{\partial \pi(a|s)}=Q(s,a)-\\alpha\\log \\pi(a\\cdot\|s)-(\alpha+\lambda)=0 ∂π(a∣s)∂L(π,λ)=Q(s,a)−αlogπ(a⋅∣s)−(α+λ)=0

也即:

log ⁡ π ( a ⋅ ∣ s ) = Q ( s , a ) α − α + λ α \log \pi(a\cdot|s)=\frac{Q(s,a)}{\alpha}-\frac{\alpha+\lambda}{\alpha} logπ(a⋅∣s)=αQ(s,a)−αα+λ

等式两边取对数得:

π ( a ⋅ ∣ s ) = exp ⁡ ( Q ( s , a ) α ) ⋅ exp ⁡ ( α + λ α ) \pi(a\cdot|s)=\exp(\frac{Q(s,a)}{\alpha})\cdot \exp(\frac{\alpha+\lambda}{\alpha}) π(a⋅∣s)=exp(αQ(s,a))⋅exp(αα+λ)

令 C = exp ⁡ ( α + λ α ) C=\exp(\frac{\alpha+\lambda}{\alpha}) C=exp(αα+λ)

对策略求和可得:

∑ a π ( a ⋅ ∣ s ) = C ∑ a exp ⁡ ( Q ( s , a ) α ) = 1 \sum_a\pi(a\cdot|s)=C\sum_a\exp(\frac{Q(s,a)}{\alpha})=1 a∑π(a⋅∣s)=Ca∑exp(αQ(s,a))=1

因此可得: C = 1 ∑ a exp ⁡ ( Q ( s , a ) α ) C=\frac{1}{\sum_a\exp(\frac{Q(s,a)}{\alpha})} C=∑aexp(αQ(s,a))1

令 Z ( s ) = ∑ a exp ⁡ ( Q ( s , a ) α ) Z(s)=\sum_a\exp(\frac{Q(s,a)}{\alpha}) Z(s)=∑aexp(αQ(s,a))

综上所述:

最优策略为:

π ∗ ( a ∣ s ) = exp ⁡ ( Q ( s , a ) α ) ∑ a exp ⁡ ( Q ( s , a ) α ) (3.1) \pi^{*}(a|s)=\frac{\exp(\frac{Q(s,a)}{\alpha})}{\sum_a\exp(\frac{Q(s,a)}{\alpha})}\text{(3.1)} π∗(a∣s)=∑aexp(αQ(s,a))exp(αQ(s,a))(3.1)

策略迭代

由于策略迭代的目的是为了逐步趋近最优策略,首先需要衡量当前策略(概率分布)与最优策略(概率分布)之间的差异。根据之前博客的介绍,我们不难发现可以利用KL散度进行衡量。数学表达如下:

D K L π θ ( ⋅ ∣ s ) ∣ ∣ π ∗ ( ⋅ ∣ s ) = E a ∼ π log ⁡ π θ ( a ∣ s ) π ∗ ( a ∣ s ) = E a ∼ π log ⁡ π θ ( a ∣ s ) exp ⁡ ( Q ( s , a ) α ) / Z ( s ) D_{KL}\\pi_{\\theta}(\\cdot\|s)\|\|\\pi\^{\*}(\\cdot\|s)=\mathbb{E}{a\sim \pi}\\log \\frac{\\pi_{\\theta}(a\|s)}{\\pi\^{\*}(a\|s)}=\mathbb{E}{a\sim \pi}\\log \\frac{\\pi_{\\theta}(a\|s)}{\\exp(\\frac{Q(s,a)}{\\alpha})/Z(s)} DKLπθ(⋅∣s)∣∣π∗(⋅∣s)=Ea∼πlogπ∗(a∣s)πθ(a∣s)=Ea∼πlogexp(αQ(s,a))/Z(s)πθ(a∣s)

展开得:

= E a ∼ π log ⁡ π θ ( a ∣ s ) − Q ( s , a ) α + log ⁡ Z ( s ) = E a ∼ π log ⁡ π θ ( a ∣ s ) − Q ( s , a ) α + log ⁡ Z ( s ) =\mathbb{E}{a\sim \pi}\\log\\pi_{\\theta}(a\|s)-\\frac{Q(s,a)}{\\alpha}+\\log Z(s)=\mathbb{E}{a\sim \pi}\\log\\pi_{\\theta}(a\|s)-\\frac{Q(s,a)}{\\alpha}+\log Z(s) =Ea∼πlogπθ(a∣s)−αQ(s,a)+logZ(s)=Ea∼πlogπθ(a∣s)−αQ(s,a)+logZ(s)

构建soft-策略价值表达式为:

L π ( θ ) = E α log ⁡ π θ ( a ∣ s ) − Q ( s , a ) L_{\pi}(\theta)=\mathbb{E}\\alpha \\log\\pi_{\\theta}(a\|s)-Q(s,a) Lπ(θ)=Eαlogπθ(a∣s)−Q(s,a)

  • 之所以不考虑 Z ( s ) Z(s) Z(s)主要是因为策略参数得迭代需要利用梯度下降法求增量, Z ( s ) Z(s) Z(s)与策略梯无关。
  • 数学上KL需要最小化和pytorch利用梯度下降法求最小值一致,因此不需要改变符号。
  • 之所以需要对散度展开式乘以 α \alpha α,主要是为了防止 当 α → 0 时, Q ( s , a ) α → ∞ 当\alpha\rightarrow0时,\frac{Q(s,a)}{\alpha}\rightarrow \infty 当α→0时,αQ(s,a)→∞
soft-策略价值中 α \alpha α的调节

根据soft-策略价值,我们不难看出 α \alpha α的数学意义:

  • 当 α \alpha α增大时,该策略的熵增大,更趋向探索。
  • 当 α \alpha α降低时,该策略的熵降低,更趋向于依赖价值函数。

根据这一数学意义构建 α \alpha α预估数学建模如下:

L ( α ) = α E H c u r r e n t − H t a r g e t L(\alpha)=\alpha\mathbb{E}H_{current}-H_{target} L(α)=αEHcurrent−Htarget

由于,

∂ L ( α ) ∂ α = H c u r r e n t − H t a r g e t \frac{\partial L(\alpha)}{\partial \alpha}=H_{current}-H_{target} ∂α∂L(α)=Hcurrent−Htarget

  • H c u r r e n t − H t a r g e t > 0 H_{current}-H_{target}>0 Hcurrent−Htarget>0,说明探索过度,需要减少 α \alpha α
  • H c u r r e n t − H t a r g e t < 0 H_{current}-H_{target}<0 Hcurrent−Htarget<0,说明探索不足,需要增大 α \alpha α
    说明适用梯度下降法进行对 α \alpha α适当调节。
最优状态

根据最优策略的定义,我们将最优策略代入价值函数中即可得到最优状态价值(这是符合最优贝尔曼方程的),数学表达为:

V ∗ ( s ) = E a ∼ π Q ( s , a ) − α ( Q ( s , a ) α − log ⁡ Z ( s ) ) = E a ∼ π α log ⁡ Z ( s ) = α log ⁡ Z ( s ) = α log ⁡ ∑ a exp ⁡ ( Q ∗ ( s , a ) α ) V^{*}(s)=\mathbb{E}{a\sim \pi}Q(s,a)-\\alpha(\\frac{Q(s,a)}{\\alpha}-\\log Z(s))=\mathbb{E}{a\sim \pi}\\alpha \\log Z(s)=\alpha \log Z(s)=\alpha \log\sum_a \exp(\frac{Q^{*}(s,a)}{\alpha}) V∗(s)=Ea∼πQ(s,a)−α(αQ(s,a)−logZ(s))=Ea∼παlogZ(s)=αlogZ(s)=αloga∑exp(αQ∗(s,a))

证明Soft 贝尔曼算子同样是压缩映射

由前面博客可知,贝尔曼期望能收敛的底层数学依据是巴拿赫不动点定理的保证。

根据巴拿赫不动点定理的保证,任何在完备度量空间上的压缩映射都有:

  • 唯一的不动点
  • 从任意初始位置出发,都能收敛到唯一不动点。

因此证明soft贝尔曼期望能唯一收敛的核心是证明soft贝尔曼算子是压缩映射(非扩展)

( T ∗ Q ) ( s , a ) = r ( s , a ) + γ E s ′ ∼ P α log ⁡ ∑ a ′ exp ⁡ ( Q ( s ′ , a ′ ) α ) (T^{*}Q)(s,a)=r(s,a)+\gamma\mathbb{E}_{s^{'}\sim P}\\alpha\\log\\sum_{a\^{'}}\\exp(\\frac{Q(s\^{'},a\^{'})}{\\alpha}) (T∗Q)(s,a)=r(s,a)+γEs′∼Pαloga′∑exp(αQ(s′,a′))

证明的关键在于:Log-Sum-Exp 函数具有非扩张性,即它不会放大误差。

由于无穷范数的定义就是取绝对值的最大值。

∣ ∣ T ∗ Q 1 − T ∗ Q 2 ∣ ∣ ∞ = γ max ⁡ s , a ∣ E ( s ′ , a ′ ) ∼ P ( ⋅ ∣ s , a ) α log ⁡ ∑ a ′ exp ⁡ ( Q 1 ( s ′ , a ′ ) α ) − α log ⁡ ∑ a ′ exp ⁡ ( Q 2 ( s ′ , a ′ ) α ) ∣ ||T^{*}Q_1-T^{*}Q_2||{\infty}=\gamma\max{s,a}|\mathbb{E}_{(s^{'},a^{'})\sim P(\cdot|s,a)}\\alpha\\log\\sum_{a\^{'}}\\exp(\\frac{Q_1(s\^{'},a\^{'})}{\\alpha})-\\alpha\\log\\sum_{a\^{'}}\\exp(\\frac{Q_2(s\^{'},a\^{'})}{\\alpha})| ∣∣T∗Q1−T∗Q2∣∣∞=γs,amax∣E(s′,a′)∼P(⋅∣s,a)αloga′∑exp(αQ1(s′,a′))−αloga′∑exp(αQ2(s′,a′))

max ⁡ 下面的 s , a 代表的是初始状态 − 动作,期望内的括号代表下一个状态 − 动作 \max下面的s,a代表的是初始状态-动作,期望内的括号代表下一个状态-动作 max下面的s,a代表的是初始状态−动作,期望内的括号代表下一个状态−动作

= γ max ⁡ s , a ∣ E s ′ ∼ P ( ⋅ ∣ s , a ) V 1 ( s ′ ) − V 2 ( s ′ ) ∣ =\gamma\max_{s,a}|\mathbb{E}_{s^{'}\sim P(\cdot|s,a)}V_1(s\^{'})-V_2(s\^{'})| =γs,amax∣Es′∼P(⋅∣s,a)V1(s′)−V2(s′)

由期望值小于最大值可得:

≤ γ max ⁡ s ′ [ V 1 ( s ′ ) − V 2 ( s ′ ) ∣ \le\gamma\max_{s^{'}}[V_1(s^{'})-V_2(s^{'})| ≤γs′max[V1(s′)−V2(s′)∣

= γ max ⁡ s ′ ∣ α log ⁡ ∑ a ′ exp ⁡ ( Q 1 ( s ′ , a ′ ) α ) − α log ⁡ ∑ a ′ exp ⁡ ( Q 2 ( s ′ , a ′ ) α ) ∣ =\gamma\max_{s^{'}}|\alpha\log\sum_{a^{'}}\exp(\frac{Q_1(s^{'},a^{'})}{\alpha})-\alpha\log\sum_{a^{'}}\exp(\frac{Q_2(s^{'},a^{'})}{\alpha})| =γs′max∣αloga′∑exp(αQ1(s′,a′))−αloga′∑exp(αQ2(s′,a′))∣

Log-Sum-Exp 函数具有非扩张性,即它不会放大误差。

≤ γ max ⁡ s ′ , a ′ ∣ Q 1 ( s ′ , a ′ ) − Q 2 ( s ′ , a ′ ) ∣ = γ ∣ ∣ Q 1 − Q 2 ∣ ∣ ∞ \le\gamma\max_{s^{'},a^{'}}|Q_1(s^{'},a^{'})-Q_2(s^{'},a^{'})|=\gamma||Q_1-Q_2||_{\infty} ≤γs′,a′max∣Q1(s′,a′)−Q2(s′,a′)∣=γ∣∣Q1−Q2∣∣∞

证毕,soft贝尔曼期望就是压缩算子,满足巴拿赫不动点定理。

当 α → 0 , V ∗ ( s ) \alpha\rightarrow0,V^{*}(s) α→0,V∗(s)相当于 max ⁡ a Q ( s , a ) \max_{a} Q(s,a) maxaQ(s,a)

(一)

Q m a x = α log ⁡ ( exp ⁡ ( Q / α ) ) ≤ α log ⁡ ∑ a exp ⁡ ( Q / α ) Q_{max}=\alpha\log(\exp(Q/\alpha))\le \alpha\log \sum_a\exp(Q/\alpha) Qmax=αlog(exp(Q/α))≤αloga∑exp(Q/α)

(二)

假设共有N个动作,则:

∑ a exp ⁡ ( Q / α ) ≤ N ⋅ exp ⁡ ( Q m a x / α ) (3.2) \sum_a\exp(Q/\alpha)\le N\cdot \exp(Q_{max}/\alpha)\text{(3.2)} a∑exp(Q/α)≤N⋅exp(Qmax/α)(3.2)

等价于:

α log ⁡ ∑ a exp ⁡ ( Q / α ) ≤ α log ⁡ N ⋅ exp ⁡ ( Q m a x / α ) = α log ⁡ N + Q m a x (3.3) \alpha\log \sum_a\exp(Q/\alpha)\le \alpha\logN\\cdot \\exp(Q_{max}/\\alpha)=\alpha\log N+Q_{max}\text{(3.3)} αloga∑exp(Q/α)≤αlogN⋅exp(Qmax/α)=αlogN+Qmax(3.3)

由(3.2)(3.3)以及夹逼定理可证:

当 α → 0 , V ∗ ( s ) \alpha\rightarrow0,V^{*}(s) α→0,V∗(s)相当于 max ⁡ a Q ( s , a ) \max_{a} Q(s,a) maxaQ(s,a)

代入soft贝尔曼期望方程: Q ( s t , a t ) = r ( s t , a t ) + γ E s t + 1 V ( s t + 1 ) Q(s_t,a_t)=r(s_t,a_t)+\gamma\mathbb{E}{s{t+1}}V(s_{t+1}) Q(st,at)=r(st,at)+γEst+1V(st+1)可得:

Q ∗ ( s , a ) = r ( s , a ) + γ E s α log ⁡ ∑ a exp ⁡ ( Q ∗ ( s , a ) α ) Q^{*}(s,a)=r(s,a)+\gamma\mathbb{E}{s{}}\\alpha \\log\\sum_a \\exp(\\frac{Q\^{\*}(s,a)}{\\alpha}) Q∗(s,a)=r(s,a)+γEsαloga∑exp(αQ∗(s,a))


#代码实现

首先在倒立摆环境下进行实验,然后再尝试将 SAC 应用到与离散动作交互的车杆环境。

1、导入所需要的库

python 复制代码
import random
import gymnasium as gym
import numpy as np
from tqdm import tqdm
import torch
import torch.nn.functional as F
from torch.distributions import Normal
import matplotlib.pyplot as plt
import rl_utils

2、创造Q函数来critic

python 复制代码
class QValueNetContinous(torch.nn.Module):
    def __init__(self,state_dim,hidden_dim,action_dim):
        super(QValueNetContinous,self).__init__()
        self.fc1=torch.nn.Linear(state_dim+action_dim,hidden_dim)
        self.fc2=torch.nn.Linear(hidden_dim,hidden_dim)
        self.fc_out=torch.nn.Linear(hidden_dim,1)
    def forward(self,x,a):
        cat=torch.cat([x,a],dim=1)
        #提取特征
        x=F.relu(self.fc1(cat))
        x=F.relu(self.fc2(x))
        return self.fc_out(x)

3、创造生成动作及其对应的分布log值得actor函数

python 复制代码
class policyNetContinuous(torch.nn.Module):
    def __init__(self,state_dim,hidden_dim,action_dim,action_bound):
        super(policyNetContinuous, self).__init__()
        self.fc1=torch.nn.Linear(state_dim,hidden_dim)
        #mu/std分别是均值和标准差
        self.mu=torch.nn.Linear(hidden_dim,action_dim)
        self.std=torch.nn.Linear(hidden_dim,action_dim)
        self.action_bound=action_bound

    def forward(self,x):
        #提取特征值
        x=F.relu(self.fc1(x))
        #根据特征值生成均值和平方差
        mu=self.mu(x)
            #std需要F.softplus的原因是平方差必须大于等于0,确保标准差始终为正,且输出平滑、梯度流畅。这是连续动作空间策略网络的常见做法。
        std=F.softplus(self.std(x))
        #创造采样空间
        dist=Normal(mu,std)
        #在采样空间里采集样本,normal_sample则是依据mu/std的正太分布下获取的动作
        normal_action_sample=dist.rsample() # rsample()是重参数化采样让采样过程的梯度不断裂,Q 值梯度 ∇a Q 能通过链式法则传递给 Actor 参数。这是 SAC 的核心技术。
        #获取所采动作样本概率分布的log值
        log_prob=dist.log_prob(normal_action_sample)
        #将对应于正太分布的概率分布的log值映射到torch.tanh(后续也需要在正太分布采样的动作映射到tanh中)
        log_prob = log_prob - torch.log(1 - torch.tanh(normal_action_sample).pow(2) + 1e-7)
        #由于倒立摆环境下的动作在[-2,2]内部,首先需要将在正太分布下采样的动作映射到[-1,1]的动作空间内
        action=torch.tanh(normal_action_sample)
        #action*self.action_bound相当于整个模型的幅度等比例增加,不影响概率分布
        action=action*self.action_bound
        return action,log_prob

.rsample() 重参数采样与普通采样的区别

核心:rsample() 构建了一个可求梯度的函数,而普通采样只是一个不可导的随机操作。这正是 SAC 能够利用 Critic 的 Q 值梯度来直接指导 Actor 更新的根本原因。

在数学上,rsample() 将动作 a 表达为 Actor 参数θ 和独立噪声 ϵ的确定性可微函数。

a = μ θ + σ θ ⋅ ϵ , ϵ ∼ N ( 0 , 1 ) a=\mu_{\theta}+\sigma_{\theta}\cdot \epsilon,\epsilon\sim N(0,1) a=μθ+σθ⋅ϵ,ϵ∼N(0,1)

因此,动作 a 不再是"从分布中随机抽出来的数",而是"以 ϵ 为输入、以 θ 为参数的可微函数"。这与 DDPG 的确定性动作 a = μ θ a=\mu_{\theta} a=μθ在本质上是同构的------两者都构建了一个从参数到动作的可微映射。区别只在于 SAC 的动作函数多了一个独立噪声项 σ θ ( s ) ⋅ ϵ \sigma_{\theta}(s)\cdot \epsilon σθ(s)⋅ϵ这是探索的来源。

反向传播时,梯度会沿着以下路径流畅传递:

∇ θ Q ( s , a ) = ∇ a Q ( s , a ) ⋅ ∇ θ a = ∇ a Q ( s , a ) ⋅ ∇ θ ( μ θ + σ θ ⋅ ϵ ) \nabla_{\theta}Q(s,a)=\nabla_a Q(s,a)\cdot \nabla_{\theta}a=\nabla_a Q(s,a)\cdot \nabla_{\theta}(\mu_{\theta}+\sigma_{\theta}\cdot \epsilon) ∇θQ(s,a)=∇aQ(s,a)⋅∇θa=∇aQ(s,a)⋅∇θ(μθ+σθ⋅ϵ)

Critic 的 Q 值梯度 ∇ a Q ( s , a ) \nabla_a Q(s,a) ∇aQ(s,a) 告诉 Actor"动作应该往哪个方向改",链式法则将这个方向转化为 Actor 参数 μ θ \mu_{\theta} μθ和 σ θ \sigma_{\theta} σθ的更新方向。 整个过程在 actor_loss.backward() 中自动完成。

F.softplus 是 PyTorch 中一个将任意实数平滑地映射为正数的激活函数

数学公式:

s o f t p l u s ( x ) = log ⁡ ( 1 + e x ) softplus(x)=\log(1+e^{x}) softplus(x)=log(1+ex)

这个公式保证了无论你喂给它多大的负数,它的输出永远是正的;而随着输入变大,它的输出会越来越接近 x 本身(近似线性)。

3、SAC的主框架

python 复制代码
class SACContinous:
    def __init__(self,state_dim,hidden_dim,action_dim,action_bound,actor_lr,critic_lr,alpha_lr,target_entropy,tau,gamma,device):
        self.actor=policyNetContinuous(state_dim,hidden_dim,action_dim,action_bound).to(device)
        #用两个Q函数主要是避免估值过高,选两个取最小的
        self.critic_1=QValueNetContinous(state_dim,hidden_dim,action_dim).to(device)
        self.critic_2=QValueNetContinous(state_dim,hidden_dim,action_dim).to(device)
        self.target_critic_1=QValueNetContinous(state_dim,hidden_dim,action_dim).to(device)
        self.target_critic_2=   QValueNetContinous(state_dim,hidden_dim,action_dim).to(device)
        #令目标Q网络的初始参数和Q网络一样
        self.target_critic_1.load_state_dict(self.critic_1.state_dict())
        self.target_critic_2.load_state_dict(self.critic_2.state_dict())
        #给需要神经网络更新的网络优化器
        self.actor_optimizer=torch.optim.Adam(self.actor.parameters(),lr=actor_lr)
        self.critic_1_optimizer=torch.optim.Adam(self.critic_1.parameters(),lr=critic_lr)
        self.critic_2_optimizer=torch.optim.Adam(self.critic_2.parameters(),lr=critic_lr)

        #alpha操作:1、初始化2、明确求梯度3、指定优化器(使用alpha的log值,可以使训练结果比较稳定)
        self.log_alpha=torch.tensor(np.log(0.01),dtype=torch.float32)
        self.log_alpha.requires_grad=True
        self.log_alpha_optimizer=torch.optim.Adam([self.log_alpha],lr=alpha_lr)

        #目标熵的大小
        self.target_entropy=target_entropy
        self.tau=tau
        self.gamma=gamma
        self.tau=tau
        self.device=device

    def take_action(self,state):
        state=torch.tensor([state],dtype=torch.float32).to(self.device)
        action=self.actor(state)[0]
        return [action.item()]

    #计算目标Q值
    def calc_target(self,rewards,next_states,dones):
        next_actions,log_prob=self.actor(next_states)
        entropy=-log_prob
        q1_value=self.target_critic_1(next_states,next_actions)
        q2_value=self.target_critic_2(next_states,next_actions)
        next_value=torch.min(q1_value,q2_value)+self.log_alpha.exp()*entropy
        td_targets=rewards+self.gamma*next_value*(1-dones)
        return td_targets

    # 缓更新目标网络
    def soft_update(self,net,target_net):
        for param_target,param in zip(target_net.parameters(),net.parameters()):
            param_target.data.copy_(param_target.data * (1.0 - self.tau) +
                                    param.data * self.tau)

    def update(self,transition_dict):
        states = torch.tensor(transition_dict['states'],
                              dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions'],
                               dtype=torch.float).view(-1, 1).to(self.device)
        rewards = torch.tensor(transition_dict['rewards'],
                               dtype=torch.float).view(-1, 1).to(self.device)
        next_states = torch.tensor(transition_dict['next_states'],
                                   dtype=torch.float).to(self.device)
        dones = torch.tensor(transition_dict['dones'],
                             dtype=torch.float).view(-1, 1).to(self.device)
        # 和之前章节一样,对倒立摆环境的奖励进行重塑以便训练
        rewards = (rewards + 8.0) / 8.0

        #更新两个Q网络
        td_target=self.calc_target(rewards,next_states,dones)
        critic_1_loss=torch.mean(F.mse_loss(self.critic_1(states,actions),td_target))
        critic_2_loss=torch.mean(F.mse_loss(self.critic_2(states,actions),td_target))
        self.critic_1_optimizer.zero_grad()
        critic_1_loss.backward()
        self.critic_1_optimizer.step()

        self.critic_2_optimizer.zero_grad()
        critic_2_loss.backward()
        self.critic_2_optimizer.step()

        #更新策略网络
        new_actions,log_probs=self.actor(states)
        entropy=-log_probs
        q1_value=self.critic_1(states,new_actions)
        q2_value=self.critic_2(states,new_actions)
        actor_loss=torch.mean(-entropy * self.log_alpha.exp()-torch.min(q1_value,q2_value))
        self.actor_optimizer.zero_grad()
        actor_loss.backward()
        self.actor_optimizer.step()

        #更新alpha值
        alpha_loss=torch.mean((entropy-self.target_entropy).detach()*self.log_alpha.exp())
        self.log_alpha_optimizer.zero_grad()
        alpha_loss.backward()
        self.log_alpha_optimizer.step()

        self.soft_update(self.critic_1,self.target_critic_1)
        self.soft_update(self.critic_2,self.target_critic_2)

α \alpha α在代码中用 α = e ln ⁡ ( α ) \alpha=e^{\ln(\alpha)} α=eln(α)表示的原因:

1、保证 α \alpha α永远为正

温度系数 α 的物理意义是"探索的强度",它必须永远是一个正数。如果直接用一个无约束的神经网络参数去存 α,优化器很可能在某个步骤把它更新成一个负数。一个负的温度系数会彻底摧毁 SAC 的数学基础------熵项的符号会反转,导致优化目标完全错误。

因此,我们需要一种无约束的参数化来隐式地保证 α > 0。设 α = exp(log_alpha)。现在,log_alpha 可以是任何实数(负无穷到正无穷),但 α 永远大于 0。无论优化器怎么更新 log_alpha,你都不用担心 α 会越界变成负数。

2、平滑梯度:匹配更新尺度

这是数值稳定的核心。假设你想把 α 从一个很小的值(比如 0.01)开始调节。如果直接优化 α,损失函数对 α 的梯度是线性的。在参数空间的一个固定步长,对应着 α 本身的一个绝对值变化。当 α 很小时,这个绝对值变化可能相对很大,导致它剧烈震荡甚至跳到负值。

如果优化 log_alpha,根据链式法则,损失对 log_alpha 的梯度会被 α 本身的尺度所缩放。这意味着,当 α 很小时,参数空间里的梯度也相应变小,自动产生更小的步长;当 α 很大时,梯度变大,步长也更大。这种自动的尺度调节,让 α 在一个很宽的动态范围(比如 0.001 到 100)内都能平稳更新,不会因为参数太小而卡死,也不会因为参数太大而剧烈震荡。

数学推导如下:

设 L 为损失函数(即 alpha_loss)。我们优化的参数是 log_alpha,而真正的温度系数α=exp(log_alpha)。

根据链式法则:

∂ L ∂ log alpha = ∂ L ∂ α ⋅ ∂ α ∂ log alpha = ∂ L ∂ α ⋅ e log alpha = ∂ L ∂ α ⋅ α \frac{\partial L}{\partial \text{log}\text{ }\text{alpha}}= \frac{\partial L}{\partial \alpha} \cdot \frac{\partial \alpha}{\partial \text{log}\text{ }\text{alpha}}= \frac{\partial L}{\partial \alpha} \cdot e^{\text{log}\text{ }\text{alpha}}= \frac{\partial L}{\partial \alpha} \cdot \alpha ∂log alpha∂L=∂α∂L⋅∂log alpha∂α=∂α∂L⋅elog alpha=∂α∂L⋅α

关键就在这个 ⋅α 上。这意味着,在参数空间里,优化器实际看到的梯度 ∂ L ∂ log alpha \frac{\partial L}{\partial \text{log}\text{ }\text{alpha}} ∂log alpha∂L与 α \alpha α本身成正比。

因此:

  • 当 α \alpha α较大时,参数更新步长更大。
  • 当 α \alpha α较小时,参数更新步长更小。
相关推荐
罗超驿1 小时前
2.算法效率的核心密码:时间复杂度和空间复杂度详解
java·数据结构·算法
:-)2 小时前
算法-堆排序
数据结构·算法·排序算法
星马梦缘2 小时前
算法设计 期末复习笔记
算法·动态规划·二分图·最小生成树·线性规划·最大流
鱼儿也有烦恼3 小时前
题型归纳与重要模版
算法
菜鸟不爱学4 小时前
第P4周:猴痘病识别
算法
QN1幻化引擎4 小时前
我写了一个「有八层意识结构」的 Python 认知引擎,它没有用任何 LLM
人工智能·算法·架构
Keven_115 小时前
算法札记:如何卡SPFA
算法·spfa
imuliuliang6 小时前
关于并发哈希表的锁分段机制与冲突减少策略7
算法
可编程芯片开发6 小时前
基于电压电流双闭环控制的三相整流器系统simulink建模与仿真
算法