【强化学习】A3C代码注释版本
生活随笔
收集整理的這篇文章主要介紹了
【强化学习】A3C代码注释版本
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
##########################################
# A3C做出的改進:
# 解決AC難以收斂的問題
# 不一樣的地方:
#import threading
# import tensorflow as tf
import tensorflow.compat.v1 as tftf.compat.v1.disable_eager_execution()
import numpy as np
import gym
import os
import shutil
import matplotlib.pyplot as pltGAME = 'CartPole-v0' # 游戲名稱
OUTPUT_GRAPH = True
LOG_DIR = '../log' # 一個文件名
N_WORKERS = 3 # 有3個并行的worker
MAX_GLOBAL_EP = 3000 # 最大執行回合數
GLOBAL_NET_SCOPE = 'Global_Net' # object名稱
UPDATE_GLOBAL_ITER = 100 # 每UPDATE_GLOBAL_ITER 步 或者回合完了,進行sync操作
GAMMA = 0.9 # 折扣因子
ENTROPY_BETA = 0.001
LR_A = 0.001 # learning rate for actor Actor的學習率
LR_C = 0.001 # learning rate for critic Actor的學習率
GLOBAL_RUNNING_R = [] # 游戲整體的這個游戲里面一共進行了多少步,這個數組長度就是多少
GLOBAL_EP = 0 # 整體進行的回合數
STEP = 3000 # Step limitation in an episode TODO 這里應該改成200,因為200步游戲就達到終止條件了
TEST = 10 # 實驗次數每100回合測試一次 TODO 這里應該是100吧?env = gym.make(GAME)
N_S = env.observation_space.shape[0] # 狀態維度(?,4)
N_A = env.action_space.n # 動作維度(2)# 這個 class 可以被調用生成一個 global net.
# 也能被調用生成一個 worker 的 net, 因為他們的結構是一樣的,
# 所以這個 class 可以被重復利用.
class ACNet(object):def __init__(self, scope, globalAC=None): # 這里的scope是網絡名稱# 當創建worker網絡的時候,我們傳入之前創建的globalAC給這個worker# 判斷當下建立的網絡是 local 還是 globalif scope == GLOBAL_NET_SCOPE: # 如果建立的網絡是Global網絡# 關于tf.name_scope和tf.variable_scopewith tf.variable_scope(scope):self.s = tf.placeholder(tf.float32, [None, N_S], 'S') # 定義一組狀態# 這里的a_params具體代表求導時的那個分母,也就是對什么求導self.a_params, self.c_params = self._build_net(scope)[-2:] # 取這個數組從第一個到倒數第三個,最后兩個數被丟棄了else: # local net, calculate losseswith tf.variable_scope(scope):# 接著計算 critic loss 和 actor loss# 用這兩個 loss 計算要推送的 gradientsself.s = tf.placeholder(tf.float32, [None, N_S], 'S') # 定義一組狀態self.a_his = tf.placeholder(tf.int32, [None, ], 'A') # 定義了一組動作self.v_target = tf.placeholder(tf.float32, [None, 1], 'Vtarget') # 目標價值self.a_prob, self.v, self.a_params, self.c_params = self._build_net(scope)# 接著計算 critic loss 和 actor loss# 用這兩個 loss 計算要推送的 gradients# 張量減法運算:tf.subtract函數是一個算術運算符, 用于表示減法, 返回x - y的元素td = tf.subtract(self.v_target, self.v, name='TD_error') # td_error = v_target - vwith tf.name_scope('c_loss'): # c網絡loss函數self.c_loss = tf.reduce_mean(tf.square(td)) # td_error先求平方然后取平均with tf.name_scope('a_loss'): # 下面這些操作都封裝到一個叫a_loss的作用域中,用來計算a_loss,方便可視化# 下面這些應該是在實現a_loss的那個公式log_prob = tf.reduce_sum(tf.log(self.a_prob + 1e-5) * tf.one_hot(self.a_his, N_A, dtype=tf.float32),axis=1, keep_dims=True)# tf.stop_gradient: 停止梯度運算,當在一個圖中執行時, 這個op按原樣輸出它的輸入張量。# 當構建ops來計算梯度時,該op會阻止將其輸入貢獻考慮在內。exp_v = log_prob * tf.stop_gradient(td)entropy = -tf.reduce_sum(self.a_prob * tf.log(self.a_prob + 1e-5),axis=1, keep_dims=True) # encourage explorationself.exp_v = ENTROPY_BETA * entropy + exp_v # expect valueself.a_loss = tf.reduce_mean(-self.exp_v) # actor_losswith tf.name_scope('local_grad'):# tf.gradients 實現a_loss對a_params求導 梯度self.a_grads = tf.gradients(self.a_loss, self.a_params)self.c_grads = tf.gradients(self.c_loss, self.c_params)with tf.name_scope('sync'): # 同步with tf.name_scope('pull'): # 調用pull,這個worker就會從global_net中獲取到最新的參數# assign相當于連線,一般是將一個變量的值不間斷地賦值給另一個變量,就像把這兩個變量連在一起,所以習慣性的當做連線用,比如把一個模塊的輸出給另一個模塊當輸入。self.pull_a_params_op = [l_p.assign(g_p) for l_p, g_p in zip(self.a_params, globalAC.a_params)]self.pull_c_params_op = [l_p.assign(g_p) for l_p, g_p in zip(self.c_params, globalAC.c_params)]with tf.name_scope('push'): # 調用push,這個worker就會將自己的個人更新推送去global_netself.update_a_op = OPT_A.apply_gradients(zip(self.a_grads, globalAC.a_params))self.update_c_op = OPT_C.apply_gradients(zip(self.c_grads, globalAC.c_params))def _build_net(self, scope): # 這里搭建Actor和Critic網絡w_init = tf.random_normal_initializer(0., .1) # 生成一組符合標準正態分布的 tensor 對象,初始化張量with tf.variable_scope('actor'): # 搭建一個actor網絡"""這個actor網絡有兩層,第一層輸入是狀態,輸出一個維度為200的東西,第一層的輸出l_a是第二層的輸入,第一層的激活函數和第二層不一樣"""# https://blog.csdn.net/yangfengling1023/article/details/81774580/# 全連接層,曾加了一個層,全連接層執行操作 outputs = activation(inputs.kernel+bias) 如果執行結果不想進行激活操作,則設置activation=None# self.s:輸入該網絡層的數據 200:輸出的維度大小,改變inputs的最后一維 tf.nn.relu6:激活函數,即神經網絡的非線性變化# kernel_initializer=w_init:卷積核的初始化器 name:層的名字l_a = tf.layers.dense(self.s, 200, tf.nn.relu6, kernel_initializer=w_init, name='la')a_prob = tf.layers.dense(l_a, N_A, tf.nn.softmax, kernel_initializer=w_init, name='ap')with tf.variable_scope('critic'): # 搭建critic網絡"""這個網絡也是有兩層,第一層有100個輸出,第二層只有一個輸出,"""l_c = tf.layers.dense(self.s, 100, tf.nn.relu6, kernel_initializer=w_init, name='lc')v = tf.layers.dense(l_c, 1, kernel_initializer=w_init, name='v') # state value# tf.get_collection 用來獲取一個名稱是‘key’的集合中的所有元素,返回的是一個列表,列表的順序是按照變量放入集合中的先后;# scope參數可選,表示的是名稱空間(名稱域),如果指定,就返回名稱域中所有放入‘key’的變量的列表,不指定則返回所有變量。# TODO 這個含義看不懂。。。TRAINABLE_VARIABLES:將由優化程序訓練的Variable對象的子集a_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope + '/actor')c_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope + '/critic')return a_prob, v, a_params, c_params # 返回均值、方差,v:state_value TODO a_params, c_params 這兩個參數什么意思啊def update_global(self, feed_dict): # run by a local 進行push操作SESS.run([self.update_a_op, self.update_c_op], feed_dict) # 將本地def pull_global(self): # run by a local #進行pull操作SESS.run([self.pull_a_params_op, self.pull_c_params_op])def choose_action(self, s): # run by a local 根據s選擇動作prob_weights = SESS.run(self.a_prob, feed_dict={self.s: s[np.newaxis, :]})action = np.random.choice(range(prob_weights.shape[1]),p=prob_weights.ravel()) # select action w.r.t the actions prob 根據概率選擇動作return actionclass Worker(object):def __init__(self, name, globalAC):self.env = gym.make(GAME).unwrapped # 創建自己的環境self.name = name # 自己的名字self.AC = ACNet(name, globalAC) # 自己的local net,并綁定上globalACdef work(self): # worker網絡的主體部分# s,a,r 的緩存,用于n_steps更新global GLOBAL_RUNNING_R, GLOBAL_EPtotal_step = 1 # 初始化步數buffer_s, buffer_a, buffer_r = [], [], [] # state,action,和reward的緩存# 當 COORD不需要停止的時候,或者當GLOBAL_EP比MAX_GLOBAL_EP小的時候while not COORD.should_stop() and GLOBAL_EP < MAX_GLOBAL_EP: # MAX_GLOBAL_EP最大訓練EPs = self.env.reset() # 重置環境ep_r = 0 # 統計ep的總rewardwhile True:# if self.name == 'W_0':# self.env.render()a = self.AC.choose_action(s) # 選擇動作s_, r, done, info = self.env.step(a) # 與環境互動if done: r = -5 # 把reward-100的時候,改為-5ep_r += r # 保存數據 ep_r 總的rewardbuffer_s.append(s) # 添加各種緩存buffer_a.append(a)buffer_r.append(r)# 每UPDATE_GLOBAL_ITER 步 或者回合完了,進行sync操作,也就是學習的步驟if total_step % UPDATE_GLOBAL_ITER == 0 or done: # update global and assign to local net# 獲得用于計算TD_error 的下一state的value# 下面對應價值函數的那個公式,如果是terminal state,則對未來的期望等于0if done:v_s_ = 0 # terminalelse:v_s_ = SESS.run(self.AC.v, {self.AC.s: s_[np.newaxis, :]})[0, 0]buffer_v_target = [] # 下 state value的緩存,用于計算TD# 計算每個state的V(s')# print(a[::-1]) ### 取從后向前(相反)的元素[1 2 3 4 5]-->[ 5 4 3 2 1 ]# (莫煩說)對MDP的一個反向的計算,對未來的reward的一個遞解的步驟for r in buffer_r[::-1]:v_s_ = r + GAMMA * v_s_buffer_v_target.append(v_s_)buffer_v_target.reverse() # 先反轉填充再轉回來# np.vstack:按垂直方向(行順序)堆疊數組構成一個新的數組buffer_s, buffer_a, buffer_v_target = np.vstack(buffer_s), np.array(buffer_a), np.vstack(buffer_v_target)# feed_dict的作用是給使用placeholder創建出來的tensor賦值feed_dict = {self.AC.s: buffer_s,self.AC.a_his: buffer_a,self.AC.v_target: buffer_v_target,}self.AC.update_global(feed_dict) # 推送更新去globalACbuffer_s, buffer_a, buffer_r = [], [], [] # 清空緩存self.AC.pull_global() # 獲取globalAC的最新參數s = s_ # 更新狀態total_step += 1 # 整體部署+1if done:# 達到游戲終止條件,更新rewardif len(GLOBAL_RUNNING_R) == 0: # 如果是第一回合GLOBAL_RUNNING_R.append(ep_r)else: # 不是第一回合GLOBAL_RUNNING_R.append(0.99 * GLOBAL_RUNNING_R[-1] + 0.01 * ep_r)print(self.name,"Ep:", GLOBAL_EP,"| Ep_r: %i" % GLOBAL_RUNNING_R[-1],)GLOBAL_EP += 1 # 加一回合break # 結束這回合if __name__ == '__main__':SESS = tf.Session() # 創建一個會話# 下面是真正的重點!!worker并行計算# 使用 tf.device() 指定模型運行的具體設備,可以指定運行在GPU還是CPU上,以及哪塊GPU上。with tf.device("/cpu:0"): # 以下部分,都在CPU0完成# tf.train.RMSPropOptimizerSHI是一種優化算法,有很多種優化算法,具體見下面這個文檔,有空好好學習下# https://www.cnblogs.com/bigcome/p/10084220.htmlOPT_A = tf.train.RMSPropOptimizer(LR_A, name='RMPropA') # 定義了一個actor優化器OPT_C = tf.train.RMSPropOptimizer(LR_C, name='RMPropC') # 定義了一個Critic優化器GLOBAL_AC = ACNet(GLOBAL_NET_SCOPE) # we only need its params 定義了一個總的AC網絡workers = [] # 定義workersfor i in range(N_WORKERS): # 創建worker,之后再并行i_name = 'W_%i' % i # worker的名字workers.append(Worker(i_name, GLOBAL_AC)) # 每個worker都有共享這個global AC# 調用 tf.train.Coordinator() 來創建一個線程協調器,用來管理之后在Session中啟動的所有線程;COORD = tf.train.Coordinator() # Tensorflow用于并行的工具SESS.run(tf.global_variables_initializer())# 執行到這兒的意思是,OUTPUT_GRAPH這個參量只出現了一次,而且是true,也就是說,刪除path這個路徑里面的LOG_DIR文件,然后保存一張新的進去,也就是咱們收斂情況的圖if OUTPUT_GRAPH:# os.path.exists()就是判斷括號里的文件是否存在的意思,path代表路徑,括號內的可以是文件名。if os.path.exists(LOG_DIR): # 這句是說,如果LOG_DIR這個文件存在,就將它刪除# 在python文件中,使用代碼刪除文件夾以及里面的文件,可以使用shutil.rmtree,遞歸地刪除文件夾以及里面的文件。shutil.rmtree(LOG_DIR)# tf.summary.FileWriter 指定一個文件用來保存圖 ,指定LOG_DIR這個文件來保存SESS.graph這個圖tf.summary.FileWriter(LOG_DIR, SESS.graph)# 開啟tf線程worker_threads = []for worker in workers: # 執行每一個workerjob = lambda: worker.work() # 有一個工人(worker),有一個方法(work),這句是說讓這個工人worker去執行work這個方法t = threading.Thread(target=job) # 添加一個工作線程t.start() # 開始這個線程worker_threads.append(t) # 把這個線程添加到worker_threads中COORD.join(worker_threads) # 當所有的worker都運行完了才會進行下面的步驟,如果沒有這一句,那么每一個worker運行完就會進行下面的步驟testWorker = Worker("test", GLOBAL_AC) # 創建一個測試worker TODO 不知道這個worker有什么作用testWorker.AC.pull_global() # 對testworker執行pull操作# 下面應該是測試部分total_reward = 0for i in range(TEST):state = env.reset() # 初始化狀態for j in range(STEP):env.render() # env.render()函數用于渲染出當前的智能體以及環境的狀態action = testWorker.AC.choose_action(state) # 選擇一個測試動作state, reward, done, _ = env.step(action)total_reward += reward # reward疊加if done: # 達到終止條件就跳出循環breakave_reward = total_reward / TEST# 打印內容:整個過程一共進行了多少回合,平均獎勵是多少print('episode: ', GLOBAL_EP, 'Evaluation Average Reward:', ave_reward)plt.plot(np.arange(len(GLOBAL_RUNNING_R)), GLOBAL_RUNNING_R) # TODO GLOBAL_RUNNING_R這個參量再研究一下plt.xlabel('step')plt.ylabel('Total moving reward')plt.show()
總結
以上是生活随笔為你收集整理的【强化学习】A3C代码注释版本的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑上如何修改路由器密码电脑端如何更改家
- 下一篇: 光纤用户的路由器怎么设置如何设置光纤的路