読者です 読者をやめる 読者になる 読者になる

クーの自由研究

かえるのクーが素朴な疑問を実験して報告します。

自己符号化器の自由研究(その9)学習率を学習途中に変化させてみます

 

はじめに

自己符号化器(AutoEncoder)の学習中にハイパーパラメータである「学習率」を変えて学習結果がどのように変化するのか実験します。

実はAdamという勾配降下法の最適化アルゴリズムを実装していました。ボク的にはちゃんと作っているつもりで、「それなり」に動作するのですが、「中盤以降」収束してしまい実質的にパラメータ固定となりました。どこが悪いのか、とんと見当もつきません。

長らくブロクを書いておりませんが、この期間、ボクのブロク始まって以来最高に「いっしょーけんめい」考えていました。思考錯乱試行錯誤の連続でしたが、結果は敗北に終わりました。

自由研究なので、失敗の記録公開もそれなりに意義があるとは思うのですが、何かを決定的に取り違えている感があり、恥ずかしいのでこのソース公開はしません。さらに思い切って最適化「アルゴリズム」からは一時撤退します。

悔しいので、「深層ならともかく、単層の自己符号化器は最適化アルゴリズムでもそれほど改善しないのさ」と負け惜しみを言っておきます。

おおまかな内容

計画を少しかなりかえて、「論理より実践あるのみ!」にシフトしてしばらく人海戦術します。(いやシフトせずとも、ブログにはもともと論理はなかったんでした。でもボク自身はひといちばい論理の理解に憧れているんです。)

気をとりなおして、今回は「学習率」をどのように変動すれば、最終的によい学習結果が得られるか。を実験します。いままでの実行全般を通して「固定」の学習率を「実際にやってみて」決めていました。今回は、学習途中に「学習率」パラメータを「変動」させるようにプログラムを変更して、ひたすらプログラムを回してパラメータを決定していきます。

(「それって結局最適化じゃん」との突っ込み期待)

なぜやるのか・どうなると思うか

一般的に学習率を変える場合は最初は学習率を多めにし、終盤では学習率を少な目にするとよいといわれています。学習率が高い初盤では局所ローカル最適に陥りにくく、早く学習が進みます。ところが学習率が高いと一般的には小さな領域である「本当の最適」箇所への学習到達が困難になるようです。ヘアピカーブやシケインではF1といえども、力いっぱい減速しないとクラッシュします。(たとえになってないか)お料理の味付けも、最初は調味料をそれなりにいれたあとは、ちょっとずつ微調整です。多すぎたら水や材料で薄めたりの調整もします。(このたとえもいまちか)学習率を低くすれば、最適箇所へアプローチするのがより確実になります。ところが初盤・中盤から学習率が低いと学習全体がとても遅くなります。

このような調整を(学習率だけではないですが)自動的にやってくれるのが学習のオプティマイザです。オプティマイザ実装がうまくできなかったボクは、ひたすら実験で(対象はMNIST一択ですが)適した学習率の変動を求めます。

本当に説明のとおりスタートは学習率が高めでいいのでしょうか。目的地付近ではどれくらい減速すればいいのでしょうか?

どうなるのか「ボク、とっても気になります。」

準備と実験のやり方

従来はパラメータを少しだけ変え、1回実験してそのパラメータにより変更前とどのようにかわるかを確認して「表」にし、その傾向から最適と思われるパラメータをボク自身が選んでいました。どのあたりが良い値かが全くわからないので、1つのパラメータを決定するのに100回くらい実験しないといけないという状況でした。

今回は従来のやり方ではそれのさらに10倍から100倍以上の実験が必要になります。おおまかな値までは1回1回の実験により求めたのですが、さすがに1日中やってもそんなに進まないので、まとめて実行できるよう(そしてパラメータ決定はロジックで実行=流しっぱなしにしておくと最適値をきめてくれる)に考えました。

基本方針

パラメータの変更タイミングは、ボクの実験プログラムで採用している「Period*1」(Epochの2**n回のマイルストーン:0,1,2,4,8,16...)です。

