不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari

機器之心報道

參與:Racoon X

還是熟悉的樹莓派!訓練 RL agent 打 Atari 不再需要 GPU 集群,這個項目讓你在邊緣設備上也能進行實時訓練。

不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari

自從 DeepMind 團隊提出 DQN,在 Atari 遊戲中表現出超人技巧,已經過去很長一段時間了。在此期間持續有新的方法被提出,不斷創造出 Deep RL 領域新 SOTA。然而,目前不論是同策略或異策略強化學習方法(此處僅比較無模型 RL),仍然需要強大的算力予以支撐。即便研究者已將 Atari 遊戲的分辨率降低到 84x84,一般情況下仍然需要使用 GPU 進行策略的訓練。

如今,來自 Ogma Intelligent Systems Corp. 的研究人員突破了這一限制。他們在稀疏預測性階層機制(Sparse Predictive Hierarchies)的基礎上,提出一種不需要反傳機制的策略搜索框架,使得實時在樹莓派上訓練 Atari 遊戲的控制策略成為可能。下圖展示了使用該算法在樹莓派上進行實時訓練的情形。

可以看到,agent 學會了如何正確調整滑塊位置來接住小球,並發動進攻的策略。值得注意的是,觀測輸入為每一時刻產生的圖片。

不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari

也就是說,該算法做到了在樹莓派這樣算力較小的邊緣設備上,實時學習從像素到策略的映射關係。

研究者開源了他們的 SPH 機制實現代碼,並提供了相應 Python API。這是一個結合了動態系統應用數學、計算神經科學以及機器學習的擴展庫。他們的方法曾經還被 MIT 科技評論列為「Best of the Physics arXiv」。

項目地址:

https://github.com/ogmacorp/OgmaNeo2

OgmaNeo2

研究者所提出的 SPH 機制不僅在 Pong 中表現良好,在連續策略領域也有不錯的表現。下圖分別是使用該算法在 OpenAI gym 中 Lunar Lander 環境與 PyBullet 中四足機器人環境的訓練結果。

不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari

不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari

在 Lunar Lander 環境中,訓練 1000 代之後,每個 episode 下 agent 取得了平均 100 分左右的 reward。如果訓練時間更長(3000 代以上),agent 的平均 reward 甚至能達到 200。在 PyBullet 的 Minitaur 環境中,agent 的訓練目標是在其自身能量限制條件下,跑得越快越好。從圖中可以看到,經過一段時間訓練,這個四足機器人學會了保持身體平衡與快速奔跑(雖然它的步態看起來不是那麼地自然)。看起來效果還是很棒的,機器之心也上手測試了一番。

算法框架

OgmaNeo2 用來學習 Pong 控制策略的整體框架如下圖所示。圖像觀測值通過圖像編碼器輸入兩層 exponential memory 結構中,計算結果輸出到之後的 RL 層產生相應動作策略。

不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari

項目實測

在安裝 PyOgmaNeo2 之前,我們需要先編譯安裝其對應的 C++庫。將 OgmaNeo2 克隆到本地:

!git clone https://github.com/ogmacorp/OgmaNeo2.git

之後將工作目錄切換到 OgmaNeo2 下,並在其中創建一個名為 build 的文件夾,用於存放編譯過程產生的文件。

import os

os.chdir('OgmaNeo2')

!mkdir build

os.chdir('build')

接下來我們對 OgmaNeo2 進行編譯。這裡值得注意的是,我們需要將-DBUILD_SHARED_LIBS=ON 命令傳入 cmake 中,這樣我們才能在之後的 PyOgmaNeo2 擴展庫裡使用它。

!cmake .. -DBUILD_SHARED_LIBS=ON

!make

!make install

當 OgmaNeo2 安裝成功後,安裝 SWIG v3 及 OgmaNeo2 的相應 Python 擴展庫:

!apt-get install swig3.0

os.chdir('/content')

!git clone https://github.com/ogmacorp/PyOgmaNeo2

os.chdir('PyOgmaNeo2')

!python3 setup.py install --user

