概要
DQN(deep Q network)を使ったロボットの大車輪運動の獲得に挑戦した。しかし、しばらく中断することにした。レポートを書く。
その後、keras-RLというライブラリを使って一矢報いました。
http://qiita.com/Zy75/items/c38e84958f250291bcf1
2017.10 少し調査したところ、深層強化学習の方法としては、今はA3C(asyncronous advantage actor-critic)が評価されているようです。
はじめに
強化学習の手法であるQ学習を使い、Q関数の近似にディープニューラルネットワークを使ったものをDeep Q Networkという。
[1]、[2]ではDQNを使ってゲームのプレイを学習させている。鉄棒ロボットに大車輪運動を獲得させる研究は、Q
学習を用いたものがある[3]。またブログ的なサイトで実装などを参考にした。[4][5]
[1] "Playing Atari with Deep Reinforcement Learning" Mnih et al 2013
[2] "Human-level control through deep reinforcement learning" Mnih et al 2015
[3] "強化学習を用いたスポーツロボットの大車輪運動の獲得とその行動形態の考察" 坂 井、川 辺、原、豊 田、藪 田 2010
[4] 倒立振子で学ぶ DQN (Deep Q Network) http://qiita.com/ashitani/items/bb393e24c20e83e54577
[5] DQNをKerasとTensorFlowとOpenAI Gymで実装する https://elix-tech.github.io/ja/2016/06/29/dqn-ja.html
強化学習とは
強化学習は、環境の中で最適な行動を獲得するための人工知能の手法です。
ある環境sに置かれたエージェントがある行動aをすると、新しい環境s'が得られ、報酬rをもらう。
将来の報酬も考えて報酬を最大化するのが目的になります。つまり以下の$R_t$を最大化させます。
R_t = \sum_{k=0}^{\infty} \gamma^k r_{t+k+1} = r_{t+1} + \gamma\ r_{t+2} + \gamma^2\ r_{t+3} + \cdots
$\gamma$は0から1の数で、遠い将来よりも近い将来を重視しようという目的で乗算しています。$\gamma^n$はnが大きいほど小さくなる。
鉄棒ロボットの例では、ロボットがエージェントで、環境の状態は体の角度や角速度で表せます。行動は腰を駆動するモーターの発生するトルクです。報酬は自分で決めることができて、大車輪を獲得させるには、例えば(普通の符号つき)角度を報酬にしてうまく行けばいいです。
Q学習
強化学習の中にQ学習という方法があります。
Qという関数があって、環境sで行動aを取った時の将来にわたる報酬$R_t$の期待値を表します。最適な方策(行動)を取った時のQ関数を最適Q関数と言います。
この最適Q関数(Q*)を近似する方法がQ学習です。最適な方策(行動)は$\rm{argmax}_a Q^* (s,a)$で求まります。
このQ関数の学習は以下のように行います。
Q(s,a) \leftarrow Q(s,a) + \alpha \Bigl(r + \gamma\ \max_{a'} Q(s',a') - Q(s,a)\Bigr)
この式の意味を自分なりに解釈すると、まず現在得られた(1タイムステップの)報酬rが大きければQ値は上方修正されます。また、次の環境の状態s'でのQ値がカッコの中の第二項にあります。これは次に評価が高い状態があれば、その直前も評価を上げるということです。直前もその状態にたどりつく可能性が高いからです。このように後に評価の高い状態があると、前へ前へと伝播していきます。
別の見方として、まず、$r + \gamma\ \max_{a'} Q(s',a')$は、次の環境s'がえられたあとの報酬の期待値なので、ひとつ先の状態が分かったあとの(将来にわたる)報酬の期待値です。
そして、Q(s,a)はいままで思っていた報酬の期待値なので、$\Bigl(r + \gamma\ \max_{a'} Q(s',a') - Q(s,a)\Bigr)$の式は、一つ先が明らかになったとき、報酬が今まで思っていたより良かったのか表し、Qの更新式は、期待が上方修正されればプラスに、下方修正されればマイナスに操作しています。
Deep Q Network
Q関数を近似するときに、ディープニューラルネットワークを使うとDeep Q Networkになります。ディープニューラルネットワークは、図のようにニューラルネットワークが何層にもなっていて深い=ディープので、そう言います。
以下のようにターゲットを設定し、
target = r + \gamma\ \max_{a'} Q(s',a')
以下の誤差関数Lを小さくするようにニューラルネットのパラメータを更新していきます。[2]によるとtargetはしばらく固定して時々,
現在のQネットで置き換えます。Lを小さくするように学習させるとQの更新式と似た効果があります。
L = \frac{1}{2}( target - Q(s,a) )^2
鉄棒の大車輪
鉄棒選手が体をのばして鉄棒軸のまわりを回転するのが大車輪です。ロボットにDQNを使って学習させるのが目的です。
実機とシミュレータの両方でやってみました。
ロボットは2つの剛体でできた2リンク構造で、腰にモーターが入っています。鉄棒をにぎる手の部分はパッシブ(受動的)でモーターはつけません。
シミュレータを作るときに、力学方程式が必要になります。方程式を以下に掲載します。
シミュレーションでは、モータの質量や慣性モーメントはゼロとします。I1,I2は重心まわりのリンクの慣性モーメント、lc1はリンク1の重心の鉄棒軸からの距離、lc2はリンク2の重心の腰の関節からの距離、l1は腰の関節と鉄棒軸の距離です。$\tau$はモータがリンク2に及ぼすトルクで符号を反転するとリンク1が受けるトルクになります。$\theta_1$は、上半身(リンク1)の角度で、ぶらさがった状態をゼロとします。$\theta_2$は腰の角度で、まっすぐをゼロとします。( $\theta_1+\theta_2$が下半身(リンク2の角度))
\ddot{\theta_1}=-\frac{d_2 \ddot{\theta_2}+\phi_1}{d_1}
\ddot{\theta_2}=\frac{\frac{d_2 \phi_1}{d_1}-\phi_2+\tau-m_2 l_1 l_{c2} \dot{\theta_1}^2 sin \theta_2}{m_2 l_{c2}^2+I_2-\frac{d_2^2}{d_1}}
d_1 = m_1 l_{c1}^2+m_2(l_1^2+l_{c2}^2+2 l_1 l_{c2} cos \theta_2) + I_1+I_2
d_2=m_2(l_{c2}^2+l_1 l_{c2} cos \theta_2)+I_2
\phi_1= (m_1 l_{c1}+m_2 l_1) g sin \theta_1 -m_2 l_1 l_{c2}(2 \dot{\theta_1} \dot{\theta_2}+\dot{\theta_2}^2) sin \theta_2 +\phi_2
\phi_2 = m_2 l_{c2} g sin(\theta_1+\theta_2)
方程式に誤植があるかもしれないので以下のコードと比較してみると良いです。
https://github.com/openai/gym/blob/master/gym/envs/classic_control/acrobot.py
R.Sutton著 強化学習 にも載っています。
ラグランジュ方程式
シンプルに運動方程式を立てる方法では、束縛力を消去して求めるが、その他にラグランジュ方程式でもやってみた。
ここが参考になった。以下は補足。
ラグランジュの運動方程式の導出
$q_i,\dot{q_i}$ は剛体の角度と角速度で良い。
$r_a$は、剛体を構成する粒子(質点)の位置と考えると分かってくる。
言いたいことは、手と腰の束縛力はラグランジュ方程式から無視できること。
まず、鉄棒の軸が手を束縛(拘束)する力は、手が動かないので仕事をしない。
鉄棒ロボットの腰の関節の束縛力がする仕事は、全粒子で足すと打ち消してゼロになる。
なぜなら、剛体を構成する2つの粒子が力を及ぼしあうとき、2つは同じように動き、作用反作用により、大きさが等しく向きが逆の力になるからだ。つまり以下を全粒子で足すとゼロになる。
\sum_{j=1}^{3} F_j \frac{\partial x_j}{\partial q_i}
また、手にかかる束縛力にトルクはない。
すると、手と腰の束縛力はラグランジュ方程式から無視できる。
腰のトルク(モータが発生)は、ラグランジュ方程式にそのまま加えればいい。(上の式を計算するとトルクそのものになるから)
重力は、ポテンシャル項に加える必要がある。
振り子ロボット
大車輪ロボットの他に簡単そうな例で、モーター軸に物体をぶら下げたをつけたロボットをシミュレートしました。
(m l_c^2+I)\ddot{\theta}=-m g l_c sin \theta + \tau
大車輪がうまくいかなかったのでデバッグ用に使った。
実験
実機
アルミフレームで作った2リンクロボットでやってみました。腰に相当する場所にサーボモータをつけてあります。
Experience Replayという方法が一般的のようで、サンプルをメモリーにためていって、そのメモリーからランダムにミニバッチを取り出してニューラルネットを学習させます。
状態は、過去4コマのリンク1、2の角度、角速度なので、全部で16個の変数で構成しています。
報酬rは上半身(リンク1)の角度や、足先と鉄棒軸を結ぶ線の角度などいくつか試しました。
しかしうまく行かず、一秒間で10回しかサンプリングと学習ができないのでシミュレータでやることにしました。
https://github.com/Zy75/giant_swing_dqn/blob/master/gs_robotA.py
シミュレータ
どこかで読んだ内容ですが、すべての(環境の)状態を十分サンプルできれば、最適なQ値への収束が証明されているというので、
Experience Replayはやめました。ERではぶらさがっている初期位置の近くしかサンプルできないからです。
ランダムな状態を用意してモーターのトルクを決めて行動をとり、次の状態と報酬が帰ってくるので、それで学習ができます。
これなら、幅広い状態のサンプルが取れます。(1タイムステップしかシミュレートしない)
状態は現在の1コマを使いました。現在のリンク1と2の角度角速度だけで、十分だと思ったからです。
数値計算の誤差
やってみて振幅が増加していって成功したかに見えたのですが、モーターのトルクは常に右回転最大値で、おかしいと思いました。
結局わかったのは、数値計算の問題で振幅が増加していたということです。
以下はダメのようです。
THdot_{nxt} = THdot + THdotdot * TSTEP
TH_{nxt} = TH + THdot * TSTEP + THdotdot * TSTEP * TSTEP / 2
次もダメです。
THdot_{nxt} = THdot + THdotdot * TSTEP
TH_{nxt} = TH + THdot * TSTEP
結局以下はOKのようなのでそれを使いました。
THdot_{nxt} = THdot + THdotdot * TSTEP
TH_{nxt} = TH + THdot_{nxt} * TSTEP
https://github.com/Zy75/giant_swing_sim_dqn/blob/master/gswing_sim.py
うまくいかない
ランダムで状態を用意すると学習に時間がかかると思いました。サンプルする範囲を狭くして密度を上げてもダメでした。 より簡単な振り子の問題でデバッグしてもだめでした。
ある確率eでランダムに行動し、1-eの確率で出来上がったQ値を使うというeグリーディ法があります。これだと、だんだんと最適なQ値に近づく束のように、必要なサンプルの密度を上げられるかと思い、またExperience Replayに戻しました。(ある程度の時間継続してシミュレートする)
Experience Replayで振り子
モータの軸に物体をぶら下げた1リンク振り子でシミュレートでやることにした。
はじめ大車輪のように振り子を一回転させようとしたが、やはりうまくいかない。
たまにできるけど、thプラスで右トルク、thマイマスで左トルクにすれば、簡単に振幅が大きくなり一回転する。偶然こうなることがあり再現性がなく、学習が進むとダメになるという結果が出た。
高さを報酬にして振り上げ
偶然できてしまうため、振り子の一回転はテーマとして良くないので、先端の高さを報酬にして、真上で止めるのを目標にすることにした。
しかし上で止めることはできなかった。
参考にしたサイトを再掲。
[4] 倒立振子で学ぶ DQN (Deep Q Network) http://qiita.com/ashitani/items/bb393e24c20e83e54577
自分の実装:
https://github.com/Zy75/giant_swing_sim_dqn/blob/master/pend_sim_er.py