强化学习可以根据是否直接输出动作分为value-based和policy-based方法。前者根据预测和控制求出最优值函数(V或Q),然后通常取贪心策略来得出最优动作,后者直接一些,他直接输出动作,即policy-based。
value-based已经比较不错,但是仍需学习policy-based原因有三点:
①:value-based无法很好解决连续动作的RL问题,比如行车,机械臂控制等。②:value-based方法常因观测受限而无法求出最优解。
③:value-based无法解决需要输出随机策略的问题,比如石头剪刀布游戏。
而以上三点可以通过policy-based方法来解决,即基于策略的强化学习方法。
基于策略的强化学习方法有:REFORCEMENT、actor-critic、A2C(A3C)、DDPG等,他们之间是依次提升的关系,但不了解最基础的算法的话,后面的高级算法将很难理解。
因此,本次我将以PyTorch为平台,以gym库的CartPole-v0为环境来实现policy-based的最基础算法REFORCEMENT。
参考代码:莫凡REFORCEMENT(他这是TF写的,Pytorch的话自己转一下就好,Pytorch的核心代码放在2.2节)
一、实验环境:
首先介绍下强化学习经典环境CartPole-v0:
我采用了gym的CartPole-v0来进行实验。
CartPole游戏就是小车去平衡杆子,让Agent去学习策略从而让杆子平衡。触发终止状态的有三个条件:1、杆子偏路中心角度超过12°。2、小车位子超过2.4(整个屏幕4.8,左右各2.4)。3、超过200步。
原gym设置的奖励是,200步之内,只要没触发前2个条件,每一步都能得1分。那么我们的RL任务目标也就明显了。就是尽可能多的获取更多的游戏得分,想要高分,小车必须坚持下去,也就是平衡住杆子。
状态空间是Box(4, ),即4维的向量,分别是小车位置、小车速度、杆子偏离竖直的角度、杆子的角速度,用一个numpy.ndArray表示。动作空间是Discrete(2),即向左开车和向右开车,用列表[0,1]表示。
小车的初始位置以及初始角度(弧度制)是在[-0.5,0.5]之间的随机位置。
二、REFORCEMENT算法
2.1、算法简介与分析
2.2、核心代码
2.3、实验过程及结果
2.1、算法简介与分析
实现该算法需要注意4点:
①:将该算法看成是一个分类问题。
②:将梯度更新看成是梯度下降问题
③:将每个输入网络的状态看成单个样本,将一串轨迹看成一个batch。
④:该算法是回合更新制,像MC算法一样,不像Q-learning,是按步更新。
⑤:上面伪代码中实现细节剖析。
针对①:如下图所示,策略的输出是一个神经网络,我们用PyTorch包来实现,这个实验比较简单,因此用1个全连接层即可,输入是状态,故网络的输入是特征数,CartPole-v0环境的状态有4个值,故输入4个神经元。输出的动作有2个,故输出层有2个神经元,所以我们的策略网络是一个2层的网络。策略有2种方式,对于离散动作我们常用softmax策略,对于连续动作我们采用高斯策略。本次动作为向左和向右,故我们选取softmax策略,即输出层后接softmax层,也就是说网络的输出为各个动作的概率。回忆下我们的分类常用softmax接cross entropy实现loss的计算,其中必不可少的就是标签了。那么在我们这个算法中标签是我们根据策略网络来选取的动作,比如80%输出向左,20%向右,那么我们就在这个概率分布中采样,采到的动作就是我们要输出的动作,同时作为标签。那么由于这是随机选取的,故这个标签不一定像分类问题的标签那样准确,故我们还需要用一个critic来做评判,这就是我们的Gt,即在每个状态施加动作a后,后续的衰减累计奖励。另外为了减弱REFORCEMENT本身因为Gt之间的高方差问题,因此可将Gt进行标准化处理(你不做这一步也可以的)。
②:REFORCEMENT这种policy-based算法的核心就是通过梯度上升来max化期望累计奖励,但是PyTorch中的optimization是基于梯度下降的,那么为了使用这个优化器,我们只需在算法中求梯度那里加个“负号”,然后梯度上升那里加个“负号”,这样就完美改造成了梯度下降,而且构造出一个loss来。这里我们不能使用PyTorch自带的那个捆绑了sotfmx和交叉熵那个loss类来使用,需要拆分开来。拆分成softmax+log+crossentropy。建议策略网络使用softmax层nn.Softmax来选择动作,而构建loss使用nn.LogSoftmax+cross entropy来实现。这么做的目的一来是LogSoftmax速度更快,数值更佳,二来拆分开来主要为了插入Gt这个评判家。交叉熵那边其实就是个根据标签索引来选择概率的函数,建议使用one-hot编码标签。
③:上面图中的代码是在一个回合之后,按每一个输入样本来更新参数,有点类似于SGD,但我们根据经验,可以使用小批量更新来实现,即将一串轨迹的样本看成一个batch来输入。
④:这个算法是policy-based最基础的算法,使用的是回合更新制,且每一串轨迹只使用一次,用完就丢掉。学习过MC和TD的就知道,后续还会有基于步更新的actor-critic算法。故该算法的会和更新值的速度自然没actor-critic快,但他是policy-gradient的开端,有必要其复现一下。
⑤:Gnerate...意思是在每串完整序列开始的时候,在初始状态选择动作a,然后用a去和环境互动,拿到下个状态、奖励、is_done,这里和Q-learning、Sarsa中是一样的。
Loop for...意思是训练过程。
2.2、核心代码
# x为全连接层的输出,y为numpy格式标签
# 实现cross entropy
def _logsoftmax_cross_entropy(self, x, y, G):
func = nn.LogSoftmax(dim=1)
x = func(x)
if y.dim() == 1: # 转为one-hot格式
y = self._one_hot(y, self.n_output)
res = (x * y * G.view(x.size()[0], -1)).mean()
return -res
此外需要注意的是:
①:REFORCEMENT的经验在开启新一幕的时候,一定清空上一幕的经验。
②:实际在做的时候,和这个理论公式有点不一样:。我们并不是弄好多幕,然后求均值来做梯度上升,来训练网络参数。而是对于每一幕,都去求均值,然后做梯度上升来训练参数。这一幕的训练会以上一幕的训练结果基础上进一步训练。
2.3、实验过程及结果
上图是整个实验的过程图:首先policy网络针对状态输出概率,对概率分布进行采样sample()输出当前动作,然后与环境进行交互得到一串轨迹,最后用轨迹构建loss,最后通过网络的反向传播进行参数更新。
实验结果:
如上图所示,参照Morvan老师的实验,第一张是第一个episode产生的Gt的分布(经过标准化的),可见刚开始杆子还没倒下,Gt很高来肯定当前动作的价值,通过反向传播来提升该动作的概率,由于是第一回合,输出的动作是不正确的,故杆子慢慢要倒下,Gt很低否定输出动作的价值,通过反向传播来抑制该动作概率的提升。
第二张图是学习曲线,由于我们的环境是坚持200个step即成功,每一个step的奖励都是1分,故我们从图中看出,REFORCEMENT算法还是有效的,尽管其结果不如Q-learning来的好,不过另一方面也体现了学习后续高级算法的价值所在。
这个算法其实不太稳定,有时候1000个episode最后都难以收敛,比如下图所示,但我们起码已经证明了REFORCEMENT算法的有效性,这就足够了,因为这个算法只是基础,并不常用。
三、展望与总结
REFORCEMENT算法是policy-based算法的基础算法,有了这个基础,才能更好的理解和运用后续的高级算法,对于处理连续动作的RL问题,学习policy-based的高级算法是有必要的,因此接下来要继续对actor-critic算法、A3C、DDPG等算法进行研究学习以及复现。
policy-based的核心思想就是通过奖励来评价策略网络选出动作的价值,如果在当前状态采样的这个动作产生后续的价值很大,那么就加大这个动作的概率,进行强化;如果当前状态这个动作后续产生的价值很低,甚至是负奖励,那么就减弱这个动作的概率,进行削弱。