在Actor-Critic里面最著名的是Asychronous Advantage Actor-Critic (A3C),而Advantage Actor-Critic是A2C。
我们先回顾一下PG:
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n ( ∑ t ′ = t T n γ t ′ − t r t ′ n − b ) ∇ log p θ ( a t ∣ s t ) (1) \nabla \bar R_\theta \approx \frac{1}{N} \sum_{n=1}^N \sum_{t=1}^{T_n} \left(\sum_{t^\prime=t}^{T_n} \gamma^{t^\prime-t}r_{t^\prime}^n -b \right) \nabla \log p_\theta(a_t|s_t) \tag{1} ∇Rˉθ≈N1n=1∑Nt=1∑Tn(t′=t∑Tnγt′−trt′n−b)∇logpθ(at∣st)(1)
其中 G t n = ∑ t ′ = t T n γ t ′ − t r t ′ n G_t^n = \sum_{t^\prime=t}^{T_n} \gamma^{t^\prime-t}r_{t^\prime}^n Gtn=∑t′=tTnγt′−trt′n使得式子(1)中括号内的那一项有正有负,是正的话就增加在 s t n s_t^n stn选择 a t n a_t^n atn的概率,是负的话就减小在 s t n s_t^n stn选择 a t n a_t^n atn的概率。
然而 G t n G_t^n Gtn是很不稳定,有随机性。如在s采取a可能会得到不同的 G t n G_t^n Gtn,由于策略和环境的随机性。当采样足够多的次数这是没有问题的,但在做PG的时候我们采样其实是不够多的。
那我们能不能直接去估计G的期望值呢?如果可以那就可以用G的期望值代替采样的 G t n G_t^n Gtn值。
\\[30pt]
在Q-learing中我们知道有两种类型的Critic,一种是估计状态的价值函数(V值),另一种是估计状态-动作对的价值函数(Q值)。他们分别为:
V π ( s ) = E π [ G t ∣ S t = s ] = E π [ ∑ k = 0 ∞ γ k R t + k + 1 ∣ S t = s ] (2) V^\pi(s)=\Bbb E_\pi[G_t|S_t=s]=\Bbb E_\pi \left[\sum_{k=0}^\infty \gamma^k R_{t+k+1}|S_t=s \right] \tag{2} Vπ(s)=Eπ[Gt∣St=s]=Eπ[k=0∑∞γkRt+k+1∣St=s](2)
Q π ( s , a ) = E π [ G t ∣ S t = s , A t = a ] = E π [ ∑ k = 0 ∞ γ k R t + k + 1 ∣ S t = s , A t = a ] (3) Q^\pi(s,a)=\Bbb E_\pi[G_t|S_t=s,A_t=a]=\Bbb E_\pi \left[ \sum_{k=0}^\infty \gamma^k R_{t+k+1} |S_t=s,A_t=a \right] \tag{3} Qπ(s,a)=Eπ[Gt∣St=s,At=a]=Eπ[k=0∑∞γkRt+k+1∣St=s,At=a](3)
\\[30pt]
\\[30pt]
更新方式从公式(1)变成了公式(2):
∇ R ˉ θ ≈ 1 N ∑ n = 1 N ∑ t = 1 T n ( r t n + V π ( s t + 1 n ) − V π ( s t n ) ) ) ∇ log p θ ( a t ∣ s t ) (5) \nabla \bar R_\theta \approx \frac{1}{N} \sum_{n=1}^N \sum_{t=1}^{T_n} \left( r_t^n+V^\pi(s_{t+1}^n) - V^\pi(s_t^n)) \right) \nabla \log p_\theta(a_t|s_t) \tag{5} ∇Rˉθ≈N1n=1∑Nt=1∑Tn(rtn+Vπ(st+1n)−Vπ(stn)))∇logpθ(at∣st)(5)
我们使用 π \pi π(actor)与环境做互动来采样,再使用critic来评估状态的价值 V π ( s t n ) 和 V π ( s t + 1 n ) V^\pi(s_t^n)和V^\pi(s_{t+1}^n) Vπ(stn)和Vπ(st+1n),然后计算出TD-error为 ( r t n + V π ( s t + 1 n ) − V π ( s t n ) ) (r_t^n+V^\pi(s_{t+1}^n) - V^\pi(s_t^n)) (rtn+Vπ(st+1n)−Vπ(stn)),critic就是使用该误差来更新自身参数。而actor采用公式(5)来更新。
具体来说呢,critic是输入一个状态s,输出该状态的价值。而actor是输入一个状态,输出一个策略,即动作的分布。
另外我们可以对actor的输出使用entropy来regularization (正则化)来保证探索新的动作。
\\[30pt]
我们需要建立两个网络,两个网络的输入都是状态,actor的输出是策略,而critic的输出是状态的价值。
actor = Actor(n_features=N_F, n_actions=N_A, lr=LR_A)
critic = Critic(n_features=N_F, lr=LR_C)
for episode in range(200):
total_reward = 0
returns = []
s = env.reset().astype(np.float32)
t = 0 # number of step in this episode
all_r = [] # rewards of all steps
for step in range(500):
a = actor.choose_action(s) #1
s_new, r, done, info = env.step(a)
s_new = s_new.astype(np.float32)
if done: break
all_r.append(r)
# Critic学习,并计算出td-error,# learn Value-function : gradient = grad[r + lambda * V(s_new) - V(s)]
td_error = critic.learn(s, r, s_new) #2
# actor学习,# learn Policy : true_gradient = grad[logPi(s, a) * td_error]
actor.learn(s, a, td_error) #3
total_reward += r
s = s_new
输入状态s,输出该状态下所有动作的概率分布,然后从概率分布随机选择动作。
# 按照分布随机动作。
def choose_action(self, s):
_logits = self.model(np.array([s]))
_probs = tf.nn.softmax(_logits).numpy()
return tl.rein.choice_action_by_probs(_probs.ravel()) # sample according to probability distribution
#1和#2: Critic网络分别计算出当前状态 s s s和下一状态 s _ s\_ s_的价值, V π ( s ) V^\pi(s) Vπ(s)和 V π ( s _ ) V^\pi(s\_) Vπ(s_)。
#3:计算出 t d _ e r r o r = r + l a m d b ∗ V π ( s _ ) − V π ( s ) td\_error =r+ lamdb * V^\pi(s\_) -V^\pi(s) td_error=r+lamdb∗Vπ(s_)−Vπ(s), 注意这里乘以了一个收益折扣lambd。
#4:我们希望这个td_error越小越好,以使得估计的价值函数越精确,因此以td_error为目标更新Critic网络。
# Critic学习,critic网络是评估状态的价值
def learn(self, s, r, s_):
v_ = self.model(np.array([s_])) #1
with tf.GradientTape() as tape:
v = self.model(np.array([s])) #2
## [敲黑板]计算TD-error
## TD_error = r + lambd * V(newS) - V(S)
td_error = r + LAMBDA * v_ - v #3
loss = tf.square(td_error) #4
grad = tape.gradient(loss, self.model.trainable_weights)
self.optimizer.apply_gradients(zip(grad, self.model.trainable_weights))
return td_error
#1:输入当前状态s到Actor中,输出动作的策略(注意未经过softmax作用)。
# Actor学习
def learn(self, s, a, td):
with tf.GradientTape() as tape:
_logits = self.model(np.array([s])) #1
## 带权重更新。
_exp_v = tl.rein.cross_entropy_reward_loss(logits=_logits, actions=[a], rewards=td[0]) #2
grad = tape.gradient(_exp_v, self.model.trainable_weights)
self.optimizer.apply_gradients(zip(grad, self.model.trainable_weights))
return _exp_v
#2:tl.rein.cross_entropy_reward_loss(logits=_logits, actions=[a], rewards=td[0])
此函数计算logits和actions之间的softmax cross entropy,然后将结果乘以权重rewards。
举个例子,假如logits=[[-4.5016805e-07, -2.3863397e-06]],action=[0],rewards=[1.0000023],该函数的计算过程如下:
\\[30pt]
在原来的actor-critic中,critic只会告诉你采取的动作是好还是不好,而在pathwise derivative PG中会直接告诉你采取什么样的动作。
在Q-learning中我们通过Q函数来选择一个动作,现在我们使用一个actor π \pi π来选择一个动作。
我们的Q网络的输入是状态s和采取的动作a,输出是(s,a)的Q值 Q π ( s , a ) Q^\pi(s,a) Qπ(s,a),这里的动作a通过actor π \pi π来做选择。
下图是该算法的一个循环过程
两个算法存在四个不同的地方:
DDPG与以上一节的PDPG的有以下不同点:
倒立摆问题是控制问题中比较经典的问题,钟摆以随机的位置开始,动作是顺时针或逆时针调整钟摆,目标是使钟摆保持直立。
序号 | 环境信息 | 最小值 | 最大值 |
---|---|---|---|
1 | cos ( θ ) \cos(\theta) cos(θ) | − 1.0 -1.0 −1.0 | 1.0 1.0 1.0 |
2 | sin ( θ ) \sin(\theta) sin(θ) | − 1.0 -1.0 −1.0 | 1.0 1.0 1.0 |
3 | θ d t \theta_{dt} θdt | − 8.0 -8.0 −8.0 | 8.0 8.0 8.0 |
动作
动作a在 [ − 2.0 , 2.0 ] [-2.0, 2.0] [−2.0,2.0]之间取值。
奖励
− ( θ 2 + 0.1 ∗ θ d t 2 + 0.001 ∗ a 2 ) -(\theta^2+0.1*\theta_{dt}^2+0.001*a^2) −(θ2+0.1∗θdt2+0.001∗a2)
参考资料:https://github.com/openai/gym/wiki/Pendulum-v0
我们重点介绍以下几个模块:
for i in range(MAX_EPISODES):
t1 = time.time()
s = env.reset()
ep_reward = 0 #记录当前EP的reward
for j in range(MAX_EP_STEPS):
a = ddpg.choose_action(s) #这里很简单,直接用actor估算出a动作
a = np.clip(np.random.normal(a, VAR), -2, 2)
# 与环境进行互动
s_, r, done, info = env.step(a)
# 保存s,a,r,s_
ddpg.store_transition(s, a, r / 10, s_)
# 第一次数据满了,就可以开始学习
if ddpg.pointer > MEMORY_CAPACITY:
ddpg.learn()
#输出数据记录
s = s_
ep_reward += r #记录当前EP的总reward
在这里插入代码片
a = ddpg.choose_action(s) #这里很简单,直接用actor估算出a动作
a = np.clip(np.random.normal(a, VAR), -2, 2)
# Critic:
# Critic更新和DQN很像,不过target不是argmax了,是用critic_target计算出来的。
# br + GAMMA * q_
with tf.GradientTape() as tape:
a_ = self.actor_target(bs_) #1
q_ = self.critic_target([bs_, a_]) #2
y = br + GAMMA * q_ #3
q = self.critic([bs, ba]) #4
td_error = tf.losses.mean_squared_error(y, q) #5
c_grads = tape.gradient(td_error, self.critic.trainable_weights)
self.critic_opt.apply_gradients(zip(c_grads, self.critic.trainable_weights))
# Actor:
# Actor的目标就是获取最多Q值的。
with tf.GradientTape() as tape:
a = self.actor(bs) #1
q = self.critic([bs, a]) #2
# 【敲黑板】:注意这里用负号,是梯度上升!也就是离目标会越来越远的,就是越来越大。
a_loss = -tf.reduce_mean(q) #3
a_grads = tape.gradient(a_loss, self.actor.trainable_weights)
self.actor_opt.apply_gradients(zip(a_grads, self.actor.trainable_weights))