目錄
- 前言
- 單步更新和回合更新
- 算法公式
- 探險者上天堂實戰(zhàn)
- 小結(jié)
前言
今天介紹的Sarsa(lambda)算法是Sarsa的改進版碉渡,二者的主要區(qū)別在于:
- Sarsa是每次獲取到reward之后只更新到reward的前一步奠旺,而Sarsa(lambda)就是更新獲取到reward的前l(fā)ambda步。
- 也就是說订晌,Sarsa在沒有獲得reward之前爷贫,當前步的Q值其實是沒有任何變化的锻煌,直到獲得reward之后才會更新前一步谷丸。而Sarsa(lambda)則會對獲得reward所有步都進行更新,離reward越近的步余越重要购城,越遠的步則越不重要(由lambda控制衰減幅度)吕座。lambda是在[0,1]之間取值,如果lambda = 0,Sarsa(lambda)就是Sarsa瘪板,只更新獲取到reward前一步吴趴。如果lambda = 1,Sarsa(lambda)更新的是獲取到reward的前所有經(jīng)歷過的步
其實lambda=0和lambda=1就是單步更新和回合更新的區(qū)別,接下來我們來舉兩個例子來說明回合更新的優(yōu)勢在哪里侮攀。
單步更新和回合更新
我們以機器人找寶藏為例子說明锣枝。
- 在單步更新的時候,雖然我們每一步都在更新兰英,但只有我們獲得寶藏的時候的更新才是和獲得寶藏有關(guān)聯(lián)撇叁,而之前的走的步都認為和寶藏沒關(guān)系;
-
在回合更新的時候箭昵,雖然我們要等到整個回合之后才更新税朴,但是我們的更新是整個回合中走過的所有步都被認為是和獲取寶藏有關(guān)系回季,都是為了得到寶藏需要學(xué)習(xí)的步家制,所以每一個腳印在下一個回合被選中的幾率又高了些,在這種角度看泡一,回合更新似乎更有效率一些颤殴。
image
同樣是機器人找寶藏。
- 單步更新的時候我們還是對每一步走完都進行更新鼻忠,但是同時記下來之前的尋寶之路涵但。我們可以想象成每走一步就插上一個小旗子,這樣我們就能清楚地知道除了最近的一步,找到寶物時還需要更新哪些步了矮瘟。不過有時候情況可能沒有這么樂觀瞳脓。可能開始的幾次完全沒有頭緒澈侠,在原地打轉(zhuǎn)了很久劫侧,然后才找到寶藏,那些重復(fù)的腳步其實對于拿到寶藏用戶不大哨啃,所以Sarsa(lambda)就來拯救你了烧栋。
算法公式
- 從上圖我們可以看出,Sarsa(lambda)比起Sarsa拳球,多了一個矩陣E(eligibility trace)审姓,它是用來保存獲得reward在路徑中所精力的每一步,因此在每次更新的時候也會對之前所經(jīng)歷的步進行更新祝峻。
- 除去這種更新方式魔吐,我們還可以在E(S,A)<-E(S,A) + 1的前面,先把當前的所有動作的value清0呼猪,這樣的效果會好很多画畅,這樣只保持了最近一次獲得reward的action。
我們用圖來說明這兩種更新方式的不同
image
這是針對于一個state-action值按精力次數(shù)的變化宋距。最上面是經(jīng)歷state-action的時間點轴踱,第二張圖是使用這種方式所帶來的"不可或缺性值":
self.eligibility_trace.ix[s, a] += 1
而第三張圖是使用下面這種方法帶來的"不可或缺性值":
self.eligibility_trace.ix[s, :] *= 0; self.eligibility_trace.ix[s, a] = 1
第一種的更新方式會有一些干擾,在試驗中第二種更新方式也確實效果更好谚赎,所以下面的實戰(zhàn)會采取第二種的方式淫僻。
探險者上天堂實戰(zhàn)
我們還是用上次的上天堂的例子來實戰(zhàn)。
代碼主結(jié)構(gòu)
使用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):
# 學(xué)習(xí)更新參數(shù) (有改變)
def learn(self, s, a, r, s_):
# 檢測 state 是否存在 (有改變)
def check_state_exist(self, state):
和上次一樣闸盔,我們選擇繼承的方式悯辙,將SarsaLambdaTable
繼承到RL
,所以我們將之前的_init_
,check_state_exist
,choose_action
,learn
全部放到這個主結(jié)構(gòu)迎吵,之后再作具體修改躲撰。
預(yù)設(shè)值
在預(yù)設(shè)值中,我們添加了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)
學(xué)習(xí)
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
# 對于經(jīng)歷過的state-action蔫巩,我們讓他+1谆棱,證明他是得到reward途中不可或缺的一環(huán)
# 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_
小結(jié)
從結(jié)果上看垃瞧,Sarsa(Lambda)由于多了一張eligibility_trace表蔫劣,那么就有了探索軌跡的記錄,且此軌跡對Q_table的value產(chǎn)生了正面或者負面的影響个从,所以Sarsa(lambda)比Sarsa能更快地學(xué)會完成任務(wù)拦宣,缺點是:由于學(xué)得快,但不一定學(xué)得精信姓,而且非常容易思維僵化鸵隧,很喜歡用固定的action完成任務(wù)。使用文中的第二種更新方式可以緩解他固執(zhí)情緒的積累速度意推,比較好豆瘫。