本专栏按照 https://lilianweng.github.io/lil-log/2018/04/08/policy-gradient-algorithms.html 顺序进行总结 。
AC算法框架被广泛应用于实际强化学习算法中,该框架集成了值函数估计算法和策略搜索算法,是解决实际问题时最常考虑的框架。
AC算法起源于策略梯度算法,因此在介绍AC算法时,我们先从策略梯度入手。(其实上篇已经介绍过了,这里再加赘述一下)
随机策略梯度的计算公式为:
利用经验平均估计策略的梯度:
下面对上式进行直观上的解释:
Actor-Critic从名字上看包括两部分,演员(Actor)和评价者(Critic)。其中:
从策略梯度的直观解释我们可以看到,轨迹回报 R ( τ ) R(\tau) R(τ) 就像是一个评价器(Critic),该评价器(Critic)评价参数更新后,该轨迹出现的概率应该变大还是变小。如果变大,应该变大多少;如果减小,应该减小多少。也就是说,策略的参数调整幅度由轨迹回报 [公式] 进行评价。可以将 R ( τ ) R(\tau) R(τ) 进行推广而不影响策略梯度大小的计算。根据Shulman的博士论文,在保持策略梯度不变的情况下,策略梯度可写为:
Ψ t Ψ_t Ψt 可以是下列任何一个:
1—3:直接应用轨迹的回报累积回报,由此计算出来的策略梯度不存在偏差,但是由于需要累积多步的回报,因此方差会很大。
4—7: 利用动作值函数,优势函数和TD偏差代替累积回报,其优点是方差小,但是这三种方法中都用到了逼近方法,因此计算出来的策略梯度都存在偏差。这三种方法以牺牲偏差来换取小的方差。当 Ψ t Ψ_t Ψt 取4—6时,为经典的AC方法。
在式(1.3)中, π θ ( a ∣ s ) \pi_\theta(a|s) πθ(a∣s) 为Actor, Ψ t Ψ_t Ψt 称为Critic,因此(1.3)式是一个广义的AC框架。
Actor为策略函数,经常用神经网络来表示,因此称为策略网络。
Critic为评价函数,对于大部分问题, Ψ t Ψ_t Ψt 也常常用神经网络进行逼近, ω ω ω 它的参数常用表示,因此Critic又称为评价网络。
当 Ψ t Ψ_t Ψt 取TD残差,并且值函数 V π ( s t ) V^{\pi}(s_{t}) Vπ(st) 由参数为 ω ω ω 的神经网络进行逼近时。AC算法的更新步骤为:
为了充分利用ac方法可以减小策略梯度的方差,同时弥补普通的ac算法中策略梯度存在较大偏差的缺点,Shulman在博士论文中提出一种GAE的方法。
GAE的方法是对优势函数进行估计。优势函数的定义为:
一般来说, Ψ t Ψ_t Ψt 取优势函数时,比取值函数 Q ( s t , a t ) Q(s_t,a_t) Q(st,at) 时计算得到的策略梯度,方差要小,收敛速度要快。
对于这个结论,我们可以从两个方面去理解:
然而,根据优势函数定义,优势函数中的值函数常常利用逼近算法近似计算,因此往往会引入偏差。
优势函数的一步估计可写为:
从优势函数的一步估计中我们看到, V ( s t ) V(s_t) V(st) 和 V ( s t + 1 ) V(s_{t+1}) V(st+1) 的真实值都是未知的,而是用到了估计值,因此优势函数存在偏差。
GAE的方法是改进对优势函数的估计,将偏差控制到一定的范围内。其方法是对优势函数进行多步估计,并将这些多步估计利用衰减因子进行组合。具体是这样做的:
优势函数的2步估计及无穷步估计分别为:
从上面的公式我们看到, k k k 越大,产生偏差的项 γ k V ( s t + k ) \gamma^kV(s_{t+k}) γkV(st+k) 越小,因此优势函数的估计偏差越小。但,相应的方差也会变大。
Shulman 提出广义优势函数估计 G A E ( γ , λ ) GAE(\gamma, \lambda) GAE(γ,λ),利用指数加权平均从1步到无穷步的优势函数估计,即:
GAE算法可以从回报shapping的角度进行理解和解释。
在回报shapping中,定义转换回报函数为:
令 Φ = V \Phi = V Φ=V ,则转换回报函数的累积回报可写为:
也就是说,广义优势函数可以看成是回报为转换回报,折扣因子为 γ λ \gamma\lambda γλ 的折扣累积回报。因此, A ^ t G A E ( γ , λ ) \hat A_t^{GAE(\gamma,\lambda)} A^tGAE(γ,λ) 很好地平衡了偏差和方差。讲了那么多,其实是为了说明,利用代替AC方法中的Critic会产生更好的效果。
GAE通过利用广义优势函数来平衡Critic的偏差和方差;
给一个Actor-Critic算法的流程总结,评估点基于TD误差,Critic使用神经网络来计算TD误差并更新网络参数,Actor也使用神经网络来更新网络参数:
算法输入:迭代轮数 T
,状态特征维度 n
, 动作集A
, 步长α,β
,衰减因子γ
, 探索率ϵ
, Critic网络结构和Actor网络结构。
输出:Actor 网络参数θ
, Critic网络参数w
Q
S
为当前状态序列的第一个状态, 拿到其特征向量 ϕ(S)
ϕ(S)
作为输入,输出动作 A
,基于动作 A
得到新的状态 S′
,反馈 R
。ϕ(S),ϕ(S′)
作为输入,得到Q
值输出 V(S),V(S′)
δ=R+γV(S′)−V(S)
w
的梯度更新θ
: θ = θ + α ∇ θ l o g π θ ( S t , A ) δ θ=θ+α∇_θlogπ_θ(S_t,A)δ θ=θ+α∇θlogπθ(St,A)δ对于Actor的分值函数 ∇ θ l o g π θ ( S t , A ) ∇_θlogπ_θ(S_t,A) ∇θlogπθ(St,A), 可以选择softmax或者高斯分值函数。
上述Actor-Critic算法已经是一个很好的算法框架,但是离实际应用还比较远。主要原因是这里有两个神经网络,都需要梯度更新,而且互相依赖,难收敛。但是了解这个算法过程后,其他基于Actor-Critic的算法就好理解了。
目前改进的比较好的有两个经典算法,一个是DDPG算法,使用了双Actor神经网络和双Critic神经网络的方法来改善收敛性。这个方法我们在从DQN到Nature DQN的过程中已经用过一次了。另一个是A3C算法,使用了多线程的方式,一个主线程负责更新Actor和Critic的参数,多个辅线程负责分别和环境交互,得到梯度更新值,汇总更新主线程的参数。而所有的辅线程会定期从主线程更新网络参数。这些辅线程起到了类似DQN中经验回放的作用,但是效果更好。
以上解析来自于:https://www.cnblogs.com/pinard/p/10272023.html
import numpy as np
import tensorflow as tf
import gym
import pandas as pd
import pdb
OUTPUT_GRAPH = False
MAX_EPISODE = 500
DISPLAY_REWARD_THRESHOLD = 200 # renders environment if total episode reward is greater then this threshold
MAX_EP_STEPS = 2000 # maximum time step in one episode
RENDER = False # rendering wastes time
GAMMA = 0.9 # reward discount in TD error
LR_A = 0.001 # learning rate for actor
LR_C = 0.001 # learning rate for critic
class Actor(object):
def __init__(self, sess, n_features, n_actions, lr=0.001):
self.sess = sess
self.s = tf.placeholder(tf.float32, [1, n_features], "state")
self.a = tf.placeholder(tf.int32, None, "action")
self.q = tf.placeholder(tf.float32, None, "q") # TD_error
with tf.variable_scope('Actor'):
l1 = tf.layers.dense(
inputs=self.s,
units=20, # number of hidden units
activation=tf.nn.relu,
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='l1'
)
self.acts_prob = tf.layers.dense(
inputs=l1,
units=n_actions, # output units
activation=tf.nn.softmax, # get action probabilities
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='acts_prob'
)
with tf.variable_scope('exp_v'):
pdb.set_trace()
log_prob = tf.log(self.acts_prob[0, self.a]) #
self.exp_v = tf.reduce_mean(log_prob * self.q) # advantage (TD_error) guided loss
with tf.variable_scope('train'):
self.train_op = tf.train.AdamOptimizer(lr).minimize(-self.exp_v) # minimize(-exp_v) = maximize(exp_v)
def learn(self, s, a, q):
# s: array([ 0.03073904, 0.00145001, -0.03088818, -0.03131252]) # shape:(4,)
# a: 0 ; q: array([[0.11351492]], dtype=float32) # shape:(1,1)
s = s[np.newaxis, :]
# s: array([[ 0.03073904, 0.00145001, -0.03088818, -0.03131252]]) # shape:(1,4)
feed_dict = {self.s: s, self.a: a, self.q: q}
_, exp_v = self.sess.run([self.train_op, self.exp_v], feed_dict)
return exp_v # -0.081048675
def choose_action(self, s):
# s: array([ 0.03073904, 0.00145001, -0.03088818, -0.03131252]) # shape:(4,)
s = s[np.newaxis, :]
# s: array([[ 0.03073904, 0.00145001, -0.03088818, -0.03131252]]) # shape:(1,4)
probs = self.sess.run(self.acts_prob, {self.s: s}) # get probabilities for all actions
# array([[0.48727563, 0.51272434]], dtype=float32) # shape:(1, 2)
return np.random.choice(np.arange(probs.shape[1]), p=probs.ravel()) # 表示从[0,1]中依概率p=[0.48, 0.52] 随机选择0或者 1 ;# return a int # 返回值是 0 或者 1
class Critic(object):
def __init__(self, sess, n_features, n_actions, lr=0.01):
self.sess = sess
self.s = tf.placeholder(tf.float32, [None, n_features], "state")
self.a = tf.placeholder(tf.int32,[None, 1],"action")
self.r = tf.placeholder(tf.float32, None, 'r')
self.q_ = tf.placeholder(tf.float32,[None,1],'q_next')
self.a_onehot = tf.one_hot(self.a, n_actions, dtype=tf.float32)
self.a_onehot = tf.squeeze(self.a_onehot,axis=1)
self.input = tf.concat([self.s, self.a_onehot],axis=1)
with tf.variable_scope('Critic'):
l1 = tf.layers.dense(
inputs=self.input,
units=20, # number of hidden units
activation=tf.nn.relu, # None
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='l1'
)
self.q = tf.layers.dense(
inputs=l1,
units=1, # output units
activation=None,
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='Q'
)
with tf.variable_scope('squared_TD_error'):
self.td_error = self.r + GAMMA * self.q_ - self.q
self.loss = tf.square(self.td_error) # TD_error = (r+gamma*V_next) - V_eval
with tf.variable_scope('train'):
self.train_op = tf.train.AdamOptimizer(lr).minimize(self.loss)
def learn(self, s, a, r, s_): # s:(4,) a:1 r:1.0 s_:(4,)
s, s_ = s[np.newaxis, :], s_[np.newaxis, :] # 均为(1,4)
next_a = [[i] for i in range(N_A)] # N_A=2
s_ = np.tile(s_, [N_A, 1]) # (2,4) tile 复制N_A份
q_ = self.sess.run(self.q, {self.s: s_, self.a: next_a})
q_ = np.max(q_, axis=0, keepdims=True)
# pdb.set_trace()
q, _ = self.sess.run([self.q, self.train_op],
{self.s: s, self.q_: q_, self.r: r, self.a: [[a]]})
return q
# action有两个,即向左或向右移动小车
# state是四维
env = gym.make('CartPole-v0')
env.seed(1) # reproducible
env = env.unwrapped
N_F = env.observation_space.shape[0]
N_A = env.action_space.n
sess = tf.Session()
actor = Actor(sess, n_features=N_F, n_actions=N_A, lr=LR_A)
critic = Critic(sess, n_features=N_F, n_actions=N_A, lr=LR_C)
sess.run(tf.global_variables_initializer())
res = []
for i_episode in range(MAX_EPISODE):
s = env.reset() # 初始状态
t = 0
track_r = []
while True:
if RENDER: env.render()
a = actor.choose_action(s)
s_, r, done, info = env.step(a)
if done: r = -20
track_r.append(r)
q = critic.learn(s, a, r, s_) # gradient = grad[r + gamma * V(s_) - V(s)]
# pdb.set_trace()
actor.learn(s, a, q) # true_gradient = grad[logPi(s,a) * td_error]
s = s_
t += 1
if done or t >= MAX_EP_STEPS:
ep_rs_sum = sum(track_r)
if 'running_reward' not in globals():
running_reward = ep_rs_sum
else:
running_reward = running_reward * 0.95 + ep_rs_sum * 0.05
if running_reward > DISPLAY_REWARD_THRESHOLD: RENDER = True # rendering
print("episode:", i_episode, " reward:", int(running_reward))
res.append([i_episode, running_reward])
break
pd.DataFrame(res, columns=['episode','ac_reward']).to_csv('../ac_reward.csv')