用50行Python程式碼解決cart pole平衡問題
作者:Mike Shi
編譯:Bing
今天的這篇文章向大家展示,如何用50行Python程式碼教會機器解決cart pole問題,保持平衡。原文作者Mike Shi將用標準的OpenAI Gym作為測試環境,僅用NumPy建立我們的智慧體。
Cart pole的玩法如下動圖所示,目標就是保持一根杆一直豎直朝上,杆由於重力原因會一直傾斜,當杆傾斜到一定程度就會倒下,此時需要朝左或者右移動杆保證它不會倒下來。這和在指尖上樹立一隻鉛筆一樣,只不過cart pole是一維的。

在詳細講解前可以先試一下我們最終的demo:

地址: ofollow,noindex">towardsdatascience.com/from-scratch-ai-balancing-act-in-50-lines-of-python-7ea67ef717
強化學習速成
如果你是機器學習新手,或是首次接觸強化學習,我會在這裡介紹一些可能用到的基礎術語。如果你已經很熟悉強化學習的概念了,可以跳過這部分。
強化學習
強化學習的主要任務是讓智慧體在沒有明確指令的情況下學會執行特定任務,或作出特定動作。可以把它想象成一個嬰兒,它正隨機地伸展腿部,在一次偶然的情況中,嬰兒可以豎直站立了,這時我們會給他一顆糖作為獎勵。同樣,智慧體的目標就是在有限的時間內實現總體獎勵的最大化,並且我們要根據想完成的任務決定獎勵型別。對於嬰兒站立的案例,如果直立站立,獎勵就是1,否則的話就是0。
強化學習智慧體的一個例子就是AlphaGo,該智慧體學習如何玩遊戲才能使獎勵最大化(贏得遊戲)。在這篇教程中,我們將創造一個智慧體,通過左右移動杆子,解決cart pole問題。
狀態

狀態是某一時刻遊戲的樣子,我們通常會處理遊戲的多種表示。在《乓》中狀態可能是球拍的豎直位置和x, y座標以及乒乓球的移動速度。在cart pole遊戲中,我們的狀態由四個數字組成:底部小車(cart)的位置,小車的速度,杆子的位置(用角度表示)和杆子角度變化的速度。這四組資料以群組的方式(或向量)給定出來。這非常重要,理解這組表示狀態的數字意味著我們可以用數學推理決定接下來做出怎樣的反應。
策略
策略是一種函式,輸入遊戲的狀態(例如,位置引數),輸出智慧體應該做出的動作。智慧體採取我們所選擇動作後,遊戲會根據下一個狀態進行更新,這一過程會一直持續到遊戲結束。策略是非常重要的,也是我們一直追求的,這是智慧體背後的決策能力體現。
點積
兩陣列(向量)間的點積就是簡單地將第一組中的每個元素和對應的元素相乘,然後把它們結合在一起假設我們想找到陣列A和B之間的點積,只需要簡單計算A[0]B[0] + A[1]B[1]…即可。我們會用這個公式將狀態(陣列)和另一個數組(我們的策略)相乘。
建立我們的策略
為了解決cart pole問題,我們想讓機器學習一種策略贏得最大獎勵。
對於我們要建立的智慧體,我們會用四個數字來表示策略,這四個數字可以體現狀態中的每個元素的重要性(例如小車的位置、杆子的位置等等),之後,我們會計算策略陣列和狀態之間的點選,輸出一個單一數字。根據數值的正負,我們決定讓小車向左還是向右。
如果這種描述聽起來比較抽象,那我們接下來用一個具體例子來展示這一過程。
假設底部小車在中央是靜止的,杆子向右傾斜,並且可能會倒向右邊:

相關的狀態可能會如下:

那麼狀態的陣列可能是[0, 0, 0.2, 0.05]。
我們的直覺是,要想讓杆子豎直,就要將小車推向右。我在訓練過程中得到了一個策略結果,它的陣列如下:[-0.116, 0.332, 0.207, 0.352]。讓我們快速地計算一下,看看在這一狀態下會輸出怎樣的動作。
這裡,我們將上述策略陣列和狀態[0, 0, 0.2, 0.05]結合計算點積。如果得出的結果是正的,那麼就將小車向右推,如果是負的就向左。

結果是正數,也就是說該策略會讓將小車推到右邊,就像我們做的一樣。現在,問題時我們如何才能得到那四個數字?如果隨機選擇會怎樣?模型會表現得怎麼樣?
開始你的編輯
首先在repl.it上開啟一個Python例項,你能從中獲取大量不同的程式設計環境例項,然後用強大的雲IDE編寫程式碼。

安裝軟體包
首先我們要安裝兩個必要的包:幫助進行數值計算的NumPy,和智慧體模擬器OpenAI Gym。

搭建基礎框架
首先,匯入兩個在 main.py
指令碼中安裝的依賴,然後設定一個新的gym環境:
import gym import numpy as np env = 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()] # Record the observations for normalization and replay if done: # If the simulation was over last iteration, exit loop break # Pick an action according to the policy matrix outcome = np.dot(policy, observation) action = 1 if outcome > 0 else 0 # Make the action, record reward observation, reward, done, info = env.step(action) score += reward return score, observations
上述程式碼主要是玩遊戲的過程以及記錄輸出,實際上,我們的策略程式碼只需要兩行:
outcome = np.dot(policy, observation) action = 1 if outcome > 0 else 0
這裡我們主要是進行策略陣列和狀態陣列之間的點積運算,之後,我們會根據結果進行動作選擇。
目前為止,我們的 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()] # Record the observations for normalization and replay if done: # If the simulation was over last iteration, exit loop break # Pick an action according to the policy matrix outcome = np.dot(policy, observation) action = 1 if outcome > 0 else 0 # Make the action, record reward observation, reward, done, info = env.step(action) score += reward return score, observations
現在我們開始玩遊戲,找到最佳策略!
開始第一局遊戲
現在我們有了能玩遊戲,並且能判斷策略好壞的函式,現在我們想生成一些其他策略,看看它們能做什麼。
如果輸入一些隨機策略會是怎樣?我們能得到什麼結果?用 numpy
生成我們的策略,這是一個含有四個元素的陣列,或者1×4矩陣。
policy = np.random.rand(1,4)
得到了策略和上述我們創造的環境,我們可以開始遊戲,並得到一個分數:
score, observations = play(env, policy) print('Policy Score', score)
執行指令碼後,它應該輸出我們策略得到的分數。

遊戲最大分是500。
觀察智慧體
為了觀察智慧體,我們用Flask設定一個輕量級伺服器,可以在瀏覽器中看到智慧體的表現。Flask是一款簡潔的Python HTTP伺服器框架,可以服務我們的HTML UI和資料。
首先安裝Flask的Python包:

接著,在我們指令碼的最下方建立一個flask伺服器。他能在端點 /data
上顯示遊戲中每一幀的記錄,在 /
上顯示UI。
from flask import Flask import json app = 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資料夾。接著我們還想建立一個index.html,可以渲染UI。具體過程這裡不詳細展開說明了,但是你需要將這個index.html上傳到你的repl.it專案中。
你現在應該有了這樣的專案資料夾:

有了這兩個資料夾,當我們執行repl時,它仍然可以演示我們的策略。一切就緒後,就能嘗試超出最佳策略了。

策略搜尋
最初我們隨機選擇了策略,但如果我們在很多策略中,只保留表現最佳的那個會怎樣?
讓我們回到執行策略的部分,編寫一段迴圈,生成多個策略,並記錄它們的表現,只儲存表現最佳的策略。
我們首先要創造一個名為max的元組,可以儲存分數、觀察到的場景和目前最佳的策略陣列。
max = (0, [], [])
接下來,我們會生成10種策略並進行評估,儲存最佳策略。
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 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()] # Record the observations for normalization and replay if done: # If the simulation was over last iteration, exit loop break # Pick an action according to the policy matrix outcome = np.dot(policy, observation) action = 1 if outcome > 0 else 0 # Make the action, record reward observation, reward, done, info = env.step(action) score += reward return score, observations max = (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 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,我們應該會得到的最大分數為500。如果沒有,可以試試再執行repl一遍!
不足之處
但是,我們在第一部分進行了小小的作弊。首先,我們隨機創造了策略陣列,範圍在0到1之間,這恰巧能執行。但如果我們稍稍改變數值,智慧體就會完全失效,可以試一試將 action = 1 if outcome > 0 else 0
改為 action = 1 if outcome < 0 else 0
。這樣就很不穩定,為了解決這一問題,我們應該提出一種對負數也能執行的策略。這樣難度就又增加了,但是演算法之後也能更通用。
我們將 policy = np.random.rand(1,4)
改為 policy = np.random.rand(1,4) - 0.5
,把策略中的每個數值從0—1改為-0.5—0.5。我們還想讓更多策略能夠進行搜尋,所以在上述迴圈中,與其在10種策略中進行迭代,我們將策略增加到100種,程式碼改為 for _ in range(100):
。
最終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()] # Record the observations for normalization and replay if done: # If the simulation was over last iteration, exit loop break # Pick an action according to the policy matrix outcome = np.dot(policy, observation) action = 1 if outcome > 0 else 0 # Make the action, record reward observation, reward, done, info = env.step(action) score += reward return score, observations max = (0, [], []) # We changed the next two lines! 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')
另外一點,既然我們的策略可以在單次執行中達到最高500分,它能保證每次都表現得這麼好嗎?當我們生成了100中策略,然後選擇表現最佳的策略,但這一策略可能恰巧表現得很好,這是由於遊戲本身具有一個隨機元素(起始位置每次都不同),所以一種策略只在同一個起始點表現不錯,而其他起始點可能就無效了。
為了解決這一點,我們想要評測一種策略能在多次試驗中的表現。現在我們選取此前表現最好的策略,看看在100次實驗中的表現如何:
scores = [] for _ in range(100): score, _= play(env, max[2]) scores += [score] print('Average Score (100 trials)', np.mean(scores))
我們記錄下了每次的分數,然後用numpy計算平均分,在終端上打印出來。
結語
恭喜:tada:,現在我們已經成功地建立一個可以解決cart pole問題的AI了,不僅有用而且非常高效。接下來,這一模型還有幾處需要改進的空間:
- 找到一個真正的最優策略
- 減少找到最優策略的次數
- 研究如何能找到正確的策略,而不是隨即進行選擇
- 解決其他環境。