---
title: TF Jam
tags: ai,unity,tensorflow
description: base on https://blog.tensorflow.org/2018/07/tf-jam-shooting-hoops-with-machine-learning.html
---
# TF Jam 復刻
利用真正的AI來做遊戲AI吧!
這篇文章會帶領你從Python 利用Keras 訓練模型,再把他放到Unity內使用
此文章基於[此處](https://blog.tensorflow.org/2018/07/tf-jam-shooting-hoops-with-machine-learning.html)重新撰寫
在訓練模型的部分,選擇使用[此處](https://www.youtube.com/watch?v=ElDzPCCIpdI)所提供方式訓練(keras->tensorflow)
文末有修改好的專案 沒意外應該能直接用
## 環境設置
* Unity (2018.4.20f1 or higher) -建議使用Unity hub安裝
* Unity 專案的[source code](https://github.com/stevecox1964/tf-jam-clone)
* Python (Install Python 3.6.1 or Higher 盡量使用虛擬環境)
* pip install jupyter
* pip install numpy
* pip install pandas
* pip install matplotlib
* pip install tensorflow == 1.15.2 (新安裝的版本號就是2開頭了 ,後面會沒辦法使用)
* pip install -I keras == 2.1.6
~~※註:Python的版本很有可能是32bit的,而Tensorflow的要求為64bit,需要從python官網下載特定版本~~
# 環境除錯
這邊主要說明Unity的環境除錯
* [TensorFlowSharp](https://s3.amazonaws.com/unity-ml-agents/0.5/TFSharpPlugin.unitypackage)
載入後,記得在設定中打開
Edit>Project Settings>選擇Player 選單中的Other Settings
裡面的Scripting Define Symbols參數調整為ENABLE_TENSORFLOW
下面的Allow 'unsafe' Code 打勾

因為會自動儲存 就可以按叉叉了
最後到File>Save Project>>重啟
* Boo.Lang.dll 找不到
因為Unity已經預設不支援**Boo.Lang**
這個函式庫能夠從"C:\Program Files\Unity\Hub\Editor\2018.4.21f1\Editor\Data\Mono\lib\mono\2.0"
這個位置下找到"Boo.Lang.dll"
並新增到專案下的"Asset/Plugins"
但這個函式庫好像也沒用到
## 目標
目標很簡單,就是 X-籃框距離 Y-投籃力道

## 流程
1. 收集資料
有資料才能訓練
所以我們的第一步是蒐集資料
投球需要哪些資料?
力道?距離?角度?
如果不考慮籃板反彈的話 其實我們不需要角度的資料
所以我們只需要蒐集力道跟距離兩個資料就好了
在Unity內怎麼收集資料呢?
用比較白話的方式來說明
詳細的可以點到Github裡面看source code
:::danger
因為數據的收集會收集到彈地球,所以我把"BallController.cs"裡的一段做修改了
private void OnCollisionEnter (Collision other) {
if (other.gameObject.name == "Court") {
// StartCoroutine (DoDespawn (2.5f));
Destroy (gameObject);
}
}
:::
```pseudo code=
void 角色操作(){
隨機距離();
隨機力道();
投球(距離,力道);
}
void 投球(float 距離,float 力道){
if(進球){
儲存到csv檔(力道,距離);
}
}
```
在Unity中 我們能夠設置一個像下面圓柱一樣的觸發器,只要球通過就會觸發進球的事件,然後加一筆資料到CSV檔裡面


收集到的資料型態

2. 訓練模型
2.1 環境設置
因為套件的函式的使用有時候會因為版本更新而改變,所以在訓練模型的時候還是建立一個專屬的虛擬環境會比較好。
```
1.virtualenv unity
建立unity的環境
2.cd ./ unity /scripts
移動到代碼部分
3.activate
啟動虛擬環境,就能開始下載套件了
pip install jupyter
pip install numpy
pip install pandas
pip install matplotlib
pip install tensorflow == 1.15.2 (新安裝的版本號就是2開頭了 ,後面會沒辦法使用)
pip install -I keras == 2.1.6
4.把虛擬環境加入jupyter(選用)
pip install ipykernel
python -m ipykernel install --user --name=virtual_env_name
開啟jupyter notebook 就有新環境可以選擇囉(下圖的tensorflow 就是建立的新環境)
```

2.2 訓練
這邊就能用到我們剛剛蒐集的資料了,利用PYTHON進行訓練的動作,是一種監督式學習的方式
下圖為792筆資料的分布圖 x-距離 y-力道,
可以看到蠻明顯的有一條比較集中的線條

先用小畫家簡單畫一下我們想訓練出的模型 應該會是下面紅線的樣子

1. 讀取csv
```python=
shots_csv = 'successful_shots.csv'
shots = pd.read_csv(shots_csv)
```
2. 標準化
為了降低數據落差 所以我們先對資料做標準化的動作
```python=
x = shots["dist"].values.reshape(-1,1).astype(np.float32)
minX = min(x)
maxX = max(x)
X = (x-minX)/(maxX-minX)
y = shots["force"].values.reshape(-1,1).astype(np.float32)
minY = min(y)
maxY = max(y)
Y = (y-minY)/(maxY-minY)
#X,Y就是我們標準化後的數據
```
3. 模型建立
再重新看一下我們預計建立的模型

我們先試試看利用線性回歸的方式來訓練
:::info
線性回歸(Linear Regression) 簡單來說,就是將複雜的資料數據,擬和至一條直線上,就能方便預測未來的資料。
[來源](https://ithelp.ithome.com.tw/articles/10206114)
:::
```
from sklearn import linear_model
regr = linear_model.LinearRegression()
regr.fit(X,Y)
y_pre_LinearRegression = regr.predict(X)
plt.scatter(X,Y,color='blue',s=1)
plt.scatter(X,y_pre_LinearRegression,color='red',s=1)
```

我們再簡單分析一下訓練出來的模型
估計只能達到20%~30%左右的準確率

如果要達到100%的準確度 想必要自己來設計模型了
看完下圖 再看看自己想做出來的模型
沒錯 激勵函數就是relu了

所以設計出下面的模型
```python=
model = Sequential()
model.add(Dense(4, name='shot_in' , input_dim=1, activation='relu'))
model.add(Dense(1, name='shot_out'))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(X, Y, epochs=100, batch_size=10)
```

### 測試過程
在建立模型的過程 主要以神經元個數與epoch來做修改
神經元在3以下的情況下沒有辦法正確測試
epoch 20開始有一個形狀出現
epoch 30基本上準確率就達到100%了








訓練完模型後,我們把它儲存成一個.h5的檔案
```
model_name = 'shotsmode.h5'
```
因為unity無法直接讀取這種格式,所以我們還要再把它改成tensorflow的格式
還好已經有大神幫我們用好轉換的Code了
我們只要呼叫同目錄下的**keras_to_tensorflow.py** 就能順利執行轉換了
```
python keras_to_tensorflow.py -input_model_file shotsmode.h5 -graph_def True
```
最後出來的shotsmodel.h5.pb就是我們unity的輸入了

3. 使用模型作為輸入
在unity裡面輸入距離
然後輸出力道
這邊是原作者所提供的程式碼
```csharp=
//有錯誤 請注意!!!
String filename = "shotsmodel.h5.pb";
var graphModel = File.ReadAllBytes ("./Assets/resources/" + filename);
graph_ = new TFGraph ();
graph_.Import (graphModel, "");
session_ = new TFSession (graph);
float force = GetForceFromTensorFlow(dist) * shotBoost;
float GetForceFromTensorFlow(float distance){
var runner = session.GetRunner();
runner.AddInput(graph["shot_in_input"][0], new float[1, 1] { { distance } });
runner.Fetch(graph["output_node0"][0]);
float[,] recurrent_tensor = runner.Run()[0].GetValue() as float[,];
return recurrent_tensor[0, 0] /10;
}
```
你會發現 力道輸出好像怪怪的?

仔細看程式碼
```
float force = GetForceFromTensorFlow(dist) * shotBoost;
return recurrent_tensor[0, 0] /10;
```
為甚麼回傳會先/10後面又要在*shotBoost?
後來我自己去測試一些資料後發現
還記得剛開始訓練資料有先做一個標準畫的動作嗎?

所以我們在unity輸入輸出資料也需要先做標準化與還原的動作
```csharp=
var distt = (dist-minX)/(maxX-minX);
float force = GetForceFromTensorFlow2(distt)*(maxY-minY)+minY ;
```

4. 小結
總共測試了500,1000,9000筆資料的訓練,
其實差異量不大,
最主要是後面忘記有標準化這個動作導致測試很久
## 修改後的檔案
[Unity專案](https://drive.google.com/file/d/1xij60TF9SOevfsoWgVZcFVjosK4Ia3K9/view?usp=sharing)
[訓練模型](https://drive.google.com/file/d/1TQxfuhtkyJIwBSOY9eQyqprSAUGlbOzN/view?usp=sharing)
## 參考資料
> [TF Jam](https://blog.tensorflow.org/2018/07/tf-jam-shooting-hoops-with-machine-learning.html)
> [KTF Jam - AI Robot Basketball using Unity/Keras/Tensorflow](https://www.youtube.com/watch?v=ElDzPCCIpdI)
> [深度學習:使用激勵函數的目的、如何選擇激勵函數 Deep Learning : the role of the activation function](https://mropengate.blogspot.com/2017/02/deep-learning-role-of-activation.html)
{%hackmd theme-dark %}