gymで強化学習環境を作る

gymは強化学習の開発プラットフォームです。比較的簡単に強化学習を行うことができるのが特徴です。
https://gym.openai.com/
インベーダーやバランスゲーム的な何かなどのいろいろな強化学習環境がgymに登録されているのですが、強化学習を使う人は目の前に解きたいゲームがある人が多いはずです。
そこで今回はgymで強化学習環境を作ります。


# バージョン

主なライブラリのバージョンは以下です。特に今回のメインであるgymは少し前のバージョンと実装方法が少し異なるので注意が必要です。

```
gym==0.10.5
tensorflow==1.8.0
Keras==2.1.6
keras-rl==0.4.0
```

# ゲームの説明
以下のようなデータを作ります。

```python
import pandas
import numpy
df = pandas.DataFrame(
    {"a": numpy.random.rand(1000),
     "b": numpy.random.rand(1000)
    }
    )
df["c"] = df["a"] * df["a"] - df["b"] * df["b"]
df.head(10)
```

|a|b|c|
|---|---|---|
|0.274406|0.085777|0.067941|
|0.483689|0.124819|0.218375|
|0.692043|0.373973|0.339068|
|0.975277|0.713221|0.442480|
|0.393246|0.286605|0.072500|
|0.138080|0.913521|-0.815454|
|0.739198|0.741095|-0.002809|
|0.304772|0.397826|-0.065379|
|0.232234|0.011288|0.053805|
|0.522116|0.525214|-0.003245|

aとbが事前に観測できる情報だとして、そこからcの正負を賭け、当たった場合はcの絶対値の金銭を得、外した場合はその金銭を失うというシンプルなゲームです。

# 強化学習環境の実装方法

gymで強化学習環境を作る場合はhttps://github.com/openai/gym/blob/master/gym/core.py#L11
ここstep,reset,render,close,seedメソッドを実装すればよいです。それぞれ実装すると以下のような感じです。

```python

import gym.spaces
import numpy
import pandas

class Game(gym.core.Env):
    metadata = {'render.modes': ['human', 'rgb_array']}
    def __init__(self, df, columns):
        self.df = df.reset_index(drop=True)
        self.columns = columns
        self.action_space = gym.spaces.Discrete(2)
        low_bound = numpy.array([0]*len(columns))
        high_bound = numpy.array([1]*len(columns))
        self.observation_space = gym.spaces.Box(low=low_bound, high=high_bound)
        self.time = 0
        self.profit = 0
        
    def step(self, action):
        reward = calc_profit(action, self.df, self.time)
        self.time += 1
        self.profit += reward       
        done = self.time == (len(self.df) - 1)
        if done:
            print("profit___{}".format(self.profit))
        info = {}
        observation = calc_observation(self.df, self.time, self.columns)
        return observation, reward, done, info

    def reset(self):
        self.time = 0
        self.profit = 0
        return calc_observation(self.df, self.time, self.columns)
    
    def render(self, mode):
        pass
    
    def close(self):
        pass
    
    def seed(self):
        pass

def calc_profit(action, df, index):
    if action == 0:
        p = 1
    elif action == 1:
        p = -1
    else:
        p = 0
    return  p * df["c"][index]

def calc_observation(df, index, columns):
    return numpy.array([df[col][index] for col in columns])
```

## gym.spaces
行動空間や観測空間を設定します。

```python
self.action_space = gym.spaces.Discrete(2)
self.observation_space = gym.spaces.Box(low=low_bound, high=high_bound)
```
1行目は行動として2つ選択できるように設定し、2行目は観測する状態の空間はlow_bound,high_boundの空間を設定しています。spaceの設定方法はほかにもいろいろあり、詳細はここ。
https://github.com/openai/gym/tree/master/gym/spaces

## step
受け取った行動(action)に対する評価値(reward)を計算し、次の時間の状態(observation)を返します。doneは一連のゲームが終了したかどうかを判定するもので、今回はdfの長さの数だけ回したら終了としています。ゲームによっては途中でHPが0になったりタイムアップになったり、そういうものを設定するものです。

## reset
doneがTrueになったときに呼び出されるメソッドで、ゲームの初期状態を返します。

## render, close, seed
renderは可視化用,seedは乱数調整用,closeは処理が終わるときに実行されるやつ？で今回は不要なのでpassにしておきます。

# その他実装
その他の実装に関してはhttps://github.com/shiibashi/qiita/tree/master/4
に書きました。私は学習中にパフォーマンスの最も高いモデルを保存するようにcallbackを実装しています。
https://github.com/shiibashi/sample/blob/master/callbacks.py
元々は
https://github.com/guillem74/DRLDBackEnd
ここで実装していたものを自分用に書き直したものです。kerasのcallbackは
https://github.com/keras-team/keras/blob/master/keras/callbacks.py#L145
に従うように実装します。
学習を繰り返すときにパフォーマンスの最高値が更新されたときに保存するような処理をここに実装しています。

# 予測
学習はfitで行うのですが、予測するときはpredictではなくforwardです。こんな感じで状態を引数にとって行動が返ります。

```
agent_v6.forward([0.5, 0.4])
```

強化学習における学習は格ゲーにおけるトレーニングモードみたいなもので事前にわかっている情報の中で学習しているにすぎず、それが本番の試合で使えるかどうかはforwardで適応可能なモデルかどうか確かめる必要があります。そうすると考える必要があるのはトレモでは当たり前のように決まるコンボが対人では刺さらない、むしろ対策されてカウンターの起点にされる、みたいなことがあるのでそれも学習させるようにactionの選択方法やreward関数を修正したりします。そういったことが理由の1つにあって囲碁のAlphaGoの例にあるように自分自身と対戦するようにしているのではないかと思っていますが実際はどういう背景があるのでしょうか。
例えばactionの選択方法はここにあるように一様分布に従う乱数によって生成されていますが、工夫することで効率的に学習させたり、
https://github.com/openai/gym/blob/master/gym/spaces/box.py#L36
reward関数に正則化項の考え方を組み込んだり行動に対するリスクをペナルティとして取り入れたりすることで実用的にしたりします。

# 最後に
今回はgymについて書きました。ソースコード全体はこちらです。
https://github.com/shiibashi/qiita/tree/master/4