文章目錄
前言
重讀《Deep Reinforcemnet Learning Hands-on》, 常讀常新, 極其深入淺出的一本深度強化學習教程。 本文的唯一貢獻是對其進行了翻譯和提煉, 加一點自己的理解組織成一篇中文筆記。
第二章 OpenAI Gym
經過第一章對基本概念的介紹之後, 這一章進行一些代碼相關的實戰。
深入解析Agent
Agent, 就是實行某些決策 (policy),做出動作與環境交互的角色。 爲了對Agent進行深入的解析, 我們首先創建一個簡單的環境對象。 這個對象可能沒什麼實際意義, 但有助於概念的理解:
import torch
import random
class Environment:
def __init__(self):
self.steps_left = 10
def get_observation(self):
return [0, 0, 0]
def get_actions(self):
return [0, 1]
def is_done(self):
return self.steps_left == 0
def action(self, action):
if self.is_done():
raise Exception("Game is over")
self.steps_left -= 1
return random.random()
上述代碼實現了一個極爲簡單的環境, 但是卻包含了所有重要組件:
- __init__初始化: 環境狀態初試化。 這裏定義,可執行的動作步驟次數初始爲10.
- get_observation 獲取觀測: 這個方法用於返回當前的環境觀測狀態給Agent。 在這個簡化例子中, 我們認爲狀態永遠0, 即這個環境並沒有什麼變化狀態。
- get_actions: 告知Agent,當前可使用的Action集合,即動作空間。 這個集合偶爾會隨着狀態時間變化——比如機器人運動到角落裏,那就不能再向右移動。 這個例子中認爲只有0和1兩種動作,也沒有賦予具體的物理含義。
- is_done: 判斷回合 (episodes)是否結束。 回合可以理解爲一輪遊戲, 由一系列步驟組成。 比如本例之中, 從一開始初始化的10次剩餘步驟,到最後10次全部走完, 就是一個回合結束。
- action:最重要的一個方法。 核心任務分兩樣: 處理用戶的動作進行響應 和 返回該動作所對應的reward。在這一例子中, 處理用戶的動作的響應就是剩餘步驟減一, 而返回的獎勵值是一個隨機數。
接下來,是Agent 部分:
class Agent:
def __init__(self):
self.total_reward = 0.0
def step(self, env):
current_obs = env.get_observation()
actions = env.get_actions()
reward = env.action(random.choice(actions))
self.total_reward += reward
同樣是簡單卻包括了各種組件的分析:
- init: 初始化,歸零reward以便統計。
- step 函數:核心函數,負責四項任務:
- 觀測環境:
env.get_observation()
- 進行決策:
random.choice(actions)
本例中使用隨機決策。 - 提交動作,和環境交互:
env.action(random.choice(actions))
- 獲得獎勵值,並統計:
reward = env.action(random.choice(actions))
- 觀測環境:
最後,使用粘合代碼,進行程序運行:
if __name__ == "__main__":
env = Environment()
agent = Agent()
while not env.is_done():
agent.step(env)
print("Total reward got: %.4f" % agent.total_reward)
這只是一段簡單的示例代碼: 強化學習的模型當然可以很複雜——這個環境可以是很複雜的物理環境, Agent也可以是使用了神經網絡技術的最新RL算法。 但是,他們在本質上是相同的:
在每一步中, Agent負責從環境中獲得觀測, 並作出決策選擇動作與環境交互。 其結果是到達新的狀態(observation), 並獲得返回的對應reward值。
有讀者可能會問, 如果Agent 和 環境的結構, 如此接近, 是不是早已有人寫好了相關的框架呢?
介紹框架前的準備
請確保有以下python庫:
- numpy
- OpenCV Python bindings
- Gym
- Pytorch
- Ptan
這是原文裏提到, 經實踐,發現 其實 只需 下載 Pytorch, Ptan 和 OpenCV, Pytorch自帶numpy, Ptan自帶Gym。
命令, 因書中版本匹配問題, Ptan不能適應於最新的Pytorch 1.4.0, 因此,需要用下方命令下載:
pip install torch==1.3.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
這是下載pytorch 1.3.0版本, CPU, 不考慮CUDA的GPU加速。
然後Ptan只需
pip install Ptan
即可了。
OpenCV的下載可以參考:傳送門
OpenAI Gym API
Gym 是 OpenAI開發的一個API庫, 提供了大量使用了統一接口的環境, 即 Env。以下將介紹, Gym提供的Env環境中包含哪些組件。
- Action Space 動作空間: 在環境中允許操作的動作集合, 包括離散動作, 連續動作, 及他們的混合。
- Observation Space 觀測空間:觀測即每個時間戳上環境提供給Agent的相關信息, 包括reward。Observation可以簡單的只有幾個數字, 也可以複雜到許多高維的圖像。 觀測空間也可以是離散的, 如 燈泡的開關狀態。
- 一個 step方法, 用於執行動作, 獲取新狀態 和 對應的 reward, 並顯示回合是否結束。
- 一個 reset方法, 讓環境回到初始狀態,重新開始一個新的回合。
Space 類
Gym 中 有一個 Space 抽象類,有以下繼承類:
- Discrete(n), 代表一個離散的空間 0 ~ n- 1。比如 Discrete(n=4)就可以用來代表動作空間 [上, 下, 左, 右]。
- Box(low, high, shape), 代表一個連續的空間, 範圍爲low~high。比如, 一張210 * 160 的RGB圖像, 其可以表示爲 Box(low = 0, high =255, shape = (210, 160, 3)) 類的一個實例。
- Tuple, 前兩個類的組合, 如 Tuple(Discrete(4), Box(1,1, (1,)))。
這些類用於代碼實現 動作空間和觀測空間。 他們都必須實現Space類的兩個方法: - sample(), 從空間中隨機採樣一份, 如
Discrete(4).sample()
就可以表示從4個動作隨機選擇一個動作。 - contains(), 檢查輸入是否存在於空間中, 一般用於檢查執行的動作是否合理(在動作空間內)等。
Env 類
將之前的文字描述用實際代碼來講述:Env類包括以下四個類成員:
- action_space: 環境內允許的動作集合。
- observation_space:提供觀測
- reset(): 重置回合
- step():最重要的方法:執行動作, 獲取reward, 檢測是否回合結束。
還有一些 類似 render()這樣的方法, 可以讓環境用更人性化的方式體現。 但我們現在的重點顯然是reset() 和 step()兩個方法。
reset()方法不需要任何參數, 就是重啓遊戲到初始狀態,返回值即是初次觀測。
step()方法
step方法一共做了以下四個任務:
- 對環境執行本次決定的動作
- 獲取執行動作後的新觀測狀態
- 獲取本步中得到的reward
- 獲取遊戲是否結束的指示
step方法的輸入值只有一個, 就是執行的動作 action,其餘都是返回值。
因此, 你大概已經get到了環境env的使用方法:
在一個循環中, 使用step()方法,直到回合結束。 然後用reset()方法重啓, 循環往復。剩下還有一個問題:如何創造環境實例?
創建環境
Gym中有各種各樣已被定義好的環境, 其命名格式都是:
環境名-vn, n代表版本號。 如 Breakout-v0。 Gym擁有超過700個已定義的環境, 除去重複的(版本不同的相同環境),也有100多種環境可供使用。 比如:
- 傳統控制問題: 一些經典RL論文的benchmark方法
- Atari 2600: 經典的小遊戲, 共63種。
更詳細的Gym預定義環境介紹可以在官網找到, 這裏不再贅述。
第一個Gym 環境實踐: CartPole
CartPole環境是對CartPole這個小遊戲的還原: 上圖的木棍會往 兩側傾斜, 玩家的任務是移動 下方的木塊——只能往左或往右,使得木棍不倒下。
- 環境的觀測值爲4個浮點數:
- 木棍質心的x座標
- 木棍的速度
- 木棍與木塊的夾角
- 木棍的角速度
- Agent的動作: 向左 或 向右
- Reward獎勵: 每一時間戳,木棍不倒, 獎勵+1
- is_done判斷: 木棍倒下時,回合(episode)結束
實現一個隨機的Agent
import gym
if __name__ == "__main__":
env = gym.make("CartPole-v0")
total_reward = 0.0
total_steps = 0
obs = env.reset()
while True:
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
total_reward += reward
total_steps += 1
if done:
break
print("Episode done in %d steps, total reward %.2f" % (total_steps, total_reward))
代碼非常簡單: 首先對獎勵值, 步驟數初始化爲0. 通過env.reset()函數, 獲取初始觀測obs。然後進入回合的循環, 當回合結束時(done=1)時退出循環。 通過 sample()方法, 隨機在動作空間中選取一個動作, 然後使用env.step()方法, agent與env進行交互, 獲取新的obs, 此步所得的reward, 以及是否結束的flag(done)。step()方法的最後一個返回參數爲默認的extra_info, 即額外信息,而本例中並沒有用到, 因此用 _ 來接受賦值。
由於是隨機選取動作的無腦Agent, 結果具有隨機性, 一般如下:
Episode done in 15 steps, total reward 15.00
一般在12-16次之間, 木棍就自然倒下了。 大部分環境都有其 “reward boundary”, 可以理解爲對Agent的評價指標的benchmark——比如CartPole的reward boundary是195,即堅持195次。 那麼顯然, 這個隨機的Agent的性能是非常低下的。 但這僅僅只是一個開始的示例, 後面我們會加以改進。
Gym 的 額外功能——裝飾器和監視器
迄今爲止,我們已經瞭解了Gym 三分之二的API功能。 這一額外功能並非必須, 但可以使得你的代碼更加輕鬆自如。
裝飾器 Wrappers
我們經常會遇到以下一些情況:
- 希望把過往的觀測儲存下來, 並將最近的N個觀測均返回給 Agent, 這在遊戲任務中很常見。
- 有時候希望對圖像觀測進行一些預處理,使得Agent更容易接受。
- 有時候希望對reward進行歸一化。
這時候, 裝飾器可以允許你在原有的Gym自帶的observation上進行自定義的修改。
針對不同的自定義需求, Wrapper也分爲上圖三個具體子類:觀測裝飾器,動作裝飾器和獎勵裝飾器。 分別暴露了 observation(obs)函數接口, reward(rew)函數接口, 和 action(act)函數接口用於自定義的改寫。
以下, 用一個動作裝飾器作爲例子展示下裝飾器的使用。 我們的目的是自定義動作函數: 即在本來的策略下, 10%的概率採用隨機的動作。 這一點在強化學習中很常用, 即不滿足於現有策略, 也會使用隨機策略來進行探索改進。
import gym
import random
class RandomActionWrapper(gym.ActionWrapper):
def __init__(self, env, epsilon=0.1):
super(RandomActionWrapper, self).__init__(env)
self.epsilon = epsilon
def action(self, action):
if random.random() < self.epsilon:
print("Random!")
return self.env.action_space.sample()
return action
if __name__ == "__main__":
env = RandomActionWrapper(gym.make("CartPole-v0"))
obs = env.reset()
total_reward = 0.0
while True:
obs, reward, done, _ = env.step(0)
total_reward += reward
if done:
break
print("Reward got: %.2f" % total_reward)
首先, 創建一個RandomActionWrapper類, 繼承自 ActionWrapper類。 初始化一個epsilon值0.1, 即代表10%的概率。 然後需要改寫action()方法: 接受action爲參數, 然後返回被我們修改的自定義的action,這裏選用的就是random的隨機動作, 實現了我們的目標。
後續的環境使用則和之前的例子沒有區別, 在運行中沒有用到action()類, 這個我們自定義的方法會在env.step()自動調用, 即我們簡潔的完成了對action的自定義, 而在外部看來沒有什麼區別。
這裏的裝飾器類, 和python的裝飾器的效果是類似的。 只是用法不同, python裝飾器是用@來實現, 而這裏則是用類的繼承。
監視器 Monitor
用於記錄訓練過程,筆者這裏暫時配置失敗, 沒能成功運行, 考慮到對強化學習的理解,影響不大,這裏先不再深究。
總結
本章介紹了 Gym庫的基本使用——如何用他人的框架快速搭建環境和Agent。 下一章中, 我們會快速回顧學習下 用pytorch實現 的 深度學習。