ChainerによるCIFAR-10の一般物体認識 (2)
ChainerによるCIFAR-10の一般物体認識 (1)(2015/11/8)のつづき。今回は畳み込みニューラルネットワークの畳込み層の数を変えたときにテスト精度がどのように変わるか調査した。前回と同じくCIFAR-10の一般物体認識をタスクとしている。
畳み込み2層(conv2)
INPUT -> (CONV -> POOL) * 2 -> FC
CONVの後のRELUは省略している。* 2
は直前の(CONV -> POOL)
を2回繰り返すことを意味している。Chainerで書くと
model = chainer.FunctionSet(conv1=F.Convolution2D(3, 32, 3, pad=1), conv2=F.Convolution2D(32, 32, 3, pad=1), l1=F.Linear(2048, 1024), l2=F.Linear(1024, 10)) def forward(x_data, y_data, train=True): x, t = chainer.Variable(x_data), chainer.Variable(y_data) h = F.max_pooling_2d(F.relu(model.conv1(x)), 2) h = F.max_pooling_2d(F.relu(model.conv2(h)), 2) h = F.dropout(F.relu(model.l1(h)), train=train) y = model.l2(h) if train: return F.softmax_cross_entropy(y, t) else: return F.accuracy(y, t)
畳込みを行っても画像サイズが小さくならないようにパディングしている。フィルタサイズが3x3のときはパディングサイズを1にすると畳み込みしても画像サイズは変わらない。たとえば、元画像サイズが32x32でフィルタサイズが3x3とするとパディングによって画像サイズは34x34になる。前回書いた式で畳み込み後の画像サイズを計算すると34-2(3/2)=32となり元画像と同じサイズになる。
同様にフィルタサイズが5x5のときはパディングサイズを2、フィルタサイズが7x7のときはパディングサイズを3、フィルタサイズが9x9のときはパディングサイズを4にすれば畳み込みしても画像サイズが変わらない。
畳み込み3層(conv3)
INPUT -> (CONV -> POOL) * 3 -> FC
model = chainer.FunctionSet(conv1=F.Convolution2D(3, 32, 3, pad=1), conv2=F.Convolution2D(32, 32, 3, pad=1), conv3=F.Convolution2D(32, 32, 3, pad=1), l1=F.Linear(512, 256), l2=F.Linear(256, 10)) def forward(x_data, y_data, train=True): x, t = chainer.Variable(x_data), chainer.Variable(y_data) h = F.max_pooling_2d(F.relu(model.conv1(x)), 2) h = F.max_pooling_2d(F.relu(model.conv2(h)), 2) h = F.max_pooling_2d(F.relu(model.conv3(h)), 2) h = F.dropout(F.relu(model.l1(h)), train=train) y = model.l2(h) if train: return F.softmax_cross_entropy(y, t) else: return F.accuracy(y, t)
畳み込み4層(conv4)
前回の構成でわかるように畳み込みによって画像サイズは変わらなくても次のプーリング層で画像サイズが半分になていく。これ以上積み重ねると非常に小さい画像になってしまうためここからプーリング層は1つおきに入れることにした。
INPUT -> CONV1 -> (CONV2 -> POOL) -> CONV3 -> (CONV4 -> POOL) -> FC
model = chainer.FunctionSet(conv1=F.Convolution2D(3, 32, 3, pad=1), conv2=F.Convolution2D(32, 32, 3, pad=1), conv3=F.Convolution2D(32, 32, 3, pad=1), conv4=F.Convolution2D(32, 32, 3, pad=1), l1=F.Linear(2048, 512), l2=F.Linear(512, 10)) def forward(x_data, y_data, train=True): x, t = chainer.Variable(x_data), chainer.Variable(y_data) h = F.relu(model.conv1(x)) h = F.max_pooling_2d(F.relu(model.conv2(h)), 2) h = F.relu(model.conv3(h)) h = F.max_pooling_2d(F.relu(model.conv4(h)), 2) h = F.dropout(F.relu(model.l1(h)), train=train) y = model.l2(h) if train: return F.softmax_cross_entropy(y, t) else: return F.accuracy(y, t)
畳み込み6層(conv6)
INPUT -> CONV1 -> (CONV2 -> POOL) -> CONV3 -> (CONV4 -> POOL) -> CONV5 -> (CONV6 -> POOL) -> FC
model = chainer.FunctionSet(conv1=F.Convolution2D(3, 32, 3, pad=1), conv2=F.Convolution2D(32, 32, 3, pad=1), conv3=F.Convolution2D(32, 32, 3, pad=1), conv4=F.Convolution2D(32, 32, 3, pad=1), conv5=F.Convolution2D(32, 32, 3, pad=1), conv6=F.Convolution2D(32, 32, 3, pad=1), l1=F.Linear(512, 512), l2=F.Linear(512, 10)) def forward(x_data, y_data, train=True): x, t = chainer.Variable(x_data), chainer.Variable(y_data) h = F.relu(model.conv1(x)) h = F.max_pooling_2d(F.relu(model.conv2(h)), 2) h = F.relu(model.conv3(h)) h = F.max_pooling_2d(F.relu(model.conv4(h)), 2) h = F.relu(model.conv5(h)) h = F.max_pooling_2d(F.relu(model.conv6(h)), 2) h = F.dropout(F.relu(model.l1(h)), train=train) y = model.l2(h) if train: return F.softmax_cross_entropy(y, t) else: return F.accuracy(y, t)
畳み込み8層(conv8)
そろそろ電気代が心配になってきた・・・これが最後だ。
INPUT -> CONV1 -> (CONV2 -> POOL) -> CONV3 -> (CONV4 -> POOL) -> CONV5 -> (CONV6 -> POOL) -> CONV7 -> (CONV8 -> POOL) -> FC
model = chainer.FunctionSet(conv1=F.Convolution2D(3, 32, 3, pad=1), conv2=F.Convolution2D(32, 32, 3, pad=1), conv3=F.Convolution2D(32, 32, 3, pad=1), conv4=F.Convolution2D(32, 32, 3, pad=1), conv5=F.Convolution2D(32, 32, 3, pad=1), conv6=F.Convolution2D(32, 32, 3, pad=1), conv7=F.Convolution2D(32, 32, 3, pad=1), conv8=F.Convolution2D(32, 32, 3, pad=1), l1=F.Linear(128, 128), l2=F.Linear(128, 10)) def forward(x_data, y_data, train=True): x, t = chainer.Variable(x_data), chainer.Variable(y_data) h = F.relu(model.conv1(x)) h = F.max_pooling_2d(F.relu(model.conv2(h)), 2) h = F.relu(model.conv3(h)) h = F.max_pooling_2d(F.relu(model.conv4(h)), 2) h = F.relu(model.conv5(h)) h = F.max_pooling_2d(F.relu(model.conv6(h)), 2) h = F.relu(model.conv7(h)) h = F.max_pooling_2d(F.relu(model.conv8(h)), 2) h = F.dropout(F.relu(model.l1(h)), train=train) y = model.l2(h) if train: return F.softmax_cross_entropy(y, t) else: return F.accuracy(y, t)
実験結果
畳み込み6層のときテスト精度77.0%が最高だった。畳み込み8層にすると悪化してしまう・・・せっかく頑張って回したのに残念。今回はエポック20で単純に学習を打ち切っているのでもっと回し続ければ上がったかもしれないけど。
同じCIFAR-10の実験結果を報告しているcuda-convnetでも単純な3層の畳み込みニューラルネットだとエラー率26%(精度74%)と報告しているため今回の結果とだいたい一致している。これ以上の精度を得るには入力画像に対する前処理が重要になってくるようだ。
Kaggle CIFAR-10に参加されたid:ultraistのアルゴリズムだと精度94.15%が得られて5位だったとのこと。1位のDeepCNetは95.53%で人間の分類精度94%を超えている。
CIFAR-10で得られたフィルタの可視化についてはまた今度。画像の前処理やスパース正則化のあり/なしによって得られるフィルタが変わるらしいのでそれらを一通り実験してからまとめたい。