Convolutional Neural Network#
畳み込みニューラルネットワーク(Convolutional Neural Network, CNN) は,主に画像データに対して高い性能を発揮する順伝播型ニューラルネットワークであり,畳み込み層とPooling層を複数積み重ねることで局所的な特徴から大域的な特徴を抽出し,その特徴から最終的に線形層から分類や回帰を行う.
このノートブックでは,シンプルなCNNを構築し,MLPと同様にダミーデータを用いて順伝播を計算する.
PyTorchでは,CNNに関しても,nn.Module を使ってニューラルネットワークの構造をクラスとして次のように定義することが一般的である.今回は,RGB画像を想定した3チャネルの画像を入力として受け取り,2回の畳み込みとPoolingを行い,特徴マップをベクトル化して,線形層に入力する構造とする.また畳み込みの後にBatch Normalizationを適用する.
これは次のように実装できる.
import torch
import torch.nn as nn
class CNN(nn.Module):
def __init__(self, in_channels, num_classes):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(in_channels, 16, kernel_size=3, stride=1, padding=1)
self.bn1 = nn.BatchNorm2d(16)
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(32)
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
self.gap = nn.AdaptiveAvgPool2d(1)
self.flatten = nn.Flatten()
self.fc = nn.Linear(32, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = nn.functional.relu(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.bn2(x)
x = nn.functional.relu(x)
x = self.pool2(x)
x = self.gap(x)
x = self.flatten(x)
x = self.fc(x)
return x
これを in_channels=3,num_classes=10 としてインスタンス化する.
in_channels = 3
num_classes = 10
model = CNN(in_channels=in_channels, num_classes=num_classes)
モデルの構造を確認する.
print(model)
CNN(
(conv1): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(gap): AdaptiveAvgPool2d(output_size=1)
(flatten): Flatten(start_dim=1, end_dim=-1)
(fc): Linear(in_features=32, out_features=10, bias=True)
)
今回は活性化関数を nn.functional.relu で作成した.これは,nn.ReLU とは異なり,インスタンス化が不要な関数であり,関数として直接呼び出すことができる.
nn.ReLU は明示的にモデル内部に活性化関数を定義することができ,nn.ReLU(inplace=True) とするとメモリ効率を向上することができるが,基本的にはどちらの実装でも良い.
続いて,MLPと同様に正しく順伝播できるかどうかをダミーデータを与えてチェックしよう.今回は,\(32 \times 32\) サイズの入力を考える.
batch_size = 3
height, width = 32, 32
input_size = (batch_size, in_channels, height, width)
dummy_input = torch.randn(*input_size)
print('dummy_input.shape:', dummy_input.shape)
print('dummy_input = ')
print(dummy_input)
dummy_input.shape: torch.Size([3, 3, 32, 32])
dummy_input =
tensor([[[[-0.1565, 0.2286, 0.8601, ..., 0.2391, 0.2863, 0.1660],
[-1.0972, -0.1033, 0.7625, ..., -1.2306, 1.0867, -0.1017],
[ 0.7740, 1.0459, -0.9941, ..., -0.8386, -0.2716, -1.7073],
...,
[-0.3900, -0.7875, 1.5450, ..., 0.0058, -0.8565, -0.7701],
[-0.2046, -0.5810, 0.7466, ..., 0.5795, -0.8087, -1.4563],
[-0.2816, -0.9777, -0.6863, ..., 0.9870, 1.3527, -0.4927]],
[[-0.2822, -2.0969, 0.2069, ..., 1.2765, -1.1445, -0.6722],
[ 1.5110, -0.5341, 0.1856, ..., -1.0878, -0.5961, 0.3866],
[-0.0551, 1.2212, -1.0149, ..., -0.0128, -1.7400, 0.2304],
...,
[-0.0947, -0.0376, -1.1992, ..., -1.1718, 0.5552, 0.0294],
[ 0.8159, -1.1638, -0.0229, ..., 1.4057, -0.8366, -0.1484],
[ 0.2596, 1.4735, -0.3366, ..., 0.7204, -1.1069, 0.4346]],
[[ 0.3092, -0.7595, -0.8823, ..., -0.4400, -2.2675, 0.8542],
[-0.5678, 1.5348, -1.0108, ..., 0.6324, 0.4893, -0.7152],
[ 0.2614, -1.8651, -0.5680, ..., 0.1823, -1.5360, 0.8124],
...,
[ 0.6331, 0.5745, 0.2628, ..., 2.6873, -0.3098, -1.0509],
[ 0.6271, 0.9510, -0.3431, ..., -1.5576, -0.5168, 0.4374],
[-0.8786, 0.1043, -0.8714, ..., -0.8062, -1.0529, 0.0757]]],
[[[-0.7152, -0.6682, -0.5128, ..., -1.4886, 2.7940, -0.1453],
[ 0.5340, 1.8580, -1.4070, ..., 0.6905, -0.5520, -0.0338],
[ 0.8809, 1.2375, -0.9951, ..., -0.2555, 2.7903, -0.2528],
...,
[-0.8084, 0.0769, 0.8554, ..., 0.6622, 1.5265, -0.3589],
[ 1.0645, 0.3453, 0.7772, ..., 0.2096, 1.2975, -0.0658],
[ 1.1107, 0.3844, 0.3059, ..., 0.5493, 0.1612, -0.4582]],
[[ 0.5384, 1.1368, 0.6902, ..., -1.3522, -0.1393, -2.0445],
[-0.7141, 2.8129, -0.7323, ..., 0.9949, 0.0039, 0.6813],
[ 0.6992, 0.5970, -1.0949, ..., 0.8671, 0.5276, -0.2007],
...,
[ 0.6055, 0.5091, -1.1120, ..., -1.2601, -0.2810, 0.1243],
[ 0.3384, 1.9837, 0.2812, ..., -0.1799, -1.7301, -0.3205],
[-0.7565, 0.5512, 0.9488, ..., -0.4212, -0.2109, -0.4160]],
[[-0.2246, 1.6078, -1.4756, ..., -1.3962, -0.5568, -0.9021],
[ 1.4219, 1.2974, -0.2616, ..., -0.1130, -1.1695, -0.7097],
[-0.3726, -1.1632, -0.0559, ..., -0.2385, 0.6909, -0.3976],
...,
[ 0.0526, 1.1780, -0.4927, ..., -1.0424, -2.4717, -0.0037],
[ 0.2022, 0.1690, 1.8039, ..., -0.8798, 0.9679, 0.2643],
[ 0.4036, -0.5976, 0.3703, ..., -0.0173, -0.5865, -0.5483]]],
[[[ 1.0472, -0.1061, 0.3516, ..., 0.3630, 0.3096, -1.5806],
[-0.3145, -1.8524, 1.9114, ..., 0.4807, -1.7114, 1.1606],
[ 0.6952, -1.7289, -0.8718, ..., 0.0382, -0.5928, 1.3194],
...,
[ 0.7542, 1.5682, -0.1010, ..., -1.2252, 1.0869, -0.2012],
[ 0.3330, 0.5217, 1.1733, ..., -0.0494, -0.0190, 1.7162],
[-0.4884, -0.2095, -0.9434, ..., -0.5511, 0.0870, 1.0822]],
[[ 0.2732, -0.0236, -0.0158, ..., -0.3170, 0.0905, -0.7608],
[-0.9542, -0.1368, -1.9948, ..., 1.1318, 1.3569, 0.7817],
[-0.5325, 1.6002, -0.3202, ..., -1.0683, -0.8974, 1.3418],
...,
[-0.1536, 0.6010, 0.0366, ..., -0.7633, -0.2056, 0.3583],
[ 0.8841, 1.1148, 0.6792, ..., 0.1254, -0.1298, -2.1933],
[ 0.0958, -1.2776, -1.4516, ..., 0.8340, 0.1177, 2.1831]],
[[-0.6398, -0.4801, 0.2255, ..., -0.4323, -0.6779, 0.7784],
[ 1.5124, 0.3968, 0.9224, ..., -0.4400, 1.0985, -1.0603],
[ 0.1343, 0.2792, 0.4372, ..., 0.7638, 0.3716, 1.7213],
...,
[ 1.5489, 0.5758, -1.6218, ..., -1.4795, 1.8001, 0.5961],
[-0.5801, 0.4135, 0.6056, ..., 2.3941, 1.8614, -0.1798],
[-1.4082, 0.8716, 0.9731, ..., -0.6819, -1.1633, 1.2477]]]])
バッチサイズも含めて4回のテンソルが作成できた.順伝播を行い,(バッチサイズ, クラス数)の出力が得られることを確認する.
output = model(dummy_input)
print('output.shape: ', output.shape)
print('output = ')
print(output)
output.shape: torch.Size([3, 10])
output =
tensor([[ 0.1762, -0.2103, -0.2505, 0.3119, -0.0749, 0.5986, 0.0574, -1.1881,
0.6807, 0.6371],
[ 0.1162, -0.3229, -0.2881, 0.3188, -0.1346, 0.4909, 0.1477, -1.0620,
0.4607, 0.6308],
[ 0.0992, -0.2821, -0.2732, 0.3480, -0.0885, 0.6098, 0.0849, -1.0814,
0.5968, 0.6771]], grad_fn=<AddmmBackward0>)
このときエラーが発生する場合は forward 関数内の処理(特に,ベクトル化の処理)か畳み込みの入出力チャネル数を間違えていることが多い.
層へのアクセスやパラメータ数の取得方法も確認しておく.基本的にはMLPの場合と同じである.
total_params = 0
for name, param in model.named_parameters():
print(f'{name} shape: {param.shape}')
total_params += param.numel()
print(f'Total number of trainable parameters: {total_params}')
conv1.weight shape: torch.Size([16, 3, 3, 3])
conv1.bias shape: torch.Size([16])
bn1.weight shape: torch.Size([16])
bn1.bias shape: torch.Size([16])
conv2.weight shape: torch.Size([32, 16, 3, 3])
conv2.bias shape: torch.Size([32])
bn2.weight shape: torch.Size([32])
bn2.bias shape: torch.Size([32])
fc.weight shape: torch.Size([10, 32])
fc.bias shape: torch.Size([10])
Total number of trainable parameters: 5514