第三章、基于表格方法求RL(1)-Sarsa
主要内容为题主在学习机器学习时记录的内容
文章目录
一、MDP与四元组
1.1 四元组
强化学习MDP四元组<S,A,P,R>分别表示 state-状态 、action-动作 、reward-奖励 、probability-状态转移概率。
1.2 MDP介绍
1.2.1 MDP简介
即为马尔可夫决策过程(Markov Decision Process,简称为MDP)。可以参考这篇文章进行学习:MDP。
在创造了四元组后的MDP中,它的状态转移概率 P 由状态 S 与输入的行动 A 共同决定。而 R也是MDP的一个重要元素,服从概率分布:
1.2.2 Model-based
在P函数和R函数已知的情况下,即你知道每一步的后果和下一步的状态,这样的模式叫做Model-based,这种情况并不适合使用强化学习来解决,更适合动态规划来研究。
如图的熊追人问题,已经有了一张比较清晰的决策树,你知道每一步的后果,那你就会选择收益最大的一步,这比较适合动态规划。
1.2.3 Model-free
而强化学习比较适合于无模型状态,你并不知道每一步的后果和下一步该做什么,你只能通过不断地试错并积累经验,从而把自己训练为对每一步都知道如何做的大神。
1.3 Q表格
1.3.1 Q表格概述
那强化学习如何记录每一步的后果,即记住每一步的经验呢?
最简单的方法是通过Q表格的方式来记录。Q即 Q(s,a),行表示每一个动作a,列表示每一个状态s,合起来就是某状态s下,做某动作a,得到的下一个状态st+1.
1.3.2 未来总收益
Q表格可以指导每一个Step的动作选项,它的目标导向为未来的总收益最大。对于未来总收益G的计算,常规的想法是 G=R1+R2+R3+…+Rt。
但是当前动作的价值,单纯地用未来所有的收益相加并不合理。
比如股票预测,单纯考虑未来收益之和并不合理,短期的变化更为重要。
所以就引入了衰减因子的概念。
通过引入衰减因子,可以让未来的较长时间后的 R 的影响作用变小,从而使得结果更注重近期的 R。
γ 等于 1 相当于每一步都考虑,γ 设为0则相当于只看下一步。
二、TD
2.1 强化概念
强化就是让智能体通过多次学习,不断地重复训练,使得下一个状态的价值可以不断地强化影响上一个状态的价值。
类似于条件反射,在训练狗狗握手的时候,你可以通过给狗粮的方式进行奖励,如果握手成功就给狗粮,握手成功的次数越多,给狗粮越多,则狗的脑子中握手就有狗粮吃的概念越被强化,最终学会握手。
2.2 TD-Temporal Difference
2.2.1 概念
这里可以详细学习该博主的文章(向大牛致敬!)。
TD是指 Temporal Difference ,时序差分。相比于DP(environment model信息非常全)、MC(蒙特卡洛,按episode学习),TD的方式是step-by-step。
MC算法需要等到一个episode 结束后才能更新value,TD和DP可以单步更新,但区别在于TD不需要知道确切的环境模型(状态转移概率等),可以根据下一步的估计来更新当前估计value。
也就是说太TD综合了DP单步和MC学习的优点。
2.2.2 数学公式
α 类似学习率,γ 是衰减因子,使用目标值减去当前值,在乘以学习率后加上当前值,用一种软更新的方式使得 Q 不断逼近于目标值。(有一说一没懂)
三、Sarsa
3.1 Sarsa引入
TD中的当前状态和动作,决定下一个状态和动作与奖励的方式,就是Sarsa算法。
Sarsa 就是(St,At,Rt+1,St+1,At+1)。
3.2 Sarsa介绍
核心就是拿下一步的Q值来更新这一步的Q值,不断强化每一个Q。
3.2.1 Sarsa伪代码
reset 是重置函数。
sample 是动作选择函数。
learn 是更新Q表格函数。
执行learn函数之前,需要先拿到next_obs, 来更新next_action。这是与Q-learning函数不同的地方。
3.2.2 Sarsa逻辑步骤
流程图:
3.3 ε-greedy
3.3.1 ε-greedy 策略
sample 函数使用了 ε-greedy 的方法。ε-greedy 是一个贪心地探索与利用的函数。
其表示在智能体做决策时,有一很小的正数ϵ ( < 1 )的概率随机选择未知的一个动作,剩下1 − ϵ 的概率选择已有动过中动作价值最大的动作。
3.3.2 ε-greedy 伪代码
predict 函数 是在已有的经验中挑选实现下一步的动作,即在已有的Q表格中查询。
samole 函数 是采样,保证除了能拿到最优的动作,其他的动作也有概率被采样到。
四、Sarsa 代码样例
问题描述:使小乌龟从左下角自学习的走到右下角,注意不要掉入悬崖(黑色),掉入悬崖则重新开始。
import gym
import numpy as np
import time
# agent代码
class SarsaAgent(object):
def __init__(self, obs_n, act_n, learning_rate=0.01, gamma=0.9, e_greed=0.1):
self.act_n = act_n # 动作维度,有几个动作可选
self.lr = learning_rate # 学习率
self.gamma = gamma # reward的衰减率
self.epsilon = e_greed # 按一定概率随机选动作
self.Q = np.zeros((obs_n, act_n))
# 根据输入观察值,采样输出的动作值,带探索
def sample(self, obs):
if np.random.uniform(0, 1) < (1.0 - self.epsilon): #根据table的Q值选动作
action = self.predict(obs)
else:
action = np.random.choice(self.act_n) #有一定概率随机探索选取一个动作
return action
# 根据输入观察值,预测输出的动作值
def predict(self, obs):
Q_list = self.Q[obs, :]
maxQ = np.max(Q_list)
action_list = np.where(Q_list == maxQ)[0] # maxQ可能对应多个action
action = np.random.choice(action_list)
return action
# 学习方法,也就是更新Q-table的方法
def learn(self, obs, action, reward, next_obs, next_action, done):
""" on-policy
obs: 交互前的obs, s_t
action: 本次交互选择的action, a_t
reward: 本次动作获得的奖励r
next_obs: 本次交互后的obs, s_t+1
next_action: 根据当前Q表格, 针对next_obs会选择的动作, a_t+1
done: episode是否结束
"""
predict_Q = self.Q[obs, action]
if done:
target_Q = reward # 没有下一个状态了
else:
target_Q = reward + self.gamma * self.Q[next_obs, next_action] # Sarsa
self.Q[obs, action] += self.lr * (target_Q - predict_Q) # 修正q
# 保存Q表格数据到文件
def save(self):
npy_file = './q_table.npy'
np.save(npy_file, self.Q)
print(npy_file + ' saved.')
# 从文件中读取Q值到Q表格中
def restore(self, npy_file='./q_table.npy'):
self.Q = np.load(npy_file)
print(npy_file + ' loaded.')
def run_episode(env, agent, render=False):
total_steps = 0 # 记录每个episode走了多少step
total_reward = 0
obs = env.reset() # 重置环境, 重新开一局(即开始新的一个episode)
action = agent.sample(obs) # 根据算法选择一个动作
while True:
next_obs, reward, done, _ = env.step(action) # 与环境进行一个交互
next_action = agent.sample(next_obs) # 根据算法选择一个动作
# 训练 Sarsa 算法
agent.learn(obs, action, reward, next_obs, next_action, done)
action = next_action
obs = next_obs # 存储上一个观察值
total_reward += reward
total_steps += 1 # 计算step数
if render:
env.render() #渲染新的一帧图形
if done:
break
return total_reward, total_steps
def test_episode(env, agent):
total_reward = 0
obs = env.reset()
while True:
action = agent.predict(obs) # greedy
next_obs, reward, done, _ = env.step(action)
total_reward += reward
obs = next_obs
# time.sleep(0.5)
# env.render()
if done:
break
return total_reward
# 使用gym创建悬崖环境
env = gym.make("CliffWalking-v0") # 0 up, 1 right, 2 down, 3 left
# 创建一个agent实例,输入超参数
agent = SarsaAgent(
obs_n=env.observation_space.n,
act_n=env.action_space.n,
learning_rate=0.1,
gamma=0.9,
e_greed=0.1)
# 训练500个episode,打印每个episode的分数
for episode in range(500):
ep_reward, ep_steps = run_episode(env, agent, False)
print('Episode %s: steps = %s , reward = %.1f' % (episode, ep_steps, ep_reward))
# 全部训练结束,查看算法效果
test_reward = test_episode(env, agent)
print('test reward = %.1f' % (test_reward))
运行结果:
最后的路线为:
之所以路线会这样,是因为Sarsa的最终的结果为,即使下一步会有随机动作,但仍然在安全范围内。所以会远离悬崖,即使这样的收益不是最大。
这与Q-learning的结果有很大的区别。
建议直接在百度的 AI Studio 上运行,速度快,安装包还特别方便。