概要

強化学習のシミュレーション環境「OpenAI Gym」について、簡単に使い方を記載しました。

類似記事はたくさんあるのですが、自分の理解のために投稿しました。


強化学習とは

ある環境において、自律エージェントが状況を観測しながら行動することを繰り返し試行し、目的を達成するための最適な意思決定を学習する、機械学習の方法。

教師あり学習とは違い、環境から得られる報酬を元に、行動の良し悪しを評価する。

Picture1.png

The Go gopher was designed by Renée French.


OpenAI Gym とは

人工知能を研究する非営利企業 OpenAIが作った、強化学習のシミュレーション用プラットフォーム。

オープンソース https://github.com/openai/gym


OpenAI Gym インストール方法

1. 基本パッケージのインストール

pip install gym

2. 依存ライブラリインストール

OSX:

brew install cmake boost boost-python sdl2 swig wget

Ubuntu 14.04:

apt-get install -y python-numpy python-dev cmake zlib1g-dev libjpeg-dev xvfb libav-tools xorg-dev python-opengl libboost-all-dev libsdl2-dev swig

3. Atari社のGameを動かすライブラリをインストール

pip install 'gym[atari]'

※ その他、全環境ライブラリ

pip install 'gym[all]'

公式ドキュメント


OpenAI Gym 使い方


基本的な使い方


エージェントを動かす環境

makeの引数に環境名(ゲーム名)を指定し、環境インスタンスenvを生成


gym.py

import gym

env = gym.make('MountainCar-v0')

生成できる環境の一覧は以下で取得できる。


gym.py

from gym import envs

envids = [spec.id for spec in envs.registry.all()]

それぞれが何の環境かは公式サイト[*]に記載されている

環境を初期化する


gym.py

observation = env.reset()


reset()にて、初期状態の観測データobservationが取得できる。

現在の環境を描画


gym.py

env.render()



観測データ

MountainCar-v0(山登り)の場合、以下のような配列が観測データとして取得できる。

それぞれの意味はGitHub wikiに記載されている。


gym.py

print(observation)

>> [-0.56957656 0. ]

内容
Min
Max

0
車の位置
$-1.2$
$0.6$

1
車の速度
$-0.07$
$0.07$

観測データの最小値と最大値は以下で取得。


gym.py

# 観測データの最大値

print(env.observation_space.high)
>> [0.6 0.07]

# 観測データの最小値
print(env.observation_space.low)
>> [-1.2 -0.07]


環境ごとに観測データが何を示すかは異なるため、それぞれ公式ドキュメント[*]を参照。

観測データの配列次元は以下で調べられる。


gym.py

print(env.observation_space)

>> Box(2,)


エージェントを動かす

環境インスタンスenvstep()関数の引数にアクションデータを設定する。

MountainCar-v0(山登り)の場合、アクションの値はそれぞれ以下の通り。


内容

0
左へ押す

1
何もしない

2
右へ押す

GitHub wikiに記載されている。


gym.py

# 左

action = 0
observation, reward, done, info = env.step(action)

行動を実施した戻り値として、行動後の観測データ・報酬・ゲーム終了フラグ・詳細情報が得られる。

山登りの場合、詳細情報はなく、報酬は各ステップごとに$-1$と固定。

環境ごとの行動の値は以下で調べられる。


gym.py

print(env.action_space)

>> Discrete(3)

Discrete(3)は、3つの離散値[0, 1, 2]


まとめ


  1. 環境を生成 gym.make(環境名)

  2. 環境をリセットして観測データ(状態)を取得 env.reset()

  3. 状態から行動を決定  ⬅︎ アルゴリズム考えるところ

  4. 行動を実施して、行動後の観測データ(状態)と報酬を取得 env.step(行動)

  5. 今の行動を報酬から評価する  ⬅︎ アルゴリズム考えるところ

  6. 3~5.を繰り返す


Q-Learningで山登りをクリアする

観測データ(状態)から行動を決定し、車を山の頂上の旗まで動かすことをゴールとして、学習を行う。

これは未学習状態で、プレイしたところ。

200ステップを超えてしまうか、旗までたどり着いたら終了(1エピソード終了)。

mountain_car_ini2.gif


Q-Learningアルゴリズム

各状態における、各行動の行動価値を定めた関数を行動価値関数といい、$Q(s,a)$であらわし、以下のアルゴリズムで$Q(s,a)$を更新する。


  1. $Q(s,a)$を適当に定めて初期化

  2. 以下を繰り返す

     2-1. $s$を初期化($s_0$)

     2-2. 以下を繰り返す

       2-2-1. $Q(s,a)$からε-グリーディ法で行動$a$を選択する。

       2-2-2. 行動$a$を行い、報酬$R(s,a)$と次状態$s'$を観測する。

       2-2-3. $Q(s,a)$を更新する

           $Q(s,a) \leftarrow Q(s,a) + \alpha \bigl(R(s,a) + \gamma \max_{a' \in A(s')}Q(s', a') -Q(s,a) \bigr)$

             ※$s′$が終端状態の場合、$ \max_{ a' \in A(s')} Q(s', a')$は $0$

       2-2-4. $s\leftarrow s′$

       2-2-5. $s$が終端状態なら、繰り返し終了。

詳細はこちら参照。

Qiita: DQN(Deep Q Network)を理解したので、Gopherくんの図を使って説明


実装


状態の取得

観測データから取得される、車の位置と速度は連続値であるため、離散値に変換する必要がある。

40個(適当)の離散値に振り分けることとする。


