时序差分算法(Temporal Difference)


基于时序差分的算法不需要事先知道环境的状态转移函数和奖励函数

1、时序差分算法

时序差分算法是一种用来估计一个策略的价值函数的方法,可以从样本中学习,不需要事先知道环境。

蒙特卡洛方法对价值函数的增量更新方式:
在这里插入图片描述
蒙特卡洛方法需要等到整个序列结束才能计算得到这一次的回报,而时序差分只需要当前步结束就行,它用当前获得的奖励加上下一个状态的价值估计来当作在当前状态会获得的回报:
在这里插入图片描述
其中 R t + γ V ( S t + 1 − V ( S t ) ) R_t + \gamma V(S_{t+1}-V(S_t)) Rt+γV(St+1V(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 LearningSarsa的最大区别在于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、参考文献

  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值