手書き数式認識ソフト、tegashiki開発その5
前スレ
- 手書き数式認識ソフト、tegashiki開発その1
- 手書き数式認識ソフト、tegashiki開発その2
- 手書き数式認識ソフト、tegashiki開発その3
- 手書き数式認識ソフト、tegashiki開発その4
ここでやった事概要
- 実機上でのnormalize等の処理の為、kotlin上の簡単なテンソルライブラリを書いた
- kotlinで簡単なテンソル処理がしたい 会心の出来!
- 実機での挙動を調査すべく、足場をいろいろ用意(json export、colab側のTF Liteや元のモデルの評価用コード等)
- モデル改善の試行錯誤
- 重要なパラメータと効果が無いパラメータ、構造の変化を探る
- Feature Extractorのis_trainingの扱いがFE作成時とその後のトレーニングでずれている事を発見、大きく改善へ
- 単体シンボル(アルファベットと数字のみ)、sub, sup, 2項、のデータセットでほぼ完璧に動くモデルを作り実機で動かす事に成功
- モデルの部分だけを抜き出したコードをgistで別途管理する事に(githubにも入れるけど)
2019/06/21
strokeデータを作るにあたり、簡単なTensorライブラリが欲しくなる。
どんな感じにするか少し考えた。
結構いいんじゃないか?という事でshapeからインデックスの列を作る回りと、sub tensorをgetで作る仕組みを作ってテストを書いてみた。 実装も簡単でいい感じ。 残りは代入の方のbroad cast回りがちょっと面倒だが、そこ以外はもう出来たも同然では無いか。
ただこういうのを延々と整備するのはアンチパターンなので、とりあえずstrokeのpaddingに必要な物だけを用意していこう。
一通りつなげてみたが、どうも精度が悪い。動いていない訳じゃないのだが。 データを眺めているとやはりノーマライズが要るかなぁ、という気がしてきた。 ノーマライズはテンソル処理的にはかなり複雑なので、結果として当初思っていた以上に高機能なテンソルライブラリになってしまった…
まぁいい。 それでもどうも結果が悪い。 なんでかな?とデータを眺めていると、頂点数がめっちゃ多い。 最大値の50を超えてる。この場合は変な挙動をしているはずなのでおかしい理由は分かったが、どうしよっかなぁ。
適当に間引いてみた。本当に適当に間抜くだけ。
で、ノーマライズがいろいろバグっていたので頑張って直す。 なんとなく正しいコードになったかなぁ。
モデルは相変わらずバカだが、多少はマシになっている気がする。
傾向
- いつも二文字並べたものを予測する(一文字しか書いていない時でも)
- supもsubも一切予想しない(必ず二文字並べたものだけを予測している)
- シンボルの予想はランダムよりは当たってそうに見える(から動いてはいそう)
- 特に二つ書いた時の精度は多少マシになる
感覚的には0.85も当たってるようには見えない。 これはquantizeのせいなのかデータが自分が想定しているのと違うのか何かバグってるのか食わせるデータが結構違うのか、いろいろ考えられて良く分からないな。
とりあえず明日トレーニングデータを見直してみよう。 また、quantizeしたモデルのvalidation accも計ってみるか。
2019/06/22
さて、今日はなぜモデルの精度がaccから想像されるより低い気がするのか、を確認したい。 どう進めるかな。 考えられる原因は幾つかある
- そもそもこのモデルのaccではこの位の結果である
- トレーニングやvalidationに食わせてるデータと実際のデータには差がある
- quantizationの結果accが低下している
- Android側の自分のコードにバグがある
どれもそれなりにありえそうだな。 2を確認するには今試しているテストアプリで、データをexport出来るようにしてquantize前のモデルで試す必要がある。 いつかはやりたいが可能ならまだ後回しにしたい気もする。
とりあえず1と3を確認したいが、どちらを先にやるかなぁ。
まずは1を確認するか。間接的には、実際のvalidation setのデータを幾つか見て、 元のモデルでpredictしてみて、昨日観測された傾向が本当にあるかを確認しよう。 なんとなくaccが0.85とか行く為にはsupやsubも予測しそうな気がするので。
これでちゃんと予測出来ていたらTF Liteにコンバートしたモデルで同じデータをpredictしてみよう。
という事で、まずはpredict用にvalidation setのデータセットを取り出す所からだな(今はtfrecord形式になってるので簡単には見れない)。
prediction用にTPUEstimator回りのコードをいろいろ書いて結果を見ているが、どうもdecoder_inputの未来のデータを見ている雰囲気だな。 次の入力に何を入れるかで前の予測が変わる。 causalというのがうまく機能していないか?
自作embedがちゃんと後ろに何入っても同じ物を返しているのを確認。
お、どうやらlayzer_normalizeで未来の情報が入っちゃうっぽい。そうか。じゃあ外してみるか。
お、ちゃんと一致した。そうか。じゃあこれでトレーニングしなおしてみるか。
途中までトレーニングしたモデルで結果を確認。良さそう。
convencdec_fixfutureleak acc 0.75
あら、acc結構落ちているな。微妙に将来の情報を使っていたのか、はたまたnormalize的な物が無くなったせいか。 ただこの位動けばまぁ評価できるかな。やってみよう。
とりあえずかなり動くようにはなった。今回は正しく動いているのは確信出来る程度には動いている。
全体的な印象
- 複数ストロークのシンボルが苦手
- 上付きはほとんど当てられない(下付きと勘違いする)
- TF Liteしてないモデルの振る舞いとも、見ていてそんなには違わない(つまりacc 0.75は相当使い物にならない)
という事で使い物にはならないが、ちゃんと実機で動く最初のモデルが出来た。
上付きが当てられないのはnormalizeがバグってるせいか? 少しnumpyで同じロジック書いて動作を比較してみよう。
比較してみた。残念な事に特にバグは無さそう。 これで正しい振る舞いっぽいなぁ。一応テストを書いておく。
さて、現状はAndroidで試したデータをPython側で試す方法が無いから、TF Liteのコンバージョンでどれくらい結果が悪くなっているかは厳密には分からない。 python上のデータセットを可視化しつつ試してみて、Android側で触ってる時の印象とあまり変わらない、といのを確認しているが、 これはAndroid側と同じデータを食わせた訳では無い。
どうしよっかなぁ。 データは取り出せるようにした方が良いと思う反面、もうちょっと進んだ段階でトレーニングセットとして何が必要か決めた後にその辺やりたい、という気持ちもある。 今日はたくさんコード書いたのでこの位にしておくか。
2019/06/23
挙動をかんがえるに、一ストローク目は正しくても二つ目の文字を書くと一文字目の結果が間違えたものになったりしがち。 これはストロークの系列がうまく扱えてない、という事じゃないか。 上付きがうまく扱えないのも、一文字目との位置関係がうまく考慮出来てない、という事に思う。
そう考えると、たぶんストロークのエンコーダー側がちゃんとしたフィーチャーになってないんじゃないか。
と思ってencoderを見直すと、filter sizeが64, 32と減らされていて、カーネルはどちらも8になっている。 これはおかしい気がする。
そもそもストロークの最大長は今は13だ。64も要らない。 あんまり現状に最適化しすぎるのは将来的には良く無いが、まずはいいだろう。 そしてカーネルサイズ8は128のfeatureを縮め過ぎじゃないか。
もともと64にしてたのは最大長がもっと長かった時のデータセットに作ったモデルをそのまま持ってきたからだろうな。 カーネルサイズが8なのはfeature extractorする前に作ったモデルでこれは次元が3を移すので8で十分だろう、という構造だったのだろう。
あんまり今のデータセットにのみハードコードするのもどうかと思うが、とりあえずは今のデータセットに合わせたモデルを見てみるべきか。 という事で13-256, 13-256にしてみよう。
convencdec_enc13x256
上記モデルを回しつつ考える。なんでストロークが増えると最初を間違えるのか? それはencoderの出力する系列データの最初の項が情報をうまくとらえられてないのだろう。 なんでかな?と思うと、これは普通にpadding sameでconvしてるので、足し合わされちゃうのだな。
そもそもフィーチャーになっているので、足し合わせる必要は無いよな。 むしろencoderは何も挟まない方がスコアが良いのではないか?その可能性はある。 residual項みたいなのを入れればいいという事かもしれないが。
なんかストロークのextractedされたfeatureをconvolutionするっていまいちだな。 もっとself attentionみたいな仕組みの方がいいよなぁ。
もう少し考えてみた。ストローク側にconvolution入れる、というのは、周辺のストロークをまとめて何かのfeatureにしている訳だな。 そういうまとめたものにattentionを入れて出力する、というのは、そのアイデア自体は悪くない気がしている。
おや、先ほどのencoderのフィルターサイズ減らしてkernelサイズ増やしたモデル、スコアが落ちた。 なんでだ?
convencdec_enc13x256 acc 0.67
こういう時というのは何か理解出来てない事が起きている証拠だな。 少し時間をおいて考えてみるか。
Transformerの論文とか読み直しつつ考えたが、なんか現状のでももうちょっと良くても良さそうだなぁ。 そろそろ昼飯なので、とりあえずいったんencoder無しのモデルを流しておこう(考えてみるとは…)
convdec_noenc acc 0.15
全然ダメだった。へー。
少し散歩しつつ考えた。まずfilter, kernelを、64-8, 32-8とした物が、13-128, 13-128としたものより良い理由。
まずはカーネルのサイズが大きすぎてうまく学習出来ない、という可能性。 これはトレーニングのロスが64-8, 32-8の方が小さいので納得できない。
次に、全ての項目で全部見ている方が良い可能性。 つまりフィルターサイズとして13だと真ん中のfeatureは全部を見る訳だが、他は掛けた状態になる。 一方でフィルタサイズ64なら端の項目でも全部見る事になる。
もしそうならFCかませればいいんじゃないか?やってみよう。
convencfcdec acc 0.19
accは全然ダメだな。validation lossも全然下がらない。ただtraing lossはまぁまぁ低いので、オーバーフィットしているっぽい。 ふむ。筋は通る。
本当にフィルタサイズなのかなぁ。ちょっとフィルタサイズを64, 64にしてみよう。
convencdec_enc64x256 acc 0.76
やはり良くなった。まだ途中だが。
あー!これ、フィルタサイズじゃなくてフィルタ数か!kernelってフィルタのサイズか!逆じゃん! なんか前TCN作った時もここ逆にした事あったんだよなぁ。定期的に逆に勘違いしている…
途中だが止めよう。そして直そう。 そしてフィルタ数を256に(extracted fature dimと同じ)、カーネルサイズをストロークの最大数の13にしてみよう。
convencdec_enc256x13 acc 0.857
よし、改善した。と見直してて、これdecoderも逆じゃん。 まだ途中だが止めて直そう。
くそぅ、FILTER_SIZEという名前の定数にしてたのが敗因だなぁ。 FILTER_NUMに変えよう。
2019/06/24
一晩流した。
convencdec_enc256x13dec128x5 acc 0.87
accはその後オーバーフィットして0.85だが、まぁいいだろう。
実機に載せてみた。
- 一文字の精度は劇的に上がった
- 下付きもほぼ正解する
- 上付きは全然でない
- 横並びは半分くらいあたる
- 二文字目を書くと一文字目が変わる(そしてはずれる)
大文字のPが全然当たらないなぁ、と思ったら合成しているデータセットに入って無かった。 ただ小文字のpは入っているがやはり当たらない(bと間違える)。 これは元のデータセットの問題だった気もする。
Python側のTFLiteのモデルを触ってる分には、あんまり一文字目が変わる現象は見られない。 kotlin側のバグか?
あやしいのはnormalizeだがコードを見直しても別段怪しい所は無い。ふむ。
データセットと比較してみて気づいたが、どうも生成しているデータセットは上付きの場所が繊細過ぎる気がしてきた。 実際に手で書くと結構上につけるのだが、生成しているデータではかなりぎりぎりはみ出るくらいのデータしか作られない。 試しにそう書いてみると確かに認識する。
これはトレーニングセットが悪そうだなぁ。乱数でばらつかせてる範囲が狭すぎる気がする。
2019/06/25
少し実機で触りつつ、菊田さんとディスカッションした。
- 上付きが認識されにくすぎる気がする
- RNNと同じくらいのスコアは出るはずなんじゃないか
- decoderにinitial stateとしてencoderの情報が入らない所が一番怪しいかなぁ
という事で、decoderにstrokeの情報を入れるようにしよう。 どうするか? Trarnsformerの論文を眺めつつ、以下みたいにする事にした。
まず最初にattentionを入れる為には一度convをかませる必要があるので最初にstroke無しでconvする。
dec_inputをデコーダーの入力、ht_encをstroke側のCNNの出力とする。
dec_inputにconvをかませたものをht_decと呼ぶ。 ht_decとht_encからattentionを入れてcontextが出来る。
このdec_encとcontextをconcatして、さらにpoint wise FCする(ようするにカーネル1のconv1D)。 フィルター数はembedサイズと同じ(つまりdec_inputの次元と同じ)にする。 これをdec_outと呼ぼう。
このdec_inputを食わせてdec_outを吐くのを1 CNN blockとして、これを三つ重ねる事にする。 三つに意味はないが、一つではこれまでのネットワークとほぼ同じなので意味がない(これまでよりpoint wise FCが余分に挟まるだけ)。
convencdec_decstacked acc 0.716
0.71からvalidationのlossは下がらず、tariningのロスは下がり続ける。
2019/06/26
ちょっと前回のが下がった理由が良く分からない(上がらないのは不思議じゃないが)。 違いといったらdecoderのhiddenをpoint size FCで半分の次元にする事くらいなので、ここを二倍にしてみる(たぶんそういう問題じゃないとは思うが)。
たぶんダメだと思うのは、前のモデルがオーバーフィットしていたから。パラメータ数が足りない訳じゃないんだよなぁ。
他に違いといえば、decoder_inputを直接見ないでconv block 3つ越しになるので、元の情報が見づらい、というのはあるかもしれない。 res connectionを足す方がいいか? うーん、なんか納得しづらい。
少し実機の挙動を試していたが、やはり同じデータで本当にPython側でも同じ結果になるか試したいな。 export機能を付けよう。
2019/06/27
昨日流したモデルは、結局以下くらい。
convencdec_dechidden2 acc 0.8
hiddenを増やす前よりは明らかに良くなっているが、そもそもstackしてない単なるCNNには負けている。 つまりstrokeの入力を混ぜたら悪化している。
悪化するのはスタックに問題がある、という事の気もするが、strokeを入れたら良いというのが間違っている事も予想されるなぁ。 場当たり的にはいろいろやってみる事も考えつくが、もうちょっと関連論文読み直す事から始めよう。
ConvS2Sとの大きそうな違いは
- GLUが入ってない
- residual connectionが無い
くらいなので、まずはGLUの論文を読む事にする。
arxiv:1612.08083 Language Modeling with Gated Convolutional Networks
GLUの論文を読むと、どうもこの前にあるtanhのgateの改善版、という位置づけらしく、 この前のgateとの比較しかない。
前のgateはこれか。
[1606.05328] Conditional Image Generation with PixelCNN Decoders
この論文にはちらっとgateありと無しの比較が載っている(Table 1)が、 コンテキストとしてはこの論文の前の論文でRNNとCNNを比較したらRNNの方が良かった、と言っていて、 その改善としてgateを入れたというは話になっている。 その前の論文というのは以下か。
[1601.06759] Pixel Recurrent Neural Networks
residual connectionの比較が載っていて、結構違いが出るね。 CNNの方がより必要という気はしないので自分のケースでは改善しない気もするが、やってみるか。
こんな感じにしてみた。decoderのhiddenはembeddingと同じサイズに戻す(足せないので)
ht_enc = stroke_embedded+encoder_CNN(stroke_embedded, is_training)
dec_ht = dec_embedded+decoder_CnnWithAttentionBlock(dec_embedded, ht_enc, is_training)
dec_ht = dec_ht+decoder_CnnWithAttentionBlock(dec_ht, ht_enc, is_training)
dec_ht = dec_ht+decoder_CnnWithAttentionBlock(dec_ht, ht_enc, is_training)
全然学習が進まない。なんで!?
とりあえず流したままにして、実機からデータを取り出す為のデバッグダンプをつけてみる。 ストレージ回りがQで動かなくなるとの事で代替案を調べたが、どうもこういうユースケースにピッタリハマる方法が無い。 sotrage access framework方向に進めたいのは分かるのだが、 これの出来が悪いのでどうにもならんなぁ。 このlegacyはこのセンスの無さでは切れなかろう、と割り切って乗り換えない方針で行く。
昼間の間流した結果、trainingのlossは0.4ちょいくらいまで下がったが、validationのlossもaccもほとんど下がらない。 何が起きているのだろうか?
convencdec_rescon acc 0.3くらい?
答えを暗記してしまっているのだと思うのだが…
とりあえずstrokeのfeatureを見て覚えちゃっているんだよなぁ。 residual connectionをつけた結果、元の数値が見えるようになった結果カンニングというか答え暗記が出来るようになってしまっているという事だと思うが…
ただfeature extractorは通すし、答えは凄い多いし納得しがたいものがある。 まぁいいや。試しにもう一層通そう。
convencdec_rescon_enc2 acc 0.46
あれ、これもvalidation lossが全然下がらない。 それって答えを見ちゃってる、という事なんじゃないかなぁ。なんか答えを見る要素ってあったっけ?
2019/06/29
一昨日の結論は謎が多いので考えるべき事も多いが、まずはその前に実機のデータをダンプした結果を見てみる。
プロットしてみるとノーマライズ結果もなかなか悪くない。ノーマライズバグってる疑惑は否定された。 次にquantizeしてないモデルでpredictしてみる。それでも実機同様、yをdと間違い、さらに上付きを下付きと間違えている。 TF Liteのせいでは無いらしい。
トレーニングセットの問題か?と歴代最強のRNN(ただしTF Lite化は出来ていない)を試してみると、こちらは正しくy^2とpredictした。 モデルの問題らしい。うーん、そうかー。
ここまでの感想を箇条書きしてみる。
- 上付きが当たらないのが謎
- feature extractorのせいでは無さそう(RNNでは当たるので)
- training lossが下がってvalidation lossが下がらないのは何が起きているのか
- RNNくらいのモデルが作れれば使えそう
- CNNがこの位の問題に答えられないのは解せない
とりあえずtraining lossだけ下がる現象について考える。 ソースを見直していると、residual connectionつける前とで大きく変わりそうなのは、decoderが最後にdec_htを直接足してしまう所な気がする。 ここだけresidual connectionを落としてみよう。 これで元のモデルより良くなるとは思えないが、residual connectionの性質を理解しておくのは先に進む為に必要と思ったので。
convencdec_rescon_declast_nores acc 0.45
まだ上がりそうだが、まぁダメだろうから止める。
ConvS2Sではルート0.5を掛けている。 これが効くかはよく分からないが、効く可能性はある気がしてきたので試してみる事に。
convencdec_rescon_mulsqrt acc 0.28
https://bit.ly/2KMAoGF
全然ダメ。そういう問題じゃなさそう。
やっぱり信じがたいので、もう一度、stackもres connectも取り除いて試そう。
plain_encdecattn acc 0.7
https://bit.ly/2XbyRLW
これはほぼconvencdec_enc256x13dec128x5と同じ物なので0.85くらいが期待されるが、 attentionの後の次元が32次元まで削減されるので、多少低い、くらいのはず。
2019/06/29
昨晩回したスタックしてない奴。 多少想像よりはスコアが悪いが、ちゃんと学習はしている。 そしていかにもオーバーフィットしている感じなのでパラメータの少なさの問題という事で理解出来る挙動になっている。
こうして考えると、スタックしているかどうかよりも、decoderの後が
- 128次元FC
- 32次元へのpointwise conv + 32次元FC
の違いが大きいっぽい。 なぜか知らないがここのパラメータ数は凄く大事っぽいな。
ここを減らすと、その手前に3つ層を増やしてもスコアは改善しない。 逆に32次元 FCよりは32次元FCでスタックした方が改善している。
FCのパラメータ数が大切なら、128次元pointwise convにすればaccは0.85くらいになるのか? 試してみよう。
plain_dechidden128(本当は256) acc 0.86
https://bit.ly/2ZVIlwJ
おぉ、ちゃんとスコア上がった。 ここの次元が大切なのか。 普通に考えれば最後のFCのパラメータ数が大切って事だよな。
そしてコードを見直したら、hiddenは128じゃなくて256だ…まぁもう手遅れ。フォルダ名はこのままで行こう。
ここをさらに増やすのはどうかね。VOCAB_SIZEが114とかなのでこれ以上増やしても無駄な気はするが。
前のplain_encdecattnと比較していると、こちらの方がオーバーフィットが少ない。 何故だろう? plain_encdecattnでもトレーニングのロスは下がっていってるんだよなぁ。
transformerの論文を見直してたら、ここはしれっと2層の1x1 convにして間を2048次元にしてるな。 これは良さそう。真似してみよう。これでスタックするのが良さそうな気はする。
plain_dechidden256_decstack acc 0.84
https://bit.ly/2RMhYX7
2019/07/01
スタックしたものはスコアが改善しなかった。ここはスコアには影響しないらしい。ふむ。
次はスタックはやめて、pointwise conv1d を2段にして間は1024とする。 ここを256にしたのが効いたのだからここをいじるのがいいんじゃないか。
Transformerは2046-512だが、自分のはfeature extractorが256なので、半分の1024-256にしてみた。 また、Transformerは二つ目をlinearにしてたが、こちらはreluにしてみる(スタックする時の事を考えて)。
plain_ff1024_256 acc 0.86
https://bit.ly/2KS1R9Q
pointwise convが問題ならこれで改善し、そのあとのFCが問題ならこれでは0.86のままのはず。
0.86のままっぽいな。もう少し回すと変わりそうだが、出かける前なので一旦止める。
次はFCのパラメータ数が問題なのではないか、という仮定のもと、point wise Convの結果を1024とする。二層にはしない。
plain_pwconv1024 acc 0.865 https://bit.ly/2XeVvrQ
多少改善するが誤差の範囲か。
ff1024_256の方をもう少し回してみた。やはりスコアは改善せず。オーバーフィット具合も少し見てみたが、あまり進まず。
GRUの奴と比べるとtraining lossが10%くらい高い気がする。それがそのままスコアの差になっている気がするなぁ。 やはりモデルの表現力が足りてない。なんでだろう?パラメータが足りないのだよな。
Encoderのフィルタの数を増やすか。
plain_pwconv1024_enc1024 acc 0.87 https://bit.ly/324Trl9
2019/07/02
明白に違いが出て改善もされているが、定性的には同じようなレベルにも見える。 GRUはトレーニングロスがもう0.2くらい低いんだよなぁ。
次はencoderとdecoderのどちらが問題かを知るべく、片方だけGRUにしてみよう。 decoder側だけGRUにしようとしたら、encoderのstateが無い。 こっちの方が自明じゃないね。
とりあえず間に一つconv足して最後のtimestepをinit_stateにしてみる。
decgru_enc1024 acc 0.85 https://bit.ly/2XdSgfe
おお、同じ所で限界が来たっぽい?これならencoderの問題、という事になりそうだ。 これが同じスコアになるっていうのは興味深いな。何かの限界がこの辺にある、という事だよな。
たぶんData processing inequality的な物がここにある、という事だよな。
カーネルが大きすぎてぼやけてるのかね。 ConvS2Sはカーネル5を6層重ねる、と言っている。
MAX_STROKE_NUMが13だから、3を5層重ねれば端まで届く。 カーネルサイズを減らすというのは面白いかもな。試してみよう。
decgru_enck3x5 acc 0.815 https://bit.ly/324pGRh
回し足りてない感じではあるが傾向としてはダメそうなのでいったんここで止める。 へー、カーネルサイズが大きすぎる、という問題ではないのか。
encoderをself attentionの6段にしてみる。 これはmultiheadじゃないのとattentionのsoftmaxにスケールを掛けない以外はtransformerと一緒。 間は2048でhiddenは512になるようにしてある。 最初は256なのでpointwise convで512にした。
decgru_encselfattn acc 0.73 https://bit.ly/2XGmufu
initial stateの作り方が、これでは先頭が入らない事に気づいた。 どっちかといえば先頭の側が重要なのでこれではダメだな。先頭側に偏らせる事にしよう。
ついでにちゃんとencoderをGRUにしたら本当に解けるのかも確認しよう。
encdecgru_initstate5 acc 0.71
まぢで!?
encdecgru acc 0.64
positional encodingが入っている以外は以前のGRUモデルと一緒なんだが… positional encoding外してみよう。
plain_gru acc 0.69
なんか開幕のロスの下がり方が以前よりだいぶ低い。
何が起こっているのか分からないな。 もはやembeddingが自作embedding以外違いは無いのだが。
まだロスは下がってるのでもうしばらく回すと何か起きるかもしれない。
ただやはりシステムのEmbeddingsを使えば昔と同じ結果が再現できるかを確認したいな。
とりあえず完全に同じモデルに戻してみたつもり。これでダメならwarm setting upでfeature extractorが変に上書きされているくらいしか思いつかないが、 それだと昔のモデルロードして正しく動く理由が説明出来ないからそれは無いか。
plain_gru_kerasembedding acc 0.64
明らかにおかしいので途中で止める。 とりあえずランタイムのリセットをして全く同じのを走らせる。
plain_gru_kerasembedding2 acc 0.5
0.5から進まなくなった…これ、システムがバグってるな。 一旦止めて明日もう一回走らせよう。
2019/07/03
朝軽く流してみたが、やはりおかしい気がする。
plain_gru_kerasembedding3 acc 0.67
昨日と似た傾向なのでこちらのコードにバグがあるのかもしれないが、まだトラブルは続いてそうなので今日はこれ以上やらないでおく。
2019/07/04
ネットワークは治ったっぽいので、まずは一番固いはずの以前の最強構成と全く同じネットワークをトレーニングしてみる。
plain_gru_kerasembedding4 acc 0.67
おや?最初のaccがめっさ低い(0.09)が。いままでこんな事無かったのだが。 まぁ乱数のシードは固定してないので最初のスコアにはランダム性はある。
なんかすぐ0.55まで来たので問題は無さそうだな。
昨日と同じだな。しかしなんかまだTensorBoardからのリクエストが多すぎる、とngrokが文句言うな。 内部がなんか変わってるっぽい?
うーむ、どうしようかな。
最近0.86だったplain_dechidden128(本当は256)をもう一度試してみるか。 これで0.6とかしか行かなかったらTPUEstimator回りに影響する何かが変わった、と解釈しよう。
plain_dechidden256_2 acc 0.765 (途中)
TensorBoardがおかしい事から考えて、1.13.1が動かなくなっている可能性はあるな。 そろそろ新しいバージョンにしたいので、feature extractorを作り直す所から始める、というのはありかもしれない。
一度githubのノートブックで試すのもやってみてもいいかも。
お、こちらはちゃんと進んだ。0.67よりは明らかに良い所まで来たのでいったん止めるか。
では次はgithubから当時のgruのモデルのバージョンのノートブックをとってきて、これを走らせる。 これでダメなら自分がいじったのを忘れてる何かのセルの影響、という線が消える。
best_expgen_rnn_small_dropout05_2 acc 0.82 (途中)
うお、ちゃんと動いている!なんかバグ入れてるのか?
短すぎるのでtensorboardのスクリーンショットは無し。以下にログを置いておく。
1000
{'accuracy': 0.6903989, 'loss': 1.3628082, 'global_step': 1000}
2000
{'accuracy': 0.75053626, 'loss': 1.0576272, 'global_step': 2000}
3000
{'accuracy': 0.7907393, 'loss': 0.8686071, 'global_step': 3000}
4000
{'accuracy': 0.82014877, 'loss': 0.7315526, 'global_step': 4000}
とりあえずそれじゃあcreate_modelをコピペしてみよう。 これは全く同じに見えるが。 ちょっと変則的なトライアルなので以下のモデルはあまり真面目には残さない。
feature_extractorにis_trainingが無いな。
plain_gru_copyfromoldと呼ぶ。ただ最初だけ流す。
流してみたら開幕のaccが違うので止めた。 同じfeature extractorのコードを使ってみよう。
plain_gru_oldfe。おぉ、これは同じだ。 つまりfeature extractorのis_trainingが問題?
そんなはずはないと思うが確認してみよう。 BatchNormalizationのcallの引数を見るとデフォルトはNoneで、Noneを入れると内部でよきにはからった値をセットする模様。 とりあえずNoneにしてみよう(Dropoutでも指定しててこちらのデフォルトはまだ確認してない)。
plain_gru_fe_trainnone。おぉ、これはちゃんと初回のaccが0.69。
TrueとNoneでトレーニング時の振る舞いが違うのか?
plain_gru_fe_traintrueは初回のaccが0.617。これは微妙… もう少し流してみよう。
学習が進まない!これは酷い落とし穴だ…
コードを読むとDropoutはNoneの時はトレーニング時はTrueになりそうだが、BatchNormalizatoinは変な処理が入っている。 恐らくkerasじゃない所で使われている時はFalseになるっぽい。 これは酷い。
まぁいい。トレーニングはとにかくNoneじゃないと進まない。 inferenceの時はFalseで以前トレーニングしたモデル使ってちゃんと動いてそうなので、とりあえずトレーニング時はNone、inference時はFalseで試してみよう。 自分の考えではこれでaccは最初落ちるが、トレーニングは進むようになって、最終的にはaccもNoneと同じくらいの値に到達するんじゃないか。
と思ったら普通に最初からaccは良くなった。うわー、ひでぇなぁ。
よし、ここまでの発見をまとめよう。
- 以前GRUを試した時にはFeature Extractorのis_trainingにはNoneを指定していた
- このis_trainingを指定しないとTF Liteのconvertで失敗するのでトレーニングが終わったモデルに対してちゃんとこのフラグを見るように直した
- 以後はそのフラグを見るようにしていたが、これではGRUはかつてのスコアを達成できないようだ
という事で、CNN系列のモデルではハンデを背負ってた可能性がある。 GRUのモデルの時と同じ設定でやり直してみよう。
今の所一番筋が良いと思われているのが、plain_dechidden256なのでこれを試す。
plain_dechidden256_fefix acc 0.98
https://bit.ly/2XFC1fx
よし、これはほぼ完ぺきに解けているな。
収束するまでの間に今後の方針を考えよう。 これは思ったより奥が深いので別エントリにしよう。
0.977くらいでほぼ収束してそうだが、微妙にトレーニングロス下がってるのでもうちょっと流しておこう。
まだ微妙に改善が続いているが、さすがにこの0.02をどうこうしても仕方なかろう、という事でここで止める。
実機で動かす。ほぼ完ぺき。これなら十分使える
次スレに続く。手書き数式認識ソフト、tegashiki開発その6