当前位置: 首页 > news >正文

强化学习的几个主要方法(策略梯度、PPO、REINFORCE实现等)(上)

本笔记有大量参考蘑菇书EasyRL https://datawhalechina.github.io/easy-rl/#/ 包括其配图和部分文本。

1. 基本概念

1.1 基本流程

强化学习是一种学习框架,其中智能体(Agent) 通过与 环境(Environment) 的交互,在每一步从环境中接收状态(State)和奖励(Reward),并通过选择行动(Action)来学习最优策略(Policy),以最大化其累计奖励。

换句话说,强化学习是让智能体找到一种行为策略,使得它在长期内获得的奖励总和(通常是期望值)最大化。

图中的每个元素代表以下含义:

  • Agent(智能体):这是我们的学习者,它会根据当前的状态(State)做出一个动作(Action)。
  • Environment(环境):这是智能体所处的外部世界,它会根据智能体的动作,给出相应的反馈:
    • Reward(奖励):智能体执行动作后,环境会给出一个数值表示的奖励。正奖励表示动作的好坏,负奖励表示动作的坏处。
    • Next State(下一个状态):执行动作后,环境的状态会发生变化,进入下一个状态。
  • State(状态):环境在某个时刻的具体情况,比如游戏中的分数、机器人的位置等。
  • Action(动作):智能体可以采取的各种行为,比如向左走、开火等。

1.2 马尔可夫过程

**马尔科夫性质:**一个随机过程的下一个状态只取决于当前状态,而与过去的状态无关。即:
p ( X t + 1 = x t + 1 ∣ X 0 : t = x 0 : t ) = p ( X t + 1 = x t + 1 ∣ X t = x t ) p(X_{t + 1}=x_{t + 1}|X_{0:t}=x_{0:t}) = p(X_{t + 1}=x_{t + 1}|X_{t}=x_{t}) p(Xt+1=xt+1X0:t=x0:t)=p(Xt+1=xt+1Xt=xt)
马尔可夫性质也可以描述为给定当前状态时,将来的状态与过去状态是条件独立的。如果某一个过程满足马尔可夫性质,那么未来的转移与过去的是独立的,它只取决于现在。马尔可夫性质是所有马尔可夫过程的基础。

也就是说,满足以下关系,其中 h t = { s 1 , s 2 , s 3 , … , s t } h_t = \{ s_1, s_2, s_3, \ldots, s_t \} ht={s1,s2,s3,,st} h t h_t ht包含了之前所有状态
p ( s t + 1 ∣ s t ) = p ( s t + 1 ∣ h t ) p(s_{t + 1} | s_{t}) = p(s_{t + 1} | h_{t}) p(st+1st)=p(st+1ht)
从当前 s t s_t st转移到 s t + 1 s_{t+1} st+1,它是直接就等于它之前所有的状态转移到 s t + 1 s_{t+1} st+1​。

**马尔可夫链:**离散时间的马尔可夫过程。其状态是有限的,例如:

可以用状态转移矩阵来描述状态转移过程
P = [ p ( s 1 ∣ s 1 ) p ( s 2 ∣ s 1 ) ⋯ p ( s N ∣ s 1 ) p ( s 1 ∣ s 2 ) p ( s 2 ∣ s 2 ) ⋯ p ( s N ∣ s 2 ) ⋮ ⋮ ⋱ ⋮ p ( s 1 ∣ s N ) p ( s 2 ∣ s N ) ⋯ p ( s N ∣ s N ) ] P = \begin{bmatrix} p(s_1|s_1) & p(s_2|s_1) & \cdots & p(s_N|s_1) \\ p(s_1|s_2) & p(s_2|s_2) & \cdots & p(s_N|s_2) \\ \vdots & \vdots & \ddots & \vdots \\ p(s_1|s_N) & p(s_2|s_N) & \cdots & p(s_N|s_N) \end{bmatrix} P= p(s1s1)p(s1s2)p(s1sN)p(s2s1)p(s2s2)p(s2sN)p(sNs1)p(sNs2)p(sNsN)
状态转移矩阵类似于条件概率(conditional probability),它表示当我们知道当前我们在状态 s t s_t st​ 时,到达下面所有状态的概率。所以它的每一行描述的是从一个节点到达所有其他节点的概率。

1.3 马尔可夫奖励过程

马尔可夫链加上奖励函数。在马尔可夫奖励过程中,状态转移矩阵和状态都与马尔可夫链一样,只是多了奖励函数(reward function)。在强化学习中我们不知要关注过程,还要关注在过程中每一步所能获得到的奖励。

这里我们进一步定义一些概念。范围(horizon) 是指一个回合的长度(每个回合最大的时间步数),它是由有限个步数决定的。 **回报(return)**可以定义为奖励的逐步叠加,假设时刻 t t t后的奖励序列为 r t + 1 , r t + 2 , r t + 3 , ⋯ r_{t+1},r_{t+2},r_{t+3},⋯ rt+1,rt+2,rt+3,,则回报为

回报(return) G t G_t Gt可以表示为从时刻t开始直到回合结束获得的所有奖励的折扣总和:
G t = r t + 1 + γ r t + 2 + γ 2 r t + 3 + . . . + γ T − t − 1 r T G_t = r_{t+1} + \gamma r_{t+2} + \gamma^2 r_{t+3} + ... + \gamma^{T-t-1} r_T Gt=rt+1+γrt+2+γ2rt+3+...+γTt1rT
其中,γ∈[0, 1]为折扣因子。随着时间的推移,未来的奖励会打折扣,表示我们更看重当前的奖励。

当我们有了回报的概念后,就可以定义状态价值函数(state-value function)V(s)。状态价值函数表示从状态s出发,按照当前策略一直执行下去,直到回合结束,所能获得的期望回报:
V π ( s ) = E [ G t ∣ S t = s ] = E [ r t + 1 + γ r t + 2 + γ 2 r t + 3 + ⋯ + γ T − t − 1 r T ∣ S t = s ] = E [ r t + 1 ∣ S t = s ] + γ E [ G t + 1 ∣ S t = s ] = E [ r t + 1 ∣ S t = s ] + γ E [ V π ( S t + 1 ) ∣ S t = s ] \begin{align} V^{\pi}(s) &= \mathbb{E}[G_t \mid S_t = s] \\ &= \mathbb{E}[r_{t+1} + \gamma r_{t+2} + \gamma^2 r_{t+3} + \cdots + \gamma^{T-t-1}r_T \mid S_t = s] \\ &= \mathbb{E}[r_{t+1} \mid S_t = s] + \gamma \mathbb{E}[G_{t+1} \mid S_t = s] \\ &= \mathbb{E}[r_{t+1} \mid S_t = s] + \gamma \mathbb{E}[V^{\pi}(S_{t+1}) \mid S_t = s] \end{align} Vπ(s)=E[GtSt=s]=E[rt+1+γrt+2+γ2rt+3++γTt1rTSt=s]=E[rt+1St=s]+γE[Gt+1St=s]=E[rt+1St=s]+γE[Vπ(St+1)St=s]
其中, G t G_t Gt是之前定义的折扣回报(discounted return) R ( s ) R(s) R(s)是奖励函数,我们对 G t G_t Gt​取了一个期望,期望就是从这个状态开始,我们可能获得多大的价值。所以期望也可以看成未来可能获得奖励的当前价值的表现,就是当我们进入某一个状态后,我们现在有多大的价值。

这些话说的有点拗口,通俗来说就是 G G G表示当下即时奖励和所有持久奖励等一切奖励的加权和,它是相对与一个序列中的一个状态节点来评估的,而价值函数 V ( s ) V(s) V(s)是对某一个状态来评估而,把这个状态从序列中抽象出来了。

