DeepReinforcementLearning筆記(二)

DeepReinforcementLearning筆記(二)

來自專欄我的機器學習的感悟

第二章 OpenAI Gym

在談了那麼多關於RL的理論概念之後,讓我們開始做一些實際的事情。在本章中,我們將學習OpenAI Gym API的基礎知識,並編寫我們的第一個隨機行為代理,來熟悉所有的概念。

再談Agent

正如在前一章中講的,在RL的理論體系中有如下幾類實體:

  • Agent:能動的人或物體。在實踐中,它會是一些代碼,實現一些策略,策略決定Agent每一步要採取什麼動作。
  • Environment:世界的某種模型,Agent之外的因素,給我們提供參照和行動反饋。它會根據Agent的行為改變它的狀態。

下面展示如何用Python實現這兩種概念。我們將定義一個Environment,不管Agent如何動作,在有限的步驟內給Agent隨機的獎勵。這個場景沒有實際的價值,但是可以讓我們一下子看到Environment和Agent類中的特定方法。讓我們從環境開始:

class Environment:

def __init__(self):

self.steps_left = 10

在前面的代碼中,環境初始化它的內部狀態。在我們的例子中,狀態只是一個計數器,它限制了Agent與Environment交互所允許的時間步驟:

def get_observation(self):

return [0.0, 0.0, 0.0]

get_observation()方法應該將當前環境的觀察返回給代理。它通常作為環境內部狀態的某種函數來實現。在本例中,觀測向量總是0,因為環境基本上沒有內部狀態:

def get_actions(self):

return [0, 1]

get_actions()方法允許Agent查詢它可以執行的操作集。通常,Agent可以執行的操作集不會隨時間變化,但是並不一定每個狀態下所有動作都能執行(例如,在TicTacToe遊戲的任何位置中,並不是每個動作都可能執行)。在我們的簡單示例中,Agent只能執行兩個操作,它們用整數0和1編碼:

def is_done(self):

return self.steps_left == 0

這個方法是通知代理髮出這個一個情節(一個Agent移動的序列episode)結束的信號。正如我們在第一章了解到的,Environment—Agent交互的系列被劃分為一系列步驟,稱為情節(Episode)。情節可以是有限的,就像下棋一樣,總會有棋局結束的時刻,也可以是無限的,就像旅行者2號飛向外太空一樣(旅行者2號是40多年前發射的一枚著名的太空探測器,它已經穿越了太陽系)。為了涵蓋這兩種情況,Environment為我們提供了一個方法,來檢測某一情節(episode)何時結束,情節結束後在也無法與Environment進行通信:

def action(self, action):

if self.is_done():

raise Exception("Game is over")

self.steps_left -= 1

return random.random()

action()方法是環境功能的核心部分。它做兩件事:處理代理的動作並返回此動作的獎勵。在本例中,獎勵是隨機的,它的動作內部沒有具體功能的行為。另外,我們更新了步驟的計數,拒絕繼續已經結束的情節(episode)。

接下來我們看看Agent部分的設計和實現,它要簡單得多,只包含兩個方法:構造函數和在環境中執行一個步驟的方法:

class Agent:

def __init__(self):

self.total_reward = 0.0

在構造函數中,初始化了計數器,該計數器將保存Agent在情節(episode)期間累積的總報酬:

def step(self, env):

current_obs = env.get_observation()

actions = env.get_actions()

reward = env.action(random.choice(actions))

self.total_reward += reward

step函數接受環境實例作為參數,並允許代理執行以下操作:

  • 觀察環境
  • 根據觀察結果做出決定採取什麼樣的下一步動作
  • 將動作提交給環境
  • 獲得當前步驟的獎勵

在這個例子中,Agent很遲鈍,在決定採取何種動作的過程中忽略了獲得的觀察結果。相反,每個動作都是隨機選擇的。最後一個部分是glue代碼,它創建了兩個類並運行一個情節(episode):

if __name__ == "__main__":

env = Environment()

agent = Agent()

while not env.is_done():

agent.step(env)

print("Total reward got: %.4f" % agent.total_reward)

您可以在本書的Git存儲庫中找到前面的代碼,網址是github.com/PacktPublish,參見Chapter02/01_agent_anatomy.py目錄。它不依賴任何Python包,可以在任何的Python版本下運行。通過多次運行它,您將獲得代理收集的不同數量的獎勵。

