Kerasメモ

Kerasメモ

TimeDistributedがわからん

レイヤーラッパー – Keras Documentationの説明を読んだ。各タイムステップごとに層を適用するにはTimeDistributedをかませないとだめということか。では、まずTimeDistributedをかまさないとどうなるか試してみよう。たぶんなんかエラーになるんだろう。

あれ?エラーでないし、TimeDistributedかまさなくても各タイプステップごとにDense層を適用できてしまった。TimeDistributedいらねーじゃん。

ではTimeDistributedをかましても同じ結果になるのか?

なんじゃこの結果は????どう解釈したらいいんだ。

ちなみに「詳解ディープラーニング」のp.275の足し算プログラムは、TimeDistributedを入れていても入れなくてもうまく学習できた。結果的にはどちらでもいいんだが、うーん。

参考:Keras Recurrentレイヤーメモ:return_sequences, RepeatVector, TimeDistributed

ちなみに、TimeDistributedを用いず、単にModel.add(Dense)をしてしまうとどうなるのでしょうか…?
パッと思いつきで、「TimeDistributedを用いないと全ての時刻にわたってFull Conncetionしてしまうのでは?」と思っていましたが、特に出力の次元が変わることもなかったので、そうではないようです。今のところ、まだ違いがわかっていないので目下調査中です。

そうなんだよ。次元は変わらない。でも中身は異なる。しかし学習はどちらでもうまくいく。???

Kerasモデルへの入力の形状

Kerasモデルへの入力は基本的にテンソル(ベクトル)の配列。たとえば以下のような入力を2次元ベクトルへ変換するだけのモデルを考える。

このモデルにテンソルではなくスカラーの配列を与えるとエラー。

入力の次元は2以上(つまりテンソル)が期待されるが、1次元(スカラー)が入力されましたーといわれる。

スカラーの配列は受け付けないが、テンソルの配列にすると飲み込んでくれる。

ただし例外的に、入力層のinput_shape(またはinput_dim)オプションで1次元を指定すると、スカラーの配列でも受け付けてくれる。

まとめ。基本的にテンソル(ベクトル)の配列を入力する。スカラーの配列を入力したい場合はinput_shape(またはinput_dim)で1次元を指定する。

predictメソッドにわたすデータの形状

predictメソッドで予測するとき、データをひとつわたすとエラーになる。

サンプル次元を追加しなければならない。

np.expand_dimsを使わずにこのようにしてもサンプル次元を追加できる。

fitメソッドにサンプル次元をつけなければならないのは当然だ。学習には複数のデータをまとめて投入する必要があるからだ。

しかしpredictメソッドにはひとつのデータを入れて予測したい場合もあると思う。しかし実際には、predictメソッドへの入力データは複数であることが前提となっているのでサンプル次元が必要。

ちなみにscikit-learnも同様にサンプル次元が必要。

kerasと異なるのは、numpy配列でもいいし配列の配列でもOKということ。kerasはnumpy配列でなければだめ。

input_shapeに与える形状にはサンプル次元を加えない

input_shapeにはひとつひとつのデータの形状を指定する。サンプル次元は加えない。以下ではX.shapeは(4, 2)となる。よってサンプル次元をとりのぞいた(2, )をinput_shapeに指定する。

一方、predict, predict_proba, predict_classesに与えるデータにはサンプル次元を加える必要がある。

出力が複数ベクトルからなる場合の交差エントロピー誤差

チュートリアルでよくある分類問題では出力がひとつのベクトルだが、複数のベクトルを出力する問題もある。出力が複数ベクトルからなる場合の交差エントロピー誤差は、個々のベクトルの交差エントロピー誤差の平均であることを確認する。

このようなモデルに対して、まずはデータをひとつずつ流してこんで誤差がいくつになるか確認。

誤差は6くらい。次のデータを流し込む。

誤差はほぼゼロ。つぎに以上のふたつのデータを同時に流し込んでみる。

誤差は3。データを同時に流し込んだときの誤差は、個別にデータを流し込んだときの平均であることが確認できた。ソース読まないと確実なことはいえないけど、たぶんまちがいないと思う。

word2vecのskip-gramとか出力がふたつ以上のやつはたぶん複数の交差エントロピー誤差の平均が損失関数なんだと思う。gensimのソースを読むのが億劫なので推測だけしておいておく。なんか矛盾が起こったらしかたなくソース読む。

Kerasでの学習済みモデルの保存と読み込み

saveメソッドでモデルの構造と重みデータの両方を保存できる。重みのみ保存するにはsave_weightsメソッドを使う。

当然だがload_weightsメソッドを使う際、モデルの構造と重みデータの形状のつじつまがあっていないとエラーになる。

オプティマイザはrmsprop一択

rmspropオプティマイザは、一般にどのような問題でも十分によい選択である。心配事が1つ減ることになる。(p.78)

「PythonとKerasによるディープラーニング」より。

情報量を徐々に絞っていく層の構成がよさげ

アヤメデータで遊んでみた結果、ニューロン数が多い層から徐々に少ない層を重ねていって、最終的に3つのソフトマックス層で終える構成がいちばん性能がよくなった。情報量を徐々に絞っていって、最終的にラベル数に落とし込む構成がよいのだと思う。途中でニューロン数を多くすると性能が悪化する。

