一、算法详解

文章最后附有博主自己实现的深度qlearning玩space invader游戏
Deep QLearning算法详解(强化学习 Reinforcement Learning)-编程之家

本文介绍的是基于神经网络的qlearning算法。我们知道传统的qlearning算法只能处理状态和动作有限的情况,对于无穷多,则是无法有效处理的。现实生活中,环境的状态肯定是无穷多的,而神经网络正好可以处理这样的情况。这里深度qlearning算法使用一个神经网络来表示一个q表,无论环境的状态有没有出现过,我们都可以将状态输入到神经网络,去评估价值函数。

1.1 几个概念

1.1.1 什么是critic?

critic:批评家,评论家。
在这里算法要更新就是一个critic,而不是一个actor(agent),使用critic来间接指导actor做决策,critic的作用就是评估一个actor有多好,水平高不高。

1.1.2 状态价值函数Vπ(s)V ^{\pi }(s)Vπ(s)和状态动作价值函数Qπ(s,a)Q^{\pi }(s,a)Qπ(s,a),以及他们之间的关系

π\piπ表示一个actor,s是一个状态,a是指actor看到状态s时做出的动作

  1. Vπ(s)V ^{\pi }(s)Vπ(s)指使用actor π\piπ时,当看到状态s时,计算累计奖励的期望值
    举个例子吧,不然有点难以理解,这里计算的依然是累计奖励的期望值。我个人理解,如果要写出具体 Vπ(s)V ^{\pi }(s)Vπ(s)公式的话,应该是这样
    Vπ(s)=∑τR(τ,s)p(τ)(1)V^{\pi}(s)=\sum_{\tau }R(\tau ,s)p(\tau)\tag{1}Vπ(s)=τR(τ,s)p(τ)(1)
    公式解释:R(τ,s)R(\tau ,s)R(τ,s)表示在一个eposide内,只计算s之后的所有累计奖励的和。比如τ={s1,a1,r1,s2,a2,r2,…,sT,aT,rT,End}\tau =\{s_{1},a_{1},r_{1},s_{2},a_{2},r_{2},…,s_{T},a_{T},r_{T},End\}τ={s1,a1,r1,s2,a2,r2,...,sT,aT,rT,End},我们计算R(τ,s2)R(\tau ,s_{2})R(τ,s2),由上面的定义知道,R(τ,s2)=r2+…+rTR(\tau ,s_{2})=r_{2}+…+r_{T}R(τ,s2)=r2+...+rT,这里没有计算r1r_{1}r1,只计算看到s2s_{2}s2之后的所有奖励。p(τ)p(\tau)p(τ)表示eposide τ\tauτ出现的概率。
    举一个sutton强化学习书上的例子。
    例子1:
    假设我们采样得到8个eposides,分别如下:
    1、sa,r=0,sb,r=0,Ends_{a},r=0,s_{b},r=0,Endsa,r=0,sb,r=0,End
    2、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    3、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    4、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    5、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    6、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    7、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    8、sb,r=0,Ends_{b},r=0,Endsb,r=0,End
    这里给出了8个采样得到的eposide,并且忽略所采取的动作,这里是采样得到8个eposide来逼近Vπ(s)V^{\pi}(s)Vπ(s),选择采样来逼近Vπ(s)V^{\pi}(s)Vπ(s),是因为actor所处的环境和actor本身都具有随机性,由上面的公式(1)可以看到,如果所有的eposides有无穷多个,那么计算机根本无法实现计算Vπ(s)V^{\pi}(s)Vπ(s)
    根据上面的8个eposides,可以计算得到:
    Vπ(sb)=1+1+1+1+1+18=34V^{\pi}(s_{b})=\frac{1+1+1+1+1+1}{8}=\frac{3}{4}Vπ(sb)=81+1+1+1+1+1=43
    只有在第二到第七个eposide时,遇到状态sbs_{b}sb才有奖励,第一个eposide中,当遇到状态sbs_{b}sb时,所有累积的奖励值是0。第8个eposide也是如此。就算第一个eposide变成sa,r=1,sb,r=0,Ends_{a},r=1,s_{b},r=0,Endsa,r=1,sb,r=0,EndVπ(s)V^{\pi}(s)Vπ(s)依然不变还是34\frac{3}{4}43.

  2. Qπ(s,a)Q^{\pi }(s,a)Qπ(s,a)指actor看到状态sss之后确定选择动作aaa之后的奖励累积期望
    为什么说确定选择动作aaa呢?看到状态sss,其实有很多个动作可以选择,但是在这里就是确定选择动作aaa,而不是别的动作。还有一个因素就是选择动作的时候具有随机性,比如采用ε−greedy\varepsilon -greedyεgreedy方法,会有一定的几率随机选择动作。

  3. 两者之间的关系
    假设有…,st,at,rt,st+1,……,s_{t},a_{t},r_{t},s_{t+1},…...,st,at,rt,st+1,...,那么就有Qπ(st,at)=E(rt+Vπ(st+1))Q^{\pi }(s_{t},a_{t})=E(r_{t}+V ^{\pi }(s_{t+1}))Qπ(st,at)=E(rt+Vπ(st+1))
    这里还是要求期望的。不过在实际操作的时候需要采样或者直接把变成Qπ(st,at)=rt+Vπ(st+1)Q^{\pi }(s_{t},a_{t})=r_{t}+V ^{\pi }(s_{t+1})Qπ(st,at)=rt+Vπ(st+1)。这种情况就是只采样一个用来逼近期望。这样网络收敛的效果可能不是太好,毕竟用一条样本来逼近还是效果不大好的。

1.1.3 如何计算Vπ(s)V ^{\pi }(s)Vπ(s)

  1. Monte-Carlo(MC)方法
    如下图,我们使用一个神经网络来计算Vπ(s)V ^{\pi }(s)Vπ(s),并且根据actor玩游戏的实际情况,来优化这个神经网络,并且利用这个神经网络(q表,我们前面说到了使用神经网络来表示这个q表以处理状态极其复杂多变的情况,所以神经网络==q表)来指导actor进行决策。
    Deep QLearning算法详解(强化学习 Reinforcement Learning)-编程之家
    例子:假设有状态 sa和sbs_{a}和 s_{b}sasb,计算Vπ(sa)和Vπ(sb)V ^{\pi }(s_{a})和V ^{\pi }(s_{b})Vπ(sa)Vπ(sb)的步骤如下:
    对于Vπ(sa)V ^{\pi }(s_{a})Vπ(sa):
    (1) actor(agent)玩游戏或者和环境互动.
    (2) 状态 sas_{a}sa 出现在某个回合(episode)中.
    (3) 记录从状态 sas_{a}sa 出现到这个回合结束后的累积的奖励,我们将其记为GaG_{a}Ga.
    (4) 将状态sas_{a}sa输入到神经网络然后输出 Vπ(sa)V ^{\pi }(s_{a})Vπ(sa),这个神经网络输出的是个标量.
    (5) 利用 回归(regression) 的方法来优化神经网络,使得神经网络的输出的Vπ(sa)V ^{\pi }(s_{a})Vπ(sa)来逼近GaG_{a}Ga.
    (6) 对于任何一个状态都可以重复以上过程,直到神经网络收敛。
    对于Vπ(sb)V ^{\pi }(s_{b})Vπ(sb)的计算也是如此。

  2. Temporal-Difference-Approach(时间差分方法)
    时间差分方法,顾名思义,肯定是需要两个连续时间步上的状态才能达到训练神经网络的目的。
    假设actor玩游戏或者与环境互动的一个episode中的连续两步是…,st,at,rt,st+1,……,s_{t},a_{t},r_{t},s_{t+1},…...,st,at,rt,st+1,...
    首先,Vπ(st)=rt+Vπ(st+1)(2)V^{\pi }(s_{t})=r_{t}+V^{\pi }(s_{t+1})\tag{2}Vπ(st)=rt+Vπ(st+1)(2)
    π{\pi}π表示agent或者actor
    时间差分方法每连续的两个时间步都会训练一次神经网络,因此收敛的速度也会相对来说较快。步骤如下
    (1) 假设在时间步 ttt 观测到状态 sts_{t}st
    (2) actor根据现在的状态 sts_{t}st 做出动作 ata_{t}at ,得到奖励 rtr_{t}rt
    (3) actor观测到下一个时间步 t+1t+1t+1 的状态 st+1s_{t+1}st+1
    (4) 将状态 sts_{t}st, st+1s_{t+1}st+1 输入进神经网络得到 Vπ(st)V^{\pi }(s_{t})Vπ(st)Vπ(st+1)V^{\pi }(s_{t+1})Vπ(st+1)
    (5) 利用公式2,我们将Vπ(st)V^{\pi }(s_{t})Vπ(st)Vπ(st+1)V^{\pi }(s_{t+1})Vπ(st+1)的差值逼近时间步的奖励 rtr_{t}rt. 还是利用回归(Regression)的方法来训练神经网络。
    Deep QLearning算法详解(强化学习 Reinforcement Learning)-编程之家
    图片来自李宏毅老师的强化学习课程,侵删!!!

  3. 两种方法之间的关系
    蒙特卡洛方法有更大的方差,方差大说明效果不好,相比来说时间差分方法的训练速度更加高效,收敛的更快,时间差分方法只需要使用两个时间步的信息就可以训练网络,而蒙特卡洛方法却需要等待一个完整的episode完成之后才可以进行训练。不知道大家发现没有,突然觉得这个方法也是有点类似监督学习方法,这个深度qlearning算法里面的“监督信息是环境反馈过来的奖励”,我们需要使用奖励来指引actor的学习。
    对于同一个采样出来的样本,使用两种方法计算出来的同一个状态 sas_{a}sa 的价值函数是不一样的
    比如下面的例子:
    假设我们采样得到8个eposides,分别如下:
    1、sa,r=0,sb,r=0,Ends_{a},r=0,s_{b},r=0,Endsa,r=0,sb,r=0,End
    2、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    3、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    4、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    5、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    6、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    7、sb,r=1,Ends_{b},r=1,Endsb,r=1,End
    8、sb,r=0,Ends_{b},r=0,Endsb,r=0,End
    我们先使用蒙特卡洛方法计算 Vπ(sa)V^{\pi}(s_{a})Vπ(sa):
    观察采样出来的数据,我们可以看到状态 sas_{a}sa 只是出现在 第一条样本里面,而且直到游戏结束,得到的两个奖励值都是0,所以 Ga=0G_{a}=0Ga=0,因为我们需要让 Vπ(sa)V^{\pi}(s_{a})Vπ(sa) 逼近 GaG_{a}Ga,所以理想情况下有Vπ(sa)=0V^{\pi}(s_{a})=0Vπ(sa)=0.
    接着使用时间差分方法来计算Vπ(sa)V^{\pi}(s_{a})Vπ(sa)
    从第一条采样样本可以看到状态 sas_{a}sasbs_{b}sb 是两个相邻的状态,因此由公式2可以知道计算 Vπ(sa)V^{\pi}(s_{a})Vπ(sa),需要使用 Vπ(sb)V^{\pi}(s_{b})Vπ(sb)的值。
    根据采样的8条样本,我们使用这八条样本的 $V^{\pi}(s_{b})_{i},i=1,2,…,8的期望值来近似逼近 Vπ(sb)V^{\pi}(s_{b})Vπ(sb)
    因此有Vπ(sb)=1+1+1+1+1+18=34V^{\pi}(s_{b})=\frac{1+1+1+1+1+1}{8}=\frac{3}{4}Vπ(sb)=81+1+1+1+1+1=43
    由在第一条样本中在状态 sas_{a}sa 时actor得到的奖励是0,所以Vπ(sa)=0+Vπ(sb)=34V^{\pi }(s_{a})=0+V^{\pi }(s_{b})=\frac{3}{4}Vπ(sa)=0+Vπ(sb)=43.
    可以看到,不同的计算方法得到的价值函数的值是不一样的。

