【强化学习】Sarsa


Sarsa概述

首先可以回顾一下之前说的Q-Learning算法,Sarsa算法与Q-Learning算法很相似,

Q-Learing中Q(s1,a2)现实值:R+\gamma ^{*}maxQ(s2)

  • Q的现实值是该行为对该状态下的奖励+衰减值乘以下一阶段状态中预估的最大行为反馈的奖励
  • Q-Learning 在这一步只是估计了一下下一步的行为动作反馈值,实际上并不一定选择该行为
  • 离线学习(off-policy)激进派

Sarsa中Q(s1,a2)现实值:R+\gamma ^{*}Q(s2,a2)

  • Q的现实值是该行为对该状态下的奖励+衰减值乘以下一阶段选择的行为的Q值
  • Sarsa 在这一步估计的最大反馈的行为,就是下一步的行为
  • 在线学习(on-policy)保守派

然后看一下Q-Learning与Sarsa的代码上的区别:

  • Q-learning对于该行为在该状态下的反馈奖励,选在在预估值中的最大值,而实际上下一个行为不一定采取这个奖励,因此我们称Q-Learning算法是大胆型、激进派的算法
  • Sarsa对于该行为在该状态下的反馈奖励,直接采取下一个最大奖励的行为,因此我们称Sarsa为胆小型、保守派的算法

案例分析

寻路案例:建议先学习Q-Learning 案例分析

  • 红色为可移动的寻路个体
  • 黑色为惩罚位置【奖励= -1】
  • 黄色为目标位置【奖励= +1】
  • 其他区域为常规状态【奖励= 0】

寻路个体其实位置如图中所示的左上角,目标是移动到黄色位置,采用算法,能够让个体自主探索,最后找到最好的可以从起始点到终点位置的路径,同时绕过黑色区域

程序

由于Q-Learning与Srasa在算法很相似,最大的不同就是下一步行为的选择,因此我们将之前在Q-Learning 案例分析里的程序进行改进就可以用在Sarsa上了,该案例的程序分为三个部分:

  • maze_env.py : 该案例的环境部分,即:该图片以及这些颜色块的搭建,采用了Tkinter,这部分暂时不细说
  • RL_brain.py:该案例的算法大脑,智能体的大脑部分,所有决策都在这部分
  • run_Sarsa.py:该案例的主要实施流程以及更新

1. maze_env.py

环境部分和之前的Learning 案例分析没有变化

"""
强化学习迷宫的例子。
红色矩形:资源管理器。
黑色矩形:地狱[奖励= -1]。
黄色垃圾桶圈:天堂[奖励= +1]。
所有其他状态:地面[奖励= 0]。
该脚本是此示例的环境部分。 RL在RL_brain.py中。
"""

import numpy as np
import time
import sys

if sys.version_info.major == 2:
    import Tkinter as tk
else:
    import tkinter as tk

UNIT = 40  # pixels
MAZE_H = 4  # grid height
MAZE_W = 4  # grid width