贝尔曼方程
V ( s ) = R ( s ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s ) V ( s ′ ) V(s) = R(s) + \gamma \sum_{s' \in S} P(s'|s) V(s') V(s)=R(s)+γsSP(ss)V(s)
一种快速求 V ( s ) V(s) V(s)的方法。

1.4 马尔可夫决策过程

马尔可夫决策过程(MDP) = 马尔可夫奖励(MRP) + 智能体动作因素

MDP 的目标是找到一个最优策略 π,使得智能体在给定状态下选择最优的动作,从而最大化长期累积的折扣奖励。

状态价值函数:
V π ( s ) = E π [ G t ∣ S t = s ] = E π [ R t + 1 + γ G t + 1 ∣ S t = s ] = E π [ R t + 1 + γ V π ( S t + 1 ) ∣ S t = s ] \begin{align} V_{\pi}(s) &= \mathbb{E}_{\pi}[G_t | S_t = s] \\ &= \mathbb{E}_{\pi}[R_{t+1} + \gamma G_{t+1} | S_t = s] \\ &= \mathbb{E}_{\pi}[R_{t+1} + \gamma V_{\pi}(S_{t+1}) | S_t = s] \end{align} Vπ(s)=Eπ[GtSt=s]=Eπ[Rt+1+γGt+1St=s]=Eπ[Rt+1+γVπ(St+1)St=s]
动作价值状态函数:
Q π ( s , a ) = E π [ G t ∣ S t = s , A t = a ] = E π [ R t + 1 + γ G t + 1 ∣ S t = s , A t = a ] = E π [ R t + 1 + γ Q π ( S t + 1 , A t + 1 ) ∣ S t = s , A t = a ] \begin{align} Q_{\pi}(s, a) &= \mathbb{E}_{\pi}[G_t | S_t = s, A_t = a] \\ &= \mathbb{E}_{\pi}[R_{t+1} + \gamma G_{t+1} | S_t = s, A_t = a] \\ &= \mathbb{E}_{\pi}[R_{t+1} + \gamma Q_{\pi}(S_{t+1}, A_{t+1}) | S_t = s, A_t = a] \end{align} Qπ(s,a)=Eπ[GtSt=s,At=a]=Eπ[Rt+1+γGt+1St=s,At=a]=Eπ[Rt+1+γQπ(St+1,At+1)St=s,At=a]

A t A_t At表示t时刻的动作, Q Q Q函数相对上面的 V V V函数来说多考虑进去了一个动作的因素,而不只是单纯的状态。

马尔可夫决策的贝尔曼方程
V π ( s ) = ∑ a ∈ A π ( a ∣ s ) [ R ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V π ( s ′ ) ] V_{\pi}(s) = \sum_{a \in A} \pi(a|s) \left[ R(s, a) + \gamma \sum_{s' \in S} P(s'|s, a) V_{\pi}(s') \right] Vπ(s)=aAπ(as)[R(s,a)+γsSP(ss,a)Vπ(s)]

Q π ( s , a ) = R ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) [ ∑ a ′ ∈ A π ( a ′ ∣ s ′ ) Q π ( s ′ , a ′ ) ] Q_{\pi}(s, a) = R(s, a) + \gamma \sum_{s' \in S} P(s'|s, a) \left[ \sum_{a' \in A} \pi(a'|s') Q_{\pi}(s', a') \right] Qπ(s,a)=R(s,a)+γsSP(ss,a)[aAπ(as)Qπ(s,a)]

2. REINFORCE

2.1 策略梯度算法

由于REINFORCE是最简单的侧率梯度算法,所以这里先介绍策略梯度算法

强化学习有 3 个组成部分:演员(actor)、环境和奖励函数。显然我们能控制的只有演员,环境和奖励函数是客观存在的。智能体玩视频游戏时,演员负责操控游戏的摇杆, 比如向左、向右、开火等操作;环境就是游戏的主机,负责控制游戏的画面、负责控制怪兽的移动等;奖励函数就是当我们做什么事情、发生什么状况的时候,可以得到多少分数, 比如打败一只怪兽得到 20 分等。在强化学习里,环境与奖励函数不是我们可以控制的,它们是在开始学习之前给定的。我们唯一需要做的就是调整演员里面的策略,使得演员可以得到最大的奖励。演员里面的策略决定了演员的动作,即给定一个输入,它会输出演员现在应该要执行的动作。

在下面的例子中,我们会用一个神经网络来当做演员,把环境输入,由神经网络给出动作。例如玩一个走格子的游戏,游戏规则如下,创建一个 n × n n \times n n×n的棋盘,旗子在 ( 0 , 0 ) (0, 0) (0,0)处,要到达 ( n , n ) (n, n) (n,n)处,并用最少的步数走到。(也可以为了增加难度在中间设置一点障碍)棋子有四种动作,分别是“上、下、左、右”。

有四个动作,所以神经网络有四个输出,根据四个输出的概率进行采样选择下一步的动作。

智能体的每一步动作都会有一个奖励,为了让他能尽快走到终点,因此它每走到一个格子,除非那个格子是终点,我们就把那个格子的奖励设置为-1,终点的奖励可以设置为10,中间也可以设置障碍,例如将某个点的奖励设置为-100,这样就让模型学会避开这个点。

我们的游戏过程应该是这样的:

在一场游戏里面,我们把环境输出的 s s s与演员输出的动作 a a a 全部组合起来,就是一个轨迹
τ = { s 1 , a 1 , s 2 , a 2 , … , s t , a t } \tau = \{s_1, a_1, s_2, a_2, \dots, s_t, a_t\} τ={s1,a1,s2,a2,,st,at}
给定演员的参数 θ θ θ(就是提到的神经网络的参数),我们可以计算某个轨迹 τ \tau τ发生的概率为
p θ ( τ ) = p ( s 1 ) p θ ( a 1 ∣ s 1 ) p ( s 2 ∣ s 1 , a 1 ) p θ ( a 2 ∣ s 2 ) p ( s 3 ∣ s 2 , a 2 ) ⋯ = p ( s 1 ) ∏ t = 1 T p θ ( a t ∣ s t ) p ( s t + 1 ∣ s t , a t ) \begin{align} p_{\theta}(\tau) &= p(s_1) p_{\theta}(a_1|s_1) p(s_2|s_1, a_1) p_{\theta}(a_2|s_2) p(s_3|s_2, a_2) \cdots \\ &= p(s_1) \prod_{t=1}^{T} p_{\theta}(a_t|s_t) p(s_{t+1}|s_t, a_t) \end{align} pθ(τ)=p(s1)pθ(a1s1)p(s2s1,a1)pθ(a2s2)p(s3s2,a2)=p(s1)t=1Tpθ(atst)p(st+1st,at)
这个计算是一步一步的,并不是并行的。其中 p ( s t + 1 ∣ s t , a t ) p(s_{t+1} \mid s_t, a_t) p(st+1st,at)意思是在环境为 s t s_t st, 演员采取动作 a t a_t at的情况下环境变成 s t + 1 s_{t+1} st+1的概率。

那既然每一把游戏都是一个轨迹 τ \tau τ,每一把游戏都会有一个得分,这个得分按照我之前设定的规则应该是每个动作的回报 G t G_t Gt然后求和,由于整局游戏我已经玩完了,所以之后的动作是什么奖励是什么我也知道,因此我们能算出这个 G t G_t Gt
G t = r t + 1 + γ r t + 2 + γ 2 r t + 3 + . . . + γ T − t − 1 r T G_t = r_{t+1} + \gamma r_{t+2} + \gamma^2 r_{t+3} + ... + \gamma^{T-t-1} r_T Gt=rt+1+γrt+2+γ2rt+3+...+γTt1rT
我们把这局游戏每一步走完之后的 G t G_t Gt都存下来,如果这个 G t G_t Gt大于零,就说明这个动作有好处,可以增大采样采到这个动作的概率,注意,增加的是 p θ ( a t ∣ s t ) p_{\theta}(a_t|s_t) pθ(atst)的概率,而不是 p θ ( a t ) p_{\theta}(a_t) pθ(at)的值,需要联系环境来考虑问题。就跟人斗地主一样,你是农民,你的队友手里只剩下两张王了,这个时候最好的决策就是将自己手里的炸弹打出来增加奖励。你出炸弹的概率就几乎是百分之百, p ( 出炸弹 ∣ 队友手里只剩下两张王了 ) = 0.99 p(出炸弹 \mid 队友手里只剩下两张王了) = 0.99 p(出炸弹队友手里只剩下两张王了)=0.99,而在别的情况下你出炸弹的概率就没必要这么高,毕竟很少有人开局就出炸弹。所以模型要提高的是 p ( 出炸弹 ∣ 队友手里只剩下两张王了 ) p(出炸弹 \mid 队友手里只剩下两张王了) p(出炸弹队友手里只剩下两张王了)的值,而不是 p ( 出炸弹 ) p(出炸弹) p(出炸弹)。这个 G t G_t Gt暂时不会用到,后面会用到。

在强化学习里面,除了环境与演员以外,还有奖励函数。如下图所示,奖励函数根据在某一个状态采取的某一个动作决定这个动作可以得到的分数。对奖励函数输入 s 1 , a 1 s_1, a_1 s1,a1,它会输出 r 1 r_1 r1,输入 s 2 , a 2 s_2, a_2 s2,a2,它会输出 r 2 r_2 r2。 我们把轨迹所有的奖励 r r r都加起来,就得到了 R ( τ ) R(\tau) R(τ),其代表某一个轨迹 τ \tau τ的奖励。

那一把游戏能不能衡量一个演员(智能体)的能力呢,可能不行,因为你有可能是蒙的,那我们如何评价一个演员的能力呢,多玩几次,求期望:
R ˉ θ = ∑ τ R ( τ ) p θ ( τ ) \bar{R}_{\theta} = \sum_{\tau} R(\tau) p_{\theta}(\tau) Rˉθ=τR(τ)pθ(τ)
那么这个 R ˉ θ \bar{R}_{\theta} Rˉθ就是我们要优化的目标了。也可以表达成这样:
R ˉ θ = ∑ τ R ( τ ) p θ ( τ ) = E τ ∼ p θ ( τ ) [ R ( τ ) ] \bar{R}_{\theta} = \sum_{\tau} R(\tau) p_{\theta}(\tau) = \mathbb{E}_{\tau \sim p_{\theta}(\tau)}[R(\tau)] Rˉθ=τR(τ)pθ(τ)=Eτpθ(τ)[R(τ)]
因为我们要让奖励越大越好,所以可以使用梯度上升(gradient ascent)来最大化期望奖励。要进行梯度上升,我们先要计算期望奖励 R ˉ θ \bar{R}_{\theta} Rˉθ的梯度。我们对 R ˉ θ \bar{R}_{\theta} Rˉθ做梯度运算。
R ˉ θ = ∑ τ R ( τ ) p θ ( τ ) \bar{R}_{\theta} = \sum_{\tau} R(\tau) p_{\theta}(\tau) Rˉθ=τR(τ)pθ(τ)
θ \theta θ求偏导:
∇ R ˉ θ = ∑ τ R ( τ ) ∇ p θ ( τ ) \nabla \bar{R}_{\theta} = \sum_{\tau} R(\tau) \nabla p_{\theta}(\tau) Rˉθ=τR(τ)pθ(τ)
由于 ∇ l o g a ( f ( x ) ) = ∇ f ( x ) f ( x ) l n a \nabla log_a(f(x)) = \frac{\nabla f(x)}{f(x)lna} loga(f(x))=f(x)lnaf(x) l n a lna lna是常数,可以忽略(直接将底数看做是e更好理解下面的公式):
∇ p θ ( τ ) = p θ ( τ ) ∇ log ⁡ p θ ( τ ) ∇ p θ ( τ ) p θ ( τ ) = ∇ log ⁡ p θ ( τ ) \nabla p_{\theta}(\tau) = p_{\theta}(\tau) \nabla \log p_{\theta}(\tau) \\ \\ \frac{\nabla p_{\theta}(\tau)}{p_{\theta}(\tau)} = \nabla \log p_{\theta}(\tau) pθ(τ)=pθ(τ)logpθ(τ)pθ(τ)pθ(τ)=logpθ(τ)
带入 ∇ R ˉ θ \nabla \bar{R}_{\theta} Rˉθ得:
∇ R ˉ θ = ∑ τ R ( τ ) ∇ p θ ( τ ) = ∑ τ R ( τ ) p θ ( τ ) ∇ p θ ( τ ) p θ ( τ ) = ∑ τ R ( τ ) p θ ( τ ) ∇ log ⁡ p θ ( τ ) = E τ ∼ p θ ( τ ) [ R ( τ ) ∇ log ⁡ p θ ( τ ) ] \begin{align} \nabla \bar{R}_{\theta} &= \sum_{\tau} R(\tau) \nabla p_{\theta}(\tau) \\ &= \sum_{\tau} R(\tau) p_{\theta}(\tau) \frac{\nabla p_{\theta}(\tau)}{p_{\theta}(\tau)} \\ &= \sum_{\tau} R(\tau) p_{\theta}(\tau) \nabla \log p_{\theta}(\tau) \\ &= \mathbb{E}_{\tau \sim p_{\theta}(\tau)} \left[ R(\tau) \nabla \log p_{\theta}(\tau) \right] \end{align} Rˉθ=τR(τ)pθ(τ)=τR(τ)pθ(τ)pθ(τ)pθ(τ)=τR(τ)pθ(τ)logpθ(τ)=Eτpθ(τ)[R(τ)logpθ(τ)]
由于轨迹是采不完的,因此 E τ ∼ p θ ( τ ) \mathbb{E}_{\tau \sim p_{\theta}(\tau)} Eτpθ(τ)无法计算,我们只能以部分估计总体,这也是统计学的精髓,多采几次样,这里假设采样 N N N τ \tau τ并计算每一个 τ \tau τ [ R ( τ ) ∇ log ⁡ p θ ( τ ) ] \left[ R(\tau) \nabla \log p_{\theta}(\tau) \right] [R(τ)logpθ(τ)]值,再将其求和取平均,用来近似 E τ ∼ p θ ( τ ) \mathbb{E}_{\tau \sim p_{\theta}(\tau)} Eτpθ(τ)

那如何处理 log ⁡ p θ ( τ ) \log p_{\theta}(\tau) logpθ(τ)呢?
∇ log ⁡ p θ ( τ ) = ∇ ( log ⁡ p ( s 1 ) + ∑ t = 1 T log ⁡ p θ ( a t ∣ s t ) + ∑ t = 1 T log ⁡ p ( s t + 1 ∣ s t , a t ) ) = ∇ log ⁡ p ( s 1 ) + ∇ ∑ t = 1 T log ⁡ p θ ( a t ∣ s t ) + ∇ ∑ t = 1 T log ⁡ p ( s t + 1 ∣ s t , a t ) = ∑ t = 1 T ∇ log ⁡ p θ ( a t ∣ s t ) = ∑ t = 1 T ∇ log ⁡ p θ ( a t ∣ s t ) \begin{aligned} \nabla \log p_{\theta}(\tau) &= \nabla \left( \log p(s_1) + \sum_{t=1}^T \log p_{\theta}(a_t|s_t) + \sum_{t=1}^T \log p(s_{t+1}|s_t, a_t) \right) \\ &= \nabla \log p(s_1) + \nabla \sum_{t=1}^T \log p_{\theta}(a_t|s_t) + \nabla \sum_{t=1}^T \log p(s_{t+1}|s_t, a_t) \\ &= \sum_{t=1}^T \nabla \log p_{\theta}(a_t|s_t) \\ &= \sum_{t=1}^T \nabla \log p_{\theta}(a_t|s_t) \end{aligned} logpθ(τ)=(logp(s1)+t=1Tlogpθ(atst)+t=1Tlogp(st+1st,at))=logp(s1)+t=1Tlogpθ(atst)+t=1Tlogp(st+1st,at)=t=1Tlogpθ(atst)=t=1Tlogpθ(atst)
第二部中有一些与 θ \theta θ无关的项直接被求偏导变成零了。因为 p p p表示的是根据动作和前面的场景生成的场景为 s t + 1 s_{t+1} st+1的概率,与演员无关。

由上式和之前分析的由部分代替总体的思想一起带入 ∇ R ˉ θ \nabla \bar{R}_{\theta} Rˉθ可得:
E τ ∼ p θ ( τ ) [ R ( τ ) ∇ log ⁡ p θ ( τ ) ] ≈ 1 N ∑ n = 1 N R ( τ n ) ∇ log ⁡ p θ ( τ n ) = 1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) ∇ log ⁡ p θ ( a t n ∣ s t n ) \begin{aligned} \mathbb{E}_{\tau \sim p_{\theta}(\tau)}[R(\tau) \nabla \log p_{\theta}(\tau)] &\approx \frac{1}{N} \sum_{n=1}^{N} R(\tau^n) \nabla \log p_{\theta}(\tau^n) \\ &= \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} R(\tau^n) \nabla \log p_{\theta}(a_t^n|s_t^n) \end{aligned} Eτpθ(τ)[R(τ)logpθ(τ)]N1n=1NR(τn)logpθ(τn)=N1n=1Nt=1TnR(τn)logpθ(atnstn)
那我们就可以将 ∇ R ˉ θ \nabla \bar{R}_{\theta} Rˉθ当做损失函数:
∇ R ˉ θ = 1 N ∑ n = 1 N ∑ t = 1 T n R ( τ n ) ∇ log ⁡ p θ ( a t n ∣ s t n ) \nabla \bar{R}_{\theta} = \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} R(\tau^n) \nabla \log p_{\theta}(a_t^n|s_t^n) Rˉθ=N1n=1Nt=1TnR(τn)logpθ(atnstn)
既然有了损失函数,那就可以直接套用深度学习神经网络那一套东西,更新参数:
θ ← θ + η ∇ R ˉ θ \theta \leftarrow \theta + \eta \nabla \bar{R}_{\theta} θθ+ηRˉθ
那么网络的训练步骤就非常的简单:

采集数据 ——> 更新参数——>采集数据——>……

值得注意的是采集到的数据只会用一次就丢掉,这显然是一个可以优化的点,之后在PPO算法中会对其进行优化。

2.2 策略梯度算法的一些实现技巧

2.2.1 基线

如果奖励一直是正的, ∇ R ˉ θ \nabla \bar{R}_{\theta} Rˉθ就会一直是正的,那概率就只会增加不会减少,什么意思呢?

如图所示:

在某一个状态有 3 个动作 a、b、c可以执行,b的奖励最大,c的奖励最小。但是如果采样没有采到a,只采到了b、c,那么b、c的概率就会增大(因为奖励是正的),因为a、b、c的概率和是1,b、c概率增大了,a的概率就会减小,这显然是不科学的。

因此我们可以引入一个基线,就是把奖励统一减去 b b b,让奖励有正有负,即:
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n ( R ( τ n ) − b ) ∇ log ⁡ p θ ( a t n ∣ s t n ) \nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} (R(\tau^n) - b) \nabla \log p_{\theta}(a_t^n|s_t^n) RˉθN1n=1Nt=1Tn(R(τn)b)logpθ(atnstn)

