Sarsa概述
首先可以回顾一下之前说的Q-Learning算法,Sarsa算法与Q-Learning算法很相似,
Q-Learing中Q(s1,a2)现实值:
- Q的现实值是该行为对该状态下的奖励+衰减值乘以下一阶段状态中预估的最大行为反馈的奖励
- Q-Learning 在这一步只是估计了一下下一步的行为动作反馈值,实际上并不一定选择该行为
- 离线学习(off-policy)激进派
Sarsa中Q(s1,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教学(十分感谢莫烦大佬的教学视频)