フルスクラッチで単回帰分析をやってみる
機械学習の理解を深める
Pythonにはsklearnという便利な機械学習ライブラリがあります。 ただ、内部の計算に触れることは大事だと思うのです。 というわけで今回は単回帰分析をsklearnを使わずに実装します。
計算の準備
計算式の確認をします。 単回帰なのでパラメータは一つです。 パラメータをaとすると、
上記のようになります。
簡単な計算式ですので、簡単に実装できます。例えば、pandasのデータフレームを使えば、
次のように実装できると思います。
xx = x * x xy = x * y a = xy.sum() / xx.sum()
簡単にパラメータが求められます。 データからの予測値は
上記の式で求めることができます。 実際にデータを扱うときはすこしデータに手を加えて、 値から平均を引いた値を使って計算しやすくするなどの工夫をしたほうがいいと思います。
TensorFlowでMNISTを使ってみる
今回やったこと
TensorFlowのチュートリアルを今回やってみました。 MNISTというかなり有名な数字画像の識別問題をやってみます。
どうやったか?
チュートリアルに倣ってsoftmaxと勾配降下法を使いました。 ミニバッチでランダムにサンプルを選んで学習させてマシンへの負担を減らしています。
PythonでのコードはTensorFlowのチュートリアルにあるので省略します。
結果は92%の識別精度が出ました!
特別なことを特にしていないのにすごい精度ですね。
99%以上の精度を出す方法もあることが知られていますが、
それはDeepLearningを使っているそうです。
MNISTは何回かコード書いて遊んでますが、TensorFlowを使うとコーディング楽ですね。
DeepQ-NetworkでOpenAI Gymに挑戦!
DeepQ-Networkとは
一言で言うと多層のQネットワークです。 Qネットワークに隠れ層を追加して多層化したもので、 今回はExperience Replayというアルゴリズムを使って実装していきます。 メモリにステート、アクション、次のステート、次のアクションを記録していきます。 メモリの上限を超えたら端から消していくDouble Ended Queueと呼ばれる構造を使いたいと思います。
今回はカートポールというOpenAI Gymの有名なゲームに挑戦します。
Pythonでサクサクと実装していきます。 コードを全部載せると長いので省略して書いていきます。
#Q-Networkのクラス class QNetwork: def __init__(self,learning_rate=0.01,state_size=4,action_size=2,hidden_size=10,name="QNetwork"): with tf.variable_scope(name): self.inputs_ = tf.placeholder(tf.float32,[None,state_size],name="inputs") self.actions_ = tf.placeholder(tf.int32,[None],name="actions") one_hot_actions = tf.one_hot(self.actions_,action_size) self.targetQs_ = tf.placeholder(tf.float32,[None],name="target") self.fc1 = tf.contrib.layers.fully_connected(self.inputs_,hidden_size) self.fc2 = tf.contrib.layers.fully_connected(self.fc1,hidden_size) self.output = tf.contrib.layers.fully_connected(self.fc2,action_size,activation_fn=None) self.Q = tf.reduce_sum(tf.multiply(self.output,one_hot_actions),axis=1) self.loss = tf.reduce_mean(tf.square(self.targetQs_-self.Q)) self.opt = tf.train.AdamOptimizer(learning_rate).minimize(self.loss)
#MemoryObjectのクラス from collections import deque class Memory: def __init__(self,max_size=2000): self.buffer = deque(maxlen=max_size) def add(self,experience): self.buffer.append(experience) def sample(self,batch_size): idx = np.random.choice(np.arange(len(self.buffer)),size=batch_size,replace=False) return [self.buffer[i] for i in idx]
#Experience Memory env.reset() state,reward,done,_ = env.step(env.action_space.sample()) memory = Memory(max_size=memory_size) for i in range(pretrain_length): action = env.action_space.sample() next_state,reward,done,_ = env.step(action) if done: next_state = np.zeros(state.shape) memory.add((state,action,reward,next_state)) env.reset() state,reward,done,_ = env.step(env.action_space.sample()) else: memory.add((state,action,reward,next_state)) state = next_state
隠れ層は64個、トレーニング回数は2000回とします。
トレーニングが完了したらいつものように、ゲームを実行してみます。
ゲームのスコアは199.0でした!
最大スコアが200なのでうまく学習してくれたことがわかります。
強化学習は面白いですね!
Q-NetworkでOpenAI Gymに挑戦!
Q-Networkに挑戦してみる
強化学習のQ-NetworkでOpenAI Gymのフローズンレイクに挑戦します。 目標は前回までのQラーニングよりさらにゲームが上手いAIを作ることです。
Q-Networkについて簡単に説明しておきます。
ステートと重みをかけ合わせてQ値を求めます。 Qテーブルの代わりに重みを用いることで、 メモリの節約や計算の高速化ができます。
Q値の目標値と推定値の二乗和誤差を最小にする重みを計算する手法です。
損失関数は下の通りです。
Loss = Σ(Q_target - Q)2
ひとまずコーディングしてみます。 Pythonでサクサク書いていきましょう。
import gym import numpy as np import random import tensorflow as tf env = gym.make("FrozenLake-v0") #tensorflowの初期化 tf.reset_default_graph #変数を設定する inputs1 = tf.placeholder(shape=[1,16], dtype=tf.float32) #重みを正規分布で用意する。分散は0.01で設定 W = tf.Variable(tf.random_uniform([16,4],0,0.01)) #かけ算を行う Qout = tf.matmul(inputs1, W) #Q値の推定値を最大にする値を一つ取り出して予測値に代入する predict = tf.argmax(Qout, 1) #Q値の推定値を入れる変数 nextQ = tf.placeholder(shape=[1,4], dtype=tf.float32) #損失関数 loss = tf.reduce_sum(tf.square(nextQ - Qout)) #勾配降下法。パラメータを更新する頻度は0.1に設定する trainer = tf.train.GradientDescentOptimizer(learning_rate=0.1) #2乗誤差を最小にするように計算する updateModel = trainer.minimize(loss)
ここまでをまず説明します。 input1は1×16のベクトルです。 15個の0と1個の1が格納されています。 ゲームのフィールドのどこにいるかの情報を入れておく変数です。
Wは16×4のベクトルでinputs1と掛け算できる形に作ります。 こいつが重みです。
inputs1とWをかけ合わせるとQ値が1×4のベクトルで得られます。 このベクトルの中身で最大の値をとるindexを1の値にして取り出します。
選んだQ値のアクション(上下左右の入力)を行い、 ステータス、報酬、穴に落ちてないか、次の報酬を予測して、 アクションした後のQ値と比較します。
今選んだQ値とその結果得られるQ値の差が最小になる重みWを求めていくことでAIが賢くなっていきます。
では、学習させるコードを一気に書いていきましょう。
#トレーニングをする。まずは初期化する。 init = tf.global_variables_initializer() #学習用のパラメータ #割引率 y = 0.99 #選択するアクションを決めるパラメータ e = 0.01 #ゲームをプレイする回数 num_episodes = 20000 #トータルの報酬を格納する rList = [] #ネットワークの学習を進めるためのコード with tf.Session() as sess: sess.run(init) #学習させる回数 for i in range(num_episodes): #変数をリセットする s = env.reset() rAll = 0 d = False #何回アクションするか j = 0 while j < 100: j += 1 #これまでのQ値から次のアクションを決める a, allQ = sess.run([predict, Qout], feed_dict = {inputs1:np.identity(16)[s:s+1]}) # ε-グリーティー法、10%以下の確率でランダムにアクションをする if np.random.rand(1) < e: a[0] = env.action_space.sample() s1, r, d, _ = env.step(a[0]) Q1 = sess.run(Qout, feed_dict = {inputs1:np.identity(16)[s1:s1+1]}) maxQ1 = np.max(Q1) targetQ = allQ #ベルマン方程式 targetQ[0, a[0]] = r + y * maxQ1 _, W1 = sess.run([updateModel,W], feed_dict={inputs1:np.identity(16)[s:s+1], nextQ:targetQ}) rAll += r s = s1 #穴に落ちたら閾値を更新して次のエピソードに移る if d == True: e = 1/((i/50)+10) break rList.append(rAll) print("全ゲーム中、どれくらいゴールできたか:" + str(sum(rList)/num_episodes*100) + "%")
全ゲーム中、どれくらいゴールできたか:67.50500000000001%
前回よりかなり賢くなってますね。
ゴールもちゃんとできています。 ゴールするまでにかかる手数も短くなっていて、 Qラーニングより今回挑戦したQ-Networkの方がゲーム上手なAIを作ることができました!
OpenAI Gymで強化学習!
ベルマン方程式
前回の続きです。 OpenAI GymのFrozenLake-v0を攻略して行きます。 Qテーブルを更新するのにベルマン方程式を使うので、 まずはベルマン方程式についてお話しします。
Q(s,a) = r + γ(max(Q(s',a')))
Q:行動価値関数 s:state a,:action
r:報酬(reward) γ:割引率
さて、数式は上記のように書きます。 どういうことかというと、 ゲームのある時点でQ値は次に行うアクションで得られる値の最大値を使います。 この時に得られる値には割引率をかけて、 古いゲームプレイの報酬の影響を減らしています。
ゲームのキー入力に相当するアクションをどう選ぶかがポイントになって来るのですが、 今回はε-グリーディー法という、何度かプレイしてうまくいったアクションを選択し、時々ランダムでアクションする手法を使います。
では早速、Qテーブルを更新していくプログラムを実装しましょう。
実装
#Qテーブルを更新するコード import gym import numpy as np env = gym.make("FrozenLake-v0") #Qテーブルの初期化 Q = np.zeros([env.observation_space.n, env.action_space.n])
ここまででQテープルの準備はできました。 今Qテーブルの中は全部0が入っているはずです。
array([[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]])
Qテーブルを初期化したのでベルマン方程式のパラメータを今度は設定しましょう。
#パラメータを更新するための値 lr = 0.8 #割引率 y = 0.95 #何回ゲームをプレイするか num_episodes = 2000 #得られる報酬の一覧を格納するリスト rList = []
ここまでできたらあとはゲームをプレイさせながらQテーブルを更新するだけです。 一気に行きます。
for i in range(num_episodes): #ステートの初期化 s = env.reset() #報酬の累積和の初期化 rAll = 0 #穴に落ちたか d = False #スタートからゴールか穴に落ちるまで何回アクションするか j = 0 while j < 100: j += 1 #ε-グリーディー法(結果が良かった方法を取り出す) a = np.argmax(Q[s,:] + np.random.randn(1, env.action_space.n)*(1/(i+1))) #アクションに対して新しいステータスと報酬と穴に落ちてるか更新する s1, r, d, _ = env.step(a) #ベルマン方程式を使って更新する Q[s,a] = Q[s,a] + lr * (r + y * np.max(Q[s1,:]) - Q[s,a]) #新しい報酬の総和を更新する rAll += r #新しいステータスに更新する s = s1 #まだ穴に落ちていなければ次の計算を始める if d == True: break #最終的に得られた報酬を報酬のリストに入れる rList.append(rAll)
rListの中には1と0が格納されています。 学習する過程で1の時はスタートからゴールまでたどり着いた時を表していて、 0の時は途中で穴に落ちたかゴールにたどり着けなくてゲームオーバーになったことを表しています。
とりあえず、QテーブルをアップデートしながらゲームをAIにプレイさせたわけですが、 2000回今回はプレイさせましたが、どの程度ゲームクリアできたか見て見ます。
print("回数ごとの結果:" + str(sum(rList)/num_episodes))
回数ごとの結果:0.4325 だいたい43%くらいクリアできてますね。 へたっぴ状態から練習させたからこんなものでしょう。
せっかくなので完成したQテーブルを見て見ます。
#最終的なQテーブルの値 print(Q)
[[3.30403805e-03 1.50916310e-01 4.67828897e-03 4.22277711e-03] [2.87994417e-04 2.36482442e-04 1.14266941e-04 5.24967471e-02] [7.66652742e-04 1.22894047e-03 9.88034171e-04 1.06186835e-02] [1.92752453e-04 6.67114284e-04 3.10096833e-04 4.74819685e-03] [1.70613675e-01 4.20748780e-03 9.26752005e-04 1.33450131e-04] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [4.08413509e-03 4.59097357e-05 5.87518554e-05 3.83861167e-05] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [3.02812052e-03 0.00000000e+00 3.16699706e-03 4.82855578e-01] [4.83901672e-03 7.84723753e-01 0.00000000e+00 0.00000000e+00] [6.29977810e-01 1.39727419e-03 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 1.41025980e-03 9.22835605e-01 3.71121000e-04] [0.00000000e+00 9.98525704e-01 0.00000000e+00 0.00000000e+00] [0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]]
各列は上下左右のキー入力だったのは覚えていますよね。 各行は今いるゲームのフィールド位置ですよ。 値が大きいほどそのコマンドを入力した時に効果的だったことを表しています。
じゃあ、実際にこのQテーブルを使ってゲームさせてみましょう。
#学習したQテーブルを使ってゲームをプレイする s = env.reset() d = False k = 0 while k < 100: k += 1 a = np.argmax(Q[s,:]) s1, r, d, _ = env.step(a) s = s1 env.render() if d == True: break
ゴールにたどり着けてますね。 AIはちゃんと玄人プレイヤーになってくれたようです。
今のQテーブルで100回プレイしたら何回クリアできるかみてみましょう。
#100回プレイして何回ゲームクリアできるか clear_num = 0 for _ in range(100): s = env.reset() d = False k = 0 while k < 100: k += 1 a = np.argmax(Q[s,:]) s1, r, d, _ = env.step(a) s = s1 if s == 15: clear_num += 1 if d == True: break print("100回ゲームをプレイして{}回ゴールまでたどり着けた!".format(clear_num))
100回ゲームをプレイして52回ゴールまでたどり着けた!
この結果は実行するたびに変化しますが50%はゲームがクリアできる子にAIは育ってくれたようです。 Qラーニングについてはここで一つの区切りとします。
次回はQ Networkについて書いてみたいと思います。
OpenAI Gymで強化学習をやってみる
FrozenLake-v0に挑戦!
フローズンレイクというToy Textゲームに挑戦することにしたよ。
どういうゲームかというのはOpenAIの公式リンクを貼っておくので見て欲しいと思う。
簡単にボクからも説明しておく。
まず4×4マスのフィールドをスタート地点からゴール地点まで、
穴に落ちて死なないように移動するゲームです。
テキストで表示されているアルファベットがマスごとのステータスを表しています。
S:スタート地点
F:氷の上で移動できるマス
H:穴。このマスに来ると落ちて死ぬ
G:ゴール地点
解き方を考えるのにQラーニングという手法を使ってみようと思う。
まず作るのはQテーブルと呼ばれるものを作ることにした。
Qテーブルはこのゲームでは16行4列の表を使います。
行はゲームフィールドの位置。
列は上下左右への移動する選択。
もう少しわかりやすく書くと、
0行〜15行はこのゲームの移動できる全てのマスをそれぞれ対応させたもの。
上段左上なら0行、上段真ん中なら1行、上段右上なら2行・・・という感じ。
列はわかりやすいと思う。
ゲームのコントローラーの十字キーをイメージして欲しい。
キーの入力する向きが4パターンなので4列用意するよ。
この上下左右に移動する列を方策と呼びます。
一度まとめ
Qテーブルとは?
移動できるマスを表す行と、
上下左右に移動するアクション(方策)からできる表。
表の中にはQ値(価値)を格納する。
この表のことをQテーブルと呼びます。
Qテーブルをどう使うの?
AIがゲームを進めていってうまく行ったら報酬をあげて、
その報酬の累積和を記録していきます!
なんどもAIにゲームをプレイさせて、
Qテーブルを更新して行きます。
AIにゲームを繰り返しプレイさせることで
AIをゲームへたっぴから玄人プレイヤーにさせること目指します!
Qテーブルを更新する方法にベルマン方程式を使います。
次回はベルマン方程式や実装について書いて行こうと思う。
今回はここまで。
ここまで読んでくれた方、ありがとう!