强化学习经典算法笔记——SARSA算法
强化学习经典算法笔记(零):贝尔曼方程的推导
强化学习经典算法笔记(一):价值迭代算法Value Iteration
强化学习经典算法笔记(二):策略迭代算法Policy Iteration
强化学习经典算法笔记(三):蒙特卡罗方法Monte Calo Method
强化学习经典算法笔记(四):时间差分算法Temporal Difference(Q-Learning算法)
简介
上一篇讲到Off-policy的TD算法——Q-Learning。这一次要说说On-policy的SARSA算法。
首先说为什么叫SARSA算法。其实很简单,算法对Q值的更新依靠的是
转移到&s_{t+1}&所收集的信息,准确地说,利用下面这5个信息,就能更新一个状态的Q值,因此把5个字母拼起来就是SARSA算法了。
然后再说说为什么SARSA算法是On-policy的算法?SARSA算法基于当前的policy直接执行一次动作,用于收集数据(下图算法倒数第4行,Choose A’ from S’ using policy derived from Q),然后用这个样本更新当前的policy(倒数第3行),因此生成样本的policy和学习时的policy相同,所以是On-policy算法。
直接贴代码。超参数的选取和上一个Q-Learning大致一样。
代码
# 导入环境
import gym
import random
import numpy as np
import matplotlib.pyplot as plt
import copy
%matplotlib inline
env = gym.make('Taxi-v2')
# 设定参数
alpha = 0.3 # 学习率,如果太大,学习后期接近收敛的时候,各个Episode total reward之间的variance非常大
gamma = 0.90 # 未来收益对现在来看的折扣
epsilon = 0.02 # 越大越接近随机策略
# Q是字典,用于存储(状态-动作对)及其对应的Q值
Q = {}
for s in range(env.observation_space.n):
for a in range(env.action_space.n):
Q[(s,a)] = 0.0
# epsilon-贪心算法,基于最新的Q-table
def epsilon_greedy(state, epsilon):
if random.uniform(0,1) < epsilon:
return env.action_space.sample()
else:
return max(list(range(env.action_space.n)), key = lambda x: Q[(state,x)])
# 记录Episode总回报值在训练过程中的变化情况
r_record = []
# 记录Q值表的新旧之差
error_qa = []
# 保存Q-Table更新前后的两个table,注意一定要用深度拷贝!!!
Q_old = copy.deepcopy(Q)
# 训练
for i in range(2000):
# 每个Episode的总回报
r = 0
state = env.reset()
action = epsilon_greedy(state,epsilon)
while True:
# 执行a_t,得到r_t 和 s_{t+1}
nextstate, reward, done, _ = env.step(action)
# 得到 a_{t+1}
nextaction = epsilon_greedy(nextstate,epsilon)
# 更新Q值:Q(s,a) = Q(s,a) + α(r_t + γQ(s',a')-Q(s,a))
Q[(state,action)] += alpha * (reward + gamma * Q[(nextstate,nextaction)]-Q[(state,action)])
# 新状态变旧状态,进行下一次迭代
action = nextaction
state = nextstate
# 记录当前步骤的回报
r += reward
if done:
break
r_record.append(r)
# 计算Q-table的更新收敛情况
error = 0
for i in Q:
error = error + np.abs(Q[i] - Q_old[i])
error_qa.append(error)
Q_old = copy.deepcopy(Q) # 必须用深度拷贝,否则error恒为0
print('done!')
env.close()
# 画收敛曲线
plt.figure(1,figsize=(10,10))
plt.plot(list(range(2000)),r_record[:2000],linewidth=0.8)
plt.title('Reword Convergence Curve',fontsize=15)
plt.xlabel('Iteration',fontsize=15)
plt.ylabel('Total Reword of one episode',fontsize=15)
plt.figure(2,figsize=(10,10))
plt.plot(list(range(2000)),error_qa[:2000],linewidth=0.8)
plt.title('Q-table difference Convergence Curve',fontsize=15)
plt.xlabel('Iteration',fontsize=15)
plt.ylabel('Error between old and new Q-table',fontsize=15)
# plt.savefig('2.pdf')
实验结果与模型超参数分析
实验一
这是在alpha = 0.3, gamma = 0.90, epsilon = 0.02
的情况下跑出来的结果。
实验二
如果调大学习率alpha
,可以预见在算法收敛后,reward的变动依然较大,因为收敛之后学习率步长应该相应减小,不能再大幅度更新Q-table了。下面这是alpha=0.8, gamma = 0.90, epsilon = 0.02
的训练效果,收敛之后的variance很大。
实验三
如果调大epsilon
,会使得policy倾向于随机策略,也就是探索(Exploration)多于利用(Exploitation)。过大的epsilon
会在一定程度上削弱训练效果,因为epsilon-贪心算法的随机性越大,policy就越不能利用先前学习到的经验,而越倾向于随机乱试。下面这是alpha=0.3, gamma = 0.90, epsilon = 0.8
的收敛性,可见训练效果很差。
实验四
gamma
表示下一个状态的价值对当前状态的意义大不大。举例来说,gamma=1
表示未来状态的收益和当前收益同等重要,这样训练出来的算法会具有相对长远的策略;如果gamma=0
,则表示完全不关心未来收益,这样训练出来的算法往往只关注“眼前利益”。算法应当具有适当的gamma
值,短期利益和长远目光要兼顾。
下面这是alpha=0.3, gamma = 0.30, epsilon = 0.02
的训练结果,第一张图和实验二结果挺像,但是“刷子”的柄和刷毛反过来了。为什么会这样呢?一个直观解释就是,Taxi
这个任务,需要接乘客,送到指定地方,然后放下乘客,这是一套连贯的动作,持续较长时间、且前后有关联的任务,gamma
太小,太关注当前收益,不利于policy处理这样的任务。
第二个图显示Q-table确实能够收敛,但是我们从第一张图知道,它收敛到了错误的地方去了。而且收敛速度非常快,收敛后的部分“毛刺”也比较小,根据公式,这是因为gamma
也可以控制Q值更新的步长,或者说幅度,gamma
很小意味着每次更新的幅度很小,所以才会出现图中的情况。