DeepMind公开课https://sites.google.com/view/deep-rl-bootcamp/lectures
David Silver教程 http://www0.cs.ucl.ac.uk/staff/d.silver/web/Teaching.html
morvanzhou课程https://morvanzhou.github.io/tutorials/machine-learning/reinforcement-learning/
【强化学习】Policy Gradient算法详解
1、算法思想
之前的QLearning DQN Sarsa都是基于值迭代的算法,我们是在确定了价值函数的基础上采用某种策略(贪婪-epsilon)的方式去选取动作,而本文所说的PolicyGradient是以策略迭代为基础的强化学习算法。
Policy Gradient不通过误差反向传播,它通过观测信息选出一个行为直接进行反向传播,当然出人意料的是他并没有误差,而是利用reward奖励直接对选择行为的可能性进行增强和减弱,好的行为会被增加下一次被选中的概率,不好的行为会被减弱下次被选中的概率。
优点:
- 连续的动作空间(或者高维空间)中更加高效;
- 可以实现随机化的策略;
- 某种情况下,价值函数可能比较难以计算,而策略函数较容易。
缺点:
- 通常收敛到局部最优而非全局最优
- 评估一个策略通常低效(这个过程可能慢,但是具有更高的可变性,其中也会出现很多并不有效的尝试,而且方差高
2、公式推导
在本篇文章将会使用参数θ逼近拟合状态值函数
和状态动作值函数
可以理解为我们使用比如神经网络的的function来逼近拟合状态值函数
和状态动作值函数
的分布。
并且将策略Policy参数化为
,使用model-free的方法,不借助与agent做决策而是将agent丢入不确定的动态环境下,不提供动态环境的信息,让agent自己瞎溜达根据所获得的策略信息更新参数。
通常情况下目标策略有三种方式
- 使用初始价值来判断:
- 使用平均价值:
- 使用每次time-step的平均奖励:
其中
是策略
的马尔科夫链的平稳分布(也就是当应用策略
得到收敛的分布)。
通常使用梯度下降法通过最大化J(θ)来确定θ的取值。定义策略梯度为:
假设策略 为零的时候可微,并且已知梯度 ,应用似然比(likelihood ratio)的技巧定义 为得分函数(score function)。二者关系如下:
在连续策略上选用Gaussian Policy在离散策略下采用softmax Policy
策略梯度定理(The policy gradient theorem)
假设一个只有一步的MDP,对它使用策略梯度下降。
表示关于参数θ的函数,映射是
。它在状态s向前走一步,获得奖励
。那么选择行动a的奖励为
,在状态 s 的加权回报为
,应用策略所能获得的奖励期望为:
梯度为:
根据上文d为
下的平稳分布(个人将其当作概率来理解)所以有
因此可得
然后再将似然率方式的策略梯度方法应用到多步MDPs上,此时使用
代替奖励值r,对于任意可微的策略梯度如下(策略价值计算公式):
蒙特卡洛梯度策略强化算法Monte-Carlo Policy Gradient
(不带基数)蒙特卡洛策略梯度通过情节采样,使用随机梯度上升法更新参数,使用策略梯度法,返回 作为
所以可得更新公式
算法伪代码如下
带基数的蒙特卡洛梯度策略强化算法ERINFORCE with baseline
当带基数的时候就要考虑 b 的问题
则原式需要修改为
3、算法实现
class PolicyGradient:
def __init__(self, n_actions, n_features, learning_rate=0.01, reward_decay=0.95, output_graph=True):
self.n_actions = n_actions
self.n_features = n_features
self.lr = learning_rate # 学习率
self.gamma = reward_decay # reward 递减率
self.ep_obs, self.ep_as, self.ep_rs = [], [], [] # 这是我们存储 回合信息的 list
self._build_net() # 建立 policy 神经网络
self.sess = tf.Session()
if output_graph: # 是否输出 tensorboard 文件
# $ tensorboard --logdir=logs
# http://0.0.0.0:6006/
# tf.train.SummaryWriter soon be deprecated, use following
tf.summary.FileWriter(r'D:\logs', self.sess.graph)
self.sess.run(tf.global_variables_initializer())
def _build_net(self):
with tf.name_scope('inputs'):
self.tf_obs = tf.placeholder(tf.float32, [None, self.n_features], name="observations") # 接收 observation
self.tf_acts = tf.placeholder(tf.int32, [None, ], name="actions_num") # 接收我们在这个回合中选过的 actions
self.tf_vt = tf.placeholder(tf.float32, [None, ],
name="actions_value") # 接收每个 state-action 所对应的 value (通过 reward 计算)
# fc1
layer = tf.layers.dense(
inputs=self.tf_obs,
units=10, # 输出个数
activation=tf.nn.tanh, # 激励函数
kernel_initializer=tf.random_normal_initializer(mean=0, stddev=0.3),
bias_initializer=tf.constant_initializer(0.1),
name='fc1'
)
# fc2
all_act = tf.layers.dense(
inputs=layer,
units=self.n_actions, # 输出个数
activation=None, # 之后再加 Softmax
kernel_initializer=tf.random_normal_initializer(mean=0, stddev=0.3),
bias_initializer=tf.constant_initializer(0.1),
name='fc2'
)
self.all_act_prob = tf.nn.softmax(all_act, name='act_prob') # 激励函数 softmax 出概率
with tf.name_scope('loss'):
# 最大化 总体 reward (log_p * R) 就是在最小化 -(log_p * R), 而 tf 的功能里只有最小化 loss
neg_log_prob = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=all_act,
labels=self.tf_acts) # 所选 action 的概率 -log 值
# 下面的方式是一样的:
# neg_log_prob = tf.reduce_sum(-tf.log(self.all_act_prob)*tf.one_hot(self.tf_acts, self.n_actions), axis=1)
loss = tf.reduce_mean(neg_log_prob * self.tf_vt) # (vt = 本reward + 衰减的未来reward) 引导参数的梯度下降
with tf.name_scope('train'):
self.train_op = tf.train.AdamOptimizer(self.lr).minimize(loss)
def choose_action(self, observation):
prob_weights = self.sess.run(self.all_act_prob, feed_dict={self.tf_obs: observation[np.newaxis, :]}) # 所有 action 的概率
action = np.random.choice(range(prob_weights.shape[1]), p=prob_weights.ravel()) # 根据概率来选 action
return action
def store_transition(self, s, a, r):
self.ep_obs.append(s)
self.ep_as.append(a)
self.ep_rs.append(r)
def learn(self):
# 衰减, 并标准化这回合的 reward
discounted_ep_rs_norm = self._discount_and_norm_rewards() # 功能再面
# train on episode
self.sess.run(self.train_op, feed_dict={
self.tf_obs: np.vstack(self.ep_obs), # shape=[None, n_obs]
self.tf_acts: np.array(self.ep_as), # shape=[None, ]
self.tf_vt: discounted_ep_rs_norm, # shape=[None, ]
})
self.ep_obs, self.ep_as, self.ep_rs = [], [], [] # 清空回合 data
return discounted_ep_rs_norm # 返回这一回合的 state-action value
def _discount_and_norm_rewards(self):
# discount episode rewards
discounted_ep_rs = np.zeros_like(self.ep_rs)
running_add = 0
for t in reversed(range(0, len(self.ep_rs))):
running_add = running_add * self.gamma + self.ep_rs[t]
discounted_ep_rs[t] = running_add
# normalize episode rewards
discounted_ep_rs -= np.mean(discounted_ep_rs)
discounted_ep_rs /= np.std(discounted_ep_rs)
return discounted_ep_rs