頭と尻尾はくれてやる!

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


強化学習(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においてどんなアクションをとった場合の角度を計算することができる。
ちなみにトルクを与えた場合には定数のプラスかマイナス角速度を与えた。

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

続く、、、はず





TensorFlow、MPS、SceneKitで強化学習

強化学習で倒立振子をやってみようとした。いわゆるDQNってやつ。
学習にはTensorFlow。
振り子の描画にはMacアプリのSceneKit。振り子の動きはSceneKitの物理演算を使う。振り子を動かす時にNNの出力が必要なのでTensorFlowが学習させた後の係数をMacアプリ側でもらって、Metal Performance Shaderを使うようにしてる。
MacアプリでTensorFlow使って学習させる方法が不明なのでMacアプリと同時にPythonを実行させておき、進行状況を示すファイルを双方で監視し、処理を行ったり来たりするようにしてる。

物理エンジンを使う状況で強化学習をやりたかったのでこんなややこしいことになってる。
面倒だけど他に方法が思いつかなかったので、、、まあ素直に機械学習のライブラリもある(らしい)Unity使えばよかったのかな、知らんけど。
WWDC2018でmacOSでCreate MLなる新フレームワークで学習ができるようになったけど、今回の用途には使えなさそう。

ともかくこんな感じで倒立振子に挑戦したんだが、、、これが予想外に難易度高かった。

倒立振子で学ぶ DQN (Deep Q Network)
↑これ見たら簡単にできそうな気がするけど、実際はとんでもなく難易度が高かった。

いくつかツイートで動画をあげてたけど、、、




とまあ結局挫折しました。
ここまでいろいろとやりましたよ、、、
・層を増やす
・損失関数にHuber関数を使う
・(総報酬の高かった)エピソードを記録しといて学習に使う(Experience Replay)
・報酬のクリッピング
などいろいろと試行錯誤したんだけどダメでした。

他にもやったことがあるのでまた別記事で。







Metal Performance Shaderで同期処理っぽく記述する

機械学習などでMetal Performance Shaderを使ってNNの計算をする時、GPUに渡してそれが終わったら結果が得られるようになるって流れなので、コードの見通しが悪くなる。

a = [self inference:s];

ってな感じで記述できればいいんだけど、実際は

[self inference:s];

ってしといて結果が出た後の処理は別なところに書かないといけない↓
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
    //続きはこんなところ!
}];
なんとかならないのかなあと思ったらsemaphoreなる仕組みがある。

Objective-Cで非同期処理を同期処理にする方法。 | 三度の飯とエレクトロン

↑これを参考にてコードを書き直してみたところ、、、

a = [self inference:s];

という感じの記述ができて意図通り動いた!
計算に時間がかかるとまずい場合もあるかもしれないが、今やってることに対しては計算時間も短く問題なさそう。


コードはざっくりこんな感じ。勝手な構造体なんかは気にしないで。
-(MPSResult_t)inferenceForInputs:(Inputs_t)inputs
{
    __block MPSResult_t mpsResult;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        
        [self->srcImage.texture replaceRegion:self->srcImageRegion
                            mipmapLevel:0
                                  slice:0
                                    withBytes:self->angles
                            bytesPerRow:sizeof(float)*4
                          bytesPerImage:0];
        
        @autoreleasepool{
            id <MTLCommandBuffer> commandBuffer = [self->commandQueue commandBuffer];
            
            [self->h1 encodeToCommandBuffer:commandBuffer sourceImage:self->srcImage destinationImage:self->h1Image];
            [self->h2 encodeToCommandBuffer:commandBuffer sourceImage:self->h1Image destinationImage:self->finalImage];
            
            [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
                
                [self->finalImage.texture getBytes:&self->qResults[0]
                                 bytesPerRow:sizeof(float)*4
                                        fromRegion:self->filnalImageRegion
                                 mipmapLevel:0];
                
                mpsResult.q0 = self->qResults[0];
                mpsResult.q1 = self->qResults[1];
                
                dispatch_semaphore_signal(semaphore);//止めてたのを終える
                
            }];
            
            [commandBuffer commit];
        }
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//これで止める
    
    return mpsResult;//結果が出てからリターンする
}





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

FC2Ad