2.2.2 分配合适的分数

观察损失函数:
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n ( R ( τ n ) − b ) ∇ log ⁡ p θ ( a t n ∣ s t n ) \nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} (R(\tau^n) - b) \nabla \log p_{\theta}(a_t^n|s_t^n) RˉθN1n=1Nt=1Tn(R(τn)b)logpθ(atnstn)
在同一场游戏里面,在同一场游戏里面,所有的状态-动作对就使用同样的奖励项进行加权。

这显然是不公平的例如:

假设游戏都很短,只有 3 ~ 4 个交互,在 s a s_a sa 执行 a 1 a_1 a1得到 5 分,在 s b s_b sb执行 a 2 a_2 a2得到 0 分,在 s c s_c sc执行 a 3 a_3 a3得到 −2 分。 整场游戏下来,我们得到 +3 分,那我们得到 +3 分 代表在 s b s_b sb执行 a 2 a_2 a2 是好的吗? 这并不一定代表在 s b s_b sb执行 a 2 a_2 a2是好的。因为这个正的分数,主要来自在 s a s_a sa执行了 a 1 a_1 a1,与在 s b s_b sb执行 a 2 a_2 a2是没有关系的,也许在 s b s_b sb执行 a 2 a_2 a2反而是不好的, 因为它导致我们接下来会进入 s c s_c sc,执行 a 3 a_3 a3​被扣分。所以整场游戏得到的结果是好的, 并不代表每一个动作都是好的。

