n步自举法


简介

n步时序差分方法(n-step TD methods)可以被看作是蒙特卡洛方法单步时序方法的结合:

  1. 蒙特卡洛方法(MC):不用对环境进行建模,直接在环境中交互产生经验进行学习,该方法基于一整个回合的序列,从整个采样轨迹中计算回报,不需要推测。
  2. 局限性:需要在一个回合结束后计算整个回合回报的期望。
  3. 单步时序方法(one-step TD):结合动态规划和蒙特卡洛算法,包括Sarsa,Q-learning方法,是一种单步自举法,采集执行一步操作后的下一个状态及奖励,然后预测下一个状态的价值来代替后面回报的期望。
  4. 局限性:相同的时刻步长(单步,总是一样的)决定动作变化的频度以及执行自举操作的时间段,自举法往往需要在一个时间段中操作才能得到好的结果,这个时间段需要有显著的状态变化。
  5. 自举法:
  6. 目标:基于原始样本中获得的多个数据样本,为总体参数(回报)创建一个估计值($R_{t+1}+\gamma V(S_{t+1})$)
  7. 过程:是通过重复采样样本数据集来创建许多模拟样本来完成的(与环境交互),每个模拟的样本被用来计算参数的估计,这些估计可以被组合起来形成一个抽样分布,自举抽样分布允许我们得出统计推论,如估计参数的标准误差。

图片.png


7.1 n步时序差分预测(n-step TD Prediction)

n步更新方法——介于MC和TD(0)的中间方法

  • 蒙特卡洛方法:根据某一状态开始到终止状态的收益序列,是“已知”的。对这个状态的价值进行更新,$v_\pi(S_t)$的估计值会沿着完整回报的方向进行更新。(极端)

$G_t \dot{=} R_{t+1}+\gamma R_{t+2}+\gamma^2 R_{t+3}+…+\gamma^{T-t-1} R_T$

  • 更新目标:累积收益(回报)

  • 时序差分方法:仅根据后面的单个即时收益,在下一个后继状态的价值估计值基础上进行自举更新。下一个状态是“估计”出来的(极端)

$G_{t:t+1} \dot{=} R_{t+1} + \gamma V_t(S_{t+1})$
$\gamma V_t(S_{t+1})-->\gamma R_{t+2}+\gamma^2 R_{t+3}+…+\gamma^{T-t-1} R_T$

  • 更新目标:即时收益加后继状态的价值函数估计值乘以折扣系数(单步回报),代替了完整回报中的后T-1项

  • n步时序差分预测:有“部分估计”,“部分已知”的特性。根据多个中间时刻的收益来进行更新,多于一个时刻的收益,但又不是到终止状态的所有收益。(中间)

  • 两步回报:

$G_{t:t+2} \dot{=} R_{t+1} + \gamma R_{t+2}+ \gamma^2 V_{t+1}(S_{t+2})$
$\gamma^2 V_{t+1}(S_{t+2})-->\gamma^2 R_{t+3}+\gamma^3 R_{t+4}+…+\gamma^{T-t-1} R_T$

  • n步回报,n步后截断得到n步回报,其余部分用$V_{t+n-1}(S_{t+n})$代替:

$G_{t:t+n} \dot{=} R_{t+1} + \gamma R_{t+2}+…+ \gamma^{n-1}R_{t+n} + \gamma^{n}V_{t+n-1}(S_{t+n})$
若$t+n \ge T$,那么n步回报超出了终止状态,n步回报等于完整回报,$G_{t:t+n} = G_t$
需要在获取到$R_{t+n}$和$V_{t+n-1}$后才能计算,也就是说需要到达时刻t+n。

图片.png

n步回报状态价值函数更新算法