徐々に情報を絞っていく構成でも、層を17層も重ねたら複雑すぎて過学習を起こすかなと思ったら、起こさなかった。

plot_model

Keras.utils.plot_modelを使うとpydotだけインポートしようとし、pydotplusのインポートはトライしないのでtensorflow配下のものを使う(pydotは開発停止していてpython3.5以降では使えないらしいのでpydotplusを使っている)。

ひとつはまった点。graphvizへパスを通したあと、開発環境(PyCharm)を再起動しないとパスが有効にならずエラーが解消されなかった。

LSTMの状態について

layers.SimpleRNNの場合、状態イコール出力のため、return_state=Trueにする意味はないと思う。

LSTMもそうだと誤解していたが、LSTMの状態はふたつある。状態h(=出力)と状態c。長短記憶ということから、たぶん状態hが短期の記憶で、状態cが長期の記憶なんだと思う。

return_state=Trueを指定することで、最後の状態hとcを得ることができる。状態hは出力とおなじ。

return_sequences=Trueとすると、最後の出力と状態hが等しいことが確認できる。

initial_stateを指定するには、LSTMの場合はSimpleRNNとちがって状態hと状態cをセットで渡さなければならない。以下はLSTMを2層並べ、最初の層の状態を次の層に引き継いでいる。

Masking層のあとのリカレント層でreturn_sequences=Trueにする場合はプレパディングがいい

ポストパディングすると以下のようになる。

これではこのあとのDense層などで困ったことになる。

プレパディングにすると以下のようになる。

Masking層

可変長のデータを扱うさいに使うものと思っていたが、実験してみたらなんかちょっと違う。Masking層はその名のとおり、たんに指定した値をゼロに変換する(マスクする)もののようだ。

mask_valueにゼロに変換したい値を指定したMasking層のみのモデル。これに時系列データを流して動作確認していく。

mask_valueに指定した値を含まないとそのままのデータが流れてくる。

mask_valueに指定した値はゼロ埋めされて流れてくる。

ポイントは、可変長のデータを事前に特殊な値でパディングするのは自分ということ。Masking層は特殊な値(mask_value)をゼロ埋めして流してくれる。

ということは、自分でパディングするさいゼロパディングすればMasking層はいらない。

↑は勘違い。Masking層を入れると指定した値をゼロに変換するだけではなく、以降のリカレント層でパディング部分の計算をスキップしてくれる。

Masking層を入れないとゼロパディングした部分も計算してしまうため、[6, 7, 8]ではなく最後の[0, 0, 0]が流れてくる。

Masking層を入れるとゼロパディング部分の計算をスキップしてくれるため、[6, 7, 8]が流れてくる。ちなみにmask_valueを指定しなくてもゼロベクトルをスキップしてくれるようだ。

逆にmask_value=[6, 7, 8]とすると[6, 7, 8]がスキップされて[0, 0, 0]が出力される。

結論。パディング部分の計算をスキップするためにMasking層が必要。

個人的に、Masking層を入れるとほかの層(リカレント層)の動作が変わるというのは違和感がある。ほかの層のオプションでスキップする値を指定できたほうが直観的にわかりやすいかな。しかし、たくさん層が積み重なっているネットワークだとぜんぶの層でオプションいじるのが面倒そうだ。Masking層ひとついれるだけでOKというのは合理的なんだろう。

Embedding層のmask_zeroオプション

まずはKerasの日本語ドキュメントから引用。

mask_zero: 真理値.入力の0をパディングのための特別値として扱うかどうか. これは入力の系列長が可変長となりうる変数を入力にもつRecurrentレイヤーに対して有効です. この引数がTrueのとき,以降のレイヤーは全てこのマスクをサポートする必要があり, そうしなければ,例外が起きます. mask_zeroがTrueのとき,index 0は語彙の中で使えません(input_dim は語彙数+1と等しくなるべきです).

わかりそうでわからなかったので実験で確かめていく。

まずはEmbedding層のみで動作確認。

mask_zeroオプションを指定していても、Embedding層の出力には変化なし。

つぎにEmbedding層のあとにRNN層を追加してみる。

mask_zero=Trueにすると、そのあとのリカレント層でインデックス0に対応するベクトルがゼロベクトルに変換される。

これってEmbedding層の0行目をゼロベクトルにするのとおなじなのでは?

やはりおなじでした。mask_zero=Trueを外す代わりに重みの0行目をゼロベクトルにしてもおなじ結果になる。こちらのほうが直観的にはわかりやすい。

けっきょくはMasking層とおなじく、可変長のデータをゼロパディングして長さをそろえるのは別途やらなければならない。

ゼロパディングはkeras.preprocessing.sequence.pad_sequences関数でかんたんにできる。

ゼロパディングしたならmask_zero=Trueにする必要はないと思う。ゼロになにかけてもゼロだから。→と思ったんだが、バイアスをFalseにしてない場合はバイアスが足されてゼロじゃなくなってしまう。バイアスFalseの場合はほぼない。ゆえにやっぱりゼロパディングした場合でもmask_zero=Trueは必要。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする