使用50行Python代碼從零開始實現一個AI平衡小遊戲

本文會爲大家展示機器學習專家 Mike Shi 如何用 50 行 Python 代碼創建一個 AI,使用增強學習技術,玩耍一個保持杆子平衡的小遊戲。本文給大家帶來實現思路及簡單代碼,感興趣的朋友跟隨小編一起看看吧

 

集智導讀:

本文會爲大家展示機器學習專家 Mike Shi 如何用 50 行 Python 代碼創建一個 AI,使用增強學習技術,玩耍一個保持杆子平衡的小遊戲。所用環境爲標準的 OpenAI Gym,只使用 Numpy 來創建 agent。

各位看官好,我(作者 Mike Shi——譯者注)將在本文教大家如何用 50 行 Python 代碼,教會 AI 玩一個簡單的平衡遊戲。我們會用到標準的 OpenAI Gym 作爲測試環境,僅用 Numpy 創建我們的 AI,別的不用。

這個小遊戲就是經典的 Cart Pole 任務,它是 OpenAI Gym 中一個經典的傳統增強學習任務。遊戲玩法如下方動圖所示,就是盡力保持這根杆子始終豎直向上。杆子由於重力原因,會出現傾斜,到了一定程度就會倒下,AI 的任務就是在此時向左或向右移動杆子,不讓它倒下。這就跟我們在手指尖上樹立一支鉛筆玩“金雞獨立”一樣,只不過我們這裏是個一維的簡單遊戲(但是還是很有挑戰性的)。

你可能好奇最終實現怎樣的結果,可以在repl.it 上查看 demo:

 https:// repl.it/@MikeShi42/Cart Pole 

增強學習速覽

如果這是你第一次接觸機器學習或增強學習,別擔心,我下面介紹一些基礎知識,這樣你就可以瞭解本文使用的術語了:)。如果已經熟悉了,大可跳過這部分,直接看看編寫 AI 的部分。

增強學習(RL)是一個研究領域:教 agent(我們的算法/機器)執行某些任務/動作,但明確告訴它該怎樣做。把它想象成一個嬰兒,以隨機的方式伸腿,如果寶寶偶然間走運站立起來,我們會給它一個糖果作爲獎勵。同樣,Agent 的目標就是在其生命週期內得到最多的獎勵,而且我們會根據是否和要完成的任務相符來決定獎勵的類型。對於嬰兒站立的例子,站立時獎勵 1,否則爲0。

增強學習 agent 的一個著名例子是 AlphaGo,其中的 agent 已經學會了如何玩圍棋以最大化其獎勵(贏得遊戲)。在本教程中,我們將創建一個 agent,或者說 AI,可以向左或向右移動小車,讓杆子保持平衡。

狀態

狀態是目前遊戲的樣子。我們通常處理遊戲的多種數字表示。在乒乓球比賽中,它可能是每個球拍的垂直位置和 x,y 座標和球的速度。在我們這個遊戲中,我們的狀態由 4 個數字組成:底部小車的位置,小車的速度,杆的位置(以角度表示)和杆的角速度。這 4 個數字都是給定的數組(或向量)。這個很重要,理解狀態是一個數字數組意味着我們可以對它進行一些數學運算來決定我們根據狀態採取什麼行動。

策略

策略是一種函數,其輸入是遊戲的狀態(例如棋盤的位置,或小車和杆的位置),輸出 agent應該在該位置採取的動作(例如,將小車向左邊移動)。在 agent 採取我們選擇的操作後,遊戲將使用下一個狀態進行更新,我們會再次將其納入策略以做出決策。這種情況一直持續到遊戲結束。策略非常重要,也是我們一直追求的,因爲代表了 agent 背後的決策能力。

點積

兩個數組(向量)之間的點積簡單地將第一個數組的每個元素乘以第二個數組的對應元素,並將它們全部加在一起。假設我們想找到數組 A 和 B 的點積,只需計算是 A [0] * B [0] + A [1] * B [1] ......我們將使用這種運算將狀態(一個數組)乘以另一個數組(我們的策略)。

創建我們的策略

爲了完成這個推車平衡遊戲,我們希望讓我們的 agent(或者說 AI)學習策略贏得比賽或獲得最大獎勵。

對於我們今天要開發的 agent,我們將策略表示爲 4 個數字的數組,分別代表狀態的各個部分的“重要性”(小車位置,杆子的位置等)然後我們會計算狀態和策略數組的點積,得到一個數字。根據數字是正數還是負數,我們將向左或向右推動小車。