将前面所说的n步差分回报预测带入增量式状态价值函数估计中:
$V_{t+n}(S_{t}) \dot{=} V_{t+n-1}(S_{t})+\alpha[G_{t:t+n}-V_{t+n-1}(S_{t})],\ \ \ \ \ 0\le t <T$

  • $V_{t+n}(S_{t})$:在t+n时刻去更新内存中$V(S_t)$的值。
  • $V_{t+n-1}(S_{t})$:在t+n-1时刻时$V(S_t)$的值,实际上,只有再次遇到这个状态后再隔n个步长才会更新值,在此期间值实际不变 。
  • *每遇到一个状态,至少会延迟n步才能更新该状态的价值,最开始的n-1时刻,价值函数不会被更新。

图片.png

图片.png

  • n步回报的误差减少性质:在最坏的情况下,采用$G_{t:t+n}$对$v_\pi$的估计可以保证比$V_{t+n-1}$更好,n步回报的期望的最坏误差能够保证不大于$V_{t+n-1}$最坏误差的$\gamma^2$倍,在合适的条件下都能收敛到正确的预测。

$\max_s|E_\pi[G_{t:t+n}|S_t=s]-v_\pi(s)|\le \gamma^n\max_s|V_{t+n-1}(s)-v_\pi(s)|$

练习

7.1 在第6章中提到,如果价值函数的估计值不是每步都被更新,则蒙特卡洛误差$G_t-V(S_t)$可以被改写成时序差分的和,请将n步误差也改写为时序差分误差之和的形式。
图片.png
7.2 在n步方法中,价值函数需要每步都更新,所以利用时序差分误差之和来代替公式中的错误项的算法会和之前有些不同。这种算法是一个更好的还是更差的算法?

  • Online:online TD error算法在回合期间每一步都更新价值函数,始终使用最新的可用值。
  • TD error sum:时序差分之和变体不断积累每个状态的TD误差,并在回合结束时通过一次大的更新来改变价值函数。
import gym
import numpy as np


def nstep_on_policy_return(v, done, states, rewards):
    """ Calculate un-discounted n-step on-policy return per (7.1) """
    assert len(states) - 1 == len(rewards)

    if not rewards:
        # Append value of the n-th state unless in the termination phase
        return 0 if done else v[states[0]]

    sub_return = nstep_on_policy_return(v, done, states[1:], rewards[1:])
    return rewards[0] + sub_return


def td_on_policy_prediction(env, policy, n, num_episodes, alpha=0.5, tderr=False):
    """ n-step TD algorithm for on-policy value prediction per Chapter 7.1. Value function updates are
     calculated by summing TD errors per Exercise 7.2 (tderr=True) or with (7.2) (tderr=False). """
    assert type(env.action_space) == gym.spaces.Discrete
    assert type(env.observation_space) == gym.spaces.Discrete

    # Number of available actions and states
    n_state, n_action = env.observation_space.n, env.action_space.n,
    assert policy.shape == (n_state, n_action)

    # Initialization of value function
    v = np.ones([n_state], dtype=np.float) * 0.5

    history = []
    for episode in range(num_episodes):
        # Reset the environment and initialize n-step rewards and states
        state = env.reset()
        nstep_states = [state]
        nstep_rewards = []

        dv = np.zeros_like(v)

        done = False
        while nstep_rewards or not done:
            if not done:
                # Step the environment forward and check for termination
                action = np.random.choice(n_action, p=policy[state])
                state, reward, done, info = env.step(action)

                # Accumulate n-step rewards and states
                nstep_rewards.append(reward)
                nstep_states.append(state)

                # Keep accumulating until there's enough for the first n-step update
                if len(nstep_rewards) < n:
                    continue
                assert len(nstep_states) - 1 == len(nstep_rewards) == n

            # Calculate un-discounted n-step return per (7.1)
            v_target = nstep_on_policy_return(v, done, nstep_states, nstep_rewards)

            if tderr is True:
                # Accumulate TD errors over the episode while v is kept constant per Exercise 7.2
                dv[nstep_states[0]] += alpha * (v_target - v[nstep_states[0]])
            else:
                # Update value function toward the target per (7.2)
                v[nstep_states[0]] += alpha * (v_target - v[nstep_states[0]])

            # Remove the used n-step reward and states
            del nstep_rewards[0]
            del nstep_states[0]

            # Update value function with the sum of TD errors accumulated during the episode
            v += dv

        history += [np.copy(v)]
    return history
