天天看點

從零開始Q-Learning,用強化學習教計程車接送乘客

可能大多數人都聽說過 AI 現在可以自己學習玩電腦遊戲了,一個衆所周知的例子就是2016年 Deepmind 的 AlphaGo 擊敗了南韓圍棋世界冠軍。在此之前也有許多成功嘗試開發 AI 來玩 Atari 遊戲的工作,如 Breakout、Pong 和 Space Invaders。這些工作中的每一個都遵循稱為強化學習的機器學習範式。如果您以前從未接觸過強化學習,本文将提供一個非常直接的例子,說明它是如何工作的。

預備知識:
  1. Python程式設計基礎
  2. 一些線性代數

你将學到:

  1. 什麼是強化學習以及它是如何工作的
  2. 如何使用 OpenAI Gym
  3. 如何在 Python 中實作 Q-Learning

什麼是強化學習

設想一個訓練寵物狗新技巧的場景:狗聽不懂人類的語言,是以我們不能直接告訴它該怎麼做。我們可以模拟一種情況(或提示),而狗會試圖以許多不同的方式做出反應。如果狗的反應是我們想要的,就用零食獎勵它們,那麼下一次狗遇到同樣的情況時,大機率會以更熱情的方式執行類似的動作,期待更多的食物。這就像從積極的經曆中學習“做什麼”一樣。同樣,狗也會傾向于學習在面對負面經曆時不該做什麼。

這正是強化學習的工作方式:

  1. 狗就是暴露在環境中的智能體(agent)。環境可以是客廳或草坪,随你。
  2. 你和狗目前的情況就類似于一種狀态。狀态的一個例子可能是你的狗站着,而你在客廳裡以某種語氣說出特定的詞。
  3. 智能體可以通過執行一個動作從一種“狀态”轉換到另一種“狀态”,例如,你的狗會從站立狀态變為坐着狀态。
  4. 轉移後,智能體可能會獲得獎勵或懲罰作為回報。例如你給它的零食或搖搖手指作為懲罰。
  5. 政策是指在給定狀态下選擇合适的行動以期望更好的結果。

強化學習介于有監督學習和無監督學習之間,有一些重要的事情需要注意:

  1. **貪心并不用是有效。**有些事情很容易獲得即時滿足,而有些事情可以提供長期獎勵,強化學習的目标不是貪心地尋找快速的即時獎勵,而是在整個訓練過程中優化以獲得最大的累計獎勵。
  2. **序列在強化學習中很重要。**智能體獲得的獎勵不僅依賴于目前狀态,還依賴于曆史狀态。與有監督學習和無監督學習不同,時間序列很重要。

強化學習過程

從零開始Q-Learning,用強化學習教計程車接送乘客

在某種程度上,強化學習是利用經驗做出最佳決策的科學。如果我們分解它,強化學習的過程涉及以下簡單步驟:

  1. 智能體觀察環境
  2. 決定如何使用某種政策采取行動
  3. 采取相應的行動
  4. 接受獎勵或懲罰
  5. 從經驗中學習并完善的政策
  6. 疊代直到找到最優政策

現在讓我們通過實際開發一個智能體自動玩遊戲來了解強化學習。

案例:自動駕駛計程車

讓我們設計一個自動駕駛駕計程車,主要目标是在簡化的環境中示範如何使用強化學習技術開發一種有效且安全的方法來解決此問題。

Smartcab 的工作是在一個地點接載乘客并在另一個地點下車,以下是我們希望 Smartcab 考慮的一些事情:

  • 将乘客送到正确的位置
  • 盡量縮短行程時間
  • 照顧乘客的安全并遵守交通規則

在對這個問題的強化學習解決方案進行模組化時,這裡需要考慮不同的方面:獎勵、狀态和動作。

獎勵函數