二、算法运行流程

步骤一: actor(agent) π{\pi}π和环境(Environment)互动
步骤二: 使用蒙特卡洛方法或者时间差分方法来计算状态价值函数Vπ(s)V^{\pi}(s)Vπ(s)或者状态动作价值函数Qπ(s,a)Q^{\pi}(s,a)Qπ(s,a)
步骤三: 使用回归(Regression)方法训练神经网络
步骤四: 价值函数指导actor做出更好的策略,循环以上步骤,直到收敛。

基于TD方法的QLearning具体如下图:
Deep QLearning算法详解(强化学习 Reinforcement Learning)-编程之家
算法流程里面的butter要改为buffer。

三、几个小技巧

3.1 技巧1 target网络和predict网络

其实在传统的qlearning里面以及涉及到了“q_target和q_predict”的概念了,这里的深度qlearning在训练的时候同样也是由target网络和predict网络,只不过是两个网络需要共享参数,其中target是固定的,只有在predict网络以target的输出为目标更新若干次之后采后将predict网络的参数重新赋给target网络。
有需要可以看看传统的qlearning算法。传送门:传统qlearning算法讲解

我们以李宏毅老师的课程讲解如何训练的
Deep QLearning算法详解(强化学习 Reinforcement Learning)-编程之家
这里使用的是时间差分方法来训练的
右边的 Target 网络的参数在一定时间内是固定的,Target网络输出的值是左边网络需要回归的目标,然后更新这个来更新左边的predict网络,更新若干次之后,然后将predict网络的参数重新赋给Target网络,一直训练,直到收敛。

3.2、技巧2之 ε−greedy\varepsilon -greedyεgreedy选择动作

使用这个技巧有利于actor学会探索,也可以确保当训练的次数足够多时,所有的动作都可以被的更新到。随机探索的可能性会随着训练的进行逐渐变小的
我们之前在强化学习(RL)QLearning算法详解介绍过了这个技巧,不再重复。

3.3 技巧3 Boltzmann选择动作

利用状态动作价值函数的大小来选择动作,值越大,这个对于的动作被选择的概率就越大,对于的动作被选择的概率公式如下:
P(a∣s)=exp(Q(s,a))∑aexp(Q(s,a))(3)P(a|s)=\frac{exp(Q(s,a))}{\sum_{a}exp(Q(s,a))}\tag{3}P(as)=aexp(Q(s,a))exp(Q(s,a))(3)

3.4 技巧4 RePlay Buffer

设计一个Buffer将actor玩过的experience存储起来,可以重复使用这个数据更新网络。
假如使用时间差分方法,我们可以在Buffer存储器里面存储只需要两个时间步就可以,比如其中一条数据可以是:st,at,rt,st+1s_{t},a_{t},r_{t},s_{t+1}st,at,rt,st+1,当储存很多时,也可以进行batch学习。

四、小例子

基于卷积神经网络的小例子

4.1 readme

  1. 安装gym
  2. 安装atari-py
    在第二步很容易出现ale_c.dll不存在的问题。
    以下是解决方法
    分三步:
    第一步:先卸载atari-py。pip uninstall atari-py
    第二步:再重新安装这个。pip install –no-index -f https://github.com/Kojoley/atari-py/releases atari_py
    第三步:pip install gym
