Pixelwise classification
nn.Linear(input_channel * width * height)
nn.Conv2d(input_channel, output_channel, ...)
input의 width, height 보다 큰 kernel size를 사용
핵심은 Transposed Convolution 에서 사용되는 kernel도 학습이 가능한 파라미터
def CBR(in_channels, out_channels, kernel_size=3, stride=1, padding=1):
return nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding),
nn.ReLU(inplace=True))
#conv1
self.conv1_1 = CBR(3, 64, 3, 1, 1) # size 변동 없음
self.conv1_2 = CBR(64, 64, 3, 1, 1)
self.pool1 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 사이즈 1/2
self.conv2_1 = CBR(64, 128, 3, 1, 1)
self.conv2_2 = CBR(128, 128, 3, 1, 1)
self.pool2 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 사이즈 1/4
self.conv3_1 = CBR(128, 256, 3, 1, 1)
self.conv3_2 = CBR(256, 256, 3, 1, 1)
self.conv3_3 = CBR(256, 256, 3, 1, 1)
self.pool3 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 사이즈 1/8
self.conv4_1 = CBR(256, 512, 3, 1, 1)
self.conv4_2 = CBR(512, 512, 3, 1, 1)
self.conv4_3 = CBR(512, 512, 3, 1, 1)
self.pool4 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 사이즈 1/16
self.conv5_1 = CBR(512, 512, 3, 1, 1)
self.conv5_2 = CBR(512, 512, 3, 1, 1)
self.conv5_3 = CBR(512, 512, 3, 1, 1)
self.pool5 = nn.MaxPool2d(2, stride=2, ceil_mode=True) # 사이즈 1/32
self.fc6 = CBR(512, 4096, 1, 1, 0) # Linear -> Conv
self.drop6 = nn.Dropout2d()
self.fc7 = CBR(4096, 4096, 1, 1, 0)
self.drop7 = nn.Dropout2d()
self.score_fr = nn.Conv2d(4096, num_classes, 1, 1, 0)
self.upscore32 = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=64, stride=32, padding=16) # 32배 크기 키움
네트워크 구조에서 block이 넘어갈 때 MaxPooling을 하면 정보를 잃을 수 밖에 없음
손실이 발생하기 전 매트릭스를 가져와 summation 해줌으로써 잃어버린 정보를 어느정도 복원
또한 Upsampled Size를 줄여주기 때문에 좀 더 효율적인 이미지 복원이 가능 ex) 32배 -> 16배
MaxPooling을 conv4까지 적용했을 때 크기는 1/16
conv4
의 결과를 가져와 Up Score
의 결과와 summation을 해주는 skip connection 구성
conv4
의 결과를 가져와 Up Score
의 결과의 channel을 맞춰주기 위해 1x1 convolution layer를 사용
Up score에서 32배가 아닌 16배만 크기 복원
처리된 두 결과를 합쳐 최종적으로 크기 복원
self.score_pool4_fr = nn.Conv2d(512, num_classes, kernel_size=1, stride=1, padding=0) # output channel을 Up Score와 동일하게 적용
self.upscore2 = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=4, stride=2, padding=1)
def forward(self, x):
h = x
...
h = upscore2 + score_pool4c # 잃어버리기 전의 정보를 어느정도 복원
self.upscore16 = nn.ConvTranspose2d(num_classes, num_classes, kernsel_size=32, stride=16, padding=8)
FCN-16s 의 결과에 conv3의 결과 (1/8 크기)를 summation
최종 Deconvolution에서 사용하는 stride가 8
Score pool3
마찬가지로 FCN-16s의 결과와 channel 숫자를 맞춰주기 위한 1x1 Convolution layer
self.score_pool3_fr = nn.Conv2d(256, num_classes, kernel_size=1, stride=1, padding=0)
self.upscore2_pool4 = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=4, stride=2, padding=1) # size * 2
def forward(self, x):
h = self.conv3_1(h)
h = self.conv3_2(h)
h = self.conv3_3(h)
pool3 = h #1/8
...
h = upscore_pool4c + score_pool3c
self.upscore8 = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=16, stride=8, padding=4)
https://towardsdatascience.com/transposed-convolution-demystified-84ca81b4baba (Transposed Convolution)
https://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Long_Fully_Convolutional_Networks_2015_CVPR_paper.pdf (Fully Convolutional Networks for Semantic Segmentation)