Q-Learning,學習Action對應期望值(Expected Utility)。1989年,Watkins提出。收斂性,1992年,Watkins和Dayan共同證明。學習期望價值,從當前一步到所有后續步驟,總期望獲取最大價值(Q值、Value)。Action->Q函數,最佳策略,在每個state下,選擇Q值最高的Action。不依賴環境模型。有限馬爾科夫決策過程(Markov Dectision Process) ,Q-Learning被證明最終可以找到最優策略。
Q-Learning目標,求解函數Q(st,at),根據當前環境狀態,估算Action期望價值。Q-Learning訓練模型,以(狀態、行為、獎勵、下一狀態)構成元組(st,at,rt+1,st+1)樣本訓練,st當前狀態,at當前狀態下執行action,rt+1執行Action后獲得獎勵,st+1下一狀態,(當前狀態,行動,獎勵,下一狀態)。特征(st,at)。學習目標(期望價值) rt+1+γ·maxaQ(st+1,a),當前Action獲得Reward,加下一步可獲得最大期望價值,當前狀態行動獎勵,加下一狀態行動最大期望價值。學習目標包含Q-Learning函數本身,遞歸求解。下一步可獲最大期望價值乘γ(衰減系數discount factor),未來獎勵的學習權重。discount factor 0,模型學習不到任何未來獎勵信息,變短視,只關注當前利益。discount factor >= 1,算法可能無法收斂,期望價值不斷累加沒有衰減(discount),期望價值發散。discount factor一般比1稍小。Qnew(st,at)<-(1-α)·Qold(st,at)+α·(rt+1+γ·maxaQ(st+1,a)),Q-Learning學習過程式子。舊Q-Learning函數Qold(st,at),向學習目標(當前獲得Reward加下一步可獲得最大期望價值),按較小學習速率α學習,得到新Q-Learning函數Qnew(st,at)。學習速率決定新獲取樣本信息覆蓋率前掌握到信息比率,通常設較小值,保證學習過程穩定,確保最后收斂性。Q-Learning需要初始值Q0,比較高初始值,鼓勵模型多探索。
學習Q-Learning模型用神經網絡,得到模型是估值網絡。用比較深的神經網絡,就是DQN。Google DeepMind,《Nature》論文,《Human-level control through deep reinforcement learning》提出。DeepMind用DQN創建達到人類專家水平玩Atari2600系列游戲Agent。
state of the art DQN Trick。第一個Trick。DQN引入卷積層。模型通過Atari游戲視頻圖像了解環境信息并學習策略。DQN需要理解接收圖像,具有圖像識別能力。卷積神經網絡,利用可提取空間結構信息卷積層抽取特征。卷積層提取圖像中重要目標特征傳給后層做分類、回歸。DQN用卷積層做強化學習訓練,根據環境圖像輸出決策。
第二個Trick。Experience Replay。深度學習需要大量樣本,傳統Q-Learning online update方法(逐一對新樣本學習)不適合DQN。增大樣本,多個epoch訓練,圖像反復利用。Experience Replay,儲存Agent Experience樣本,每次訓練隨機抽取部分樣本供網絡學習。穩定完成學習任務,避免短視只學習最新接觸樣本,綜合反復利用過往大量樣本學習。創建儲存Experience緩存buffer,儲存一定量較新樣本。容量滿了,用新樣本替換最舊樣本,保證大部分樣本相近概率被抽到。不替換舊樣本,訓練過程被抽到概率永遠比新樣本高很多。每次需要訓練樣本,直接從buffer隨機抽取一定量給DQN訓練,保持樣本高利用率,讓模型學習到較新樣本。
第三個Trick。用第二個DQN網絡輔助訓練,target DQN,輔助計算目標Q值,提供學習目標公式里的maxaQ(st+1,a)。兩個網絡,一個制造學習目標,一個實際訓練,讓Q-Learning訓練目標保持平穩。強化學習 Q-Learning學習目標每次變化,學習目標分部是模型本身輸出,每次更新模型參數會導致學習目標變化,更新頻繁幅度大,訓練過程會非常不穩定、失控,DQN訓練會陷入目標Q值與預測Q值反饋循環(陷入震蕩發散,難收斂)。需要穩定target DQN輔助網絡計算目標Q值。target DQN,低頻率、緩慢學習,輸出目標Q值波動較小,減小訓練過程影響。
第4個Trick。Double DQN。DeepMind 《Deep Reinforcement Learning with Double Q-Learning》。傳統DQN高估Action Q值,高估不均勻,導致次優Action被高估超過最優Action。target DQN 負責生成目標Q值,先產生Q(st+1,a),再通過maxa選擇最大Q值。Double DQN,在主DQN上通過最大Q值選擇Action,再獲取Action在target DQN Q值。主網選擇Action,targetDQN生成Action Q值。被選擇Q值,不一定總是最大,避免被高估次優Action總是超過最優Action,導致發現不了真正最好Action。學習目標公式:Target=rt+1+γ·Qtarget(st+1,argmaxa(Qmain(st+1,a)))。
第5個Trick。Dueling DQN。Google 《Dueling Network Architectures for Deep Reinforcement Learning》。Dueling DQN,Q值函數Q(st,at)拆分,一部分靜態環境狀態具有價值V(st),Value;另一部分動態選擇Action額外帶來價值A(at),Advantage。公式,Q(st,at)=V(st)+A(at)。網絡分別計算環境Value和選擇Action Advantage。Advantage,Action與其他Action比較,零均值。網絡最后,不再直接輸出Action數量Q值,輸出一個Value,及Action數量 Advantage值。V值分別加到每個Advantage值上,得最后結果。讓DQN學習目標更明確,如果當前期望價值主要由環境狀態決定,Value值大,所有Advantage波動不大;如果期望價值主要由Action決定,Value值小,Advantage波動大。分解讓學習目標更穩定、精確,DQN對環境狀態估計能力更強。
實現帶Trick DQN。任務環境 GridWorld導航類水言纟工。GridWorld包含一個hero,4個goal,2個fire。控制hero移動,每次向上、下、左、右方向移動一步,多觸碰goal(獎勵值1),避開fire(獎勵值-1)。游戲目標,限度步數內拿到最多分數。Agent 直接通過GridWorld圖像學習控制hero移動最優策略。
創建GridWorld任務環境。載入依賴庫,itertools迭代操作,scipy.misc、matplotlib.pyplot繪圖,訓練時間長,os定期儲存模型文件。
創建環境內物體對象class。環境物體屬性,coordinates(x,y坐標)、size(尺寸)、intensity(亮度值)、channel(RGB顏色通道)、reward(獎勵值)、name(名稱)。
創建GridWorld環境class,初始化方法只傳入參數環境size。環境長、寬為輸入size,環境Action Space設4,初始化環境物體對象列表。self.reset()方法重置環境,得到初始observation(GridWorld圖像),plt.imshow展示observation。
定義環境reset方法。創建所有GridWorld物體,1個hero(用戶控制對象)、4個goal(reward 1)、2個fire(reward -1),添加到物體對象列表self.objects。self.newPosition()創建物體位置,隨機選擇沒有被占用新位置。物有物體size、intensity 1,hero channel 2(藍色),goal channel 1(綠色),fire channel 0(紅色)。self.renderEnv()繪制GridWorld圖像,state。
實現移動英雄角色方法,傳入值0?1?2?3四個數字,分別代表上、下、左、右。函數根據輸入操作英雄移動。如果移動該方向會導致英雄出界,不會進行任何移動。
定義newPosition方法,選擇一個跟現有物體不沖突位置。itertools.product方法得到幾個變量所有組合,創建環境size允許所有位置集合points,獲取目前所有物體位置集合currentPositions,從points去掉currentPositions,剩下可用位置。np.random.choice隨機抽取一個可用位置返回。
定義checkGoal函數。檢查hero是否觸碰goal、fire。從objects獲取hero,其他物體對象放到others列表。編歷others列表,如果物體和坐標與hero完全一致,判定觸碰。根據觸碰物體銷毀,self.newPosition()方法在隨機位置重新生成物體,返回物體reward值(goal 1,fire -1)。
創建長宛size+2?顏色通道數 3 圖片。初始值全1,代表全白色。最外圈內部像素顏色值全部賦0,代表黑色。遍歷物體對象列表self.objects,設置物體亮度值。scipy.misc.imresize將圖像從原始大小resize 84x84x3尺寸,正常游戲圖像尺寸。
定義GridWorld環境執行一步Action方法。輸入參數Action,self.moveChart(action)移動hero位置,self.checkGoal()檢測hero是否觸碰物體,得到reward、done標記。self.renderEnv獲取環境圖像state,返回state、reward、done。
調用gameEnv類初始化方法,設置size 5,創建5x5大小GridWorld環境,每次創建GridWorld環境隨機生成。小尺寸環境相對容易學習,大尺寸較難,訓練時間更長。
設計DQN(Deep Q-Network)網絡。使用卷積層,可以直接從環境原始像素學習策略。輸入scalarInput,扁平化長為84x84x3=21168向量,恢復成[-1,84,84,3]尺寸圖片ImageIn。tf.contrib.layers.convolution2d創建第1個卷積層,卷積核尺寸8x8,步長4x4,輸出通道數(filter數量)32,padding模型VALID,bias初始化器空。用4x4步長和VALID模型padding,第1層卷積輸出維度20x20x32。第2層卷積尺寸4x4,步長2x2,輸出通道數64,輸出維度9x9x64。第3層卷積尺寸3x3,步長1x1,輸出通道數64,輸出維度7x7x64。第4層卷積尺寸7x7,步長1x1,輸出通道數512,空間尺寸只允許在一個位置卷積,,輸出維度1x1x512。
tf.split(),第4個卷積層輸出conv4平均拆分兩段,streamAC、streamVC,Dueling DQN Advantage Function(Action帶來的價值)和Value Function(環境本身價值)。tf.split函數第2參數代表要拆分成幾段。第3參數代表要拆分幾個維度。tf.contrib.layers.flatten將streamAC和streamVC轉遍平的steamA和steamV。創建streamA和streamV線性全連接層參數AW和VW。tf.random_normal初始化權重,tf.matmul做全連接層矩陣乘法,得到self.Advantage和self.Value。Advantage針對Action,輸出數量為Action數量。Value針對環境統一的,輸出數量 1。Q值由Value、advantage復合成,Value加上減少均值Advantage。Advantage減去均值操作 tf.subtract,均值計算tf.reduce_mean函數(reduce_indices 1,代表Action數量維度)。最后輸出Action,Q值最大Action,tf.argmax。
定義Double DQN目標Q值targetQ輸入placeholder,Agent動作actions輸入placeholder。計算目標Q值,action由主DQN選擇,Q值由輔助target DQN生成。計算預測Q值,scalar形式actions轉onehot編碼形式,主DQN生成的Qout乘以actions_onehot,得預測Q值(Qout和actions都來自主DQN)。
定義loss,tf.square、tf.reduce_mean計算targetQ和Q均方誤差,學習速率1e-4 Adam優化器優化預測Q值和目標Q值偏差。
實現Experience Replay策略。定義experience_buffer class。初始化定義buffer_size存儲樣本最大容量,創建buffer列表。定義向經buffer添加元素方法。如果超過buffer最大容量,清空最早樣本,列表末尾添加新元素。定義樣本抽樣方法,用random.sample()函數隨機抽取一定數量樣本。
定義84x84x3 states扁平化 1維向量函數processState,方便后面堆疊樣本。
updateTargetGraph函數,更新target DQN模型參數(主DQN用DQN class self.updateModel方法更新模型參數)。輸入變量tfVars,TensorFlow Graph全部參數。tau,target DQN向主DQN學習的速率。函數updateTargetGraph取tfVars前一半參數,主DQN模型參數。再令輔助targetDQN參數朝向主DQN參數前進很小比例(tau,0.001),target DQN緩慢學習主DQN。訓練時,目標Q值不能在幾次迭代間波動太大,訓練非常不穩定、失控,陷入目標Q值和預測Q值反饋循環。需要穩定目標Q值訓練網絡,緩慢學習target DQN網絡輸出目標Q值,主網絡優化目標Q值和預測Q值間loss,target DQN跟隨主DQN緩慢學習。函數updateTargetGraph創建更新target DQN模型參數操作,函數updateTarget執行操作。
DQN網絡訓練過程參數。batch_size,每次從experience buffer獲取樣本數,32。更新頻率update_freq,每隔多少step執行一次模型參數更新,4。Q值衰減系數(discount factor)γ,0.99。startE起始執行隨機Action概率。endE最終執行隨機Action概率。anneling_steps從初始隨機概率降到最終隨機概率所需步數。num_episodes總共多少次GridWorld環境試驗。pre_train_steps正式用DQN選擇Action前進行多少步隨機Action測試。max_epLength每個episode進行多少步Action。load_model是否讀取之前訓練模型。path模型儲存路徑。h_size是DQN網絡最后全連接層隱含節點數。tau是target DQN向主DQN學習速率。
Qnetwork類初始化mainQN和輔助targetQN。初始化所有模型參數。trainables獲取所有可訓練參數。updateTargetGraph創建更新target DQN模型參數操作。
experience_buffer創建experience replay class,設置當前隨機Action概率e,計算e每一步衰減值stepDrop。初始化儲存每個episode的reward列表rList,總步數total_steps。創建模型訓練保存器(Saver)檢查保存目錄是否存在。
創建默認Session,如果load_model標志True,檢查模型文件路徑checkpoint,讀取載入已保存模型。執行參數初始化操作,執行更新targetQN模型參數操作。創建GridWorld試驗循環,創建每個episode內部experience_buffer,內部buffer不參與當前迭代訓練,訓練只使用之前episode樣本。初始化環境得第一個環境信息s,processState()函數扁平化。初始化默認done標記d、episode內總reward值rAll、episode內步數j。
創建內層循環,每次迭代執行Action。總步數小于pre_train_steps,強制用隨機Action,只從隨機Action學習,不強化過程。達到pre_train_steps,保留較小概率隨機選擇Action。不隨機選擇Action,傳入當前狀態s給主DQN,預測得到應該執行Action。env.step()執行一步Action,得到接下來狀態s1?reward、done標記。processState對s1扁平化處理,s、a、r、s1?d傳入episodeBuffer存儲。
總步數超過pre_train_steps,持續降低隨機選擇Action概率e,直到最低值endE。每當總步數達到update_freq整數部,進行一次訓練,模型參數更新。從myBuffer中sample出一個batch_size樣本。訓練樣本第3列信息,下一狀態s1,傳入mainQN,執行main.predict,得到主模型選擇Action。s1傳入輔助targetQN,得到s1狀態下所有Action的Q值。mainQN輸出Action ,選擇targetQN輸出Q,得到doubleQ。兩個DQN網絡把選擇Action和輸出Q值兩個操作分隔開,Double DQN。訓練樣本第2列信息,當前reward,加doubleQ乘以衰減系數γ,得到學習目標targetQ。傳入當前狀態s,學習目標targetQ和實際采取Action,執行updateTarget函數,執行targetQN模型參數更新(緩慢向mainQN學習)。完整完成一次訓練過程。每個step結束,累計當前這步獲取reward,更新當前狀態為下一步試驗做準備。如果done標記為True,直接中斷episode試驗。
episode內部episodeBuffer添加到myBuffer,作以后訓練抽樣數據集。當前episode reward添加到rList。每25個episode展示平均reward值。每1000個episode或全部訓練完成,保存當前模型。
初始200個episode內,完全隨機Action的前10000步內,平均可以獲得reward在2附近,基礎baseline。
訓練最后episode輸出,平均reward 22,非常大提升。
計算每100個episode平均reward,plt.plot展示reward變化趨勢。從第1000個episode開始,reward快速提升,到第4000個episode基本達到高峰,后面進入平臺期,提升不大。
import numpy as np
import random
import tensorflow as tf
import os
%matplotlib inline
from gridworld import gameEnv
env = gameEnv(size=5)
class Qnetwork():
def __init__(self,h_size):
#The network recieves a frame from the game, flattened into an array.
#It then resizes it and processes it through four convolutional layers.
self.scalarInput = tf.placeholder(shape=[None,21168],dtype=tf.float32)
self.imageIn = tf.reshape(self.scalarInput,shape=[-1,84,84,3])
self.conv1 = tf.contrib.layers.convolution2d( \
inputs=self.imageIn,num_outputs=32,kernel_size=[8,8],stride=[4,4],padding='VALID', biases_initializer=None)
self.conv2 = tf.contrib.layers.convolution2d( \
inputs=self.conv1,num_outputs=64,kernel_size=[4,4],stride=[2,2],padding='VALID', biases_initializer=None)
self.conv3 = tf.contrib.layers.convolution2d( \
inputs=self.conv2,num_outputs=64,kernel_size=[3,3],stride=[1,1],padding='VALID', biases_initializer=None)
self.conv4 = tf.contrib.layers.convolution2d( \
inputs=self.conv3,num_outputs=512,kernel_size=[7,7],stride=[1,1],padding='VALID', biases_initializer=None)
#We take the output from the final convolutional layer and split it into separate advantage and value streams.
self.streamAC,self.streamVC = tf.split(self.conv4,2,3)
self.streamA = tf.contrib.layers.flatten(self.streamAC)
self.streamV = tf.contrib.layers.flatten(self.streamVC)
self.AW = tf.Variable(tf.random_normal([h_size//2,env.actions]))
self.VW = tf.Variable(tf.random_normal([h_size//2,1]))
self.Advantage = tf.matmul(self.streamA,self.AW)
self.Value = tf.matmul(self.streamV,self.VW)
#Then combine them together to get our final Q-values.
self.Qout = self.Value + tf.subtract(self.Advantage,tf.reduce_mean(self.Advantage,reduction_indices=1,keep_dims=True))
self.predict = tf.argmax(self.Qout,1)
#Below we obtain the loss by taking the sum of squares difference between the target and prediction Q values.
self.targetQ = tf.placeholder(shape=[None],dtype=tf.float32)
self.actions = tf.placeholder(shape=[None],dtype=tf.int32)
self.actions_onehot = tf.one_hot(self.actions,env.actions,dtype=tf.float32)
self.Q = tf.reduce_sum(tf.multiply(self.Qout, self.actions_onehot), reduction_indices=1)
self.td_error = tf.square(self.targetQ - self.Q)
self.loss = tf.reduce_mean(self.td_error)
self.trainer = tf.train.AdamOptimizer(learning_rate=0.0001)
self.updateModel = self.trainer.minimize(self.loss)
class experience_buffer():
def __init__(self, buffer_size = 50000):
self.buffer = []
self.buffer_size = buffer_size
def add(self,experience):
if len(self.buffer) + len(experience) >= self.buffer_size:
self.buffer[0:(len(experience)+len(self.buffer))-self.buffer_size] = []
self.buffer.extend(experience)
def sample(self,size):
return np.reshape(np.array(random.sample(self.buffer,size)),[size,5])
def processState(states):
return np.reshape(states,[21168])
def updateTargetGraph(tfVars,tau):
total_vars = len(tfVars)
op_holder = []
for idx,var in enumerate(tfVars[0:total_vars//2]):
op_holder.append(tfVars[idx+total_vars//2].assign((var.value()*tau) + ((1-tau)*tfVars[idx+total_vars//2].value())))
return op_holder
def updateTarget(op_holder,sess):
for op in op_holder:
sess.run(op)
batch_size = 32 #How many experiences to use for each training step.
update_freq = 4 #How often to perform a training step.
y = .99 #Discount factor on the target Q-values
startE = 1 #Starting chance of random action
endE = 0.1 #Final chance of random action
anneling_steps = 10000. #How many steps of training to reduce startE to endE.
num_episodes = 10000#How many episodes of game environment to train network with.
pre_train_steps = 10000 #How many steps of random actions before training begins.
max_epLength = 50 #The max allowed length of our episode.
load_model = False #Whether to load a saved model.
path = "./dqn" #The path to save our model to.
h_size = 512 #The size of the final convolutional layer before splitting it into Advantage and Value streams.
tau = 0.001 #Rate to update target network toward primary network
tf.reset_default_graph()
mainQN = Qnetwork(h_size)
targetQN = Qnetwork(h_size)
init = tf.global_variables_initializer()
trainables = tf.trainable_variables()
targetOps = updateTargetGraph(trainables,tau)
myBuffer = experience_buffer()
#Set the rate of random action decrease.
e = startE
stepDrop = (startE - endE)/anneling_steps
#create lists to contain total rewards and steps per episode
rList = []
total_steps = 0
#Make a path for our model to be saved in.
saver = tf.train.Saver()
if not os.path.exists(path):
os.makedirs(path)
#%%
with tf.Session() as sess:
if load_model == True:
print('Loading Model...')
ckpt = tf.train.get_checkpoint_state(path)
saver.restore(sess,ckpt.model_checkpoint_path)
sess.run(init)
updateTarget(targetOps,sess) #Set the target network to be equal to the primary network.
for i in range(num_episodes+1):
episodeBuffer = experience_buffer()
#Reset environment and get first new observation
s = env.reset()
s = processState(s)
d = False
rAll = 0
j = 0
#The Q-Network
while j < max_epLength: #If the agent takes longer than 200 moves to reach either of the blocks, end the trial.
j+=1
#Choose an action by greedily (with e chance of random action) from the Q-network
if np.random.rand(1) < e or total_steps < pre_train_steps:
a = np.random.randint(0,4)
else:
a = sess.run(mainQN.predict,feed_dict={mainQN.scalarInput:[s]})[0]
s1,r,d = env.step(a)
s1 = processState(s1)
total_steps += 1
episodeBuffer.add(np.reshape(np.array([s,a,r,s1,d]),[1,5])) #Save the experience to our episode buffer.
if total_steps > pre_train_steps:
if e > endE:
e -= stepDrop
if total_steps % (update_freq) == 0:
trainBatch = myBuffer.sample(batch_size) #Get a random batch of experiences.
#Below we perform the Double-DQN update to the target Q-values
A = sess.run(mainQN.predict,feed_dict={mainQN.scalarInput:np.vstack(trainBatch[:,3])})
Q = sess.run(targetQN.Qout,feed_dict={targetQN.scalarInput:np.vstack(trainBatch[:,3])})
doubleQ = Q[range(batch_size),A]
targetQ = trainBatch[:,2] + y*doubleQ
#Update the network with our target values.
_ = sess.run(mainQN.updateModel, \
feed_dict={mainQN.scalarInput:np.vstack(trainBatch[:,0]),mainQN.targetQ:targetQ, mainQN.actions:trainBatch[:,1]})
updateTarget(targetOps,sess) #Set the target network to be equal to the primary network.
rAll += r
s = s1
if d == True:
break
#Get all experiences from this episode and discount their rewards.
myBuffer.add(episodeBuffer.buffer)
rList.append(rAll)
#Periodically save the model.
if i>0 and i % 25 == 0:
print('episode',i,', average reward of last 25 episode',np.mean(rList[-25:]))
if i>0 and i % 1000 == 0:
saver.save(sess,path+'/model-'+str(i)+'.cptk')
print("Saved Model")
saver.save(sess,path+'/model-'+str(i)+'.cptk')
#%%
rMat = np.resize(np.array(rList),[len(rList)//100,100])
rMean = np.average(rMat,1)
plt.plot(rMean)
參考資料:
《TensorFlow實戰》
歡迎付費咨詢(150元每小時),我的微信:qingxingfengzi