chainerのConvolution2DやLinearは、入力チャンネル数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からLinerにつなぐとき、ノード数の数みんな手で計算してるのかな。結構めんどくさい・・・
— ピクシィ (@icoxfog417) 2016年9月15日
ChainerはLinearの入力サイズをNoneにすることで手計算を省けるように最近なりました https://t.co/vLhHy7QO78
— ビーム | Seiya Tokui (@beam2d) 2016年9月15日
特に多層な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をインストールすればこの苦しい手計算から解放されるはずです。やったぜ。
追記
さきほど紹介させて頂いたツイートの方から。
@hiho_karuta qiita記事ありがとうございます、僕の確認不足でそこの対応はおっしゃる通り次リリースでした。火曜日まで待っていただけると……!(書いていただいた通りreshape挟めばいまでも使えますね)
— ビーム | Seiya Tokui (@beam2d) 2016年9月15日
やったぜ。