理想情况下这种情况不会发生,只要采样足够多,多次遇到 ( s a , a 1 ) (s_a, a_1) (sa,a1)​的情况,我们就能通过梯度更新来合理评估它的概率,但是问题是采样成本很高,采样那么多次不太现实,因此我们可以为每个状态-动作对分配合理的分数,要让大家知道它合理的贡献。

我们之前谈到过 G t G_t Gt
G t = r t + 1 + γ r t + 2 + γ 2 r t + 3 + . . . + γ T − t − 1 r T G_t = r_{t+1} + \gamma r_{t+2} + \gamma^2 r_{t+3} + ... + \gamma^{T-t-1} r_T Gt=rt+1+γrt+2+γ2rt+3+...+γTt1rT
它就能很好的反应每个状态的价值即:
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n ( ∑ t ′ = t T n γ t ′ − t r t ′ n − b ) ∇ log ⁡ p θ ( a t n ∣ s t n ) = 1 N ∑ n = 1 N ∑ t = 1 T n ( G t − b ) ∇ log ⁡ p θ ( a t n ∣ s t n ) \begin{align} \nabla \bar{R}_{\theta} &\approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} \left( \sum_{t'=t}^{T_n} \gamma^{t'-t} r_{t'}^n - b \right) \nabla \log p_{\theta}(a_t^n|s_t^n) \\ &=\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} \left( G_t - b \right) \nabla \log p_{\theta}(a_t^n|s_t^n) \end{align} RˉθN1n=1Nt=1Tn(t=tTnγttrtnb)logpθ(atnstn)=N1n=1Nt=1Tn(Gtb)logpθ(atnstn)
说明:

事实上 b b b通常是一个网络估计出来的,它是一个网络的输出。我们把 R − b R-b Rb这一项称为优势函数(advantage function), 用 A θ ( s t , a t ) A^{\theta}(s_t, a_t) Aθ(st,at)来代表优势函数。优势函数取决于 s s s a a a,我们就是要计算在某个状态 s s s采取某个动作 a a a的时候,优势函数的值。在计算优势函数值时,我们要计算 ∑ t ′ = t T n r t ′ n \sum_{t'=t}^{T_n} r_{t'}^n t=tTnrtn,需要有一个模型与环境交互,才能知道接下来得到的奖励。优势函数 A θ ( s t , a t ) A^{\theta}(s_t, a_t) Aθ(st,at)的上标是 θ \theta θ θ \theta θ 代表用模型 θ \theta θ与环境交互。从时刻 t t t开始到游戏结束为止,所有 r r r的加和减去 b b b,这就是优势函数。优势函数的意义是,假设我们在某一个状态 s t s_t st执行某一个动作 a t a_t at,相较于其他可能的动作, a t a_t at有多好。优势函数在意的不是绝对的好,而是相对的好,即相对优势(relative advantage)。因为在优势函数中,我们会减去一个基线 b b b,所以这个动作是相对的好,不是绝对的好。 A θ ( s t , a t ) A^{\theta}(s_t, a_t) Aθ(st,at)​通常可以由一个网络估计出来,这个网络称为评论员(critic)。

2.3 REINFORCE算法实现

由于在游戏中我的策略是每个格子如果不是终点,奖励分数就是-1,因此无需使用基线,但是代码中使用了“分配合适分数”方法,所有函数作用代码中有详细注释,因此不再赘述。

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt# 环境定义
class GridWorldEnv:def __init__(self, size=5):# 初始化网格世界环境self.size = size  # 网格的大小self.state = (0, 0)  # 初始状态self.goal = (size - 1, size - 1)  # 目标状态self.actions = ['up', 'down', 'left', 'right']  # 可用的动作self.action_space = len(self.actions)  # 动作空间的大小def reset(self):# 重置环境到初始状态self.state = (0, 0)return self.statedef step(self, action):# 根据动作更新状态,并返回新的状态、奖励和是否完成x, y = self.stateif action == 0:  # 向上x = max(0, x - 1)elif action == 1:  # 向下x = min(self.size - 1, x + 1)elif action == 2:  # 向左y = max(0, y - 1)elif action == 3:  # 向右y = min(self.size - 1, y + 1)self.state = (x, y)reward = -1  # 默认奖励if x == 2 and y != 2:reward = -10done = self.state == self.goal  # 检查是否到达目标if done:reward = 10  # 到达目标的奖励return self.state, reward, donedef render(self):# 渲染当前状态的网格世界grid = np.zeros((self.size, self.size))  # 创建一个全零的网格x, y = self.stategrid[x, y] = 1  # 将当前状态的位置设为1print(grid)# 策略网络
class PolicyNetwork(nn.Module):def __init__(self, input_dim, output_dim):# 初始化策略网络super(PolicyNetwork, self).__init__()self.fc1 = nn.Linear(input_dim, 24)  # 第一层全连接层self.fc2 = nn.Linear(24, 128)  # 第二层全连接层self.fc3 = nn.Linear(128, output_dim)  # 第三层全连接层self.relu = nn.ReLU()  # ReLU激活函数self.softmax = nn.Softmax(dim=-1)  # Softmax输出层def forward(self, x):# 前向传播x = self.relu(self.fc1(x))  # 第一层激活x = self.relu(self.fc2(x))  # 第二层激活x = self.softmax(self.relu(self.fc3(x)))  # 第三层激活return x# REINFORCE算法
class REINFORCE:def __init__(self, state_space, action_space, learning_rate=0.01, gamma=0.99):# 初始化REINFORCE算法self.policy_network = PolicyNetwork(state_space, action_space)  # 策略网络self.optimizer = optim.Adam(self.policy_network.parameters(), lr=learning_rate)  # Adam优化器self.gamma = gamma  # 折扣因子def choose_action(self, state):# 根据状态选择动作state = torch.tensor(state, dtype=torch.float32).unsqueeze(0)  # 将状态转换为张量action_probs = self.policy_network(state).detach().numpy()[0]  # 获取动作概率action = np.random.choice(len(action_probs), p=action_probs)  # 根据概率选择动作return actiondef compute_discounted_rewards(self, rewards):# 计算折扣奖励discounted_rewards = np.zeros_like(rewards, dtype=np.float32)  # 初始化折扣奖励cumulative = 0for i in reversed(range(len(rewards))):cumulative = cumulative * self.gamma + rewards[i]  # 计算累计折扣奖励discounted_rewards[i] = cumulativereturn discounted_rewardsdef train(self, states, actions, rewards):# 训练策略网络discounted_rewards = self.compute_discounted_rewards(rewards)  # 计算折扣奖励# 归一化奖励discounted_rewards = (discounted_rewards - np.mean(discounted_rewards)) / (np.std(discounted_rewards) + 1e-8)discounted_rewards = torch.tensor(discounted_rewards, dtype=torch.float32)states = torch.tensor(states, dtype=torch.float32)  # 将状态转换为张量actions = torch.tensor(actions, dtype=torch.long)  # 将动作转换为张量# 计算损失log_probs = torch.log(self.policy_network(states))  # 计算对数概率selected_log_probs = log_probs[range(len(actions)), actions]  # 选择执行动作的对数概率loss = -torch.mean(selected_log_probs * discounted_rewards)  # 计算损失# 优化网络self.optimizer.zero_grad()  # 清零梯度loss.backward()  # 反向传播self.optimizer.step()  # 更新参数# 主程序
def main():env = GridWorldEnv(size=5)  # 创建网格世界环境agent = REINFORCE(state_space=10, action_space=4, learning_rate=0.01)  # 创建REINFORCE智能体episodes = 1000  # 训练回合数reward_history = []  # 奖励历史state = env.reset()  # 重置环境env.render()  # 渲染初始状态for episode in range(episodes):state = env.reset()  # 重置环境states, actions, rewards = [], [], []  # 初始化状态、动作和奖励列表done = Falsewhile not done:# 状态转换为独热向量state_onehot = np.eye(env.size)[state[0]].tolist() + np.eye(env.size)[state[1]].tolist()action = agent.choose_action(state_onehot)  # 选择动作next_state, reward, done = env.step(action)  # 执行动作states.append(state_onehot)  # 记录状态actions.append(action)  # 记录动作rewards.append(reward)  # 记录奖励state = next_state  # 更新状态agent.train(states, actions, rewards)  # 训练智能体reward_history.append(sum(rewards))  # 记录总奖励if episode % 100 == 0:print(f"Episode {episode}, Total Reward: {sum(rewards)}")  # 每100回合打印一次总奖励# 绘制奖励曲线plt.plot(reward_history)plt.xlabel("Episodes")plt.ylabel("Total Reward")plt.show()if __name__ == "__main__":main()

3. A2C算法

3.1 策略梯度的一些缺陷

策略梯度算法在理想情况下,在采样次数足够多的情况下效果是能很不错的,但是当采样不够时就会出现一些问题,例如 G t G_t Gt的取值是很不稳定的,下图可以形象说明:

由于 G t G_t Gt的取值不稳定,所以 ( s t , a t ) (s_t, a_t) (st,at)更新也不稳定。

3.2 DQN中的一些概念

由于 G G G的值有点太不稳定太玄学了,因此我们可以想办法去用一个神经网络去预测在 s s s状态下采取行动 a a a时对应的 G G G期望值,之后再训练中我们就直接用这个期望值去替代采样的值。为了完成这个目的,我们可以使用基于价值的方法深度Q网络,深度Q网络有两种函数,也就是两种评论员,他们分别是 V π ( s ) V_{\pi}(s) Vπ(s) Q π ( s , a ) Q_{\pi}(s,a) Qπ(s,a) V π ( s ) V_{\pi}(s) Vπ(s)是指假设演员的策略是 π {\pi} π,使用 π {\pi} π与环境交互,当智能体看到状态 s s s时,接下来累积奖励的期望值是多少。 Q π ( s , a ) Q_{\pi}(s,a) Qπ(s,a)是指把 s s s a a a当作输入,它表示在状态 s s s采取动作 a a a,接下来用策略 π {\pi} π与环境交互,累积奖励的期望值是多少。 V π ( s ) V_{\pi}(s) Vπ(s)接收输入 s s s,输出一个标量。 Q π ( s , a ) Q_{\pi}(s,a) Qπ(s,a)接收输入 s s s,它会给每一个 s s s都分配一个 Q Q Q值。

有了这两个函数,我们可以用这两个函数去替换原来的梯度公式:

又出现一个问题,神经网络本来就不稳定,现在还要估计两个神经网络,Q网络和V网络,那岂不是更不稳定,估不准的概率岂不是就翻倍了,那就得想点办法只估计一个网络,事实上,在演员-评论员算法中,我们可以只估计网络 V,并利用 V V V的值来表示 Q Q Q的值, Q π ( s t n , a t n ) Q_{\pi}(s_t^n, a_t^n) Qπ(stn,atn)可以写成$ r_t^n + V_{\pi}(s_{t+1}^n) $的期望值,即:
Q π ( s t n , a t n ) = E [ r t n + V π ( s t + 1 n ) ] Q_{\pi}(s_t^n, a_t^n) = \mathbb{E} \left[ r_t^n + V_{\pi}(s_{t+1}^n) \right] Qπ(stn,atn)=E[rtn+Vπ(st+1n)]
在状态 s s s采取动作 a a a,我们会得到奖励 r r r,进入状态 s t + 1 s_{t+1} st+1。但是我们会得到什么样的奖励 r r r,进入什么样的状态 s t + 1 s_{t+1} st+1,这件事本身是有随机性的。所以要把$ r_t^n + V_{\pi}(s_{t+1}^n) $​取期望值才会等于Q函数的值。但我们现在把取期望值去掉,即:
Q π ( s t n , a t n ) = r t n + V π ( s t + 1 n ) Q_{\pi}(s_t^n, a_t^n) = r_t^n + V_{\pi}(s_{t+1}^n) Qπ(stn,atn)=rtn+Vπ(st+1n)
即:Q函数与V函数的差值可以变成 r t n + V π ( s t + 1 n ) − V π ( s t n ) r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n) rtn+Vπ(st+1n)Vπ(stn)

至于究竟为什么可以去掉期望值,也没有什么很强有力的数学解释,只是研究人员发现这么做效果不错,其他方法效果没这个好,大家伙就都用这个方法了,都直接把期望去掉了。

推了半天,得到一个 r t n + V π ( s t + 1 n ) − V π ( s t n ) r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n) rtn+Vπ(st+1n)Vπ(stn),这个公式就是时序差分误差(TD-error),它代替了我们之前提到的优势函数,就是替代了 A θ ( s t , a t ) A^{\theta}(s_t, a_t) Aθ(st,at)

