Dilated Convolution を chainer で実装しました。
Dilated Convolution の説明
Dilated Convolution は、フィルターとの積を取る相手の間隔をあける畳み込みのことです。
例えば、以下のような画像において、 12 を中心に 3 x 3 の普通の畳み込みフィルターを適用すると、 6, 7, 8, 11, 12, 13, 16, 17, 18 との積を取って和を取ると思います。
0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 |
3 x 3 の dilate = 2 の Dilated Convolution フィルターを 12 を中心に適用すると、0, 2, 4, 10, 12, 14, 20, 22, 24 と 1 つおきに取ってきて、それらに 3 x 3 の畳み込みフィルターを適用します。
これは 5 x 5 の通常の畳み込みフィルターのうち、9 箇所以外を 0 に固定したものと見ることもできます。
dilate = 1 の Dilated Convolution は普通の畳込みになります。
dilate が 1 増えるごとに間隔が 1 ずつ開きます。
縦横で異なる dilate を使用する場合もあります。
この Dilated Convolution の何が嬉しいかというと、受容野(あるマスに影響する入力部分)を簡単に増やすことができることです。
例えば、 3 x 3 の畳み込み層を 10 層適用すると、出力の 1 つのマスにある情報は、最初の層の 21 x 21 の部分の情報をまとめたものになります。
入力画像がとても大きい場合、これではごく局所的な情報しか集約できていません。
ここで、簡単に受容野を増やすには
- 層の数を増やす
- フィルターを大きくする
- プーリング層を使う
などの方法があります。
1, 2 については、受容野の一辺の長さは層数とフィルターの一辺の長さに対して線形なので、画像がとても大きい場合はあまり有用ではありません。
3 は簡単に情報を集約できるので有用で、昔からよく使われてきました。
ただ、3 の欠点の一つとして、プーリング層を入れると画像がその分小さくなってしまうことがあります。
例えば画像を入力として、そのうち人間が写っている部分を出力したいといったタスクを考えると、できるだけ元の入力と同じ大きさの画像を出力したいです。
また、ネットワークを深くしたいときに画像の大きさによって限界がきてしまうのも困ります。
そこで、Dilated Convolution を使うと、この欠点なしに情報を集約できます。
よく使われるのは、Dilate を 1, 2, 4, 8, 16, …, 512 と指数的に増やす方法です。
こうすると、パラメータ数は層数に線形なのに対して、受容野の大きさを指数的に増やすことができ、画像もそこまで小さくなりません(パディングで対応できる程度です)。
実際に、 Dilated Convolution は大域的な情報が必要なタスクでは有用なようです。
実装
chainer で実装してみました。
最初は愚直な 7 重ループで実装したのですが、重すぎて学習が始まらなかったので書き直しました。
numpy 配列をまとめて計算するのとランダムアクセスするのが同じ計算時間でできると思うと痛い目をみるようです。
このへんの細かい仕様はよく分かってないのでまた調べたいです。
二度目の実装にあたっては chainer の dilated_convolution_2d を大いに参考にしました。
相違点として気をつけるべきなのが、変形後の x の次元が、chainer の方では (batch, in_layer, conv_y, conv_x, out_height, out_width) なのに対し、この実装は (batch, in_layer, out_height, out_width, conv_y, conv_x) となってることです。
こちらの方が直感的だとは思ったのですが、よく考えると画像の大きさよりもフィルターの大きさの方が小さいので、 chainer の実装方針でやったほうが効率は良いはずです。
他にも要因はあるかと思いますが、実際に速度は 4 倍ほど chainer の実装の方が速いです。
また、パラメータの初期値でもハマりました。
最初は全てのパラメータを平均 0 分散 1 の正規分布で初期化していたのですが、さすがに大きすぎたようでほとんど精度が出ませんでした。
chainer 標準の initializer を使うとうまくいきました。
initializer も中身をよく理解していないのでまた調べたいです。
実験
MNIST の分類を行いました。
Dilated Convolution
通常の Convolution
ほとんど変わりませんね。
むしろ最終的に dilate = 1 の方が精度が出ていて残念です。
MNIST の分類程度では違いは出ないということなのでしょうか。
Dilated Convolution のほうでもちゃんと学習が進んでくれたのでよしとします。
最後に
何か間違いや質問などがあればお願いします。
最後まで読んでいただきありがとうございました。
参考文献
[1] F. Yu et al. (2015). Multi-Scale Context Aggregation by Dilated Convolutions
[2] Dilated Convolutions and Kronecker Factored Convolutions ( http://www.inference.vc/dilated-convolutions-and-kronecker-factorisation/ )
[3] van den Oord et al. (2016). WaveNet: A Generative Model for Raw Audio