mountain_car.py

def get_status(_observation):

env_low = env.observation_space.low # 位置と速度の最小値
env_high = env.observation_space.high # 位置と速度の最大値
env_dx = (env_high - env_low) / 40 # 40等分
# 0〜39の離散値に変換する
position = int((_observation[0] - env_low[0])/env_dx[0])
velocity = int((_observation[1] - env_low[1])/env_dx[1])
return position, velocity


Qテーブルの定義

車の位置(0, 1,..., 39) × 車の速度(0, 1,..., 39) × 行動(0, 1, 2)の多次元配列を作成し、行動価値の値を格納する。

shapeは(40,40,3)

初期値は全部の要素を$0$とする


mountain_car.py

q_table = np.zeros((40, 40, 3))



Qテーブルの更新

以下のようにQテーブルを更新する。

$Q(s,a) \leftarrow Q(s,a) + \alpha \bigl(R(s,a) + \gamma \max_{a' \in A(s')}Q(s', a') -Q(s,a) \bigr)$


mountain_cat.py

def update_q_table(_q_table, _action,  _observation, _next_observation, _reward, _episode):

alpha = 0.2 # 学習率
gamma = 0.99 # 時間割引き率

# 行動後の状態で得られる最大行動価値 Q(s',a')
next_position, next_velocity = get_status(_next_observation)
next_max_q_value = max(_q_table[next_position][next_velocity])

# 行動前の状態の行動価値 Q(s,a)
position, velocity = get_status(_observation)
q_value = _q_table[position][velocity][_action]

# 行動価値関数の更新
_q_table[position][velocity][_action] = q_value + alpha * (_reward + gamma * next_max_q_value - q_value)

return _q_table



ε-グリーディ法の実装

基本的にはQテーブルを参照して、行動価値が最大になる行動を選択するが、一定の確率でランダムに行動するようにする。


mountain_car.py

def get_action(_env, _q_table, _observation, _episode):

epsilon = 0.002
if np.random.uniform(0, 1) > epsilon:
position, velocity = get_status(observation)
_action = np.argmax(_q_table[position][velocity])
else:
_action = np.random.choice([0, 1, 2])
return _action


メイン処理


mountain_car.py

if __name__ == '__main__':

env = gym.make('MountainCar-v0')

# Qテーブルの初期化
q_table = np.zeros((40, 40, 3))

observation = env.reset()
rewards = []

# 10000エピソードで学習する
for episode in range(10000):

total_reward = 0
observation = env.reset()

for _ in range(200):

# ε-グリーディ法で行動を選択
action = get_action(env, q_table, observation, episode)

# 車を動かし、観測結果・報酬・ゲーム終了FLG・詳細情報を取得
next_observation, reward, done, _ = env.step(action)

# Qテーブルの更新
q_table = update_q_table(q_table, action, observation, next_observation, reward, episode)
total_reward += reward

observation = next_observation

if done:
# doneがTrueになったら1エピソード終了
if episode%100 == 0:
print('episode: {}, total_reward: {}'.format(episode, total_reward))
rewards.append(total_reward)
break



結果

10000回学習後の車の動き。

mountain_car_clear.gif

エピソードごとのトータル報酬の推移。

bokeh_plot.png

トータル報酬が-200より大きいと、山登りをクリアしていることになる。

1500エピソード以降は頻繁にクリアするようになっているが、必ずクリアできている訳ではない。


DQNで山登りをクリアする

DQNでは行動価値関数をネットワーク化させて、学習します。

$R(s,a) + \gamma \max_{a' \in A(s')} Q(s', a')$を教師データと考えて$\theta$を更新することとします。

詳細はこちらQiita: DQN(Deep Q Network)を理解したので、Gopherくんの図を使って説明


DQNの実装

Keras-RLというKerasで書ける、強化学習+ディープラーニングのライブラリがあるので、これを使いました。keras-rl GitHub


mountaincar_dqn.py

from keras.models import Sequential

from keras.layers import Dense, Activation, Flatten
from keras.optimizers import Adam
import gym
from rl.agents.dqn import DQNAgent
from rl.policy import EpsGreedyQPolicy
from rl.memory import SequentialMemory

env = gym.make('MountainCar-v0')
nb_actions = env.action_space.n

model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))

memory = SequentialMemory(limit=50000, window_length=1)

policy = EpsGreedyQPolicy(eps=0.001)
dqn = DQNAgent(model=model, nb_actions=nb_actions,gamma=0.99, memory=memory, nb_steps_warmup=10,
target_model_update=1e-2, policy=policy)
dqn.compile(Adam(lr=1e-3), metrics=['mae'])

history = dqn.fit(env, nb_steps=50000, visualize=False, verbose=2)

dqn.test(env, nb_episodes=1, visualize=True)



DQNの結果

Q-learningよりずっと少ないエピソードで攻略できてますね。

なぜか、ちょっと悔しいです。

dqn_mountain.gif

bokeh_plot_DQN.png


おわり

強化学習のシミュレーションが、ものすごく簡単にできて興奮しました。

理論だけで終わらせず、実装まですることで、より理論の理解が深まるなぁと思ったので、OpenAI Gymを使ってどんどん試していきたいです。


参考

Qiita : ゲームでAIをトレーニングするジム「OpenAI Gym」の環境構築手順 on Mac OS X

GitHub : sezan92/ReinforcementOpenA

*gymのGitHubには公式サイトがOld linksと記載されているので、今後はGitHub上で情報が更新されるのかもしれません。