那么 ∇ R ˉ θ \nabla \bar{R}_{\theta} Rˉθ就变成了:
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n ( r t n + V π ( s t + 1 n ) − V π ( s t n ) ) ∇ log ⁡ p θ ( a t n ∣ s t n ) \nabla \bar{R}_{\theta} \approx \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} (r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n)) \nabla \log p_{\theta}(a_t^n | s_t^n) RˉθN1n=1Nt=1Tn(rtn+Vπ(st+1n)Vπ(stn))logpθ(atnstn)
算法流程:

3.3 Actor网络和Critic网络的损失函数

讲了这么多,那么我们要训练两个网络,Actor网络和Critic网络,既然是训练这两个模型就需要损失函数,两个模型就需要两个损失函数,那他们分别是什么呢?

Actor网络的损失函数如下:
L a c t o r = − 1 N ∑ n = 1 N ∑ t = 1 T n ( δ t n ⋅ log ⁡ π θ ( a t n ∣ s t n ) ) L_{actor} = -\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} (\delta_t^n \cdot \log \pi_{\theta}(a_t^n | s_t^n)) Lactor=N1n=1Nt=1Tn(δtnlogπθ(atnstn))

∙ δ t n = r t n + V π ( s t + 1 n ) − V π ( s t n ) :时序差分误差 (TD-error)。 ∙ log ⁡ π θ ( a t n ∣ s t n ) :策略的对数概率,表示 Actor 在状态  s t n 下选择动作  a t n 的对数概率。 ∙ N :是采样的轨迹数量。 ∙ T n :轨迹  n 的时间步数。 \begin{align*} & \bullet \delta_t^n = r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n) \text{:时序差分误差 (TD-error)。} \\ & \bullet \log \pi_{\theta}(a_t^n|s_t^n) \text{:策略的对数概率,表示 Actor 在状态 } s_t^n \text{ 下选择动作 } a_t^n \text{ 的对数概率。} \\ & \bullet N \text{:是采样的轨迹数量。} \\ & \bullet T_n \text{:轨迹 } n \text{ 的时间步数。} \end{align*} δtn=rtn+Vπ(st+1n)Vπ(stn):时序差分误差 (TD-error)logπθ(atnstn):策略的对数概率,表示 Actor 在状态 stn 下选择动作 atn 的对数概率。N:是采样的轨迹数量。Tn:轨迹 n 的时间步数。

