今回は、機械学習の中でも機械が自ら答えを模索していく強化学習について扱っていきます。
今回はQ学習と方策勾配法と呼ばれる2つをNumPyで実装していきましょう。Q学習の詳しい実装は以下の記事でされているので本記事ではQ学習の解説と方策勾配法の解説と実装とをメインに扱っていきます。
強化学習の基本的な考え方もこの記事に掲載されていますので是非1度目を通してからこの記事を見ることをおすすめします。
これさえ読めばすぐに理解できる強化学習の導入と実践 /machine_learning/2017/08/10/reinforcement-learning.html
OpenAI Gymとは
OpenAIと言われる団体が作った、強化学習を試すためのプラットフォームです。この中には様々なゲームが内包され、Python上で簡単に実行できるようになっています。OpenAI Gymはメンテナンスされなくなってしまったため、Gymnasium
というフォークされたプロジェクトを使用します。
今回の強化学習では、その中でCartPoleを扱っていきたいと思います。
CartPole
CartPoleは台座の上に連結されている棒を倒さないように台座を左右に押していくゲーム内容となっています。一定の角度以上棒(Pole)が倒れてしまうとゲームオーバーです。
ゲーム画面は以上のようになっています。
実際は、1
(台座を右に押す)か0
(左に押す)を入力していき、その都度台座(カート)の位置や速度、棒(ポール)の角度と速度が環境変数として返されます。概要は以下のとおりです。
状態
環境から出力される状態 は以下の表の4つの変数からなります。
番号 |
名前 |
最小値 |
最大値 |
0 |
カートの位置 |
-2.4 |
2.4 |
1 |
カートの速度 |
-inf |
inf |
2 |
ポールの角度 |
-41.8° |
41.8° |
3 |
ポールの速度 |
-inf |
inf |
行動
ある状態からとりうる行動は以下の2つになります。
番号 |
名前 |
0 |
左にカートを押す |
1 |
右にカートを押す |
報酬
報酬はポールが倒れない限り1タイムステップ毎に1得られます。
インストールからゲームの実行まで
まずはGymnasium
をインストールしましょう。
以下のコマンドを実行すれば基本パッケージはインストールされます。
$ pip install gymnasium pygame
では次にPythonのインタプリタを起動していきます。
gymをインポート
状態の初期化
CartPoleで利用者がとれる行動は右に押す(1)か左に押す(0)かなのでこれをactionという変数に入れてゲームを実行してみます。
これは各ステップごとに実行しないと様子が動いていかないので注意してください。
では簡単に実行してみましょう。
実行するとすぐに終了してしまうのが分かると思います。gymansiumのCartPole-v1では角度が12°以上左右どちらかに倒れるか、画面半分以上進むとゲームオーバーになってしまいます。
これをこれから学習させて棒が倒れないように台座を動かす方法を習得していきます。
Q学習
Q学習とは
Q学習(Q-learning)とは、状態(s)のときにとる行動(a)によってどの程度の価値があるのかを
示す価値関数Q(s,a)があり、この関数を学習させていきます。
この価値関数がより高い値になる行動を選んでいけばよいというのがこのQ学習の考え方です。
(学習を進める関係上ある程度ランダムな行動を挟んだほうがよいと言われています。この手法を-グリーディー法と呼びます。)
Q学習の最も基本的なモデルではこの価値関数の値をテーブルを使って表していきます。
例えば10個の状態があってそれぞれに2個ずつ行動の選択肢が存在するとすると
10×2のテーブルでこの価値関数を表現することができるわけです。
今回は4つある状態変数をそれぞれ4つに区分していくので状態の全てが通りに分類することができます。
その256通りの状態の中で右に押す場合、左に押す場合それぞれにおける価値を更新していきます。
これらの価値関数の更新は通常以下の式によってなされます。
次の状態の価値観数の中で最大となるものに減衰係数をつけ、次の状態で得られた報酬を足し合わせたものをある一定の比率で足し合わせることで更新されます。
では、実際の実装を見ていきましょう。コード自体は先程リンクを掲載した記事に載っているものですが、それにコメントを加えています。
アルゴリズムの考え方自体は記事を読んでもらえればよくわかると思いますのでそちらに譲ります。
以下のコードをcartpole.py
などの名前で保存し、
で実行することができます。
この実行結果は以下のようになりました。
紹介していない関数があったので紹介します。
連続値を離散値に格納する関数np.digitize
です。これのAPIドキュメントは以下のとおりです。
numpy.digitize(x, bins, right=False)
params:
パラメータ名 |
型 |
概要 |
x |
配列に相当するもの |
基数に当てはめたい値(もしくは値が格納されている配列)を指定します。 |
bins |
1次元配列 |
基数(bins)の配列を指定します。 |
right |
bool値 |
(省略可能)初期値False 右端と左端どちらを範囲に含むかを指定します。デフォルトでは右端を含まない(right==False)ようになっています。 |
returns:
xと同じ形状でそれぞれの値が格納される基数のインデックスを返します。
方策勾配法
次は方策勾配法で同じ問題を解いていきます。
この方法はいくつかのエピソードごとにパラメータを更新していくものでニューラルネットワークと組み合わせてよく使われます(1エピソード毎ではなくいくつかのエピソードをバッチとしてそれらの情報を用いて更新する場合がほとんどです)。
設定された報酬関数の値を最大化するようにネットワークを学習させていくものです。教師あり学習では損失関数を使っていましたが、今回理想とする状態というのがわからない状態ですのでその状態がどれだけ良いものなのかを評価する報酬関数で代用しますがそれ以外の基本的な考え方は教師あり学習とほとんど変わりません。
今回は右に押す確率をobservationで得られた4つのパラメータ(カートの位置、カートの速度、ポールの角度、ポールの速度)を使って学習させていきたいと思います。
今回使っていく報酬はかなりシンプルなものです。1エピソードにおいていくつのステップ耐えることができたかをステップ数で記録し、それが200以上であれば報酬は-1、200を超えなければそのエピソードにおけるステップ数から200を引くという実装にしました。
なのでsエピソード目の報酬は以下のようになります。
今回は扱わない報酬ですが、よく使われる報酬関数の与え方を紹介します。そのステップにおける報酬と、これに減衰率をかけ合わせたnステップ先における報酬を足していきます。
ステップ自体の報酬はステップ数が基本ですがポールが倒れた場合(terminatedがTrueになるとき)は報酬をかなり小さいものにします(Q学習だと-200にしました)。
具体的には以下のがそのステップにおける報酬ということになります。
学習の進め方
それぞれのパラメーターWを偏微分を使って更新していきます。
出力p(x)(コードの中では関数calculate(X, w)
の返り値)、報酬をとすると
誤差逆伝播法を用いて計算しています。
ここでのは学習率です。このあたりの詳しい解説はニューラルネットワークの実装の部分で行っているのでそちらの方を参考にしてください。今回損失関数ではなく報酬を最大化するために学習をすすめていくため、パラメーターに対して偏微分の値を引く のではなく 足す ということ以外はやってることは変わりません。
実装上では、予め値をずらした状態でエピソードを進行させ、そのずらした分をパラメーター自身の偏差として扱っています。
NumPyでニューラルネットワークを実装してみる 理論編/features/numpy-neuralnetwork-2.html
NumPyでニューラルネットワークを実装してみる 多層化と誤差逆伝播法 /features/numpy-neuralnetwork-4.html
では簡単に実装していきましょう。今回はバイアスにあたるパラメーターbは使わないでシンプルなものを使って実装していきます。
ニューラルネットワークは中間層なしの単層構造です。活性化関数は使わず、最後の値が0を超えていれば右に押し、0を超えなければ左に押すというシンプルな手法で試してみます。
これの実行結果は以下のようになります。
これをグラフで示すと以下のようになります。
うまく学習できました。
20エピソードあるバッチを1000回繰り返すのが最大ですが、2000エピソード以内には収束します。
まとめ
今回はNumPyを使って強化学習の問題に取り組みました。
この記事では代表的な手法であるQ学習と方策勾配法を扱いましたが、他にも色々な手法があったり違う実装の仕方も多数存在します。
是非自分なりに実装して色々試してみてください。
参考