前面代碼的簡單性使我們能夠說明來自RL模型的重要基本概念。環境可能是一個極其複雜的物理模型,神經網路,一個代理可以很容易地實現最新的RL演算法,但基本模式保持不變:在每一步,代理需要從環境中觀察到的一些情況,它的計算,並選擇行動的問題。這一行動的結果是一種獎勵和新的觀察。

您可能想知道,如果編碼模式相同,為什麼需要從頭開始編寫它?難道沒人實現一個框架,可以用作庫?當然,這樣的框架是存在的,在我們討論它們之前,讓我們準備一下您的開發環境。

硬體和軟體需求

本書中的示例是使用Python 3.6版本實現和測試的。我假設您已經熟悉虛擬環境之類的語言和通用概念,因此我將不詳細介紹如何安裝包以及如何以獨立的方式進行安裝。我們將在本書中使用的外部庫是開源軟體,包括以下內容:

  • Numpy:科學計算和實現矩陣運算、通用函數的庫。
  • OpenCV Python Binding:計算機視覺庫,提供了許多圖像處理功能。
  • Gym:這是OpenAI開發和維護的RL框架,可以用統一的方式與各種環境進行通信。
  • PyTorch:這是一個靈活而富有表現力的深度學習(DL)庫。下一章將提供一個關於它的簡短的基本速成課程。
  • Ptan(github.com/Shmuma/ptan):這是作者為支持現代深度RL方法而創建的Gym的開源擴展。所有使用的類都將與源代碼一起詳細描述。

本書的很大一部分(第二部分、第三部分和第四部分)關注的是過去幾年發展起來的現代深度RL方法。在這種情況下,單詞「deep」意味著深度學習被大量使用,你可能知道DL方法需要計算。一個現代的GPU可以比最快的多處理器系統快10到100倍。在實踐中,這意味著同樣的代碼在一個有GPU的系統上需要一個小時的訓練,即使在最快的CPU系統上也需要半天到一周的時間。這並不意味著您不能在沒有GPU的情況下嘗試本書中的示例,但這會花費更長的時間。要自己試驗代碼(這是學習任何東西最有用的方法),最好是使用具有GPU的機器。這可以通過多種方式實現:

  • 購買一塊支持CUDA的GPU
  • 使用雲實例:Amazon AWS和谷歌雲都可以為您提供gpu驅動的實例

如何配置系統超出了這本書的範圍,但是在互聯網上有大量的手冊可用。就操作系統而言,您應該使用Linux或macOS,因為PyTorch和GYM都不支持Windows(現在已支持)。

為了讓您確切地了解我們將在本書中使用的外部依賴關係的版本,下面是pip freeze命令的輸出(它可能對書中示例的故障排除很有用,因為開放源代碼軟體和DL工具包的發展非常迅速):

numpy==1.14.2

atari-py==0.1.1

gym==0.10.4

ptan==0.3

opencv-python==3.4.0.12

scipy==1.0.1

torch==0.4.0

torchvision==0.2.1

tensorboard-pytorch==0.7.1

tensorflow==1.7.0

tensorboard==1.7.0

書中的所有示例都是用PyTorch 0.4編寫和測試的,可以用pip install PyTorch==0.4.0命令安裝。

現在,讓我們來看一下OpenAI Gym API的細節,它並不複雜,但是為我們提供了大量的環境,從瑣碎的到有挑戰性的。

OpenAI GYM API

Python庫Gym是由OpenAI (openai.com)開發和維護的。Gym的主要目標是使用統一的介面為RL實驗提供豐富的環境集合。因此,庫中的中心類是一個名為Env的環境就不足為奇了。它公開了一些方法和欄位,這些方法和欄位提供了關於環境功能的必要信息。從高層來說,每個環境都為您提供這些信息和功能:

  • 允許在環境中執行的一組操作。GYM支持離散和連續的動作,以及它們的組合。
  • 環境給代理提供了觀察環境的的參數和介面。
  • step的方法,用於執行操作,它返回當前的觀察結果、獎勵以及情節(episode)結束的指示。
  • reset的方法,用於將環境返回到初始狀態並獲得第一次觀察。

讓我們詳細討論一下環境的這些組件。

動作空間

您可能還記得,Agent可以執行的動作可以是離散的、連續的或兩者的組合。離散操作是一個Agent可以做的一組固定的動作,例如,網格中的方向,比如左、右、上或下;另一個例子是按鈕,可以按下或釋放,這兩種狀態是相互排斥的。離散動作空間的一個主要特徵:Agent行動每次只能從動作空間選取一個動作。

