上一章与本章衔接的一个习题:无偏估计与有偏估计
方差越来越大:
它这里的第n步不是指更新了n次,而指的是,从现在这个(s,a)开始,往后取n个(r,s,a)数值对,用那n个的(r,s,a)数值对更新现在这个(s,a)的价值,n取得越大,越接近于monte carlo法。
monte carlo法是一种无偏估计,也就是它是正儿八经去采样了,去执行了后面的状态和动作,用这些执行后的结果来更新,这些结果是真实数据,没有利用统计方法或其他方法进行数值平滑化,因此无偏估计的方差,相比起加入了数学方法的有偏估计的方差,会大一点。
因此一言以蔽之,n越大,越接近monte carlo法,越接近无偏估计,方差越大。
【因为方差描述的是预测值的变化范围、离散程度,而无偏估计更贴近直接采样,不确定性更大一些,因此离散程度会更高,距离根据无偏估计算出来的期望值的距离之和也会更大,因此方差更大】
蒙特卡洛
直接把N(St)/1 变成 α (学习率)【超参数】,α 代表着更新的速率有多快,我们可以进行设置。
在每个回合中,如果在时间步 t 状态 s 被访问了,那状态 s 的访问数 N(s) 增加 1。 已经把一条轨迹跑完了,可以算每个状态实际的 return。没用 bootstrapping,因为它是根据实际的 return 来更新。
TD
TD(0)
,每往前走一步,就做一步 bootstrapping,用得到的估计回报(estimated return)来更新上一时刻的值。【自举】一步一更新。
之前是只往前走一步,即 one-step TD,TD(0)。可以调整步数,变成 n-step TD
。比如 TD(2)
,即往前走两步,然后利用两步得到的 return,使用 bootstrapping 来更新状态的价值。
model-free control
对于 未知 奖励R 和 状态转移P,无法使用 动态规划 (贝尔曼最优方程),所以 采用 Monte Carlo 的方式 去 估计 Q函数。
为了确保 MC 方法能够有足够的探索,我们使用了ε-greedy 【超参数】exploration。策略会不断改变(epsilon ε 会不断变小)
对比之前的 greedy ,增加 了 探索 到别的策略的可能性
Q_learning 和 SARSA
- Sarsa 是 Rt+1+γQ(St+1,At+1) ;只有一个策略 policy π ,既用于 与环境交互 ,又用来 利用 ,要用到的 At+1 是下一步 step 一定 会去执行的 action 。 所以 在探索时 会相对保守
- Q-learning 是Rt+1+γmaxaQ(St+1,a) 。有两个策略 ,一个 behavior policy μ 可以是一个随机的 policy ,是一个 勇士 会进行 各种尝试 ,更新 Q-table ,另一个Target policy π 直接在 Q-table 上取 greedy ,选择最优的那一个 。
代码实践
import math
import gym
import numpy as np
import turtle
from collections import defaultdict
from matplotlib import pyplot as plt
import seaborn as sns
class QLearning(object):
def __init__(self, n_states, n_actions, cfg):
self.n_actions = n_actions
self.lr = cfg.lr # 学习率
self.gamma = cfg.gamma
self.epsilon = 0
self.sample_count = 0
self.epsilon_start = cfg.epsilon_start
self.epsilon_end = cfg.epsilon_end
self.epsilon_delay = cfg.epsilon_delay
self.Q_table = defaultdict(lambda: np.zeros(n_actions)) # 用嵌套字典存放状态->动作->状态-动作值(Q值)的映射,即Q表
def choose_action(self, state):
self.sample_count += 1
self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \
math.exp(-1. * self.sample_count / self.epsilon_delay) # epsilon是会递减的,这里选择指数递减
# e-greedy 策略
if np.random.uniform(0, 1) > self.epsilon:
action = np.argmax(self.Q_table[str(state)]) # 选择Q(s,a)最大对应的动作
else:
action = np.random.choice(self.n_actions) # 随机选择动作
return action
def predict(self,state):
action = np.argmax(self.Q_table[str(state)])
return action
def update(self, state, action, reward, next_state, done):
Q_predict = self.Q_table[str(state)][action]
if done: # 终止状态
Q_target = reward
else:
Q_target = reward + self.gamma * np.max(self.Q_table[str(next_state)])
self.Q_table[str(state)][action] += self.lr * (Q_target - Q_predict)
class Config():
def __init__(self):
self.lr = 0.01
self.gamma = 0.9
self.epsilon_start = 0.95
self.epsilon_end = 0.01
self.epsilon_delay = 300
self.train_episode = 50
def train(cfg,env,agent): # 与环境交互
print('开始训练!')
rewards = [] # 记录奖励
for i_ep in range(cfg.train_episode):
ep_reward = 0 # 记录每个回合的奖励
state = env.reset() # 重置环境,即开始新的回合
while True:
action = agent.choose_action(state) # 根据算法选择一个动作
next_state, reward, done, _ = env.step(action) # 与环境进行一次动作交互
agent.update(state, action, reward, next_state, done) # Q学习算法更新
state = next_state # 更新状态
ep_reward += reward
if done:
break
rewards.append(ep_reward)
print("回合数:{}/{},奖励{:.1f}".format(i_ep+1, cfg.train_episode,ep_reward))
print('完成训练!')
return rewards
env = gym.make('CliffWalking-v0') ## *定义环境
#env = CliffWalkingWapper(env) ### !!! 2. 定义动作导致状态变化。。修饰环境。。
states = env.observation_space.n
actions = env.action_space.n
cfg = Config()
agent = QLearning(states, actions, cfg)
# print(f"state_number:{states},action_number:{actions}")
env.seed(10)
reward = train(cfg,env,agent)
plt.figure()
plt.plot(reward)
# plt.legend()
plt.show()
env.close()
注::(提醒自己!!)
- += 是简写,a += 1就是a = a+1
- =+并不是简写,a =+ a直接对a的赋值,±符号代表的是正负(完全可以省略不写),即a =+ b其实就是a = b。