Critic网络损失函数如下:
L c r i t i c = 1 N ∑ n = 1 N ∑ t = 1 T n ( δ t n ) 2 L_{critic} = \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} \left( \delta_t^n\right)^2 Lcritic=N1n=1Nt=1Tn(δtn)2

δ t n = r t n + V π ( s t + 1 n ) − V π ( s t n ) :时序差分误差 (TD-error)。 ∙ N :是采样的轨迹数量。 ∙ T n :轨迹  n 的时间步数。 \begin{align*} & \delta_t^n = r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n) \text{:时序差分误差 (TD-error)。} \\ & \bullet N \text{:是采样的轨迹数量。} \\ & \bullet T_n \text{:轨迹 } n \text{ 的时间步数。} \end{align*} δtn=rtn+Vπ(st+1n)Vπ(stn):时序差分误差 (TD-error)N:是采样的轨迹数量。Tn:轨迹 n 的时间步数。

有了损失函数又改如何求梯度算参数呢:

首先需要明白有两个网络,Actor网络和Critic网络,他们在上述公式中的体现是什么,时序差分误差 δ t n = r t n + V π ( s t + 1 n ) − V π ( s t n ) \delta_t^n = r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n) δtn=rtn+Vπ(st+1n)Vπ(stn),既然是误差,涉及到评估工作,因此V网络就是Critic网络,其网络参数并未在公式中标注,可以将其标注为 ϕ {\phi} ϕ。至于Actor网络,公式中已经标明过了, π θ {\pi}_{\theta} πθ表示 π \pi π这个策略网络的参数就是 θ \theta θ,因此求梯度也就很轻松。

Actor网络的梯度公式如下:
∇ θ L a c t o r = − 1 N ∑ n = 1 N ∑ t = 1 T n δ t n ⋅ log ⁡ π θ ( a t n ∣ s t n ) , \nabla_{\theta} L_{actor} = -\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} \delta_t^n \cdot \log \pi_{\theta}(a_t^n|s_t^n), θLactor=N1n=1Nt=1Tnδtnlogπθ(atnstn),

∙ δ t n = r t n + V π ( s t + 1 n ) − V π ( s t n ) :TD-error。 ∙ ∇ θ log ⁡ π θ ( a t n ∣ s t n ) :策略关于参数 θ 的梯度。 \begin{align*} & \bullet \delta_t^n = r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n) \text{:TD-error。} \\ & \bullet \nabla_{\theta} \log \pi_{\theta}(a_t^n|s_t^n) \text{:策略关于参数}\theta\text{的梯度。} \end{align*} δtn=rtn+Vπ(st+1n)Vπ(stn)TD-errorθlogπθ(atnstn):策略关于参数θ的梯度。

Critic网络的梯度公式如下:
∇ ϕ L c r i t i c = 1 N ∑ n = 1 N ∑ t = 1 T n 2 ⋅ δ t n ⋅ ∇ ϕ V π ( s t n ; ϕ ) \nabla_{\phi} L_{critic} = \frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} 2 \cdot \delta_t^n \cdot \nabla_{\phi} V_{\pi}(s_t^n; \phi) ϕLcritic=N1n=1Nt=1Tn2δtnϕVπ(stn;ϕ)

∙ δ t n = r t n + V π ( s t + 1 n ) − V π ( s t n ) :TD-error。 ∙ ∇ ϕ V π ( s t n ; ϕ ) :价值函数关于参数 ϕ 的梯度。 \begin{align*} & \bullet \delta_t^n = r_t^n + V_{\pi}(s_{t+1}^n) - V_{\pi}(s_t^n) \text{:TD-error。} \\ & \bullet \ \nabla_{\phi} V_{\pi}(s_t^n; \phi) \text{:价值函数关于参数}\phi\text{的梯度。} \\ \end{align*} δtn=rtn+Vπ(st+1n)Vπ(stn)TD-error ϕVπ(stn;ϕ):价值函数关于参数ϕ的梯度。

3.4 A2C算法实现

实现优势演员-评论员算法的时候,有两个一定会用到的技巧。

第一个技巧是,我们需要估计两个网络: V V V网络和策略的网络(也就是演员)。评论员网络 V π ( s ) V_{\pi}(s) Vπ(s)接收一个状态,输出一个标量。演员的策略 π ( s ) {\pi(s)} π(s)​接收一个状态,如果动作是离散的,输出就是一个动作的分布。如果动作是连续的,输出就是一个连续的向量。

离散动作的例子如下,连续动作的情况也是一样的。输入一个状态,网络决定现在要采取哪一个动作。演员网络和评论员网络的输入都是 s s s​,所以它们前面几个层(layer)是可以共享的。

第二个技巧是我们需要探索的机制。在演员-评论员算法中,有一个常见的探索的方法是对 π {\pi} π​输出的分布设置一个约束。这个约束用于使分布的熵(entropy)不要太小,也就是希望不同的动作被采用的概率平均一些。这样在测试的时候,智能体才会多尝试各种不同的动作,才会把环境探索得比较好,从而得到比较好的结果。

这里运用了两个技巧,第二个技巧在Actor网络的损失函数上加了个正则项,因此Actor损失函数变成了这样:
L a c t o r = − 1 N ∑ n = 1 N ∑ t = 1 T n ( δ t n ⋅ log ⁡ π θ ( a t n ∣ s t n ) ) − λ H ( π θ ) L_{actor} = -\frac{1}{N} \sum_{n=1}^{N} \sum_{t=1}^{T_n} (\delta_t^n \cdot \log \pi_{\theta}(a_t^n|s_t^n)) - \lambda H(\pi_{\theta}) Lactor=N1n=1Nt=1Tn(δtnlogπθ(atnstn))λH(πθ)

∙ H ( π θ ) :策略的熵。 ∙ λ :控制探索的权重。 \begin{align*} & \bullet \ H(\pi_{\theta}) \text{:策略的熵。} \\ & \bullet \ \lambda \text{:控制探索的权重。} \end{align*}  H(πθ):策略的熵。 λ:控制探索的权重。

代码中主要是以玩一个CartPole的游戏举例来实现的,游戏的具体玩法可以去网上搜到,大概就是一个平衡杆左右移动让杆子不倒的游戏。

