2편: 두 접근의 접점, DeepLab V3+

심현주
Hyunjulie
Published in
11 min readDec 15, 2018

1편에서 소개했던 두가지 방법을 합쳐놓은, 두 세계가 만난 순간… 너의 가능성은?

영화 ‘너의 이름은' — 만날 수 없었던 다른 공간/시간의 주인공들이 만난 영화. 출처

이번 포스트를 읽기 전에, Xception 에 익숙하지 않으시다면 Xception 설명 & PyTorch 코드를 보고 와주세요 :)

1편: Semantic Segmentation 첫걸음! 에 이어서 2018년 2월에 구글이 공개한 DeepLab V3+ 의 논문을 리뷰하며 PyTorch로 함께 구현해보겠습니다. 좋은 성과를 거둔 다양한 방식들을 많이 적용한 모델이기 때문에 각 방식들이 어떻게 적용되었는지 볼 수 있는 재미있는 논문이라고 생각합니다.

이전 포스트에서 Semantic Segmentation을 할 때 자주 사용되는 신경망의 형태로, ‘인코더&디코더 구조’와 ‘Atrous Convolution 구조'를 알아봤습니다.

  • 인코더 & 디코더 구조: 물체의 뚜렷한 모서리를 담을 수 있습니다 (e.g. SegNet)
SegNet 의 구조. 출처
  • Atrous Convolution 구조: Atrous 는 프랑스어의 ‘A trous’ (구멍이 있는) 에서 나온 말로, 일반적인 convolution 사이에 공간을 넣어서 구멍이 뚤린 듯한 구조입니다. 같은 연산량으로 더 큰 특징을 잡아낼 수 있습니다. 다양한 확장 비율을 가진 atrous convolution 을 병렬적으로 사용해서 더 많은 특징을 담을 수 있습니다. DeepLab V1~V3에서 쓰이는 방법입니다.
Atrous Convolution. 왼쪽부터 dilation rate: 1, 2, 3

단순하게 얘기한다면 DeepLab V3+ 는 이러한 두 구조를 섞어놓은, (폭탄주같은) 모델이라고 할 수 있습니다. Atrous Convolution 을 인코더에서 적절히 사용하고, 여기서 얻어진 특징맵을 디코더에 넣습니다.

논문 의의 & 간단 정리

  1. 인코더 + 디코더 형태의 모델
    디코더는 동일하게 유지하고 2가지의 인코더를 제시함.
    a) DeepLab V3 를 인코더로
    b) 변형된 Xception 를 인코더로
    PASCAL VOC 2012 데이터셋을 기준으로 ‘변형된 Xception’을 인코더로 쓸 때 성능이 좋았음. 변형된 Xception 의 학습된 weight 를 공개함.
  2. Atrous Convolution 사용
    일반적인 인코더-디코더 구조에서는 불가능했던 인코더에서 추출된 특징맵의 해상도를 임의로 제어할 수 있음

3. ASPP 모듈과 디코더 모듈에 Depth-wise Separable Convolution 을 적용

4. PASCAL VOC 2012 와 Cityscapes dataset 에서 State-of-art를 달성 (2018/02)

핵심 개념 정리

모델구조에 들어가기 앞서, 핵심적인 Related Work & 개념에 대해서 정리해보았습니다.

  1. ASPP: Atrous + Spatial Pyramid Pooling
    ASPP 는 DeepLab V3에서 소개된 Pooling 방법입니다. 기존의 Spatial Pyramid Pooling 에서, 각 convolution 을 atrous convolution 으로 바꾼 형태입니다.
    Spatial Pyramid 는 밑에 사진에서 보시는 것과 같이 여러 Grid Scale 에서 pooling 을 진행해서 마지막에 특징맵을 concatenate 시키는 형태입니다. 대표적으로 PSPNet 이 있습니다. 즉, ASPP 는 여러개의 확장 비율을 사용해서 convolution 을 한 후 마지막에 concatenate 시킵니다.
