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 - ![](https://i.imgur.com/Ssu9BBj.png) - 設定判斷區(下圖紅色區域)(只在球由此區域向下時,計算且更新 落點 `target_x` 之數值) - ![](https://i.imgur.com/zuf1E5s.png) > **上圖為此文提到之 108 個訓練集 所使用的Rule Base 1** 因為不知道其他關卡有磚塊位於下方,因此改進 Rule Base 2 為: - ![](https://i.imgur.com/uwbMMTE.png) > 但我在下方提到的所有訓練過程,都是使用 Rule Base 1 作為訓練集 - **結論**:當 ( ball正在下降 and 進入判斷區 ) 成立時: - 計算出落點 `target_x` - ![](https://i.imgur.com/4cxk1Cd.png) > `Eular` 為計算簡諧運動的部分 - 移動 platform 直到 platform 能夠接住 target_x - 結果:完美無失誤 - Result: ![](https://i.imgur.com/e55kSQv.gif) > 感覺 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`... - 改變之前: ![](https://i.imgur.com/WkoHf7T.png) - 改變之後: ![](https://i.imgur.com/7YrHlGh.png) 上傳遊戲.pickle至google colab --- - 將剛剛被重新編號過的`.pickle`檔案上傳至 Google Colab - ![](https://i.imgur.com/sPg0pdB.png =400x) 訓練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: ![](https://i.imgur.com/q3GbxTM.gif) - **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: ![](https://i.imgur.com/0S2Nsvg.gif) - **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: ![](https://i.imgur.com/gudd9Dj.gif) - **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: ![](https://i.imgur.com/f2Hqdt5.gif) - **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: ![](https://i.imgur.com/YKZwoTR.gif) 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: - ![](https://i.imgur.com/enaqTl8.gif) 並且!!! 我利用 108 個遊玩 NORMAL 3 的紀錄檔訓練出的模組,在模型完全沒看過 NORMAL 1, 2, 5 的狀況下,也可以有不錯的成效! - NORMAL 5: (訓練集沒有這關,但仍然可以玩) - ![](https://i.imgur.com/ebsNJuT.gif) - **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次遊戲中,最後一次是強制結束的,因此將最後還未碰撞到平面的資料刪除 - 控制器台整為,以`預測落點`位置調整`平台位置`: - ![](https://i.imgur.com/5I1PC7Y.png) - 結果分析: - 因為在遊戲前處理的過程中,將所有不相關的資料(ball非處於下降期間的數值)帶入模組訓練,並且設定這期間Train_y都相同 - 因此導致訓練出的 model 大部分的時間不會改變預測之 target_x 數值 - 因此導致 target_x 與 實際落點 較無關聯性 - Result: - ![](https://i.imgur.com/4zQ6NHo.gif) - **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: - ![](https://i.imgur.com/mEZUdCV.gif) 儲存Model: --- - Official: https://scikit-learn.org/stable/modules/model_persistence.html - ![](https://i.imgur.com/7ev5lNb.png) - 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 (助教提供之範例) - ![](https://i.imgur.com/6d7E9Oy.png) - ml_knn.py (添加 feature) - ![](https://i.imgur.com/d5SeWdM.png) - pred_x,py - ![](https://i.imgur.com/U1irqpb.png) 結論 --- - 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個)訓練 心得 --- 做了很多不同演算法不同架構的嘗試: ![](https://i.imgur.com/xYcnOap.png) 訓練出了很多不同架構不同參數的模型: ![](https://i.imgur.com/JKvBL4r.png) 甚至是故意製造一個龐大的數據集,用來訓練模型還訓練到跑了7小時沒跑出來卻當機 ![](https://i.imgur.com/ViJj9Tt.png) 因為資料集的前處理、訓練模型的參數優化,導致每一次的嘗試都耗費許多時間 每一次的嘗試,都在想為什麼結果會這樣、還有什麼方法可以改進 但而後我得知故意讓模型 overfitted,使之和Rule Base一致就可以過關時,那時我其實感到很失望。 這並不是我想像中該出現的方法。 > 使用`左移、右移、或是不動`是否相同,作為評斷模型好壞(prediction, f1 score...)的方式,想起來就怪怪的吧? > 若一個訓練解答是"左+右+左",但你的模型是"右+左+右",實際上都能打到球,但你的model在訓練的過程中就是被評為0%準確率 > 也就是若使用 grid 選擇參數時,就會被當成爛參數淘汰 > 我想像中一個模型的好壞,判斷方式應該要是當`ball與platform同高`的時候,`ball是否有落在platform裡面`作為評斷準確率的依據,這樣較為合理~? > 但事實是,目前為止我還無法透過這種方法訓練出可以用的模型