import gym
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

import randomwalk
from td import td_on_policy_prediction

env = gym.make('RandomWalk-v0')
policy = np.ones([env.observation_space.n, env.action_space.n], dtype=np.float)
size = env.observation_space.n

n = 4
history0 = td_on_policy_prediction(env, policy, n=n, num_episodes=10,
                                   alpha=0.1, tderr=False)
history1 = td_on_policy_prediction(env, policy, n=n, num_episodes=10,
                                   alpha=0.1, tderr=True)

v_star = np.arange(1, size + 1) / (size + 1)



plt.figure()
plt.title(f"Learning Curves (n={n})")
plt.xlabel("Episodes")
plt.ylabel("RMS Error")
rms0 = np.sqrt((np.mean(history0 - v_star, axis=1) ** 2))
rms1 = np.sqrt((np.mean(history1 - v_star, axis=1) ** 2))
plt.plot(rms0, label='Online')
plt.plot(rms1, label='TD Error Sum')
plt.legend()
plt.show()


plt.figure()
plt.title(f"Value Function (n={n})")
plt.xlabel("State")
plt.ylabel("Value")
plt.xticks(range(size + 1), [chr(ord('A') + i) for i in range(size + 1)])
plt.plot(history0[-1], 'o-', label='Online')
plt.plot(history1[-1], 'x-', label='TD Error Sum')
plt.plot(v_star, 'o--', color='gray', label='Theoretical')
plt.legend()
plt.show()

ns = [2, 4, 8]
alphas = np.linspace(0, 0.5, num=20)
rms10int0 = np.zeros([len(ns), len(alphas)])
rms10int1 = np.zeros([len(ns), len(alphas)])

for i, n in enumerate(ns):
    for j, alpha in enumerate(alphas):
        env.seed(7)
        history0 = td_on_policy_prediction(env, policy, n=n, num_episodes=10,
                                           alpha=alpha, tderr=False)
        rmsint0 = np.sqrt((history0 - v_star) ** 2).mean()
        rms10int0[i, j] = rmsint0

        env.seed(7)
        history1 = td_on_policy_prediction(env, policy, n=n, num_episodes=10,
                                           alpha=alpha, tderr=True)
        rmsint1 = np.sqrt((history1 - v_star) ** 2).mean()
        rms10int1[i, j] = rmsint1



matplotlib.rcParams['figure.figsize'] = [10, 10]

plt.figure()
plt.title(f"RMS Mean")
plt.xlabel(r"$\alpha$"); plt.ylim([0, 0.55])
plt.ylabel("RMS Mean Error Over First 10 Episodes")
for i, n in enumerate(ns):
    plt.plot(alphas, rms10int0[i, :], 'o-', label=f'Online (n={n})')
for i, n in enumerate(ns):
    plt.plot(alphas, rms10int1[i, :], 'x-', label=f'TD Error Sum (n={n})')
plt.legend(loc=0)
plt.show()

图片.png图片.png
图片.png

TD 误差和变体表现更差的原因是在回合期间累积的误差和变得“陈旧”。当在一个情节中频繁地重新访问状态时,例如频繁来回走动的随机游走,TD 误差和版本的更新变得太大,开始受到噪声的影响,最终使学习过程完全不稳定.如果学习率很小或者环境不频繁地重新访问相同的状态,两种方法的价值函数更新非常接近,并且在变得相同的极限内。
例7.1 n步时序差分方法在随机游走上的应用

import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from tqdm import tqdm

# all states
N_STATES = 19

# discount
GAMMA = 1

# all states but terminal states
STATES = np.arange(1, N_STATES + 1)

# start from the middle state
START_STATE = 10

# two terminal states
# an action leading to the left terminal state has reward -1
# an action leading to the right terminal state has reward 1
END_STATES = [0, N_STATES + 1]

