【深度強化學習】OpenAI Gym的使用

前言

重讀《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實現 的 深度學習。

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