1. 程式人生 > 其它 >TensorFlow強化學習入門(0)——Q-Learning的查詢表實現和神經網路實現

TensorFlow強化學習入門(0)——Q-Learning的查詢表實現和神經網路實現

我們將學習如何處理OpenAI FrozenLake問題,當然我們的問題不像圖片中那樣逼真

在我這系列的強化學習教程中,我們將探索強化學習大家族中的Q-Learning演算法,它和我們後面的教程(1-3)中基於策略的演算法有一些差異。在本節中,我們先放下複雜而笨重的深度神經網路,首先在一個簡單的查詢表基礎上實現第一個演算法版本,隨後我們再考慮如何使用TensorFlow將神經網路的形式整合進來。考慮到該節主要是回顧基礎知識,所以我把它歸為第0部分。對Q-Learning中發生的細節有所瞭解對於我們後面學習將策略梯度(policy gradient)和Q-Learning結合來構建先進的RL agent大有裨益。(如果你對策略網路更感興趣或者已經掌握了Q-Learning相關知識,可以等譯者後面的翻譯或者

查閱原文

與利用函式直接將當前觀測轉化為行動的策略梯度方法不同,Q-Learning嘗試學習給定狀態下的對應值並據此在給定狀態下作出特定的行動。儘管兩者作出行動的手段不同,但是都可以達到在給定場合下作出智慧的行動的效果。你之前可能聽說過深度Q-網路已經可以玩雅達利遊戲了。這其實只是我們下面討論的Q-Learning演算法的更大更復雜的實現而已。

查詢表實現

# FrozenLake 問題的規則
SFFF       (S: 起始點, 安全)
FHFH       (F: 冰層, 安全)
FFFH       (H: 空洞, 跌落危險)
HFFG       (G: 目的地, 飛盤所在地)

本教程會基於

OpenAI gym嘗試解決上述的FrozenLake問題。OpenAI gym給定了描述這個簡單遊戲的陣列,人們可以讓agent基於此進行學習。FrozenLake問題發生在一個4*4的網格區域上,其中包括起始區,安全冰層區,危險空洞區和目標地點,,在任意的時刻agent可以上下左右移動,我們的目標是讓agent在不跌落至空洞的前提下到達目的地。這裡有一個特殊的問題就是偶爾會有一陣風吹過,使agent被吹到並非它選擇的區域。因此在這個問題中每一時刻都作出最優解是不可能的,但是避開空洞抵達目的地還是可行的。只有到達目的地才可以得到1分,除此之外都是0分。由此我們需要一個基於長期過程後的獎懲進行學習的演算法,這正是Q-Learning設計的目的。

在最簡單的解法中,Q-Learning就是一個表格,包含了問題中所有可能發生情況,表格中的值表徵著我們在當前情況下應當作出什麼行動。在FrozenLake問題中,有16個狀態(每一個表格單元對應一個情況),4個可選行動,這產生了一個16*4的Q值表格。我們首先將表格初始化為全0,當有行動得分之後我們據此對錶格進行更新。

我們使用貝爾曼方程對Q值表進行更新,貝爾曼方程可以將一系列行動帶來的獎勵值分配至當前的行動上。在這個方法中,我們不斷根據未來決策得到的獎勵值來更新當前的表格! 其數學表示式可以寫作:

Eq 1. Q(s,a) = r + γ(max(Q(s’,a’))

這個方程中,當前狀態(s)和行動(a)對應的Q值等於當前的獎勵值(r)加上折算係數乘上當前行動後的狀態和下一步行動可能產生的最大Q值。這個可變的折算值可以控制可能的未來獎勵值和當前獎勵值相比下的重要程度。通過這種方法,表格開始緩慢逼近得到預期獎勵值所需的當前各行動的精確度量值。下面給出FrozenLake問題中Q表演算法的Python實現:

# Q-Table Learning
import gym
import numpy as np
# 載入實驗環境
env = gym.make('FrozenLake-v0')

# 整合Q表學習演算法
# 初始表(全0)
Q = np.zeros([env.observation_space.n,env.action_space.n])
# 設定超引數
lr = .8
y = .95
num_episodes = 2000
# 建立episodes中包含當前獎勵值和步驟的列表
rList = []
for i in range(num_episodes):
    # 初始化環境,得到第一個狀態觀測值
    s = env.reset()
    rAll = 0
    d = False
    j = 0
    # Q表學習演算法
    while j < 99:
        j += 1
        # 根據Q表和貪心演算法(含噪)選定當前的動作
        a = np.argmax(Q[s,:] + np.random.randn(1, env.action_space.n) * (1./(i+1)))
        # 獲取新的狀態值和獎勵值
        s1, r, d, _ = env.step(a)
        # 更新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
    rList.append(rAll)

print("Score over time: " +  str(sum(rList)/num_episodes))
print("Final Q-Table Values")
print(Q)

神經網路實現

在完成上面的例子的過程中,你可能已經意識到這一點:用表格的方式來實現固然不錯,但是彈性太差了。上述的簡單問題使用表格實現是很簡單,但是有可能問題中的狀態(s)和行動(a)會多到無法用表格來儲存。不幸的是,大部分我們感興趣的問題中可能的狀態數和行動數都很多,無法使用上面的表格解法。這迫使我們尋找一種新的方式來描述狀態,不再依賴Q表來決定下一步的行動:這正是神經網路擅長的地方。通過函式逼近的方法,我們可以將任意的狀態表示為向量形式並通過對映得到Q值。

在FrozenLake的例子中,我們使用單層網路來接受虛擬編碼(One-hot encoding)後的當前狀態(1x16),輸出為包含4個Q值的向量,每個Q值對應一個方向。這樣一個簡單的網路就可以充當上面的獎勵值表格,網路中的權重值取代了之前的表格單元。更關鍵的一點是我們可以嘗試增加層數,啟用函式和不同的輸入型別,這些在常規的表格中都是不可能實現的。除此之外,神經網路的更新方法也更勝一籌,和表格中直接更新值的做法不同,神經網路通過損失函式和反向傳播的結合來實現權重更新。我們選取目標Q值和當前Q值差的平方和作為損失函式,“目標”值在計算之後其梯度會反饋於網路上。在理想的情況下,每一步之後的Q值應當都是不變的(當然如果一步一颳風的情況就不一定了~)

Eq2. Loss = ∑(Q-target - Q)²

下面給出我們的簡易Q網路的TensorFlow整合:

import gym
import numpy as np
import random
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline

# 載入實驗環境
env = gym.make('FrozenLake-v0')
# Q網路解法
tf.reset_default_graph()
# 建立用於選擇行為的網路的前向傳播部分
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)
# 計算預期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)
# 訓練網路
init = tf.initialize_all_variables()
# 設定超引數
y = .99
e = 0.1
num_episodes = 2000 # 為了快速設定為2000,實驗調為20000時可以達到0.6的成功率
# 建立episodes中包含當前獎勵值和步驟的列表
jList = []
rList = []
with tf.Session() as sess:
    sess.run(init)
    for i in range(num_episodes):
        # 初始化環境,得到第一個狀態觀測值
        s = env.reset()
        rAll = 0
        d = False
        j = 0
        # Q網路
        while j < 99:
            j += 1
            # 根據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()
            # 獲取新的狀態值和獎勵值
            s1, r, d, _ = env.step(a[0])
            # 通過將新的狀態值傳入網路獲取Q'值
            Q1 = sess.run(Qout, feed_dict={inputs1:np.identity(16)[s1:s1+1]})
            # 獲取最大的Q值並選定我們的動作
            maxQ1 = np.max(Q1)
            targetQ = allQ
            targetQ[0, a[0]] = r + y*maxQ1
            # 用目標Q值和預測Q值訓練網路
            _, W1 = sess.run([updateModel, W], feed_dict={inputs1:np.identity(16)[s:s+1], nextQ:targetQ})
            rAll += r
            s = s1
            if d == True:
                # 隨著訓練的進行不斷減小隨機行動的可能性
                e = 1./((i/50) + 10)
                break
        jList.append(j)
        rList.append(rAll)
print("Percent of succesful episodes: " + str(sum(rList)/num_episodes))
# 網路效能統計
plt.plot(rList)
plt.plot(jList)

雖然這個網路可以解決FrozenLake的問題,但是效率遠遠不及Q表。在Q-Learning中神經網路解法的靈活性是以犧牲穩定性的代價換來的。在我們上面簡單的網路的基礎上,我們有很多可供選擇的擴充套件來提供更好的效能和更健壯的學習。這裡特別強調兩個方法:歷程重現(Experience Replay)和目標網路凍結(Freezing Target Networks),這些調整和提升是深度Q網路能夠玩轉雅達利遊戲的關鍵,這些部分我們在後面會一一提及。如果想進一步瞭解Q-Learning背後的理論基礎,可以參考Tambet Matiisen的這篇文章。希望這篇文章可以幫助到對Q-Learning演算法感興趣的同學:)

譯者計劃翻譯的系列文章:

  1. (0)Q-Learning的查詢表實現和神經網路實現
  2. (1) 雙臂賭博機
  3. Part 1.5 — Contextual Bandits
  4. Part 2 — Policy-Based Agents
  5. Part 3 — Model-Based RL
  6. Part 4 — Deep Q-Networks and Beyond
  7. Part 5 — Visualizing an Agent’s Thoughts and Actions
  8. Part 6 — Partial Observability and Deep Recurrent Q-Networks
  9. Part 7 — Action-Selection Strategies for Exploration
  10. Part 8 — Asynchronous Actor-Critic Agents (A3C)