class Maze(tk.Tk, object):
    def __init__(self):
        super(Maze, self).__init__()
        self.action_space = ['u', 'd', 'l', 'r']
        self.n_actions = len(self.action_space)
        self.title('maze')
        self.geometry('{0}x{1}'.format(MAZE_H * UNIT, MAZE_H * UNIT))
        self._build_maze()

    def _build_maze(self):
        self.canvas = tk.Canvas(self, bg='white',
                                height=MAZE_H * UNIT,
                                width=MAZE_W * UNIT)

        # create grids
        for c in range(0, MAZE_W * UNIT, UNIT):
            x0, y0, x1, y1 = c, 0, c, MAZE_H * UNIT
            self.canvas.create_line(x0, y0, x1, y1)
        for r in range(0, MAZE_H * UNIT, UNIT):
            x0, y0, x1, y1 = 0, r, MAZE_W * UNIT, r
            self.canvas.create_line(x0, y0, x1, y1)

        # create origin
        origin = np.array([20, 20])

        # hell
        hell1_center = origin + np.array([UNIT * 2, UNIT])
        self.hell1 = self.canvas.create_rectangle(
            hell1_center[0] - 15, hell1_center[1] - 15,
            hell1_center[0] + 15, hell1_center[1] + 15,
            fill='black')
        # hell
        hell2_center = origin + np.array([UNIT, UNIT * 2])
        self.hell2 = self.canvas.create_rectangle(
            hell2_center[0] - 15, hell2_center[1] - 15,
            hell2_center[0] + 15, hell2_center[1] + 15,
            fill='black')

        # create oval
        oval_center = origin + UNIT * 2
        self.oval = self.canvas.create_oval(
            oval_center[0] - 15, oval_center[1] - 15,
            oval_center[0] + 15, oval_center[1] + 15,
            fill='yellow')

        # create red rect
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')

        # pack all
        self.canvas.pack()

    def reset(self):
        self.update()
        time.sleep(0.5)
        self.canvas.delete(self.rect)
        origin = np.array([20, 20])
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')
        # return observation
        return self.canvas.coords(self.rect)

    def step(self, action):
        s = self.canvas.coords(self.rect)
        base_action = np.array([0, 0])
        if action == 0:  # up
            if s[1] > UNIT:
                base_action[1] -= UNIT
        elif action == 1:  # down
            if s[1] < (MAZE_H - 1) * UNIT:
                base_action[1] += UNIT
        elif action == 2:  # right
            if s[0] < (MAZE_W - 1) * UNIT:
                base_action[0] += UNIT
        elif action == 3:  # left
            if s[0] > UNIT:
                base_action[0] -= UNIT

        self.canvas.move(self.rect, base_action[0], base_action[1])  # move agent

        s_ = self.canvas.coords(self.rect)  # next state

        # reward function
        if s_ == self.canvas.coords(self.oval):
            reward = 1
            done = True
            s_ = 'terminal'
        elif s_ in [self.canvas.coords(self.hell1), self.canvas.coords(self.hell2)]:
            reward = -1
            done = True
            s_ = 'terminal'
        else:
            reward = 0
            done = False

        return s_, reward, done

    def render(self):
        time.sleep(0.1)
        self.update()


def update():
    for t in range(10):
        s = env.reset()
        while True:
            env.render()
            a = 1
            s, r, done = env.step(a)
            if done:
                break


if __name__ == '__main__':
    env = Maze()
    env.after(100, update)
    env.mainloop()

2. RL_brain.py

该部分为算法的大脑部分,所有的决策函数都在这儿

(1)参数初始化,包括算法用到的所有参数:行为、学习率、衰减率、决策率、以及q-table

(2)方法1:选择动作:随机数与决策率做对比,决策率为0.9,90%情况选择下一个反馈最大的奖励的行为,10%情况选择随机行为

(3)方法2:学习更新q-table:通过数据参数(该状态、该行为、该行为对该状态的奖励、下一个状态),计算该行为在该状态下的真实值与估计值,然后更新q-table里的预估值

(4)方法3:用来将新的状态作为索引添加在q-table里

为了能够使这种Q-table类型的算法的案例分析能有很好的扩展性,我们采用继承的方法,将可以提供Sarsa与Q-Learning共同使用,其中唯一不同的就是方法2:学习更新q-table部分,因此这部分需要重写

包括参数初始化、方法1、2、3的父类:

其中def learn方法,由于不同算法学习策略不同,因此采用pass

import numpy as np
import pandas as pd