由于智能體(想象中的司機)是受獎勵驅動的,并且将通過在環境中的試驗經驗來學習如何控制計程車,我們需要相應地決定獎勵和懲罰及其幅度。這裡有幾點需要考慮:

  • 如果智能體成功将乘客送達目的地,應該獲得很高的積極獎勵,因為這種行為是非常需要的。
  • 如果智能體試圖在錯誤的地點放下乘客,則應受到處罰。
  • 智能體應該因為在每個時間步後沒有到達目的地而獲得輕微的負面獎勵。 “輕微”負面,因為我們希望智能體即使晚一點到達目的地,也不要做出錯誤的行動以試圖盡快到達目的地。

狀态空間

在強化學習中,智能體遇到一個狀态,然後根據它所處的狀态采取行動。狀态空間是我們的計程車可能存在的所有可能情況的集合。狀态應該包含智能體做出正确動作所需的有用資訊。

假設我們有一個 Smartcab 訓練場,我們正在教它如何将人員運送到四個不同的位置(R、G、Y、B):

從零開始Q-Learning,用強化學習教計程車接送乘客

假設 Smartcab 是這個訓練場中唯一的車輛。我們可以将訓練場分成一個 5x5 的網格,這樣我們就有 25 個可能的計程車位置。這 25 個位置是我們狀态空間的一部分。請注意,我們計程車的目前位置狀态是坐标 (3, 1)。

您還會注意到我們可以在四 (4) 個地點接送乘客:R、G、Y、B 或 [(0,0), (0,4), (4,0), (4,3)]((row, col)坐标)。我們圖示的乘客位于位置 Y,他們希望前往位置 R。

當我們還考慮到計程車内是否有乘客狀态時,我們可以根據乘客位置和目的地位置的所有組合得出我們計程車環境的狀态總數;有四 (4) 個目的地和五 (4 + 1) 個乘客位置。是以,我們的計程車環境總共有 5×5×5×4=500 個可能的狀态。

動作空間

代理遇到 500 個狀态之一并采取行動。在我們的例子中,動作可以是向一個方向移動或決定接送乘客。

換句話說,我們有六種可能的行動:① 東,② 西,③ 南,④ 北,⑤ 接乘客,⑥ 放乘客。

這是動作空間:我們的智能體在給定狀态下可以采取的所有動作的集合。

您會在上圖中注意到,由于牆壁,計程車在某些狀态下無法執行某些操作。在環境代碼中,我們将簡單地為每次撞牆提供 -1 懲罰,并且計程車不會移動到任何地方。這隻會加重處罰,導緻計程車考慮繞過圍牆。

實戰:Python實作

幸運的是,​​OpenAI Gym​​​ 已經為我們建構了這個精确的環境。

Gym 提供了不同的遊戲環境,我們可以将其插入代碼并測試智能體。該庫通過 API 以提供我們的智能體所需的所有資訊,例如可能的操作、分數和目前狀态。我們隻需要關注智能體的算法部分。

我們将使用名為 Taxi-V2 的 Gym 環境,上面解釋的所有細節都來自該環境。目标、獎勵和行動都是相同的。

Gym接口

我們需要先安裝 Gym。在 Jupyter 筆記本中執行以下操作:

!pip install cmake gym[toy_text] scipy      

安裝後,我們可以加載遊戲環境并渲染它的樣子:

import gym

env = gym.make("Taxi-v3")
env.reset()
env.render()      
從零開始Q-Learning,用強化學習教計程車接送乘客

核心的gym接口是env,是統一的環境接口。以下是對我們很有幫助的 env 方法:

  • ​env.reset()​

    ​:重置環境并傳回随機初始狀态。
  • ​env.step(action)​

    ​:将環境推進一個時間步。傳回結果:
  • **observation:**智能體所觀察到的環境
  • **reward:**智能體所采取的動作是否有利
  • **done:**智能體是否将乘客送達目的地,也稱為一集
  • **info:**用于調試的附加資訊,例如性能和延遲
  • ​env.render()​

    ​:渲染環境的一幀(有助于可視化環境)

回顧問題