import gym
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np 
import matplotlib.pyplot as plt # 配置类,集中管理所有超参数
class Config:def __init__(self):# 神经网络结构参数self.hidden_dim = 128        # 共享网络隐藏层维度self.actor_hidden_dim = 64   # Actor网络隐藏层维度self.critic_hidden_dim = 64  # Critic网络隐藏层维度# 算法训练参数self.lr = 0.001             # 学习率self.gamma = 0.99           # 折扣因子,用于计算未来奖励的现值self.entropy_coef = 0.01    # 熵系数,用于鼓励探索self.value_loss_coef = 0.5  # 价值损失系数,平衡actor和critic损失# 训练过程参数self.max_episodes = 5000    # 最大训练回合数self.max_steps = 500        # 每个回合的最大步数# 其他设置self.seed = 42              # 随机种子,确保实验可重复性self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 设备选择self.rewards_history = []  # 用于存储每个episode的reward# Actor-Critic网络结构
class ActorCritic(nn.Module):def __init__(self, state_dim, action_dim, config):super(ActorCritic, self).__init__()# 共享特征提取层self.common = nn.Sequential(nn.Linear(state_dim, config.hidden_dim),  # 状态到特征的映射nn.ReLU()                                # 非线性激活函数)# Actor网络:决策策略self.actor = nn.Sequential(nn.Linear(config.hidden_dim, action_dim), # 特征到动作概率的映射nn.Softmax(dim=-1)                       # 输出动作概率分布)# Critic网络:状态价值评估self.critic = nn.Sequential(nn.Linear(config.hidden_dim, 1)          # 特征到状态价值的映射)def forward(self, state):common_out = self.common(state)     # 提取共享特征policy = self.actor(common_out)     # 计算动作概率value = self.critic(common_out)     # 估计状态价值return policy, value# A2C算法实现
class A2C:def __init__(self, state_dim, action_dim, config):self.config = configself.model = ActorCritic(state_dim, action_dim, config)  # 创建神经网络self.optimizer = optim.Adam(self.model.parameters(), lr=config.lr)  # Adam优化器self.gamma = config.gamma  # 折扣因子def compute_loss(self, states, actions, rewards, dones, next_states):# 数据预处理:转换为PyTorch张量states = torch.FloatTensor(states)next_states = torch.FloatTensor(next_states)actions = torch.LongTensor(actions)rewards = torch.FloatTensor(rewards)dones = torch.FloatTensor(dones)# 获取当前状态的策略和价值估计policy, values = self.model(states)_, next_values = self.model(next_states)# 计算TD目标和优势函数# TD目标 = 即时奖励 + 折扣因子 * 下一状态的价值 * (1-终止标志)td_targets = rewards + self.gamma * next_values.squeeze(1) * (1 - dones)# 优势函数 = TD目标 - 当前状态的价值估计advantages = td_targets - values.squeeze(1)# 计算策略(Actor)损失action_probs = policy.gather(1, actions.unsqueeze(1)).squeeze(1)# 计算策略熵,用于鼓励探索entropy = -(policy * torch.log(policy + 1e-10)).sum(1).mean()# Actor损失 = -(log策略 * 优势函数) - 熵系数 * 熵actor_loss = -(torch.log(action_probs) * advantages.detach()).mean() - \self.config.entropy_coef * entropy# Critic损失 = 优势函数的平方误差critic_loss = self.config.value_loss_coef * advantages.pow(2).mean()# 总损失 = Actor损失 + Critic损失return actor_loss + critic_lossdef update(self, states, actions, rewards, dones, next_states):loss = self.compute_loss(states, actions, rewards, dones, next_states)self.optimizer.zero_grad()  # 清除之前的梯度loss.backward()            # 反向传播计算梯度self.optimizer.step()      # 更新网络参数# 训练主循环
def train_a2c():config = Config()              # 创建配置对象# 设置随机种子确保可重复性torch.manual_seed(config.seed)np.random.seed(config.seed)# 创建CartPole环境env = gym.make("CartPole-v1")state_dim = env.observation_space.shape[0]  # 状态空间维度action_dim = env.action_space.n            # 动作空间维度# 创建A2C智能体agent = A2C(state_dim, action_dim, config)# 训练循环for episode in range(config.max_episodes):state, _ = env.reset()     # 重置环境# 初始化回合数据存储states, actions, rewards, next_states, dones = [], [], [], [], []total_reward = 0           # 记录回合总奖励episode_reward = 0         # 记录每个episode的reward# 单回合交互循环for step in range(config.max_steps):# 将状态转换为张量并添加批次维度state_tensor = torch.FloatTensor(state).unsqueeze(0)# 获取动作概率分布policy, _ = agent.model(state_tensor)# 从概率分布中采样动作action = torch.multinomial(policy, 1).item()# 执行动作next_state, reward, done, _, _ = env.step(action)# 存储交互数据states.append(state)actions.append(action)rewards.append(reward)dones.append(done)next_states.append(next_state)total_reward += reward  # 累积奖励episode_reward += reward  # 累积每个episode的rewardstate = next_state      # 更新状态if done:               # 如果回合结束,跳出循环break# 回合结束后更新模型agent.update(states, actions, rewards, dones, next_states)print(f"Episode {episode + 1}, Total Reward: {total_reward}")# 记录每个episode的rewardconfig.rewards_history.append(episode_reward)# 如果达到目标奖励,提前结束训练if total_reward >= 500:print("Solved!")break# 绘制训练奖励曲线plot_rewards(config.rewards_history)env.close()                    # 关闭环境# 绘图函数
def plot_rewards(rewards):plt.figure(figsize=(10,5))plt.plot(rewards)plt.title('Training Rewards')plt.xlabel('Episode')plt.ylabel('Reward')plt.grid()plt.show()# 程序入口
if __name__ == "__main__":train_a2c()

设置的reward到达500就提前结束训练,结果图:

由于平台字数限制,下文链接:https://blog.csdn.net/ooblack/article/details/144198573

相关文章:

强化学习的几个主要方法(策略梯度、PPO、REINFORCE实现等)(上)

本笔记有大量参考蘑菇书EasyRL https://datawhalechina.github.io/easy-rl/#/ 包括其配图和部分文本。 1. 基本概念 1.1 基本流程 强化学习是一种学习框架,其中智能体(Agent) 通过与 环境(Environment) 的交互&#…...

Git远程仓库操作

文章目录 远程仓库连接Gitee克隆代码 多人协同问题说明 🏡作者主页:点击! 🤖Git专栏:点击! ⏰️创作时间:2024年12月1日13点10分 远程仓库 Git 是分布式版本控制系统,同一个 Git …...

GAGAvatar: Generalizable and Animatable Gaussian Head Avatar 学习笔记

1 Overall GAGAvatar(Generalizable and Animatable Gaussian Avatar),一种面向单张图片驱动的可动画化头部头像重建的方法,解决了现有方法在渲染效率和泛化能力上的局限。 旋转参数 现有方法的局限性: 基于NeRF的方…...

什么是VISUAL STUDIO CODE (V S CODE)

Visual Studio Code(简称VS Code)是由微软开发的一个免费的、开源的源代码编辑器。它是一个轻量级但功能强大的工具,支持多种编程语言和框架,广泛用于开发各种应用程序,尤其是Web开发。VS Code具备以下特点&#xff1a…...

2024年09月中国电子学会青少年软件编程(Python)等级考试试卷(三级)答案 + 解析

青少年软件编程(Python)等级考试试卷(三级) 分数:100 题数:38 一、单选题(共25题,共50分) 1. 以下表达式的值为True的是?( ) A. all( ,1,2,3) B. any([]) C. bool(abc) D. divmod(6,0)...

C++初阶——动态内存管理

目录 1、C/C内存区域划分 2、C动态内存管理:malloc/calloc/realloc/free 3、C动态内存管理:new/delete 3.1 new/delete内置类型 3.2 new/delete自定义类型 4、operator new与operator delete函数 5、new和delete的实现原理 5.1 内置类型 5.2 自定…...

如何查看阿里云ddos供给量

要查看阿里云上的 DDoS 攻击量,你可以通过阿里云的 云盾 DDoS 防护 服务来进行监控和查看攻击数据。阿里云提供了详细的流量监控、攻击日志以及攻击趋势分析工具,帮助用户实时了解 DDoS 攻击的情况。以下是九河云总结的查看 DDoS 攻击量的步骤&#xff1…...

MySQL中的事务隔离全详解

第一部分:MySQL事务的特性与并行事务引发的问题 1. 什么是事务及其四大特性(ACID)? 事务(Transaction)是数据库操作的基本单位,它将一组操作组合在一起,以确保这些操作作为一个整体…...

异常--C++

文章目录 一、异常的概念及使用1、异常的概念2、异常的抛出和捕获3、栈展开4、查找匹配的处理代码5、异常重新抛出6、异常安全问题7、异常规范 二、标准库的异常 一、异常的概念及使用 1、异常的概念 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并…...

SeggisV1.0 遥感影像分割软件【源代码】讲解

在此基础上进行二次开发,开发自己的软件,例如:【1】无人机及个人私有影像识别【2】离线使用【3】变化监测模型集成【4】个人私有分割模型集成等等,不管是您用来个人学习还是公司研发需求,都相当合适,包您满…...

锁-读写锁-Swift

实现一 pthread_mutex_t: ReadWriteLock/Sources/ReadWriteLock at main SomeRandomiOSDev/ReadWriteLock GitHub https://swiftpackageindex.com/reers/reerkit/1.0.39/documentation/reerkit/readwritelock/ // // Copyright © 2022 reers. // // Pe…...

Kafka如何保证消息可靠?

