頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


強化学習(DQN)に強引にSceneKitの物理エンジンを使う

強化学習で物理エンジンを使いたいが
↑ここにも書いたんだけど、強化学習(DQN)やる時に物理エンジンを使うと次の時刻の状態や報酬は1通りしか取れないのでだめじゃん?と理解してる。

【強化学習中級者向け】実装例から学ぶDueling Network DQN 【CartPoleで棒立て:1ファイルで完結】
↑Dueling Networkとかあるみたいなんだけど、まだよくわからないのでとりあえずもっとベタな方法を試してみた。

物理エンジンにある状態を与えてdT後の状態を得るというのを繰り返す、というもの。

物理エンジンに下手に初期値を与えると瞬間的に移動するので慣性ですっとんで行ってしまったり期待通りの位置、姿勢になるのに時間がかかるんじゃないの?とか思っていたので、ちゃんとSceneKitで指定の状態を与えることができるのか?というところから確かめた。

与える状態は
位置 position
姿勢 orientation
速度 velocity
角速度 angularVelocity
の4つ。
これをループの最初に振り子(SCNNodeオブジェクト)に与える。
SCNSceneRendererDelegateメソッドのおなじみの
- (void)renderer:(id)renderer updateAtTime:(NSTimeInterval)time;
内で上記の状態を取得する。
このメソッドは適当な時間間隔でコールされるので、きっちりと指定したタイミングでの状態を得ることはできないので仕方なく近似してる。
これでなんとかそれらしい値を取得することができそうだと確認できた。



↑学習時にはこんな感じに見える。ある状態から2種類のアクションをやった場合にどういう状態になるかを得るのでちらついて見える。
負荷を減らすつもりで照明もなし、振り子以外のオブジェクトもなし。
scnscene.physicsWorld.speed
でスピードを早くすることもできなくもないんだけど、2.0にしただけで取得する値がばらつきやすくなったので結局デフォの1.0のままでやってる。
なので計算に時間がかかる(やっぱDueling Networkとか勉強するか?)。

なんとも面倒なことをやってるけど、これがうまくいけば物理エンジンを使わないとできないようなややこしい動き(二足歩行とか)に対しても強化学習ができる可能性が出てくるんだよな。


SceneKitでオブジェクトの速度・角速度が取得できない

SCNNodeオブジェクトの位置・姿勢が取得できない
↑ここで、SceneKitにおいてオブジェクトの位置、姿勢(クォータニオン )を得るのにpresentationNodeを使わないとダメ、という話をしてるけど、今回は速度、角速度を得ようとしてはまったというお話。もちろん物理エンジンを使ってる。

振り子

オブジェクトはいつもの(?)振り子だ。棒の上部をSCNPhysicsHingeJoint使って”世界”に固定してる(支持してる板は飾り)。

このSCNNodeオブジェクトの速度、角速度なので
node.physicsBody.velocity
node.physicsBody.angularVelocity
で取得できると思うやん?

もう半分罠にかかってるで。

結論から言うと
- (void)renderer:(id)renderer updateAtTime:(NSTimeInterval)time;
などのSCNSceneRendererDelegateメソッド内でないと意図する値を得ることができなかった。

例えば、
・画面をクリックした時にコールされるメソッド
・タイマーでコールされるメソッド
なんかでは値がどれもゼロになってしまう。
なんでやあ〜?って叫ぶ前によくリファレンス読んだら書いてた。
さっきの
- (void)renderer:(id)renderer updateAtTime:(NSTimeInterval)time;
などのSCNSceneRendererDelegateのメソッド内はOK。
でもその他のメソッドだと前にsetされた値を返すよって書いてる。

えええ?なにそのトラップ。
確かにセットした後に調べると値はゼロじゃないなあ、、、謎仕様や、、、
まあいいや。深く考えるのはよそう、だってSceneKitだもの(みつを)。


強化学習(DQN)で報酬のクリッピング

強化学習で物理エンジンを使いたいが
↑この続き。
物理エンジンを使わずに4次のルンゲクッタ法なるものを使って振り子の角度、角速度を得られるようにして、いざ学習を進めるも、、、どういうわけかうまくいかない。

ところが、報酬のクリッピングという手法があるらしく、、、

ゼロからDeepまで学ぶ強化学習
↑このあたりを参考に、報酬の高い方を1、低い方を-1としてみたところこれがなかなか効いた。
単に報酬を振り子の高さや角度(ラジアン)なんかよりももっとはっきりと差がつくように与えるといいのだろうか?

Q値更新式
↑そもそもの式との整合性はどうなんよ?と思うけどきっといけるんだろう(理解不能)。


なお、Experience Replayの派生(?)で総報酬がよかったエピソードを残しといて使う、というのは全然効かなかった。似たようなデータが多くなってしまうのがよくないのか?なんとなくいろんなデータを使って学習させる方がいいんじゃね?と感じた。

ちなみに全く同じデータはデータセットから削除してから学習にまわしてた。

あと、損失関数にHuber関数を使うと発散しにくくなるとかなんとか見たので試してみたら、確かにQ値が発散していくのが抑えられたと思う。


まあ単に今回のケースではこうだった、ってだけかも。
ともかく、強化学習(DQN)ってのは難しいということはわかった。



日足チャート画像のCNNで株価予測

PythonのPILで日足チャートを描く
↑こんな感じで作成した日足チャートの画像をCNN使って株価予測をしてた。
225銘柄、919,381枚のチャート画像。データ数も多いので時間かかる。
夜や休日などMacから離れる時にこの学習を進めて約2ヶ月。

現時点でテストデータに対してこんな結果↓

CNNで株価予測結果(修正)

まだ続ければ損失関数の値は下がっていきそうだけど、テストデータに対しての損失関数の値は0.77くらいなのでいつものごとく乖離してきてる。つまり、何の予測もパターン分析らしきこともできていない。

ということで、機械学習で株価予測は撤退。
日計りだとさっぱりダメだったけど、違うスパンだと可能性あるのかなあ?まあ二度とやる気ないけど。


強化学習で物理エンジンを使いたいが

TensorFlow、MPS、SceneKitで強化学習
↑この続き。

SceneKitの物理エンジンを使おうとしたんだけど、肝心のQ関数の更新であれ?となった。

強化学習入門 ~これから強化学習を学びたい人のための基礎知識~ - Platinum Data Blog by BrainPad
↑ここに強化学習でおなじみの↓この式があるんだけど、

Q値更新式

Q(s,a)って見るとなんとなくQ値はsとaを入力したら出てきそう〜って雰囲気だけど、NNではsが入力で各actionのQ値がそれぞれ出てくるよな。
Q(s,a)を更新、学習させるためにデータをたくさん集めないといけないわけだど、各actionのQ値を用意しないといけないことになる。
今回の倒立振子で言うと、
action0 : プラスのトルクを与える
action1 : マイナスのトルクを与える
という二種類のactionがあるとして、ある状態s(ここでは過去4つの振り子の角度)においてaction0,action1の両方のQ値が欲しいことになる。
Q(s,a0)とQ(s,a1)の二つ。
そうしないとTensorFlowに渡すデータセットにならないから。

でもさ、物理エンジン使ってたら、ある時点では片方のactionしかとられへんやん?!
Q値の更新に必要なデータには両方のactionをしたらどういうs’になるねん?っていうのが必要だけど、それが得られへんねん!
物理エンジンで強化学習やってる人はどうやってるんやろね?

単振り子(単振動)の運動方程式(微分)を解く(エクセルを用いたルンゲクッタ法)
↑幸い振り子の運動方程式を解析的に扱う方法があった。
振り子の運動方程式見たら簡単に解けそうなもんだけど、難しいのね、、、
ここにある4次のルンゲクッタ法を使えば微笑時刻後の振り子の角度、角速度を得ることができる!すげー!
{
    float omega0 = angularVelocity;
    float theta0 = angle;
    
    float k1 = MicroDuration * (-G*sinf(theta0));
    float m1 = MicroDuration * omega0;
    
    float k2 = MicroDuration*(-G*sinf(theta0+m1/2.0));
    float m2 = MicroDuration * (omega0 + k1/2.0);
    
    float k3 = MicroDuration*(-G*sinf(theta0+m2/2.0));
    float m3 = MicroDuration * (omega0 + k2/2.0);
    
    float k4 = MicroDuration*(-G*sinf(theta0+m3));
    float m4 = MicroDuration * (omega0+k3);
    
    float k = (k1+2.0*k2+2.0*k3+k4)/6.0;
    float m = (m1+2.0*m2+2.0*m3+m4)/6.0;
    
    
    float newAngularVelocity = omega0 + k;
    float newAngle = theta0 + m;
}
ええ、Objective-Cですが、何か?(と言いつつWWDC2018でSwiftのみのフレームワークを見てぼちぼちSwift勉強しようかなと思ってます)

これである時刻tにおいてどんなアクションをとった場合の角度を計算することができる。
ちなみにトルクを与えた場合には定数のプラスかマイナス角速度を与えた。

これでいける!と思ったんだが、、、そうそう簡単にはうまくいかなかったのです、、、

続く、、、はず







Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.

FC2Ad