這是我們重組的問題陳述(來自 Gym 文檔):

“有4個地點(用不同的字母标記),我們的工作是在一個地點接乘客并在另一個地點讓乘客下車。成功下車我們獲得+20分,在每一個時間步-1分。非法接送行為将被扣 10 分。”

讓我們更深入地了解環境。

print("Action Space {}".format(env.action_space))
print("State Space {}".format(env.observation_space))      

輸出:

Action Space Discrete(6)
State Space Discrete(500)      
  • 智能體為黃色表示沒有乘客,為綠色表示有乘客。
  • R、G、Y、B 是可能的接乘客和目的地位置。藍色字母代表目前乘客上車地點,紫色字母代表目前目的地。

正如列印結果所證明的,我們有一個大小為 6 的動作空間和一個大小為 500 的狀态空間。正如您将看到的,強化學習算法将隻需要這兩樣資訊。我們所需要的隻是一種通過為每個可能的狀态配置設定一個唯一編号來唯一識别狀态的方法,并且強化學習可以從 0-5 中選擇一個動作編号,其中:

  • 0 = 向南
  • 1 = 向北
  • 2 = 向東
  • 3 = 向西
  • 4 = 接乘客
  • 5 = 送乘客

回想一下,這 500 個狀态對應于計程車位置、乘客位置和目的地位置的編碼。強化學習将學習狀态到通過探索在該狀态下執行的最佳動作的映射,即智能體探索環境并根據環境中定義的獎勵采取行動。每個狀态的最優動作是具有最高累積長期獎勵的動作。

詳解

我們實際上可以自己對狀态進行編碼,并将其提供給環境以在 Gym 中進行渲染。回想一下,我們在第 3 行第 1 列有計程車,我們的乘客在位置 2,我們的目的地是位置 0。使用 Taxi-v3 狀态編碼方法,我們可以執行以下操作:

state = env.encode(3, 1, 2, 0) # (taxi row, taxi column, passenger index, destination index)
print("State:", state)

env.s = state
env.render()      

輸出:

State: 328      
從零開始Q-Learning,用強化學習教計程車接送乘客

然後我們可以使用 ​

​env.s​

​ 通過編碼數字手動設定環境的狀态,也可以自定義這些數字,你會看到計程車、乘客和目的地四處移動。

獎勵表

建立 Taxi 環境時,還會建立一個初始 Reward 表,稱為“P”。我們可以把它想象成一個矩陣,其中狀态數為行,動作數為列,即狀态×動作矩陣。

由于每個狀态都在這個矩陣中,我們可以看到配置設定給插圖狀态的預設獎勵值:

env.P[328]      
{0: [(1.0, 428, -1, False)],
1: [(1.0, 228, -1, False)],
2: [(1.0, 348, -1, False)],
3: [(1.0, 328, -1, False)],
4: [(1.0, 328, -10, False)],
5: [(1.0, 328, -10, False)]}      

這個字典的結構是 ​

​**{action: [(probability, nextstate, reward, done)]}**​

​​。

需要注意的幾點:

  • 0-5 對應于圖中計程車在我們目前狀态下可以執行的操作(南、北、東、西、上客、下客)。
  • 在此環境中,機率始終為 1.0。
  • 如果我們在字典的這個索引處采取了這個行動,nextstate是我們将處于的狀态。
  • 在此特定狀态下,所有移動動作都有 -1 獎勵,拾取/放下動作有 -10 獎勵。如果我們處于計程車有乘客并且位于正确目的地的狀态,我們将在下車動作中得到 20 的獎勵
  • done 用于告訴我們何時成功地将乘客送到正确的位置。每一次成功的下車都是一集的結束。

請注意,如果我們的智能體選擇在此狀态下探索的動作撞牆,将繼續累積 -1 懲罰,這會影響長期獎勵。

傳統解決方案

讓我們看看如果我們在沒有強化學習的情況下嘗試暴力解決問題會發生什麼。