大家好,我是锋哥。今天分享关于【Kafka如何保证消息可靠?】面试题。希望对大家有帮助; Kafka如何保证消息可靠? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka通过多种机制来确保消息的可靠性,主要包…...

5.10【机器学习】

如果FLAG的画,就是已经有模型了,不然就新建一个模型,通过TORCH方法 在训练的时候,如果TRAIN的话就是训练,不然就是预测 forward前向预测出来一个结果,就是1234 在train方法里,进行多轮迭代&am…...

[白月黑羽]关于仿写股票数据软件题目的解答

原题: 对应问题视频: 实现的效果 不同点 实现的作品和原题要求的不同点 题目要求爬虫获取数据,作品中是调库获取所有股票历史数据实时数据使用爬虫的方式爬取指定股票的数据,需要实时更新,我做了修改,改…...

详解LZ4文件解压缩问题

详解LZ4文件解压缩问题 一、LZ4文件解压缩方法1. 使用LZ4命令行工具2. 使用Python库3. 使用第三方工具4. 在线解压工具 二、常见问题及解决方法1. 解压显示文件损坏2. 解压后文件大小异常 三、总结 LZ4是一种快速的压缩算法,广泛应用于需要实时压缩和解压缩大文件的…...

vue项目中单独文件的js不存在this.$store?.state怎么办

在Vue项目中,如果你在单独的文件(比如插件、工具函数等)中遇到this.$store不存在的情况,这通常是因为this上下文不指向Vue实例,或者Vuex store没有被正确地注入到Vue实例中。以下是几种可能的解决方案: 确保…...

Github提交Pull Request教程 Git基础扫盲(零基础易懂)

1 PR是什么? PR,全称Pull Request(拉取请求),是一种非常重要的协作机制,它是 Git 和 GitHub 等代码托管平台中常见的功能,被广泛用于参与社区贡献,从而促进项目的发展。 PR的整个过…...

Java函数式编程【二】【Stream的装饰】【中间操作】【map映射器】【摊平映射器flatMap】

一、Java的Stream流式编程中的中间操作 Java的Stream流式编程中,中间操作是对数据流进行处理的一种方式,这些操作通常返回流对象本身,以便可以链接更多的操作。以下是一些常见的中间操作: filter(Predicate predicate) - 用于通过…...

树莓派明明安装了opencv和numpy,却找不到

当然不止树莓派,配置python环境都可能存在这个问题 可能是因为安装的 numpy 或者 opencv 版本与 Python 的包路径不匹配。下面是问题的常见原因及解决方法:【方法一和二优先考虑】 原因分析 多版本 Python 环境冲突: 树莓派上可能有多个版本…...

numpy.float8不存在;Python中,实现16位浮点数

目录 python中矩阵的浮点数存储 numpy.float8不存在 Python中,实现16位浮点数 实现 float16 关于 float8 python中矩阵的浮点数存储 在Python中,矩阵通常是通过嵌套列表(list of lists)、NumPy数组(numpy.ndarray)或其他类似的数据结构来表示的。矩阵中存储的数值所…...

Redis集群配置 (不使用docker 部署)

1. Redis集群简介 1.1 什么是Redis集群 Redis集群是一种通过将多个Redis节点连接在一起以实现高可用性、数据分片和负载均衡的技术。它允许Redis在不同节点上同时提供服务,提高整体性能和可靠性。根据搭建的方式和集群的特性,Redis集群主要有三种模式&…...

HTML5系列(7)-- Web Storage 实战指南

前端技术探索系列:HTML5 Web Storage 实战指南 🗄️ 致读者:本地存储的新纪元 👋 前端开发者们, 今天我们将深入探讨 HTML5 中的 Web Storage 技术,这是一个强大的本地存储解决方案,让我们能…...

【在Linux世界中追寻伟大的One Piece】读者写者问题与读写锁

目录 1 -> 读者写者问题 1.1 -> 什么是读者写者问题 1.2 -> 读者写者与生产消费者的区别 1.3 -> 如何理解读者写者问题 2 -> 读写锁 2.1 -> 读写锁接口 3 -> 读者优先(Reader-Preference) 4 -> 写者优先(Writer-Preference) 1 -> 读者写者…...

用到动态库的程序运行过程

当我们写好了一段代码然后编译运行后会生成可执行文件,该文件会存在磁盘的当前目录下,而当我们开始运行这段程序时,操作系统(加载器)需要将其从磁盘加载进内存然后执行相关操作,而对于用到动态库的程序&…...

类型转换与IO流:C++世界的变形与交互之道

文章目录 前言🎄一、类型转换🎈1.1 隐式类型转换🎈1.2 显式类型转换🎁1. C 风格强制类型转换🎁2. C 类型转换操作符 🎈1.3 C 类型转换操作符详解🎁1. static_cast🎁2. dynamic_cast&…...

Pytorch使用手册- TorchVision目标检测微调Tutorial的使用指南(专题十二)

这篇教程的目标是对一个预训练的 Mask R-CNN 模型进行微调,应用于 Penn-Fudan 行人检测与分割数据集。该数据集包含 170 张图像,里面有 345 个行人实例,我们将通过这个教程来演示如何使用 torchvision 中的新特性,训练一个面向自定义数据集的目标检测和实例分割模型。 注意…...

人工智能机器学习算法分类全解析

目录 一、引言 二、机器学习算法分类概述 (一)基于学习方式的分类 1. 监督学习(Supervised Learning) 2. 无监督学习(Unsupervised Learning) 3. 强化学习(Reinforcement Learning&#xf…...

Linux 35.6 + JetPack v5.1.4@DeepStream安装

Linux 35.6 JetPack v5.1.4DeepStream安装 1. 源由2. 步骤Step 1 安装Jetpack 5.1.4 L4T 35.6Step 2 安装依赖组件Step 3 安装librdkafkaStep 4 安装 DeepStream SDKStep 5 测试 deepstream-appStep 6 运行 deepstream-app 3. 总结3.1 版本问题3.2 二进制help 4. 参考资料 1. …...

图数据库 | 11、图数据库架构设计——高性能图存储架构(下)

在上篇内容中,老夫着重讲了高性能图存储系统的特点,咱们继续往下讲重点——高性能存储架构的设计思路!! 2.高性能存储架构设计思路 首先呢,存储架构以及核心数据结构的设计思路通常围绕如下4个维度来进行&#xff1a…...

【HTTP】HTTP协议

一个Web Server就是个服务器软件(程序),或者是运行这个服务器软件的硬件(计算机),其主要功能是通过HTTP协议与客户端进行通信,来接收,存储,处理来自客户端的HTTP请求&…...

wordpress教程bt/上海企业seo

开头 学习能力,尤其是自学能力,你啥时看到那些有名的程序高手在论坛上问“学习XX该看什么书,如何快速学习XXX,学习XXX有什么代码推荐”之类的问题,他们想学什么很快就能自己找到相关资料。这个行业发展太快&#xff0…...

设计师做兼职的网站/网络营销的新特点

最新版 RHEL 8.2系统, 2020-04-28 已正式发布,下面就带大家一步一步完成系统的安装。 此安装方法适用于在该系统上安装 Oracle 数据库,或者搭建自己项目所需要的相关服务。1. 插入安装光盘,开启服务器,安装界面选择&am…...

新手建设html5网站/手机百度免费下载

CSS3 给 Web 开发带来了革命性的影响,以前很多需要 JavaScript 才能实现的复杂效果,现在使用 CSS3 就能简单的实现。如果你想了解 CSS3 可以做些什么的话,可以参考本文列出的 CSS3 能实现的很炫的应用。这些很酷的例子中有渐变、旋转、字体效…...

移动互联网 传统网站/百度在线入口

一起来学UML(1)——UML建模工具Umbrello初体验 http://blog.csdn.net/donhao/article/details/5668499...

成都山而网站建设公司/深圳搜索引擎优化推广便宜

报表模板生成软件:iReport 、 润乾、水晶。 一、Jaspersoft iReport Desiginer 5.60 的使用 1、软件jar包的下载地址与配置 百度云盘下载链接: https://pan.baidu.com/s/1Ln9ewKMhYuau1bG9EgdUNQ 密码:cspl 此软件最高仅支持1.7版本的JDK,如果是1.8版本…...

两学一做专题网站/湖南网络推广服务

要想在浏览器启动APP的方法例如以下&#xff1a; 在须要跳转的ACTIVITY中加入intent-filter的相关信息&#xff1a; <intent-filter> <data android:scheme"com.example.scheme" /> </intent-filter> 实比例如以下&#xff1a;<activity an…...