Deep-Q-Network(ConvNetJS)で簡単なカードデッキ構築ゲームを学習させてみる

#はじめに
本格的にゲームなどの強化学習を行うには、「Gym」という強化学習アルゴリズムの統合環境を使うのが良さそうですが、JavaScriptで作ったお手製のゲームに簡単に組み込めないかなと、調べてみると「ConvNetJS」というJavaScript製のDeepLearningフレームワークがありました。
今回は、このフレームワークの評価も含めて、とても簡単なDQNによる強化学習を試してみることにしました。

##DQN(Deep Q-Network)
Q学習と呼ばれる強化学習を行うためのニュートラルネットワークのこと。ゲーム中に得られたスコアなどを報酬として与え、学習させることができます。
ある環境sがinputされた時に、Q学習は行動αを選択する関数Q(s,α)を使って行動をoutputします。
これに層学習の技術を+プラスしたものがDeep Q-Networkと呼ばれるものだそうです。詳しい説明はこちらのドキュメントがわかりやすかったです。

>「DQNの生い立ち　＋　Deep Q-NetworkをChainerで書いた」
>http://qiita.com/Ugo-Nama/items/08c6a5f6a571335972d5

##JavaScript製のDeepLearningフレームワーク
ConvNetJS
http://cs.stanford.edu/people/karpathy/convnetjs/

#作ってみた
##1.ゲームの設計
今回作ったゲームは、３種類のカードを5枚自由に組み替えできるゲームで味方と敵に分かれて対戦します。
あらかじめ示された敵の5枚のデッキに対して、同じ列のカードにのみ攻撃を仕掛けます。
攻撃の結果は３すくみで決まり(水は火に勝つ、火は木に勝つ、木は水に勝つ)
勝利に対して+1ポイント。負ければ-1ポイントが入ります。
5回すべてに勝利すれば最大で5点が入るような簡単なゲームを想定します。
(人間であれば、すべての相手のデッキが示されているので、3すくみのルールさえ理解できれば、
必ず5点が取れるようなゲーム内容ですが、今回はコンピューターに結果の報酬のみを与えて、この3すくみのルールを理解させていくところがミソと言えます。このルールでうまくいけば、出撃させれるカードの枚数に制限を与えた上で、一部のカードを隠してしまうなど、少しずつ複雑にしていくとよりゲームとして成立するかなと思います。)

![battle-1.png](https://qiita-image-store.s3.amazonaws.com/0/43766/b1606ec4-e8f8-78e3-db9e-6c69dc2fe239.png)
グラフィック素材　【あとらそふと】　http://atorasoft.blog18.fc2.com/

##2.入力と出力の決定
入力の値として、敵のカード5枚の種類と、自身の入力中の枚数(n枚目)を入れる事にしました。
敵のカードが[火、水、水、木、木]というパラメータ5個 + 自分の手札n列目という数(1~5)のパラメータ1個で、合計6個が入力となります。
それに対して出力は、カードの種類である火、水、木の3種類で3個が出力となります。

```js
	var num_inputs = 6;
	var num_actions = 3;
	var temporal_window = 1;
	var network_size = num_inputs*temporal_window + num_actions*temporal_window + num_inputs
```

##3.層を作る

レイヤーを何層に重ねれば良いのか、未だよくわからないので、中間の層の数を増やしたパターンと、temporary memoryの数を変えたものを用意してみました。

```js
	layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth:network_size})
	layer_defs.push({type:'fc', num_neurons: 30, activation:'relu'})
	layer_defs.push({type:'fc', num_neurons: 30, activation:'relu'})
	layer_defs.push({type:'fc', num_neurons: 30, activation:'relu'})
	layer_defs.push({type:'fc', num_neurons: 30, activation:'relu'})
	layer_defs.push({type:'regression', num_neurons:num_actions})
```

##4.学習させる

```js
	this.brain = new Brain()
	
	//入力値を取得する
	var input = this.getInput()

	//出力を決める
	var output = this.brain.forward(input)

	//報酬計算
	this.brain.backward(this.reward)

	//save & load
	var brain = JSON.stringify(this.brain.value_net.toJSON())
	this.brain.value_net.fromJSON(brain)

```

##5.報酬の与え方
報酬は、スコアの与え方と同様にして、
勝利で+1、引き分け+0、敗北-1という感じで与えてみました。


##6.結果 
評価用の値として、1000試合の平均値スコアをプロットしていきました。(横軸にx1000したものが実際の試合の回数になります。)
最初の1000試合では、だいたい平均が0点に近いところからスタートしたので、半分勝利して、半分敗北するような感じ。ほとんどランダムに近い感じだと思います。
徐々に施行を重ねるにつれて、得点が得られるようになっていきました。
このグラフから見ると20000試合前後で平均的に完全勝利の5点に近い値まで学習して、収束していくのがわかります。
層の厚みの違いで、序盤の8000試合前後までは、層の厚みが多い方グラフの方が、少しだけ早く学習ができているような感じに見えますが、それ以降は徐々に差が縮まっているようにみえました。この辺りはもう少し今後色々と試してみたいと思っています。

<img width="797" alt="スクリーンショット 2017-01-07 0.40.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/43766/f7ec72a8-b3c9-0045-8ba8-56e11fe34fe7.png">

青：層x2 / 赤：層x6　/ 緑:層x6 + temporary memoryx5 

##7.コード
https://oggata.github.io/convnetjs-demo/3sukumi/src/index.html
(立ち上げて1hくらい経過すればどんどんloseのカウントがされなくなる)

##8. おしまい
ChainerやTensorFlowなどよりもJavaScriptで使えるのはかなり便利だなぁと思う反面、当然ながら入力値や層の厚みを増やしすぎると速度面が厳しいのかなと思いました。(今回の例では入力値が6つに対して出力が3という非常に簡単なものでしたので問題ありませんでしたが。)。特にゲームなどで60や30fpsで動作させつつ大きいinputを得ながら学習させるのは厳しそうなので、ConvNetJSの学習部分だけNode.jsなどを使ってサーバーサイドに分けるか、事前に作られたbrain.txtを読み込むなど工夫すれば色々とゲームなどにも活かせるのではないかなと思いました。次回はもう少し複雑な問題に取り組んでみたいと思います。