接下來輸入 import pyogmaneo,如果沒有錯誤提示就說明已經成功安裝了 PyOgmaNeo2。

我們先用一個官方提供的時間序列迴歸來測試一下,在 notebook 中輸入:

import numpy as np

import pyogmaneo

import matplotlib.pyplot as plt

# Set the number of threads

pyogmaneo.ComputeSystem.setNumThreads(4)

# Create the compute system

cs = pyogmaneo.ComputeSystem()

# This defines the resolution of the input encoding - we are using a simple single column that represents a bounded scalar through a one-hot encoding. This value is the number of "bins"

inputColumnSize = 64

# The bounds of the scalar we are encoding (low, high)

bounds = (-1.0, 1.0)

# Define layer descriptors: Parameters of each layer upon creation

lds = []

for i in range(5): # Layers with exponential memory

ld = pyogmaneo.LayerDesc()

# Set the hidden (encoder) layer size: width x height x columnSize

ld.hiddenSize = pyogmaneo.Int3(4, 4, 16)

ld.ffRadius = 2 # Sparse coder radius onto visible layers

ld.pRadius = 2 # Predictor radius onto sparse coder hidden layer (and feed back)

ld.ticksPerUpdate = 2 # How many ticks before a layer updates (compared to previous layer) - clock speed for exponential memory

ld.temporalHorizon = 2 # Memory horizon of the layer. Must be greater or equal to ticksPerUpdate, usually equal (minimum required)

lds.append(ld)

# Create the hierarchy: Provided with input layer sizes (a single column in this case), and input types (a single predicted layer)

h = pyogmaneo.Hierarchy(cs, [ pyogmaneo.Int3(1, 1, inputColumnSize) ], [ pyogmaneo.inputTypePrediction ], lds)

# Present the wave sequence for some timesteps

iters = 2000

for t in range(iters):

# The value to encode into the input column

valueToEncode = np.sin(t * 0.02 * 2.0 * np.pi) * np.sin(t * 0.035 * 2.0 * np.pi + 0.45) # Some wavy line

valueToEncodeBinned = int((valueToEncode - bounds[0]) / (bounds[1] - bounds[0]) * (inputColumnSize - 1) + 0.5)

# Step the hierarchy given the inputs (just one here)

h.step(cs, [ [ valueToEncodeBinned ] ], True) # True for enabling learning

# Print progress

if t % 100 == 0:

print(t)

# Recall the sequence

ts = [] # Time step

vs = [] # Predicted value

trgs = [] # True value

for t2 in range(300):

t = t2 + iters # Continue where previous sequence left off

# New, continued value for comparison to what the hierarchy predicts

valueToEncode = np.sin(t * 0.02 * 2.0 * np.pi) * np.sin(t * 0.035 * 2.0 * np.pi + 0.45) # Some wavy line

# Bin the value into the column and write into the input buffer. We are simply rounding to the nearest integer location to "bin" the scalar into the column

valueToEncodeBinned = int((valueToEncode - bounds[0]) / (bounds[1] - bounds[0]) * (inputColumnSize - 1) + 0.5)

# Run off of own predictions with learning disabled

h.step(cs, [ [ valueToEncodeBinned ] ], False) # Learning disabled

predIndex = h.getPredictionCs(0)[0] # First (only in this case) input layer prediction

# Decode value (de-bin)

value = predIndex / float(inputColumnSize - 1) * (bounds[1] - bounds[0]) + bounds[0]

# Append to plot data

ts.append(t2)

vs.append(value)

trgs.append(valueToEncode)

# Show predicted value

print(value)

# Show plot

plt.plot(ts, vs, ts, trgs)

可得到如下結果。圖中橙色曲線為真實值,藍色曲線為預測值。可以看到,該方法以極小的誤差擬合了真實曲線。

不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari

最後是該項目在 CartPole 任務中的表現。運行!python3 ./examples/CartPole.py,得到如下訓練結果。可以看到,其僅用 150 個 episode 左右即解決了 CartPole 任務。

不需要藉助GPU的力量,用樹莓派也能實時訓練agent玩Atari


分享到:


相關文章: