何かを始めようと思ったとき、まずは「それは前例のあることだろうか」と調べたり、他の人の意見を聞いたりすることがあるかと思います。
過去に同じような事例があると分かった場合、仲間を見つけたような安心感がでてきますね。確かにその事例を参考にすることで行動がしやすくなり、失敗するリスクを減らすことができます。
とはいえ、前例ばかり気にするのはよくありません。前例がないと行動できないのであれば、新しいものを世に送り出すことができません。前例がないからこそ、うまくいけば成功事例として注目されるわけです。
と誰よりも慎重派ビビリ属の僕が申し上げてしまいました。ただ「AI」ならばどうでしょうか。強化学習と呼ばれる手法により、失敗を恐れることなく試行錯誤を繰り返し、成功へと導いていきます。
では今回は、AI強化学習の手法を通して、新しいことへのチャレンジのコツを学んでいきましょう。
強化学習とは
「強化学習」は、赤ちゃんが失敗を繰り返しながら歩き方を習得していくのと似てます。
こちらは、僕の大好きな「物理エンジンくん」さんの強化学習シミューレーション動画です。
- 重心が前進する
- 頭がある高さの幅にある
を「報酬」にして学習していますね。
強化学習を理解するには、「状態」「行動」「報酬」という3つの概念が重要です。
- 状態: 環境が今どのような状態か
- 行動: その状態での実際の行動
- 報酬: その行動によって獲得したスコア
赤ちゃんの歩き方習得を例にすると、「強化学習」では赤ちゃんのことを「エージェント」と言います。赤ちゃん(エージェント)は、まだ歩くことができない状態(=1. 状態)で歩こうとチャレンジし(=2. 行動)、それによってどれだけ歩けたか(=3. 報酬)がより大きくなるように頑張ります。
つまり、「強化学習」におけるエージェントは、「1. 状態」において何かしらの「2. 行動」を起こし、その行動から得られる「3. 報酬」を獲得するという処理を何度も行い、報酬の合計が一番大きくなるように学習していきます。
こちらは、もう一つの強化学習動画です。シュールですね。大好きです。
OpenAI Gymとは
さて、「物理エンジンくん」ほどのシミュレーションはできませんが、OpenAI Gymというオープンソースの強化学習シミュレーション用プラットフォームを利用して、強化学習を試してみます。
山登りチャレンジ
OpenAI Gymの古典的な強化学習環境であるMountainCar-v0で山登りチャレンジをしましょう。
2つの山の間にいる車(エージェント)が、前後に勢いをつけ、坂を登ろうとします。車が右の山の頂上(位置0.5)にあるゴールまで到達できたら、1エピソード終了となります。200ステップ内にゴールまで到達できなかった場合もタイムオーバーで1エビソード終了です。車は、速度が「0」、位置は「-0.6」〜「-0.4」のランダム位置から開始します。
報酬はステップ毎に「-1」(マイナスの報酬=罰)が与えられ、例えば200ステップ内にゴールできずタイムオーバーとなった場合は、トータル報酬は「-200」になります。強化学習は、このもらえる報酬ができるだけ大きくなるように行動する方法を学んでいきます。
1. ライブラリと定数の定義
定数の中の「時間割引率」とは、すぐにもらえる小さい報酬を優先するか、将来にもらえる大きい報酬を優先するかを定義します。せっかち度ですね。「0」に近いほど将来の報酬は無視されて、「1」に近いほど将来の報酬が重視されます。
import gym import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl mpl.use('Agg') # matplotlibがAnti-Grain Geometry (AGG) を使う指定 # 定数 alpha = 0.2 # 学習係数 gamma = 0.99 # 時間割引率(0〜1) epsilon = 0.002 # ε-グリーディ法のεの確率 num_divisions = 40 # 離散化時の分割数
2. 連続値から離散値に変換
連続値とは連続して無限に流れている「時間」のようなもの。また、離散値とは「人数」のように数えることができるものです。連続値である「位置」と「速度」を離散値に変換します。
def get_state(_observation): env_low = env.observation_space.low # 位置と速度の最小値 env_high = env.observation_space.high # 位置と速度の最大値 env_dx = (env_high - env_low) / num_divisions # 分割 position = int((_observation[0] - env_low[0])/env_dx[0]) # 位置の離散値 velocity = int((_observation[1] - env_low[1])/env_dx[1]) # 速度の離散値 return position, velocity
3. Q関数を更新
経験(行動、状態、次の状態、報酬)に応じてQ関数を更新します。これにより、エージェントは時間の経過とともに、最適な行動をとれるようになります。
Q関数は以下のように表します。
]
- :時間tの時の状態
- :時間tの時の行動
- :学習係数
- :に遷移したときに得る報酬
- :時間割引率
- :次ステップの価値を最大化するQ関数
def update_q_table(_q_table, _action, _observation, _next_observation, _reward, _episode): next_position, next_velocity = get_state(_next_observation) next_max_q_value = max(_q_table[next_position][next_velocity]) # 次ステップの価値を最大化するQ関数 position, velocity = get_state(_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
4. ε-グリーディ法
「ε-グリーディ法」という手法を用いて、現在の状態に応じた行動を選択するようにします。1-εの確率で最良の行動、εの確率でランダム行動を選択します。
現状に甘んじず、さらなる最適解を求めてランダムな行動を恐れることなくとるところが素敵ですね。
def get_action(_env, _q_table, _observation, _episode): if np.random.uniform(0, 1) > epsilon: position, velocity = get_state(observation) _action = np.argmax(_q_table[position][velocity]) else: _action = np.random.choice([0, 1, 2]) return _action
5. 動画保存
山登りチャレンジ結果を動画として保存します。
from JSAnimation.IPython_display import display_animation from matplotlib import animation from IPython.display import display def display_frames_as_gif(frames): plt.figure(figsize=(frames[0].shape[1]/72.0, frames[0].shape[0]/72.0), dpi=72) patch = plt.imshow(frames[0]) plt.axis('off') def animate(i): patch.set_data(frames[i]) anim = animation.FuncAnimation(plt.gcf(), animate, frames=len(frames), interval=50) anim.save('movie_mountain_car.mp4') display(display_animation(anim, default_mode='loop'))
6. 学習実行
では、5000エピソードで学習を実行してみましょう。
if __name__ == '__main__': # 環境の生成 env = gym.make('MountainCar-v0') # 初期化 is_episode_final = False q_table = np.zeros((40, 40, 3)) observation = env.reset() rewards = [] history = [] frames = [] fig = plt.figure() # 学習 for episode in range(5000): total_reward = 0 observation = env.reset() if episode == 4999: is_episode_final = True for _ in range(200): # ε-グリーディ法で行動選択 action = get_action(env, q_table, observation, episode) # 行動により、次の状態・報酬・ゲーム終了フラグを取得 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 is_episode_final is True: frames.append(env.render(mode='rgb_array')) # ゲーム終了フラグがTrueになったら1エピソード終了 if done: if episode%100 == 0: # ログ出力 print('episode: {}, total_reward: {}'.format(episode, total_reward)) rewards.append(total_reward) history.append(total_reward) plt.plot(history) break if is_episode_final is True: # グラフを保存 fig.savefig("img.png") # 動画を保存 display_frames_as_gif(frames)
7. 実行結果
5000エピソード目を実行時の動画です。最初は全く登れる気配がなかったのに、強化学習の結果、悠々と登りきっています。
1〜5000エピソードのトータル報酬の推移グラフです。1000エピソードぐらいまでは、トータル報酬は「-200」となっており、200ステップ内で登りきれていませんが、1000エピソード以降はエピソードを積むにつれてより少ないステップで登れるようになっています。知的ですね。
最後に
現在、弊社も新規サービス立ち上げのために試行錯誤しております。強化学習のように、実施した結果をしっかりと評価、反省して次の行動につなげ、成功したとしてもその前例にとらわれず、更なる高みへ向けて果敢にチャレンジしていきたいと思います!
今回、参考にさせていただいた記事は以下です。ありがとうございました。
OpenAI Gym 入門 - Qiita
OpenAI Gym入門 / Q学習|npaka|note