スーパーファミコンのソフトをTensorFlowで学習させてみる！

##はじめに

今回スーパファミコン(SNES)からROMを抜き出し、強化学習 + 深層学習で学習させAIで操作したときの技術メモ。
構築方法などを書きます。
今回使用したAIフレームワークはTensorFlow。
[tensorflowはこちら](https://www.tensorflow.org/)
[ROMの抜き出し方法はこちらの記事に書いてあります](http://qiita.com/tsunaki/items/a316d08cd292cde00bf6)

![IMG_3730.JPG](https://qiita-image-store.s3.amazonaws.com/0/52867/3732a20e-9cd6-6919-ee1c-75cd3f5c45e9.jpeg "IMG_3730.JPG")


## 事前準備

###環境
以下のマシン環境を実施

|環境||
|:-----------|:------------|
|OS | Ubuntu 16.04 LTS
|CPU | i7
|MEM | 16GB
|GPU | NVIDIA GeForce GTX 980
|開発言語|エミュレータやROMを操作するのはC++、deeplearningはpython|


![IMG_3677.JPG](https://qiita-image-store.s3.amazonaws.com/0/52867/90c6a5d3-6885-e29f-8a33-ec2e0d651867.jpeg "IMG_3677.JPG")

## 学習方法
ゲームキャプチャ + アクションで報酬(game score)を与えそれを学習させる。
game scoreの取得については、ROMのバイナリ情報を解析する必要があります。
この辺はあまり詳しくありませんが海外にROMの情報が出ているとのことです。


### ニューラルネットワークアーキテクチャ
学習関数は定番のReLU。教科学習のアルゴリズムは勾配降下法(Adam)でやってます。
[ReLU](https://ja.wikipedia.org/wiki/%E6%B4%BB%E6%80%A7%E5%8C%96%E9%96%A2%E6%95%B0)
[勾配降下法](https://ja.wikipedia.org/wiki/%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95)

###ニューラルネットワークの外観
![qiita2.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/52867/2e9812a1-7ab2-5d49-45fd-5070a4a9724f.jpeg "qiita2.001.jpeg")


![qiita.002.jpeg](https://qiita-image-store.s3.amazonaws.com/0/52867/04b0453e-7486-d225-e492-d86ef2bfe4cf.jpeg "qiita.002.jpeg")


## 手順
### ⅰ.エミュレータの準備

エミュレータの動作に関しては処理が複雑なので[git](https://github.com/tsunaki00/siva_game)にあげておきます。
https://github.com/tsunaki00/siva_game
※ 他のマシンで試してないので動くかわかりません。
エミュに興味あるかたは海外のgitなどをぐぐってみてください！

### ⅱ.ROMの抜き出し
上記にも記載しましたが、[ROMの抜き出し方法はこちらの記事に書いてあります。](http://qiita.com/tsunaki/items/a316d08cd292cde00bf6)
※ 注意 ROMについては購入したものを抜くのは問題ありませんが、再配布等は禁止です。

![6085634a-0ce5-105f-be0f-59293e5c6a3c.jpeg](https://qiita-image-store.s3.amazonaws.com/0/52867/22d85117-f544-b057-bb48-b6ddab6f11d2.jpeg "6085634a-0ce5-105f-be0f-59293e5c6a3c.jpeg")


### ⅲ.プログラム
エミュレータの仕様上、画面キャプチャが224 × 256なのでそちらを入力にして学習する。
時間あるときにopenCVに変更します。
※※※追記
画像が大きくて遅い問題はopencvで84 * 84にresizeしたら快速になりました。

```追記内容
 screen = self.rle.getScreenGrayscale()
 screen = cv2.resize(screen, (84, 84), interpolation=cv2.INTER_LINEAR)
 screen = np.reshape(screen, (84, 84, 1))
```

```execute.py
#!/usr/bin/env python
import tensorflow as tf
import sys
import numpy as np
from random import randrange
import rle_python_interface

class Game:

  def __init__(self) :
    if len(sys.argv) < 2:
      print ('Usage:', sys.argv[0], 'rom_file', 'core_file')
      sys.exit()
    self.rle = rle_python_interface.RLEInterface()
    self.rle.setInt('random_seed', 123)
  
  
  def weight_variable(self, shape):
    initial = tf.truncated_normal(shape, stddev = 0.01)
    return tf.Variable(initial)

  
  def bias_variable(self, shape):
    initial = tf.constant(0.01, shape = shape)
    return tf.Variable(initial)

  def conv2d(self, x, W, stride):
    return tf.nn.conv2d(x, W, strides = [1, stride, stride, 1], padding = "SAME")

  def max_pool_2x2(self, x):
    return tf.nn.max_pool(x, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding = "SAME")

  
  def createNetwork(self, action_size):
    # network weights
    n = 256  
    W_conv1 = self.weight_variable([56, 64, 4, 16])
    b_conv1 = self.bias_variable([16])
    W_conv2 = self.weight_variable([28, 32, 64, 32])
    b_conv2 = self.bias_variable([32])
    W_conv3 = self.weight_variable([7, 8, 32, 16])
    b_conv3 = self.bias_variable([16])
    W_fc1 = self.weight_variable([n, action_size])
    b_fc1 = self.bias_variable([action_size])
    s = tf.placeholder("float", [None, 224, 256, 4])
    # hidden layers
    h_conv1 = tf.nn.relu(self.conv2d(s, W_conv1, 4) + b_conv1)
    h_pool1 = self.max_pool_2x2(h_conv1)
    h_conv2 = tf.nn.relu(self.conv2d(h_pool1, W_conv2, 2) + b_conv2)
    h_pool2 = self.max_pool_2x2(h_conv2)
    h_conv3 = tf.nn.relu(self.conv2d(h_pool2, W_conv3, 1) + b_conv3)
    h_pool3 = self.max_pool_2x2(h_conv3)
    
    h_conv3_flat = tf.reshape(h_pool3, [-1, n])
    readout = tf.matmul(h_conv3_flat, W_fc1) + b_fc1
    return s, readout 

  def trainNetwork(self,  s, readout, sess):
    minimal_actions = self.rle.getMinimalActionSet()
    a = tf.placeholder("float", [None, len(minimal_actions)])
    y = tf.placeholder("float", [None, 1])
    readout_action = tf.reduce_sum(tf.multiply(readout, a), reduction_indices = 1)
    cost = tf.reduce_mean(tf.square(y - readout_action))
    train_step = tf.train.AdamOptimizer(1e-6).minimize(cost)
    saver = tf.train.Saver()
    sess.run(tf.initialize_all_variables())

    checkpoint = tf.train.get_checkpoint_state("./saved_networks/checkpoints")
    if checkpoint and checkpoint.model_checkpoint_path:
        saver.restore(sess, checkpoint.model_checkpoint_path)
        print "Successfully loaded:", checkpoint.model_checkpoint_path
    else:
        print "Could not find old network weights"

    # Play 100 episodes
    for episode in xrange(100):
      total_reward = 0
      act_count = 1
      actions = []
      rewards = []
      images = []
      while not self.rle.game_over():
        if episode >= 1 :
          readout_t = readout.eval(feed_dict = {s : [self.rle.getScreenRGB()]})[0]
          print(readout_t)
          action_index = np.argmax(readout_t)
        else :
          action_index = randrange(len(minimal_actions))

        action = minimal_actions[action_index]
        # Apply an action and get the resulting reward
        action_array = np.zeros(len(minimal_actions))
        action_array[action_index] = 1
        actions.append(action_array)
        reward = self.rle.act(action)
        total_reward += reward
        array = np.zeros(1)
        array[0] = float(reward)
        rewards.append(array)
        images.append(self.rle.getScreenRGB())
        print ('Episode', episode, 'REWARD:', reward, 'Action', action)
        
        # mini_batch
        if act_count % 50  == 0 :
          train_step.run(feed_dict = {
            a : actions,
            y : rewards,
            s : images
          })
          actions = []
          rewards = []
          images = []
        act_count += 1
    
      print ('Episode', episode, 'ended with score:', total_reward)
      saver.save(sess, 'saved_networks/model-dqn', global_step = episode)
      self.rle.reset_game()


  def playGame(self):
    USE_SDL = True
    if USE_SDL:
      if sys.platform == 'darwin':
        import pygame
        pygame.init()
        self.rle.setBool('sound', False) # Sound doesn't work on OSX
      #elif sys.platform.startswith('linux'):
      #  rle.setBool('sound', True)
      self.rle.setBool('display_screen', True)
    #  rle.setBool('two_players', True)
    self.rle.loadROM(sys.argv[1], sys.argv[2])

    # Get the list of legal actions
    minimal_actions = self.rle.getMinimalActionSet()
    #minimal_actions = rle.getAllActionSet()

    sess = tf.InteractiveSession()
    s, readout = self.createNetwork(len(minimal_actions))
    self.trainNetwork(s, readout, sess)


if __name__ == '__main__' :
  game = Game()
  game.playGame()

```


### 実行
以下のコマンドを実行。

```
 $ python execute.py [ROMファイル] snes 
```

処理が重いけど学習はOKでした。

![IMG_3727.JPG](https://qiita-image-store.s3.amazonaws.com/0/52867/da1359dc-2cf8-7c96-b7f5-58e912645c3e.jpeg "IMG_3727.JPG")

### [今回の結果]
いろいろ試した結果グラディウスの結果が良かったです！
![IMG_3740.JPG](https://qiita-image-store.s3.amazonaws.com/0/52867/b5b3e548-8240-207e-9b46-b596e4a96c49.jpeg "IMG_3740.JPG")


今後、学習結果については動画などでUPします！！


## 最後に
他にtensorflowで学習したマリオの動画はこちら
https://www.youtube.com/watch?v=T4dO1GKPx4Y

また、ディープラーニングで競馬予想なども実施していますので、いいね！フォローお願いします。
［facebook］
https://www.facebook.com/AIkeiba/
［Twitter］
https://twitter.com/Siva_keiba