DRL基础(六)——强化学习中的时间差分方法(Sarsa、Q-learning)

【引言】上一篇文章介绍了表格型强化学习中的蒙特卡洛方法及其实现。这篇文章将介绍强化学习中的另一类重要思想—— 时间差分(Temporal difference, TD) 思想,以及该思想衍生出的两个重要算法:

  • Sarsa
  • Q-learning

了解原理后将实现这两个算法,并且在“悬崖行走”环境中对算法进行测试。



中英文术语对照表


中文英文缩写或符号
时间差分temporal difference T D TD TD
Q学习Q-learning-
同轨策略on-policy-
离轨策略off-policy

1 强化学习中的时间差分算法

“在强化学习的所有思想中,时序差分学习无疑是最核心、最新新颖的思想。”时序差分方法不需要知道环境的动态特性,同时也无需等待一个episode结束即可估计当前的价值函数。对于给定的策略 π \pi π,蒙特卡洛的方法需要一个episode结束后才能对价值函数进行更新,即使用未来奖励和 G t G_t Gt进行更新。时间差分方法则不同,只使用下一个状态相关信息对价值函数进行更新。

先考虑状态价值函数,用 V ( S t ) V(S_t) V(St)表示状态价值函数 v π ( S t ) v_\pi(S_t) vπ(St)的估计值。智能体执行动作 A t A_t At后,得到奖励 R t + 1 R_{t+1} Rt+1,环境转移到下一个状态 S t + 1 S_{t+1} St+1。如果价值函数的估计是十分准确的,那么应当有 V ( S t ) = R t + 1 + γ V ( S t + 1 ) V(S_t)=R_{t+1}+\gamma V(S_{t+1}) V(St)=Rt+1+γV(St+1)这里 R t + 1 + γ V ( S t + 1 ) R_{t+1}+\gamma V(S_{t+1}) Rt+1+γV(St+1)实际上是使用了与环境交互所得的新信息 R t + 1 R_{t+1} Rt+1以及 S t + 1 S_{t+1} St+1来重新估计 V ( S t ) V(S_t) V(St)。实际上,很多文献中都把 R t + 1 + γ V ( S t + 1 ) R_{t+1}+\gamma V(S_{t+1}) Rt+1+γV(St+1)称为目标(target),并且用 y y y表示,即 y = R t + 1 + γ V ( S t + 1 ) y=R_{t+1}+\gamma V(S_{t+1}) y=Rt+1+γV(St+1)。但是在算法未收敛前, V ( S t ) V(S_t) V(St) R t + 1 + γ V ( S t + 1 ) R_{t+1}+\gamma V(S_{t+1}) Rt+1+γV(St+1)一般是不相等的。

那到底那个估计更准确呢?既然环境提供了新的信息,不能完全忽视,但也不能完全相信,那就适当参考一下。用 α \alpha α表示更新步长,按照以下方式更新价值函数的估计值:
V ( S t ) ← V ( S t ) + α [ R t + 1 + γ V ( S t + 1 ) − V ( S t ) ] V(S_t)\leftarrow V(S_t) + \alpha \left[R_{t+1}+\gamma V(S_{t+1})-V(S_t)\right] V(St)V(St)+α[Rt+1+γV(St+1)V(St)]

这种方法使用了下一步的信息,称为 T D ( 0 ) TD(0) TD(0),或者单步 T D TD TD。那么可以不可以使用更多步来估计呢?肯定是可以的。如果把后面所有的步都考虑进来,那不就成了MC了嘛。

T D ( 0 ) TD(0) TD(0)更新公式中括号内的项称为时间差分误差(TD error),即
δ t ≐ R t + 1 + V ( s t + 1 ) − V ( S t ) \delta_t\doteq R_{t+1}+V(s_{t+1})-V(S_t) δtRt+1+V(st+1)V(St)
TD误差取决于下一时刻的奖励和状态,但是相比蒙特卡洛需要一个episode结束才能更新,TD更新更及时。特别是有时候一个episode非常长,甚至任务没完没了,那TD更新的优势就更加明显了。

1.1 Sarsa算法

1.1.1 原理介绍

按照上面定义的TD更新方式,考虑动作价值函数,即Q值表,那么更新方式可以写成:
Q ( S t , A t ) ← Q ( S t , A t ) + α [ R t + 1 + γ Q ( S t + 1 , A t + 1 ) − Q ( S t , A t ) ] Q(S_t,A_t)\leftarrow Q(S_t,A_t) + \alpha \left[R_{t+1}+\gamma Q(S_{t+1},A_{t+1})-Q(S_t,A_t)\right] Q(St,At)Q(St,At)+α[Rt+1+γQ(St+1,At+1)Q(St,At)]
这里需要注意:

  • 使用 A t + 1 A_{t+1} At+1在估计当前策略的时候,并不会立马交给环境去执行,而是下一时刻才执行
  • 如果 S t + 1 S_{t+1} St+1为终止状态,则将 Q ( S t + 1 , A t + 1 ) Q(S_{t+1},A_{t+1}) Q(St+1,At+1)定义为0

再次观察TD(0)的 Q Q Q版本的更新公式,发现更新过程涉及五元组 ( S t , A t , R t + 1 , S t + 1 , A t + 1 ) (S_t,A_t,R_{t+1},S_{t+1},A_{t+1}) (St,At,Rt+1,St+1,At+1),根据它从而命名“Sarsa”算法。

在这里插入图片描述

图1 Sarsa算法更新涉及的核心元素

1.1.2 算法伪代码

算法1 Sarsa算法

在这里插入图片描述

跟着Sarsa算法伪代码跑一遍,我们会发现,当前时刻( t t t)选择的下一个动作 A t + 1 A_{t+1} At+1,会在下一时刻( t + 1 t+1 t+1)被执行。这样的方式称为同轨策略(On-policy)

1.2 Q-learning算法

1.2.1 原理介绍

Q-learning是Watkins在1989年提出的,这一大名鼎鼎的算法,其实和Sarsa十分相似。我们先回顾Sarsa的更新公式 Q ( S t , A t ) ← Q ( S t , A t ) + α [ R t + 1 + γ Q ( S t + 1 , ? ) − Q ( S t , A t ) ] Q(S_t,A_t)\leftarrow Q(S_t,A_t) + \alpha \left[R_{t+1}+\gamma Q(S_{t+1},?)-Q(S_t,A_t)\right] Q(St,At)Q(St,At)+α[Rt+1+γQ(St+1,?)Q(St,At)]这里将Sarsa更新公式中的 A t + 1 A_{t+1} At+1用“?”代替了。思考一下,这里一定要用 A t + 1 A_{t+1} At+1吗?Q-learning使用了另一种与智能体策略不一样的选择方式,它使用的是一个完全贪婪的方式—— max ⁡ a Q ( S t + 1 , a ) \max_{a}Q(S_{t+1},a) maxaQ(St+1,a)。也就是说,哪个动作使得 Q ( S t + 1 , ⋅ ) Q(S_{t+1},\cdot) Q(St+1,)最大,就用哪个动作,时间差分也就变成了 R t + 1 + γ max ⁡ a Q ( S t + 1 , a ) − Q ( S t , A t ) R_{t+1}+\gamma \max_a Q(S_{t+1},a)-Q(S_t,A_t) Rt+1+γamaxQ(St+1,a)Q(St,At)因此Q-learning的更新公式为: Q ( S t , A t ) ← Q ( S t , A t ) + α [ R t + 1 + γ max ⁡ a Q ( S t + 1 , a ) − Q ( S t , A t ) ] Q(S_t,A_t)\leftarrow Q(S_t,A_t) + \alpha \left[R_{t+1}+\gamma \max_aQ(S_{t+1},a)-Q(S_t,A_t)\right] Q(St,At)Q(St,At)+α[Rt+1+γamaxQ(St+1,a)Q(St,At)]Q-learning在当前时刻评估下一时刻价值函数的时候总是用最大值,因此也成为“SarsaMax”,即取最大值的Sarsa。

1.2.2 算法伪代码