一個連續的動作必須有個連續值來反應器變化,例如:方向盤,可以在一角度的範圍內轉動,油門踏板,踩踏踏板力度值的的範圍。對連續動作的描述的主要指標是動作的取值範圍。方向盤一般是:?720度到720度。對於一個油門踏板,它通常是從0到1。

當然,我們不能局限於執行一個動作,環境可以有多個動作,比如同時按多個按鈕,或者操縱方向盤、按兩個踏板(剎車和加速器)。為了支持這種情況,Gym定義了一個特殊的容器類,它允許多個動作空間嵌套為一個統一的動作空間。

觀察空間

觀察是環境在每個時間戳上除了獎勵之外給Agent提供的信息片段。觀測可以是簡單一堆數字,也可以是複雜的多個包含多種相機提供的彩色圖像的多維張量。觀察甚至可以是離散的,象動作空間那樣,比如,對一個有兩種狀態的燈泡,開或關,離散觀察空間對外反饋的就是0和1的布爾值。

所以,在GYM中,你可以看到動作(action)和觀察(observation)之間的相似性,以及如在表示,我們來看一個類圖:

圖1:Gym中空間類的層次結構

基本抽象類Space包含與我們相關的兩個方法:

  • sample():將從空間返回一個隨機樣本
  • contains(x):檢查參數x是否屬於空間域

