策略梯度算法(PG)推导与代码实现

策略梯度算法是强化学习中基础的学习算法之一,但是对于AI小白来说(比如我(#^.^#)),推导的过程还是折腾了一会儿,因为查出来的资料参差不齐,所以浏览了很多博主的见解才算弄明白了大部分的原理。于是,现在我将试着按照自己的见解进行一次完整的推导,复习理清思路的同时希望也能帮助到同样有疑惑的人。

1. 蒙特卡罗

在介绍PG之前先介绍一下蒙特卡洛算法

蒙特卡罗算法是基于采样的方法,给定策略π,让智能体与环境进行交互,就会得到很多条轨迹。 每条轨迹都有对应的回报,我们把每条轨迹的回报进行平均,就可以知道某一个策略下面对应状态的价值。

2. 策略梯度算法

在强化学习中,有三个组成部分:

  1. 演员(actor)
  2. 环境
  3. 奖励函数

其中环境和奖励函数不是我们可以控制的,在开始学习之前就已经事先给定。演员里会有一个策略,它用来决定演员的动作。策略就是给定一个外界输入,它会输出演员现在应该要执行的动作。我们唯一可以做的就是调整演员里面的策略,使得演员可以得到最大的奖励。

当我们将深度学习与强化学习相结合时,策略π就是一个网络,用θ表示π的参数。输入的环境状态在经过策略网络后会输出所有可选动作的概率,然后演员就根据这个概率的分布来决定它要采取的动作,概率分布不同,演员采取的动作也不同。简单来说,策略的网络输出是一个概率分布,演员根据这个分布去做采样,决定实际上要采取的动作是哪一个。

而PG就是蒙特卡罗与神经网络结合的算法,该算法会在一个连续区间内直接输出当前状态可以采用的所有动作的概率。

在PG算法中,因为策略是一个概率,不能直接用来迭代,所以我们将其转化为函数形式,其实就是策略参数化,目的是使用带有θ参数的函数对策略进行近似,通过更新参数,逼近最优策略。

而现在我们需要设置一个可以优化的目标函数,它的主要作用就是用来衡量策略的好坏程度,通过最大化这个目标函数来优化策略参数。

现在,我们开始数学建模并进行目标函数的推导,完整过程如下图:

 

最终的梯度公式用直观的方式解释就是:如果我们在st执行at,最后发现\tau的奖励是正的,我们就要增加在st执行at的概率。反之,在st执行at会导致\tau的奖励变成负的,我们就要减少这一项的概率。

目标函数优化:

(1)增加基线:

  • 问题一:因为采取行动的概率和为1,所以如果我们得到的奖励总是正的,也就是说最低也就是0,那么可能存在进行归一化后,R大的会上升,而R小的会下降的(因为智能体是在不同轨迹中的同一状态下改变不同动作的概率)。比如下面这种情况,假设某一状态下有三个动作,分别是a,b,c,奖励都是正的。根据公式\nabla \bar{R}_{\theta},我们希望将这三个动作的概率以及对数概率都拉高,但是它们前面的权重R不一样,有大有小,所以权重大的,上升的多一点;权重小的,上升的少一些,又因为对数概率是一个概率,三个动作的和要为0,那么在做完归一化后,上升多的才会上升,上升的少的就是下降的。
  • 问题二:如果智能体在学习时,s状态下动作a的概率很小,所以只采样了少量的s跟a的对,甚至有一些动作可能从来都没有被采样到,那么因为所有动作的奖励都是正的,根据公式\nabla \bar{R}_{\theta},它的每一项概率都应上升,而因为a没有被采样到,其它动作的概率如果都要上升,a的概率就下降,但a不一定是一个不好的动作,它只是没被采样到,但概率却下降了,这显然是有问题的,所以我们希望奖励不要总是正的。
  • 为了解决奖励总是正的这一问题,可以把奖励减掉一项b,具体可看后图式子。其中,b叫做基线,减掉b以后,就可以让R\left(\tau^{n}\right)-b这一项有正有负。所以如果得到的总奖励R\left(\tau^{n}\right)大于b的话,就让它的概率上升。如果这个总奖励小于b,就算它是正的,正的很小也是不好的,就要让这一项的概率下降。b通常是把\tau^{n}的值取期望,算一下\tau^{n}的平均值,即b \approx E[R(\tau)]。在实际训练的时候,不断地把R\left(\tau^{n}\right)的分数记录下来,不断地计算R\left(\tau^{n}\right)的平均值当作b。

(2)分配合适的分数

  • 在同一个回合中,所有的状态跟动作的对都会使用同样的奖励项进行加权,这不公平,因为在同一场游戏中也许有些动作是好的,有些动作是不好的。假设整场游戏的结果是好的,但不代表每一个动作都是对的,反之也是。举个例子,假设在一个回合中,在s1执行a1的奖励r1是5,在s2执行a2的奖励r2是0,在s3执行a3的奖励r3是-2。整场游戏结束,总奖励为3。但不代表在s2执行动作a2是好的,因为这个正的分数,主要来自于在s1执行了a1, 跟在s2执行a2是没有关系的,也许在s2执行a2反而是不好的,因为它导致你接下来会进入s3,执行s3被扣分,所以整个回合得到的结果是好的,并不代表每一个动作都是对的。
  • 在理想的状况下,这个问题,如果采样够多是可以被解决的。但现在的问题是我们采样的次数不够多,所以我们计算这个状态-动作对的奖励的时候,不把整个回合得到的奖励全部加起来,只计算从这个动作执行以后所得到的奖励。因为这个回合在执行这个动作之前发生的事情是跟执行这个动作是没有关系的,所以在执行这个动作之前得到多少奖励都不能算是这个动作的功劳。跟这个动作有关的东西,只有在执行这个动作以后发生的所有的奖励把它加起来,才是这个动作真正的贡献。
  • 其中,我们用折扣因子平衡当前和未来奖励的重要性。

3. 相关代码

这里我就不举具体例子了,就只把和PG算法相关的代码拿出来研究一下。

class PolicyGradient():

    def __init__(self, n_states_num, n_actions_num, learning_rate=0.01, reward_decay=0.95 ):
        # 状态数,确定状态维度。
        self.n_states_num = n_states_num
        # 动作维度。
        self.n_actions_num = n_actions_num
        # 学习率
        self.lr = learning_rate
        # 折扣因子
        self.gamma = reward_decay
        # 策略网络
        self.pi = PolicyNet(n_states_num, n_actions_num, 128)
        # 优化器
        self.optimizer = torch.optim.Adam(self.pi.parameters(), lr=learning_rate)
        # 存储轨迹  存储方式为  (每一次的reward,动作的概率)
        self.data = []
        self.cost_his = []

    # 存储轨迹数据
    def put_data(self, item):
        # 记录某状态下,单次动作奖励r,动作的对数概率log_P(a|s)。
        self.data.append(item)

    def train_net(self):
        # 计算梯度并更新策略网络参数。
        R = 0  # 终结状态的初始回报为0
        policy_loss = []
        for r, log_prob in self.data[::-1]:  # 逆序取(这个递归方式很巧妙也很常见)
            R = r + gamma * R  # 计算每个时间戳上的回报
            # 每个时间戳都计算一次梯度
            loss = -log_prob * R
            policy_loss.append(loss)
        self.optimizer.zero_grad()
        policy_loss = torch.cat(policy_loss).sum()  # 求和
        # 反向传播
        policy_loss.backward()
        self.optimizer.step()
        self.cost_his.append(policy_loss.item())
        self.data = []  # 清空轨迹

    # 将状态传入神经网络 根据概率选择动作
    def choose_action(self, state):
        # 将state转化成tensor 并且转化维度(增加batch_size,兼容torch框架)。
        s = torch.Tensor(state).unsqueeze(0)
        prob = self.pi(s)  # 动作分布:[0,1,2,3]
        # 从类别分布中采样1个动作。
        # 在相应的位置处进行取样,取样返回的是该位置的整数索引。不一定是最大的,是按照概率采样的那个,采样到那个就是哪个的索引
        m = torch.distributions.Categorical(prob)  # 生成分布
        action = m.sample()
        return action.item(), m.log_prob(action)
特别声明:当且仅当技术类文章可转载,转载时请标明出处和作者哟~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