# true state value from bellman equation
TRUE_VALUE = np.arange(-20, 22, 2) / 20.0
TRUE_VALUE[0] = TRUE_VALUE[-1] = 0

# n-steps TD method
# @value: values for each state, will be updated
# @n: # of steps
# @alpha: # step size
def temporal_difference(value, n, alpha):
    # initial starting state
    state = START_STATE

    # arrays to store states and rewards for an episode
    # space isn't a major consideration, so I didn't use the mod trick
    states = [state]
    rewards = [0]

    # track the time
    time = 0

    # the length of this episode
    T = float('inf')
    while True:
        # go to next time step
        time += 1

        if time < T:
            # choose an action randomly
            if np.random.binomial(1, 0.5) == 1:
                next_state = state + 1
            else:
                next_state = state - 1

            if next_state == 0:
                reward = -1
            elif next_state == 20:
                reward = 1
            else:
                reward = 0

            # store new state and new reward
            states.append(next_state)
            rewards.append(reward)

            if next_state in END_STATES:
                T = time

        # get the time of the state to update
        update_time = time - n
        if update_time >= 0:
            returns = 0.0
            # calculate corresponding rewards
            for t in range(update_time + 1, min(T, update_time + n) + 1):
                returns += pow(GAMMA, t - update_time - 1) * rewards[t]
            # add state value to the return
            if update_time + n <= T:
                returns += pow(GAMMA, n) * value[states[(update_time + n)]]
            state_to_update = states[update_time]
            # update the state value
            if not state_to_update in END_STATES:
                value[state_to_update] += alpha * (returns - value[state_to_update])
        if update_time == T - 1:
            break
        state = next_state

# Figure 7.2, it will take quite a while
def figure7_2():
    # all possible steps
    steps = np.power(2, np.arange(0, 10))

    # all possible alphas
    alphas = np.arange(0, 1.1, 0.1)

    # each run has 10 episodes
    episodes = 10

    # perform 100 independent runs
    runs = 100

    # track the errors for each (step, alpha) combination
    errors = np.zeros((len(steps), len(alphas)))
    for run in tqdm(range(0, runs)):
        for step_ind, step in enumerate(steps):
            for alpha_ind, alpha in enumerate(alphas):
                # print('run:', run, 'step:', step, 'alpha:', alpha)
                value = np.zeros(N_STATES + 2)
                for ep in range(0, episodes):
                    temporal_difference(value, step, alpha)
                    # calculate the RMS error
                    errors[step_ind, alpha_ind] += np.sqrt(np.sum(np.power(value - TRUE_VALUE, 2)) / N_STATES)
    # take average
    errors /= episodes * runs

    for i in range(0, len(steps)):
        plt.plot(alphas, errors[i, :], label='n = %d' % (steps[i]))
    plt.xlabel('alpha')
    plt.ylabel('RMS error')
    plt.ylim([0.25, 0.55])
    plt.legend()

    plt.savefig('../rl7/images/figure_7_2.png')
    plt.close()

if __name__ == '__main__':
    figure7_2()

图片.png
7.3 使用一个更大的随机游走任务的原因是什么?在小任务上n的不同值的优势会改变吗?

  • 因为我们在使用n步时序差分方法,需要尽可能避免回合过早的结束,如果这是一个小的问题,在过程中得到的不同状态的价值函数数量会比n小,使得性能测试对于较大的n不公平和准确。如果这是一个较小的问题,那么一个相对较小的n会得到更好的性能,若问题规模较大,比如说在随机游走的游戏中有100个状态,那么最好的n肯定也会更大。

7.2 n步Sarsa

使用n步方法来用作控制

n步方法与Sarsa直接结合来产生同轨策略下的时序差分学习控制方法。

  • 核心思想:将状态替换为“状态-动作”二元组,使用epsilon-贪心策略,除了在$S_t,A_t$状态进行更新之外,所有其他状态的价值都保持不变。($t+n \ge T$时,$G_{t:t+n} = G_t$)

