强化学习-动态规划

5.1简介

动态规划(Dynamic Programming DP)是一类优化方法,在给定一个用马尔可夫决策过程描述的完备环境模型的情况下,其可以计算最优的策略。对于强化学习问题,传统的DP算法的作用有限。但其提供了一个必要的基础,为其他方法提供了一种近似。

基于动态规划的强化学习算法主要有两种:一是策略迭代(policy iteration),二是价值迭代(value iteration)。其中,策略迭代由两部分组成:策略评估(policy evaluation)和策略提升(policy improvement)。具体来说,策略迭代中的策略评估使用贝尔曼期望方程来得到一个策略的状态价值函数,这是一个动态规划的过程;而价值迭代直接使用贝尔曼最优方程来进行动态规划,得到最终的最优状态价值。

在本节里面,我们假设环境是一个有限的MDP,也就是说,假设动作集合A,状态集合S和奖励集合R都是有限的。不同于 4.5 节介绍的蒙特卡洛方法和第 6章将要介绍的时序差分算法,基于动态规划的这两种强化学习算法要求事先知道环境的状态转移函数和奖励函数,也就是需要知道整个马尔可夫决策过程。在这样一个白盒环境中,不需要通过智能体和环境的大量交互来学习,可以直接用动态规划求解状态价值函数。但是,现实中的白盒环境很少,这也是动态规划算法的局限之处,我们无法将其运用到很多实际场景中。另外,策略迭代价值迭代通常只适用于有限马尔可夫决策过程,即状态空间和动作空间是离散且有限的。

5.2 悬崖漫步环境

本节使用策略迭代价值迭代来求解悬崖漫步(Cliff Walking)这个环境中的最优策略。接下来先简单介绍一下该环境。

悬崖漫步是一个非常经典的强化学习环境,它要求一个智能体从起点出发,避开悬崖行走,最终到达目标位置。如上图 所示,有一个 4×12 的网格世界,每一个网格表示一个状态。智能体的起点是左下角的状态,目标是右下角的状态,智能体在每一个状态都可以采取 4 种动作:上、下、左、右。如果智能体采取动作后触碰到边界墙壁则状态不发生改变,否则就会相应到达下一个状态。环境中有一段悬崖,智能体掉入悬崖或到达目标状态都会结束动作并回到起点,也就是说掉入悬崖或者达到目标状态是终止状态。智能体每走一步的奖励是 −1,掉入悬崖的奖励是 −100。

其实是有这个现成的悬崖漫步的环境的,别人做的也很好,但是呢,为了让大家更加清楚的理解,所以我们自己写一遍。

# -*- coding: utf-8 -*-
"""
@File    : CliffWalkingEnv.py
@Author  : Administrator
@Time    : 2024-07-12 13:56
@IDE     : PyCharm
@Version : 
@comment : ···
"""
class CliffWalkingEnv:
    """悬崖漫步环境"""
    def __init__(self, ncol=12, nrow=4):
        self.ncol = ncol #定义网格世界的列
        self.nrow = nrow #定义网格世界的行
        #转移矩阵P[state][action] = [(P, next_state, reward, done)]
        self.P = self.createP()

    def createP(self):
        #初始化
        P = [[[] for j in range(4)] for i in range(self.nrow * self.ncol)]
        #4种动作,change[0]:上;change[1]:下;change[2]:左;change[3]:右;
        #初始位置定义在左上角
        change = [[0, -1],[0, 1],[-1, 0],[1, 0]]
        for i in range(self.nrow):
            for j in range(self.ncol):
                for a in range(4):
                    # 位置在悬崖或者目标状态,因为无法进行继续交互,所以任何动作奖励都为0
                    if i == self.nrow - 1 and j > 0:
                        P[i * self.ncol + j][a] = [1, i * self.ncol + j, 0, True]
                        continue
                    # 其他位置
                    next_x = min(self.ncol - 1, max(0, j + change[a][0]))
                    next_y = min(self.nrow - 1, max(0, i + change[a][1]))
                    next_state = next_y * self.ncol + next_x
                    reward = -1
                    done = False
                    # 下一个位置在悬崖或者终点
                    if next_y == self.nrow - 1 and next_x > 0:
                        done = True
                        if next_x != self.ncol - 1:
                            reward = -100
                    P[i * self.ncol + j][a] = [(1, next_state, reward, done)]
        return P

if __name__ == '__main__':
    cliffWalkingEnv = CliffWalkingEnv()


4.3 策略迭代算法

