獲取更多資訊,趕快關注上面的公衆號吧!
【強化學習系列】
- 第一章 強化學習及OpenAI Gym介紹-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第二章 馬爾科夫決策過程和貝爾曼等式-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第三章 動態規劃-基於模型的RL-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第四章 蒙特卡洛方法-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第五章 基於時序差分和Q學習的無模型預測與控制-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第六章 函數逼近-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第七章 深度強化學習-深度Q網絡系列1(Deep Q-Networks,DQN)
- 第八章 深度強化學習-Nature深度Q網絡(Nature DQN)
- 第九章 深度強化學習-Double DQN
- 第十章 深度強化學習-Prioritized Replay DQN
- 第十一章 策略梯度(Policy Gradient)-強化學習理論學習與代碼實現(強化學習導論第二版)
- (本文)第十二章 演員評論家(Actor-Critic)-強化學習理論學習與代碼實現(強化學習導論第二版)
第十二章 Actor-Critic演員評論家
我們在上一章中介紹了策略梯度(Policy Gradient)方法,並代碼練習了蒙特卡羅策略梯度reinforce算法。但是由於該算法需要完整的狀態序列,同時單獨對策略函數進行迭代更新,不太容易收斂。
在本章我們將討論一種將策略(Policy Based)和價值(Value Based)相結合的方法:Actor-Critic算法,在強化學習領域最受歡迎的A3C算法,DDPG算法,PPO算法等都是AC框架,所以AC重要性不言而喻。
12.1 Actor-Critic算法簡介
Actor-Critic從名字上看包括兩部分,演員(Actor)和評價家(Critic)。其中Actor使用的是上一章講到的策略函數,負責生成動作(Action)並和環境交互。而Critic使用我們之前講到了的價值函數,負責評估Actor的表現,並指導Actor下一階段的動作。
回想上一篇的策略梯度,策略函數就是我們的Actor,但是那裏是沒有Critic的,當時使用了蒙特卡羅法來計算每一步的價值部分替代了Critic的功能,但是場景比較受限。因此現在我們使用類似DQN中用的價值函數來替代蒙特卡羅法,作爲一個比較通用的Critic。
也就是說在Actor-Critic算法中,我們需要做兩組近似,第一組是策略函數的近似:
第二組是價值函數的近似,對於狀態價值和動作價值函數分別是:
對於我們上一節講到的蒙特卡羅策略梯度reinforce算法,需要進行改造才能變成Actor-Critic算法,將會在下一節中詳細介紹。
首先,在蒙特卡羅策略梯度reinforce算法中,策略的參數更新公式是:
梯度更新部分中,是得分函數,無需改變,要變成Actor的話改動的是,這塊不能再使用蒙特卡羅法來得到,而應該從Critic得到。
而對於Critic來說,完全可以參考之前DQN的做法,即用一個Q網絡來做爲Critic, 這個Q網絡的輸入可以是狀態,而輸出是每個動作的價值或者最優動作的價值。
總體上來說,就是Critic通過Q網絡計算狀態的最優價值vt, 而Actor利用這個最優價值迭代更新策略函數的參數θ,進而選擇動作,並得到反饋和新的狀態,Critic使用反饋和新的狀態更新Q網絡參數w, 在後面Critic會使用新的網絡參數w來幫Actor計算狀態的最優價值。
12.2 Actor-Critic框架引出
在上一章中我們已經得到策略梯度的更新公式如下:
這裏將記爲,由於通過交互得到,其值非常不穩定(由於環境的動態性,本身也是一個分佈),方差會比較大,因此需要尋找減少方差的辦法。一種方法就是在上一章中採用的添加基線b, 這個b會使得的期望不變,但是方差會變小,常用的baseline函數就是,在此基礎上,爲了進一步降低的隨機性,我們用的期望替代,這樣上面的更新公式變爲:
在根據Q學習部分(),可知期望就是在狀態下執行動作,並遵循策略所能得到的Q值,即=,由此得到下式:
上式中存在的問題是,需要兩個網絡來分別預測Q和V,這就無形中增加了誤差來源,考慮到貝爾曼等式,即:
這裏將期望去掉(個人理解,雖然去掉期望會導致有偏,但是最終還是會收斂到真實值):
那麼最終就得到:
這樣只需要一個網絡就可以估算出V值了,而估算V的網絡正是我們在Q-learning 中做的,所以我們就把這個網絡叫做Critic。這樣就在Policy Gradient算法的基礎上引進了Q-learning 算法了。
12.3 Actor-Critic算法流程
Critic使用神經網絡來計算TD誤差並更新網絡參數,Actor將TD誤差作爲輸入,也使用神經網絡來更新網絡參數
算法輸入:迭代輪數T,狀態特徵維度n, 動作集A, 步長α,β,衰減因子γ, 探索率ϵ, Critic網絡結構和Actor網絡結構。
輸出:Actor 網絡參數θ, Critic網絡參數w
1.隨機初始化所有的狀態和動作對應的價值Q
2.for i from 1 to T,進行迭代。
a)初始化S爲當前狀態序列的第一個狀態, 得到其特徵向量
b)在Actor網絡中使用作爲輸入,輸出動作A,基於動作A得到新的狀態S′,獎勵r。
c)在Critic網絡中分別使用,作爲輸入,得到Q值輸出V(S),V(S′)
d)計算TD誤差
e)使用均方差損失函數作Critic網絡參數w的梯度更新
f)更新Actor網絡參數θ:
對於Actor的得分函數∇θlogπθ(St,A),可以選擇softmax或者高斯分值函數。
12.4 代碼練習
代碼針對的環境的是 CliffWalkingEnv,在該環境中智能體在一個 4x12 的網格中移動,狀態編號如下所示:
[[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
[36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]]
在任何階段開始時,初始狀態都是狀態 36,狀態 47 是唯一的終止狀態,懸崖對應的是狀態 37 到 46。智能體有 4 個可選動作(UP = 0,RIGHT = 1,DOWN = 2,LEFT = 3)。智能體每走一步都會得到-1 的獎勵,跌入懸崖會得到-100 的獎勵並重置到起點,當達到目標時,片段結束。
AC完整的代碼如下:
import gym
import itertools
import matplotlib
import numpy as np
import sys
import tensorflow as tf
import collections
if "../" not in sys.path:
sys.path.append("../")
from Lib.envs.cliff_walking import CliffWalkingEnv
from Lib import plotting
matplotlib.style.use('ggplot')
env = CliffWalkingEnv()
class PolicyEstimator():
"""
策略函數逼近
"""
def __init__(self, learning_rate=0.01, scope="policy_estimator"):
with tf.variable_scope(scope):
self.state = tf.placeholder(tf.int32, [], "state")
self.action = tf.placeholder(dtype=tf.int32, name="action")
self.target = tf.placeholder(dtype=tf.float32, name="target")
# This is just table lookup estimator
state_one_hot = tf.one_hot(self.state, int(env.observation_space.n))
self.output_layer = tf.contrib.layers.fully_connected(
inputs=tf.expand_dims(state_one_hot, 0),
num_outputs=env.action_space.n,
activation_fn=None,
weights_initializer=tf.zeros_initializer)
self.action_probs = tf.squeeze(tf.nn.softmax(self.output_layer))
self.picked_action_prob = tf.gather(self.action_probs, self.action)
self.loss = -tf.log(self.picked_action_prob) * self.target
self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
self.train_op = self.optimizer.minimize(
self.loss, global_step=tf.contrib.framework.get_global_step())
def predict(self, state, sess=None):
sess = sess or tf.get_default_session()
return sess.run(self.action_probs, {self.state: state})
def update(self, state, target, action, sess=None):
sess = sess or tf.get_default_session()
feed_dict = {self.state: state, self.target: target, self.action: action}
_, loss = sess.run([self.train_op, self.loss], feed_dict)
return loss
class ValueEstimator():
"""
值函數逼近器
"""
def __init__(self, learning_rate=0.1, scope="value_estimator"):
with tf.variable_scope(scope):
self.state = tf.placeholder(tf.int32, [], "state")
self.target = tf.placeholder(dtype=tf.float32, name="target")
# This is just table lookup estimator
state_one_hot = tf.one_hot(self.state, int(env.observation_space.n))
self.output_layer = tf.contrib.layers.fully_connected(
inputs=tf.expand_dims(state_one_hot, 0),
num_outputs=1,
activation_fn=None,
weights_initializer=tf.zeros_initializer)
self.value_estimate = tf.squeeze(self.output_layer)
self.loss = tf.squared_difference(self.value_estimate, self.target)
self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
self.train_op = self.optimizer.minimize(
self.loss, global_step=tf.contrib.framework.get_global_step())
def predict(self, state, sess=None):
sess = sess or tf.get_default_session()
return sess.run(self.value_estimate, {self.state: state})
def update(self, state, target, sess=None):
sess = sess or tf.get_default_session()
feed_dict = {self.state: state, self.target: target}
_, loss = sess.run([self.train_op, self.loss], feed_dict)
return loss
def actor_critic(env, estimator_policy, estimator_value, num_episodes, discount_factor=1.0):
"""
Actor Critic 算法.通過策略梯度優化策略函數逼近器
參數:
env: OpenAI環境.
estimator_policy: 待優化的策略函數
estimator_value: 值函數逼近器,用作評論家
num_episodes: 回合數
discount_factor: 折扣因子
返回值:
EpisodeStats對象,包含兩個numpy數組,分別存儲片段長度和片段獎勵
"""
# Keeps track of useful statistics
stats = plotting.EpisodeStats(
episode_lengths=np.zeros(num_episodes),
episode_rewards=np.zeros(num_episodes))
Transition = collections.namedtuple("Transition", ["state", "action", "reward", "next_state", "done"])
for i_episode in range(num_episodes):
state = env.reset()
episode = []
for t in itertools.count():
action_probs = estimator_policy.predict(state)
action = np.random.choice(np.arange(len(action_probs)), p=action_probs)
next_state, reward, done, _ = env.step(action)
episode.append(Transition(
state=state, action=action, reward=reward, next_state=next_state, done=done))
stats.episode_rewards[i_episode] += reward
stats.episode_lengths[i_episode] = t
# 計算TD目標
value_next = estimator_value.predict(next_state)
td_target = reward + discount_factor * value_next
td_error = td_target - estimator_value.predict(state)
# 更新值函數逼近
estimator_value.update(state, td_target)
# 更新策略逼近
# 使用TD誤差作爲優勢估計
estimator_policy.update(state, td_error, action)
print("\rStep {} @ Episode {}/{} ({})".format(
t, i_episode + 1, num_episodes, stats.episode_rewards[i_episode - 1]), end="")
if done:
break
state = next_state
return stats
tf.reset_default_graph()
global_step = tf.Variable(0, name="global_step", trainable=False)
policy_estimator = PolicyEstimator()
value_estimator = ValueEstimator()
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
stats = actor_critic(env, policy_estimator, value_estimator, 300)
plotting.plot_episode_stats(stats, smoothing_window=10)
結果如下: