OpenAI Gym是一款用於研發和比較強化學習演算法的工具包,本文主要介紹Gym模擬環境的功能和工具包的使用方法,並詳細介紹其中的經典控制問題中的倒立擺(CartPole-v0/1)問題。最後針對倒立擺問題如何建立控制模型並採用爬山演算法優化進行了介紹,並給出了相應的完整python代碼示例和解釋。要... ...
摘要:OpenAI Gym是一款用於研發和比較強化學習演算法的工具包,本文主要介紹Gym模擬環境的功能和工具包的使用方法,並詳細介紹其中的經典控制問題中的倒立擺(CartPole-v0/1)問題。最後針對倒立擺問題如何建立控制模型並採用爬山演算法優化進行了介紹,並給出了相應的完整python代碼示例和解釋。要點如下:
1. 前言
自從AlphaGo的橫空出世之後,整個工業界都為之振奮,也確定了強化學習在人工智慧領域的重要地位,越來越多的人加入到強化學習的研究和學習中。強化學習(Reinforcement learning, RL)是機器學習的一個子領域,在智能控制機器人及分析預測等領域有許多應用。強化學習通過與環境進行交互獲得的獎賞指導行為,目標是使智能體獲得最大的獎賞,最終開發出智能體(Agent)做出決策和控制。
OpenAI Gym是一個研究和比較強化學習相關演算法的開源工具包,包含了許多經典的模擬環境和各種數據。目前強化學習的研究面臨著使用的環境缺乏標準化的問題,這個問題使得很難複製已發表的研究結果以及比較不同論文的結果,個人認為Gym正好為這一問題提供了很好的解決方案。因此非常有必要學習一下Gym這一快捷工具包,經過一段時間對深度強化學習的研究和學習,我這裡將前期所學做一個整理和總結。
2. OpenAI Gym模擬環境介紹
Gym是一個研究和開發強化學習相關演算法的模擬平臺,無需智能體先驗知識,並相容常見的數值運算庫如 TensorFlow、Theano等。OpenAI Gym由以下兩部分組成:
- Gym開源庫:測試問題的集合。當你測試強化學習的時候,測試問題就是環境,比如機器人玩游戲,環境的集合就是游戲的畫面。這些環境有一個公共的介面,允許用戶設計通用的演算法。
- OpenAI Gym服務:提供一個站點和API(比如經典控制問題:CartPole-v0),允許用戶對他們的測試結果進行比較。
簡單來說OpenAI Gym提供了許多問題和環境(或游戲)的介面,而用戶無需過多瞭解游戲的內部實現,通過簡單地調用就可以用來測試和模擬。接下來以經典控制問題CartPole-v0為例,簡單瞭解一下Gym的特點,以下代碼來自OpenAI Gym官方文檔
import gym
env = gym.make('CartPole-v0')
env.reset()
for _ in range(1000):
env.render()
env.step(env.action_space.sample()) # take a random action
env.close()
運行效果如下:
以上代碼中可以看出,gym的核心介面是Env。作為統一的環境介面,Env包含下麵幾個核心方法:
- reset(self):重置環境的狀態,返回觀察。
- step(self, action):推進一個時間步長,返回observation, reward, done, info。
- render(self, mode='human', close=False):重繪環境的一幀。預設模式一般比較友好,如彈出一個視窗。
- close(self):關閉環境,並清除記憶體。
以上代碼首先導入gym庫,第2行創建CartPole-v0環境,併在第3行重置環境狀態。在for迴圈中進行1000個時間步長(timestep)的控制,第5行刷新每個時間步長環境畫面,第6行對當前環境狀態採取一個隨機動作(0或1),最後第7行迴圈結束後關閉模擬環境。
2.1 觀測(Observations)
在上面代碼中使用了env.step()函數來對每一步進行模擬,在Gym中,env.step()會返回 4 個參數:
- 觀測 Observation (Object):當前step執行後,環境的觀測(類型為對象)。例如,從相機獲取的像素點,機器人各個關節的角度或棋盤游戲當前的狀態等;
- 獎勵 Reward (Float): 執行上一步動作(action)後,智能體( agent)獲得的獎勵(浮點類型),不同的環境中獎勵值變化範圍也不相同,但是強化學習的目標就是使得總獎勵值最大;
- 完成 Done (Boolen): 表示是否需要將環境重置 env.reset。大多數情況下,當 Done 為True 時,就表明當前回合(episode)或者試驗(tial)結束。例如當機器人摔倒或者掉出臺面,就應當終止當前回合進行重置(reset);
- 信息 Info (Dict): 針對調試過程的診斷信息。在標準的智體模擬評估當中不會使用到這個info,具體用到的時候再說。
總結來說,這就是一個強化學習的基本流程,即"agent-environment loop",在每個時間點上,智能體(可以認為是你寫的演算法)選擇一個動作(action),環境返回上一次action的觀測(Observation)和獎勵(Reward),用圖表示為
在 Gym 模擬中,每一次回合開始,需要先執行 reset() 函數,返回初始觀測信息,然後根據標誌位 done 的狀態,來決定是否進行下一次回合。所以更恰當的方法是遵守done的標誌,同樣我們可以參考OpenAI Gym官方文檔中的代碼如下
import gym
env = gym.make('CartPole-v0')
for i_episode in range(20):
observation = env.reset()
for t in range(100):
env.render()
print(observation)
action = env.action_space.sample()
observation, reward, done, info = env.step(action)
if done:
print("Episode finished after {} timesteps".format(t+1))
break
env.close()
當done 為真時,控制失敗,此階段episode 結束。可以計算每 episode 的回報就是其堅持的t+1時間,堅持的越久回報越大,在上面演算法中,agent 的行為選擇是隨機的,平均回報為20左右。
2.2 空間(Spaces)
在前面的兩個小例子中,每次執行的動作(action)都是從環境動作空間中隨機進行選取的,但是這些動作 (action) 是什麼?在 Gym 的模擬環境中,有運動空間 action_space 和觀測空間observation_space 兩個指標,程式中被定義為 Space類型,用於描述有效的運動和觀測的格式和範圍。下麵是一個代碼示例:
import gym
env = gym.make('CartPole-v0')
print(env.action_space)
#> Discrete(2)
print(env.observation_space)
#> Box(4,)
從程式運行結果可以看出:
- action_space 是一個離散Discrete類型,從discrete.py源碼可知,範圍是一個{0,1,...,n-1} 長度為 n 的非負整數集合,在CartPole-v0例子中,動作空間表示為{0,1}。
- observation_space 是一個Box類型,從box.py源碼可知,表示一個 n 維的盒子,所以在上一節列印出來的observation是一個長度為 4 的數組。數組中的每個元素都具有上下界。
print(env.observation_space.high)
print(env.observation_space.low)
[4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38]
[-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38]
利用運動空間和觀測空間的定義和範圍,可以將代碼寫得更加通用。在許多模擬環境中,Box和Discrete是最常見的空間描述,在智體每次執行動作時,都屬於這些空間範圍內,代碼示例為:
from gym import spaces
space = spaces.Discrete(8)
# Set with 8 elements {0, 1, 2, ..., 7}
x = space.sample()
print(space.contains(x))
print(space.n == 8)
True
True
在CartPole-v0慄子中,運動只能選擇左和右,分別用{0,1}表示。
2.3 OpenAI Gym中可用的環境
Gym中從簡單到複雜,包含了許多經典的模擬環境和各種數據,其中包括:
-
經典控制和文字游戲:經典的強化學習示例,方便入門;
-
演算法:從例子中學習強化學習的相關演算法,在Gym的模擬演算法中,由易到難方便新手入坑;
-
雅達利游戲:利用強化學習來玩雅達利的游戲。Gym中集成了對強化學習有著重要影響的Arcade Learning Environment,並且方便用戶安裝;
-
2D和3D的機器人:這個是我一直很感興趣的一部分,在Gym中控制機器人進行模擬。需要利用第三方的物理引擎如 MuJoCo。
2.4 註冊表
Gym是一個包含各種各樣強化學習模擬環境的大集合,並且封裝成通用的介面暴露給用戶,查看所有環境的代碼如下
from gym import envs
print(envs.registry.all())
#> [EnvSpec(DoubleDunk-v0), EnvSpec(InvertedDoublePendulum-v0), EnvSpec(BeamRider-v0), EnvSpec(Phoenix-ram-v0), EnvSpec(Asterix-v0), EnvSpec(TimePilot-v0), EnvSpec(Alien-v0), EnvSpec(Robotank-ram-v0), EnvSpec(CartPole-v0), EnvSpec(Berzerk-v0), EnvSpec(Berzerk-ram-v0), EnvSpec(Gopher-ram-v0), ...
Gym支持將用戶製作的環境寫入到註冊表中,需要執行 gym.make()和在啟動時註冊register,具體可參考這篇博客:gym介紹。同時我們可以通過寫入新的註冊表實現對環境中的某些參數設置進行修改,例如
form gym.envs.registration import register
register(
id='CartPole-v2',
entry_point='gym.envs.classic_control:CartPoleEnv',
max_episode_steps=200*4,
reward_threshold=195.0*4,
)
env = gym.make('CartPole-v2')
2.5 OpenAI Gym評估平臺
用戶可以記錄和上傳演算法在環境中的表現或者上傳自己模型的Gist,生成評估報告,還能錄製模型玩游戲的小視頻。在每個環境下都有一個排行榜,用來比較大家的模型表現。詳細介紹可以參考這篇博文:OpenAI Gym評估平臺、OpenAI教程,當然更加準確的表述還是應該參考OpenAI Gym官方文檔。
3. CartPole-v0/1原理與功能
在CartPole-v0的環境中,實際參考了論文:AG Barto, RS Sutton and CW Anderson, "Neuronlike Adaptive Elements That Can Solve Difficult Learning Control Problem", IEEE Transactions on Systems, Man, and Cybernetics, 1983.中的倒立擺控制問題。
Cart Pole即車桿游戲,游戲模型如下圖所示。游戲裡面有一個小車,上有豎著一根桿子,每次重置後的初始狀態會有所不同。小車需要左右移動來保持桿子豎直,為了保證游戲繼續進行需要滿足以下兩個條件:
- 桿子傾斜的角度\(\theta\)不能大於15°
- 小車移動的位置\(x\)需保持在一定範圍(中間到兩邊各2.4個單位長度)
動作(action):
- 左移(0)
- 右移(1)
狀態變數(state variables):
- \(x\):小車在軌道上的位置(position of the cart on the track)
- \(\theta\):桿子與豎直方向的夾角(angle of the pole with the vertical)
- \(\dot{x}\):小車速度(cart velocity)
- \(\dot{\theta }\):角度變化率(rate of change of the angle)
import gym
env = gym.make('CartPole-v0')
observation = env.reset()
print(observation)
#> [-0.00478028 -0.02917182 0.00313288 0.03160127]
以上代碼顯示了初始狀態下的取值,每次調用env.reset( )將重新產生一個初始狀態。列印出的observation的四個元素分別表示了小車位置、小車速度、桿子夾角及角變化率。
游戲獎勵(reward):
在gym的Cart Pole環境(env)裡面,左移或者右移小車的action之後,env會返回一個+1的reward。其中CartPole-v0中到達200個reward之後,游戲也會結束,而CartPole-v1中則為500。最大獎勵(reward)閾值可通過前面介紹的註冊表進行修改。
4. 爬山演算法解決倒立擺問題
為了能夠有效控制倒立擺首先應建立一個控制模型。明顯的,這個控制模型的輸入應該是當前倒立擺的狀態(observation)而輸出為對當前狀態做出的決策動作(action)。從前面的知識我們瞭解到決定倒立擺狀態的observation是一個四維向量,包含小車位置(\(x\))、桿子夾角(\(\theta\))、小車速度(\(\dot{x}\))及角變化率(\(\dot{\theta }\)),如果對這個向量求它的加權和,那麼就可以根據加權和值的符號來決定採取的動作(action),用sigmoid函數將這個問題轉化為二分類問題,從而可以建立一個簡單的控制模型。其模型如下圖所示:
上圖的實際功能與神經網路有幾分相似,但比神經網路要簡單得多。通過加入四個權值,我們可以通過改變權重值來改變決策(policy),即有加權和\(H_{sum}=\omega _{1}x+\omega _{2}\theta +\omega _{3}\dot{x}+\omega _{4}\dot{\theta}+b\),若\(H_{sum}\)的符號為正判定輸出為1,否則為0。為了得到一組較好的權值從而有效控制倒立擺,我們可以採用爬山演算法(hill climbing algorithm)進行學習優化。爬山演算法是一種啟髮式方法,是對深度優先搜索的一種改進,它利用反饋信息幫助生成解的決策。
爬山演算法的基本思路是每次迭代時給當前取得的最優權重加上一組隨機值,如果加上這組值使得有效控制倒立擺的持續時間變長了那麼就更新它為最優權重,如果沒有得到改善就保持原來的值不變,直到迭代結束。在迭代過程中,模型的參數不斷得到優化,最終得到一組最優的權值作為控制模型的解。其代碼如下:
# coding: utf8
import numpy as np
import gym
import time
def get_action(weights, observation):# 根據權值對當前狀態做出決策
wxb = np.dot(weights[:4], observation) + weights[4] # 計算加權和
if wxb >= 0:# 加權和大於0時選取動作1,否則選取0
return 1
else:
return 0
def get_sum_reward_by_weights(env, weights):
# 測試不同權值的控制模型有效控制的持續時間(或獎勵)
observation = env.reset() # 重置初始狀態
sum_reward = 0 # 記錄總的獎勵
for t in range(1000):
# time.sleep(0.01)
# env.render()
action = get_action(weights, observation) # 獲取當前權值下的決策動作
observation, reward, done, info = env.step(action)# 執行動作並獲取這一動作下的下一時間步長狀態
sum_reward += reward
# print(sum_reward, action, observation, reward, done, info)
if done:# 如若游戲結束,返回
break
return sum_reward
def get_weights_by_random_guess():
# 選取隨機猜測的5個隨機權值
return np.random.rand(5)
def get_weights_by_hill_climbing(best_weights):
# 通過爬山演算法選取權值(在當前最好權值上加入隨機值)
return best_weights + np.random.normal(0, 0.1, 5)
def get_best_result(algo="random_guess"):
env = gym.make("CartPole-v0")
np.random.seed(10)
best_reward = 0 # 初始最佳獎勵
best_weights = np.random.rand(5) # 初始權值為隨機取值
for iter in range(10000):# 迭代10000次
cur_weights = None
if algo == "hill_climbing": # 選取動作決策的演算法
# print(best_weights)
cur_weights = get_weights_by_hill_climbing(best_weights)
else: # 若為隨機猜測演算法,則選取隨機權值
cur_weights = get_weights_by_random_guess()
# 獲取當前權值的模型控制的獎勵和
cur_sum_reward = get_sum_reward_by_weights(env, cur_weights)
# print(cur_sum_reward, cur_weights)
# 更新當前最優權值
if cur_sum_reward > best_reward:
best_reward = cur_sum_reward
best_weights = cur_weights
# 達到最佳獎勵閾值後結束
if best_reward >= 200:
break
print(iter, best_reward, best_weights)
return best_reward, best_weights
# 程式從這裡開始執行
print(get_best_result("hill_climbing")) # 調用爬山演算法尋優並輸出結果
# env = gym.make("CartPole-v0")
# get_sum_reward_by_weights(env, [0.22479665, 0.19806286, 0.76053071, 0.16911084, 0.08833981])
爬山演算法本質是一種局部擇優的方法,效率高但因為不是全局搜索,所以結果可能不是最優。在這裡採用的模型較為簡單,如若想要獲得更好的學習效果可以考慮更加複雜的模型,如深度神經網路。
5. 結束語
可以看到網上有很多通過深度Q學習演算法解決倒立擺問題的文章,DQN確實不失為一種較好的解決方法,不過作為強化學習的基礎部分這裡就總結這麼多了,關於DQN後面也會具體總結介紹。
由於博主能力有限,博文中提及的方法與代碼即使經過測試,也難免會有疏漏之處。希望您能熱心指出其中的錯誤,以便下次修改時能以一個更完美更嚴謹的樣子,呈現在大家面前。同時如果有更好的實現方法也請您不吝賜教。
人工智慧博士,機器學習及機器視覺愛好者,公眾號主及B站UP主,專註專業知識整理與項目總結約稿、軟體項目開發、原理指導請聯繫微信:sixuwuxian(備註來意),郵箱:[email protected],微信公眾號:“AI技術研究與分享”。