如果這聽起來有點抽象,那麼我們選擇一個具體的例子,看看會發生什麼。

假設小車在遊戲中居中並且靜止,杆子向右傾斜且可能倒向右邊。它看起來像這樣:

相關狀態可能如下所示:

那麼狀態數組將是 [0,0,0.2,0.05]。

從直覺上,我們要把小車推向右邊,將支桿拉直。我從訓練中得到了一個很好的策略,其策略數據如下:[ - 0.116,0.332,0.207 0.352]。我們快速計算一下,看看該策略會輸出怎樣的動作。

這裏,我們將狀態數組 [0,0,0.2,0.05] 和上述策略數組結合計算點積。如果數字是正數,我們將車推向右邊,如果數字是負數,我們向左推。

結果爲正,意味着策略會向右推動小車,符合我們的預期。

現在比較明顯了,我們需要 4 個像上面這樣的神奇數字來幫我們解決問題。那麼我們該如何獲得這些數字?如果我們只是隨機挑選它們會怎樣?AI 的效果會怎樣?我們來一起看代碼!

啓動你的編輯器!

首先在repl.it 上打開一個 Python 實例。Repl.it 能讓我們快速啓動大量不同編程環境的雲實例,並在任何地方都能訪問的強大雲 IDE 中編輯代碼!

安裝軟件包

我們首先安裝這個項目所需的兩個軟件包:numpy 幫助進行數值計算;OpenAI Gym 作爲我們代理的模擬器。

只需在編輯器左側的包搜索工具中輸入 gym 和 numpy,然後單擊加號按鈕即可安裝包。

創建基礎框架

我們首先將我們剛剛安裝的兩個依賴項導入到main.py 腳本中,並設置一個新的 gym 環境:

import gymimport numpy as npenv = gym.make('CartPole-v1')

接下來,我們定義一個名爲“play”的函數,爲該函數提供一個環境和一個策略數組,在環境中計算策略數組並返回分數,以及每個時步的遊戲快照(用於觀察)。我們將使用分數來判斷策略的效果以及查看每個時步的遊戲快照來判斷策略的表現。這樣我們就可以測試不同的策略,看看它們在遊戲中的表現如何!

首先我們理解函數的定義,然後將遊戲重置爲開始狀態。

def play(env, policy): observation = env.reset()

接下來,我們將初始化一些變量以跟蹤遊戲是否已經結束,包括策略的總分以及遊戲中每個步驟的快照(供觀察)。

done = False score = 0 observations = []

現在我們多次運行遊戲,直到 gym 告訴我們遊戲已經完成。

for _ in range(5000): 
observations += [observation.tolist()] 
# 記錄用於正則化的觀察值,並回放 
 if done:
 # 如果模擬在最後一次迭代中結束,則退出循環  
 break  
# 根據策略矩陣選擇一種行爲 
outcome = np.dot(policy, observation) 
 action = 1 if outcome > 0 else 0 
 # 創建行爲,記錄反饋 
observation, reward, done, info = env.step(action) 
 score += reward 
return score, observations

上面的大部分代碼主要是玩遊戲的過程以及記錄的結果。實際上,我們的策略代碼只需要兩行:

outcome = np.dot(policy, observation) 
action = 1 if outcome > 0 else 0

我們在這裏所做的只是策略數組和狀態數組之間的點積運算,就像我們之前在具體例子中所示的那樣。然後我們根據結果是正還是負,選擇 1 或 0(左或右)的動作。

到目前爲止,我們的main.py 應如下所示:

import gymimport numpy as npenv = gym.make('CartPole-v1')def play(env, policy): 
 observation = env.reset() 
done = False score = 0 
observations = [] for _ in range(5000): 
 observations += [observation.tolist()] 
# 如果模擬在最後一次迭代中結束,則退出循環  
if done: # 如果模擬在最後一次迭代中結束,則退出循環  break  
# 根據策略矩陣選擇一種行爲 
outcome = np.dot(policy, observation) 
action = 1 if outcome > 0 else 0  
# 創建行爲,記錄反饋 
observation, reward, done, info = env.step(action) 
score += reward 
return score, observations

現在,我們開始玩遊戲,尋找我們的最佳策略!

第一局遊戲

由於我們有了能夠玩遊戲的函數,並且能告訴我們的策略有多好,那麼下面就創建一些策略,看看它們的效果怎樣。

