基于时序差分的算法不需要事先知道环境的状态转移函数和奖励函数
1、时序差分算法
时序差分算法是一种用来估计一个策略的价值函数的方法,可以从样本中学习,不需要事先知道环境。
蒙特卡洛方法对价值函数的增量更新方式:
蒙特卡洛方法需要等到整个序列结束才能计算得到这一次的回报,而时序差分只需要当前步结束就行,它用当前获得的奖励加上下一个状态的价值估计来当作在当前状态会获得的回报:
其中
R
t
+
γ
V
(
S
t
+
1
−
V
(
S
t
)
)
R_t + \gamma V(S_{t+1}-V(S_t))
Rt+γV(St+1−V(St)) 通常被称为时序误差(TD Error)
2、Sarsa
直接使用时序差分方法来估计动作价值函数 Q:
然后使用贪心方法来选取使得在某个状态下动作价值最大的那个动作。
即:用贪心方法根据动作状态价值选取动作与环境交互,得到的数据再用时序差分方法来更新动作价值函数的估计
采用
ϵ
−
g
r
e
e
d
y
\epsilon-greedy
ϵ−greedy策略代替贪心算法:
现在就得到了一个实际的基于时序差分方法的强化学习算法,这个算法被称为Sarsa
,因为它的动作价值更新用到了当前状态 s ,当前动作 a,获得的奖励 r,下一个状态 s’ 和下一个动作 a’,于是就有了算法的名称
算法具体过程:
现在基于CliffWalking
环境(下图),实现Sarsa
:
实现CliffWallking
环境:
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
# 搭建游戏环境
class CliffWalkingEnv:
def __init__(self,ncol,nrow):
self.nrow = nrow
self.ncol = ncol
self.x = 0 # 记录当前agent位置的横坐标
self.y = self.nrow - 1 # 记录当前agent位置的纵坐标
# 外部调用setp函数让当前位置改变
def step(self,action):
change = [[0,-1],[0,1],[-1,0],[1,0]] # 代表上、下、左、右
self.x = min(self.ncol - 1 , max(0,self.x + change[action][0]))
self.y = min(self.nrow - 1 , max(0,self.y + change[action][1]))
next_state = self.y * self.ncol + self.x
reward = -1
done = False
# 下一个位置在悬崖或者终点
if self.y == self.nrow - 1 and self.x > 0:
done = True
if self.x != self.ncol - 1:
reward = -100
return next_state,reward,done
# 回归初始状态,坐标轴原点在左下角
def reset(self):
self.x = 0
self.y = self.nrow - 1
return self.y * self.ncol + self.x
Sarsa
算法:
class Sarsa:
def __init__(self,ncol,nrow,epsilon,alpha,gamma,n_action=4):
self.Q_table = np.zeros([nrow * ncol,n_action]) # 初始化Q(s,a)表格
self.n_action = n_action # 动作个数
self.alpha = alpha # 学习率
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-greedy策略中的参数
# 选取下一步的操作
def take_action(self,state):
if np.random.random() < self.epsilon:
action = np.random.randint(self.n_action)
else:
action = np.argmax(self.Q_table[state])
return action
# 打印策略
def best_action(self,state):
Q_max = np.max(self.Q_table[state])
a = [0 for _ in range(self.n_action)]
for i in range(self.n_action):
if self.Q_table[state,i] == Q_max:
a[i] = 1
return a
def update(self,s0,a0,r,s1,a1):
td_error = r + self.gamma * self.Q_table[s1,a1] - self.Q_table[s0,a0]
self.Q_table[s0,a0] += self.alpha * td_error
在CliffWalking
中运行Sarsa
算法:
ncol = 12
nrow = 4
env = CliffWalkingEnv(ncol,nrow)
np.random.seed(0)
epsilon = 0.1
alpha = 0.1
gamma = 0.9
agent = Sarsa(ncol,nrow,epsilon,alpha,gamma)
# 智能体在环境中运行多少条序列
num_episodes = 500
return_list = []
for i in range(10):
with tqdm(total=int(num_episodes/10), desc='Iteration %d' % i) as pbar: # tqdm的进度条功能
for i_episode in range(int(num_episodes/10)):
episode_return = 0
state = env.reset()
action = agent.take_action(state)
done = False
while not done:
next_state,reward,done = env.step(action)
next_action = agent.take_action(next_state)
episode_return += reward
agent.update(state,action,reward,next_state,next_action)
state = next_state
action = next_action
return_list.append(episode_return)
if (i_episode+1) % 10 == 0: # 每10条序列打印一下这10条序列的平均回报
pbar.set_postfix({'episode': '%d' % (num_episodes / 10 * i + i_episode+1), 'return': '%.3f' % np.mean(return_list[-10:])})
pbar.update(1)
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('Sarsa on {}'.format('Cliff Walking'))
plt.show()
在经过Sarsa
算法之后,agent获得的回报越来越高,现在可以看一下得到的策略在每步都会采取什么动作:
def print_agent(agent, env, action_meaning, disaster=[], end=[]):
for i in range(env.nrow):
for j in range(env.ncol):
if (i * env.ncol + j) in disaster:
print('****', end=' ')
elif (i * env.ncol + j) in end:
print('EEEE', end=' ')
else:
a = agent.best_action(i * env.ncol + j)
pi_str = ''
for k in range(len(action_meaning)):
pi_str += action_meaning[k] if a[k] > 0 else 'o'
print(pi_str, end=' ')
print()
action_meaning = ['^', 'v', '<', '>']
print('Sarsa算法最终收敛得到的策略为:')
print_agent(agent, env, action_meaning, list(range(37, 47)), [47])
---------------------------------------------------------------------------
Sarsa算法最终收敛得到的策略为:
ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ovoo
ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ovoo
^ooo ooo> ^ooo ooo> ooo> ooo> ooo> ^ooo ^ooo ooo> ooo> ovoo
^ooo **** **** **** **** **** **** **** **** **** **** EEEE
Sarsa
比较保守,会采取相对安全的路径到达终点
3、多步Sarsa算法
多步时序差分的意思是使用 n 步的奖励,然后再用之后的状态价值估计。 也就是将
替换为:
于是,替换掉上面Sarsa
算法值函数的更新公式:
多步Sarsa
算法实现:
# 多步Sarsa算法更新
class nstep_Sarsa:
def __init__(self,n,ncol,nrow,epsilon,alpha,gamma,n_action=4):
self.Q_table = np.zeros([ncol*nrow,n_action])
self.n_action = n_action
self.alpha = alpha
self.gamma = gamma
self.epsilon = epsilon
self.n = n # n步Sarsa
self.state_list = [] # 保存之前的状态
self.action_list = [] # 保存之前的动作
self.reward_list = [] # 保存之前的奖励
def take_action(self,state):
if np.random.random() < self.epsilon:
action = np.random.randint(self.n_action)
else:
action = np.argmax(self.Q_table[state])
return action
def best_action(self,action):
Q_max = np.max(self.Q_table[state])
a = [0 for _ in range(self.n_action)]
for i in range(self.n_action):
if self.Q_table[state,i] == Q_max:
a[i] = 1
return a
def update(self,s0,a0,r,s1,a1,done):
self.state_list.append(s0)
self.action_list.append(a0)
self.reward_list.append(r)
# 若保存的数据可以进行n步更新
if len(self.state_list) == self.n:
G = self.Q_table[s1,a1]
for i in reversed(range(self.n)):
# 不断向前计算每一步的回报
G = self.gamma * G + self.reward_list[i]
# 如果到达终止状态,最后几步虽然长度不够n步,也对其进行更新
if done and i > 0:
s = self.state_list[i]
a = self.action_list[i]
self.Q_table[s,a] += self.alpha * (G - self.Q_table[s,a])
s = self.state_list.pop(0)
a = self.action_list.pop(0)
self.reward_list.pop(0)
self.Q_table[s,a] += self.alpha * (G - self.Q_table[s,a])
if done:
self.state_list = []
self.action_list = []
self.reward_list = []
np.random.seed(0)
n_step = 5 # 5步Sarsa算法
alpha = 0.1
epsilon = 0.1
gamma = 0.9
agent = nstep_Sarsa(n_step, ncol, nrow, epsilon, alpha, gamma)
num_episodes = 500 # 智能体在环境中运行多少条序列
return_list = [] # 记录每一条序列的回报
for i in range(10): # 显示10个进度条
with tqdm(total=int(num_episodes/10), desc='Iteration %d' % i) as pbar: # tqdm的进度条功能
for i_episode in range(int(num_episodes/10)): # 每个进度条的序列数
episode_return = 0
state = env.reset()
action = agent.take_action(state)
done = False
while not done:
next_state, reward, done = env.step(action)
next_action = agent.take_action(next_state)
episode_return += reward # 这里回报的计算不进行折扣因子衰减
agent.update(state, action, reward, next_state, next_action, done)
state = next_state
action = next_action
return_list.append(episode_return)
if (i_episode+1) % 10 == 0: # 每10条序列打印一下这10条序列的平均回报
pbar.set_postfix({'episode': '%d' % (num_episodes / 10 * i + i_episode+1), 'return': '%.3f' % np.mean(return_list[-10:])})
pbar.update(1)
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('5-step Sarsa on {}'.format('Cliff Walking'))
plt.show()
5步Sarsa算法的收敛速度比单步Sarsa算法更快,看一下策略表现:
action_meaning = ['^', 'v', '<', '>']
print('5步Sarsa算法最终收敛得到的策略为:')
print_agent(agent, env, action_meaning, list(range(37, 47)), [47])
------------------------------------------------------
5步Sarsa算法最终收敛得到的策略为:
ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ovoo
^ooo ^ooo ^ooo oo<o ^ooo ^ooo ^ooo ^ooo ooo> ooo> ^ooo ovoo
ooo> ^ooo ^ooo ^ooo ^ooo ^ooo ^ooo ooo> ooo> ^ooo ooo> ovoo
^ooo **** **** **** **** **** **** **** **** **** **** EEEE
4、Q-Learning
Q Learning
和Sarsa
的最大区别在于Q-Learning
的时序差分更新方式为:
具体算法:
使用python
实现:
class QLearning:
""" Q-learning算法 """
def __init__(self, ncol, nrow, epsilon, alpha, gamma, n_action=4):
self.Q_table = np.zeros([nrow * ncol, n_action]) # 初始化Q(s,a)表格
self.n_action = n_action # 动作个数
self.alpha = alpha # 学习率
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-greedy策略中的参数
def take_action(self, state): #选取下一步的操作
if np.random.random() < self.epsilon:
action = np.random.randint(self.n_action)
else:
action = np.argmax(self.Q_table[state])
return action
def best_action(self, state): # 用于打印策略
Q_max = np.max(self.Q_table[state])
a = [0 for _ in range(self.n_action)]
for i in range(self.n_action):
if self.Q_table[state, i] == Q_max:
a[i] = 1
return a
def update(self, s0, a0, r, s1):
td_error = r + self.gamma * self.Q_table[s1].max() - self.Q_table[s0, a0]
self.Q_table[s0, a0] += self.alpha * td_error
np.random.seed(0)
epsilon = 0.1
alpha = 0.1
gamma = 0.9
agent = QLearning(ncol, nrow, epsilon, alpha, gamma)
num_episodes = 500 # 智能体在环境中运行多少条序列
return_list = [] # 记录每一条序列的回报
for i in range(10): # 显示10个进度条
with tqdm(total=int(num_episodes/10), desc='Iteration %d' % i) as pbar: # tqdm的进度条功能
for i_episode in range(int(num_episodes/10)): # 每个进度条的序列数
episode_return = 0
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done = env.step(action)
episode_return += reward # 这里回报的计算不进行折扣因子衰减
agent.update(state, action, reward, next_state)
state = next_state
return_list.append(episode_return)
if (i_episode+1) % 10 == 0: # 每10条序列打印一下这10条序列的平均回报
pbar.set_postfix({'episode': '%d' % (num_episodes / 10 * i + i_episode+1), 'return': '%.3f' % np.mean(return_list[-10:])})
pbar.update(1)
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('Q-learning on {}'.format('Cliff Walking'))
plt.show()
action_meaning = ['^', 'v', '<', '>']
print('Q-learning算法最终收敛得到的策略为:')
print_agent(agent, env, action_meaning, list(range(37, 47)), [47])
打印策略:
Q-learning算法最终收敛得到的策略为:
^ooo ovoo ovoo ^ooo ^ooo ovoo ooo> ^ooo ^ooo ooo> ooo> ovoo
ooo> ooo> ooo> ooo> ooo> ooo> ^ooo ooo> ooo> ooo> ooo> ovoo
ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ooo> ovoo
^ooo **** **** **** **** **** **** **** **** **** **** EEEE
5、参考文献
- Reinforcement Learning : An Introduction
- 深入浅出强化学习:原理入门
- Shusen Wang的《强化学习基础》视频,link:https://www.youtube.com/watch?v=XRGquU0ZJok
- 动手学强化学习,link:https://hrl.boyuai.com/