딥러닝을 이용한 segmentation으로는 FCN이 있다. FCN이란 Fully convolutional Networks의 약자로 CVPR에 소개 되었고 주요한 특징은 아래와 같다.
vgg network는 3x3 convolution network를 깊게 쌓음으로써 적은 파라미터로 receptive field를 효과적으로 키워 높은 성능을 달성했다. FCN은 이러한 VGG network를 backbone로 사용한다. 그러나 아래의 그림과 다르게 FCN에서는 vgg의 (1x1xc)차원으로 mapping하는 FC layer 대신 convolution Layer를 사용한다.
receptive field: 출력 레이어의 뉴런하나에 영향을 미치는 입력 레이어 뉴런들의 공간 크기
segmentation에서 backbone의 fc layer를 convolution layer로 변경하는 것이 어떤 의미가 있을까?
segmentatation task에서 입력이 224x224x3이라면 출력값은 각 픽셀에 대한 클래스의 확률으므로 224x224xclass_items로 출력되야한다. FCN은 feature extractor에서 축소된 차원을 다시 늘리는 upsampling 기법으로 Transposed Convolution을 사용한다.
FCN의 경우 vgg16을 backbone으로 사용했기에 5개의 layer를 거쳐 feature를 추출하고 이후 7x7, 1x1, 1x1 convolution layer를 거쳐 transposed conv를 통해 최종 출력을 거치지만 fc 6에서 7x7을 적용했을때 문제가 발생하기 때문에 이번 파트에서는 fc6에 1x1 conv를 사용한 network를 설명한다.
우선 FCN의 pipeline은 다음과 같다
3단계를 보면 feature에 대해 단번에 32배 upsampling으로 사이즈를 복구하는 것이 성능에는 문제가 없을까?
실제 결과를 보면 FCN-32s와 Ground Truth를 비교해 봤을때 디테일한 부분의 정보가 사라진 것을 볼 수 있다.
앞서 설명한 FCN에서 Fc6는 7x7이 아닌 1x1 kernel을 사용하는지 알아보자.
그림과 같이 7x7 conv를 적용할때 padding이 없기 때문에 입력의 HxW와 출력이 달라진다 입출력의 크기가 다른 상태에서 upsampling을 적용하면 최종 결과의 사이즈가 원본과 달라질 수 있기 때문에 1x1 kernel을 사용한다.
원 논문에서는 7x7 kernel을 사용하기 위해 입력 이미지 사이즈를 늘리거나 conv1 layer에 padding을 100으로 설정하는 등 여러 기법이 적용된다.
FCN의 경우 Segmentation Task의 baseline으로 통하는 모델이지만 아래와 같은 한계를 갖고 있다.
- 위 그림을 보면 버스의 앞 부분 범퍼는 버스로 예측하지만, 유리창에 비친 자전거를 보고 자전거로 인식
- 같은 Object여도 다르게 labeling
- Deconvolution 절차가 간단해 경계를 학습하기 어렵다.
앞으로 설명할 DeconvNet, SegNet과 같은 경우 앞서 설명한 FCN의 한계점을 Encoder & Decoder 구조를 이용해 개선하려고 한다.
model architecture
DeconvNet과 같은 경우 1x1 conv을 기점으로 대칭형태를 띄고 있는 것을 볼수 있다. Decoder 부분에 경우 Deconv(transposed convolution), upsampling 기법 중 하나인 Unpooling layer로 구성 되어 있다. 위 모델 구조에서 pooling layer사이 연결된 선은 maxpooling시 max_indices의 정보를 전달하는 것을 표현한다
Decoder
DeconvNet의 Encoder구조는 FCN과 마찬가지로 vgg16을 Backbone를 사용하므로 생략하고 Decoder를 살펴보자.
Decoder의 구조를 보면 Unpooling(디테일한 경계 포착)과 transposed Conv(전반적인 모습 포착)이 반복적으로 이루어진 형태이다.
Unpooling
디테일한 경계를 포착하는 unpooling이 무엇인지 알아보자.
우선 Maxpooling이란 각 영역 별로 가장 큰 값을 추출하는 과정이다. 하지만 이렇게 될경우 각 위치에 대한 spatial한 정보를 잃게 되는 문제점이 있는데. Unpooling같은 경우 Maxpooling의 결과가 입력값의 어느 위치에 존재하는지에 대한 정보 (max indices)를 저장했다가 해당 영역을 복원해 나가는 과정이라 생각할 수 있다.
일반적으로 classification의 경우 pooling은 대표값을 추출하여 노이즈를 제거하거나 가장 중요한 부분을 추출하여 일반화 성능을 높이고 해상도를 줄이기 때문에 메모리를 효율성을 높여주는 장점이 있다. 하지만 segmentation의 경우 pooling을 통해 손실된 경계에 대한 정보가 있고 이러한 정보를 복원해줄 필요가 있고 이를 가능하게 해주는 것이 Unpooling기법이다.
하지만 sparse한 activation map을 가지기 때문에 이를 Transposed Convolution을 이용하여 채워줄 필요가 있음.
이러한 Unpooling과 Transposed Conv가 반복 되면서 unpooling에서 자세한 구조를 잡아내고, Transposed Conv를 거치며 unpooling결과의 빈 부분을 채워 넣는다
SegNet은 DeconvNet과 비슷하지만 빠른 성능 보다는 inference 속도에 중점을 둔 모델이다. 특히 SegNet은 자율주행과 같이 다양한 class를 빠르고, 정확하게 구분해야하는 Task에 적합하다.
SegNet 특징
- DeconvNet구조와 달리 Encoder와 Decoder를 연결하는 conv layer를 제거하여 파라미터 수를 감소 시켰다.
- Decoder sparse한 map을 dense하게 바꿔주기 위해 Deconv가 아닌 단순 Conv를 사용했다.
앞서 FCN은 Object가 큰 물체는 잘 검출을 못하는 문제가 있다. 지금부터 설명한 두 모델들은 Receptive Field를 키우는 것으로 FCN의 한계를 극복했다.
Max Pooling
conv 과 max pooling을 사용하면 효율적으로 receptive field를 키울수 있다. 정리하면 max pooling을 이용하면 노이즈를 제거하고 이미지의 크기를 줄여 메모리를 효과적으로 사용하는 것 이외에도 Receptive Field를 키울 수 있다.
그러나 max pooling을 이용하여 receptive field를 키우는 방법에는 한가지 한계점이 있다. segmentation에서는 각 pixel마다 분류를 해야하기 때문에 각 pixel들의 정보가 중요하다. 그러나 이미지의 크기를 줄인 상황에서 convolution 진행후 upsampling을 진행하면 그림과 같이 feature map의 해상도가 많이 줄어드는 문제가 발생한다.
Dilated Convolution
Dilated convolution을 사용하면 image의 크기를 적게 줄이면서도 효율적으로 receptive field를 넓힐 수 있다.
Dilated Kernel을 적용한 DeepLab v1모델은 FCN과 비슷해 보이지만 conv1 ~ conv4 layer까지 2x2 max pooling을 3x3으로 변경했고, conv5와 FC6에 convolution이 아닌 Dilated convolution을 적용했다는 점이 다르다.
# conv1 layer의 구성
conv1 = nn.Sequential(conv_relu(3,64,3,1), #conv block을 두개 쌓는다
conv_relu(64,64,3,1),
nn.MaxPool2d(3, stride=2,padding=1))#maxpooling을 kernel size 3 stride 2 padding 1로 설정하여 사이즈를 1/2로 줄임
conv4 = nn.Sequential(conv_relu(256,512,3,1),
conv_relu(512,512,3,1),
conv_relu(512,512,3,1),
#stride를 1로 설정해서 feature map의 크기를 고정
nn.MaxPool2d(3, stride=1,padding=1))
conv4 = nn.Sequential(#rate=2로 설정해서 dilated conv적용
conv_relu(256,512,3,rate=2),
conv_relu(512,512,3,rate=2),
conv_relu(512,512,3,rate=2),
#stride를 1로 설정해서 feature map의 크기를 고정
nn.MaxPool2d(3, stride=1,padding=1))
nn.AvgPool2d(3, stride=1,padding=1)),
class classifier(nn.Module):
def __init__(self,num_classes):
super(classifier,self).__init__()
self.classifier = nn.Sequential(conv_relu(512,1024,3,rate=12),
nn.Dropout2d(0.5),
conv_relu(1024,1024,1,1),
nn.Dropout2d(0.5),
nn.Conv2d(1024,num_classes,1))
def foward(self,x):
out=self.classifier(x)
return out
굿~👍