由于我們在每個狀态都有預設獎勵的 P 表,我們可以嘗試讓我們的計程車僅使用它來導航。

我們将建立一個無限循環,該循環一直運作到一名乘客到達一個目的地(一集),或者換句話說,直到收到的獎勵為 20 時。 ​​

​env.action_space.sample()​

​ 方法自動從所有集合中選擇一個随機動作可能的行動。

import gym

env = gym.make("Taxi-v3")
env.reset()
env.render()

state = env.encode(3, 1, 2, 0)  # (taxi row, taxi column, passenger index, destination index)
print("State:", state)

env.s = state
env.s = 328  # set environment to illustration's state

epochs = 0
penalties, reward = 0, 0

frames = []  # for animation

done = False

while not done:
    action = env.action_space.sample()
    state, reward, done, info = env.step(action)
    env.render()

    if reward == -10:
        penalties += 1

        # Put each rendered frame into dict for animation
    frames.append({
        'frame': env.render(mode='ansi'),
        'state': state,
        'action': action,
        'reward': reward
    }
                 )

    epochs += 1

print("Timesteps taken: {}".format(epochs))
print("Penalties incurred: {}".format(penalties))      

輸出:

Timesteps taken: 200
Penalties incurred: 62      
從零開始Q-Learning,用強化學習教計程車接送乘客

效果并不好。我們的智能體需要數千個時間步長,并做出許多錯誤的下車,才能将一名乘客送到正确的目的地。

這是因為我們沒有從過去的經驗中學習。我們可以一遍又一遍地運作它,它永遠不會優化。智能體不知道哪個動作最适合每個狀态,這正是強化學習将為我們做的事情。

強化學習方案

我們将使用一種稱為 Q-learning 的簡單 RL 算法,它會給我們的智能體一些記憶。

Q-Learning

從本質上講,Q-learning 讓智能體使用環境的獎勵來學習,随着時間的推移,在給定狀态下采取的最佳行動。

在我們的 Taxi 環境中,我們有獎勵表 P,代理将從中學習。它通過檢視在目前狀态下采取行動而獲得獎勵,然後更新 Q 值以記住該行動是否有益。

存儲在 Q 表中的值稱為 Q 值,它們映射到(狀态、動作)組合。

特定狀态-動作組合的 Q 值代表從該狀态采取的動作的“品質”。更好的 Q 值意味着獲得更大獎勵的機會更大。

例如,如果計程車面臨的狀态包括乘客在其目前位置,則與其他動作(如下車或北上)相比,上車的 Q 值很可能更高。

Q 值被初始化為任意值,當代理将自己暴露在環境中并通過執行不同的動作接收不同的獎勵時,Q 值使用以下等式更新:

其中:

  • α是學習率 (0<α≤1) - 就像在監督學習設定中一樣,α 是我們的 Q 值在每次疊代中更新的程度。
  • γ是折扣因子 (0≤γ≤1) - 确定我們希望對未來獎勵給予多大的重視。折扣因子的高值(接近 1)捕獲了長期有效獎勵,而 0 的折扣因子使我們的代理隻考慮即時獎勵,是以使其變得貪婪。

這是什麼話?

我們通過首先擷取舊 Q 值的權重 (1-α),然後添加學習值來配置設定 (←) 或更新代理目前狀态和動作的 Q 值。學習值是在目前狀态下采取目前行動的獎勵和一旦我們采取目前行動後我們将處于的下一個狀态的折扣最大獎勵的組合。

基本上,我們通過檢視目前狀态/動作組合的獎勵以及下一個狀态的最大獎勵來學習在目前狀态下采取的正确動作。這最終将導緻我們的計程車考慮将最好的獎勵串在一起的路線。

狀态-動作對的 Q 值是即時獎勵和折扣未來獎勵(結果狀态)的總和。我們存儲每個狀态和動作的 Q 值的方式是通過 Q 表。

Q-Table

