強化学習事始め(Open AIのgymを使って手っ取り早く始める)
数式は、一切挟まない解説(と言うか主に実装)です。
強化学習の中でもQ-learningの一番単純な話です。
数式込みでちゃんと理解したい人は以下の記事をお勧めします。
強化学習とは?(What is Reinforcement Learning?)
また、最新の話題も知りたいよって人は以下の2nd editionが良いと思います。
http://webdocs.cs.ualberta.ca/~sutton/book/the-book.html
概要
強化学習とは、環境(Environment)との相互作用を通してエージェントを学習させる手法です。
ポイントは、プログラムの書き手が事細かにエージェントに指示を出さなくても、勝手に学習してくれる点です。
ざっくり書くと、エージェントの行動(action) に対して,状況(state)が変化し、報酬(reward)が支払われ、
エージェントは、試行錯誤の末、将来貰える報酬が最大になるように学習します。
例題(Open AIのFrozenLake)に基づく解説
今回題材として扱うのは、以下のFrozenLake-v0です。
gym.openai.com
環境(env)は、OpenAI側が引き取ってくれるので、エージェントの実装のみに注力できます。
FrozenLake-v0の問題設定
- ペンギンがいて、碁盤の目状の氷の上からの脱出を目指します。
- 碁盤の目は、縦4、横4マスです(state(s)は、16パターン)。
- ペンギンは、上下左右へ1マス移動できます(=action(a)は、4パターン)。
エージェントの学習
- Q(s,a)は、64パターン(16*4)になります。
- Q(s,a)は、ある状態(s)の時にとった行動(a)により得られる(将来に渡る)報酬です。
- ある状態(s)のもと、ある行動(a)をとることで、報酬が得られた場合は、次回もその行動を取りやすくするために、Q(s,a)は増加します。
- 逆に、ある行動(a)をとった次の状態(s+1)で得られる報酬(Q(s+1,a))が、現在の状態(s)で得られる報酬(Q(s,a))よりも小さいのであれば、Q(s,a)を減じてその行動を慎むようにします。
実装 (ここを読んで頂くのが一番手っ取り早いかも)
# -*- coding: utf-8 -*- #!/usr/bin/env python import gym; import numpy as np; import matplotlib.pyplot as plt; #結果をグラフ描画しないなら不要 env = gym.make('FrozenLake-v0'); #環境の読み込み """ 環境(Environment)は、以下の通り。 SFFF (S: starting point, safe) FHFH (F: frozen surface, safe) FFFH (H: hole, fall to your doom) HFFG (G: goal, where the frisbee is located) """ #状態(state)は、16種、行動(action)は、4種 なので、16行4列 Q = np.zeros([env.observation_space.n, env.action_space.n]); lr = 0.7; #学習率(=learning rate) (小さい(0.0)と遅いし、大きい(1.0)と局所解に陥りやすい) disct= 0.99; #報酬のディスカウント(discount) history = []; #グラフにプロットするためなので、グラフ描画しないなら不要 #state :s #action : a for i in range(2000): #2,000回ほど繰り返す。 s = env.reset(); #環境を初期化し、初期状態を返却する (本問題では初期状態は、0) done = False; #学習 for _ in range(300): # 1episode中の試行は、300回まで #選び方は、貪欲アルゴリズム #ランダム(np.random.randn)がエージェントの試行錯誤を表す。 #学習が進むとrandomな行動をとることは慎むように(i+1)で割っている。 a = np.argmax(Q[s,:] + np.random.randn(1,env.action_space.n)*(1.0/(i+1))); #試行が終了した場合は、done = True (goalにたどり着いた or 穴に落ちた) #ゴールできた場合は、reward = 1.0、その他は0.0 s_1,reward,done,info = env.step(a); #今回、infoは利用しない #Qテーブルの更新 (ここが、学習の肝) Q[s,a] = (1-lr) * Q[s,a] + lr * (reward + disct * np.max(Q[s_1,:])); """ 以下のように展開した方が理解しやすい(かも知れない) Q[s,a] = Q[s,a] + lr * ( reward + disct * np.max(Q[s_1,:]) - Q[s,a]); 成功していた場合は、rewardが1.0なので、次回もその行動を取りやすいくなるように、Q[s,a]は、加算される もし、とった行動(a)により、将来の報酬(Q[s_1,:])が減るようであれば、 次回は、その行動は取りづらくなるように、Q[s,a]は減じられる (ただし、報酬が発生するようなら加算に転じることもある) """ s = s_1; if done == True: break; history.append(reward); #ゴールできていれば1.0。その他は0.0 plt.plot(history); plt.show();
結果
結果は、以下の通りです。
横軸は、試行回数(episodeの数)で、縦軸は、成功(1)or失敗(0)です。
はじめの方は、失敗ばかりですが、200回あたりからは、失敗はほぼなくなっていることがわかると思います。
また、700回付近で、スランプ(ちょっと失敗している)が見られますが、以降は安定しています。
ランダムな要素ありで学習しているので、得られるグラフは毎回異なります。