增强学习入门3——策略迭代法

本文收录在无痛的机器学习第二季目录

前言

这个系列是我和InfoQ合作的《无痛的增强学习入门》系列,现转载到知乎专栏中。首发地址在:无痛的增强学习入门: 策略迭代 ,欢迎围观。

上一节我们曾简单介绍了计算最优策略的方法——先得到同一状态下不同行动的价值估计,再根据这些价值估计计算出最优的策略选择。本节将详细介绍采用这个战术实现的算法——策略迭代法(Policy Iteration)

3.1 策略迭代法

在上面的计算思路中,我们要想知道最优的策略,就需要能够准确估计价值函数。然而如果想准确估计价值函数,又需要策略是最优,数字才能够估计准确。所以实际上这是一个鸡生蛋,蛋生鸡的问题。碰上这样无解的问题,我们往往需要一些“曲线救国”的问题。我们能不能把这个问题考虑成一个迭代优化的问题,通过一轮一轮的计算逐渐接近最优的结果呢?答案是可以的。

我们的假想思路是这样的:首先以某种策略开始,计算当前策略下的价值函数;然后利用这个价值函数,找到更好的策略;接下来再用这个策略继续前行,更新价值函数……这样经过若干轮的计算,如果一切顺利,我们的策略会收敛到最优的策略,问题也就得到了解答。下面我们先来实践一下这个思路。

为了实践这个思路并验证我们的结果,我们需要将蛇棋的难度降低。我们这里将梯子数量变为0,同时只需用两种骰子:可以投掷1-3的投掷手法和可以投掷1-6的投掷手法。对于这样的问题,我们可以直接猜测出最优的方案:在前进至97,98,99前,全部使用1-6的骰子显然可以获得最优的前进步数,而这三个位置最好使用1-3的骰子,因为这样有更大的概率一次性到达终点。

下面就来构建这种策略,并用两种相对简单的策略进行一下对比。两种简单的策略自然是一直使用其中的一种投掷手法不做变化。我们使用每一种策略随机进行1万局游戏,以下是对应的代码:

def simple_eval():
    env = Snake(0, [3,6])
    agent = TableAgent(env.state_transition_table(), env.reward_table())
    print 'return3={}'.format(eval(env,agent))
    agent.policy[:]=1
    print 'return6={}'.format(eval(env,agent))
    agent.policy[97:100]=0
    print 'return_ensemble={}'.format(eval(env,agent))

游戏最终的平均得分如下所示:

return3=49
return6=68
return_ensemble=70

可以看出,我们设想的策略获得了最高的平均得分,说明我们的思路确实有厉害之处。如果把寻找策略的事情交给算法呢?

我们来实现一下上面提到的优化算法的两个步骤,首先是计算当前策略的价值函数估计。我们采用了迭代的方式去求解,求解的方式就是采用了Bellman等式:

 v(s_t)= \sum_a \pi (a|s_t) \sum_{s_{t+1}}p(s_{t+1}|s_t,a)[R_t+ \gamma v(s_{t+1})]

由于有 \gamma 的存在,每个状态的价值最终将得到收敛,于是代码可以写作:

def policy_evaluation(self):
    # iterative eval
    while True:
        # one iteration
        new_value_pi = self.value_pi.copy()
        for i in range(1, self.state_num): # for each state
            value_sas = []
            for j in range(0, self.act_num): # for each act
                value_sa = np.dot(self.table[j, i, :], self.reward + self.gamma * self.value_pi)
                value_sas.append(value_sa)
            new_value_pi[i] = value_sas[self.policy[i]]
        diff = np.sqrt(np.sum(np.power(self.value_pi - new_value_pi, 2)))
        if diff < 1e-6:
            break
        else:
            self.value_pi = new_value_pi

完成了这一步,下一步就是根据前面的状态价值函数计算状态-行动价值函数:

q(s_t,a_t)=\sum_{s_{t+1}}p(s_{t+1}|s_t,a_t)[R_t+\gamma v(s_{t+1})]

完成计算后根据同一状态下的行动价值更新策略:

\pi(s)=argmax_a q(s,a)

这样就完成了状态的更新。代码如下所示:

def policy_improvement(self):
    new_policy = np.zeros_like(self.policy)
    for i in range(1, self.state_num):
        for j in range(0, self.act_num):
            self.value_q[i,j] = np.dot(self.table[j,i,:], self.reward + self.gamma * self.value_pi)
            # update policy
            max_act = np.argmax(self.value_q[i,:])
            new_policy[i] = max_act
            if np.all(np.equal(new_policy, self.policy)):
                return False
            else:
                self.policy = new_policy
                return True

串联起来,整个算法的执行如下所示:

def policy_iteration(self):
    iteration = 0
    while True:
        iteration += 1
        self.policy_evaluation()
        ret = self.policy_improvement()
        if not ret:
            break
        print 'Iter {} rounds converge'.format(iteration)

那么最终执行的结果如何呢?

def policy_iteration_demo():
    env = Snake(0, [3,6])
    agent = TableAgent(env.state_transition_table(), env.reward_table())
    agent.policy_iteration()
    print 'return_pi={}'.format(eval(env,agent))
    print agent.policy

结果为:

Iter 2 rounds converge
return_pi=70
[0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0]