$G_{t:t+n} \dot{=} R_{t+1} + \gamma R_{t+2}+…+ \gamma^{n-1}R_{t+n} + \gamma^{n}Q_{t+n-1}(S_{t+n},A_{t+n})$
$Q_{t+n}(S_{t},A_t) \dot{=} Q_{t+n-1}(S_{t},A_t)+\alpha[G_{t:t+n}-Q_{t+n-1}(S_{t},A_t)],\ \ \ \ \ 0\le t <T$

  • 和n步时序差分方法的回溯图类似,由交替出现的状态和动作构成,Sarsa回溯图首末两端都是动作而不是状态。

图片.png

n步Sarsa算法

图片.png
例7.4 同轨策略下的控制:为什么n步Sarsa相较于单步Sarsa能够加速学习?
图片.png表示了智能体在一个回合内的轨迹,其中只有在G点有正收益,其他网格的价值都为0

  • 在1步控制中(即之前所谓的“时序差分学习”),到达G的前1步的“状态-动作”价值得到了增强;
  • 在10步控制中,到达G的前的第10步就可以“感受”到价值的增强。

n步期望Sarsa

  • 期望Sarsa:不去真的采样出一个具体的action,至少在更新的时候没有采样采出来。具体操作是求期望。
  • 在n步之后,对所有可能的动作采用策略$\pi$下的概率进行了加权,$\overline V_{t+n-1}(S_{t+n})$是状态s的期望近似价值

$G_{t:t+n} \dot{=} R_{t+1} + \gamma R_{t+2}+…+ \gamma^{n-1}R_{t+n} + \gamma^{n}\overline V_{t+n-1}(S_{t+n})$
$\overline V_{t}(S) \dot{=} \sum_a \pi(a|s)Q_t(s,a)$,对所有的$s\in S$

  • 回溯图:由采样动作和状态组成的线性串,与n步Sarsa不同的是,它最后一个节点是一个分支。

7.3 n步离轨策略学习(n-step off-policy learning)

离轨策略回顾

  • 行动策略$b$:用于生成行动数据。需要对所有可能的动作采样(软性,更具试探性,如$\epsilon-$贪心策略)
  • 目标策略$\pi$:被评估和改善的策略。策略可以是确定性的(如贪心策略)
  • 重要度采样率:考虑两种策略之间的不同。

n步离轨策略

  • 在n步方法中,回报根据n步来建立($G_{t:t+n}$),我们需要考虑这n步的相对概率,重要度采样率。
  • 简单离轨策略n步时序差分学习:简单使用$\rho_{t:t+n-1}$来加权t+n时刻的更新

$V_{t+n}(S_{t}) \dot{=} V_{t+n-1}(S_{t})+\alpha\rho_{t:t+n-1}[G_{t:t+n}-V_{t+n-1}(S_{t})],\ \ \ \ \ 0\le t <T$
$\rho_{t:h}\dot{=}\prod_{k=t}^{min(h,T-1)} {\pi(A_k|S_k)\over b(A_k|S_k)}$

  • 重要度采样率:衡量两种策略采取$A_t-A_{t+n}$这n个动作的相对概率,假定$\pi$永远都不采取某个动作($\pi(A_k|S_k)=0$),则n步回报的权重应为0;若$\pi$选中某个动作的概率远远大于行动策略,那么将增加对应回报的权重,学习策略的特性。
  • n步Sarsa:可完整地被简单离轨策略代替

$Q_{t+n}(S_{t},A_t) \dot{=} Q_{t+n-1}(S_{t},A_t)+\alpha\rho_{t+1:t+n}[G_{t:t+n}-Q_{t+n-1}(S_{t},A_t)]$

  • *注意:这里的$\rho_{t+1:t+n}$比$\rho_{t:t+n-1}$中要晚一步,因为第一步的动作已经确定了不用策略选择(对应的时间为t+1 ),而最后一步的动作需要行为策略确定(对应的时间为t+n )。

图片.png

带控制变量的每次决策型方法

n步回报

$G_{t:h}=R_{t+1}+\gamma G_ {t+1:h}$

