21/8/30 读书笔记 Q-Learning & Sarsa & Sarsa(Lambda)
21/8/30 读书笔记
强化学习 Q-Learning
Q-Learning算法简介
Q-Learning是一种value-based的off-policy方法,其采用Q-Table描述状态\(s\)下采取动作\(a\)的预期收益,表示为\(Q(s,a)\)。
价值取向value-based & 策略取向policy-based:前者为每个(离散的)action赋予一个确定的有限值作为它的value,因此总是选择最大value的action;后者使用于具有连续性质的action空间,为action空间赋予一个概率分布,概率越高则value越大,然后随机选择。
Q-算法更新可被描述为:
- 进入状态\(s_1\)
- 按一定策略从可选动作中选择动作\(a\)
- 采取动作\(a\),转到状态\(s_2\)并获得奖励\(r\)
- \(Q(s_1,a)\leftarrow Q(s_1,a)+\alpha[r+\gamma \max_{b}Q(s_2,b)-Q(s,a)]\)更新原Q-Table中\(Q(s_1,a)\)的值
Q-Table的更新中,我们认为\(r+\gamma \max_{b}Q(s_2,b)\)反映了\(Q(s_1,a)\)的现实(即当前步的奖励+未来步的最大期望收益*\(\gamma\)),而原\(Q(s_1,a)\)反映了事先对\(Q(s_1,a)\)的估计。
离线学习off-policy:优化算法使用的数据, 和数据是用哪个策略在环境中模拟获得的无关。Q-Learning之所以是off-policy的,是因为其总是基于\(\max_{b}Q(s_2,b)\)对下一步进行估计,无论采取任何策略,这个值都是不变,因此Q-Learning在更新公式中并不依赖任何特定的策略。
我们称\(\alpha\)为学习效率,体现了现实和估计的误差对更新的影响大小。称\(\gamma\)为未来奖励的衰减值,其反映了距离当前步越远的期望奖励对当前步的奖励影响越小,\(\gamma\)越小,算法对更远的奖励越不敏感。
请注意,我们的Q值是未来发展情况的累计变量,而不只是下一步的现实值。
一维空间探索者——算法示例
我们模拟一个探索者需要在一维空间内通过左右移动来走到终点。
以下示例来自莫烦Python教程,仅就pandas版本导致的问题进行了修缮
import numpy as np
import pandas as pd
import time
np.random.seed(2)
"""
Global Variables
"""
N_STATES = 6 # the length of the 1-dimensional world 一维世界长度
ACTIONS = ['left', 'right'] # available actions 动作空间
EPSILON = 0.9 # epsilon-greedy ε-贪婪算法参数
ALPHA = 0.1 # Learning rate 学习效率
LAMBDA = 0.9 # discount-factor 衰减因子 对应更新公式的γ
MAX_EPISODES = 12 # maximum episodes 最大迭代次数
FRESH_TIME = 0.1 # fresh time for one move 智能体移动的刷新时间
def build_q_table(n_states, actions):
table = pd.DataFrame(
np.zeros((n_states, len(actions))), # Q-Table初始化为全零
columns=actions
)
return table
def choose_action(state, q_table): # 采用
state_actions = q_table.iloc[state, :]
if (np.random.uniform() > EPSILON) or (state_actions.all() == 0):
action_name = np.random.choice(ACTIONS)
else:
action_name = ACTIONS[state_actions.argmax()]
return action_name
def get_env_feedback(S, A): # 环境的状态转移以及奖赏函数反馈
if A == 'right':
if S == N_STATES - 2:
S_ = 'terminal'
R = 1
else:
S_ = S + 1
R = 0
else:
R = 0
if S == 0:
S_ = S
else:
S_ = S - 1
return S_, R
def update_env(S, episode, step_counter): # 状态的输出函数
env_list = ['-'] * (N_STATES - 1) + ['T']
if S == 'terminal':
interaction = 'episode %s:total_steps = %s' % (episode + 1, step_counter)
print('\r{}'.format(interaction), end='')
time.sleep(FRESH_TIME)
print('\r ', end='')
else:
env_list[S] = 'o'
interaction = ''.join(env_list)
print('\r{}'.format(interaction), end='')
time.sleep(FRESH_TIME)
def main_loop(): # 算法主循环
q_table = build_q_table(N_STATES, ACTIONS)
for episode in range(MAX_EPISODES):
step_counter = 0
S = 0
is_terminated = False
update_env(S, episode, step_counter)
while not is_terminated:
A = choose_action(S, q_table)
S_, R = get_env_feedback(S, A)
q_predict = q_table.iloc[S][A]
if S_ != 'terminal':
q_target = R + LAMBDA * q_table.iloc[S_, :].max()
else:
q_target = R
is_terminated = True
q_table.iloc[S][A] += ALPHA * (q_target - q_predict)
S = S_
step_counter += 1
update_env(S, episode, step_counter)
if __name__ == '__main__':
main_loop()
强化学习 Sarsa
Sarsa vs. Q-Learning
Sarsa和Q-Learning的决策部分一致,均是使用Q-Table来选择当前状态下预期奖励最大的动作。
不同的地方在于Sarsa在更新Q-Table时的计算方式是不一样的:
- Q-Learning在更新时,采用了\(\max_{b}Q(s_2,b)\),但是当其真正处在\(s_2\)状态时,由于\(\epsilon-greedy\)而使得实际选择的动作不一定是我们用于更新Q-Table的动作\(b\)
- Sarsa在更新时,我们首先利用\(\epsilon-greedy\)选择状态\(s_2\)时将要采取的动作\(a_2\),然后再根据\(a_2\)来计算\(Q(s_1,a_1)=Q(s_1,a_1)+\alpha[r+\gamma Q(s_2,a_2)-Q(s_1,a_1)]\)。这样使得我们接下来采取的动作必定是我们用于更新Q-Table的动作
正因为Sarsa利用了自己接下来将要采取的动作对应的\(Q(s_2,a_2)\)而不是\(\max_{b}Q(s_2,b)\)来进行更新,其更新过程依赖于当前的决策,因此变成为了on-policy的算法。
对于Sarsa和Q-Learning,“决策”的本质就是Q-Table,它们的目标都是为了填好Q-Table中的值,但是由于更新Q-Table的行为不同,它们的归纳偏好也有所区别。
Q-Learning永远都是想着将\(\max_{b}Q(s_2,b)\)最大化,因此而变得贪婪,在更新Q-Table的时候不会考虑次优的动作(请注意,这里说的是更新Q-Table时只考虑了预期奖励最大的动作,但是在真正实施时不一定选择它),这使得Q-Learning是一种贪婪勇敢的算法,在更新决策时永远选择最优的下一步,而不在乎这一步是否走向死亡。而Sarsa对更新Q-Table所选择的动作十分保守,因为其用于更新Q-Table的动作是真真切切在下一步要执行的,因此在训练过程中会尽可能避免较高的损失。
Sarsa(\(\lambda\))
Sarsa(\(\lambda\))是Sarsa算法的一种性能优化。
Sarsa是一种on-policy的单步更新算法,其每走一步都会更新一次Q-Table(对Q-Learning也一样),也称Sarsa(0),表示走完当前步后立即更新。由此,我们可以认为Sarsa(\(\lambda\))是在走完所有步再进行更新,即回合更新算法。
单步更新和回合更新的性能区别: 对于经过a-b-c-d三步到达终点的任务,单步更新在每次动作结算后即刻更新,这使得a-b、b-c的更新并没有收到最终奖励的反馈(因为此时还不知道是否能够走到终点),只有最后一步c-d在更新时收到了奖励反馈。为了让奖励依次传播,我们需要重复多个轮次,使得奖励的影响逐渐次序传递,且每轮只能传递一步。 如果进行回合更新,则在抵达终点d时,同时更新了a-b、b-c、c-d三个动作的value,且只进行了一轮次的尝试。由此,回合更新通常能够更快学得正确的策略。
由于要一次性更新多个动作,因此Sarsa(\(\lambda\))认为更新时,距离更新点越近的动作更新的幅度越大,\(\lambda\)就是来体现对动作重要程度的衰减强度,取值在0~1之间。当\(\lambda=1\)时,我们认为走的每一步都同等重要;反之当\(\lambda=0\),则认为最近一步是唯一需要重视的,也就退化为原来的Sarsa算法。

(上图来自莫烦的教程 点击以跳转链接)
可以从上图中得知,我们维护了一个\(E(S,A)\),称其为eligibility trace,其记录了一个state-action对在当前时刻的“敏感度”,“敏感度”越高,说明其距离当前时刻越近,更新时幅度越大。可以看到每个step都会对所有state-action对根据“敏感度”进行Q-Table的更新,并用\(\gamma\lambda\)来对所有state-action对的“敏感度”进行衰减。
上述算法中,"敏感度"是不断累加的,这可能会带来一个极大的值。因此我们可以为敏感度设置一个上限,使其不至于累计过高。

浙公网安备 33010602011771号