2018年4月19日 星期四

訓練 Gym Retro 玩 Sonic The Hedgehog - Episode 2


要教懂電腦玩「超音鼠」,首先需要訓練數據,亦即是甚麼時候要跑、甚麼時候要跳、甚麼時候要怎麼走...。既然玩遊戲是用到「強化學習 Reinforcement Learning」,即是透過獲得獎勵而加強神經網絡間的權重,從而達到智能效果;那其中一個方法是讓電腦隨機亂跑亂跳。這個方法很耗時,但不用盡神。由它玩過一萬幾千局,總會得到一些精彩的舉動。

不過,很多時得到的結果是不停左右移動,變成原地踏步,怪沒出了多少;寶物又沒得到多少。於是我把右鍵長期按下。這對於不斷向右跑的關卡來說是有效,但會轉向的就會卡住,不得不想其他方法。我在想,要是由人類在適當時候介入一下,應該能解決問題;於是我把決定按鍵操作的部份加入手動處理:
def getAction():
    up = 0
    down = 0
    left = 0
    right = 0
    a = 0
    b = 0
    
    up = (random.getrandbits(1))
    down = (random.getrandbits(1))
    left = (random.getrandbits(1))
    right = (random.getrandbits(1))
    
    if (random.randrange(0, 4) == 3):
        a = (random.getrandbits(1))
    if (random.randrange(0, 4) == 3):
        b = (random.getrandbits(1))
    
    try:
        key_u = keyboard.is_pressed('u')
        key_j = keyboard.is_pressed('j')
        key_h = keyboard.is_pressed('h')
        key_k = keyboard.is_pressed('k')
        key_q = keyboard.is_pressed('q')
        key_space = keyboard.is_pressed(' ')
        
        if key_u or key_j or key_h or key_k or key_q or key_space:
            up = int(key_u)
            down = int(key_j)
            left = int(key_h)
            right = int(key_k)
            a = int(key_q)
            b = int(key_space)
    
    except:
        print("Keyboard error...")
    
    action = [a, b, 0, 0, up, down, left, right, 0, 0, 0, 0]
    return numpy.asarray(action)

2018年4月14日 星期六

訓練 Gym Retro 玩 Sonic The Hedgehog - Episode 1


要訓練 Agent 玩「超音鼠」最大的難度是如何穿越 360 度的圈圈...

2018年4月12日 星期四

Numpy 小技巧

為了提升效率及節省空間,在機器學習的世界,很多時會把圖像轉換成灰階,Numpy 有一個簡單的轉換可以達成:
import numpy
numpy.mean(image, axis=2).astype(numpy.uint8)
雖然這個技巧跟正統的灰階算法有點出入,但總算是夠快夠接近的處理方法。而另一個技功是縮小 50%,也有一句指令可以輕易達成:
image[::2, ::2]

2018年4月11日 星期三

儲存及載入機器學習模型

這幾天努力地學習「臉部識別」技術,嘗試過訓練出可用的模型;下一步是找出儲存及讀取訓練好的模型的方法。畢竟沒可能無次使用前都要用同一筆素材來進行訓練。找到用 Joblib 來達成。我用了 2493 張相片,每張 64x64 灰階 PNG;當中包含 6 位同事的相片,輸入層有 4096 節點,隱藏層有 1024 節點,輸出層有 6 節點,儲存後的 PCA 為 230KB,MLP 為 399 KB。

儲存模型的方法:
from sklearn.externals import joblib
joblib.dump(pca, "model.pca")
joblib.dump(mlp, "model.mlp")
載入模型的方法:
from sklearn.externals import joblib
loadedPCA = joblib.load("model.pca")
loadedMLP = joblib.load("model.mlp")

testset, fileset = loadTestImageInDirectory("./testset_64x64/")
testset_pca = loadedPCA.transform(testset)
predict = loadedMLP.predict(testset_pca)

2018年4月10日 星期二

儲存 Gym Retro 的遊戲畫面


要在 Gym Retro 中輸出遊戲畫面,可以利用 step() 傳回的 Observation:
observation, reward, done, info = environment.step(action)

那是畫面 RGB 像素數據。用 reshape(1, -1) 把它由三維轉換成一維後,執行 astype('int8') 指令確保輸出為整數而不是浮點數,再以 tofile() 指令儲存:
pixelArray = observation.reshape(1, -1)
pixelArray.astype('int8').tofile('screen.raw')


這時的 screen.raw 是 RAW 格式,不是每台系統也能讀取。用 Photoshop 把它載入成 320 像素闊、224 像素高、3 原色頻道就可以,之後隨自己喜歡轉換成其他圖像格式。

還有另一個更簡單的方法,直接輸出成 JPG 格式或 PNG 格式:
import scipy.misc
scipy.misc.imsave('screen.jpg', observation)
scipy.misc.imsave('screen.png', observation)

2018年4月9日 星期一

修改 Gym Retro 中超音鼠遊戲的 scenario.json 設定

Gym Retro 針對超音鼠的 scenario.json 設定是,當分數有變化時會得到 10 分獎勵。在遊戲中,超音鼠取得金環時,分數不會有變化,造成不會有任何獎勵;同時當扣掉生命時也沒有任何懲罰。我認為這樣是不足夠。因此修改了一下 scenario.json:
{
    "done": {
        "variables": {
            "lives": {
                "op": "zero"
            }
        }
    },
    "reward": {
        "variables": {
            "score": {
                "reward": 10.0
            },
            "rings": {
                "reward": 1.0,
                "penalty": 1.0
            },
            "lives": {
                "penalty": 10.0
            }
        }
    }
}