其中

$G_{h:h}=V_{h-1}(S_h)\\ (G_{t:t+n}=R_{t+1}+\gamma R_{t+2}+…+\gamma^{(n-1)} R_{t+n}+\gamma^{n} V_{t+n-1}(S_{t+n}))$

使用更精巧的定义,视界h结束的n步回报的离轨策略版本的定义

$G_{t:h}=\rho_t(R_{t+1}+\gamma G_ {t+1:h})+(1-\rho_t)V_{h-1}(S_t),t<h<T$

  • 如果直接在回报等式右侧加权,在t时刻动作永远不会被$\pi$选择时,则$\rho(t)=\frac{\pi(A_K|S_k)}{b(A_k|S_k)}$为0,直接在回报中加入权重会使步的回报为0,当它被当作目标时可能产生很大的方差。
  • 在该方法中,即使$\rho _t=0$,并不会使得目标为0并导致估计值收缩,而是使目标与估计值一样,不会带来变化。重要度采样率为0意味应忽略样本,让估计值保持不变是合理的结果
  • 添加的项为控制变量,该量并不会改变更新值的期望:重要度采样率的期望为1且与估计值没有关联,控制变量的期望值为0

动作价值中,n步回报的离轨策略

$G_{t:h}=R_{t+1}+\gamma\rho_{t+1}(G_ {t+1:h}-Q_{h-1}(S_{t+1},A_{t+1}))+\gamma \overline V_{h-1}(S_{t+1}),t<h\leq T\\ G_{h:h}=\overline V_{h-1}(S_t)$

  • 终止于视界h的同轨策略下的n步回报(期望Sarsa的递归公式)的递归公式结束于$G_{h:h}=\overline V_{h-1}(S_{h})$
  • 对于动作价值函数,第一个动作确定,在重要度采样中不起作用,重要度采样只会作用在这之后的动作上
  • 如果$h<T$,上述递归公式结束于$G_{h:h}= Q_{h-1}(S_{t+1},A_{t+1})$,如果$h\geq T$,递归公式结束于$G_{T-1:h}=R_T$,由此得到的预测算法是期望sarsa的扩展

练习

image-20220818210703176.png

相比于n步时序差分算法改变部分

$G_{t:h}=\rho_t(R_{t+1}+\gamma G_ {t+1:h})+(1-\rho_t)V_{h-1}(S_t)\\ V_h(S_t)=V_{h-1}(S_t)+\alpha[G_{t:h}-V_{h-1}(S_t)]$

image-20220818221238705.png
image-20220818221254799.png
image-20220818221411232.png

$\begin{aligned} &如果\tau\geq0: \\ &\quad 如果t+1 \geq T :\\

&\qquad G \gets R_T:\\ &\quad 否则:\\ &\qquad G_{t:h}\gets R_{t+1}+\gamma\rho_{t+1}(G_ {t+1:h}-Q_{h-1}(S_{t+1},A_{t+1}))+\gamma \overline V_{h-1}(S_{t+1})\\ &Q(S_t,A_t)\gets Q(S_t,A_t)+\alpha (G-Q(S_t,A_t)) \end{aligned}$


image-20220818221422412.png
TD误差
image-20220818223257981.png
写成TD误差的形式
image-20220818223319323.pngimage-20220818221437333.png
TD误差
image-20220818223818048.png
写成TD误差的形式
image-20220818223825643.png

n步树回溯算法

不使用重要度采样的离轨策略学习方法——树回溯算法

image-20220818190921999.png

沿中心轴向下,图中标注的是三个采样状态和收益,以及两个采样动作。连接在每个状态侧边的都是没有被采样选择的动作,对于最后一个状态,所有动作都被认为是还未被选择的。图顶端的节点的估计价值由沿途所有经过折扣的收益与底部节点的估计价值组合而成的。在树回溯中,目标包含了所有的这些东西再加上每一层悬挂在两侧的动作节点的估计价值。

