---
# System prepended metadata

title: TF Jam
tags: [tensorflow, unity, ai]

---

---
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 打勾
![](https://i.imgur.com/nFCKXOt.png)
因為會自動儲存 就可以按叉叉了
最後到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-投籃力道
![](https://i.imgur.com/lw9t6Ik.png)


## 流程
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檔裡面
![](https://i.imgur.com/hxhzO9o.png)
![](https://i.imgur.com/Tx0yiQz.png)


收集到的資料型態
![](https://i.imgur.com/1cSgEiV.png)

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 就是建立的新環境)
```
![](https://i.imgur.com/Vm7yTEq.png)



2.2 訓練
這邊就能用到我們剛剛蒐集的資料了，利用PYTHON進行訓練的動作，是一種監督式學習的方式
下圖為792筆資料的分布圖 x-距離 y-力道，
可以看到蠻明顯的有一條比較集中的線條
![](https://i.imgur.com/6Y1XRTQ.png)
先用小畫家簡單畫一下我們想訓練出的模型 應該會是下面紅線的樣子
![](https://i.imgur.com/DJTprcX.png)

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. 模型建立
再重新看一下我們預計建立的模型
![](https://i.imgur.com/DJTprcX.png)
我們先試試看利用線性回歸的方式來訓練
:::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)
```
![](https://i.imgur.com/4xYy0Oc.png)
我們再簡單分析一下訓練出來的模型
估計只能達到20%~30%左右的準確率
![](https://i.imgur.com/xrZfCEj.png)


如果要達到100%的準確度 想必要自己來設計模型了

看完下圖 再看看自己想做出來的模型 
沒錯 激勵函數就是relu了
![](https://i.imgur.com/lZuI1Ka.png)

所以設計出下面的模型
```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)
```
![](https://i.imgur.com/JhFJRJl.png)

### 測試過程
在建立模型的過程 主要以神經元個數與epoch來做修改
神經元在3以下的情況下沒有辦法正確測試
epoch 20開始有一個形狀出現
epoch 30基本上準確率就達到100%了
![](https://i.imgur.com/17a2qCA.png)
![](https://i.imgur.com/FTt9yJK.png)
![](https://i.imgur.com/dSWleky.png)
![](https://i.imgur.com/ics6euP.png)
![](https://i.imgur.com/CEoLG9v.png)
![](https://i.imgur.com/uFiThDN.png)
![](https://i.imgur.com/QmcvphF.png)


![](https://i.imgur.com/oUomHx0.png)

訓練完模型後，我們把它儲存成一個.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的輸入了
![](https://i.imgur.com/MGte0JO.png)


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;
}
```
你會發現 力道輸出好像怪怪的?
![](https://i.imgur.com/vi3MvOt.png)

仔細看程式碼
```
float force = GetForceFromTensorFlow(dist) * shotBoost;
return recurrent_tensor[0, 0] /10;
```
為甚麼回傳會先/10後面又要在*shotBoost?

後來我自己去測試一些資料後發現
還記得剛開始訓練資料有先做一個標準畫的動作嗎?
![](https://i.imgur.com/WR6ayif.png)
所以我們在unity輸入輸出資料也需要先做標準化與還原的動作
```csharp=
var distt = (dist-minX)/(maxX-minX);
			
float force = GetForceFromTensorFlow2(distt)*(maxY-minY)+minY ;
```
![](https://i.imgur.com/M3iQt4Y.png)




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 %}