Q 表是一個矩陣,其中每個狀态 (500) 都有一行,每個動作 (6) 都有一列。它首先初始化為 0,然後在訓練後更新值。請注意,Q 表與獎勵表具有相同的次元,但用途完全不同。

從零開始Q-Learning,用強化學習教計程車接送乘客

總結 Q-Learning 過程

将其分解為步驟,我們得到

  • 用全零初始化 Q 表。
  • 開始探索動作:對于每個狀态,從目前狀态 (S) 的所有可能動作中選擇任何一個。
  • 作為該動作 (a) 的結果,前往下一個狀态 (S’)。
  • 對于狀态 (S’) 中的所有可能動作,選擇具有最高 Q 值的動作。
  • 使用等式更新 Q 表值。
  • 将下一個狀态設定為目前狀态。
  • 如果達到目标狀态,則結束并重複該過程。

利用學習價值

在對動作進行足夠的随機探索之後,Q 值趨于收斂,為我們的代理提供動作值函數,它可以利用它從給定狀态中選擇最佳動作。

在探索(選擇随機動作)和利用(根據已經學習的 Q 值選擇動作)之間存在權衡。我們希望防止動作總是采用相同的路線,并且可能會過度拟合,是以我們将引入另一個稱為 ϵ “epsilon” 的參數以在訓練期間迎合這一點。

我們有時會傾向于進一步探索動作空間,而不是僅僅選擇最好的學習 Q 值動作。較低的 epsilon 值會導緻具有更多懲罰的情節(平均而言),這很明顯,因為我們正在探索和做出随機決定。

通過Python實作Q-Learning

訓練智能體

首先,我們将 Q 表初始化為一個 500×6 的零矩陣:

import numpy as np

q_table = np.zeros([env.observation_space.n, env.action_space.n])      

我們現在可以建立訓練算法,當代理在數千個情節中探索環境時更新這個 Q 表。

在尚未完成的第一部分,我們決定是選擇随機動作還是利用已經計算的 Q 值。這隻需使用 epsilon 值并将其與 random.uniform(0, 1) 函數進行比較即可完成,該函數傳回 0 和 1 之間的任意數字。

我們在環境中執行選擇的動作以獲得 next_state 和執行動作的獎勵。之後,我們計算與 next_state 對應的動作的最大 Q 值,然後我們可以輕松地将 Q 值更新為 new_q_value:

import random

import gym
import numpy as np

env = gym.make("Taxi-v3")
env.reset()

# Hyperparameters
alpha = 0.1
gamma = 0.6
epsilon = 0.1

# For plotting metrics
all_epochs = []
all_penalties = []

q_table = np.zeros([env.observation_space.n, env.action_space.n])

for i in range(1, 100001):
    state = env.reset()

    epochs, penalties, reward, = 0, 0, 0
    done = False

    while not done:
        if random.uniform(0, 1) < epsilon:
            action = env.action_space.sample()  # Explore action space
        else:
            action = np.argmax(q_table[state])  # Exploit learned values

        next_state, reward, done, info = env.step(action)

        old_value = q_table[state, action]
        next_max = np.max(q_table[next_state])

        new_value = (1 - alpha) * old_value + alpha * (reward + gamma * next_max)
        q_table[state, action] = new_value

        if reward == -10:
            penalties += 1

        state = next_state
        epochs += 1

print("Training finished.\n")      

現在 Q 表已經建立超過 100,000 集,讓我們看看在我們的插圖狀态下 Q 值是多少:

q_table[328]      

輸出:

array([ -2.30108105,  -1.97092096,  -2.30357004,  -2.20591839, -10.3607344 ,  -8.5583017 ])      

最大 Q 值是“北”(-1.971),是以看起來 Q 學習已經有效地學習了在我們的插圖狀态下采取的最佳行動!

評估智能體

讓我們評估一下代理的性能。我們不需要進一步探索動作,是以現在總是使用最佳 Q 值選擇下一個動作:

total_epochs, total_penalties = 0, 0
episodes = 100

for _ in range(episodes):
    state = env.reset()
    epochs, penalties, reward = 0, 0, 0

    done = False

    while not done:
        action = np.argmax(q_table[state])
        state, reward, done, info = env.step(action)

        if reward == -10:
            penalties += 1

        epochs += 1

    total_penalties += penalties
    total_epochs += epochs

print(f"Results after {episodes} episodes:")
print(f"Average timesteps per episode: {total_epochs / episodes}")
print(f"Average penalties per episode: {total_penalties / episodes}")      

輸出:

Results after 100 episodes:
Average timesteps per episode: 12.3
Average penalties per episode: 0.0      

從評估中我們可以看出,該代理的性能顯着提高,并且沒有受到任何處罰,這意味着它對 100 名不同的乘客執行了正确的上車/下車操作。

對比

Q-learning 代理最初在探索過程中會犯錯誤,但一旦探索足夠多(看到大多數狀态),它就可以明智地采取行動,最大限度地提高智能移動的回報。 讓我們看看與僅進行随機移動的代理相比,我們的 Q 學習解決方案要好得多。

我們根據以下名額評估我們的代理:

  • 每集的平均懲罰次數:數字越小,我們的代理的性能越好。理想情況下,我們希望這個名額為零或非常接近于零。
  • 每次行程的平均時間步數:我們也希望每集的時間步數較少,因為我們希望我們的代理采取最少的步驟(即最短路徑)到達目的地。
  • 每次移動的平均獎勵:獎勵越大意味着代理正在做正确的事情。 這就是為什麼決定獎勵是強化學習的關鍵部分。 在我們的例子中,由于時間步長和懲罰都是負獎勵,更高的平均獎勵意味着代理以最少的懲罰盡快到達目的地”
從零開始Q-Learning,用強化學習教計程車接送乘客

這些名額是在 100 集以上計算得出的。結果表明,我們的 Q-learning 代理成功了!

超參數與優化

​alpha​

​​、​

​gamma​

​​ 和 ​

​epsilon​

​​ 的值主要是基于直覺和一些“試一試”,但有更好的方法來得出好的值。

理想情況下,這三個都應該随着時間的推移而減少,因為随着代理繼續學習,它實際上會建立更有彈性的先驗;

  • :随着您繼續獲得越來越大的知識庫,(學習率)應該會降低。
  • :随着你越來越接近最後期限,你對近期獎勵的偏好應該會增加,因為你不會有足夠長的時間來獲得長期獎勵,這意味着你的 gamma 應該會降低。
  • :随着我們制定政策,我們不再需要探索和更多的利用來從我們的政策中獲得更多效用,是以随着試驗的增加,epsilon 應該減少。

超參數調參

以程式設計方式得出最佳超參數值集的一種簡單方法是建立一個綜合搜尋函數(類似于網格搜尋),該函數選擇将産生最佳獎勵/時間步長比的參數。 使用reward/time_steps 的原因是我們希望選擇能夠讓我們盡快獲得最大獎勵的參數。 我們可能還想跟蹤與超參數值組合相對應的懲罰數量,因為這也可能是一個決定因素(我們不希望我們的智能代理以更快到達為代價而違反規則)。 獲得正确的超參數值組合的一種更奇特的方法是使用遺傳算法。

總結與展望

好吧!我們首先借助現實世界的類比來了解強化學習。然後,我們深入研究了強化學習的基礎知識,并将自動駕駛計程車設計為強化學習問題。然後我們在 python 中使用 OpenAI 的 Gym 為我們提供了一個相關的環境,我們可以在其中開發我們的代理并對其進行評估。然後我們觀察到我們的代理在不使用任何算法玩遊戲的情況下有多糟糕,是以我們從頭開始實施 Q-learning 算法。在 Q-learning 之後,智能體的性能顯着提高。最後,我們讨論了為我們的算法确定超參數的更好方法。

繼續閱讀