プログラム的には n=20 (2**20= 1,048,576 Epoch)までの重みW、バイアス b のそれぞれのパラメータ計40点を準備します。よく使うn=10までを実測導出して、n=11以降は10までの傾向から想定して決定します。(本当はn=20まで測定したいですが、1Epochが2分としても4年くらいかかります。(しかも1回の実験のみで!)なのでやむなく「想定」になります。実際の利用や実験ででそこまでまわすことはまずないので評価も難しいんですが。。。どちかというとボクの実験は終盤重視(精度探求)ではなく、初盤重視(学習立ち上がりのしくみ探求)です)

おおまかな導出値を中心にして、各パラメータ点につき-0.05~中心値~0.05 を0.01単位で11種類の実験測定します。測定にはコストではなくイメージ差の導出値を使用します。(「コスト」(=誤差)よりも学習に対する傾きが高いため今回の判定に適しています)

単純に考えても n=10 まででWとbの20点 で組み合わせ的に11点の20乗になるので、日が暮れるどころか、人生終わるどころか、子孫末代まで実験しても厳しいです。

なので、掛け算でなく、足し算の考え方で実験します。

Wのn=1 で最適(たとえばPeriod:11=Epoch:1024*2をもとめた値で、bのn=1で最適を求め、Wのn=2での最適を求め、...b=10を求めていきます。そうすると最初に求めたWのn=1での値は最適値ではなくなるので、もう一度やっていき、次第にいい感じの「学習率セット」に近づいていくはずです。

その方法では、20(*11)+20(*11)+20(*11)+20(*11)で4ローテーションすれば880回くらいの実験になりますが、そこそこの値にはなると考えます。1回2分として29時間で、精度をあげて1回10分の実験とすると、6日で終わります。1ローテーションごとに精度をかえて実験するつもりなので、まる2~3日かかる見込みです。シグモイド活性化関数以外でも順次やっていくつもりです。(苦労の割に実りがすくないのは予感していますが、実験なので割り切っています。)

実験の結果

ほぼ1日中マシンをまわしているところです。ブログ更新したのははるか昔になってしまい、そろそろ意地でも更新したいころあいなので、実験中の途中結果ではありますが報告をします。

中間結果

実験途中の値です。(例)

...

editPeriod = 5, Wparam = 0.45, WParamOffset = -0.05, befor_img_diff = 897.54, img_diff = 897.31
editPeriod = 5, Wparam = 0.45, WParamOffset = -0.04, befor_img_diff = 897.54, img_diff = 896.26
editPeriod = 5, Wparam = 0.45, WParamOffset = -0.03, befor_img_diff = 897.54, img_diff = 896.20
editPeriod = 5, Wparam = 0.45, WParamOffset = -0.02, befor_img_diff = 897.54, img_diff = 897.58
editPeriod = 5, Wparam = 0.45, WParamOffset = -0.01, befor_img_diff = 897.54, img_diff = 898.34
editPeriod = 5, Wparam = 0.45, WParamOffset = 0.00, befor_img_diff = 897.54, img_diff = 898.84
editPeriod = 5, Wparam = 0.45, WParamOffset = 0.01, befor_img_diff = 897.54, img_diff = 900.65
editPeriod = 5, Wparam = 0.45, WParamOffset = 0.02, befor_img_diff = 897.54, img_diff = 902.48
editPeriod = 5, Wparam = 0.45, WParamOffset = 0.03, befor_img_diff = 897.54, img_diff = 903.81
editPeriod = 5, Wparam = 0.45, WParamOffset = 0.04, befor_img_diff = 897.54, img_diff = 904.87
editPeriod = 5, Wparam = 0.45, WParamOffset = 0.05, befor_img_diff = 897.54, img_diff = 905.97
Update editPeriod = 05, WParam = 0.45, WParamOffset = -0.03, best_img_diff = 897.54, img_diff = 896.20

...


alphaParams  = [0.05, 0.45, 0.41, 0.35, 0.29, 0.42, 0.38, 0.41, 0.29, 0.32,
0.26, 0.24, 0.23, 0.21, 0.19, 0.18, 0.16, 0.14, 0.13, 0.11]
alphaBiasParams = [1.00, 1.01, 0.61, 0.66, 0.71, 0.73, 0.72, 0.75, 0.73, 0.74,
0.72, 0.71, 0.70, 0.69, 0.67, 0.66, 0.64, 0.62, 0.60, 0.58]

更新途中の学習率パラメータの内容です。

グラフ

シグモイド活性化関数での実験途中の値を使ったグラフです。実験により各点(その区間での最適パラメータと思われる値)が微妙にあがったりさがったりしています。

f:id:np2LKoo:20161009214359p:plain

中間結果続報(事件発生

もう結果を発表して締めくくるはずの時期ですが、事件が発生しており再調整中です。

忙しくて3日ほど放置プレイでループさせていました。(毎回4096Epochを実行して、フロイドローズ式チューニング(仮称)していく自動プログラム)なんと指標値が2桁の値になっているではありませんか!65,536Epoch実行してものイメージ差の指標値(img_diff)は100ノードでは700をきることは一度もなかったのです。おお!これは画期的なことだ!と一瞬よろこびしました。見たことない良い数字がならんでいます。(表示フォーマットは前回に比べすこし見やすく改善)

Update editPeriod = 11, oldbp = 0.82, newbp = 0.82, old_best = 62.62, new_best = 62.62
17:42:20:editPeriod = 12, Wp = 0.24, WpOffset = -0.03, org_iDiff = 62.62, new_iDiff = 110.44
17:44:58:editPeriod = 12, Wp = 0.24, WpOffset = -0.02, org_iDiff = 62.62, new_iDiff = 94.66
17:47:37:editPeriod = 12, Wp = 0.24, WpOffset = -0.01, org_iDiff = 62.62, new_iDiff = 75.72
17:50:15:editPeriod = 12, Wp = 0.24, WpOffset = 0.00, org_iDiff = 62.62, new_iDiff = 216.76
17:52:53:editPeriod = 12, Wp = 0.24, WpOffset = 0.01, org_iDiff = 62.62, new_iDiff = 71.51
17:55:30:editPeriod = 12, Wp = 0.24, WpOffset = 0.02, org_iDiff = 62.62, new_iDiff = 72.34
17:58:08:editPeriod = 12, Wp = 0.24, WpOffset = 0.03, org_iDiff = 62.62, new_iDiff = 137.95
Update editPeriod = 12, oldWParam = 0.24, newWParam = 0.25, old_best = 62.62, new_best = 71.51
18:00:46:editPeriod = 12, bp = 0.78, bpOffset = -0.03, org_iDiff = 71.51, new_iDiff = 75.94
18:03:25:editPeriod = 12, bp = 0.78, bpOffset = -0.02, org_iDiff = 71.51, new_iDiff = 68.61
18:06:02:editPeriod = 12, bp = 0.78, bpOffset = -0.01, org_iDiff = 71.51, new_iDiff = 170.31
18:08:40:editPeriod = 12, bp = 0.78, bpOffset = 0.00, org_iDiff = 71.51, new_iDiff = 71.51
18:11:19:editPeriod = 12, bp = 0.78, bpOffset = 0.01, org_iDiff = 71.51, new_iDiff = 147.96
18:13:57:editPeriod = 12, bp = 0.78, bpOffset = 0.02, org_iDiff = 71.51, new_iDiff = 148.59
18:16:36:editPeriod = 12, bp = 0.78, bpOffset = 0.03, org_iDiff = 71.51, new_iDiff = 148.21
Update editPeriod = 12, oldbp = 0.78, newbp = 0.76, old_best = 71.51, new_best = 68.61

...

が、

この3ヶ月弱の経験上から、何かの間違いであるに違いないことをすぐ悟りました。局所ローカルというには数字があまりに良すぎます。局所ローカルでないとするとたぶん「過学習」です。

プログラムを止めたので詳しい状況はわからないのですが、得られたパラメータで改めて実行すると淡い期待もむなしく、いつもの800台の指標値にしかなりませんでした。さらに改めて全く同じ条件でやっても単発ではimg_diffの値が普通であることから、以下の予想をしています。

・実験の途中からなんからの影響で乱数初期化が動作しなかった

・そのためある時期以降毎回同じ順序で学習した(といっても6万件ありますが)

・4096Epoch目の処理においてimg_diffを評価している箇所が固定され、その部分がいつも評価されていた。(高速化のため6万件すべての値をエンコード、デコードして指標値を求めているわけではなく、速度とのかねあいで約1000件のデコード結果の平均で評価しています。)

・この箇所「のみ」の値が良くなるようパラメータが調整されていった。(特定エリアの数字のみ「ほぼマル覚え=特徴抽出ではなく過学習状態」と思われます。)

・この状態にとらわれたので、パラメータが最適値に収束したようになり以降のパラメータ更新が実質的にされていなかった。

プログラムの初盤は乱数が働きうまく動作しているので、本当に予想とおりか不明です。実行途中からデバッガを動かすこともできないので、迷宮入りにします。

すすんでいると思ったパラメータ調整はこのおそらくは「過学習」のポイントにとらわれ、それ以降更新されていませんでした。仕切り直して実験をすすめます。(まぁ、とっても見つけにくいバグが表面化しただけの可能性もありますが。。。)なお、肝心のWとbの学習率のチューニング傾向には大きな変化はありません。

まとめ

予想に反して、Wの学習率については「極初期においては学習率はかなり低めのほうが最終的には学習状態がよくなる*3」という結果がでました。(極初期は具体的には10バッチ程度です)発散・局所ローカル被捕捉防止の観点から、学習パラメータは0.05~1.00付近を限度としていますが、重みWの学習係数(α)は下限まで振り切れました。逆にバイアスbの学習係数(α_b)は上限の1.0付近の上限に振り切れました。前回(その8)のバイアスbの学習係数は別にしましたが、「重みWの学習係数とバイアスbの学習係数は連動したほうがよさげ」の予感ははずれました。完全に別係数とします。

(追記)極初期の重みWの学習係数はもっと小さく、バイアスbの学習係数はもっと大きくなる傾向があるため、リミッタを拡大して実験中です。ただ、そうすると値がとてもぶれる(=何回やっても収束の方向にいかない)のでほぼ中央値を採用していくことになりそうです。

わかったこと・わからなかったこと

「極初期においてはWの学習率はかなり低め、bの学習率は高めのほうが最終的には学習状態がよくなる」ことはこの実験の成果といえます。この内容が一般的なことなのか、当実験条件だけにあてはまることなのかは今後の課題です。(最終結果としては微小な差(ホントにスズメの涙)なのですが、しくみの理解、今後の改善やアルゴリズムの改良にはとても有用な情報と思っています)

現在実験中の状況では(データに依存しているためか中盤の)パラメータの分散はかなりあります。最終的には極初期はかなり低め、初期は高め、中~終盤はジョジョに低めの値に近づいていっています。(シグモイド関数以外でも同様になるのか乞うご期待)

感想・思ったこと・考えたこと

学習の立ち上がりの「極初期」は、Wの表示ではノイズにしかみえませんが、復号結果は学習前と各段の違いがあります。指標値(の差)をみると「もっとも学習している時期」ともいえます。まだ基底ベクトル(テンソル?)がほぼランダムなこの時期の重みづけは、それ以降の学習傾向(特徴抽出)にとても大きな影響を与えると思われます。
この時期に学習率が高いと「強い方向付け」あるいは「トラウマ」になるのではと考えます。
この方向性をキャンセルしたり平滑化したり調整するのに多少なりのコストがかかるのだと思います。
10バッチもすれば、平均的な傾向がまあまあ出揃うと思われ、ここからは急激に学習していってよい感じです。(発進はタイヤが確実にグリップするまではほどよく緩めで、そのあとはベタ踏みOKです)

adamがうまくいけば、次はVAE(変分オートエンコーダ:Variational Autoencoder)に進むつもりでしたが、まだできるスキルがないことを十分自覚したので路線変更します。
次は学習カリキュラム(学習順序)による学習効率についてテストをしたいと考えています。(が、当実験でパラメータをもう少しだけ調整していきます。) 

フロイドローズってチューニング難しいけど、音が狂いにくいんです

ギターのブリッジ(弦を固定するためのボディ側の部品)に「フロイドローズ」という種類があります。1980年代に大流行したパーツで、特許が切れライセンス品が安価になった最近では、また多くなってきたとのことです。ヘビメタ系の方や一部の速弾き系の人に好まれるようです。((追記)速弾き系の方の本命は弦高を抑えた、スキャロップ加工ストラト系とのことでした)
ボクはフロイドローズブリッジのギターを持っていますが、チューニングが半端なく面倒です。
1本の弦をあわせると、他の弦のチューニングが狂い、他の弦をあわせると、最初に調整した弦のチューニングがずれるそして、その弦を合わせるとまた。。。
というものです。しかも弦の高さも調整したい場合は超絶たいへんになります。(チューニングするとブリッジが浮いてくるので弦の高さが変わる)
実際のチューニングに興味がある方は「こちら」をご覧ください。
よいところは以下です。
「苦労してチューニングを合わせたあとはチューニングが狂いにくい」
「温度変化で弦が伸び縮みしても(他のブリッジよりは)音が狂いにくい」
「アーミング(ブリッジに付いたアームで弦を伸ばしたり縮めたりして音程を変える)を多用してもチューニングが狂いにくい」
欠点は
「1本弦が切れると、全部の弦のチューニングが極端に狂う」
です。

音については好みの領域ですが、すっきりした音が出てエフェクターの乗りがとっても自然です。ディストーションやオーバドライブが本当にきれいにかかります。
今回の学習率パラメータの調整はまさにこの「フロイドローズ」ブリッジのギターチューニングに通じるものがあると感じました。

さて、シグモイド関数のパラメータ調整がおわったら次はReLUなどを順次やっていきます(つづく)

(追記)PRSのギターを持ているわけではないです。むちゃくちゃ欲しいだけです。

実験は条件を変えながらやっているのでまだおわりません。条件を変えてもおおまかな傾向は変わっていないです。重みWの調整をする前にバイアスbを調整したがります。(バイアスを適切にしてから本格的な学習を進めたほうが、イメージとしても良い感じはします。Wの初期値はランダムなので、まずはそれで最適になるようバイアスを導出(学習)してから、Wの学習をすすめるのは理に適っている「感じ」がします。ここで数式をかいて、ほらこの通り!と言えればむちゃかっこいいんですがねぇ。)

あと、余談ですが、よくバイアスとして「1」を層の横に書いてつなげるだけの図がありますが、あれは嫌いです。その図ではWとBは同列に思え、ボクも含め初心者は単なる便宜上の入力データであると見誤りかねません。計算的には図の通りなのですが、WとBはエンコード及びデコードにおける役割について全く別物ですのでボクのように混乱しないでくださいませ。せめてバイアスの部分は図形や線をかえたり、バイアス!と表記してほしいです。

今月はブログ更新ペースがガタ落ちですが、サボっているわけではありませんので、今しばらくご容赦ください。

(さらに追記)ほぼ100%正解だ!!とよろこんだら過学習だっただけで、テストデータでやったらフツーだった、というのは皆が経験することのようです。(ジブンをなぐさめてみます)

(さらに追記)よいオタマジャクシのみなさんは、本物のくだものにペンを刺してはいけません。


*1:ちなみに地質学的にEpochは「世」で、その上の「紀」がPeriod、その上の「代」がEraです。Epochの下は「期」でAgeです。古生代とかジュラ紀とか洪積世とかとか

*2:Periodの0は実験前の状態をあらわしており、Epoch=2**(Period-1) の関係があります。

*3:学習状態がよいとは、復元の精度がいいだけでなく、よりスパース性が高いことや全体のバランスがよいことを意図しています