出力ノード数を手計算せずにConvolutionの出力をLinearに入力する(chainer 1.15)

  • 11
    いいね
  • 0
    コメント

 chainerのConvolution2DLinearは、入力チャンネル数in_channelsやノード数in_sizeにNoneを指定すると、最初のデータをforwardした時に自動で合わせてくれる機能があります。ドキュメントの引数の説明で言及されていますが、意外と気づきにくいです(chainer version 1.15.0.1)。

in_size (int) – Dimension of input vectors. If None, parameter initialization will be deferred until the first forward data pass at which time the size will be determined.

 とりあえず試します。

import chainer
import numpy


class LinearLinear(chainer.Chain):
    def __init__(self):
        super().__init__(
            linear1=chainer.functions.Linear(in_size=10, out_size=20),
            linear2=chainer.functions.Linear(in_size=None, out_size=30),
        )

    def __call__(self, x):
        h = self.linear1(x)
        h = self.linear2(h)
        return h


linearlinear = LinearLinear()

x = numpy.random.rand(64, 10).astype(numpy.float32)
h = linearlinear(x)
print(h.data.shape)  # (64, 30)
import chainer
import numpy


class ConvConv(chainer.Chain):
    def __init__(self):
        super().__init__(
            conv1=chainer.functions.Convolution2D(in_channels=4, out_channels=4, ksize=3),
            conv2=chainer.functions.Convolution2D(in_channels=None, out_channels=3, ksize=3),
        )

    def __call__(self, x):
        h = self.conv1(x)
        h = self.conv2(h)
        return h


convconv = ConvConv()

x = numpy.random.rand(64, 4, 32, 32).astype(numpy.float32)
h = convconv(x)
print(h.data.shape)  # (64, 3, 28, 28)

 おかげさまでコードの変更を行いやすくなりました。ありがたや。

 ところで、先ほどこのようなやり取りを目にしました。

 特に多層なConvolutionの出力ノード数を計算するのはかなり面倒なため、Conv->Linearで入力ノード数を省けるのは待望の機能です。さっそく試してみました。

import chainer
import numpy


class ConvLinear(chainer.Chain):
    def __init__(self):
        super().__init__(
            conv1=chainer.functions.Convolution2D(in_channels=4, out_channels=4, ksize=3),
            linear2=chainer.functions.Linear(in_size=None, out_size=30),
        )

    def __call__(self, x):
        h = self.conv1(x)
        h = self.linear2(h)
        return h


convlinear = ConvLinear()

x = numpy.random.rand(64, 4, 32, 32).astype(numpy.float32)
h = convlinear(x)
print(h.data.shape)

# Invalid operation is performed in: LinearFunction (Forward)
# Expect: prod(in_types[0].shape[1:]) == in_types[1].shape[1]
# Actual: 3600 != 4

 んー・・・?よくわからないけど、エラーが出ました。

 原因はさておいて、やっぱり手計算は必要なのかと悲観することはなく、reshapeを挟んであげると自動で推定してくれます

class ConvLinear(chainer.Chain):
    def __init__(self):
        super().__init__(
            conv1=chainer.functions.Convolution2D(in_channels=4, out_channels=4, ksize=3),
            linear2=chainer.functions.Linear(in_size=None, out_size=30),
        )

    def __call__(self, x):
        h = self.conv1(x)
        h = chainer.functions.reshape(h, (h.data.shape[0], -1, 1))
        h = self.linear2(h)
        return h


convlinear = ConvLinear()

x = numpy.random.rand(64, 4, 32, 32).astype(numpy.float32)
h = convlinear(x)
print(h.data.shape)  # (64, 30)

 ありがたや。

 しかし先ほどのエラーは何かしらの手違いかもしれません。これはコミットチャンスです。そしておそらくこの辺りです。しかしこれはこのコミットで修正されています。うーん、残念。(?)

 このコミットを含むPRは現在masterにマージ済みで、最新のものを利用すれば先程のエラーは発生しません。もうしばらく待って、chainer1.16をインストールすればこの苦しい手計算から解放されるはずです。やったぜ。

追記

 さきほど紹介させて頂いたツイートの方から。

 やったぜ。