PSPNet의 구조: Spatial Pyramid Pooling 의 예시. 출처

2. Depthwise Separable Convolution: 모델의 성능은 유지하면서 계산 비용과 parameter의 개수를 줄일 수 있는 방법입니다. Xception 에서 사용됩니다.
기존의 Convolution 이 두 개의 Convolution 으로 나눠져서 진행됩니다.
첫 단계에서 Depth/Channel별로 convolution 을 한 후, Point 별로 convolution 을 합니다. 직관적으로 보면

Depth-wise separable convolution 출처.

3. Output Stride
논문에서는 ‘Input 이미지 해상도’와 (Global Pooling을 하기 전) ‘Output 해상도' 의 비율로 정의했습니다. Strided Convolution 을 거치면 해상도가 점점 작아지면서 output stride 는 커집니다. 보통 Image Classification 의 마지막 특징맵은 input 보다 32배 정도 해상도가 줄어들지만 (이 경우 output stride: 32), Semantic Segmentation 더 빽빽한 특징맵이 필요하기 때문에 16 또는 8로 조절해줍니다 (output stride 가 작을수록 계산비용이 증가합니다.)
조절하는 방법으로는 마지막 block 에서 stride 를 없애거나, atrous convolution 으로 대체하는 등의 방법을 사용합니다.

모델의 구조

앞서 얘기한 것 처럼 DeepLab V3+ 는 2개의 Encoder 를 제시하지만, 전체적인 모델의 구조는 밑 그림과 같습니다. 모델의 구조는 이 그림을 보면 이해가 가지만 몇 가지 중요한 디테일을 얘기하고 2개의 인코더를 나눠서 자세히 살펴보겠습니다.

  • 인코더를 통과해서 나온 특징맵은 원본 사진의 해상도보다 16배가 작습니다 (Output Stride: 16). 효과적으로 이미지의 디테일을 회복하기 위해서 Decoding을 할 때 Low-Level Feature 를 합쳐서 진행합니다. 16으로 한 이유는 실험 결과 속도와 정확도의 좋은 trade-off point 였기 때문입니다.
  • ASPP 구조에서 ‘rate’는 atrous convolution 의 확장비율 (dilation rate)를 얘기합니다
  • Low-Level Feature 에 1x1 Conv 를 하는 이유는 인코더의 결과물과 채널의 줄이기 위해서 입니다. 실험을 통해서 채널의 개수가 48개 일 때 가장 좋은 결과를 내어서 Low-Level Feature 의 채널수에 48을 사용합니다.
출처: DeepLab V3+ 논문
  • 디코더의 마지막에 3x3 Conv 를 하는 이유는 물체의 모서리에 더욱 선명한 결과를 가져오기 위해서 입니다.

A. DeepLab V3를 인코더로 사용한 모델

DeepLab V3 는 기본적으로 ResNet 을 Backbone 으로 사용합니다. ResNet 은 Residual Network 를 사용하는 깊은 네트워크입니다. ResNet 에 대한 자세한 설명은 많은 분들이 올려주셨네요 :)

말보다는 사진으로 보는게 직관적이니, 논문에 나온 DeepLab V3+ 모델의 그림에 DeepLab V3를 합성해보았습니다.

  • Stride를 하기 전 Conv2 특징맵을 Low-Level 특징맵으로 사용하였습니다
  • 인코더의 결과물은 256개의 채널을 가지고 있습니다.
DeepLab V3를 인코더로 사용한 DeepLab V3+ 의 구조

B. 변형된 Xception 을 인코더로 사용한 모델

MSRA의 변형된 Xception 과 비슷하게 변형해서 사용합니다. 기존의 Xception 과 비교해서
(1) 더 깊고
(2) Max Pooling 은 stride가 있는 Depthwise Separable Convolution로 바꾸었고
(3) 모바일넷과 비슷하게 3x3 Depthwise Convolution 후에 Batch Normalization 과 ReLU 를 더해주었습니다.

