以下为阅读《强化学习:原理与python实现》这本书第八章的学习笔记。
本章介绍带自益的策略梯度算法,这类算法将策略梯度和自益结合起来:一方面,用一个含参函数近似价值函数,利用这个近似值来估计回报值;另一方面,利用估计得到的回报值估计策略梯度,进而更新策略参数。这两方面常常被称为评论者(critic)和执行者(actor)。所以带自益的策略梯度算法称为执行者/评论者算法。
执行者/评论者算法用含参函数表示偏好,用其softmax运算的结果
来近似最优策略。在更新参数
时,也是根据策略梯度定理,取
为梯度方向迭代更新。
可以是以下几种形式:
(动作价值)
(优势函数)
(时序差分)
我们可以用含参函数或
来近似
动作价值同策执行者/评论者算法
1.(初始化)任意值,
任意值
2.(带自益的策略更新)对每个回合执行以下操作:
2.1(初始化累计折扣)
2.2(决定初始动作)用得到动作A
2.3 如果回合未结束,执行以下操作:
2.3.1(采样)根据状态S和动作A得到采样R和下一状态S'
2.3.2(执行)用得到动作A'
2.3.3(估计回报)
2.3.4(策略改进)更新以减小
如
2.3.5(更新价值)更新w以减小 如
2.3.6(更新累计折扣)
2.3.7(更新状态)
优势执行者/评论者算法
在基本执行者/评论者算法中引入基线函数,就会得到
,其中,
是优势函数的估计,从而得到了优势执行者/评论者算法。不过,为了避免需要搭建两个函数分别表示q(w)和v(w),我们可以采用
做目标,这样优势函数的估计就变为单步时序差分的形式
,算法如下:
输入:环境(无数学描述)
输出:最优策略的估计
参数:优化器(隐含学习率),折扣因子
,控制回合数和回合内步数的参数
1.(初始化)任意值,
任意值
2.(带自益的策略更新)对每个回合执行以下操作
2.1(初始化累积折扣)
2.2(决定初始动作)用得到动作A
2.3 如果回合未结束,执行以下操作:
2.3.1(采样)根据状态S和动作A得到采样R和下一状态S'
2.3.2(执行)用得到动作A'
2.3.3(估计回报)
2.3.4(策略改进)更新以减小
,如
2.3.5(更新价值)更新w以减小,如
2.3.6(更新累积折扣)
2.3.7(更新状态)
其他算法
在书中还提到了其他算法,例如基于代理优势的同策算法,信任域算法,柔性执行者/评论者算法等,这些算法都比较复杂,在此略过,感兴趣的朋友可以详细阅读这本书。
双节倒立摆案例
以Gym库中的双节倒立摆(Acrobot-v1)作为案例。可以看到有两根杆子首尾相接,一端固定在原点,另一端在二维垂直面上活动。可以在两个杆子的连接处施加动作{0,1,2},每过一步,惩罚奖励值-1。当活动端超过一个固定高度时,或者回合达到500步,回合结束。我们希望回合数尽量少。
以下视频是以随机的动作执行的情况:
Acrobot_random
以下是优势执行者/评论者算法的代码,经过测试,优势执行者/评论者算法比简单的同策执行者/评论者算法性能更好,训练所花费的时间也更少。
import gym
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
import tensorflow.keras as keras
from keras.initializers import GlorotUniform
import tqdm
%matplotlib notebook
from IPython import display
env = gym.make("Acrobot-v1")
class Chart:
def __init__(self):
self.fig, self.ax = plt.subplots(1, 1)
def plot(self, episode_rewards):
self.ax.clear()
self.ax.plot(episode_rewards)
self.ax.set_xlabel('iteration')
self.ax.set_ylabel('episode reward')
self.fig.canvas.draw()
class AdvantageActorCriticAgent:
def __init__(self, env, actor_kwargs, critic_kwargs, gamma=0.99):
self.action_n = env.action_space.n
self.gamma = gamma
self.discount = 1.
self.actor_net = self.build_network(output_size=self.action_n,
output_activation=tf.nn.softmax,
loss=tf.losses.categorical_crossentropy,
**actor_kwargs)
self.critic_net = self.build_network(output_size=1,
**critic_kwargs)
def build_network(self, hidden_sizes, output_size, input_size=None,
activation=tf.nn.relu, output_activation=None,
loss=tf.losses.mse, learning_rate=0.01):
model = keras.Sequential()
for idx, hidden_size in enumerate(hidden_sizes):
kwargs = {}
if idx == 0 and input_size is not None:
kwargs['input_shape'] = (input_size,)
model.add(keras.layers.Dense(units=hidden_size,
activation=activation, **kwargs))
model.add(keras.layers.Dense(units=output_size,
activation=output_activation))
optimizer = tf.optimizers.Adam(learning_rate)
model.compile(optimizer=optimizer, loss=loss)
return model
def decide(self, observation):
probs = self.actor_net.predict(observation[np.newaxis])[0]
action = np.random.choice(self.action_n, p=probs)
return action
def learn(self, observation, action, reward, next_observation, done):
x = observation[np.newaxis]
u = reward + (1. - done) * self.gamma * \
self.critic_net.predict(next_observation[np.newaxis])
td_error = u - self.critic_net.predict(x)
# 训练执行者网络
x_tensor = tf.convert_to_tensor(observation[np.newaxis],
dtype=tf.float32)
with tf.GradientTape() as tape:
pi_tensor = self.actor_net(x_tensor)[0, action]
logpi_tensor = tf.math.log(tf.clip_by_value(pi_tensor,
1e-6, 1.))
loss_tensor = -self.discount * td_error * logpi_tensor
grad_tensors = tape.gradient(loss_tensor, self.actor_net.variables)
self.actor_net.optimizer.apply_gradients(zip(
grad_tensors, self.actor_net.variables)) # 更新执行者网络
# 训练评论者网络
self.critic_net.fit(x, u, verbose=0) # 更新评论者网络
if done:
self.discount = 1. # 为下一回合初始化累积折扣
else:
self.discount *= self.gamma # 进一步累积折扣\
def play_qlearning(env, agent, train=False, render=False):
episode_reward = 0
observation = env.reset()
step = 0
while True:
if render:
env.render()
action = agent.decide(observation)
next_observation, reward, done, _ = env.step(action)
episode_reward += reward
if train:
agent.learn(observation, action, reward, next_observation,
done)
if done:
break
step += 1
observation = next_observation
return episode_reward
actor_kwargs = {'hidden_sizes' : [100,], 'learning_rate' : 0.0001}
critic_kwargs = {'hidden_sizes' : [100,], 'learning_rate' : 0.0002}
agent = AdvantageActorCriticAgent(env, actor_kwargs=actor_kwargs,
critic_kwargs=critic_kwargs)
# 训练
episodes = 100
episode_rewards = []
chart = Chart()
for episode in tqdm.trange(episodes):
episode_reward = play_qlearning(env, agent, train=True)
episode_rewards.append(episode_reward)
chart.plot(episode_rewards)
训练过程中的回合回报如下:
训练完成后,我们可以测试一下,如以下视频:
Acrobot_train