目录
- 前言
- 单步更新和回合更新
- 算法公式
- 探险者上天堂实战
- 小结
前言
今天介绍的Sarsa(lambda)算法是Sarsa的改进版,二者的主要区别在于:
- Sarsa是每次获取到reward之后只更新到reward的前一步,而Sarsa(lambda)就是更新获取到reward的前lambda步。
- 也就是说,Sarsa在没有获得reward之前,当前步的Q值其实是没有任何变化的,直到获得reward之后才会更新前一步。而Sarsa(lambda)则会对获得reward所有步都进行更新,离reward越近的步余越重要,越远的步则越不重要(由lambda控制衰减幅度)。lambda是在[0,1]之间取值,如果lambda = 0,Sarsa(lambda)就是Sarsa,只更新获取到reward前一步。如果lambda = 1,Sarsa(lambda)更新的是获取到reward的前所有经历过的步
其实lambda=0和lambda=1就是单步更新和回合更新的区别,接下来我们来举两个例子来说明回合更新的优势在哪里。
单步更新和回合更新
我们以机器人找宝藏为例子说明。
- 在单步更新的时候,虽然我们每一步都在更新,但只有我们获得宝藏的时候的更新才是和获得宝藏有关联,而之前的走的步都认为和宝藏没关系;
-
在回合更新的时候,虽然我们要等到整个回合之后才更新,但是我们的更新是整个回合中走过的所有步都被认为是和获取宝藏有关系,都是为了得到宝藏需要学习的步,所以每一个脚印在下一个回合被选中的几率又高了些,在这种角度看,回合更新似乎更有效率一些。
同样是机器人找宝藏。
- 单步更新的时候我们还是对每一步走完都进行更新,但是同时记下来之前的寻宝之路。我们可以想象成每走一步就插上一个小旗子,这样我们就能清楚地知道除了最近的一步,找到宝物时还需要更新哪些步了。不过有时候情况可能没有这么乐观??赡芸嫉募复瓮耆挥型沸?,在原地打转了很久,然后才找到宝藏,那些重复的脚步其实对于拿到宝藏用户不大,所以Sarsa(lambda)就来拯救你了。
算法公式
- 从上图我们可以看出,Sarsa(lambda)比起Sarsa,多了一个矩阵E(eligibility trace),它是用来保存获得reward在路径中所精力的每一步,因此在每次更新的时候也会对之前所经历的步进行更新。
- 除去这种更新方式,我们还可以在E(S,A)<-E(S,A) + 1的前面,先把当前的所有动作的value清0,这样的效果会好很多,这样只保持了最近一次获得reward的action。
我们用图来说明这两种更新方式的不同
这是针对于一个state-action值按精力次数的变化。最上面是经历state-action的时间点,第二张图是使用这种方式所带来的"不可或缺性值":
self.eligibility_trace.ix[s, a] += 1
而第三张图是使用下面这种方法带来的"不可或缺性值":
self.eligibility_trace.ix[s, :] *= 0; self.eligibility_trace.ix[s, a] = 1
第一种的更新方式会有一些干扰,在试验中第二种更新方式也确实效果更好,所以下面的实战会采取第二种的方式。
探险者上天堂实战
我们还是用上次的上天堂的例子来实战。
代码主结构
使用SarsaLambdaTable
在算法更新迭代的部分,是和之前的SarsaTable
一样的,所以本次就只阐述思维决策的部分。
class SarsaLambdaTable:
# 初始化 (有改变)
def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9, trace_decay=0.9):
# 选行为 (与之前一样)
def choose_action(self, observation):
# 学习更新参数 (有改变)
def learn(self, s, a, r, s_):
# 检测 state 是否存在 (有改变)
def check_state_exist(self, state):
和上次一样,我们选择继承的方式,将SarsaLambdaTable
继承到RL
,所以我们将之前的_init_
,check_state_exist
,choose_action
,learn
全部放到这个主结构,之后再作具体修改。
预设值
在预设值中,我们添加了trace_decay=0.9
这个就是lambda
的值。这个值会使得拿到reward的每一步都有价值。
class SarsaLambdaTable(RL):
def __init__(self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9, trace_decay=0.9):
super(SarsaLambdaTable, self).__init__(actions, learning_rate, reward_decay, e_greedy)
# backward view算法,eligibility trace
self.lambda_ = trace_decay
self.eligibility_trace = self.q_table.copy()#空的eligibility trace表
检查state是否存在
这里和之前唯一的不同就是考虑了eligibility_trace
def check_state_exist(self, state):
if state not in self.q_table.index:
# append new state to q table
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)
# 同样需要更新eligibility_trace
self.eligibility_trace = self.eligibility_trace.append(to_be_append)
学习
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_] # next state is not terminal
else:
q_target = r # next state is terminal
error = q_target - q_predict
# 对于经历过的state-action,我们让他+1,证明他是得到reward途中不可或缺的一环
# Method 1:
# self.eligibility_trace.loc[s, a] += 1
# Method 2:
self.eligibility_trace.loc[s, :] *= 0
self.eligibility_trace.loc[s, a] = 1
# Q表更新
self.q_table += self.lr * error * self.eligibility_trace
# 随着时间衰减eligibility_trace的值,离获取reward越远的步,他的"不可或缺性"越小
self.eligibility_trace *= self.gamma*self.lambda_
小结
从结果上看,Sarsa(Lambda)由于多了一张eligibility_trace表,那么就有了探索轨迹的记录,且此轨迹对Q_table的value产生了正面或者负面的影响,所以Sarsa(lambda)比Sarsa能更快地学会完成任务,缺点是:由于学得快,但不一定学得精,而且非常容易思维僵化,很喜欢用固定的action完成任务。使用文中的第二种更新方式可以缓解他固执情绪的积累速度,比较好。