機械学習とかコンピュータビジョンとか

CVやMLに関する勉強のメモ書き。

Glow: Generative Flow with Invertible 1×1 Convolutionsの実装をした

はじめに

生成モデルの勉強も落ち着いたので理解深めるためにPytorchでGlowを実装した話(コードは載せてないので注意).

所感

著者のコードは幸いgithubに公開されていたので実装自体はそこまで難しくなかった.予定外だった(ちゃんと論文読めばわかるけど)のがglowがめちゃくちゃメモリを食うこと(論文の高解像度の顔画像を生成しようとすると確か600層くらいの畳み込みが必要.基本モデル的に使われているdepth=32のL=3のモデルでもかなりメモリを食う).論文にのっていないことがいくつかあったので,コードを見返さないでいいようにまとめとく.

畳み込みについて

論文にも書いてあることとしては,coupling layerのNNの構成は3層の畳み込みになっているが,最終層は0で初期化されていることとinvertible convは回転行列で初期化されていることには注意. またFigure 2のmulti-scaleの実装でz_iの従うガウス分布のパラメータをゼロ初期化した畳み込みで推定している.ゼロ初期化以外の畳み込みについては平均0,分散0.05のガウス分布を使って初期化が行われていた.

衝撃なのがinvertible convは論文ではLU分解をしてパラメータを保持してヤコビアンの計算を簡単にすると書いてあったが(というかこれが一つのコントリビューションに思えたが),実装のdefaultはLU分解しないものになっていた.LU分解するデメリットとしてP,L,U,sと一つの重み行列Wを4つのパラメータで保持しなければならないのでメモリをめっちゃ食う.LU分解しないと行列式を計算するのに多少コストがかかるがそれ以上にメモリを抑えられるから計算機が限られている限り普通に畳み込みしたほうがいい.

LU分解した際には,sについては\log|s|\mathrm{sign}(s)のように対数で保持して符号は別で持っていた.この辺もちょっと疑問に思ったあたりだけど,そうしておけばヤコビアンの対数がすぐに計算できるからいいということでしょう.

畳み込みのパディングはadd_edge_paddingという関数で行われている.処理としては入力と同一サイズで0初期化された行列を作りパディングされた部分のみ1の値を持つ変数を作った後チャネル方向にconcatするというもの.パディングされた領域には別途バイアスを加えたいというものかと.

Act normについて

couplingのNNの関数について論文だと(自分が見逃していなければ)畳み込み3層とReLUを使ったと書いてあるが実装を見ると最後の畳み込み以外reluの前にactnormを加えていた(これに関してはdefaultがそうなっているだけで実験のときには使ってないかもしれない). またゼロ初期化した畳み込みに対しては畳み込み計算後にスケーリングの処理(学習可能な係数をかける処理)を行なっていた(couplingの畳み込み以外もゼロ初期化畳み込みは全てスケーリング処理が入る).ただし,パラメータは対数の形で持っていて演算の際にはexpをかける.多分スケーリングで符号を変えたくないから.これはNICEの論文に書いてあったスケーリングと同じことなのか?

Squeezeについて

論文内では図にのってるだけで触れられてないsqueezeの処理(RealNVPの論文で説明されているから割愛したのか).基本的にはバッチxチャネルx縦x横の入力に対しfactorと呼ばれる整数値を用意して(実装では2)入力をバッチxチャネルx縦/factorx横/factorにreshapeした後,バッチxチャネルxfactorxfactorx縦/factorx横/factorに並び替えて,バッチx(チャネルxfactorxfactor)x縦/factorx横/factorのようにチャネルに含めるような処理を行う(当然逆変換もできる).ただし実装では入力がバッチx縦x横xチャネルになっているので注意.実際にはfactor 2の場合はチェッカーボード状にピクセルをサンプリングしてチャネル方向にくっつけるというもの.

最適化について

optimizerはAdamで各パラメータはいわゆるdefault値を使って重み減衰はなし.ただし,最初の10epochの間で学習率を0からdefault値の0.001に線形に変化させている.resnetとかでもwarm upは使われていたけどここまで極端なのはやっぱり学習が難しいからなのか.

その他

coupling等でデータを二つに分ける際にはチャネルを半分に分けるだけ(論文にも書いてあったけど一応). 後normalizing flowは逆関数が定義されているから,ちゃんと元に戻るか試して見るのがdebugになる.

細かいところだと著者実装では画像から潜在変数方向のactnormの計算がぱっと見はz=(x+b)*sになっていて論文に書いてある式と一致していないが,初期化の段階でbを負の平均,sを標準偏差の逆数を使って行なっているため実質z=(x-b)/sをしているのと等しく論文の記述通りの計算をしていることになる(なんでこんな回りくどい実装になっているかは不明).

学習結果

mnistでちょこっと学習させたモデルで補間をやってみた.

f:id:peluigi:20180831124354p:plain

f:id:peluigi:20180831124402p:plain

f:id:peluigi:20180831124409p:plain

概ねできてるっぽい.

まとめ

ざっと殴り書いたけどこんな感じ.実装見る限りtensorflowは畳み込みのチャネル等を入力のデータのサイズに合わせて勝手に初期化してくれるみたいなのでパラメータ追うのが結構しんどかった. もうちょっといろいろ遊んでみたいところ.