這兩種方法都是抽象的,在Space的子類中重新實現:

  • Discrete類代表一組互斥項目,編號從0到n?1。它唯一的欄位n是它描述的項的計數。例如,Discrete(n=4)表示有四個方向移動動作的動作空間 :[左、右、上或下]。
  • Box類表示一個n維張量,每一維的區間為(低,高)。
    1. 例如:一個單值在0.0到1.0之間的油門踏板可以用Box(low=0.0, high=1.0, shape=(1,),dtype=np.float32)來表示,其中 shape(1,)是一個一維張量,目前只有一個值:1。dtype參數指定了空間的值類型,這裡將其指定為一個32位的NumPy浮點型。
          1. Box類的的另一個例子Atari屏幕Observation(稍後我們會看到很多Atari環境的例子),屏幕的Observation是一個大小為210×160 RGB圖像:Box(low= 0,high= 255,shape=(210、160、3),dtype = np.uint8)。在本例中,shape參數是三個元素的元組:第一個維度是圖像的高度,第二個維度是圖像的寬度,第三個維度是圖像的通道(也稱層)3,分別對應紅、綠、藍三個顏色平面。所以,每一次觀測都是一個有100800位元組的三維張量。
    • 這裡我們要討論的Space的最後一個子類是一個元組類,它允許我們將幾個空間類實例組合在一起,有了它我們就能創建任何的複雜的行為和觀察空間。例如,假設我們想為汽車創建一個動作空間規範。汽車有幾個控制單元可以隨時改變汽車的狀態:方向盤的角度,剎車踏板和油門踏板的位置。這三個控制項可以由一個框實例中的三個浮點值指定。除了這些基本的控制,汽車有額外的離散控制,如轉向燈(「關」、「右」或「左」)、喇叭(「開」或「關」),等等。將所有這些組合到一個動作空間規範類中,創建Tuple(space =(Box(low=-1.0, high=1.0, shape=(3, ), dtype=np.float32), Discrete(n=3), Discrete(n=2))。這種靈活性很少被使用,在本書中,我們用到的大多數還是Box和Diskcrete的動作和觀察空間獨立形式,元組類在少數的情況下會用到。

    在Gym中還定義了其他的Space子類,但最有用的還是上面提到的三個。所有子類都實現sample()和contains()方法。sample()函數的作用:執行與Space類和參數相對應的隨機採樣,當我們需要選擇隨機動作時,這對於動作空間非常有用。contains()方法驗證給定的參數是否符合Space參數要求,在Gym的內使用它來檢查Agent的動作是否健全。例如,discrete .sample()從一個離散範圍返回一個隨機元素,Box.sample()返回的是一個合適維度隨機張量,這個張量的每一個維度的值的取值範圍在規定之內。

    每個環境都有兩個類型空間的成員,稱為action_space和observation_space。這允許您創建通用代碼,可以在任何環境下工作。當然,處理屏幕上的像素不同於處理離散觀察(在前一種情況下,您可能會使用卷積層或計算機視覺庫中的其他方法對圖像進行預處理);所以,大多數時候,我們會為特定的環境或一組環境優化代碼,但是Gym不會阻止你編寫通用代碼。

    環境(The Environment)

    Gym的環境由Env類代表,它有以下成員:

    • action_space:這是Space類的欄位,提供了環境中允許的動作的規範。
    • observation_space:也是Space類的欄位,但是指定了環境提供的觀察。
    • reset():將環境重置為初始狀態,返回初始觀察向量
    • step():此方法允許Agent執行動作(action)並返回關於執行動作的結果信息:下一個觀察、本次獎勵和情節是否結束標誌。這個方法有點複雜,我們將在本節後面詳細討論。

    Env類中還有一些其他的實用方法,比如render(),它允許您以人類習慣的形式獲得觀察結果,但我們不會使用它們。您可以在Gym的文檔中找到完整的列表,但是現在讓我們關注核心Env方法:reset()和step()。

    到現在為止,我們已經了解了代碼如何獲得關於環境動作和觀察的信息,所以現在是熟悉動作本身的時候了。與環境進行通信是通過Env類的兩個方法:step和reset。

    由於reset更簡單,我們將從它開始。reset()方法沒有參數,它指示環境將其重置為初始狀態並獲得初始觀察值。注意,在創建環境之後,必須調用reset()。我們第一章解釋過,Agent與Environment的通信可能會結束(就像屏幕上的「遊戲結束」)。這樣的會話稱為情節(episode),在情節結束後,Agent需要重新開始,這個方法返回的值就是對環境的第一次觀察。

    step()方法是環境功能的核心部分,它在一個調用中完成幾件事情,如下所示:

    1. 告訴環境我們將在下一步執行哪些動作
    2. 在執行動作之後從環境中獲得新的觀察
    3. 給Agent返回此步驟獲得的獎勵
    4. 獲得情節是否結束的標誌

    第一個項(Action)作為唯一參數傳遞給這個方法,其餘的由函數返回。確切地說,它是由四個元素(observation、reward、done、extra_info)組成的元組(tuple) (Python tuple,而不是我們在前一節中討論的tuple類)。它們的類型和含義如下:

    • observation:觀測數據,類型為NumPy向量或矩陣。
    • reward:獎勵,類型為浮點型。
    • done:布爾值,如果為True,表示情節(episode)結束。
    • extra_info:這可能是任何與環境相關的額外信息。通常的做法是在一般的RL方法中忽略這個值(不考慮特定環境的具體細節)。

    現在,大致描述一下在Agent代碼中使用Environment的方法:在循環中,調用step()方法並執行一個動作,直到該方法的done標記為True。然後我們調用reset()重新開始。那麼嗨缺什麼?環境,環境怎麼創建:下面我們就介紹如何創建環境對象。

    創造的環境

    每個環境都有一個獨特的EnvironmentName-vN表單,其中N是的數字,用於區分相同環境的不同版本 (例如,當某些bug在環境中得到修復或執行了一些其他主要更改時)。要創建環境,Gym包提供make(env_name)函數,這個函數就一個參數:環境名稱,以字元串形式出現。

    在編寫本書時,Gym 0.9.3版本包含了777個不同名稱的環境。當然,這不意味著是777個獨立不同環境,其中有很多是一個環境的不同版本。此外,相同的環境可能會在在配置和觀察空間中有不同的變數。例如,Atari遊戲的Breakout環境有以下環境名稱:

    • Breakout-v0,Breakout -v4:最初的Breakout環境,有一個隨機的初始位置和球的方向
    • BreakoutDeterministic-v0,BreakoutDeterministic -v4:具有相同的初始位置和球的速度矢量的Breakout環境
    • BreakoutNoFrameskip-v0, BreakoutNoFrameskip-v4:Breakout環境的每幀都會顯示給Agent
    • Breakout-ram-v0, Breakout-ram-v4觀察完整的Atari模擬內存(128位元組)而不是屏幕像素的Breakout環境。
    • Breakout-ramDeterministic-v0,Breakout-ramDeterministic-v4
    • Breakout-ramNoFrameskip-v0,Breakout-ramNoFrameskip-v4

    總的來說,Breakout環境有12個不同的衍生版本。如果你之前沒接觸過它,看看下圖,你就能大致明白它是什麼變,和大致的基本玩法:

    圖2:突破的遊戲玩法

    即使去掉了相同環境的衍生版本,Gym 0.9.3仍然提供了一個令人印象深刻的環境列表:116個不同的環境,這些環境根據其解決問題領域不同大致可以分成如下幾組:

    • 經典的控制問題:這是些標準場景,作為基準和演示用於最優控制理論和RL論文成果的檢驗。它們通常很簡單,具有低維的觀察和操作空間,主要用於新演算法的驗證。可以把它們看作是「RL的MNIST」(如果你還沒有聽說過MNIST,它是來自Yann LeCun的手寫數字識別數據集)。
    • Atari 2600:這是20世紀70年代經典遊戲平台,有63個不同的的遊戲。
    • 演算法:這些是旨執行小的計算任務,比如複製觀察到的序列或添加數字。
    • 棋盤遊戲:圍棋和Hex博弈遊戲。
    • Box2D: Box2D物理模擬器來學習步行或汽車控制。
    • MuJoCo:這是另一個物理模擬器,用於一些連續的控制問題。
    • 參數調優:RL用於優化神經網路參數的環境。
    • 玩具文本:簡單的網格世界文本環境。
    • PyGame:使用PyGame引擎實現的幾個環境。
    • Doom:這是在ViZdoom之上實現的九個迷你遊戲的環境。

    完整的環境列表可以在gym.openai.com/envs或項目GitHub知識庫的wiki頁面上找到。OpenAI Universe還提供了一組更強大的環境,它在提供了通用連接器連接到虛擬機,可以同時運行Flash和本地遊戲、web瀏覽器和其他真實世界的應用程序。OpenAI universal擴展了Gym API,但是遵循了相同的設計原則和範例。您可以查看github.com/openai/unive

    足夠的理論化了,現在讓我們來看一個Python會話,該會話與Gym的一個環境一起工作。

    CartPole會話

    $ python

    Python 3.6.5 |Anaconda, Inc.| (default, Mar 29 2018, 18:21:58)

    [GCC 7.2.0] on linux

    Type "help", "copyright", "credits" or "license" for more information.

    >>> import gym

    >>> e = gym.make(CartPole-v0)

    WARN: gym.spaces.Box autodetected dtype as <class numpy.float32>. Please provide explicit dtype.

    在這裡,我們導入Gym包並創建一個名為CartPole的環境,這個環境來自「經典控制組」,它的主旨是用底部帶著一根棍子的控制平台(見下圖)。棘手的是,這個棍子會向左或向右傾斜,你需要通過在每一步向左或向右移動使它平衡不會倒下。提示一下:我們看到的那個警告信息不是我們的錯,而是Gym內部的一個小小的不一致,並不影響結果。

    圖3:CartPole環境

    對這個環境的觀察是4個浮點數:操縱桿質心的x坐標、速度、它到平台的角度和它的角速度的信息。當然,通過運用一些數學和物理知識,在我們需要平衡它時將這些數據轉化為動作並不困難,但我們的問題更棘手:我們如何在不知道觀察數字確切含義和只能通過得到獎勵的情況下來平衡這個系統?在這個環境中,每一步的獎勵是1,情節繼續,直到棍子倒下,因此,為了獲得更多的獎勵,需要平衡平台以避免棍子倒下。

    這個問題看起來很困難,但是在後面兩章中,我們講編寫一個演算法,它可以在幾分鐘內輕鬆地解出這個CartPole平衡的問題,而且是在不需要知道觀察到的數字是什麼意思的情況下,我們將只通過試錯和一點點RL魔法來完成這個訓練。

    然而,讓我們繼續玩我們的會話:

    >>> obs = e.reset()

    >>> obs

    array([-0.04937814, -0.0266909 , -0.03681807, -0.00468688])

    在這裡,我們重置環境並獲得第一次觀察(我們總是需要重置新創建的環境)。就像我剛才說的,觀測結果是4個數字,是真的嗎?讓我們來看看:

    >>> e.action_space

    Discrete(2)

    >>> e.observation_space

    Box(4,)

    action_space欄位是離散類型,因此我們的操作將是0或1,其中0表示將平台向左推,1表示向右推。觀測空間Box(4,)意味著一個向量包含四個值,這四個值的取值範圍都是在[?正無窮,正無窮]區間:

    >>> e.step(0)

    (array([-0.04991196, -0.22126602, -0.03691181, 0.27615592]), 1.0, False, {})

    在這裡,我們通過執行動作0將平台推到左邊,得到了四個元素的元組:

    • 一個新的觀測結果,它是一個四位數的新向量
    • 獎勵為1.0
    • done標記= False,這意味著這集還沒有結束,我們還可以做其他的平衡動作
    • 環境的額外信息,這時是一個空字典

    >>> e.action_space.sample()

    0

    >>> e.action_space.sample()

    1

    >>> e.observation_space.sample()

    array([ 2.06581792e+00, 6.99371255e+37, 3.76012475e-02,

    -5.19578481e+37])

    >>> e.observation_space.sample()

    array([4.6860966e-01, 1.4645028e+38, 8.6090848e-02, 3.0545910e+37],

    dtype=float32)

    這裡,我們在action_space和observation_space上使用了Space類的sample()方法,該方法從底層空間返回一個隨機樣本:在離散動作空間中0和1隨機選一、觀察空間是一個4個數字的隨機向量,觀察空間的隨機樣本事實上用處不大,一般當我們不確定如何執行操作時,會執行動作空間的隨機動作,這個對現在還不了解任何RL方法又想使用Gym的初學者來說是一個福音。現在我們已經知道了足夠的知識來實現我們的第一個隨機行為的CartPole,讓我們來做一下。

    隨機CartPole代理

    雖然環境比我們在分析代理部分中的第一個示例複雜得多,但是代理的代碼要短得多。這就是可重用性、抽象和第三方庫的力量!

    下面是代碼(你可以在chapter02/02_cartpole_random.py中找到它):

    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))

    在這個循環中,我們取樣一個隨機動作,然後讓環境執行它,並返回給我們下一個觀察(obs)、獎勵和完成標記。如果這一集結束了,我們將停止循環,並顯示我們已經完成了多少步驟以及累積了多少獎勵。如果你開始這個例子,你會看到這樣的東西(不完全是,由於代理的隨機性):

    rl_book_samples/Chapter02$ python 02_cartpole_random.py

    WARN: gym.spaces.Box autodetected dtype as <class numpy.float32>. Please provide explicit dtype.

    Episode done in 12 steps, total reward 12.00

    (譯者註:可能你的步數和獎勵可能會不一樣,機器配置問題,不用緊張)

    與互動式會話一樣,警告與我們的代碼無關,而是與Gym的內部結構有關。平均來說,我們的隨機代理人會在桿落地或情節結束前走12-15步。大多數的Gym環境都有一個「獎勵邊界」,這是Agent在連續100情節「解決」 問題時環境應該獲得的平均獎勵。對於CartPole來說,這個邊界是195,這意味著平均來說,agent必須在195個步驟或更長時間內保持桿的平衡。從平均性能來說,我們的隨機Agent的性能真不咋地,但是,不要太早失望,因為我們才剛剛開始,很快我們就會解決CartPole和許多其他更有趣、更有挑戰性的環境。

    額外的健身房功能-包裝和顯示器

    到目前為止,我們討論的內容涵蓋了Gym核心API的三分之二,以及開始編寫Agent所需的基本功能。您可以不使用API的其餘部分,但使用它將使您的工作更輕鬆,代碼更簡潔。那麼,讓我們快速瀏覽一下API的其餘部分。

    包裝器

    通常,您需要以某種通用的方式擴展環境的功能,例如:在玩遊戲的時候我們都會碰到這樣的情形,一個獨立的遊戲幀無法判定當前遊戲的狀態,往往需要積累一定量的遊戲幀才能判定遊戲的狀態,這個時候我們會希望環境給一系列觀察,並適當緩存,通過提供給Agent最後N個觀察來判定整個場景的狀態;另一個例子:為了Agent能舒服的處理圖像,我們希望夠對圖像的進行裁剪或預處理,或者希望以合適的方式對獎勵分數進行規範化。類似這個兩個例子有很多這樣的情況有這樣的需求:希望「包裝」現有環境並添加一些額外的邏輯來執行某些操作。Gym為這些情形提供了一個方便的框架,稱為包裝類。類結構如下圖所示。

    包裝器類繼承Env類,它的構造函數接受唯一的參數:要「包裝」的Env類實例;要添加額外的功能,需要重新定義要擴展的方法,例如step()或reset(),唯一的需求是調用父類的原始方法。

    圖4:Gym中的包裝類的層次結構

    為了處理更具體的需求,例如包裝器類要麼處理來自環境的觀察要麼處理來自環境的動作,二者只能處理一種,包裝器的子類只允許過濾特定部分的信息。

    他們如下:

    • ObservationWrapper:你需要重新定義父對象的observation(obs)方法。obs參數是來自包裝環境的觀察,該方法返回給Agent的所需的觀察。
    • RewardWrapper:暴露父類的reward(rew)方法,這個方法里可以在給Agent返回獎勵值之前做預處理邏輯。
    • ActionWrapper:需要重寫action(act)方法,它可以調整包裝環境傳遞給Agent的動作。

    為了使它更實際一點,讓我們設想這樣一種情況:我們希望給Agent發送的動作流進行干預,並以10%的概率將當前動作替換為隨機動作。這看起來似乎是不明智的,但是這個簡單的技巧是解決我在第一章簡要提到的「exploration/exploitation問題」的最實用和強大的方法之一,通過隨機的行為,我們使我們的代理人探索環境,並不時偏離其策略的既定軌道。使用ActionWrapper類(完整的例子,Chapter02/03_random_action_wrapper.py),這是一件簡單的事情。

    import gym

    import random

    class RandomActionWrapper(gym.ActionWrapper):

    def __init__(self, env, epsilon=0.1):

    super(RandomActionWrapper, self).__init__(env)

    self.epsilon = epsilon

    在這裡,我們通過調用父類的__init__方法和保存epsilon(一個隨機動作的概率)來初始化包裝器:

    def action(self, action):

    if random.random() < self.epsilon:

    print("Random!")

    return self.env.action_space.sample()

    return action

    我們需要從父類中重寫這個方法來調整代理的操作。每次我們擲骰子,如果概率小於預定值,我們就從動作空間中取樣一個隨機動作並將它返回,而不是代理髮送給我們的動作。注意,使用action_space和包裝器抽象,我們能夠編寫抽象代碼,它可與Gym的任何環境一起使用。在代碼中,我們每次替換原始動作都會列印這個標記,這僅僅是本例用來檢驗包裝器是否正常工作的,在生產代碼中,這可以不寫:

    if __name__ == "__main__":

    env = RandomActionWrapper(gym.make("CartPole-v0"))

    現在是應用包裝器的時候了,我們將創建一個普通的CartPole環境,並將其傳遞給包裝器構造函數,從這裡開始,我們使用包裝器作為一個普通的Env實例,而不是原始的CartPole。當包裝器類繼承Env類並公開相同的介面時,我們可以將包裝器嵌套在任何我們想要使用這個環境的組合中,這是一個強大的、優雅的、通用的解決方案:

    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)

    這裡幾乎是相同的代碼,只是每次我們發出相同的動作:0,我們的Agent很遲鈍,總是做同樣的事情。通過運行代碼,您應該看到包裝器確實在工作:

    rl_book_samples/Chapter02$ python 03_random_actionwrapper.py

    WARN: gym.spaces.Box autodetected dtype as <class numpy.float32>. Please provide explicit dtype.

    Random!

    Random!

    Random!

    Random!

    Reward got: 12.00

    如果願意,您可以在包裝器的創建上使用epsilon參數,並驗證隨機性提高了Agent的平均表現。我們應該繼續,看看另一個隱藏在Gym中的有趣的寶石:Monitor類。

    Monitor類監控

    另一個需要注意的類是Monitor,它像Wrapper一樣執行,可以Agent性能的數據記錄到一個文件中,可以通過視頻記錄Agent訓練過程,當然這個是可選選項,不是強制的。前一段時間,可以上傳監控類的結果記錄到gym.openai.com網站,比較一下你Agent的訓練結果和其他人的比較表現如何(見你以下截圖),但是,不幸的是,在2017年8月底,OpenAI決定關閉這個上傳功能和凍結了所有的結果。有些活動組織計劃實現對原始網站這個功能的替代,但到目前為止還沒有成功的產品出來。我希望這種情況能儘快得到解決,但在寫這篇文章的時候,你不可能將自己的結果與別人的結果進行比較。為了讓你了解一下Gym的網站Agent排行榜界面是什麼樣子的,我們給出了CartPole環境排行榜:

    圖5:帶有CartPole提交的OpenAI體育館web界面

    web界面中的每個提交都有訓練過程的詳細信息。例如,下面是作者對《毀滅戰士》迷你遊戲的解決方案:

    圖6:末日防禦線環境的提交動態

    儘管不能上傳,Monitor仍然很有用,您可以查看您的Agent在環境中的活動信息。因此,我們將Monitor添加到我們的隨機CartPole代理中,這是惟一的區別(整個代碼在Chapter02/04_cartpole_random_monitor.py中):

    if __name__ == "__main__":

    env = gym.make("CartPole-v0")

    env = gym.wrappers.Monitor(env, "recording")

    我們傳遞給Monitor的第二個參數是它將結果寫入到的目錄的名稱。這個目錄不應該存在,否則您的程序將失敗,只有一個例外(為了克服這個問題,您可以刪除現有目錄,或者將force=True參數傳遞給Monitor類的構造函數)。

    Monitor類需要FFmpeg,FFmpeg用於將捕獲的觀察結果轉換為輸出視頻文件,FFmpeg是必須的,否則Monitor類運行會出錯。安裝FFmpeg最簡單的方法是使用系統的包管理器,不同系統包管理器不一樣,當然安裝方式亦不同,不同系統的安裝方式請Google或者訪問FFmpeg官網。

    要運行示例,需要滿足以下三個先決條件之一:

    • 代碼應該在帶有OpenGL擴展名(GLX)的X11會話中運行
    • 代碼應該在Xvfb虛擬顯示中啟動
    • 可以在ssh連接中使用X11轉發

    造成這種情況的原因是視頻錄製,這是通過對環境繪製的窗口進行截屏來完成的。一些環境使用OpenGL繪製它的圖片,所以需要使用OpenGL的圖形模式。對於雲中的虛擬機來說,這可能是一個問題,因為它的物理上沒有運行監視器和圖形界面。為了克服這一點,有一種特殊的「虛擬」圖形化顯示,稱為Xvfb (X11虛擬幀緩衝區),它基本上啟動伺服器上的虛擬圖形化顯示,並強製程序在其中繪圖。這足以讓Monitor愉快地創建所需的視頻。

    要在Xvbf環境中啟動程序,您需要在您的計算機上安裝它(通常需要安裝xvfb包),並運行特殊的腳本xvfb-run:

    $ xvfb-run -s "-screen 0 640x480x24" python 04_cartpole_random_monitor.py

    [2017-09-22 12:22:23,446] Making new env: CartPole-v0

    [2017-09-22 12:22:23,451] Creating monitor directory recording

    [2017-09-22 12:22:23,570] Starting new video recorder writing to recording/openaigym.video.0.31179.video000000.mp4

    Episode done in 14 steps, total reward 14.00

    [2017-09-22 12:22:26,290] Finished writing results. You can upload them to the scoreboard via gym.upload(recording)

    正如日誌中看到的,視頻已經成功捕獲,因此您可以通過播放它來查看代理的活動情況。

    記錄Agent的行為的另一種方法是使用ssh X11轉發,它使用ssh隧道X11客戶端 (想要顯示某些圖形信息的Python代碼)和X11伺服器(知道如何顯示這些信息的軟體,有權訪問你的物理顯示裝置) 之間的通信能力。在X11架構中,客戶機和伺服器是分開的,可以在不同的機器上工作。要使用這種方法,您需要以下方法:

    1. 在本地計算機上運行的X11伺服器。Linux附帶X11 server作為標準組件(所有桌面環境都使用X11)。在Windows機器上,您可以設置第三方X11實現,比如開源的VcXsrv(可在sourceforge.net/project中獲得)。
    2. 通過ssh登錄到遠程計算機的能力,傳遞-X命令行選項:ssh -X servername。這允許X11隧道化,並允許在此會話中啟動的所有進程使用本地顯示來輸出圖形。

    然後,您可以啟動一個使用Monitor類的程序,它將顯示代理的操作,將圖像捕獲到視頻文件中。

    總結

    我祝賀你!您已經開始學習RL的實用方面了!在這一章中,我們安裝了OpenAI Gym,可以使用大量的環境,研究了它的基本API並創建了一個隨機行為的代理。您還了解了如何以模塊化的方式擴展現有環境的功能,並熟悉了使用Monitor包裝器記錄代理活動的方法。

    在下一章中,我們將使用PyTorch快速回顧DL, PyTorch是DL研究人員最喜歡的庫。請繼續關注。


    推薦閱讀:

    《學習之道》讀後筆記
    #讀書 上癮:讓用戶養成習慣的四大產品邏輯 之一
    林徽因與徐志摩
    讀書筆記(23)

    TAG:筆記 | 做筆記 | 讀書筆記 |