概要
あるタスクで,X方向の特徴量を少なくしつつ,Y方向の特徴量を多くしたくなった.
例えば大きさ(100, 100)の画像を,conv/deconvを使って(50,200)にしたい.
これを解決する方法は大きく分けて2つある.
- conv→deconv もしくは deconv→conv
- 引き伸ばし→conv
最初の方法だと2層構造になってしまうため避けたい.
そのため引き伸ばしてconvする手法を検討し,実装した.
しかし良い実装方法が思いつかずfunctions.deconvolution_2d
を用いた.
可能であればもっとスマートな実装をしたい.
背景
convolutionを使えば,位置情報を維持しつつより少ない特徴量に写像できる.
x = numpy.random.rand(1, 1, 100, 100).astype(numpy.float32)
shape = chainer.links.Convolution2D(1, 1, ksize=(4, 1), stride=(2, 1), pad=(1, 0))(x).shape
# shape: (1, 1, 50, 100)
deconvolutionを使えば,位置情報を維持しつつより多い特徴量に写像できる.
x = numpy.random.rand(1, 1, 100, 100).astype(numpy.float32)
shape = chainer.links.Deconvolution2D(1, 1, ksize=(1, 4), stride=(1, 2), pad=(0, 1))(x).shape
# shape: (1, 1, 100, 200)
しかし,ある次元では少ない特徴量に,別の次元では多い特徴量に写像するLayerはたぶんない.
手法検討
conv→deconv/deconv→conv
一番シンプルな実装だが,どうしても2層構造になり,勾配が消失しやすくなりそうなので避けたい.
x = numpy.random.rand(1, 1, 100, 100).astype(numpy.float32)
x = chainer.links.Convolution2D(1, 1, ksize=(4, 1), stride=(2, 1), pad=(1, 0))(x)
x = chainer.links.Deconvolution2D(1, 1, ksize=(1, 4), stride=(1, 2), pad=(0, 1))(x)
# x.shape: (1, 1, 50, 200)
x = numpy.random.rand(1, 1, 100, 100).astype(numpy.float32)
x = chainer.links.Deconvolution2D(1, 1, ksize=(1, 4), stride=(1, 2), pad=(0, 1))(x)
x = chainer.links.Convolution2D(1, 1, ksize=(4, 1), stride=(2, 1), pad=(1, 0))(x)
# x.shape: (1, 1, 50, 200)
引き伸ばし→conv
2つ考えた.まずその1.
functions.unpooling_2d
を使って引き伸ばしたあと,convで小さくする.
x = numpy.random.rand(1, 1, 100, 100).astype(numpy.float32)
x = chainer.functions.unpooling_2d(x, ksize=(1, 2))
x = chainer.links.Convolution2D(1, 1, ksize=(4, 4), stride=(2, 1), pad=(1, 2))(x)
# x.shape: (1, 1, 50, 200)
続いてその2.
functions.deconvolution_2d
を使って引き伸ばしたあと,convで小さくする.
1010101010...といったマスクを作って,deconvで引き伸ばす感じ.
x = numpy.random.rand(1, 1, 100, 100).astype(numpy.float32)
x = chainer.functions.deconvolution_2d(x, W=numpy.array([0, 1, 0], numpy.float32).reshape(1, 1, 1, 3), stride=(1, 2))
x = chainer.links.Convolution2D(1, 1, ksize=(4, 4), stride=(2, 1), pad=(1, 1))(x)
# x.shape: (1, 1, 50, 200)
考察
どれがいいのだろう.
- conv->deconv
- convしたときに特徴量がやたら少なくなっているので良くなさそう
- メモリ使用量は少ない
- deconv->conv
- この2層構造は
upsample->conv
と同値・・・な気がする - deconvのあとに活性化関数を挟むならありかも
- この2層構造は
- unpooling->conv
- upconvだとか呼ばれてたりする手法に似ている
- これが一番無難そう
- upsample->conv
- 良い実装方法が思いつかなかった
- 専用のLayerを定義できればメモリ削減できそう
今後
そもそも2Dではなく,links.ConvolutionND
を使って3次元convする際に適用するつもりなのだが,functions.unpooling_nd
が無いことに気づいた.どうしよう.