再次執行模擬時,當扣掉生命便會減 10 分,得到金環是會加 1 分:

2018年4月8日 星期日

顯示模型中的 Eigen Vectors 影像


今年有機會開辦關於機器學習的基礎課程,當中會教授人像識別相關內容,教導學生編寫 Python 程序去訓練模型,並能辨認出自己的樣貌。在這之前,我需要惡補一下自己的技術。在網上這篇不錯的教學能滿足以上的需要。

教學中詳細講解了數據集的由來、如何減低數據負載、選擇 Princpal Component Analysis 的好處、何時避免過度訓練...等。最後也提示了能顯示模型中的 Eigen Vectors 影像,亦即是模型內心。可是卻沒有示範這個部份的代碼。我只好自行編寫:
##  Get eigen vectors
plt.figure()
eigenVectors = pca.components_
for i in range(16):
    plt.subplot(4, 4, i + 1)
    plt.imshow(eigenVectors[i].reshape((h, w)), cmap=plt.cm.gray)
    plt.xticks()
    plt.yticks()

plt.show()
參考:http://scikit-learn.org/stable/auto_examples/applications/plot_face_recognition.html

2018年4月7日 星期六

把超音鼠遊戲導入到 Gym Retro


安裝好 Gym Retro,下一步便是加入遊戲。在得到遊戲後,在 ROM 檔案的目錄中輸入指令「sudo python3 -m retro.import .」把遊戲加入到 Gym Retro 卻不成功。追查加入的程式,它會搜尋 /usr/local/lib/python3.6/site-packages/retro/data/ 內各個遊戲設定的 SHA1 值,如果載入的 ROM 的 SHA1 相同才會進入導入。Gym Retro 支援「SonicTheHedgehog-Genesis」的 SHA1 值是「69e102855d4389c3fd1a8f3dc7d193f8eee5fe5b」,跟手上那個 ROM 的 SHA1 值不同,因此沒有導入。

既然知道原理,只要把 Gym Retro 內關於「SonicTheHedgehog-Genesis」的 SHA1 值修改成手上的 SHA1 值,便能順利導入。於是我參考了 /usr/local/lib/python3.6/site-packages/retro/data.py 編寫了以下 Python 程序:
##--------------------------------------------------------------------
##  Get SHA1 Value of ROM File for Gym Retro
##--------------------------------------------------------------------
##  Platform: macOS High Sierra + Python 3.6
##  Written by Pacess HO
##  Copyright Pacess Studio, 2018.  All rights reserved.
##--------------------------------------------------------------------

import hashlib

##--------------------------------------------------------------------
def parse_smd(header, body):
 import numpy as np
 try:
  if body[0x80] != b'E' or body[0x81] != b'A':
   return header + body
  body2 = b''
  for i in range(len(body) / 0x4000):
   block = body[i * 0x4000:(i + 1) * 0x4000]
   if not block:
    break
   nb = np.fromstring(block, dtype=np.uint8)
   nb = np.flipud(nb.reshape(2, 0x2000))
   nb = nb.flatten(order='F')
   body2 += nb.tostring()
 except IndexError:
  return header + body
 return body2

##--------------------------------------------------------------------
##  Read ROM file
romFile = 'SonicTheHedgehog-Genesis.md'
with open(romFile, 'rb') as r:
 header = r.read(512)
 body = r.read()

##--------------------------------------------------------------------
##  Calculate SHA1
##  * Sonic 1 = c9dceee00a3d24c5460bdd7cd8c45e2f6d02c9fb
##  * Sonic 2 = 14dd06fc3aa19a59a818ea1f6de150c9061b14d4
body = parse_smd(header, body)
header = b''
hashlib.sha1(body).hexdigest()

得到「c9dceee00a3d24c5460bdd7cd8c45e2f6d02c9fb」這個 SHA1 值。把它更新到 /usr/local/lib/python3.6/site-packages/retro/data/SonicTheHedgehog-Genesis/rom.sha 再重新以「sudo python3 -m retro.import .」指令導入,成功了!其實簡簡單單把 ROM 檔案改名做 rom.md 然後放到遊戲目錄都可能會成功... ^_^

要在 Python3 中執行把超音鼠遊戲,輸入以下程序:
import retro

env = retro.make(game='SonicTheHedgehog-Genesis', state='GreenHillZone.Act1')
env.reset()
_obs, _rew, done, _info = env.step(env.action_space.sample())
env.render()

2018年4月6日 星期五

在 macOS High Sierra 成功安裝 Gym Retro


OpenAI 最近推出了一個比賽,無獨有偶是利用遊戲模擬器去訓練機器學習算法。在我的 High Sierra 安裝 Binary 時卻說不支援;那只好由 Source 去進行安裝,等了 50 分鐘也沒反應。後來不依官方的「pip install -e .」安裝,改為利用「pip install -v -e .」才發現原來發生錯誤。這是由於使用了 Python 2.7 而導致。不過,即使改為「sudo pip3.6 install -v -e .」還是不行。最後嘗試 Binary 版本,但把指令改為「sudo pip3.6 install https://storage.googleapis.com/gym-retro/builds/gym_retro-0.5.3-cp36-cp36m-macosx_10_7_x86_64.whl」就成功了!


有一點要留意,如果在 Gym Retro 源文件目錄下執行 import retro 會出現「No module named 'retro._retro'」錯誤,只要在其他目錄就沒有問題。

2018年4月5日 星期四

在 macOS 安裝 LUA 5.1


為了學習 Machine Learning,需要安裝模擬器;而這個過程需要用到 LUA 5.1。在 macOS 上的安裝方法是「brew install pkg-config lua@5.1」。