class RL(object):
    def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9):
        self.actions = actions  # a list
        self.lr = learning_rate  # 学习率
        self.gamma = reward_decay  # 衰减率
        self.epsilon = e_greedy  # 决策率
        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float64)  # 初始化q-table

    # 检查状态是否存在,若不存在将作为索引添加在 q-table中,行为的值初始化为0
    def check_state_exist(self, state):
        if state not in self.q_table.index:
            # 如果该状态不在q-table的索引里存在,则将该状态添加到q-table中,
            # q-table是dataframe类型,字典的索引为状态,值的表头有四种【0,1,2,3】,分别代表前、后、左、右的行为
            self.q_table = self.q_table.append(
                pd.Series(
                    [0] * len(self.actions),
                    index=self.q_table.columns,
                    name=state,
                )
            )

    # 选择动作
    def choose_action(self, observation):
        self.check_state_exist(observation)  # 检查该状态是否在q-table中存在,如不存在则添加
        # 行为选择
        if np.random.uniform() < self.epsilon:
            # 如果随机数小于0.9 则选择最优行为
            state_action = self.q_table.loc[observation, :]
            # 有一些行为可能存在相同的最大预期值,则在最大预期值行为里随机选择
            action = np.random.choice(state_action[state_action == np.max(state_action)].index)
        else:
            # 如果随机数大于0.9 则随机选择
            action = np.random.choice(self.actions)
        return action

    # 学习策略,由于不同算法学习策略不同,因此采用pass
    def learn(self, *args):
        pass  # 每种的都有点不同, 所以用 pass

继承父类的Q-Learning:

class QLearningTable(RL):  # 继承了父类 RL
    def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9):
        super(QLearningTable, self).__init__(actions, learning_rate, reward_decay, e_greedy)  # 表示继承关系

    def learn(self, s, a, r, s_):  # learn 的方法在每种类型中有不一样, 需重新定义
        self.check_state_exist(s_)
        q_predict = self.q_table.loc[s, a]
        if s_ != 'terminal':
            q_target = r + self.gamma * self.q_table.loc[s_, :].max()
        else:
            q_target = r
        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)

继承父类的Sarsa:

class SarsaTable(RL):  # 继承 RL class

    def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9):
        super(SarsaTable, self).__init__(actions, learning_rate, reward_decay, e_greedy)  # 表示继承关系

    def learn(self, s, a, r, s_, a_):
        self.check_state_exist(s_)
        q_predict = self.q_table.loc[s, a]
        if s_ != 'terminal':
            q_target = r + self.gamma * self.q_table.loc[s_, a_]  # q_target 基于选好的 a_ 而不是 Q(s_) 的最大值
        else:
            q_target = r  # 如果 s_ 是终止符
        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)  # 更新 q_table

3. run_Sarsa.py

该脚本就是算法实施的主要流程:

与Q-Learning相比较,Sarsa最大的区别就是直接采用了下一个奖励最大的行为,因此与Q-Learning最大的不同就是:

  • 计算了下一个行为a'
  • 直接采用Q(s',a')计算Q(s,a)
  • 更新s为s',a为a'
from maze_env import Maze
from RL_brain import SarsaTable


def update():
    for episode in range(100):
        print('回合数:' + str(episode + 1))
        observation = env.reset()   # 初始化环境

        # Sarsa 根据 state 观测选择行为
        action = RL.choose_action(str(observation))

        while True:
            env.render()    # 刷新环境
            observation_, reward, done = env.step(action)   # 在环境中采取行为, 获得下一个 state_ (obervation_), reward, 和是否终止
            action_ = RL.choose_action(str(observation_))   # 根据下一个 state (obervation_) 选取下一个 action_
            # 从 (s, a, r, s, a) 中学习, 更新 Q_tabel 的参数 ==> Sarsa
            RL.learn(str(observation), action, reward, str(observation_), action_)
            # 将下一个当成下一步的 state (observation) and action
            observation = observation_
            action = action_
            # 终止时跳出循环
            if done:
                break

    # 大循环完毕
    print('game over')
    env.destroy()


if __name__ == "__main__":
    env = Maze()
    RL = SarsaTable(actions=list(range(env.n_actions)))

    env.after(100, update)
    env.mainloop()

完成之后,在run_Sarsa.py里运行就可以看到Sarsa算法的学习探索路径的过程了

这样的好处就是,我们只需要按照Q-Learning的算法流程,写好run_Qlearing的脚本,环境与大脑(maze_env.py,RL_brain.py)是可以共用的

 

代码以及学习过程来源:莫烦Python教学(十分感谢莫烦大佬的教学视频)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

蓝色蛋黄包

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

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

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

打赏作者

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

抵扣说明:

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

余额充值