策略迭代算法指的是策略评估策略提升不断循环交替,直至到最后得到最优策略的过程,这里将会对这两个过程进行详细讲解。

4.3.1 策略评估

策略评估这一过程用来计算一个策略的状态价值函数,首先我们来回顾一下之前学习的贝尔曼期望方程:

V^{\pi}(s) = \sum_{a \in A} \pi(a|s)(r(s,a) + \gamma \sum_{s' \in S} p(s'|s,a)V^{\pi}(s'))

其中,\pi(a|s)是策略\pi在状态s下采取动作a的概率。可以看到,当知道奖励函数和状态转移函数时,我们可以根据下一个状态的价值来计算当前状态的价值。因此,根据动态规划的思想,可以把计算下一个可能状态的价值当成一个子问题,把计算当前状态的价值看作当前问题。在得知子问题的解后,就可以求解当前问题。更替般的,考虑所有的状态,就变成了用上一轮的状态价值函数来计算当前这一轮的状态价值函数,即:

V^{k+1}(s) = \sum_{a \in A} \pi(a|s)(r(s,a) + \gamma \sum_{s' \in S} P(s'|s,a)V^k (s'))

我们可以选定任意初始值V^0.根据贝尔曼方程,可以得知V^k = V^\pi是以上更新公式的一个不动点。事实上,可以证明当k \rightarrow \infty,序列\{V^k\}会收敛到V^\pi,所以可以据此来计算得到一个策略的状态价值函数。可以看到,由于需要不断做贝尔期望方程迭代,策略评估其实会耗费很大的计算代价。在实际的实现过程中,如果某一轮max_{s \in S}|V^{k+1}(s) - V^k (s)|的值非常小,可以提前结束策略评估。这样做可以提升效率,并且得到的价值也非常接近真实的价值。

4.3.2 策略提升

使用策略评估计算得到当前策略的状态价值函数之后,我们可以据此来改进策略。假设此时对于策略\pi,我们一直京知道其价值V^\pi,也就是知道了在策略\pi下从每一个状态s出发最终得到的期望ui包。我们如何改变策略来获得在状态s下更高的期望回报呢?价值智能体在状态s下采取动作a,之后的动作依旧遵循策略\pi,此时得到的期望回报其实就是动作价值Q\pi(s,a).如果我们有Q\pi(s,a) > V^\pi (s),则说明在状态s下采取动作a会比原来的策略\pi\{a|s\}得到更高的期望回报。以上假设只是针对一个状态,现在假设存在一个确定性策略\pi',在任意一个状态s下,都满足:

Q^\pi(s, \pi'(s)) \geq V^\pi(s)

于是在任意状态s下,我们有

V^{\pi'} (s) \geq V^\pi(s)

这就是策略提升定理。于是可以直接贪心的在每一个状态选择动作价值最大的动作,也就是

\pi'(s) = argmax_aQ^{\pi}(s,a) = argmax_a{r{s,a} + \gamma \sum_{s'}P(s'|s,a) V^{\pi}(s')}\pi'

我们发现构造的贪心策略\pi'满足策略提升定理的条件,所以策略\pi'能够比策略\pi更好或者至少与其一样好。这个根据贪心选取动作从而得到新的策略的过程称为策略提升。当策略提升之后得到的策略\pi'和之前的策略\pi一样时,说明策略迭代达到了收敛,此时\pi\pi'就是最优策略。

策略提升定理的证明通过以下推导过程证明,使用上述提升公式得到的策略\pi'在每个状态的价值不低于原策略\pi在该状态的价值。

V^\pi(s) \leq Q^\pi(s, \pi'(s)) \\ = \mathbb{E}^\pi \left[ R_t + \gamma V^\pi(S_{t+1}) \ \middle| \ S_t = s \right] \\ \leq \mathbb{E}^\pi \left[ R_t + \gamma Q^\pi(S_{t+1}, \pi'(S_{t+1})) \ \middle| \ S_t = s \right] \\ = \mathbb{E}^\pi \left[ R_t + \gamma R_{t+1} + \gamma^2 V^\pi(S_{t+2}) \ \middle| \ S_t = s \right] \\ \leq \mathbb{E}^\pi \left[ R_t + \gamma R_{t+1} + \gamma^2 R_{t+2} + \gamma^3 V^\pi(S_{t+3}) \ \middle| \ S_t = s \right] \\ \vdots \\ \leq \mathbb{E}^\pi \left[ R_t + \gamma R_{t+1} + \gamma^2 R_{t+2} + \gamma^3 R_{t+3} + \cdots \ \middle| \ S_t = s \right] \\ = V^{\pi'}(s)

可以看到,推导过程中的每一个时间步都用到局部动作价值优V^\pi(S_{t+1} \leq Q^\pi(S_{t+1},\pi'(S_{t+1}))),累积到无穷步或者终止状态时,我们就得到了整个策略价值提升的不等式。

"""
@File    : PolicyIteration.py
@Author  : Administrator
@Time    : 2024-07-15 10:26
@IDE     : PyCharm
@Version : 
@comment : ···
"""
import copy
from CliffWalkingEnv import  CliffWalkingEnv

class PolicyIteration:
    """ 策略迭代算法 """
    def __init__(self, env, theta, gamma):
        self.env = env
        self.v = [0] * self.env.ncol * self.env.nrow  # 初始化价值为0
        self.pi = [[0.25, 0.25, 0.25, 0.25]
                   for i in range(self.env.ncol * self.env.nrow)]  # 初始化为均匀随机策略
        self.theta = theta  # 策略评估收敛阈值
        self.gamma = gamma  # 折扣因子

    def policy_evaluation(self):  # 策略评估
        cnt = 1  # 计数器
        while 1:
            max_diff = 0
            new_v = [0] * self.env.ncol * self.env.nrow
            for s in range(self.env.ncol * self.env.nrow):
                qsa_list = []  # 开始计算状态s下的所有Q(s,a)价值
                for a in range(4):
                    qsa = 0
                    for res in self.env.P[s][a]:
                        p, next_state, r, done = res
                        qsa += p * (r + self.gamma * self.v[next_state] * (1 - done))
                        # 本章环境比较特殊,奖励和下一个状态有关,所以需要和状态转移概率相乘
                    qsa_list.append(self.pi[s][a] * qsa)
                new_v[s] = sum(qsa_list)  # 状态价值函数和动作价值函数之间的关系
                max_diff = max(max_diff, abs(new_v[s] - self.v[s]))
            self.v = new_v
            if max_diff < self.theta: break  # 满足收敛条件,退出评估迭代
            cnt += 1
        print("策略评估进行%d轮后完成" % cnt)

    def policy_improvement(self):  # 策略提升
        for s in range(self.env.nrow * self.env.ncol):
            qsa_list = []
            for a in range(4):
                qsa = 0
                for res in self.env.P[s][a]:
                    p, next_state, r, done = res
                    qsa += p * (r + self.gamma * self.v[next_state] * (1 - done))
                qsa_list.append(qsa)
            maxq = max(qsa_list)
            cntq = qsa_list.count(maxq)  # 计算有几个动作得到了最大的Q值
            # 让这些动作均分概率
            self.pi[s] = [1 / cntq if q == maxq else 0 for q in qsa_list]
        print("策略提升完成")
        return self.pi

    def policy_iteration(self):  # 策略迭代
        while 1:
            self.policy_evaluation()
            old_pi = copy.deepcopy(self.pi)  # 将列表进行深拷贝,方便接下来进行比较
            new_pi = self.policy_improvement()
            if old_pi == new_pi: break

现在我们已经写熬了环境代码和策略迭代代码。为了更好地展现最终的策略,接下来增加一个打印策略的函数,用于打印当前策略在每个状态下的价值以及智能体会采取的动作。对于打印出来的动作,我们用^o<o表示等概率采取向左和向上两种动作,ooo>表示在当前状态之采取向右动作。

def print_agent(agent, action_meaning, disaster=[], end=[]):
    print("状态价值:")
    for i in range(agent.env.nrow):
        for j in range(agent.env.ncol):
            # 为了输出美观,保持输出6个字符
            print('%6.6s' % ('%.3f' % agent.v[i * agent.env.ncol + j]), end=' ')
        print()

    print("策略:")
    for i in range(agent.env.nrow):
        for j in range(agent.env.ncol):
            # 一些特殊的状态,例如悬崖漫步中的悬崖
            if (i * agent.env.ncol + j) in disaster:
                print('****', end=' ')
            elif (i * agent.env.ncol + j) in end:  # 目标状态
                print('EEEE', end=' ')
            else:
                a = agent.pi[i * agent.env.ncol + j]
                pi_str = ''
                for k in range(len(action_meaning)):
                    pi_str += action_meaning[k] if a[k] > 0 else 'o'
                print(pi_str, end=' ')
        print()

if __name__ == '__main__':
    env = CliffWalkingEnv()
    action_meaning = ['^', 'v', '<', '>']
    theta = 0.001
    gamma = 0.9
    agent = PolicyIteration(env, theta, gamma)
    agent.policy_iteration()
    print_agent(agent, action_meaning, list(range(37, 47)), [47])

输出如下:

策略评估进行60轮后完成
策略提升完成
策略评估进行72轮后完成
策略提升完成
策略评估进行44轮后完成
策略提升完成
策略评估进行12轮后完成
策略提升完成
策略评估进行1轮后完成
策略提升完成
状态价值:
-7.712 -7.458 -7.176 -6.862 -6.513 -6.126 -5.695 -5.217 -4.686 -4.095 -3.439 -2.710 
-7.458 -7.176 -6.862 -6.513 -6.126 -5.695 -5.217 -4.686 -4.095 -3.439 -2.710 -1.900 
-7.176 -6.862 -6.513 -6.126 -5.695 -5.217 -4.686 -4.095 -3.439 -2.710 -1.900 -1.000 
-7.458  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000 
策略:
ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovoo 
ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovoo 
ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ovoo 
^ooo **** **** **** **** **** **** **** **** **** **** EEEE 

Process finished with exit code 0

经过 5 次策略评估和策略提升的循环迭代,策略收敛了,此时将获得的策略打印出来。用贝尔曼最优方程去检验其中每一个状态的价值,可以发现最终输出的策略的确是最优策略。

4.4 价值迭代算法

从上面的代码运行结果可以发现,策略迭代中的策略评估需要进行很多轮才能收敛得到某一策略的状态函数,这需要很大的计算量,尤其是在状态和动作空间比较大的情况下。我们是否必须要等到策略评估完成后在进行策略提升呢?试想一下,可能出现这样的情况:虽然状态价值函数还没有收敛,但是不论接下来怎么更新状态价值,策略提升得到的都是同一个策略。如果只在策略评估中进行一轮价值更新,然后直接根据更新后的价值进行策略提升,这样的话就将其替换成了价值迭代算法,它可以被认为是一种策略评估只进行了一轮更新的策略迭代算法。但需要注意的是,价值迭代中不存在显式的策略,我们只维护一个状态价值函数。确切的说,价值迭代可以看成一种动态规划过程,他利用的是贝尔曼最优方程:

V^*(s) =max_{a \in A}\{r(s,a)\ + \gamma \sum_{s' \in S} P(s'|s,a) V^*(s') \}

将其写成迭代更新的方式为:

V^{k+1}(s) = max_{a \in A}\{r(s,a) + r\sum_{s' \in S}P(s'|s,a)V^k(s')\}

价值迭代变是按照以上更新方式进行的。等到V^{k+1}V^k相同时,它就是贝尔曼方程的不动点,此时对应着最优状态价值函数V^*.然后我们利用\pi(s) = argmax_a\{r(s,a) + \gamma \sum_{s'} p(s'|s,a)V^{k+1}(s')\},从中恢复出最优策略即可。

价值迭代算法流程如下:

\cdot \quad Random \quad Init \quad V(s)\\ \cdot \quad While \quad \Delta > \theta \quad do:\\ \cdot \quad \quad \Delta \leftarrow 0\\ \cdot \quad \quad for \quad every \quad state \quad s \in S:\\ \cdot \quad \quad \quad v \leftarrow V(s)\\ \cdot \quad \quad \quad V(s) \leftarrow max_a r(s,a) + \gamma \sum_{s'} P(s'|s,a) V(s')\\ \cdot \quad \quad \quad \Delta \leftarrow max(\Delta, |v- V(s)|)\\ \cdot \quad end \quad while\\ \cdot Return \quad \pi(s) = argmax_a{r(s,a) + \gamma \sum_{s'} P(s'|s,a) V(s')}

我们现在来编写价值迭代的代码。

# -*- coding: utf-8 -*-
"""
@File    : ValueIteration.py
@Author  : Administrator
@Time    : 2024-07-16 15:51
@IDE     : PyCharm
@Version : 
@comment : ···
"""
from CliffWalkingEnv import CliffWalkingEnv
class ValueIteration:
    """ 价值迭代算法 """
    def __init__(self, env, theta, gamma):
        self.env = env
        self.v = [0] * self.env.ncol * self.env.nrow  # 初始化价值为0
        self.theta = theta  # 价值收敛阈值
        self.gamma = gamma
        # 价值迭代结束后得到的策略
        self.pi = [None for i in range(self.env.ncol * self.env.nrow)]

    def value_iteration(self):
        cnt = 0
        while 1:
            max_diff = 0
            new_v = [0] * self.env.ncol * self.env.nrow
            for s in range(self.env.ncol * self.env.nrow):
                qsa_list = []  # 开始计算状态s下的所有Q(s,a)价值
                for a in range(4):
                    qsa = 0
                    for res in self.env.P[s][a]:
                        p, next_state, r, done = res
                        qsa += p * (r + self.gamma * self.v[next_state] * (1 - done))
                    qsa_list.append(qsa)  # 这一行和下一行代码是价值迭代和策略迭代的主要区别
                new_v[s] = max(qsa_list)
                max_diff = max(max_diff, abs(new_v[s] - self.v[s]))
            self.v = new_v
            if max_diff < self.theta: break  # 满足收敛条件,退出评估迭代
            cnt += 1
        print("价值迭代一共进行%d轮" % cnt)
        self.get_policy()

    def get_policy(self):  # 根据价值函数导出一个贪婪策略
        for s in range(self.env.nrow * self.env.ncol):
            qsa_list = []
            for a in range(4):
                qsa = 0
                for res in self.env.P[s][a]:
                    p, next_state, r, done = res
                    qsa += p * (r + self.gamma * self.v[next_state] * (1 - done))
                qsa_list.append(qsa)
            maxq = max(qsa_list)
            cntq = qsa_list.count(maxq)  # 计算有几个动作得到了最大的Q值
            # 让这些动作均分概率
            self.pi[s] = [1 / cntq if q == maxq else 0 for q in qsa_list]

def print_agent(agent, action_meaning, disaster=[], end=[]):
    print("状态价值:")
    for i in range(agent.env.nrow):
        for j in range(agent.env.ncol):
            # 为了输出美观,保持输出6个字符
            print('%6.6s' % ('%.3f' % agent.v[i * agent.env.ncol + j]), end=' ')
        print()

    print("策略:")
    for i in range(agent.env.nrow):
        for j in range(agent.env.ncol):
            # 一些特殊的状态,例如悬崖漫步中的悬崖
            if (i * agent.env.ncol + j) in disaster:
                print('****', end=' ')
            elif (i * agent.env.ncol + j) in end:  # 目标状态
                print('EEEE', end=' ')
            else:
                a = agent.pi[i * agent.env.ncol + j]
                pi_str = ''
                for k in range(len(action_meaning)):
                    pi_str += action_meaning[k] if a[k] > 0 else 'o'
                print(pi_str, end=' ')
        print()
if __name__ == '__main__':
    env = CliffWalkingEnv()
    action_meaning = ['^', 'v', '<', '>']
    theta = 0.001
    gamma = 0.9
    agent = ValueIteration(env, theta, gamma)
    agent.value_iteration()
    print_agent(agent, action_meaning, list(range(37, 47)), [47])
C:\python3.8\python.exe F:\projects\data\runs\demo\RL\ValueIteration.py 
价值迭代一共进行14轮
状态价值:
-7.712 -7.458 -7.176 -6.862 -6.513 -6.126 -5.695 -5.217 -4.686 -4.095 -3.439 -2.710 
-7.458 -7.176 -6.862 -6.513 -6.126 -5.695 -5.217 -4.686 -4.095 -3.439 -2.710 -1.900 
-7.176 -6.862 -6.513 -6.126 -5.695 -5.217 -4.686 -4.095 -3.439 -2.710 -1.900 -1.000 
-7.458  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000  0.000 
策略:
ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovoo 
ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovo> ovoo 
ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ovoo 
^ooo **** **** **** **** **** **** **** **** **** **** EEEE 

Process finished with exit code 0

可以看到,解决同样的训练任务,价值迭代总共进行了数十轮,而策略迭代中的策略评估总共进行了数百轮,价值迭代中的循环次数远少于策略迭代。

4.5小结

本章讲解了强化学习中两个经典的动态规划算法:策略迭代算法和价值迭代算法,它们都能用于求解最优价值和最优策略。动态规划的主要思想是利用贝尔曼方程对所有状态进行更新。需要注意的是,在利用贝尔曼方程进行状态更新时,我们会用到马尔可夫决策过程中的奖励函数和状态转移函数。如果智能体无法事先得知奖励函数和状态转移函数,就只能通过和环境进行交互来采样(状态-动作-奖励-下一状态)这样的数据,我们将在之后的章节中讲解如何求解这种情况下的最优策略。

  • 37
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值