Arkanoid game
===
> F14071075 @2021
- 原始遊戲Github網址: https://github.com/LanKuDot/MLGame/tree/beta7.1.3?fbclid=IwAR1ieKsT-bvlnu-YddUREAct141ay61-lmRhBKLIO3631WZJQTHHlYz9KSc
環境:
---
- Ubuntu 20.04
- git
- python 3.8.5
- pygame
- sklearn
下載遊戲:
---
- 在terminal開啟想要儲存的資料夾後,使用git複製資料庫:
`git clone https://github.com/LanKuDot/MLGame.git`
- 手動遊玩:
- 進入到MLGame資料夾後,
`python3 MLGame.py -m -f 45 arkanoid NORMAL 3`
- 參數設定:
- `-m` 手動模式
- `-f 300` fps 300 模式
- `-r` 將遊戲記錄成`.pickle`檔案存放到 `MLGame/games/arkanoid/log`資料夾內
使用程式操控遊戲:
---
- 在 MLGame/games/arkanoid/ml 資料夾中新增 test01.py 檔案:
- 編輯`test01.py`
- 程式控制遊戲遊玩:
- `python3 MLGame.py -r -i test01.py -f 500 arkanoid NORMAL 3`
Rule_Base 控制遊戲進行:
---
- 背景:
- 可取得之遊戲 Info
- ball_x
- ball_y
- platform_x
- platform_y
- frame
- bricks (list of (x,y))
- hard_bricks
- 可操控之遊戲 Cmd
- SERVE_TO_LEFT
- SERVE_TO_RIGHT
- MOVE_LEFT
- MOVE_RIGHT
- NONE
- RESET
- 遊戲長寬:
- 左到右: 0 ~ 200
- 上到下方平台: 0 ~ 400
- ball: 5
- platform: 40
- **結論**:
- **ball_x: 0 ~ 195**
- **ball_y: 0 ~ 395**
- **platform 實際涵蓋: platform ~ platform+40**
- 遊戲分析:
- ball 上升過程不重要
- ball 下降時與牆壁碰撞所構成的 x 軸運動為 簡諧運動SHM
- 半週期為 195
- 完整週期為 390
- 
- 設定判斷區(下圖紅色區域)(只在球由此區域向下時,計算且更新 落點 `target_x` 之數值)
- 
> **上圖為此文提到之 108 個訓練集 所使用的Rule Base 1**
因為不知道其他關卡有磚塊位於下方,因此改進 Rule Base 2 為:
- 
> 但我在下方提到的所有訓練過程,都是使用 Rule Base 1 作為訓練集
- **結論**:當 ( ball正在下降 and 進入判斷區 ) 成立時:
- 計算出落點 `target_x`
- 
> `Eular` 為計算簡諧運動的部分
- 移動 platform 直到 platform 能夠接住 target_x
- 結果:完美無失誤
- Result:

> 感覺 Rule Base 的遊戲控制寫法,將會極大的影響到後來使用 ML 訓練模組時的結果
遊戲紀錄檔(.pickle)處理:
---
- 遊戲執行時加上`-r`就會自動產生遊戲紀錄檔,而記錄檔會在`MLGame/games/arkanoid/log`資料夾內
因為名稱都很亂,為了方便等等使用:
**批量編號改名(Linux):**
- 在terminal中,進入到`log`資料夾後,用指令
`i=1; for x in *; do mv $x $i.pickle; let i=i+1; done`
將所有檔案重新編號為 `1.pickle` `2.pickle` `3.pickle`...
- 改變之前:

- 改變之後:

上傳遊戲.pickle至google colab
---
- 將剛剛被重新編號過的`.pickle`檔案上傳至 Google Colab
- 
訓練Model
===
KNN
---
- **K-Means KNN 01:** (with knn.py)
https://drive.google.com/file/d/10WItiS_38domdN0dvPwsnQhFzadR39ms/view?usp=sharing
- Model 訓練架構:
- Input: `[目前ball_x, 目前ball_y, 目前platform_x]`
- Output: `[0, 1, 2]` # 分別代表(NONE, MOVE_LEFT, MOVE_RIGHT)
- 訓練資料前處理:
- Train_X: 所有的 `[目前ball_x, 目前ball_y, 目前platform_x]` 直接作為 input
- Train_Y: 讀取 data['ml']['command']並且轉換為 0, 1, 2
- 總共使用 108 個 Rule_Base 遊玩 NORMAL 3 產生的 .pickle 檔案訓練
- 除了 .pickle 檔案前處理,其他全部照抄助教的程式碼
- Result:

- **K-Means KNN 02:** (with knn.py)
https://drive.google.com/file/d/1XopG31HAfChY4Ejgxk6LZQBa5wg7vnZ_/view?usp=sharing
- 修改自 KNN 01
- 修改 param_grid 內容:
- `param_grid = {'n_neighbors':[1, 2, 3, 4, 5, 7, 10, 20, 30], 'weights':['uniform', 'distance'], 'leaf_size':[10, 20, 30], 'p':[1, 2]}`
- grid.best_params:
- leaf_size = 20
- n_neighbors = 7
- p = 1
- weights = uniform
- 貌似有進步,但仍然無法過關NORMAL 3,與 KNN 01 差距不大。
- Result:

- **K-Means KNN 03:** (with ml_knn.py)
https://drive.google.com/file/d/1gITO3U6Y3zkoZf34Ru1949aqDqVO-8EP/view?usp=sharing
- 修改自 KNN 02
- grid.best_params:
- leaf_size = 30
- n_neighbors = 30
- p = 2
- weights = distance
- 增加 feature
- ball_x - platform_x
- 395 - ball_y
- 有明顯進步,有成功破關1次
- Result:

- **K-Means KNN 04:** (with ml_knn_04.py)
https://drive.google.com/file/d/1l5AwLLB7IdEDNPAgxl_VEDlbhawLb8AG/view?usp=sharing
- 修改自 KNN 02
- grid.best_params:
- leaf_size = 30
- n_neighbors = 30
- p = 1
- weights = distance
- 增加 feature
- 195 - ball_x
- ball_x - 195
- Result:
- 淒淒慘慘淒淒 不秀了
- KNN 缺點體現: 每一個feature都很重要,若加入較無相關的資料誤差會增大
- Conclusion: **KNN 03** https://drive.google.com/file/d/1gITO3U6Y3zkoZf34Ru1949aqDqVO-8EP/view?usp=sharing
SVM Classifier
---
- **SVM 01:** (with ml_knn.py)
https://drive.google.com/file/d/1nRS7Y4--g3VNUyvDp3zCWBiq0sNoHdrF/view?usp=sharing
- Model 訓練架構:
- Input: `[目前ball_x, 目前ball_y, 目前platform_x, ball_x - platform_x, 395 - ball_y]`
- Output: `[0, 1, 2]` # 分別代表(NONE, MOVE_LEFT, MOVE_RIGHT)
- 訓練資料前處理:
- Train_X: 所有的 `[目前ball_x, 目前ball_y, 目前platform_x, all_x - platform_x, 395 - ball_y]` 直接作為 input
- Train_Y: 讀取 data['ml']['command']並且轉換為 0, 1, 2
- 總共使用 108 個 Rule_Base 遊玩 NORMAL 3 產生的 .pickle 檔案訓練
AdaBoost Classifier
---
- **AdaBoost 01:** (with ml_knn.py)
https://drive.google.com/file/d/1FGQYuBkYDDBS9j6GjK1X044hMZP4l1wl/view?usp=sharing
- Model 訓練架構:
- Input: `[目前ball_x, 目前ball_y, 目前platform_x, ball_x - platform_x, 395 - ball_y]`
- Output: `[0, 1, 2]` # 分別代表(NONE, MOVE_LEFT, MOVE_RIGHT)
- 訓練資料前處理:
- Train_X: 所有的 `[目前ball_x, 目前ball_y, 目前platform_x, all_x - platform_x, 395 - ball_y]` 直接作為 input
- Train_Y: 讀取 data['ml']['command']並且轉換為 0, 1, 2
- 總共使用 108 個 Rule_Base 遊玩 NORMAL 3 產生的 .pickle 檔案訓練
- grid.best_params:
- learning_rate: 1.0
- n_estimators: 30
- 不差,但絕對不算好
- Result:

- **AdaBoost 02:** (with knn.py)
https://drive.google.com/file/d/1FGQYuBkYDDBS9j6GjK1X044hMZP4l1wl/view?usp=sharing
- feature 移除 ball_x - platform_x 和 395 - ball_y]`
- grid.best_params:
- learning_rate: 1.0
- n_estimators: 30
- 一樣慘
- Result:

Gradient_Boosting Classifier
---
- **Gradient_Boosting 01:** (with knn.py)
https://drive.google.com/file/d/1o6y0miIKVT5R9x56bWfd6oXghQ5njL7U/view?usp=sharing
- Model 訓練架構:
- Input: `[目前ball_x, 目前ball_y, 目前platform_x, ball_x - platform_x, 395 - ball_y]`
- Output: `[0, 1, 2]` # 分別代表(NONE, MOVE_LEFT, MOVE_RIGHT)
- 訓練資料前處理:
- Train_X: 所有的 `[目前ball_x, 目前ball_y, 目前platform_x, all_x - platform_x, 395 - ball_y]` 直接作為 input
- Train_Y: 讀取 data['ml']['command']並且轉換為 0, 1, 2
- 總共使用 108 個 Rule_Base 遊玩 NORMAL 3 產生的 .pickle 檔案訓練
- grid.best_params:
- learning_rate: 1.0
- max_depth: 7
- 有成功通關幾次!
- 容易卡關在相同的位置
- Result:
- 
並且!!! 我利用 108 個遊玩 NORMAL 3 的紀錄檔訓練出的模組,在模型完全沒看過 NORMAL 1, 2, 5 的狀況下,也可以有不錯的成效!
- NORMAL 5: (訓練集沒有這關,但仍然可以玩)
- 
- **Gradient_Boosting 02** (with knn.py)
https://drive.google.com/file/d/1MJCE1cQhYSyh9Bb5t1mShY4tvywqkKzP/view?usp=sharing
- grid.params:
- learning_rate: 0.01, 0.1, 1, 10 結果使用遊戲實測
- learning_rate = 1 時最好,其他狀況很慘淡
Gradient Boosting Regression
---
- **改變架構**: 由"預測`左移,不動,右移`" 更改為 "預測`預測落點位置_x`".
- 因此控制遊戲時,是依據預測的`預測落點位置_x`與目前的platform_x位置,進行判定移動
- **Gradient Boosting Regression 1:** (with ml_GBR01.py) https://colab.research.google.com/drive/15auVBoBQTDferIQo4AVIlohabkozuoLu?usp=sharing
- Model 訓練架構:
- Input: `[目前ball_x, 目前ball_y, 目前斜率(與前一瞬間計算得知)]`
- Output: `[預測落點位置_x]`
- 訓練資料前處理:
- Train_X: 所有的 `[目前ball_x, 目前ball_y, 目前斜率(與前一瞬間計算得知)]` 直接作為 input
- Train_Y: 在前一次ball碰撞到platform,與後一次ball碰撞到platform 期間的 `[預測落點位置_x]` 值,全部設定為後一次碰撞到時的 x 數值。
- 總共使用 108 個 Rule_Base 遊玩 NORMAL 3 產生的 .pickle 檔案訓練
- 因為108次遊戲中,最後一次是強制結束的,因此將最後還未碰撞到平面的資料刪除
- 控制器台整為,以`預測落點`位置調整`平台位置`:
- 
- 結果分析:
- 因為在遊戲前處理的過程中,將所有不相關的資料(ball非處於下降期間的數值)帶入模組訓練,並且設定這期間Train_y都相同
- 因此導致訓練出的 model 大部分的時間不會改變預測之 target_x 數值
- 因此導致 target_x 與 實際落點 較無關聯性
- Result:
- 
- **Gradient Boosting Regression 2:** (with ml_GBR01.py)
https://colab.research.google.com/drive/1-zKKo8NJGnrR_0PY0KFBPJo5CnI_QTjx?usp=sharing
- 增加 feature
- ball_x - platform_x
- 395 - ball_y
- 結果一樣不行。我認為仍然是 model 訓練過程大部分的時間不會改變預測之 target_x 數值 所影響的
- Result:
- 
儲存Model:
---
- Official: https://scikit-learn.org/stable/modules/model_persistence.html
- 
- import: `import pickle`
- store file:
```
f = open('model_name.pickle', 'wb')
pickle.dump(model, f)
f.close()
```
- read file:
```
f = open('model_name.pickle', 'rb')
model = pickle.load(f)
f.close()
```
ML Model 控制遊戲進行:
---
- knn.py (助教提供之範例)
- 
- ml_knn.py (添加 feature)
- 
- pred_x,py
- 
結論
---
- Rule_Base
- 因為某些時候,球會卡在固定的輪迴中,導致永遠無法撞擊到磚塊,因此我加入 Random 去避免此事的發生
- 加入 Random 使得模型是動態的,所以儘管108次的遊玩.pickle檔案都是玩"NORMAL 3",但108次的結果都會是不一樣的。
- 無法 overfitted到100%,使得破關變得不容易
- 在 KNN 03 和 KNN 01 的比較中可以發現,添增兩個 feature: `球和平台的X軸距離` 與 `球和地面(395)的垂直距離` 對於結果有不小的幫助
- 但事實上,這兩個feature是從原本的三個舊feature中就可以計算出的數值
- 也就是,他其實資訊量沒有變多,只是我們將兩個特徵直接抓出來給Model,讓Model學更準確
- KNN 對於每個 feature 都很敏感(重要),因此當我的 KNN 04 再另外添增兩個比較不相關的 feature 時,就導致結果變差。
- 很振奮人心的是,GradinetBoostingClassifier 01 只有透過 遊玩NORMAL 3的108次紀錄訓練,但卻能將NORMAL 5打到剩下一個磚塊
- GradinetBoostingClassifier 01 是我最終選擇的模型
- 僅使用 NORMAL 3 的通關紀錄(108個)訓練
心得
---
做了很多不同演算法不同架構的嘗試:

訓練出了很多不同架構不同參數的模型:

甚至是故意製造一個龐大的數據集,用來訓練模型還訓練到跑了7小時沒跑出來卻當機

因為資料集的前處理、訓練模型的參數優化,導致每一次的嘗試都耗費許多時間
每一次的嘗試,都在想為什麼結果會這樣、還有什麼方法可以改進
但而後我得知故意讓模型 overfitted,使之和Rule Base一致就可以過關時,那時我其實感到很失望。
這並不是我想像中該出現的方法。
> 使用`左移、右移、或是不動`是否相同,作為評斷模型好壞(prediction, f1 score...)的方式,想起來就怪怪的吧?
> 若一個訓練解答是"左+右+左",但你的模型是"右+左+右",實際上都能打到球,但你的model在訓練的過程中就是被評為0%準確率
> 也就是若使用 grid 選擇參數時,就會被當成爛參數淘汰
> 我想像中一個模型的好壞,判斷方式應該要是當`ball與platform同高`的時候,`ball是否有落在platform裡面`作為評斷準確率的依據,這樣較為合理~?
> 但事實是,目前為止我還無法透過這種方法訓練出可以用的模型