一、强化学习:机器通过与环境交互来实现目标的一种计算方法。
机器和环境的一轮交互:机器在环境的一个状态下做一个动作决策,把这个动作作用到环境当中,这个环境发生相应的改变并且将相应的奖励反馈和下一轮状态传回机器。
迭代进行,机器的目标是最大化在多轮交互过程中获得的累积奖励的期望。
智能体 → \rightarrow →感知、决策和奖励
(一)强化学习的环境
环境的下一刻状态的概率分布将由当前状态和智能体的动作来共同决定
下一状态
∼
P
(
⋅
∣
当前状态,智能体的动作
)
\text{下一状态}\sim P(\cdot|\text{当前状态,智能体的动作 })
下一状态∼P(⋅∣当前状态,智能体的动作 )
智能体决策的动作作用到环境中,使得环境发生相应的状态改变,而智能体接下来则需要在新的状态下进一步给出决策。
每一轮状态转移 两方面的随机性:智能体决策的动作的随机性,环境基于当前状态和智能体动作来采样下一刻状态的随机性。
(二)强化学习的目标
即使环境和智能体策略不变,智能体的初始状态也不变,智能体和环境交互产生的结果也很可能是不同的,对应获得的回报也会不同。
因此,在强化学习中,智能体学习的优化目标–>回报的期望–>价值(value)
(三)强化学习中的数据
数据是在智能体与环境交互的过程中得到的。如果智能体不采取某个决策动作,那么该动作对应的数据就永远无法被观测到。
当前智能体的训练数据来自之前智能体的决策结果。智能体的策略不同,与环境交互所产生的数据分布就不同
占用度量(occupancy measure),归一化的占用度量用于衡量在一个智能体决策与一个动态环境的交互过程中,采样到一个具体的状态动作对(state-action pair)的概率分布。
给定两个策略及其与一个动态环境交互得到的两个占用度量,当且仅当这两个占用度量相同时,这两个策略相同。一个智能体的策略有所改变,那么它和环境交互得到的占用度量也会相应改变。
- 强化学习的策略在训练中会不断更新,其对应的数据分布(即占用度量)也会相应地改变。智能体看到的数据分布是随着智能体的学习而不断发生改变的。
- 奖励建立在状态动作对之上,一个策略对应的价值–>一个占用度量下对应的奖励的期望。寻找最优策略–>寻找最优占用度量。
与监督学习相似点和不同点:
- 有监督学习和强化学习的优化目标相似,即都是在优化某个数据分布下的一个分数值的期望。一般的有监督学习关注寻找一个模型,使其在给定数据分布下得到的损失函数的期望最小;
- 二者优化途径不同,有监督学习直接通过优化模型对于数据特征的输出来优化目标,即修改目标函数而数据分布不变;强化学习则通过改变策略来调整智能体和环境交互数据的分布,进而优化目标,即**修改数据分布而目标函数不变。**强化学习关注寻找一个智能体策略,使其在与动态环境交互的过程中产生最优的数据分布,即最大化该分布下一个给定奖励函数的期望。
二、 ϵ \epsilon ϵ-Greedy算法
完全贪婪算法即在每一时刻采取期望奖励估值最大的动作(拉动拉杆),这就是纯粹的利用,而没有探索,所以我们通常需要对完全贪婪算法进行一些修改,其中比较经典的一种方法为 ϵ \epsilon ϵ-贪婪( ϵ \epsilon ϵ -Greedy)算法。 ϵ \epsilon ϵ-贪婪算法在完全贪婪算法的基础上添加了噪声,每次以概率 ϵ \epsilon ϵ选择以往经验中期望奖励估值最大的那根拉杆(利用),以概率 ϵ \epsilon ϵ随机选择一根拉杆(探索),公式如下:
a t = { arg max a ∈ A Q ^ ( a ) , 采样概率:1- ϵ 从 A 中随机选择 , 采样概率: ϵ a_t=\begin{cases}\arg\max_{a\in\mathcal{A}}\hat{Q}(a),&\text{采样概率:1-}\mathcal{\epsilon}\\\text{从 }\mathcal{A}\text{中随机选择},&\text{采样概率:}\mathcal{\epsilon}\end{cases} at={argmaxa∈AQ^(a),从 A中随机选择,采样概率:1-ϵ采样概率:ϵ
随着探索次数的不断增加,我们对各个动作的奖励估计得越来越准,此时我们就没必要继续花大力气进行探索。所以在 ϵ \epsilon ϵ-贪婪算法的具体实现中,我们可以令 ϵ \epsilon ϵ随时间衰减,即探索的概率将会不断降低。但是请注意, ϵ \epsilon ϵ不会在有限的步数内衰减至 0,因为基于有限步数观测的完全贪婪算法仍然是一个局部信息的贪婪算法,永远距离最优解有一个固定的差距。
(一)马尔可夫性质
当且仅当某时刻的状态只取决于上一时刻的状态时,一个随机过程被称为具有马尔可夫性质(Markov property) P ( S t + 1 ∣ S t ) = P ( S t + 1 ∣ S 1 , … , S t ) P(S_{t+1}|S_t)=P(S_{t+1}|S_1,\ldots,S_t) P(St+1∣St)=P(St+1∣S1,…,St)。当前状态是未来的充分统计量,下一个状态只取决于当前状态,而不会受到过去状态的影响。
具有马尔可夫性并不代表这个随机过程就和历史完全没有关系。因为虽然时刻 t + 1 t+1 t+1的状态只与 t t t时刻的状态有关,但是时刻的状态其实包含了 t − 1 t-1 t−1时刻的状态的信息,通过这种链式的关系,历史的信息被传递到了现在。
(二)马尔可夫过程(Markov process)
指具有马尔可夫性质的随机过程,也被称为马尔可夫链(Markov chain)。我们通常用元组 ⟨ S , P ⟩ \langle\mathcal{S},\mathcal{P}\rangle ⟨S,P⟩描述一个马尔可夫过程,其中 S \mathcal{S} S是有限数量的状态集合, P \mathcal{P} P是状态转移矩阵(state transition matrix)。假设一共有 n n n个状态,此时 S = { s 1 , s 2 , … , s n } \mathcal{S}=\{s_1,s_2,\ldots,s_n\} S={s1,s2,…,sn}。状态转移矩阵 P \mathcal{P} P定义了所有状态对之间的转移概率,即 P = [ P ( s 1 ∣ s 1 ) ⋯ P ( s n ∣ s 1 ) ⋮ ⋱ ⋮ P ( s 1 ∣ s n ) ⋯ P ( s n ∣ s n ) ] \mathcal{P}=\begin{bmatrix}P(s_1|s_1)&\cdots&P(s_n|s_1)\\\vdots&\ddots&\vdots\\P(s_1|s_n)&\cdots&P(s_n|s_n)\end{bmatrix} P= P(s1∣s1)⋮P(s1∣sn)⋯⋱⋯P(sn∣s1)⋮P(sn∣sn)
矩阵 P \mathcal{P} P中第 i i i行第 j j j列元素 P ( s j ∣ s i ) = P ( S t + 1 = s j ∣ S t = s i ) P(s_j|s_i)=P(S_{t+1}=s_j|S_t=s_i) P(sj∣si)=P(St+1=sj∣St=si)表示从状态 s i s_i si转移到状态 s j s_j sj的概率,我们称 P ( s ′ ∣ s ) P(s'|s) P(s′∣s)为状态转移函数。从某个状态出发,到达其他状态的概率和必须为 1,即状态转移矩阵的每一行的和为 1。
(三)马尔可夫奖励过程由 ⟨ S , P , r , γ ⟩ \langle\mathcal{S},\mathcal{P},r,\gamma\rangle ⟨S,P,r,γ⟩构成
- S \mathcal{S} S是有限状态的集合。
- P \mathcal{P} P是状态转移矩阵。
- r r r是奖励函数,某个状态 s s s的奖励 r ( s ) r(s) r(s)指转移到该状态时可以获得奖励的期望。
- γ \gamma γ是折扣因子(discount factor), γ \gamma γ的取值范围为 [ 0 , 1 ) [0,1) [0,1)。引入折扣因子的理由为远期利益具有一定不确定性,有时我们更希望能够尽快获得一些奖励,所以我们需要对远期利益打一些折扣。接近 1 的 γ \gamma γ更关注长期的累计奖励,接近 0 的 γ \gamma γ更考虑短期奖励。
在一个马尔可夫奖励过程中,从第 t t t时刻 S t S_t St状态开始,直到终止状态时,所有奖励的衰减之和称为回报 G t G_t Gt(Return) G t = R t + γ R t + 1 + γ 2 R t + 2 + ⋯ = ∑ k = 0 ∞ γ k R t + k G_t=R_t+\gamma R_{t+1}+\gamma^2R_{t+2}+\cdots=\sum_{k=0}^\infty\gamma^kR_{t+k} Gt=Rt+γRt+1+γ2Rt+2+⋯=∑k=0∞γkRt+k
R t R_t Rt表示在时刻 t t t获得的奖励.
(四)状态价值函数
用 V π ( s ) V^{\pi}(s) Vπ(s)表示在 MDP 中基于策略 π \pi π的状态价值函数(state-value function),定义为从状态 s s s出发遵循策略 π \pi π能获得的期望回报 V π ( s ) = E π [ G t ∣ S t = s ] V^\pi(s)=\mathbb{E}_\pi[G_t|S_t=s] Vπ(s)=Eπ[Gt∣St=s]
(五)动作价值函数(action-value function)
我们 Q π ( s , a ) Q^\pi(s,a) Qπ(s,a)用表示在 MDP 遵循策略 π \pi π时,对当前状态 s s s执行动作 a a a得到的期望回报: Q π ( s , a ) = E π [ G t ∣ S t = s , A t = a ] Q^\pi(s,a)=\mathbb{E}_\pi[G_t|S_t=s,A_t=a] Qπ(s,a)=Eπ[Gt∣St=s,At=a]
状态价值函数和动作价值函数之间的关系:在使用策略 π \pi π中,状态 s s s的价值等于在该状态下基于策略 π \pi π采取所有动作的概率与相应的价值相乘再求和的结果: V π ( s ) = ∑ a ∈ A π ( a ∣ s ) Q π ( s , a ) V^\pi(s)=\sum_{a\in A}\pi(a|s)Q^\pi(s,a) Vπ(s)=∑a∈Aπ(a∣s)Qπ(s,a)
使用策略 π \pi π时,状态 s s s下采取动作 a a a的价值等于即时奖励加上经过衰减后的所有可能的下一个状态的状态转移概率与相应的价值的乘积: Q π ( s , a ) = r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V π ( s ′ ) Q^\pi(s,a)=r(s,a)+\gamma\sum_{s^{\prime}\in S}P(s^{\prime}|s,a)V^\pi(s^{\prime}) Qπ(s,a)=r(s,a)+γ∑s′∈SP(s′∣s,a)Vπ(s′)
(六)贝尔曼期望方程(Bellman Expectation Equation):
V π ( s ) = E π [ R t + γ V π ( S t + 1 ) ∣ S t = s ] = ∑ a ∈ A π ( a ∣ s ) ( r ( s , a ) + γ ∑ s ′ ∈ S p ( s ′ ∣ s , a ) V π ( s ′ ) ) Q π ( s , a ) = E π [ R t + γ Q π ( S t + 1 , A t + 1 ) ∣ S t = s , A t = a ] = r ( s , a ) + γ ∑ s ′ ∈ S p ( s ′ ∣ s , a ) ∑ a ′ ∈ A π ( a ′ ∣ s ′ ) Q π ( s ′ , a ′ ) \begin{aligned} V^{\pi}(s)& =\mathbb{E}_\pi[R_t+\gamma V^\pi(S_{t+1})|S_t=s] \\ &=\sum_{a\in A}\pi(a|s)\left(r(s,a)+\gamma\sum_{s^{\prime}\in S}p(s^{\prime}|s,a)V^\pi(s^{\prime})\right) \\ Q^{\pi}(s,a)& =\mathbb{E}_\pi[R_t+\gamma Q^\pi(S_{t+1},A_{t+1})|S_t=s,A_t=a] \\ &\begin{aligned}&=r(s,a)+\gamma\sum_{s^{\prime}\in S}p(s^{\prime}|s,a)\sum_{a^{\prime}\in A}\pi(a^{\prime}|s^{\prime})Q^{\pi}(s^{\prime},a^{\prime})\end{aligned} \end{aligned} Vπ(s)Qπ(s,a)=Eπ[Rt+γVπ(St+1)∣St=s]=a∈A∑π(a∣s)(r(s,a)+γs′∈S∑p(s′∣s,a)Vπ(s′))=Eπ[Rt+γQπ(St+1,At+1)∣St=s,At=a]=r(s,a)+γs′∈S∑p(s′∣s,a)a′∈A∑π(a′∣s′)Qπ(s′,a′)
(七)蒙特卡洛方法
一个状态的价值是它的期望回报,用策略在 MDP 上采样很多条序列,计算从这个状态出发的回报再求其期望就可以了 V π ( s ) = E π [ G t ∣ S t = s ] ≈ 1 N ∑ i = 1 N G t ( i ) V^\pi(s)=\mathbb{E}_\pi[G_t|S_t=s]\approx\frac1N\sum_{i=1}^NG_t^{(i)} Vπ(s)=Eπ[Gt∣St=s]≈N1∑i=1NGt(i)
用策略 π \pi π从状态 s s s开始采样序列,据此来计算状态价值。我们为每一个状态维护一个计数器和总回报
(1) 使用策略 π \pi π采样若干条序列:
s 0 ( i ) → a 0 ( i ) r 0 ( i ) , s 1 ( i ) → a 1 ( i ) r 1 ( i ) , s 2 ( i ) → a 2 ( i ) ⋯ → a T − 1 ( i ) r T − 1 ( i ) , s T ( i ) s_0^{(i)}\xrightarrow{a_0^{(i)}}r_0^{(i)},s_1^{(i)}\xrightarrow{a_1^{(i)}}r_1^{(i)},s_2^{(i)}\xrightarrow{a_2^{(i)}}\cdots\xrightarrow{a_{T-1}^{(i)}}r_{T-1}^{(i)},s_T^{(i)} s0(i)a0(i)r0(i),s1(i)a1(i)r1(i),s2(i)a2(i)⋯aT−1(i)rT−1(i),sT(i)
(2) 对每一条序列中的每一时间步 t t t的状态 s s s进行以下操作:
- 更新状态 s s s的计数器 N ( s ) ← N ( s ) + 1 ; N(s)\leftarrow N(s)+1; N(s)←N(s)+1;
- 更新状态 s s s的总回报 M ( s ) ← M ( s ) + G t ; M(s)\leftarrow M(s)+G_t; M(s)←M(s)+Gt;
(3) 每一个状态的价值被估计为回报的平均值 V ( s ) = M ( s ) / N ( s ) V(s)=M(s)/N(s) V(s)=M(s)/N(s)
根据大数定律,当$N(s)\rightarrow\infty ,有 ,有 ,有V(s)\rightarrow V^{\pi}(s)$ 。计算回报的期望时,除了可以把所有的回报加起来除以次数,还有一种增量更新的方法。对于每个状态 s s s和对应回报 G G G,进行如下计算:
N ( s ) ← N ( s ) + 1 V ( s ) ← V ( s ) + 1 N ( s ) ( G − V ( S ) ) \begin{aligned}&N(s)\leftarrow N(s)+1\\&V(s)\leftarrow V(s)+\frac1{N(s)}(G-V(S))\end{aligned} N(s)←N(s)+1V(s)←V(s)+N(s)1(G−V(S))
大部分强化学习现实场景(例如电子游戏或者一些复杂物理环境),其马尔可夫决策过程的状态转移概率是无法写出来的,也就无法直接进行动态规划。在这种情况下,智能体只能和环境进行交互,通过采样到的数据来学习,这类学习方法统称为无模型的强化学习(model-free reinforcement learning)。
Sarsa 和 Q-learning,都是基于时序差分(temporal difference,TD)的强化学习算法。
三、时序差分方法
时序差分是一种用来估计一个策略的价值函数的方法,它结合了蒙特卡洛和动态规划算法的思想。时序差分方法和蒙特卡洛的相似之处在于可以从样本数据中学习,不需要事先知道环境;和动态规划的相似之处在于根据贝尔曼方程的思想,利用后续状态的价值估计来更新当前状态的价值估计。回顾一下蒙特卡洛方法对价值函数的增量更新方式: V ( s t ) ← V ( s t ) + α [ G t − V ( s t ) ] V(s_t)\leftarrow V(s_t)+\alpha[G_t-V(s_t)] V(st)←V(st)+α[Gt−V(st)]
蒙特卡洛方法必须要等整个序列结束之后才能计算得到这一次的回报 G t G_t Gt,而时序差分方法只需要当前步结束即可进行计算。具体来说,时序差分算法用当前获得的奖励加上下一个状态的价值估计来作为在当前状态会获得的回报,即: V ( s t ) ← V ( s t ) + α [ r t + γ V ( s t + 1 ) − V ( s t ) ] V(s_t)\leftarrow V(s_t)+\alpha[r_t+\gamma V(s_{t+1})-V(s_t)] V(st)←V(st)+α[rt+γV(st+1)−V(st)]
其中 r t + γ V ( s t + 1 ) − V ( s t ) r_t+\gamma V(s_{t+1})-V(s_t) rt+γV(st+1)−V(st)通常被称为时序差分(temporal difference,TD)误差(error),时序差分算法将其与步长的乘积作为状态价值的更新量。可以用 r t + γ V ( s t + 1 ) r_t+\gamma V(s_{t+1}) rt+γV(st+1)来代替 G t G_t Gt的原因是:
V π ( s ) = E π [ G t ∣ S t = s ] = E π [ ∑ k = 0 ∞ γ k R t + k ∣ S t = s ] = E π [ R t + γ ∑ k = 0 ∞ γ k R t + k + 1 ∣ S t = s ] = E π [ R t + γ V π ( S t + 1 ) ∣ S t = s ] \begin{aligned} V_{\pi}(s)& =\mathbb{E}_\pi[G_t|S_t=s] \\ &=\mathbb{E}_\pi[\sum_{k=0}^\infty\gamma^kR_{t+k}|S_t=s] \\ &=\mathbb{E}_\pi[R_t+\gamma\sum_{k=0}^\infty\gamma^kR_{t+k+1}|S_t=s] \\ &=\mathbb{E}_\pi[R_t+\gamma V_\pi(S_{t+1})|S_t=s] \end{aligned} Vπ(s)=Eπ[Gt∣St=s]=Eπ[k=0∑∞γkRt+k∣St=s]=Eπ[Rt+γk=0∑∞γkRt+k+1∣St=s]=Eπ[Rt+γVπ(St+1)∣St=s]
蒙特卡洛方法将上式第一行作为更新的目标,而时序差分算法将上式最后一行作为更新的目标。于是,在用策略和环境交互时,每采样一步,我们就可以用时序差分算法来更新状态价值估计。时序差分算法用到了 V ( s t + 1 V(s_{t+1} V(st+1)的估计值,可以证明它最终收敛到策略 π \pi π的价值函数,我们在此不对此进行展开说明。
(一)Sarsa 算法
可以直接用时序差分算法来估计动作价值函数 Q Q Q
Q ( s t , a t ) ← Q ( s t , a t ) + α [ r t + γ Q ( s t + 1 , a t + 1 ) − Q ( s t , a t ) ] Q(s_t,a_t)\leftarrow Q(s_t,a_t)+\alpha[r_t+\gamma Q(s_{t+1},a_{t+1})-Q(s_t,a_t)] Q(st,at)←Q(st,at)+α[rt+γQ(st+1,at+1)−Q(st,at)]
用贪婪算法来选取在某个状态下动作价值最大的那个动作,即 arg max a Q ( s , a ) \arg\max_aQ(s,a) argmaxaQ(s,a)。这样似乎已经形成了一个完整的强化学习算法:用贪婪算法根据动作价值选取动作来和环境交互,再根据得到的数据用时序差分算法更新动作价值估计。
π ( a ∣ s ) = { ϵ / ∣ A ∣ + 1 − ϵ 如果 a = arg max a ′ Q ( s , a ′ ) ϵ / ∣ A ∣ 其他动作 \pi(a|s)=\begin{cases}\epsilon/|\mathcal{A}|+1-\epsilon&\textit{如果}a=\arg\max_{a^{\prime}}Q(s,a')\\\epsilon/|\mathcal{A}|&\textit{其他动作}\end{cases} π(a∣s)={ϵ/∣A∣+1−ϵϵ/∣A∣如果a=argmaxa′Q(s,a′)其他动作
动作价值更新用到了当前状态 s s s、当前动作 a a a、获得的奖励 r r r、下一个状态 s ′ s' s′和下一个动作 a ′ a' a′
(二)Q-learning 算法
Q-learning 和 Sarsa 的最大区别在于 Q-learning 的时序差分更新方式为
Q ( s t , a t ) ← Q ( s t , a t ) + α [ R t + γ max a Q ( s t + 1 , a ) − Q ( s t , a t ) ] Q(s_t,a_t)\leftarrow Q(s_t,a_t)+\alpha[R_t+\gamma\max_aQ(s_{t+1},a)-Q(s_t,a_t)] Q(st,at)←Q(st,at)+α[Rt+γmaxaQ(st+1,a)−Q(st,at)]
用价值迭代的思想来理解 Q-learning,即 Q-learning 是直接在估计 Q ∗ Q^* Q∗,因为动作价值函数的贝尔曼最优方程是
Q ∗ ( s , a ) = r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) max a ′ Q ∗ ( s ′ , a ′ ) Q^*(s,a)=r(s,a)+\gamma\sum_{s^{\prime}\in S}P(s^{\prime}|s,a)\max_{a^{\prime}}Q^*(s^{\prime},a^{\prime}) Q∗(s,a)=r(s,a)+γs′∈S∑P(s′∣s,a)a′maxQ∗(s′,a′)
而 Sarsa 估计当前 ϵ \mathcal{\epsilon} ϵ-贪婪策略的动作价值函数
算法上其实就是算Q现实时候不一样,上一步的Q现实
Q_learing
:下一步q表最大值 γ m a x a ′ Q ( s ′ , a ′ ) +r \gamma max_{a^{\prime}}Q(s^{\prime},a^{\prime})\text{+r} γmaxa′Q(s′,a′)+rSarsa
:具体的某一步估计q值 γ Q ( s ′ , a ′ ) +r \gamma Q(s^{\prime},a^{\prime})\text{+r} γQ(s′,a′)+r
目标:更新S环境下,做出a动作后的Q(s,a)
① Q-learning算法和Sarsa算法都是从状态s开始,根据当前的Q表使用一定的策略( Epsilon gready)选择一个动作a。
② 然后观测到下一个状态s’,此时这个状态下有个奖励r。
③ 并再次根据Q表选择动作a’。只不过两者选取a’的方法不同:
Q-learning
: 使用贪心策略(gready),即选取值最大的a’,此时只是计算出哪个a‘可以使Q(s,a)取到最大值,并没有真正采用这个动作a‘;Sarsa
: 仍使用策略策略(Epsilon gready),并真正采用了这个动作a‘ 。
④ 然后带入式子进行更新
Q-learning
: 行动策略是E-gready,但是目标策略,是gready,所以是off-policySarsa
: 行动策略是E-gready,但是目标策略,是E-gready,所以是on-policy
(三) 在线策略算法与离线策略算法
采样数据的策略为行为策略(behavior policy),称用这些数据来更新的策略为目标策略(target policy)。判断二者类别的一个重要手段是看计算时序差分的价值目标的数据是否来自当前的策略.
-
on-policy 行动策略和目标策略是同一个策略。
要求使用在当前策略下采样得到的样本进行学习,一旦策略被更新,当前的样本就被放弃了,就好像在水龙头下用自来水洗手;
-
off-policy 行动策略和目标策略不是同一个策略。
- 行动策略:就是每一步怎么选动作的方法,它产生经验样本
- 目标策略:我们选什么样更新方式,去寻找最好的Q表
-
对于 Sarsa,它的更新公式必须使用来自当前策略采样得到的五元组 ( s , a , r , s ′ , a ′ ) (s,a,r,s',a') (s,a,r,s′,a′),因此它是在线策略学习方法;
-
对于 Q-learning,它的更新公式使用的是四元组 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s′)来更新当前状态动作对的价值 Q ( s , a ) Q(s,a) Q(s,a),数据中的 s s s和 a a a是给定的条件, r r r和 s s s皆由环境采样得到,该四元组并不需要一定是当前策略采样得到的数据,也可以来自行为策略,因此它是离线策略算法。
四、DQN 算法
用函数拟合的方法来估计 Q Q Q值,即将这个复杂的 Q Q Q值表格视作数据,使用一个参数化的 Q θ Q_{\theta} Qθ函数来拟合这些数据。很显然,这种函数拟合的方法存在一定的精度损失,因此被称为近似方法。 DQN 算法可以用来解决连续状态下离散动作的问题。
经验回放(experience replay)的技术,将代理在每一个时间步的体验 e t = ( s t , a t , r t , s t + 1 ) e_t=(s_t,a_t,r_t,s_{t+1}) et=(st,at,rt,st+1)存放在数据集 D = e 1 , … , e N \mathcal{D}=e_1,\ldots,e_N D=e1,…,eN中,通过多个回合积累为一个回放记忆(replay memory)。在算法的内循环中,我们将 Q-learning 更新应用于从存储的记忆中随机采样的小批量经验样本 e ∼ D e∼\mathcal{D} e∼D。在执行完经验回放后,代理循 ** ϵ \epsilon ϵ贪婪策略选择并执行一个动作。由于标准神经网络难以处理不定长的输入,所以本研究通过一个函数 $\phi $ 先将序列状态映射为一个「固定长度」的表示,再作为 Q-函数的输入。
(一)2013版《Playing Atari with Deep Reinforcement Learning》伪代码:
首先初始化容量为 N N N的回放记忆 D \mathcal D D,以及随机权重的动作价值函数 Q Q Q;然后执行回合迭代(外循环,共 M M M 个回合),在每个回合中,先初始化序列 s 1 = { x 1 } s_1=\{x_1\} s1={x1},并将其预处理为定长 ϕ 1 = ϕ ( s 1 ) \phi_{1}=\phi(s_{1}) ϕ1=ϕ(s1);再执行时间步迭代(内循环,共 T T T步),在每一步中,先基于 ϵ \epsilon ϵ策略选择动作 a t a_t at(随机动作或当前最优动作),然后在模拟器中执行 a t a_t at观察奖励 r t r_t rt和图像 x t + 1 x_{t+1} xt+1;设置 s t + 1 = s t , a t , x t + 1 s_{t+1}=s_t,a_t,x_{t+1} st+1=st,at,xt+1并执行预处理 ϕ t + 1 = ϕ ( s t + 1 ) \phi_{t+1}=\phi(s_{t+1}) ϕt+1=ϕ(st+1);将当前时间步中得到的转移 ( ϕ t , a t , r t , p h i t + 1 ) (\phi_t,a_t,r_t,phi_{t+1}) (ϕt,at,rt,phit+1)存储到 D \mathcal D D中;基于 D \mathcal D D 随机采样小批量的转移 ( ϕ j , a j , r j , ϕ j + 1 ) (\phi_j,a_j,r_j,\phi_{j+1}) (ϕj,aj,rj,ϕj+1)根据 ϕ j + 1 \phi_{j+1} ϕj+1是否为终止状态,设置 y i y_i yi为:
y j = { r j for terminal ϕ j + 1 r j + γ max a ′ Q ( ϕ j + 1 , a ′ ; θ ) for non-terminal ϕ j + 1 \left.y_j=\left\{\begin{matrix}r_j&\text{for terminal }\phi_{j+1}\\r_j+\gamma\max_{a'}Q\left(\phi_{j+1},a';\theta\right)&\text{for non-terminal }\phi_{j+1}\end{matrix}\right.\right. yj={rjrj+γmaxa′Q(ϕj+1,a′;θ)for terminal ϕj+1for non-terminal ϕj+1
根据 (3) 式基于损失函数 ( y j − Q ( ϕ j , a j ; θ ) ) 2 \left(y_j-Q\left(\phi_j,a_j;\theta\right)\right)^2 (yj−Q(ϕj,aj;θ))2执行梯度下降,更新网络参数。执行上述过程直到达到收敛条件或循环结束。
与标准 Q-learning 相比,本研究提出的方法具有如下几点优势:首先,每一步中获得的经验都可能用于多次权重更新,这样可以提升数据的利用率;其次,由于样本间的强相关性,直接从连续样本中学习是低效的。随机化样本可以打破这种相关性,减少更新的方差;最后,对于 on-policy 式的学习来说,当前的参数决定了下一次训练所需的数据样本,由于执行当前动作后训练的分布会发生变化,因此延用当前策略可能会导致局部最优、参数发散等异常情况的发生;经验回放机制基于多个先前的状态对行为分布进行平均,可以平滑学习过程,避免参数的振荡和发散。同时由于使用了经验回放,梯度更新时的参数(状态)和用于生成样本的参数(状态)并不相同,因此自然需要使用 类似 Q-learning 的 off-policy方法。
(二)2015版《Human-level control through deep reinforcement learning》伪代码:
1. Experience Replay
概念:在传统的Q-learning中,智能体(agent)在 s t s_t st 中执行 a t a_t at ,得到Reward r t r_t rt,和下一个状态 s t + 1 s_{t+1} st+1。此时根据 Q-learning的更新公式有:
Q ( s t , a t ; θ ) = Q ( s t , a t ; θ ) + α ( r t + γ ∗ m a x a Q ( s t + 1 , a ; θ ) − Q ( s t , a t ; θ ) ) Q(s_t,a_t;\theta)=Q(s_t,a_t;\theta)+\alpha\left(r_t+\gamma*max_aQ(s_{t+1},a;\theta)-Q(s_t,a_t;\theta)\right) Q(st,at;θ)=Q(st,at;θ)+α(rt+γ∗maxaQ(st+1,a;θ)−Q(st,at;θ))
将 e t = ( s t , a t , r t , s t + 1 ) e_t=(s_t,a_t,r_t,s_{t+1}) et=(st,at,rt,st+1)带入上式更新 θ \theta θ( e t e_t et为t时刻的transition或experience)。
然而在 Experience Replay 中,智能体在 s t s_t st 中执行 a t a_t at,得到Reward r t r_t rt,和下一个状态 s t + 1 s_{t+1} st+1。却并不是直接将 e t = ( s t , a t , r t , s t + 1 ) e_t=(s_t,a_t,r_t,s_{t+1}) et=(st,at,rt,st+1)带入上式,而是将 e t e_t et 放入一个固定大小的experience pool D \mathcal D D中;然后,从 D \mathcal D D中 均匀随机采样出 B B B个 e e e作为一个batch,比如 e 1984 , e 38711 , e 230 , … . {e1984,e38711,e230,….} e1984,e38711,e230,….,代入上式。
淘汰:既然 D \mathcal D D是固定大小的,那当 D \mathcal D D满了之后,需要淘汰最早进入 D \mathcal D D的transition。
优势:Experience Replay的优势论文中提到三点:
- 重复使用 Transition,提高data effienciy。传统的Q-learning中,transition使用一次便丢弃了。
- 打散连续transition之间的相关性,同一个batch中的transition都是时间上不相邻的。如果同一个batch之间是相邻的,会影响网络的训练。
off-policy:Experience Replay 只能用在在off-policy算法上上。on-policy中,当我们利用transition e t e_t et更新policy时, e t e_t et是同一个policy所生成的。然而在Experience Replay的更新中,每次用很久之前的某个transition e j e_j ej更新 Q Q Q, 而生成 e j e_j ej的 policy早已不是当前的policy了,在从$ j→t 的这段时间中, p o l i c y 已经改变了 的这段时间中,policy已经改变了 的这段时间中,policy已经改变了t-j$次了(因为policy本质上就是 Q Q Q ,而 Q Q Q一直都在变)。自然而然,这篇paper选用了一个off policy的算法,Q-learning。
2. Target Network
Q-learning的Loss Function:
L ( θ ) = 1 2 ( r t + γ ∗ m a x a Q ( s t + 1 , a ; θ ) − Q ( s t , a t ; θ ) ) 2 L(\theta)=\frac12(r_t+\gamma*max_aQ(s_{t+1},a;\theta)-Q(s_t,a_t;\theta))^2 L(θ)=21(rt+γ∗maxaQ(st+1,a;θ)−Q(st,at;θ))2
如果类比监督学习,那么groundtruth y t = r t + γ ∗ m a x a Q ( s t + 1 , a ; θ ) y_t=r_t+\gamma*max_aQ(s_{t+1},a;\theta) yt=rt+γ∗maxaQ(st+1,a;θ),但是与监督学习不一样, y y y中还涉及了 θ θ θ 。论文中提出的解决方式是用一个target Network(参数为 θ − \theta^{-} θ−)替换 y y y中的网络 (参数为 θ θ θ ),loss function变为了如下形式: L ( θ ) = 1 2 ( r t + γ ∗ m a x a Q ( s t + 1 , a ; θ − ) − Q ( s t , a t ; θ ) ) 2 L(\theta)=\frac12\left(r_t+\gamma*max_aQ(s_{t+1},a;\theta^-)-Q(s_t,a_t;\theta)\right)^2 L(θ)=21(rt+γ∗maxaQ(st+1,a;θ−)−Q(st,at;θ))2
现在 y t y_t yt与 θ θ θ没有关系了,可以按照监督学习的那一套更新 θ θ θ。
(每隔 C = 10000 C=10000 C=10000个 timestep,更新 target Network中的值: θ = θ − θ=θ^{-} θ=θ−)
DQN代码:
转自DQN(Deep Q Network)及其代码实现,仅添加wandb可视化内容
需要安装OpenAI gym,wandb的使用也很简单
import torch # 导入torch
import torch.nn as nn # 导入torch.nn
import torch.nn.functional as F # 导入torch.nn.functional
import numpy as np # 导入numpy
import gym # 导入gym
import wandb
# 超参数
BATCH_SIZE = 32 # 样本数量
LR = 0.01 # 学习率
EPSILON = 0.9 # greedy policy
GAMMA = 0.9 # reward discount
TARGET_REPLACE_ITER = 100 # 目标网络更新频率
MEMORY_CAPACITY = 2000
env = gym.make("CartPole-v1", render_mode="human") # 记忆库容量
N_ACTIONS = env.action_space.n # 杆子动作个数 (2个)
N_STATES = env.observation_space.shape[0] # 杆子状态个数 (4个)
# 定义Net类 (定义网络)
class Net(nn.Module):
def __init__(self): # 定义Net的一系列属性
# nn.Module的子类函数必须在构造函数中执行父类的构造函数
super(Net, self).__init__() # 等价与nn.Module.__init__()
self.fc1 = nn.Linear(N_STATES, 50) # 设置第一个全连接层(输入层到隐藏层): 状态数个神经元到50个神经元
self.fc1.weight.data.normal_(0, 0.1) # 权重初始化 (均值为0,方差为0.1的正态分布)
self.out = nn.Linear(50, N_ACTIONS) # 设置第二个全连接层(隐藏层到输出层): 50个神经元到动作数个神经元
self.out.weight.data.normal_(0, 0.1) # 权重初始化 (均值为0,方差为0.1的正态分布)
def forward(self, x): # 定义forward函数 (x为状态)
x = F.relu(self.fc1(x)) # 连接输入层到隐藏层,且使用激励函数ReLU来处理经过隐藏层后的值
actions_value = self.out(x) # 连接隐藏层到输出层,获得最终的输出值 (即动作值)
return actions_value # 返回动作值
# 定义DQN类 (定义两个网络)
class DQN(object):
def __init__(self): # 定义DQN的一系列属性
self.eval_net, self.target_net = Net(), Net() # 利用Net创建两个神经网络: 评估网络和目标网络
self.learn_step_counter = 0 # for target updating
self.memory_counter = 0 # for storing memory
self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2)) # 初始化记忆库,一行代表一个transition
self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR) # 使用Adam优化器 (输入为评估网络的参数和学习率)
self.loss_func = nn.MSELoss() # 使用均方损失函数 (loss(xi, yi)=(xi-yi)^2)
def choose_action(self, x): # 定义动作选择函数 (x为状态)
x = torch.unsqueeze(torch.FloatTensor(x), 0) # 将x转换成32-bit floating point形式,并在dim=0增加维数为1的维度
if np.random.uniform() < EPSILON: # 生成一个在[0, 1)内的随机数,如果小于EPSILON,选择最优动作
actions_value = self.eval_net.forward(x) # 通过对评估网络输入状态x,前向传播获得动作值
action = torch.max(actions_value, 1)[1].data.numpy() # 输出每一行最大值的索引,并转化为numpy ndarray形式
action = action[0] # 输出action的第一个数
else: # 随机选择动作
action = np.random.randint(0, N_ACTIONS) # 这里action随机等于0或1 (N_ACTIONS = 2)
return action # 返回选择的动作 (0或1)
def store_transition(self, s, a, r, s_): # 定义记忆存储函数 (这里输入为一个transition)
transition = np.hstack((s, [a, r], s_)) # 在水平方向上拼接数组
# 如果记忆库满了,便覆盖旧的数据
index = self.memory_counter % MEMORY_CAPACITY # 获取transition要置入的行数
self.memory[index, :] = transition # 置入transition
self.memory_counter += 1 # memory_counter自加1
def learn(self): # 定义学习函数(记忆库已满后便开始学习)
# 目标网络参数更新
if self.learn_step_counter % TARGET_REPLACE_ITER == 0: # 一开始触发,然后每100步触发
self.target_net.load_state_dict(self.eval_net.state_dict()) # 将评估网络的参数赋给目标网络
self.learn_step_counter += 1 # 学习步数自加1
# 抽取记忆库中的批数据
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE) # 在[0, 2000)内随机抽取32个数,可能会重复
b_memory = self.memory[sample_index, :] # 抽取32个索引对应的32个transition,存入b_memory
b_s = torch.FloatTensor(b_memory[:, :N_STATES])
# 将32个s抽出,转为32-bit floating point形式,并存储到b_s中,b_s为32行4列
b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES+1].astype(int))
# 将32个a抽出,转为64-bit integer (signed)形式,并存储到b_a中 (之所以为LongTensor类型,是为了方便后面torch.gather的使用),b_a为32行1列
b_r = torch.FloatTensor(b_memory[:, N_STATES+1:N_STATES+2])
# 将32个r抽出,转为32-bit floating point形式,并存储到b_s中,b_r为32行1列
b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])
# 将32个s_抽出,转为32-bit floating point形式,并存储到b_s中,b_s_为32行4列
# 获取32个transition的评估值和目标值,并利用损失函数和优化器进行评估网络参数更新
q_eval = self.eval_net(b_s).gather(1, b_a)
# eval_net(b_s)通过评估网络输出32行每个b_s对应的一系列动作值,然后.gather(1, b_a)代表对每行对应索引b_a的Q值提取进行聚合
q_next = self.target_net(b_s_).detach()
# q_next不进行反向传递误差,所以detach;q_next表示通过目标网络输出32行每个b_s_对应的一系列动作值
q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1)
# q_next.max(1)[0]表示只返回每一行的最大值,不返回索引(长度为32的一维张量);.view()表示把前面所得到的一维张量变成(BATCH_SIZE, 1)的形状;最终通过公式得到目标值
loss = self.loss_func(q_eval, q_target)
# 输入32个评估值和32个目标值,使用均方损失函数
self.optimizer.zero_grad() # 清空上一步的残余更新参数值
loss.backward() # 误差反向传播, 计算参数更新值
self.optimizer.step() # 更新评估网络的所有参数
# 初始化 WandB
wandb.init(project='DQN', name='DQN2') # 替换成你的项目名和运行名
dqn = DQN()
for i in range(400): # 400个episode循环
print('<<<<<<<<<Episode: %s' % i)
s,_ = env.reset() # 重置环境
episode_reward_sum = 0 # 初始化该循环对应的episode的总奖励
while True: # 开始一个episode (每一个循环代表一步)
env.render() # 显示实验动画
a = dqn.choose_action(s) # 输入该步对应的状态s,选择动作
s_, r, done, info, _= env.step(a) # 执行动作,获得反馈
# 修改奖励 (不修改也可以,修改奖励只是为了更快地得到训练好的摆杆)
x, x_dot, theta, theta_dot = s_
r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
new_r = r1 + r2
dqn.store_transition(s, a, new_r, s_) # 存储样本
episode_reward_sum += new_r # 逐步加上一个episode内每个step的reward
s = s_ # 更新状态
if dqn.memory_counter > MEMORY_CAPACITY: # 如果累计的transition数量超过了记忆库的固定容量2000
# 开始学习 (抽取记忆,即32个transition,并对评估网络参数进行更新,并在开始学习后每隔100次将评估网络的参数赋给目标网络)
dqn.learn()
if done: # 如果done为True
# round()方法返回episode_reward_sum的小数点四舍五入到2个数字
print('episode%s---reward_sum: %s' % (i, round(episode_reward_sum, 2)))
# 记录奖励值到 WandB
wandb.log({'Reward': episode_reward_sum}, step=i)
break
# 关闭 WandB
wandb.finish() # 该episode结束
env.close()
gym场景输出
reward结果
wandb跑出的曲线,同样参数跑了两轮