本文是對Arthur Juliani在Medium平台發布的強化學習系列教程的個人中文翻譯。(This article is my personal translation for the tutorial written and posted by Arthur Juliani on Medium.com。)
原文地址(URL for original article):https://medium.com/emergent-future/simple-reinforcement-learning-with-tensorflow-part-0-q-learning-with-tables-and-neural-networks-d195264329d0
我們將學習如何解決OpenAI的冰湖(FrozenLake)問題。不過我們的冰湖版本和上圖呈現的圖片可不太一樣~
作為本強化學習教程系列的第一章,我們將一同探索強化學習算法的一個大家庭———Q-Learning算法—。它們和后面章節基於策略的算法(Policy-based algorithms)(1-3 part)有些不一樣。相對於用一個復雜而臃腫的深度神經網絡,我們將以實現一個簡單的查閱表(lookup-table)版本的算法為初始目標,然后再展示如何用Tensorflow框架來實現一個等價的神經網絡版本的算法。考慮到我們將回歸基礎知識,所以本教程可以視作系列教程的第0部分。希望本教程能夠幫助你理解Q-Learning的工作原理,基於此,我們將最終結合策略梯度(Policy Gradient)和Q-Learning來構造最先進的強化學習代理(Agents)。(如果你對策略網絡(Policy Networks)更感興趣,或者早就掌握了Q-Learning,你可以直接從這里開始。)
不像策略梯度方法那樣試圖學習到可以將一個觀察(Observation)與一個動作(action)直接映射的函數,Q-Learning會嘗試去學習給定一個狀態(State)下,采取某個具體動作的值。雖然這兩種方式最終都可以實現在給定情況下的智能行為決策,但是如何最終完成行為選擇的過程是非常不一樣的。你可能聽說過可以玩Atari游戲的深度Q網絡(Deep Q-Networs),而它其實本質上也就是更復雜的Q-Learning算法的實現。
表環境下的表方法(Tabular Approaches for Tabular Environments)
冰湖環境的規則:
在這個教程里,我們將試圖解決OpenAI的gym項目中包含的冰湖FrozenLake問題。對於不熟悉gym項目的人來說,OpenAI gym提供了一種能方便簡易地在一系列游戲中,對學習代理(learning agents)進行各類實驗或測試的方式。而其中之一的冰湖環境是一個4*4的網格。格子包含三種類型:起始格,目標格,安全的冰格和危險的冰洞。我們的目標是讓一個agent能夠自己從起點到終點,並且不會在中途掉入洞中。在任何時候,agent都可以選擇從上,下,左,右四個中選擇一個方向進行一步移動。而后會使問題變復雜的是,在冰湖環境中,還有風會偶爾把agent吹得偏離到一個並非它移動目標的格子上。因此,在這種環境下agent就不太可能每次都能表現完美,但是學習如何去避免掉入洞中並達到目標格還是可以做到的。agent每走一步的回報(reward)都是0,除了在達到目標時為1。因此,我們需要一個算法可以學習到長期期望回報。這也就是Q-Learning發揮用處的地方。
在這個算法最簡單的實現中,Q-Learning會以一個表(table)的形式呈現。這個表的行代表不同的狀態(所在方格的坐標),列代表不同的可采取的行動(上、下、左、右移動一步)。在冰湖環境中,我們有16個狀態(每個方格算一個),以及四種動作,因此我們將得到一個16*4的Q值表,這個表的每一個具體值的含義是:該狀態下,采取該行為的長期期望回報。我們從初始化一個一致的Q值表(全部賦值為0)開始,再根據我們觀察得到的對各種行動的回報來更新Q值表的對應值。
我們對Q值表的更新准則是貝爾曼方程(Bellman Equation)。貝爾曼方程告訴我們:一個給定行動的期望長期回報等於【當前回報】加上【下一個狀態下,采取期望中所能帶來最優未來長期回報的行為對應的回報】。因此,我們可以在Q表上估計如何對未來行動的Q值進行更新。貝爾曼方程用數學公式表示如下:
這個公式的描述了一個給定狀態s下,采取行動a的Q值等於當即獲得的回報r加上一個折現因子y乘以能夠最大化的在下一狀態s’采取時能獲得的最大長期回報的動作a’對應的長期回報。折現因子y允許我們決定相對於當前就可以獲得的回報,未來的可能回報的相對重要性。通過這種方式,Q表會慢慢開始獲得更准確的任一給定狀態下,采取任意動作所對應的期望未來回報值。以下是Python版的對於Q表版本的冰湖環境解決方案的完整實現:
import gym
import numpy as np
env = gym.make('FrozenLake-v0')
# Implement Q-Table learning algorithm
# 實現Q表學習算法
# Initialize table with all zeros
# 初始化Q表為全0值
Q = np.zeros([env.observation_space.n,env.action_space.n])
# Set learning parameters
# 設置學習參數
lr = .8
y = .95
num_episodes = 2000
# create lists to contain total rewards and steps per episode
# 創建列表以包含每個episode的總回報與總步數
#jList = []
rList = []
for i in range(num_episodes):
# Reset environment and get first new observation
# 初始化環境並獲得第一個觀察
s = env.reset()
rAll = 0
d = False
j = 0
# The Q-Table learning algorithm
# Q表學習算法
while j < 99:
j+=1
# Choose an action by greedily (with noise) picking from Q table
# 基於Q表貪婪地選擇一個最優行動(有噪音干擾)
a = np.argmax(Q[s,:] + np.random.randn(1,env.action_space.n)*(1./(i+1)))
# Get new state and reward from environment
# 從環境中獲得回報和新的狀態信息
s1,r,d,_ = env.step(a)
#Update Q-Table with new knowledge
# 用新的知識更新Q表
Q[s,a] = Q[s,a] + lr*(r + y*np.max(Q[s1,:]) - Q[s,a])
rAll += r
s = s1
if d == True:
break
#jList.append(j)
rList.append(rAll)
print("Score over time: " + str(sum(rList)/num_episodes))
print("Final Q-Table Values")
print(Q)
(感謝Praneet D找到了該實現方法對應的最優的超參數)
基於神經網絡的Q-Learning(Q-Learning with Neural Networks)
現在你可能認為:表格方法挺好的,但是它不能規模化(scale),不是嗎?因為對一個簡單的網格世界建立一個16*4的表是很容易的,但是在任何一個現在的游戲或真實世界環境中都有無數可能的狀態。對於大多數有趣的問題,表格都無法發揮出作用。因此,我們需要一些代替性的方案來描述我們的狀態,並生成對應動作的Q值:這也就是神經網絡(Neural Network,簡稱NN)可以大展身手的地方。NN可以作為一個動作估計器(function approximator),我們能夠輸入任意多的可能狀態,因為所有狀態都可以被編碼成一個個向量,並把它們和各自對應的Q值進行對應(map)。
在冰湖例子中,我們將使用一個一層的NN,它接受以one-hot形式編碼的狀態向量(1*16),並輸出一個含有4個Q值的向量,每個分量對應一個動作的長期期望回報。這樣一個簡單的NN就像一個強化版的Q表,而網絡的權重就發揮着曾經的Q表中的各個單元格的作用。關鍵的不同時我們可以方便簡易地擴充Tensorflow網絡,包括加新的層,激活函數以及不同的輸出類型,而這些都是一個一般的表格無法做到的。於是,更新的方法也發生了一點變化。相比於之前直接更新表格,我們現在將使用逆傳播(backpropagation)和損失函數(loss function)**來完成更新。我們的損失函數采取平方和損失的形式,即加總當前預測的Q值與目標值間的差值的平方,並以梯度形式在網絡中傳播。這種情況下,所選行動的目標Q值依然采用上面提到的貝爾曼方程中的計算方法。
以下是用Tensorflow實現簡單Q網絡的完整代碼:
# Q-Network Learning
# Q網絡學習
import gym
import numpy as np
import random
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
# Load the environment
# 加載環境
env = gym.make('FrozenLake-v0')
# The Q-Network Approach
# Q網絡方法
# Implementing the network itself
# 實現網絡
tf.reset_default_graph()
# These lines establish the feed-forward part of the network used to choose actions
# 下面的幾行代碼建立了網絡的前饋部分,它將用於選擇行動
inputs1 = tf.placeholder(shape=[1,16],dtype=tf.float32)
W = tf.Variable(tf.random_uniform([16,4],0,0.01))
Qout = tf.matmul(inputs1,W)
predict = tf.argmax(Qout,1)
# Below we obtain the loss by taking the sum of squares difference between the target and prediction Q values.
# 下面的幾行代碼可以獲得預測Q值與目標Q值間差值的平方和加總的損失。
nextQ = tf.placeholder(shape=[1,4],dtype=tf.float32)
loss = tf.reduce_sum(tf.square(nextQ - Qout))
trainer = tf.train.GradientDescentOptimizer(learning_rate=0.1)
updateModel = trainer.minimize(loss)
# Training the network
# 訓練網絡
init = tf.initialize_all_variables()
# Set learning parameters
# 設置學習參數
y = .99
e = 0.1
num_episodes = 2000
#create lists to contain total rewards and steps per episode
# 創建列表以包含每個episode對應的總回報與總步數。
jList = []
rList = []
with tf.Session() as sess:
sess.run(init)
for i in range(num_episodes):
# Reset environment and get first new observation
# 初始化環境並獲得第一個觀察
s = env.reset()
rAll = 0
d = False
j = 0
# The Q-Network
# Q網絡
while j < 99:
j+=1
#Choose an action by greedily (with e chance of random action) from the Q-network
# 基於Q網絡的輸出結果,貪婪地選擇一個行動(有一定的概率選擇隨機行動)
a,allQ = sess.run([predict,Qout],feed_dict={inputs1:np.identity(16)[s:s+1]})
if np.random.rand(1) < e:
a[0] = env.action_space.sample()
# Get new state and reward from environment
# 從環境中獲得回報以及新的狀態信息
s1,r,d,_ = env.step(a[0])
# Obtain the Q' values by feeding the new state through our network
# 通過將新的狀態向量輸入到網絡中獲得Q值。
Q1 = sess.run(Qout,feed_dict={inputs1:np.identity(16)[s1:s1+1]})
# Obtain maxQ' and set our target value for chosen action.
# 獲得最大的Q值,並為所選行為設定目標值
maxQ1 = np.max(Q1)
targetQ = allQ
targetQ[0,a[0]] = r + y*maxQ1
# Train our network using target and predicted Q values
# 用目標和預測的Q值訓練網絡
_,W1 = sess.run([updateModel,W],feed_dict={inputs1:np.identity(16)[s:s+1],nextQ:targetQ})
rAll += r
s = s1
if d == True:
# Reduce chance of random action as we train the model.
# 隨着訓練的進行,主鍵減少選擇隨機行為的概率
e = 1./((i/50) + 10)
break
jList.append(j)
rList.append(rAll)
print("Percent of succesful episodes: " + str(sum(rList)/num_episodes) + "%")
# Percent of succesful episodes: 0.352%
# 成功的episode比例:0.352%
# Some statistics on network performance
# 一些網絡性能的統計量
# We can see that the network beings to consistently reach the goal around the 750 episode mark.
# 我們可以看到,當網絡訓練了750個episode左右的時候,agent就可以比較穩定地到達目標了。
plt.plot(rList)
# It also begins to progress through the environment for longer than chance aroudn the 750 mark as well.
# 在750個episode之后,agent也一般需要更長的步數以到達目標。
plt.plot(jList)
雖然網絡學會了解決冰湖問題,但是結果表明它似乎比如Q表方法那么高效。即雖然神經網絡在Q-Learning問題上提供了更高的靈活性,但它也犧牲了一定的穩定性。還有很多可能的對我們的簡單Q網絡進行擴展的辦法,這些擴展可以讓NN獲得更好的性能並實現更穩定的學習過程。兩種需要提到的特別技巧分別是經驗重放(Experience Replay)和冰凍目標網絡(Freezing Target Networks)。這些改進方式或者其它的一些技巧都是讓DQN可以玩Atari游戲的關鍵所在,並且我們也將在后面探索這些相關知識。對於有關Q-Learning的更多理論,可以看Tambet Matiisen的這篇博文,希望本教程可以幫助對實現簡單Q-Learning算法的人們一些幫助!
如果這篇博文對你有幫助,你可以考慮捐贈以支持未來更多的相關的教程、文章和實現。對任意的幫助與貢獻都表示非常感激!
如果你想跟進我在深度學習、人工智能、感知科學方面的工作,可以在Medium上follow我 @Arthur Juliani,或者推特@awjliani。
用Tensorflow實現簡單強化學習的系列教程:
- Part 0 — Q-Learning Agents
- Part 1 — Two-Armed Bandit
- Part 1.5 — Contextual Bandits
- Part 2 — Policy-Based Agents
- Part 3 — Model-Based RL
- Part 4 — Deep Q-Networks and Beyond
- Part 5 — Visualizing an Agent’s Thoughts and Actions
- Part 6 — Partial Observability and Deep Recurrent Q-Networks
- Part 7 — Action-Selection Strategies for Exploration
- Part 8 — Asynchronous Actor-Critic Agents (A3C)