Google Sheets에서 데이터 확인
TensorFlow에 대해 알아보기 전에 데이터를 한 번 살펴보고 싶어서 레드가 약 50회의 슛에 성공할 때까지 Unity가 계속 실행되도록 하였습니다. Unity 프로젝트의 루트 디렉토리를 살펴보면 successful_shots.csv라는 새 파일이 보일 것입니다. 이 파일은 골인에 성공한 각 슛에 대해 Unity에서 생성된 원시 덤프 파일입니다. 스프레드시트에서 쉽게 분석할 수 있도록 Unity에서 이 파일을 내보내도록 한 것이죠.
이 .csv 파일에는 거리와 힘을 포함하여 열 색인이 딱 세 개만 있습니다. Google Sheets로 이 파일을 가져와서 데이터 분포를 알 수 있게 해 줄 추세선을 포함한 산점도를 생성했습니다.
자, 한번 보세요! 우리가 얻은 산점도 를 한번 보시라니까요! 놀랍지 않나요? 실은 저도 처음부터 이 정도 결과를 얻으리라곤 확신하지 못했거든요. 자, 그럼 이 그래프를 분석해봅시다.
그래프에서 Y축을 따라서는 슛을 던질 때의 힘을 기준으로 하고, X축을 따라서는 슛을 던진 거리를 기준으로 하여 일련의 점이 분포되어 있습니다. 여기서 우리는 슛을 던질 때 필요한 힘과 거리 사이에 매우 분명한 상관관계를 볼 수 있습니다(제멋대로 튀어 오른 공이 바스켓에 들어가는 예외도 몇 개 포함됨).
실질적으로 이를 'TensorFlow가 이런 일에 매우 능숙할 것이다'고 해석할 수 있습니다.
이 사용 사례는 단순하긴 하지만, TensorFlow의 대단한 점 중 하나는 원한다면 비슷한 코드를 사용해 더 복잡한 모델을 빌드할 수 있다는 사실입니다. 예를 들어 최종 완성된 게임에서는 다른 선수의 위치와 과거에 이들 선수가 슛을 블로킹한 빈도에 대한 통계 같은 기능을 포함하여 우리 팀 선수가 슛을 해야 할지, 패스를 해야 할지 결정할 수도 있을 것입니다.
모델 TensorFlow.js 만들기
즐겨 쓰는 편집기에서 tsjs/index.js 파일을 여세요. 이 파일은 Unity와는 무관하며 단지 successful_shots.csv에 있는 데이터를 기준으로 모델을 훈련하기 위한 스크립트일 뿐입니다.
다음은 우리의 모델을 훈련하고 저장하는 전체 메서드입니다.
# See : https://medium.com/media/48bb2ad5058e7bbb3ee829c4659780cc/href
(async () => {
/*
Load our csv file and get it into a properly shaped array of pairs likes...
[
[distanceA, forceB],
[distanceB, forceB],
...
]
*/
var pairs = getPairsFromCSV();
console.log(pairs);
/*
Train the model using the data.
*/
var model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
const xs = tf.tensor1d(pairs.map((p) => p[0] / 100));
const ys = tf.tensor1d(pairs.map((p) => p[1]));
console.log(`Training ${pairs.length}...`);
await model.fit(xs, ys, {epochs: 100});
await model.save("file://../Assets/shots_model");
})();
보시다시피, 그다지 많은 양은 아닙니다. .csv 파일에서 데이터를 로드하고 일련의 X 및 Y 점을 생성합니다. 마치 위의 Google Sheet처럼 말이죠. 그리고 우리가 만든 모델에 이 데이터에 '맞추라'고 지시합니다. 그 후, 다음에 사용할 수 있도록 모델을 저장합니다.
다행인 것은, 여러분이 원한다면 그 모든 과정을 건너뛰고 그냥 tsjs/build.sh를 실행할 수 있고, 모든 것이 잘 돌아가면 모든 단계가 자동으로 진행되어 Unity 에 프로즌 모델이 올라간다는 점입니다.
Unity 내부에서 우리는 Assets/BallSpawnController.cs의 GetForceFromTensorFlow()를 통해 우리 모델과 상호 작용하고 있는 대상이 어떤 모습인지 볼 수 있습니다.
# See : https://medium.com/media/90eb3de32a320803ad49c628cae5eb47/href
float GetForceFromTensorFlow(float distance)
{
var runner = session.GetRunner ();
runner.AddInput (
graph["shots_input"][0],
new float[1,1]{{distance}}
);
runner.Fetch (graph ["shots/BiasAdd"] [0]);
float[,] recurrent_tensor = runner.Run () [0].GetValue () as float[,];
var force = recurrent_tensor[0, 0] / 10;
Debug.Log(String.Format("{0}, {1}", distance, force));
return force;
}
그래프를 정의할 때 여러 단계가 있는 복잡한 시스템을 정의하게 됩니다. 이 사례에서는 모델을 단 (암묵적인 입력 계층을 포함한) 하나의 조밀한 신경망 층으로 정의했는데, 이는 모델이 단일 입력을 받아 출력 결과를 내놓는다는 의미입니다.
TensorFlow.js에서 model.predict를 사용하면 입력 데이터가 올바른 입력 그래프 노드로 자동으로 공급되고 계산이 완료되면 적절한 노드에서 출력이 나옵니다. 하지만 TensorFlowSharp는 다르게 작동하므로 우리가 그래프 노드의 이름을 통해 이들 노드와 직접 상호작용해야 합니다.
그 점을 염두에 두면 이제 그래프에 적절한 포맷으로 입력데이터를 가져온 후, 레드에게 출력을 보내는 문제가 됩니다.