可以看出,它求出的策略结果和我们想象中的结果是一样的,说明这种算法在这个case上是没有问题的。这个算法就被称为策略迭代法。可以看出,每一轮迭代后,策略进行了一次更新,当策略无法更新时,迭代结束。算法的两个部分也分别被称为:策略评估部分和策略提升部分。

从上面的分析中,我们可以发现,策略评估部分实际上是没有问题的,主要的问题出现在策略提升上,我们能不能证明这种改进是一定得到最优结果的呢?当然是可以的。如果把每一轮迭代生成的策略形成一个策略组,并以迭代轮数进行编号,那么我们可以得到一个策略列 \{\pi_t\} ,我们不但可以证明随着t增大,这个策略列依价值函数最终收敛到最优的策略 \pi^* ,也就是说:

lim_{t \rightarrow \infty} |v(\pi_t) - v(\pi^*)|=0

而且我们可以证明这个策略列可以依价值函数一致收敛到最优策略,也就是说对于任意的数 \epsilon ,我们都存在一个k,使得当t>k时:

|v(\pi_t)-v(pi^*)|<\epsilon

换句话说,随着迭代进行,策略不断趋近于最优,每一轮迭代的策略都不会比前一轮迭代的策略差。这个证明就落在策略提升这一步了。

3.2 策略提升的证明

我们利用Bellman等式进行证明。假设现在有一个策略 \pi ,经过计算得到了对应的价值函数 v(\pi) ,并以此求出了对应的。这时我们发现存在一个状态 \hat{s} 和a,使得

q(\hat{s},a) \geq q(\hat{s}, \pi(\hat{s}))

那么我们就可以对这部分策略进行更新,得到一个新的策略 \pi^+ ,这个策略除了在状态s的决策与原策略不同,其他完全一致,那么对于任意一个状态s来说,有:

 v_{\pi^+}(s_t)= \sum_a \pi^+ (a|s_t) \sum_{s_{t+1}}p(s_{t+1}|s_t,a)[R_t+ \gamma v_{\pi^+}(s_{t+1})]

=\sum_a \pi (a|s_t) [\sum_{s_{t+1} \neq \hat{s}}p(s_{t+1}|s_t,a)[R_t+ \gamma v_{\pi^+}(s_{t+1})]+p(\hat{s}|s_t, a)[R_t + \gamma v_{\pi^+}(\hat{s})]]

\geq \sum_a \pi (a|s_t) [\sum_{s_{t+1} \neq \hat{s}}p(s_{t+1}|s_t,a)[R_t+ \gamma v_{\pi}(s_{t+1})]+p(\hat{s}|s_t, a)[R_t + \gamma v_{\pi}(\hat{s})]

= \sum_a \pi (a|s_t) \sum_{s_{t+1}}p(s_{t+1}|s_t,a)[R_t+ \gamma v_{\pi}(s_{t+1})]

=v_{\pi}(s_t)

所以可以证明每一次策略提升都不会对当前策略的价值造成下降,同理可以证明如果策略 \pi_1 下状态的价值不高于策略 \pi_2 下状态的价值,且 \pi_2 下状态的价值又不高于 \pi_3 ,那么 \pi_1 下状态的价值也不高于 \pi_3 ,基于这种传递性,也可以得到策略迭代不断趋近最优的性质。

3.3 策略迭代的展示

上面证明了策略迭代的分布性质,下面就来看看上面那个例子中分布迭代的具体表现。我们假设一开始所有的策略都采用1-3的投掷手法,于是在第一轮策略评估中,我们共进行了94轮迭代,过程中的状态的迭代值在不断变化,我们以”50“这个位置为例,做一张94轮迭代下价值的变化值:


图1 第一轮策略评估时位置“50”的价值变化图

其中横轴为迭代轮数,纵轴为价值,可以看出随着迭代轮数的增加,价值总体趋于平稳。完成第一轮的策略提升后,实际上策略已经被更新为最优策略,于是在第二轮策略评估中,再经过94轮迭代,”50“位置的价值又经历了如下的变化:


图2 第二轮策略评估时位置“50”的价值变化图

看完了上面那个简单的例子,下面让我们回到复杂的例子中来,对于一个拥有10个梯子的问题,策略迭代会给我们如何的解答呢?

def policy_iteration_demo():
    env = Snake(10, [3,6])
    agent = TableAgent(env.state_transition_table(), env.reward_table())
    print 'return3={}'.format(eval(env,agent))
    agent.policy[:]=1
    print 'return6={}'.format(eval(env,agent))
    agent.policy[97:100]=0
    print 'return_ensemble={}'.format(eval(env,agent))
    agent.policy_iteration()
    print 'return_pi={}'.format(eval(env,agent))
    print agent.policy

结果如下:

return3=-45
return6=21
return_ensemble=31
return_pi=41
[0 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 1 0 1 1 1 1 1
 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 0 0 0]

可以看出,策略迭代的方法优于前面的三种方法,经过4轮迭代,它的策略已经将两种手法混合使用了。我们可以猜想,它一定是在靠近上升梯子附近使用1-3的投掷手法,在靠近下降梯子或者无梯子时使用1-6的投掷手法,对于最后几步自然是使用1-3的投掷手法。所以从最终的策略,我们也可以猜出棋盘的样子。

以上就是策略迭代的算法,除了这种算法之外,我们还有一些其他的方法,下一节我们就来介绍其他方法。

编辑于 2017-11-22

文章被以下专栏收录