【莫烦强化学习】视频笔记(三)3.SARSA(lambda)

第9节 SARSA(lambda)


9.1 SARSA(lambda)简介

通过之前的学习,我们了解了什么是SARSA,它是一种On-Policy(同策略)的单步更新的算法,在环境中,我们每走一步就更新一次Q表,那么我们可以说,这样的更新方式叫做SARSA(0)
那么走完一步,再走一步,才进行更新,就可以叫做SARSA(1)。那么极端一些,等到整个episode结束才进行更新,那么就叫SARSA(n)(假设该episode n步结束),那么lambda就是我们想要更新的步数,记为SARSA(lambda)。这个lambda值在0~1之间,表示
在这里插入图片描述


9.2 单步更新与回合更新在这里插入图片描述

这里感觉说的有些迷惑,我所理解的单步更新与回合更新可能是这样的:

  • 单步更新: 每走一步就更新一次,虽然每次都更新,但是只有最后一步是与奖励有关的,前面的更新都是根据中间的步来的。
  • 回合更新: 虽然在episode中所有步结束后才进行更新,但是所有的步都会进行更新,更新是根据最后结果来的。

这样来看,会和更新的效率可能会更高,因为所更新的每一步都是和最终结果有关的。


9.3 编程实现

概括来说,单步更新SARSA(0)指的是对前一步Q值的更新,而SARSA(1)是对之前所有步的更新。lambda可以理解为脚步的衰变值(不是指脚步减少了多少,而是对脚步更新程度的衰减),即离目标奖励越近的脚步更重要,越远的越不重要。
更进一步的理解是在编写代码之后才有所了解的,SARSA(lambda)的伪代码如下:
在这里插入图片描述用程序来解释可能会更加直观一些,程序与之前编写的SARSA方法的程序类似,仍然可以采用继承的方式编写,为了方便运行,我没有采用继承的方式。之前的代码请参考:【莫烦强化学习】视频笔记(三)2.SARSA学习实现走迷宫
SARSA(lambda)与SARSA的主循环代码都是一样的,最后会给出所有的代码,在这里不详细讨论,只叙述与SARSA算法不同的部分。

初始化

初始化其他的参数都差不多,添加了两个元素:lambda值eligibility trace表(用来记录随时间变化的重要程度的表格)。

    def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9, trace_decay = 0.9):  # 初始换函数,后面是默认参数
        self.actions = actions  # 动作空间
        self.lr = learning_rate  # 学习率
        self.gamma = reward_decay  # 奖励衰减
        self.epsilon = e_greedy  # 贪婪度
        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)  # 初始Q表
        # SARSA(lambda)中需要的另外两个参数
        self.lambda_ = trace_decay  # 即lambda,步数衰减
        self.eligibility_trace = self.q_table.copy()  # 和Q表相同的E表,用来记录访问印记
检查状态是否存在

这里同之前的函数没有太大的变化,变化主要在E表,E表也是动态增长的,从空表到所有状态,最后可以编写代码,使得Q表与E表都打印出来,这样就能够观察优化的结果了。

    def check_state_exit(self, state):  # 输入状态
        if state not in self.q_table.index:  # Q表中没有该状态
            # 插入新的行,Q值初始化为0
            to_be_append = pd.Series([0] * len(self.actions), index=self.q_table.columns, name=state)  # 要添加的行信息
            self.q_table = self.q_table.append(to_be_append)  # Q表更新
            self.eligibility_trace = self.eligibility_trace.append(to_be_append)  # E表更新
学习函数

与SARSA不同的是,需要在E表中记录下经过的步,经历过的 E ( s , a ) E(s,a) E(s,a)就+1,表示其重要程度上升。更新Q表之后,E表还有衰减(lambda),重要程度会随着时间而减少。

    def learn(self, s, a, r, s_, a_):
        self.check_state_exit(s_)  # 查看状态s_是否存在,s_是在选择动作之后与环境交互获得的下一状态
        q_predict = self.q_table.loc[s, a]  # 当前状态s和动作a对应的Q值
        if s_ != 'terminal':  # 若下一步不是终态
            q_target = r + self.gamma * self.q_table.loc[s_, a_]  # 下一动作已经采样得到,直接使用s'与a'的Q值即可
        else:
            q_target = r  # 否则直接为立即回报
        error = q_target - q_predict  # 和之前一样

        self.eligibility_trace.ix[s, a] += 1  # 经历过,就做一个标记
        self.q_table += self.lr * error * self.eligibility_trace  # 更新Q(s,a),这里还要添加重要程度
        self.eligibility_trace *= self.gamma * self.lambda_  # E表随着时间衰减

这里self.eligibility_trace.ix[s, a] += 1还可以有更加高效的方式,相当于对E表做一个标准化,因为Q值过大会有BIAS等误差干扰:

self.eligibility_trace.ix[s, :] *= 0
self.eligibility_trace.ix[s, a] = 1
主循环

最后,主循环需要加上一句:RL.eligibility_trace *= 0,在开始新的回合前需要将E表清零。


9.4 理解lambda

通过上面的代码可以看出,lambda是E表随时间变化的衰退因子
更新Q表时,这句self.q_table += self.lr * error * self.eligibility_trace # 更新Q(s,a),这里还要添加重要程度代码中表明,更新Q表时,需要乘上“重要性表示”的E表,那么最近没有被访问过的 Q ( s , a ) Q(s,a) Q(s,a)基本上不会更新,就像不断地尝试在沙漠中走到绿洲,原来有脚印的、容易成功的路当然会作为首选,然而沙漠中有风,脚印会随风而逝,如果不经常走这里,这里的脚印就会消失,也就是从程序中self.eligibility_trace *= self.gamma * self.lambda_ # E表随着时间衰减这句代码,每一次行走都会有衰减。感觉这个和蚁群算法有点相似的地方,可以加快收敛速度。
通过打印Q表和E表可以清楚的看到算法的进程。


9.5 全代码一览
SARSAlambda类 SARSAlambda.py
import numpy as np
import pandas as pd


class SARSALambda:
    def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9, trace_decay = 0.9):  # 初始换函数,后面是默认参数
        self.actions = actions  # 动作空间
        self.lr = learning_rate  # 学习率
        self.gamma = reward_decay  # 奖励衰减
        self.epsilon = e_greedy  # 贪婪度
        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)  # 初始Q表
        # SARSA(lambda)中需要的另外两个参数
        self.lambda_ = trace_decay  # 即lambda,步数衰减
        self.eligibility_trace = self.q_table.copy()  # 和Q表相同的E表,用来记录访问印记

    def check_state_exit(self, state):  # 输入状态
        if state not in self.q_table.index:  # Q表中没有该状态
            # 插入新的行,Q值初始化为0
            to_be_append = pd.Series([0] * len(self.actions), index=self.q_table.columns, name=state)  # 要添加的行信息
            self.q_table = self.q_table.append(to_be_append)  # Q表更新
            self.eligibility_trace = self.eligibility_trace.append(to_be_append)  # E表更新

    def choose_action(self, observation):  # 根据当前的状态选择动作
        self.check_state_exit(observation)  # 检查状态是否存在,不存在添加到Q表中
        if np.random.uniform() < self.epsilon:  # 直接选择Q值最大的动作
            state_action = self.q_table.loc[observation, :]  # 选择对应的一行
            # 由于Q值最大的动作也有可能有多个,我们需要对这些动作随机选择(乱序)
            action = np.random.choice(state_action[state_action == np.max(state_action)].index)
        else:
            action = np.random.choice(self.actions)  # 随机选择一个动作
        return action

    def learn(self, s, a, r, s_, a_):
        self.check_state_exit(s_)  # 查看状态s_是否存在,s_是在选择动作之后与环境交互获得的下一状态
        q_predict = self.q_table.loc[s, a]  # 当前状态s和动作a对应的Q值
        if s_ != 'terminal':  # 若下一步不是终态
            q_target = r + self.gamma * self.q_table.loc[s_, a_]  # 下一动作已经采样得到,直接使用s'与a'的Q值即可
        else:
            q_target = r  # 否则直接为立即回报
        error = q_target - q_predict  # 和之前一样

        self.eligibility_trace.ix[s, a] += 1  # 经历过,就做一个标记
        self.q_table += self.lr * error * self.eligibility_trace  # 更新Q(s,a),这里还要添加重要程度
        self.eligibility_trace *= self.gamma * self.lambda_  # E表随着时间衰减
主循环 main.py
from SARSAlambda import SARSALambda
from maze_env import Maze


def update():  # 更新主函数
    for episode in range(10):  # 玩游戏的局数
        observation = env.reset()  # 初始化环境
        action = RL.choose_action(str(observation))
        RL.eligibility_trace *= 0  # SARSA(lambda)专用,清空E表
        while True:
            env.render()  # 刷新图像
            observation_, reward, done = env.step(action)  # 动作与环境交互,获得下一状态、奖励值和是否为终态的反馈
            action_ = RL.choose_action(str(observation_))  # 直接通过ε-greedy获得下一个动作a'
            RL.learn(str(observation), action, reward, str(observation_), action_)  # 更新Q表
            observation = observation_  # 转移到下一状态
            action = action_  # 动作直接就是刚才的动作
            print('打印Q表')
            print(RL.q_table)
            print('打印E表')
            print(RL.eligibility_trace)
            if done:
                break
    print('Game Over')  # 游戏结束
    env.destroy()  # 关闭窗口


if __name__ == '__main__':
    env = Maze()  # 创建环境
    RL = SARSALambda(actions=list(range(env.n_actions)))  # Q学习类

    env.after(100, update)  # 100ms后调用函数
    env.mainloop()  # 开始可视化环境

上一篇:【莫烦强化学习】视频笔记(三)2.SARSA学习实现走迷宫
下一篇:【莫烦强化学习】视频笔记(四)1.什么是DQN?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值