算法2 Q-learning算法

在这里插入图片描述

根据Q-learning的更新方式,用于评估下一时刻状态价值的动作并不是当前智能体做出的。下一时刻执行什么动作与当前评估时间差分使用的动作没啥关系。因此Q-learning是一种离轨策略(Off-policy)

2 环境介绍:悬崖行走(CliffWalkingEnv)

在这里插入图片描述

图1 悬崖行走任务(图片来源https://hrl.boyuai.com/static/540.f28e3c6f.png )
import sys
import gym  # gym环境
import numpy as np
import random
import math
from collections import defaultdict, deque
import matplotlib.pyplot as plt  # 画图工具包
%matplotlib inline  
# 在页面显示画图结果,而不是单独弹出一个窗口

import check_test
from plot_utils import plot_values # 这个是用于可视化价值函数的方法,在文末给出下载链接

通过下面这句代码可以创建悬崖行走环境 CliffWalking

env = gym.make('CliffWalking-v0')

智能体在一个 4 × 12 4\times 12 4×12 网格世界中行走, 每个网格的状态用数字表示。整个网格为:

[[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11],
 [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
 [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
 [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]]

每一幕任务都是从 36 这个初始状态开始的,目的地是状态47 ,到达47 即为成功执行任务。 状态 37- 46为悬崖,进入这些状态将导致任务失败。

智能体在每个状态可以执行以下4个动作之一:

  • UP = 0 # 向上
  • RIGHT = 1 # 向右
  • DOWN = 2 # 向下
  • LEFT = 3 # 向左

因此,环境的状态集为 S + = { 0 , 1 , … , 47 } \mathcal{S}^+=\{0, 1, \ldots, 47\} S+={0,1,,47}, 这里使用了 S + \mathcal{S}^+ S+这个符号,表示包含了终止状态在内的状态集。智能体的动作集为 A = { 0 , 1 , 2 , 3 } \mathcal{A} =\{0, 1, 2, 3\} A={0,1,2,3}。执行下列语句可以查看状态空间和动作空间。

print(env.action_space)
print(env.observation_space)
Discrete(4)
Discrete(48)

如何找到最优的策略来完成悬崖行走任务呢?假设状态价值函数是下面这个样子,那么很容易就能找到一条通往目的地的安全而又快捷的路。

# 假设状态价值函数是这样子的
V_opt = np.zeros((4,12)) # 生成4行12列的全0数组
V_opt[0][0:13] = -np.arange(3, 15)[::-1] 
# np.arange(a,b)函数,使用了默认参数步长(step=1),生成[3,4,5,...,13,14]的数组(取头不取尾)
# 这里的“::-1”可以将数组取逆序,如果x=[1,2,3,4],则x[::-1]为[4,3,2,1]
V_opt[1][0:13] = -np.arange(3, 15)[::-1] + 1
V_opt[2][0:13] = -np.arange(3, 15)[::-1] + 2
V_opt[3][0] = -13

plot_values(V_opt)

在这里插入图片描述

图2 状态价值函数可视化

3 Sarsa算法实现

接下来时实现一种重要的时间差分算法——Sarsa。

函数输入:

  • env: 任务环境.
  • num_episodes: 指定智能体执行多少幕(episode)任务.
  • alpha: 更新步长.
  • gamma: 折扣因子.

函数输出:

  • Q: 字典类型,Q[s][a]表示动作价值函数 q π ( s , a ) q_\pi(s,a) qπ(s,a)的估计值。

先实现Sarsa算法中的Q值表的更新过程,这是核心:

def update_Q_sarsa(alpha, gamma, Q, state, action, reward, next_state=None, next_action=None):
    """返回使用最新轨迹更新后的Q值表"""
    current = Q[state][action]  # 存储当前状态的Q值
    # 获取下一时刻的Q值
    Qsa_next = Q[next_state][next_action] if next_state is not None else 0    
    target = reward + (gamma * Qsa_next)               # 构建TD目标函数
    new_value = current + (alpha * (target - current)) # 更新Q值表
    return new_value

再写一个 ε − g r e e d y \varepsilon-greedy εgreedy策略:

def epsilon_greedy(Q, state, nA, eps):
    """使用epsilon-greedy方式进行动作选择.
    
    参数
    ======
        Q : dictionaty类型,存储了不同状态-动作对的价值
        state: int类型,表示当前状态
        nA : int类型,表示动作空间的大小
        eps : float类型,表示探索率,探索率越大动作越随机
    """
    if random.random() > eps: # 以1-eps的概率贪婪选择动作
        return np.argmax(Q[state])
    else:                     # 以eps的概率随机选择动作
        return random.choice(np.arange(env.action_space.n))

然后就是智能体与环境的交互过程了:

def sarsa(env, num_episodes, alpha, gamma=1.0, plot_every=20):
    nA = env.action_space.n                # 动作空间大小
    Q = defaultdict(lambda: np.zeros(nA))  # 初始化Q值表,这个字典类型在上一节MC里面仔细介绍过
    
    # 定义几个变量用来监测一下学习过程
    tmp_scores = deque(maxlen=plot_every)     # 用一个队列来存储每一幕的奖励,deque是先进先出,超过maxlen后扔掉先进入的
    avg_scores = deque(maxlen=num_episodes)   # 将奖励的均值存储起来,以便后面画图分析
    
    for i_episode in range(1, num_episodes+1):
        # 监测学习过程
        if i_episode % 100 == 0:
            print("\rEpisode {}/{}".format(i_episode, num_episodes), end="")
            sys.stdout.flush()   
        score = 0                                             # 初始化得分
        state = env.reset()                                   # 初始化环境
        
        eps = 1.0 / i_episode                                 # 设置探索率,可以看出,探索率随着episode的增加而减小
        action = epsilon_greedy(Q, state, nA, eps)            # 使用epsilon-greedy进行选择,这个函数前面已经定义好了
        
        while True:
            next_state, reward, done, info = env.step(action) # 执行动作, 观测新的状态和奖励信息
            score += reward                                   # 把单步获得的即使奖励进行累加
            if not done:
                next_action = epsilon_greedy(Q, next_state, nA, eps) # 按照epsilon-greedy选择动作
                Q[state][action] = update_Q_sarsa(alpha, gamma, Q, \
                                                  state, action, reward, next_state, next_action)
                
                state = next_state     # S <- S' 更新状态,这一步很关键
                action = next_action   # A <- A'
            if done:
                Q[state][action] = update_Q_sarsa(alpha, gamma, Q, \
                                                  state, action, reward)
                tmp_scores.append(score)    # 记录当前episode得分
                break
        if (i_episode % plot_every == 0):
            avg_scores.append(np.mean(tmp_scores))

    # 画图
    plt.plot(np.linspace(0,num_episodes,len(avg_scores),endpoint=False)[30:], np.asarray(avg_scores)[30:])
    plt.xlabel('Episode Number')
    plt.ylabel('Average Reward (Over Next %d Episodes)' % plot_every)
    plt.show()
    # 将最高平均得分打印出来
    print(('Best Average Reward over %d Episodes: ' % plot_every), np.max(avg_scores))    
    
    return Q # 返回Q值表

用下面的代码调用Sarsa算法,看看结果如何。

Q_sarsa = sarsa(env, 5000, .01)

policy_sarsa = np.array([np.argmax(Q_sarsa[key]) if key in Q_sarsa else -1 for key in np.arange(48)]).reshape(4,12)
print("\n估计的最优策略 (UP = 0, RIGHT = 1, DOWN = 2, LEFT = 3, N/A = -1):")
print(policy_sarsa)

# 将状态价值函数可视化
V_sarsa = ([np.max(Q_sarsa[key]) if key in Q_sarsa else 0 for key in np.arange(48)])
plot_values(V_sarsa)
Episode 5000/5000

在这里插入图片描述

图3 Sarsa算法学习曲线
Best Average Reward over 20 Episodes:  -13.0

估计的最优策略 (UP = 0, RIGHT = 1, DOWN = 2, LEFT = 3, N/A = -1):
[[ 1  0  1  1  1  0  1  1  1  2  2  2]
 [ 0  1  0  1  1  2  3  1  1  1  1  2]
 [ 1  1  1  1  1  1  1  1  1  1  1  2]
 [ 0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1]]

在这里插入图片描述

图4 Sarsa算法学习后的状态价值函数可视化

4 Q-learning算法实现

接下来实现大名鼎鼎的Q-learning算法。

算法的输入参数:

  • env: 任务环境.
  • num_episodes: 指定智能体执行多少幕(episode)任务.
  • alpha: 更新步长.
  • gamma: 折扣因子.

算法输出:

  • Q: 字典类型,Q[s][a]表示动作价值函数 q π ( s , a ) q_\pi(s,a) qπ(s,a)的估计值。

(是的,不用返回去核对了,和Sarsa的输入输出是一样的)

def update_Q_sarsamax(alpha, gamma, Q, state, action, reward, next_state=None):
    """Returns updated Q-value for the most recent experience."""
    current = Q[state][action]  # 存储Q值表
    Qsa_next = np.max(Q[next_state]) if next_state is not None else 0  # 存储下一个状态的最大价值函数
    target = reward + (gamma * Qsa_next)               # 都见 TD 目标
    new_value = current + (alpha * (target - current)) # 计算新的Q值
    return new_value
def q_learning(env, num_episodes, alpha, gamma=1.0, plot_every=100):
    """Q-Learning 算法
    
    参数
    ======
        num_episodes (int): 用于学习的轨迹数量
        alpha (float): 更新步长
        gamma (float): 折扣因子
        plot_every (int): 计算平均分幕得分所使用的轨迹数量
    """
    nA = env.action_space.n                # 动作空间大小
    Q = defaultdict(lambda: np.zeros(nA))  # 初始化Q值表
    
    # 定义几个变量用于记录学习过程
    tmp_scores = deque(maxlen=plot_every)     # 用于记录最近若干条轨迹的得分
    avg_scores = deque(maxlen=num_episodes)   # 用于记录平均分幕得分
    
    for i_episode in range(1, num_episodes+1):
        # 记录学习过程
        if i_episode % 100 == 0:
            print("\rEpisode {}/{}".format(i_episode, num_episodes), end="")
            sys.stdout.flush()
        score = 0                                              # 重置这一幕的累计奖励
        state = env.reset()                                    # 初始化环境
        eps = 1.0 / i_episode                                  # 设置这一幕的探索率,这样设置会使得探索率随着episode增多而一直减小
        
        while True:
            action = epsilon_greedy(Q, state, nA, eps)         # 按照epsilon-greedy 动作
            next_state, reward, done, info = env.step(action)  # 执行动作A, 观察 R, S'
            score += reward                                    # 将单步获得的即使奖励进行累加
            Q[state][action] = update_Q_sarsamax(alpha, gamma, Q, \
                                                 state, action, reward, next_state)        
            state = next_state                                 # S <- S'更新状态,这一步很关键
            if done:
                tmp_scores.append(score)                       
                break
        if (i_episode % plot_every == 0):
            avg_scores.append(np.mean(tmp_scores))
            
    # 将训练中的平均得分画成曲线,便于后面分析
    plt.plot(np.linspace(0,num_episodes,len(avg_scores),endpoint=False), np.asarray(avg_scores))
    plt.xlabel('Episode Number')
    plt.ylabel('Average Reward (Over Next %d Episodes)' % plot_every)
    plt.show()
    # 打印平均分幕得分的最大值
    print(('%d幕平均得分的最大值: ' % plot_every), np.max(avg_scores))
    return Q

使用下面的代码调用上面封装好的Q-learning学习

Q_sarsamax = q_learning(env, 5000, .01)
Episode 5000/5000

在这里插入图片描述

图5 Q-learning算法学习曲线
100幕平均得分的最大值:  -13.0

可视化Q-learning算法学到的最优策略

# 整理一下,这里没有探索率的事了,每个状态直接取最大值对应的动作
policy_sarsamax = np.array([np.argmax(Q_sarsamax[key]) if key in Q_sarsamax else -1 for key in np.arange(48)]).reshape((4,12))

print("\n学到的各个状态对应的最优动作如下,其中 ,上 = 0, 右 = 1, 下 = 2, 左 = 3, 不存在 = -1:")
print(policy_sarsamax)

# 将各个状态对应的价值可视化显示,颜色从蓝到红表示数值之间增大
plot_values([np.max(Q_sarsamax[key]) if key in Q_sarsamax else 0 for key in np.arange(48)])
学到的各个状态对应的最优动作如下,其中 ,上 = 0, 右 = 1, 下 = 2, 左 = 3, 不存在 = -1:
[[ 3  0  0  1  2  3  1  2  2  3  1  3]
 [ 1  1  1  1  1  1  2  2  3  1  1  2]
 [ 1  1  1  1  1  1  1  1  1  1  1  2]
 [ 0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1  0]]

在这里插入图片描述

图6 Q-learning算法学习后的状态价值函数可视化

4 期望Sarsa算法实现

接下来实现期望Sarsa算法(Expected Sarsa)。

前面说了,Q-learning实际上可以称为SarsaMax,因为Q-learning在当前时刻评估下一时刻价值函数的时候总是用最大值。那么有人就会想,那不取最大值,用平均值可以吗?记得多年前做文献调研,给老师汇报说说别人发表的算法可能会存在啥啥毛病,然后老师发现根本没有复现别人的算法就瞎逼逼,一顿狠批。是啊,很容易就实现的东子,如果理论上的不出明显结论,那就实现出来看看吧。

函数输入:

  • env: 任务环境.
  • num_episodes: 指定智能体执行多少幕(episode)任务.
  • alpha: 更新步长.
  • gamma: 折扣因子.

函数输出:

  • Q: 字典类型,Q[s][a]表示动作价值函数 q π ( s , a ) q_\pi(s,a) qπ(s,a)的估计值。

(是的,不用返回去核对了,和Sarsa以及Q-learning的输入输出是一样的)

def update_Q_expsarsa(alpha, gamma, nA, eps, Q, state, action, reward, next_state=None):
    """返回使用最新轨迹更新后的Q值."""
    current = Q[state][action]         # 存储Q值表
    policy_s = np.ones(nA) * eps / nA  # 一个动作被随机选中的概率
    policy_s[np.argmax(Q[next_state])] = 1 - eps + (eps / nA) # 贪婪动作被选中的概率,举例np.argmax([1,2,3,1,1])=2,即第二个元素最大
    Qsa_next = np.dot(Q[next_state], policy_s)         # 求期望
    target = reward + (gamma * Qsa_next)               # 构建TD目标
    new_value = current + (alpha * (target - current)) # 计算新的Q值
    return new_value

上面这一段代码里面:

(1)policy_s = np.ones(nA) * eps / nA 这一句,进行随机选择的概率是eps,再次条件下选到某个动作的概率为1/nA,因此某个每个动作被随机方式选中的概率是esp/nA。

(2)policy_s[np.argmax(Q[next_state])] = 1 - eps + (eps / nA)这一句,值函数最大的那个动作还可以通过贪婪的方式被选中,而贪婪的概率是1-eps,因此贪婪动作被选择的综合概率是1-eps+eps/nA。

(3)Qsa_next = np.dot(Q[next_state], policy_s)这一句,用期望来估计下一个动作的回报,这里用的点积实现。

def expected_sarsa(env, num_episodes, alpha, gamma=1.0, plot_every=100):
    """期望 SARSA - TD Control
    
    参数
    ======
        num_episodes (int): 用于学习的轨迹数量
        alpha (float): 更新步长
        gamma (float): 折扣因子
        plot_every (int): 计算平均分幕得分所使用的轨迹数量
    """
    nA = env.action_space.n                # 动作空间大小
    Q = defaultdict(lambda: np.zeros(nA))  # 初始化Q值表
    
    # 定义几个变量用于记录学习过程
    tmp_scores = deque(maxlen=plot_every)     # 用于记录最近若干条轨迹的得分
    avg_scores = deque(maxlen=num_episodes)   # 用于记录平均分幕得分
    
    for i_episode in range(1, num_episodes+1):
        # 记录学习过程
        if i_episode % 100 == 0:
            print("\rEpisode {}/{}".format(i_episode, num_episodes), end="")
            sys.stdout.flush()
        score = 0                                              # 重置这一幕的累计奖励
        state = env.reset()                                    # 初始化环境
        eps = 0.005                                            # 设置探索率
        
        while True:
            action = epsilon_greedy(Q, state, nA, eps)         # 按照epsilon-greedy 动作
            next_state, reward, done, info = env.step(action)  # 执行动作A, 观察 R, S'
            score += reward                                    # 将单步获得的即使奖励进行累加
            Q[state][action] = update_Q_expsarsa(alpha, gamma, nA, eps, Q, \
                                                 state, action, reward, next_state)        
            state = next_state              # S <- S'更新状态,这一步很关键
            if done:
                tmp_scores.append(score)    
                break
        if (i_episode % plot_every == 0):
            avg_scores.append(np.mean(tmp_scores))
            
    # 将训练中的平均得分画成曲线,便于后面分析
    plt.plot(np.linspace(0,num_episodes,len(avg_scores),endpoint=False), np.asarray(avg_scores))
    plt.xlabel('Episode Number')
    plt.ylabel('Average Reward (Over Next %d Episodes)' % plot_every)
    plt.show()
    # 打印平均分幕得分的最大值
    print(('%d幕平均得分的最大值: ' % plot_every), np.max(avg_scores))
    return Q

使用下面的代码调用上面封装好的期望Sarsa学习

# obtain the estimated optimal policy and corresponding action-value function
Q_expsarsa = expected_sarsa(env, 5000, 1)

Episode 5000/5000

在这里插入图片描述

图7 期望Sarsa算法学习曲线
100幕平均得分的最大值:  -13.02

打印策略

policy_expsarsa = np.array([np.argmax(Q_expsarsa[key]) if key in Q_expsarsa else -1 for key in np.arange(48)]).reshape(4,12)

print("\n学到的各个状态对应的最优动作如下,其中 ,上 = 0, 右 = 1, 下 = 2, 左 = 3, 不存在 = -1:")
print(policy_expsarsa)
学到的各个状态对应的最优动作如下,其中 ,上 = 0, 右 = 1, 下 = 2, 左 = 3, 不存在 = -1:
[[ 1  1  1  2  1  1  0  1  1  3  1  3]
 [ 1  1  1  1  1  1  1  1  1  1  1  2]
 [ 1  1  1  1  1  1  1  1  1  1  1  2]
 [ 0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1  0]]

可视化价值函数

plot_values([np.max(Q_expsarsa[key]) if key in Q_expsarsa else 0 for key in np.arange(48)])

在这里插入图片描述

图8 期望Sarsa算法学习后的状态价值函数可视化

5 小结

本文介绍了强化学习中的时间差分这一重要思想进行了介绍,并详细介绍了由此思想发展出的Sarsa、Q-learning、ExpectedSarsa算法。本文主要内容总结如下:

  • Sarsa算法是最基础的TD算法,即用“多看一步”的策略,来估计当前动作价值函数。这与之前的使用整条轨迹的回报来估计动作价值函数的方式是有区别的。
  • Q-leaning算法也是采用“多看一步”的策略,但是总是使用下一个状态的最大动作价值函数来估计当前动作价值函数,因此又称为SarsaMax。
  • Expected Sarsa则使用下一个状态不同动作对应的动作价值函数期望来估计当前策略。
  • Sarsa是一种同轨强化学习算法,而Q-learning是一种离轨强化学习算法。
  • 需要重点掌握Sarsa、Q-leaning的更新过程的推导及其实现代码,可视化部分会用就行。

代码下载

链接:https://pan.baidu.com/s/1mT-i7lmQAJ1At6HvgkCnuQ
提取码:p3xo

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二向箔不会思考

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值