更精确来说,更新量是由树的叶子节点的动作价值的估计值计算出的。每个叶子节点贡献会被加权,一个一级动作a的贡献权值为$\pi (a|S_{t+1})$,未被选择的二级动作$a’$贡献权值为$\pi(A_{t+1}|S_{t+1})\pi(a’|S_{t+2})$,依此类推。每个指向动作节点的箭头得到了加权,权重为该动作在目标策略中被选择的概率。

单步回报:与期望Sarsa相同,对于$t<T-1$

$G_{t:t+1}=R_{t+1}+\gamma \sum_a \pi(a|S_{t+1})Q_t(S_{t+1},a)$

两步树回溯回报:对于$t<T-2$

$\begin{align} G_{t:t+2}&=R_{t+1}+\gamma \sum_{a\neq A_{t+1}} \pi(a|S_{t+1})Q_{t+1}(S_{t+1},a)+\gamma\pi(A_{t+1}|S_{t+1})(R_{t+2}+\gamma \sum_a \pi(a|S_{t+2})Q_{t+1}(S_{t+2},a))\\&=R_{t+1}+\gamma \sum_{a\neq A_{t+1}} \pi(a|S_{t+1})Q_{t+1}(S_{t+1},a)+\gamma\pi(A_{t+1}|S_{t+1})G_{t+1:t+2} \end{align}$

后者显示了树回溯的n步回报的递归定义的一般形式,对于$t<T-1,n\geq 2$
$\begin{align} &G_{t:t+n}=R_{t+1}+\gamma \sum_{a\neq A_{t+1}} \pi(a|S_{t+1})Q_{t+n-1}(S_{t+1},a)+\gamma\pi(A_{t+1}|S_{t+1})G_{t+1:t+n}\\ &G_{T-1:t+n}=R_T \end{align}$

动作价值函数更新

$Q_{t+n}(S_t,A_t)=Q_{t+n-1}(S_t,A_t)+\alpha[G_{t:t+n}-Q_{t+n-1}(S_t,A_t)]$

image-20220818194949023.pngimage-20220818190921999.png

练习

image-20220818221556033.pngimage-20220818221622684.png

image-20220818223851694.png
例子
image.png
代码

def tree_backup(env, num_episodes, n, alpha, gamma=1.0):
    """
    env: 游戏环境
    num_episodes:总的训练幕数
    alpha: 增量步长
    gamma: 折扣因子
    return:预测的状态价值
    """
    # 初始化存储状态-动作轨迹列表
    state_action_trajectory = np.zeros((n+1, 2), dtype=int)
    # 初始化收益值的轨迹列表
    reward_trajectory = np.zeros(n)
    # 环境总的可选动作数
    nA = env.action_space.n
    # 初始化状态-动作价值表
    Q = defaultdict(lambda: np.zeros(nA))
    # 开始训练迭代
    for each_episode in range(1, num_episodes + 1):
        # 进度显示
        print("Episode {}/{}".format(each_episode, num_episodes), end="\r")
        sys.stdout.flush()
        # 环境重置
        state = env.reset()
        # 设置逐渐递减的epsilon
        epsilon = 1 / each_episode
        # 随机选择动作
        action = np.random.randint(4)
        # 记录初始时刻的状态-动作
        state_action_trajectory[0] = [state, action]
        # 初始化采样时间
        t = 0
        T = float('inf')
        while True:
            # 若非幕终止则继续动作
            if t < T:
                # 执行动作
                next_state, reward, done, info = env.step(action)
                reward_trajectory[np.mod(t, n)] = reward
                # 如果下个时刻幕终止则记录终止时刻与状态
                if done:
                    T = t + 1
                    next_action = action
                # 否则记录下状态-动作二元组
                else:
                    next_action = np.random.randint(4)
                    action = next_action
                state_action_trajectory[np.mod(t + 1, n + 1)] = [next_state, next_action]
            # 定义更新时刻
            tau = t - n + 1
            # 最开始的n-1个时刻后开始更新,
            if tau >= 0:
                # 记录下一采样时刻的回报估计值
                if t+1 >= T:
                    returns = reward_trajectory[np.mod(T-1, n)]
                else:
                    next_state_value = sum(greedy_pro(Q, next_state, act, nA, epsilon) * Q[next_state][act] for act in range(4))
                    returns = reward_trajectory[np.mod(t, n)] + gamma * next_state_value
                for k in range(min(t, T-1), tau+1, -1):
                    state_k = state_action_trajectory[np.mod(k, n + 1), 0]
                    action_k = state_action_trajectory[np.mod(k, n + 1), 1]
                    next_state_value_part = sum(greedy_pro(Q, state_k, act, nA, epsilon) * Q[state_k][act] if act!=action_k else 0 for act in range(4))
                    returns = reward_trajectory[np.mod(k-1, n)] + gamma * next_state_value_part + gamma * greedy_pro(Q, state_k, action_k, nA, epsilon) * returns
                state_tau = state_action_trajectory[np.mod(tau, n + 1), 0]
                action_tau = state_action_trajectory[np.mod(tau, n + 1), 1]
                Q[state_tau][action_tau] += alpha * (returns - Q[state_tau][action_tau])
            if tau == T - 1:
                break
            t += 1
    return Q

