「世界模型」實現,一步步讓機器掌握賽車和躲避火球的技能

機器之心發表於2018-05-07

前段時間,由谷歌大腦研究科學家 David Ha 與瑞士 AI 實驗室 IDSIA 負責人 Jürgen Schmidhuber(他也是 LSTM 的提出者)共同提出的「世界模型」讓人工智慧在「夢境」中訓練的論文吸引了人們的熱烈討論。本文將帶你一步步實現論文中研究的賽車和躲避火球智慧體。

簡言之,該論文被稱為傑作的原因有三:

1. 它結合了多種深度/強化學習技術以得到驚人的結果——已知第一個解決當下流行的「賽車」強化學習環境的智慧體;

2. 模型編寫方式易於理解,所以對任何對尖端 AI 技術感興趣的人而言,這都是很好的學習資源;

3. 你可以自己寫該解決方案的程式碼。

這篇文章是一份分步指南。

本文涵蓋的內容有模型的技術細節,以及該如何得到一個可以在自己的機器上執行的版本。

正如我在 AlphaZero(https://medium.com/applied-data-science/how-to-build-your-own-alphazero-ai-using-python-and-keras-7f664945c188)一文中所說的,我與文章作者沒有任何關係,我只是想分享一些關於他們傑出工作的見解。

第 1 步:問題

我們將建立一個強化學習演算法(一個「智慧體」),該演算法可以很好地在 2D 的賽道上驅動車輛。可以通過 OpenAI Gym(https://gym.openai.com/)得到這個(賽車)環境。

在每一個時間步(time step)中,該演算法都會被饋送一個觀察(一張畫素為 64 * 64 的車輛和即時環境的彩色影象),還需要返回接下來採取的一系列行為引數——也就是轉向的方向(-1 到 1)、加速度(0 到 1)以及剎車(0 到 1)。

然後將這一行為傳遞到環境中,返回下一個觀察,再開始下一次迴圈。

對要訪問的 N 個追蹤塊而言,每一個智慧體能得 1000/N 分,而每經過一個時間步會減去 0.1 分。例如,如果一個智慧體要通過 732 幀才能完成追蹤,那麼得分就是 1000–0.1*732 = 926.8 分。

「世界模型」實現,一步步讓機器掌握賽車和躲避火球的技能

這是一個智慧體的例子:在前 200 時間步中選擇行為 [0,1,0],然後其他隨機……顯然這不是很好的行駛策略。

該專案的目標是要訓練智慧體,使其理解可以利用周圍環境的資訊在下一步採取最佳行動。

第 2 步:解決辦法

World Models 的作者寫了一個關於其方法的線上的互動解釋(https://worldmodels.github.io/),所以在此不再贅述同樣的細節,而是要聚焦於這些片段該如何組裝在一起的高層次問題上,與真實的駕駛過程進行類比,來直觀說明為什麼這樣的解決方案有意義。

解決方案由三部分組成,會對這三部分分別進行訓練:

變分自編碼器(VAE)

當你在開車過程中做決策時,你不會對視野中的每一個「畫素」進行分析——相反,你的大腦會將這些視覺資訊濃縮成少量的「潛在」實體,例如道路的平直度、即將到來的彎道以及你相對於道路的位置,你會通過這些資訊來決定下一個行為。

這也是訓練後的 VAE 要做的事——將 64*64*3(RPG)的輸入影象濃縮為服從高斯分佈的 32 維潛在向量(z)。

這是很有用的,因為智慧體可以用更小的環境表徵工作,從而使學習過程更加高效。

具有混合密度網路輸出層的迴圈神經網路(MDN-RNN)

「世界模型」實現,一步步讓機器掌握賽車和躲避火球的技能

如果在決策時沒有 MDN-RNN 元件,你的駕駛過程看起來可能像這樣。

當你駕駛時,視野中出現的每一個觀察都不會讓你特別驚訝。你知道如果當前的觀察結果說明路前方有左轉彎,你就會左轉,在你預期中的下一次觀察會顯示你仍沿著路在前進。

這種前瞻性思維就是 RNN 要起到的作用——具體地說就是一個有著 256 個隱藏單元的 LSTM。h 表示隱藏狀態的向量。

和 VAE 類似,RNN 試圖捕獲環境中車輛當前狀態的潛在理解,但這一次是要以之前的「z」和行為為基礎,預測下一個「z」可能是什麼樣的。

事實上,MDN 輸出層允許從幾個高斯分佈中的任意一個得到下一個「z」。

「世界模型」實現,一步步讓機器掌握賽車和躲避火球的技能

生成手寫體的 MDN

在這篇同一作者寫的文章中用同樣的技術生成手寫體(http://blog.otoro.net/2015/12/28/recurrent-net-dreams-up-fake-chinese-characters-in-vector-format-with-tensorflow/),說明筆的下一點可以落在不同的紅色區域的任意一點。

相似的是,在 World Models 這篇文章中,可以從五個高斯分佈的任意一箇中得出下一個觀察的潛在狀態。

控制器

到目前為止,我們還沒有提到任何有關行為選擇的問題,該任務由控制器執行。

簡單地說,控制器就是一個密集連線的神經網路,這個網路的輸入是級聯的 z(從 VAE 得到的潛在狀態——長度為 32)和 h(RNN 的隱藏狀態——長度是 256)。這三個輸出神經元對應三個行為,且被縮放至適合的範圍。

一段對白

為了理解這三個元件所扮演的不同角色以及它們是如何運作的,我們可以想象一段在它們中間發生的對白:

「世界模型」實現,一步步讓機器掌握賽車和躲避火球的技能

World Model 架構的圖示(來源:https://arxiv.org/pdf/1803.10122.pdf)

VAE(看著最新的 64*64*3 的觀察):汽車前進方向(z)的道路像是一條平直、有輕微左轉彎的道路。

RNN:根據描述(z)和控制器選擇在最後的時間步加速(行為)的事實,我會更新我的隱藏狀態(h),這樣下一個觀察才能預測為筆直的道路,但是在視野中有輕微的左轉。

控制器:基於來自 VAE 的描述(z)和來自 RNN 的當前的隱藏狀態(h),我的神經網路下一行為的輸出會是 [0.34, 0.8, 0]。

然後將這一行為傳送至環境中去,這會返回一個更新的觀察,然後開始下一迴圈。

現在我們要看一下要如何設定一個讓你訓練自己賽車智慧體的環境。

接下來我們來寫程式碼吧!

第 3 步:設定你自己的環境

如果你使用的是高配置筆記本,你可以在本地執行,但我建議使用 Google 雲端計算https://cloud.google.com/compute/),這允許你在短時間內使用強大的計算機。

以下程式碼已經在 Linux(Ubuntu 16.04)進行過測試了——如果你在 Mac 或 Windows 上執行只需改變相關安裝包的命令即可。

1. 複製 github 專案(https://github.com/AppliedDataSciencePartners/WorldModels

下面的命令列可以幫你導航到專案位置並進入該專案:

git clone https://github.com/AppliedDataSciencePartners/WorldModels.git

該專案是通過 World Models 文章第一作者 David Ha 開發的很有用的 estool 庫(https://github.com/hardmaru/estool)改寫而成的。

在訓練神經網路的過程中,使用了帶有 TensorFlow 後端的 Keras,但是在原文中,作者使用的是原始的 TensorFlow

2. 設定一個虛擬環境

建立一個 Python3 虛擬環境(我用的是 virutalenv 和 virtualenvwrapper)。

sudo apt-get install python-pip
sudo pip install virtualenv
sudo pip install virtualenvwrapper
export WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
mkvirtualenv --python=/usr/bin/python3 worldmodels

3. 安裝依賴包

sudo apt-get install cmake swig python3-dev zlib1g-dev python-opengl mpich xvfb xserver-xephyr vnc4server

4. 安裝要求.txt

cd WorldModels
pip install -r requirements.txt

與賽車例子中要求的相比,這裡有更多依賴包,建議你把每一個包都裝好,以備在 Open AI Gym 中進行其他測試,這些測試可能需要額外的依賴包。

第 4 步:生成隨機事件

對賽車環境而言,VAE 和 RNN 都可以在隨機事件資料上訓練——也就是在每一個時間步隨機採取的行為產生的觀察資料。事實上,我們用了偽隨機的行為,這樣可以迫使車輛在初始時加速,使其脫離起跑線。

因為 VAE 和 RNN 是獨立於做出決策行為的控制器的,我們需要保證遇到不同觀察後選擇不同行為,並將其儲存為訓練資料。

為了產生隨機事件,執行下面的命令列:

python 01_generate_data.py car_racing --total_episodes 2000 --start_batch 0 --time_steps 300

如果你在沒有顯示的伺服器上操作,執行下面的命令列:

xvfb-run -a -s "-screen 0 1400x900x24" python 01_generate_data.py car_racing --total_episodes 2000 --start_batch 0 --time_steps 300

這會產生 2000 個事件(儲存在 10 個批次中),批次從 0 開始。每一個事件最長為 300 個時間步。

  • 兩部分檔案存在 ./data 中(* 是批次號)

  • obs_data_*.npy(將 64*64*3 的圖存為 numpy 陣列)

  • action_data_*.npy(儲存三維行為)

第 5 步:訓練 VAE

訓練 VAE 只需要 obs_data_*.npy 檔案。確保已經完成了第 4 步,這樣這些檔案才會在./data 資料夾中。

執行:

python 02_train_vae.py --start_batch 0 --max_batch 9 --new_model

這會在 0~9 的每一個批次上都訓練一個新的 VAE 。

模型權重會存在 ./vae/weights.h5 中,--new_model 標記告訴指令碼要從頭訓練模型。

如果該資料夾中已經存在 weights.h5,並且沒有 --new_model 標記,指令碼將從這個檔案中載入權重再訓練已經存在的模型。這樣你就可以按批次反覆訓練 VAE。

VAE 架構設定在 ./vae/arch.py 檔案中。

第 6 步:生成 RNN 資料

既然我們已經訓練了 VAE,我們就可以用 VAE 產生 RNN 的訓練集。

RNN 需要編碼來自 VAE 的影象資料(z)和行為(a)作為輸入,將編碼下一時間步的 VAE 的影象資料作為輸出。

執行命令列生成資料:

python 03_generate_rnn_data.py --start_batch 0 --max_batch 9

批次 0 到批次 9,會用 obs_data_*.npy 和 obs_data_*.npy 檔案,並將這兩個檔案轉換為訓練 RNN 需要的正確格式。

  • 兩部分檔案將儲存在 ./data 中(* 是批次號碼)

  • rnn_input_*.npy(儲存了級聯向量 [z,a])

  • rnn_output_*.npy(儲存了下一時間步的向量 z)

第 7 步:訓練 RNN

訓練 RNN 只需要 rnn_input_*.npy 和 rnn_output_*.npy 檔案。確保已經完成了第 6 步,這樣才能在 ./data 資料夾中找到這些檔案。

執行命令列:

python 04_train_rnn.py --start_batch 0 --max_batch 9 --new_model

這可以在 0~9 每一個批次的資料上訓練新的 RNN。

模型權重將儲存在 ./rnn/weights.h5 中。——new_model 標記告訴指令碼要從頭訓練模型。

與 VAE 相似,如果資料夾中沒有 weights.h5,--new_model 標記也沒有特別說明的話,指令碼將從檔案中載入權重並繼續訓練現存模型。這樣,你就可以反覆訓練 RNN。

RNN 架構設定存為./rnn/arch.py 檔案。

第 8 步:訓練控制器

現在進行到有趣的部分了!

迄今為止,我們只用深度學習方法建立了可以將高維影象壓縮成低維潛在空間的 VAE,而 RNN 可以預測潛在空間是怎樣隨著時間變化而變化的。我們可以通過隨機事件資料建立一個同時適用於 VAE 和 RNN 的訓練集。

為了訓練控制器,我們可以使用強化學習,利用一種被稱為 CMA-ES(協方差矩陣適應—進化策略)的演算法。

由於輸入是維度為 288(=32+256)的向量,輸出是維度為 3 的向量,我們有 288*3+1=867 個引數要訓練。

CMA-ES 的工作原理是:先建立這 867 個引數的多個隨機初始化副本(即「群體」)。然後在環境中測試群體中的每一個元素並記錄其平均成績。事實上,這與自然選擇的原理一樣,允許產生最高得分的權重「複製」,並允許產生下一次迭代。

為了在你的機器上開始這一程式,取適當的引數執行下述命令:

python 05_train_controller.py car_racing --num_worker 16 --num_worker_trial 4 --num_episode 16 --max_length 1000 --eval_steps 25

若在沒有顯示的伺服器上操作,則執行下述命令:

xvfb-run -s "-screen 0 1400x900x24" python 05_train_controller.py car_racing --num_worker 16 --num_worker_trial 2 --num_episode 4 --max_length 1000 --eval_steps 25
  • --num_worker 16:將該引數設定為不超過最大可用核數目

  • --num_work_trial 2 :每個核中待測群體中元素的數目(num_worker * num_work_trial 得出每一次迭代後群體大小)

  • --num_episode 4:給群體中每個元素打分的 episode 的數量(這個成績是 episode 數量的平均獎勵)

  • --max_length 1000:一個 episode 中時間步的最大數量

  • --eval_steps 25:在 100 個 episode 中,最佳權重集合的評估之間的迭代數

  • --init_opt ./controller/car_racing.cma.4.32.es.pk 預設情況下,控制器將從頭開始執行指令碼,並將程式的當前狀態存在控制器目錄的 pickle 檔案中。該引數通過指向相關檔案,允許使用者從最後一個儲存點繼續訓練。

在每一次迭代之後,演算法的當前狀態和最佳權重集將會作為輸出儲存在 ./controller 資料夾中。

第 9 步:視覺化智慧體

在這篇文章的寫作期間,我成功訓練了該智慧體,並在訓練了 200 代之後得到了 833.13 的平均分。使用 Ubuntu 16.04、18 vCPU、67.5GB RAM 的機器在 Google Cloud 上訓練了本文提到的步驟和引數

論文的作者在訓練 2000 代之後得到了 906 的平均分,這是迄今為止該環境下最高的分數,但他們用的配置要更高一點(例如,對資料訓練了 10,000 個 episode,群體數量達到 64,64 核的機器,每次試驗有 16 個 episode 等)。

為了視覺化你的控制器的當前狀態,執行:

python model.py car_racing --filename ./controller/car_racing.cma.4.32.best.json --render_mode --record_video
  • --filename:您想附加到控制器的權重的 json 的路徑

  • --render_mode :在螢幕上渲染環境

  • --record_video:將 mp4 檔案輸出到 ./video 資料夾,每個 episode 展示一次

  • --final_mode:控制器執行 100 個 episode 測試輸出的平均成績

演示如下!

「世界模型」實現,一步步讓機器掌握賽車和躲避火球的技能

第 10 步:夢境學習

這已經很酷了——但這篇文章的下一個部分令人印象深刻,而我認為對人工智慧而言這意義重大。

在另一個環境,DoomTakeCove(https://github.com/ppaquette/gym-doom)中,這篇文章展現了驚人的結果。在這裡,目標是移動智慧體躲避火球,活得越久越好。

作者展示了智慧體如何不在真實環境的情況下,而在 VAE/RNN 的啟發下的夢境(虛擬環境)中學會玩遊戲。

額外需要的只有 RNN 也要被訓練得可以預測在下一個時間步死亡的可能性。這樣,VAE/RNN 聯合體可以作為環境獨立分裝,並用於訓練控制器。這是「世界模型」的核心概念。

我們可以將夢境學習歸納如下:

智慧體的初始訓練資料只不過是與真實環境的隨機互動。通過這種方式,智慧體建立了這個世界是如何「運作」的潛在理解——這個世界的自然分組、物理以及智慧體的行為會對這個世界產生怎樣的影響。

給定一個任務,智慧體會用這種潛在理解,在無需在真實世界對任務進行測試的情況下,建立最佳策略。因為它會將自己理解的環境的模型作為「場地」,試著解決問題。

這也可以簡單解釋為嬰兒學習走路。兩者間有驚人的相似之處,但或許比單純的類比要更加深刻,這一點使它成為真正迷人的研究領域。

原文連結:https://medium.com/applied-data-science/how-to-build-your-own-world-model-using-python-and-keras-64fb388ba459

相關文章