从零实践强化学习之连续动作空间上求解RL(PARL)

回顾这五节课的内容,其实可以分成四大内容:

最后一节课的主要内容就是学习用强化学习来求解连续状态空间的问题

连续动作空间

连续动作和离散动作是一个相对的概念,通过回顾离散动作来学习什么是连续动作

连续动作 VS 离散动作

前面几节课接触到的,比如倒立摆、小乌龟还有雅达利的乒乓球,动作的步长都已经提前给定的,比如按一下就走一个单位长度
在这里插入图片描述
但是现实生活中,比如开车时方向盘的角度,或者是无人机的电压都是连续的,也就是说,输出的动作是不可数的,就好像倒立摆可以左右走,当然也可以设置一个浮点数,正数和负数就决定了小车往哪走,走多少

换句话说,要想更好地控制他们,那么就要对精度有更高的要求,从整型变为浮点型,这样,可调节的范围就变多了

对于这些连续动作空间,前面讲的算法都无法处理,这时,"万能"的神经网络可以解决这一问题:
在这里插入图片描述

  • 对于前面的随机性策略,输入一个s,网络会输出在这个状态下做出某个动作的概率
  • 但对于确定性策略来说,网络的参数确定下来后,同样的状态必然输出同样的动作

具体来看的话,其实就是最后一层全连接层的激活函数不同:
在这里插入图片描述

  • softmax的输出范围是[0,1],所以它可以直接输出概率,并且可以确保所有动作概率的和为1
  • tanh的输出范围是[-1,1],它直接输出每个action的值,然后再根据实际,缩放到合适的范围内

在连续控制领域,比较经典的算法是DDPG

DDPG

DDPG的全称是Deep Deterministic Policy Gradient

实际上,DDPG的特点也可以从它的名字里拆解出来:
在这里插入图片描述

  • Policy Gradient
    上节课里的更新方法是每个episode更新一次;和上节课有所不同的是这里的更新频率更高,DDPG的网络里,每个step都会更新一次,也就是说,它是单步更新的policy网络

  • Deterministic
    Deterministic翻译过来是确定性的意思,也就是说,DDPG能直接输出确定的动作,可以用于连续动作的环境

  • Deep
    前面加了一个Deep,是因为DDPG用了神经网络,它借鉴了DQN的技巧,使用目标网络+经验回放

从DQN到DDPG

当初提出DDPG其实是为了让DQN可以扩展到连续控制动作空间

在这里插入图片描述

所以DDPG直接在DQN的基础上加了一个策略网络,用来直接输出动作值,DDPG实际上一边学习Q网络(用w表示),一边学习策略网络(用θ表示)

这样的结构称为Actor-Critic结构(评论家-演员)

Actor-Critic结构

在这里插入图片描述

  • 策略网络扮演的是演员的角色,她负责对外展示(输出动作)
  • Q网络是评论家,他会在每个step都会演员的动作评估打分,估计一下这次的action在未来能有多少总收益,也就是演员的Q值

演员需要根据评委的打分调整动作;而评委也需要根据观众的反映以及演员的动作来调整自己的打分

所以策略网络要做的是更新网络的参数θ,争取下次做的更好
而Q网络要做的就是根据环境的反馈reward来调整网络的参数w

在这样的场景下,其实策略网络和Q网络在一开始的时候都是随机的,随机输出动作,随机打分,但是由于有环境反馈的reward的存在,所以Q网络的评分会越来越准确,他也会带着策略网络,使它的输出越来越好

既然是神经网络,就要做参数更新,更新的方法跟DQN类似:
在这里插入图片描述
在计算Loss时,大的方向其实没变,只不过变成了一个复合函数:
在这里插入图片描述
原来的动作action是直接获取的,现在是神经网络的输出

目标网络

因为Q_target是不稳定的,所以为了稳定Q_target,DDPG分别给策略网络和Q网络都搭建了一个target_network,专门用来稳定Q_target:
在这里插入图片描述
这就是在看DDPG的文章时,会有四个网络的原因

另外.经验回放的方法跟DQN是一样的

用PARL实现DDPG

思路其实也很明显了,也是3大部分:
在这里插入图片描述
最核心的其实就是这四个函数:
在这里插入图片描述

Model

Model里实现了3个类,但是调用时只调用Model就可以了

Q网络的结构放在了CriticModel里:

class CriticModel(parl.Model):
    def __init__(self):
        hid_size = 100

        self.fc1 = layers.fc(size=hid_size, act='relu')
        self.fc2 = layers.fc(size=1, act=None)

    def value(self, obs, act):
        concat = layers.concat([obs, act], axis=1)
        hid = self.fc1(concat)
        Q = self.fc2(hid)
        Q = layers.squeeze(Q, axes=[1])
        return Q

actor的网络结构放在了ActorModel里:

class ActorModel(parl.Model):
    def __init__(self, act_dim):
        hid_size = 100

        self.fc1 = layers.fc(size=hid_size, act='relu')
        self.fc2 = layers.fc(size=act_dim, act='tanh')

    def policy(self, obs):
        hid = self.fc1(obs)
        means = self.fc2(hid)
        return means

value()和police()方法都在Model里包了一层,所以直接调用Model就可以了:

class Model(parl.Model):
    def __init__(self, act_dim):
        self.actor_model = ActorModel(act_dim)
        self.critic_model = CriticModel()

    def policy(self, obs):
        return self.actor_model.policy(obs)

    def value(self, obs, act):
        return self.critic_model.value(obs, act)

    def get_actor_params(self):
        return self.actor_model.parameters()

另外,这个Model类还实现了一个方法get_actor_params(),它用来获取actor所有参数的名称,这个方法在PARL底层是已经实现好了的,直接用就行

至于为什么要用这个方法,获取参数的名称,这主要是在更新网络参数时会用到,详细的后面会说到

Algorithm

下面再来看看Algorithm里的learn()函数

Critic网络(Q网络)更新

计算Q网络的Loss函数是符合函数,在_critic_learn()这个方法里定义:
在这里插入图片描述
首先是把经验池输入进去,计算下一个动作,然后拿到策略网络的Q值

def _critic_learn(self, obs, action, reward, next_obs, terminal):
    next_action = self.target_model.policy(next_obs)
    next_Q = self.target_model.value(next_obs, next_action)

    terminal = layers.cast(terminal, dtype='float32')
    target_Q = reward + (1.0 - terminal) * self.gamma * next_Q
    target_Q.stop_gradient = True # 阻止梯度回传

    Q = self.model.value(obs, action)
    cost = layers.square_error_cost(Q, target_Q)
    cost = layers.reduce_mean(cost)
    optimizer = fluid.optimizer.AdamOptimizer(self.critic_lr)
    optimizer.minimize(cost)
    return cost

Actor网络( 策略网络)更新

它的计算过程相对于前面来说,比较简单
在这里插入图片描述
这里要注意的是,和这个Loss有关的是两个网络的参数,这里要更新的,只是策略网络的参数,因此,前面提到的get_actor_params()就非常有用了

在optimizer.minimize()里可以指定要更新的参数,它会自动地筛选,选择对应的参数来更新

def _actor_learn(self, obs):
    action = self.model.policy(obs)
    Q = self.model.value(obs, action)
    cost = layers.reduce_mean(-1.0 * Q)
    optimizer = fluid.optimizer.AdamOptimizer(self.actor_lr)
    optimizer.minimize(cost, parameter_list=self.model.get_actor_params())
    return cost

Target network参数软更新

在DQN里,采用的是硬更新的方式,每隔一段时间,就直接把网络复制过来
在这里插入图片描述
而DDPG采用的是软更新,以非常平滑地方式来更新参数,每次只更新一点点,用一个参数来控制调整的幅度,每次只把参数的一部分拷贝进去

主要的目的就是不让target网络"大换血"

连续动作空间版本的CartPole

这里的例子还是倒立摆:
在这里插入图片描述
代码在PARL的lesson5下:
在这里插入图片描述

因为在正式训练前,它会随机地大胆尝试,不断地试错,这一部分需要等待一段时间:
在这里插入图片描述

在200多个episode之后,就有比较好的效果了,收敛的速度比DQN要快得多

下面是DDPG的效果:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章