n=4时
image.png
n=2时
image.png
n=1时
image.png

n步$Q(\sigma)$

统一框架的思想:对状态逐个决定是否要采取采样操作,即依据分布选取某个动作作为样本 (Sarsa 算法的情况) ,或考虑所有可能动作的期望(树回溯算法的情况) 如果一个人总是用采样操作,他就会得Sarsa 算法;反之,如果从不采样,就会得到树回溯算法。期望 Sarsa 算法是在最后步之前进行采样
image-20220818195039574.png
考虑采样和期望之间的连续变化,令$\sigma_t \in[0,1]$表示在步骤$t$时采样的程度,$\sigma=1$表示完整采样,$\sigma=0$表示求期望而不进行采样

使用$h=t+n$时的树回溯算法的n步回报,然后将其用期望近似价值$\overline V$表达

$\begin{aligned} G_{t:h}&=R_{t+1}+\gamma \sum_{a\neq A_{t+1}} \pi(a|S_{t+1})Q_{h-1}(S_{t+1},a)+\gamma\pi(A_{t+1}|S_{t+1})G_{t+1:t+n}\\ &=R_{t+1}+\gamma \overline V_{h-1}-\gamma \pi(A_{t+1}|S_{t+1})Q_{h-1}(S_{t+1},A_{t+1})+\gamma \pi(A_{t+1}|S_{t+1})G_{t+1:h}\\ &=R_{t+1}+\gamma \pi(A_{t+1}|S_{t+1})(G_{t+1:h}-Q_{h-1}(S_{t+1},A_{t+1}))+\gamma \overline V_{h-1} \end{aligned}$

与带控制变量的n步Sarsa回报形式相似,不同的是使用动作概率$\pi(A_{t+1}|S_{t+1})$取代重要度采样比$\rho_{t+1}$

$G_{t:h}=R_{t+1}+\gamma (\sigma_{t+1}\rho _{t+1}+(1-\sigma_{t+1})\pi(A_{t+1}|S_{t+1}))(G_{t+1:h}-Q_{h-1}(S_{t+1},A_{t+1}))+\gamma \overline V_{h-1}$

上面递归公式终止条件为:

  • $h<T$时,$G_{h:h}=Q_{h-1}(S_h,A_h)$
  • $h=T$时,$G_{T-1:T}=R_T$

image-20220818210357986.png
image-20220818210420787.png

参考链接

https://zhuanlan.zhihu.com/p/350651471
https://zhuanlan.zhihu.com/p/328072447
https://github.com/vojtamolda/reinforcement-learning-an-introduction
https://github.com/ShangtongZhang/reinforcement-learning-an-introduction
https://github.com/LyWangPX/Reinforcement-Learning-2nd-Edition-by-Sutton-Exercise-Solutions
https://blog.csdn.net/qq_56937808/article/details/122008501