# -*- coding: utf-8 -*-import gym
import torch.nn as nn
import torch as t
from torch.nn import functional as F
import randomdicount_factor = 0.9
eplison = 0.1
lr = 0.001
epochs = 50
nums_p2t = 100 #每隔100词将Q_Net_predict的参数赋给Q_Net_target,然后继续固定target网络
env = gym.make("SpaceInvaders-v0") # 构造一个太空入侵者的环境# 下面这个神经网络是用来预测
class Q_Net_predict(nn.Module):    def __init__(self, nums_action):super(Q_Net_predict,self).__init__()#下面开始定义卷积和全连接层,计划使用两个全连接层和两个卷积层self.conv1 = nn.Conv2d(3, 16, 5, 2)self.conv2 = nn.Conv2d(16, 16, 5, 2)self.linear1 = nn.Linear(1728,256)self.linear2 = nn.Linear(256,nums_action)def forward(self, x):#先进行类型的转换state = t.from_numpy(x[:,:,::-1].copy())state = state.permute((2,0,1)).unsqueeze(dim=0).float()#开始使用卷积,最大池化和线性层out = self.conv1(state)out = F.relu(out)out = F.max_pool2d(out,(2,2))out = self.conv2(out)out = F.relu(out)out = F.max_pool2d(out,(2,2))s = out.size()out = out.view(1,s[1]*s[2]*s[3])out = F.relu(self.linear1(out))out = self.linear2(out)return out# 下面这个神经网络是用来作为Q_Net_predict拟合的目标函数
class Q_Net_target(nn.Module):    def __init__(self, nums_action):super(Q_Net_target,self).__init__()#下面开始定义卷积和全连接层,计划使用两个全连接层和两个卷积层self.conv1 = nn.Conv2d(3, 16, 5, 2)self.conv2 = nn.Conv2d(16, 16, 5, 2)self.linear1 = nn.Linear(1728,256)self.linear2 = nn.Linear(256,nums_action)def forward(self, x):#先进行类型的转换state = t.from_numpy(x[:,:,::-1].copy())state = state.permute((2,0,1)).unsqueeze(dim=0).float()#开始使用卷积,最大池化和线性层out = self.conv1(state)out = F.relu(out)out = F.max_pool2d(out,(2,2))out = self.conv2(out)out = F.relu(out)out = F.max_pool2d(out,(2,2))s = out.size()out = out.view(1,s[1]*s[2]*s[3])out = F.relu(self.linear1(out))out = self.linear2(out)return outdef choose_action(logits):## 使用eplison-greedy选择agent需要执行的动作v = random.uniform(0, 1)q_value, index = t.topk(logits, 1, dim = 1)#下面开始eplison-greedy 算法if v > eplison:#这里是求最大的状态价值函数对应的动作q_value_t = logits[0,index[0][0]]action = index[0][0].item()else:#下面是随机产生动作action = random.randint(0, 5)q_value_t = logits[0, action]return action, q_value_tdef q_learning():all_count = 0#下面开始#先定义两个状态价值函数网络q_target = Q_Net_target(6)q_predict = Q_Net_predict(6)#定义一个优化器opt_Adam = t.optim.Adam(q_predict.parameters(),lr = lr)#将target网络的参数冻结for p in q_target.parameters():p.requires_grad = Falsefor _ in range(epochs):done = False#初始化一个状态observation = env.reset() #每个episode的初始状态while not done:env.render()#下面开始网络的参数复制if all_count % nums_p2t == 0:target_paras = q_target.state_dict()predict_paras = q_predict.state_dict()target_paras.update(predict_paras)q_target.load_state_dict(target_paras)#下面使用q_predict网络的输出选择动作predict_logits = q_predict(observation)action, q_value_t = choose_action(logits= predict_logits)#下面根据动作得到奖励以及下一个时间步的状态observationobservation, reward, done, info = env.step(action)#现在有了observation,需要使用使用target网络计算observation的状态价值函数target_qvalue = q_target(observation)q_value_t_ = max(target_qvalue[0]).item()#我们需要使reward+q_value_t_ 和 q_value_t接近loss = (reward + q_value_t_ - q_value_t)**2loss.backward()opt_Adam.step()all_count +=1env.close()if __name__ == '__main__':q_learning()

五、参考文献

1、李宏毅老师的强化学习算法
2、莫烦python的强化学习系列
3、ale_c.dll确实解决方法
4、openai官网