왼: 원래 Xception 모델, 우: 논문에서 사용된 변형된 Xception 모델

Xception 을 사용한 학습 과정

  • 데이터셋: PASCAL VOC 2012 + Semantic contours from inverse detectors (Hariharan, B., Arbela ́ez, P. et al., ICCV 2011) → 총 10,582 개의 트레이닝 이미지를 갖고 학습 진행 (20개의 물체 + 배경 => 21 개의 클래스)
  • mIOU 를 사용
  • Learning Rate: 초기에 0.007로 시작해서 아래의 식을 사용해서 점점 줄여나감 (power = 0.9)
    이와 같은 방법을 “poly” learning rate policy 라고 불립니다.
  • Crop size: 513 x 513
  • 트레이닝을 하는 동안 random scale 방법으로 데이터를 augment
  • ImageNet 으로 Pre-train을 시킨 Xception 을 사용

결과

일단 모델의 성능에 output stride 가 큰 영향을 준다는 것을 예측할 수 있습니다. 보통 이미지 classification 작업을 할 때는 output stride 를 32로 하지만, 디테일이 필요한 segmentation 의 경우 16 혹은 8을 사용하였습니다. Benchmark 와 비교할 때 연산량은 고려하지 않기 때문에 성능을 더 좋게 하기 위해서 8을 사용하였습니다.

일단 결과를 표로 보여드린다면

변형된 Xception 을 기반으로한 DeepLabV3+ 사용 (PASCAL VOC 2012 validation 기준)

굉장히 다양한 실험을 진행했다는 것을 알 수 있습니다. 트레이닝을 할 때의 output stride, 테스팅을 할 때 output stride, Pretraining 을 어떤 데이터에 했는지에 따라서 굉장히 다양한 조합들을 만들어 낼 수 있습니다.

전체적으로 output stride를 줄였을 때, 다양한 데이터셋에 pretraining 을 시켰을 때 모델의 성능이 향상했다는 것을 알 수 있습니다. ‘가장 성능이 좋았던 모델’과 다른 모델들과 비교를 한 결과는 아래와 같이 나왔습니다. 변형된 Xception 을 기반으로 하고, JFT데이터셋에서 pretraining 을 했을 때, 현존했던 모델들보다 좋은 성과를 거두었습니다.

위 두 줄은 좋은 결과를 얻은 예시들이고, 아래 줄은 좋지 않은 결과들 입니다. 물체들이 서로 엉켜있거나 겹쳐져 있으면 역시 좋은 결과를 얻기 어려운 것 같습니다. 이번 논문을 정리하자면, 기존에 두개의 큰 접근법을 적절하게 섞어서 더 좋은 성과를 낸 예시라고 생각합니다.

읽어주셔서 감사합니다 :D

P.S. PyTorch 튜토리얼 대회에 코드와 함께 제출하려 했었지만 프로젝트로 정신이 없어서 코드는 완성하지 못하게 됐네요ㅠㅠ 그래도 더 다양한 튜토리얼들이 한국어로 나오길 바라는 마음에서 다른 논문들도 열심히 리뷰해보겠습니다 :-)

참고로 깃헙에 있는 DeepLab V3+ 의 코드 중 jfzhang95의 깃헙에 있는 코드가 가장 깔끔 한 것 같습니다.
실행하실 때 arguments — backbone: Xception 으로 설정하시면 됩니다 ! Modelling>Backbone>Xception.py 에 가시면 변형된 Xception 의 코드가 있습니다. 구현하기 어려운 코드라기 보다, 굉장히 깊고 반복되는 레이어들이 많기 때문에 읽는데 구현하거나 읽을 때 시간이 생각보다 걸릴 수 있습니다. 그래도 익숙한 encoder-decoder 의 형태를 가지고 있기도 하고, encoder 로 유명한 모델들을 많이 써서 코드를 이해하시는데는 어려움이 없을 거라고 생각합니다.

--

--