如果我們首先只想嘗試隨機策略呢?能達到怎樣的效果?我們使用 numpy 來生成我們的策略,它是一個 4 元素數組或 1x4 矩陣。它會選擇 0 到 1 之間的 4 個數字作爲我們的策略。

policy = np.random.rand(1,4)

根據該策略和我們上面創建的環境,我們可以用它們來玩遊戲,獲得一個分數。

score, observations = play(env, policy)print('Policy Score', score)

點擊運行,執行我們的腳本,然後會輸出我們的策略得分:

遊戲的最大得分是 500 分,你的策略有可能達不到這個水平。如果達到了,恭喜你!絕對是你的大日子!只是看一個數字並沒有特別大的意義。如果能看到我們的 agent 是如何玩遊戲的,那就太好了,下一步我們就會設置它!

查看我們的agent

要查看我們的 agent,我們會使用 Flask 設置一個輕量級服務器,以便我們可以在瀏覽器中查看代理的性能。Flask 是一個輕量級的 Python HTTP 服務器框架,可以爲我們的 HTML UI 和數據伺服。這部分我就一筆帶過了,因爲渲染和 HTTP 服務器背後的細節對訓練我們的 agent 並不重要。

我們首先將 Flask 安裝爲 Python 包,就像我們在前面安裝 gym 和 Numpy 一樣。

 

接着,在我們腳本的底部,我們將創建一個 Flask 服務器。它將在端點 / data 上顯示遊戲的每一幀的記錄,並在/上託管UI。

from flask import Flaskimport jsonapp = Flask(__name__, static_folder='.')@app.route("/data")def data(): 
 return json.dumps(observations)@app.route('/')def root(): 
 return app.send_static_file('./index.html')
 app.run(host='0.0.0.0', port='3000')

另外,我們需要添加兩個文件。一個是項目的空白 Python 文件。這是repl.it 如何檢測 repl 是處於 eval 模式還是項目模式的專用術語。只需使用新文件按鈕添加空白 Python 腳本即可。

之後我們還想創建一個用於渲染 UI 的 index.html。這裏不再深入講解,只需將此 index.html 上傳到你的repl.it 項目即可。

現在你應該有一個如下所示的項目目錄:

現在有了這兩個新文件,當我們運行 repl 時,它應該能演示我們的策略。有了這個,我們嘗試找到最佳策略!

策略搜索

在我們的第一局遊戲中,我們只是隨機選擇了一個策略,但是如果我們選擇了一批策略,並且只保留那個表現最好的策略呢?

我們回到發佈策略的部分,這次不是僅生成一個,而是編寫一個循環來生成多個策略,並跟蹤每個策略的執行情況,最終僅保存最佳策略。

首先我們創建一個名爲 max 的元組,它將存儲我們迄今爲止看到的最佳策略的得分,觀察值和策略數組。

max = (0, [], [])

接着我們會生成和評估 10 個策略,並將最優策略保存在 max 中。

for _ in range(10): 
 policy = np.random.rand(1,4) 
 score, observations = play(env, policy) 
if score > max[0]: 
max = (score, observations, policy)print('Max Score', max[0])

我們還要讓 /data 端點返回最優策略的回放。

該端點:

@app.route("/data")def data():return json.dumps(observations)

應該改爲:

@app.route("/data")def data():return json.dumps(max[1])

你的main.py 應該如下所示:

import gymimport numpy as npenv = gym.make('CartPole-v1')def play(env, policy): 
 observation = env.reset() done = False score = 0 
 observations = [] for _ in range(5000): 
observations += [observation.tolist()]  
 if done:  
break  
outcome = np.dot(policy, observation) 
 action = 1 if outcome > 0 else 0 
 observation, reward, done, info = env.step(action) score += reward return score, observationsmax = (0, [], [])for _ in range(10): policy = np.random.rand(1,4) score, observations = play(env, policy) if score > max[0]: 
max = (score, observations, policy)print('Max Score', max[0])from flask import Flaskimport jsonapp = Flask(__name__, static_folder='.')@app.route("/data")def data(): return json.dumps(max[1])@app.route('/')def root(): 
 return app.send_static_file('./index.html') 
app.run(host='0.0.0.0', port='3000')

如果我們現在運行 repl,應該會得到最多爲 500 分的分數,如果沒有達到這個結果,那就再運行 repl 一遍。另外我們可以看到策略幾乎完美地讓推車上的杆子保持平衡。

不是那麼快

不過實際上或許沒有這麼好,因爲我們在第一部分稍微有一點作弊。首先,我們只是在 0 到 1 的範圍內隨機創建了策略數組。這恰好可行,但是如果我們修改一下運算符,就會看到 agent 出現災難性的失敗。你自己可以試試將 action = 1 if outcome > 0 else 0 改成 action = 1 if outcome < 0 else 0

但是效果仍然不穩定,因爲如果我們恰好選擇少於而不是大於 0,我們永遠找不到最優的策略。爲了解決這個問題,我們實際上應該生成對負數同樣適用的策略。雖然這爲我們的工作增加了難度,但我們再也不必通過將我們的特定算法擬合特定遊戲來“作弊”了。不然,如果我們試圖在 OpenAIgym 以外的其他環境中運行算法時,算法肯定會失敗。

要做到這一點,我們不再使用 policy = np.random.rand(1,4),而是改爲 policy = np.random.rand(1,4) - 0.5。這樣我們策略中的每個數字都在 -0.5 到 0.5 之間,而不是 0 到 1。但是因爲這樣難度更高,我們還想搜索更多的策略。在上面的 for 循環中,不是迭代 10 個策略,而是通過讓代碼改爲讀取 for _ in range(100): 來嘗試 100 個策略。此外也鼓勵大家嘗試首先只迭代 10 個策略,看看現在用負數來獲得好的策略的難度如何。

現在我們的main.py 應該如下所示:

import gym
import numpy as np
env = gym.make('CartPole-v1')
def play(env, policy):
 observation = env.reset()
 done = False
 score = 0
 observations = []
 for _ in range(5000):
  observations += [observation.tolist()] 
  if done: 
   break
  outcome = np.dot(policy, observation)
  action = 1 if outcome > 0 else 0
  observation, reward, done, info = env.step(action)
  score += reward
 return score, observations
max = (0, [], [])
# 修改接下來兩行!
for _ in range(100):
 policy = np.random.rand(1,4) - 0.5
 score, observations = play(env, policy)
 if score > max[0]:
  max = (score, observations, policy)
print('Max Score', max[0])
from flask import Flask
import json
app = Flask(__name__, static_folder='.')
@app.route("/data")
def data():
  return json.dumps(max[1])
@app.route('/')
def root():
  return app.send_static_file('./index.html')
app.run(host='0.0.0.0', port='3000')

如果現在運行 repl,無論我們使用的值是否大於或小於 0,我們仍然可以爲遊戲找到一個好的策略。

但是等等,這還沒完!即使我們的策略可以運行一次就達到最高分 500,但每次都能做到嗎?當我們生成 100 個策略,並選擇出在單一運行中表現最佳的策略時,該策略可能只是走運而已,甚至它可能是一個非常糟糕的策略,只是恰好運行效果很好。這是因爲遊戲本身具有隨機性因素(起始位置每次都不同),因此策略可能只適用於一個起始位置,換成其他起始位置就不行了。

因此,爲了解決這個問題,我們需要評估策略在多次試驗中的表現。現在,我們使用之前找到的最優策略,看看它在 100 次試驗中的表現如何。

scores = []for _ in range(100): score, _ = play(env, max[2])
 scores += [score] print('Average Score (100 trials)', np.mean(scores))

這裏我們將該策略運行 100次,並且每次都記錄它的得分。然後我們使用 numpy 計算平均分數並將其打印到我們的終端。沒有嚴格的已發佈的“已解決”定義,但它應該只有少數幾個點。你可能會注意到最好的政策實際上可能實際上是低於平均水平。但是,我會把解決方案留給你決定!

當然,對於何爲“最優”並沒有嚴格的定義,但是至少比最高分 500 來說不應太差。你可能注意到最優策略有時是低於平均水平的,但是最終的最優策略如何,還是要靠大家根據自己的實際情況來定奪。

結語

恭喜!至此我們成功創建了一個 AI,能夠很好地玩耍這個簡單的平衡遊戲。不過,仍然有很多需要改進的地方:

  • 找到一個“真正的”最優策略(每局遊戲都能表現良好)
  • 減少我們尋找最優策略的搜索次數
  • 研究怎樣找到正確的策略,而不是隨機選擇它們
  • 嘗試其它開發環境

以上所述是小編給大家介紹的使用50行Python代碼從零開始實現一個AI平衡小遊戲,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對神馬文庫網站的支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章