[Computer Vision] 6(1). 인코더-디코더로 이미지 변환

8 minute read


인코더-디코더로 이미지 변환

이 포스팅에서는 전체 이미지를 편집하거나 생성하는 모델인 인코더-디코더를 소개한다. 인코더-디코더 네트워크가 이미지에서 노이즈를 제거하는 것부터 객체와 인스턴스 분할에 이르기까지 광범위한 응용 분야에 어떻게 적용될 수 있는지 설명한다.


인코더-디코더 소개


복잡한 애플리케이션을 해결하기 전에 먼저 인코더-디코더가 무엇으며 이 네트워크가 어떤 목적을 달성하는 지 소개한다.


인코딩과 디코딩

인코더-디코더 프레임워크에 따르면 인코더는 입력 샘플을 잠재 공간, 즉 인코더에 의해 정의된 숨겨진 구조화된 값 집합에 매핑하는 함수다. 디코더는 이 잠재 공간의 요소를 사전 정의된 타깃 도메인으로 매핑하는 여함수다. 예를 들어 이미지나 오디오의 압축 및 복원 과정이 그러하다.

KakaoTalk_20210819_145114405

인코더는 샘플에 포함된 의미론적 정보를 추출/압축하기 위해 설계 또는 훈련된다. 그런 다음 디코더는 타깃 도메인에 대한 지식을 적용해 정보를 적절하게 압출 해제/완성한다. 영어 문장을 프랑스어 문장으로 변환하는 과정을 예로 들 수 있다.


오토인코딩

오토인코더는 특수한 유형의 인코더-디코더이다. 오토 인코더는 입력과 타깃 도메인이 동일해서 이 모델의 병목 계층(저차원의 잠재 공간)에도 불구하고 적절하게 인코딩한 다음 품질에 영향을 주지 않고 이미지를 디코딩하는 것을 목표로 한다. 예를 들어 JPEG 도구가 그러하다.

입력과 출력 데이터 사이의 거리는 오토인코딩 알고리즘을 최소화하기 위한 전형적인 손실이다. 이미지의 경우 이 거리는 입력 이미지와 겨로가 이미지 사이의 교차-엔트로피 손실 또는 L1/L2 손실(각각 맨해튼 거리와 유클리드 거리)로 계산될 수 있다.

머신러닝에서 오토인코딩 네트워크는 손실 표현이 단순할 뿐만 아니라 훈련에 레이블이 필요 없기 때문에 실제로 훈련하기 편리하다. 입력 이미지 자체가 손실을 계산하기 위해 사용되는 목표 이미지다.

오토인코더 잠재 공간이 더 작기 때문에 인코딩 하부 네트워크는 데이터를 적절히 압축하는 방법을 배워야 하며 반면 디코더는 다시 압축을 풀기 위해 적절한 매핑을 학습해야 한다. 일부 전문가들은 이 학습 방식이 비지도 학습이라고 하며, 다른 전문가들은 비지도 학습과는 다른 자기 지도 학습이라고 한다.


목적

인코더-디코더 모델을 사용해 이미지를 변환하거나 한 도메인 혹은 양식에서 다른 도메인이나 양식으로 매핑할 수 있다. 또는 깊이 회귀, 즉 각 픽셀에 대해 카메라와 이미지 콘텐츠 사이의 거리 추정에 사용하여 VR에 적용된다.

마찬가지로 의미론적 분할을 위해 인코더-디코더를 일반적으로 사용한다. 이 경우 네트워크는 깊이가 아니라 각 픽셀에 대해 추정한 클래스를 반환하기 위해 훈련된다. 이는 매우 중요한 애플리케이션으로, 다음 포스팅에서 중점적으로 다룬다.

마지막으로 인코더-디코더는 낙서 예술을 현실적인 허구 이미지로 변환하거나 밤에 찍은 사진에서 낮 사진을 추정하는 것과 같이 좀 더 예술적인 용도에서 유명하다.


이제 오토인코더를 알아보자. 왜 네트워크가 입력 이미지를 반환하도록 훈련돼야 할까? 그 대답은 오토인코더의 병목 속성에 있다. 인코딩과 디코딩 구성 요소가 전체로 훈련되지만, 용도에 따라 이 둘은 개별로 적용된다.

병목 때문에 인코더는 가능한 한 많은 정보를 보존하면서 데이터를 압축해야 한다. 따라서 오토 인코더의 인코더 부분은 모델이 훈련된 도메인에서의 이미지의 저차원 표현을 얻기 위해 사용될 수 있다.

예를 들면 인코더가 제공하는 저차원 표현은 종종 이미지 사이의 콘텐츠 유사도를 보존하는 데 강하다. 따라서 인코더는 경우에 따라 클러스터와 패턴을 강조하기 위해 데이터셋을 시각화할 때 사용한다.

경우에 따라 오토인코더는 디코더를 위해 훈련되어 생성 작업을 위해 사용될 수 있다. 하지만 이미지 생성을 위해 디코더를 훈련시키는 일은 실제로 그렇게 쉽지 않으며 현실적인 이미지를 출력하기 위해 신중한 설계가 필요하다. (특히 GAN을 훈련시킬 때 그렇다)

현실적으로 가장 보편적으로 발견되는 AE 인스턴스는 노이즈를 제거하는 오토인코더다. 이 모델은 입력 이미지가 네트워크에 전달되기 전에 손실이 있는 변환을 겪게 된다는 특수성이 있다. 이 모델은 여전히 원본 이미지를 반환하도록 훈련되므로 손실이 있는 연산을 취소하고 누락된 정보 일부를 복원하는 방법을 학습한다.

전형적인 모델은 백색 노이즈나 가우스 노이즈를 취소하거나 누락된 콘텐츠를 복원하기 위해 훈련된다. 이러한 오토 인코더는 이미지 해상도 개선이라고도 부르는 스마트 이미지 업스케일링에서도 사용된다.



기본 예제 - 이미지 노이즈 제거


훼손된 MNIST 이미지의 노이즈를 제거하는 간단한 에제를 통해 오토인코더의 유용성을 알아본다.

참조: 6-2장. 오토인코더로 노이즈 제거하기


간단한 완전 연결 오토인코더

이 모델이 얼마나 단순하면서도 효율적일 수 있는지 보여주기 위해 케라스로 구현한 얕은 완전 연결 아키텍처를 살펴보자.

inputs = Input(shape=[img_height * img_width])
# 인코딩 계층:
enc_1 = Dense(128, activation='relu')(inputs)
code = Dense(64, activation='relu')(enc_1)
# 디코딩 계층:
dec_1 = Dense(64, activation='relu')(code)
preds = Dense(128, activation='sigmoid')(dec_1)
autoencoder = Model(inputs, preds)
# 훈련:
autoencoder.compile(loss='binary_crossentropy')
autoencoder.fit(x_train, x_train) # 입력과 타깃으로 모두 x_train(입력 이미지) 사용

한 번 훈련되면 이 단순한 모델은 데이터셋을 임베딩하는 데 사용될 수 있다.


이미지 노이즈 제거에 적용

앞서 구현한 모델을 이미지 노이즈 제거를 위해 훈련하는 일은 훈련 이미지에 노이즈가 낀 사본을 생성해서 대신 네트워크에 입력으로 전달하는 것으로 끝난다.

x_noisy = x_train + np.random.normal(loc=.0, scale=.5, size=x_train.shape)
autoencoder.fit(x_noisy, x_train)



합성곱 인코더-디코더


다른 신경망 기반의 시스템처럼 인코더-디코더 모델 또한 합성곱 계층과 풀링 계층을 도입합으로썸 많은 이점을 얻었다. 심층 오토인코더와 다른 아키텍처는 점점 더 복잡해지는 작업을 위해 광범위하게 사용됐다.

여기서는 먼저 합성곱 인코더-디코더를 위해 개발된 새로운 계층을 소개하고 이 연산을 기반으로 한 중요한 이키텍처를 설명한다.


언풀링, 전치, 팽창

CNN은 훌륭한 특징 추출기이다. 합성곱 계층은 입력 텐서를 훨씬 더 많은 고차원의 특징 맵으로 변환하고 풀링 계층은 점진적으로 데이터를 다운 샘플링함으로써 간결하면서도 의미상으로 풍부한 특징을 얻을 수 있다. 따라서 CNN은 성능이 우수한 인코더를 만든다.

그렇다면 이 저차원 특징을 전체 이미지로 디코딩하기 위해 어떻게 이 프로세스를 되돌릴 수 있을까? 이를 위해 전치 합성곱(transposed convolution, 디컨볼루션(deconvolution)), 팽창된 합성곱(dilated convolutions), 언풀링(unpooling) 같은 역산들이 개발되었다.

  • 전치합성곱(디컨볼루션)

합성곱 계층에서 출력 형상은 다음과 같이 계산할 수 있다.

KakaoTalk_20210807_154656167

합성곱의 공간 변환을 역으로 수행하는 계층을 개발해야 한다고 가정하다. 즉, 형상이 (Ho, Wo, N)인 특징맵과 파라미터 k, D, N, p, s가 주어졌을 때 형상이 (H, W, D)인 텐서를 찾는 것이다.

이는 위 수식을 H, W에 대해 정리함으로써 간단히 얻을 수 있다!

이것이 전치합성곱이 정의된 방식이다.

이 계층은 kxkxKxN(FH=FW=k)의 커널 스택을 이용해 HoxWoxN 텐서에 합성곱을 적용해 HxWxD의 맵으로 변환한다. 이를 달성하기 위해 먼저 입력 텐서는 팽창(dilation) 과정을 거쳐야 한다.

  • 비율 d에 의해 정의된 팽창 연산은 입력 텐서의 행과 열의 쌍 사이에 (개별적으로) d-1개의 0으로 채워진 행과 열을 추가하는 것으로 구성된다. 전치 합성곱에서 d=s 로 설정된다.
  • 이 재표본 추출 후에 텐서는 p’ = k-p-1 에 의해 패딩된다.
  • 그런 다음 보폭 s’=1을 사용해 실제 텐서를 해당 계층의 필터와 합성곱을 수행하고 그 결과 HxWxD의 결과를 얻게 된다.

KakaoTalk_20210819_174615589

KakaoTalk_20210820_151725578

프로세스를 이해하기 어렵다면, 전치 합성곱 계층은 일반적으로 표준 합성곱 계층을 반전시켜 특징 맵의 콘텐츠와 훈련 가능한 필터 사이의 합성곱을 통해 특징 맵의 공간 차원을 증가시키기 위해 사용된다는 점만 기억해도 충분하다.

전치 합성곱 계층은 tf.layers.conv2d_transpose()와 tf.keras.layres.Conv2DTranspose()를 사용해 인스턴스화 할 수 있으며, 이 둘은 표준 conv2d 계층과 동일한 시그니처를 갖는다.


  • 언풀링

보폭이 적용된 합성곱이 CNN 아키텍처에 종종 사용된다면 이미지의 공간 차원을 축소하기 위해 가장 보편적으로 사용되는 연산은 평균-풀링과 최대-풀링이다. 이에 대한 역산을 위해 최대-언풀링 연산(언풀링 연산)을 사용한다. 언풀링 연산은 풀링을 정확히 역으로 뒤집을 수는 없지만(즉 수학적으로 최대-풀링 연산에서 폐기한 비최댓값들을 모두 복원할 수는 없지만) 최소한 공간 샘플링 관점에서 그에 가깝게 연산을 정의할 수 있다.

  • 먼저 각 최대-풀링 계층을 수정해 결과 텐서를 따라 풀링 마스크를 출력한다.
  • 풀링 마스크를 사용해 입력 값을 풀링되기 전의 형상으로 업스케일링된 텐서에 뿌린다.

KakaoTalk_20210819_174807570

풀링 계층처럼 언풀링 연산을 고정된/훈련할 수 없는 연산이다.


  • 업샘플링과 크기 조정

마찬가지로 평균-언풀링 연산은 평균-풀링 연산을 반전시키기 위해 개발됐다. KakaoTalk_20210820_171033101최대-언풀링 연산보다 자주 사용되는 것이 평균-언풀링 연산이며, 일반적으로는 업 샘플링으로 알려져있다. 이 연산은 tf.keras.layers.UpSampling2D( )를 통해 인스턴스화될 수 있다. 파라미터로는 interpolation을 “nearest”, “bilinear” 등으로 지정한다. 또는 tf.image.resize( ) 함수를 사용하여 method에 보간법(tf.image.ResizeMehtod.NEAREST_NEIGHBOR 등)을 지정합으로써 사용할 수도 있다.

디코더 아키텍처에서 최근접 이웃 방식이나 양선형 방식 뒤에는 일반적으로 보폭이 s=1이고 패딩 옵션이 “SAME”인 합성곱 계층이 따른다. 사전에 정의된 업스케일링과 함성곱 연산의 조합은 인코더를 구성하는 합성곱과 풀링 계층을 반전시킴으로써 디코더가 자신만의 특징을 학습해 타깃 시그널을 더 잘 복원할 수 있게 한다.


  • 팽창된/아트루스 합성곱

마지막 연산은 제공된 특징 맵을 업샘플링하는 용도로 쓰이지 않으므로 이전에 소개한 연산과는 약간 다르다. 대신 이 연산은 데이터의 공간 차원을 더 희생시키지 않고 합성곱의 수용 영역을 인위적으로 증가시키기 위해 제안된다. 이를 달성하기 위해 여기에서도 (많이 다른 방식이지만) 팽창 연산이 적용된다.

KakaoTalk_20210820_152209781

이러한 속성을 갖춘 이 연산은 현재 인코더-디코더에서 한 도메인의 이미지를 다른 도메인으로 매핑하기 위해 자주 사용된다. 텐서플로와 케라스에서 팽창된 합성곱을 인스턴스화하려면 tf.layers.conv2d와 tf.keras.layers.Conv2D에서 dilation_rate 파라미터에 기본 값인 1 이상의 값을 제공하면 된다.


대표적인 아키텍처 - FCN와 U-Net

대부분의 합성곱 인코더-디코더는 완전 연결 인코더-디코더와 동일한 템플릿을 따르지만 결과의 품질을 높이기 위해 국소 연결 계층의 공간 속성을 활용한다. 2015년에 출시된 FCN과 U-Net 모델은 여전히 인기가 많고 일반적으로 더 복잡한 시스템(의미론적 분할, 도메인 적응 등)을 위한 구성 요소로 사용된다.

  • FCN(Fully Connected Network)

FCN은 VGG-16 아키텍처에 기반해 마지막 밀집 계층을 1x1 계층으로 대체해 구성된다. 이 네트워크가 일반적으로 업샘플링 블록을 사용해 확장되어 인코더-디코더로 사용된다.

  • 특징 추출을 위한 CNN을 효율적인 인코더로 사용할 수 있는 방법
  • 앞에서 소개한 연산들을 사용해 CNN의 특징 맵을 효율적으로 업샘플링하고 디코딩하는 방법

캘리포니아 대학교 버클리의 조나단 롱 팀은 사전 훈련된 VGG-16을 특징 추출기로 재사용할 것을 제안했다. VGG-16은 5개의 합성곱 블록을 사용해 이미지를 효율적으로 특징 맵으로 변환하지만 매 블록 다음에 공간 차원을 반으로 나눈다. 마지막 블록의 특징 맵을 디코딩하기 위해 분류를 위해 사용되던 완전 연결 계층을 합성곱 계층으로 대체한다. 그런 다음 최종 계층으로 데이터를 입력 형상으로 업샘플링 하기 위한 전치합성곱을 적용한다.

그런데 최종 계층의 보폭이 너무 크면 세부 사항의 크기를 제한(공간을 정의하는 정보의 유실)하기 때문에 마지막 블록의 특징 맵을 더 큰 특징 맵과 결합하는 방식을 사용한다.

아래 그림은 FCN-8s 의 아키텍처다.

KakaoTalk_20210820_164748900

FCN-8s는 전치 학습을 사용하고 다양한 크기의 특징 맵을 결합합으로써 세부적인 부분가지 자세히 드러나는 이미지를 출력할 수 있다. 게다가 FCN의 본질 때문에 다양한 크기의 이미지를 인코딩/디코딩 하는 데 FCN-8s를 적용할 수 있다. 성능이 뛰어나고 다재다능한 FCN-8s는 여러 다른 아키텍처에도 영감을 주면서 여전히 수많은 애플리케이션에서 보편적으로 사용되고 있다.


  • U-Net

U-Net은 FCN으로부터 영감을 받은 솔루션 중 최초로 고안되었으면서도 가장 인기 있는 아키텍처이다.

의미론적 분할을 위해 개발된 이 모델은 FCN과 여러 속성을 공유한다. 또한 특징 깊이를 증가시키면서 공간 차원은 축소시키는 다중 블록 축소 인코더와 이미지 해상도를 복원하는 확장 디코더로 구성된다.

게다가 FCN처럼 스킵 연결은 인코딩 블록을 그에 대응하는 디코딩 블록과 연결한다. 따라서 디코딩 블록은 이전 블록으로부터 컨텍스트 정보를 제공받고 인코딩 경로에서 위치 정보를 제공받는다.

KakaoTalk_20210820_164818232

U-Net과 FCN의 주요 차이점은 두 가지이다.

  • U-Net은 전통적인 U자 형태의 인코더-디코더 구조로 돌아가 대칭형이다.
  • 스킵 연결에서 나온 특징 맵은 덧셈 대신 연결을 통해 결합된다.

U-Net은 그 인기를 감안하면 다양한 변형을 알고 있으며 여전히 수많은 아키텍처에 영감을 준다. (예를 들어 U-Net 블록을 잔차 블록으로 대체하거나 내부 블록과 외부 블록 사이의 연결 밀도를 높이는 등.)


중간 예제 - 이미지 해상도 개선

FCN 모델을 새로운 문제인 이미지 해상도 개선 작업에 간단히 적용해보자.

전체 구현 코드: https://github.com/PacktPublishing/Hands-On-Computer-Vision-with-TensorFlow-2/blob/master/Chapter06/

inputs = Input(shape=(224,224,3))
# 인코더로 사전 훈련된 VGG-16 특징 추출기 구성
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=inputs)
# 3개의 최종 블록 각각이 반환한 특징 맵을 복원:
f3 = vgg16.get_layer('block3_pool').output # shape = (28, 28, 256)
f4 = vgg16.get_layer('block4_pool').output # shape = (14, 14, 512)
f5 = vgg16.get_layer('block5_pool').output # shape = (7, 7, 512)
# VGG 밀집 계층을 합성곱 계층으로 대체하고 대신 합성곱/풀링 블록 다음에 '디코딩' 계층을 추가
f3 = Conv2D(filters=out_ch, kernel_size=1, padding='same')(f3)
f4 = Conv2D(filters=out_ch, kernel_size=1, padding='same')(f4)
f5 = Conv2D(filters=out_ch, kernel_size=1, padding='same')(f5)
# 'f4'와 결합될 수 있도록 'f5'를 14x14 맵으로 업샘플링:
f5x2 = Conv2DTranspose(filters=out_chh, kernel_size=4, strides=2, 
                       padding='same', activation='relu')(f5)
# 2개의 특징 맵을 요소-단위 덧셈으로 결합
m1 = add([f4, f5x2])
# 'm1'과 'f3'을 28x28 map으로 통합하기 위해 이 연산을 반복
m1x2 = Conv2DTranspose(filters=out_ch, kernel_size=4, strides=2, 
                       padding='same', activation='relu')(m1)
m2 = add([f3, m1x2])
# 마지막으로 전치합성곱을 적용해 원본 형상을 복원
outputs = Conv2DTranspose(filters=out_ch, kernel_size=16, strides=8, 
                          padding='same', activation='sigmoid')(m2)
fcn_8s = Model(inputs, outputs)

케라스로 구현한 VGG와 함수형 API를 재사용하면 최소한의 노력으로 FCN-8s 모델을 생성할 수 있다.


이미지 업스케일링에 적용

해상도 개선 작업을 위해 네트워크를 훈련시키는 간단한 요령으로는 이미지를 모델에 공급하기 전, 일부 전통적인 업스케일링 기법(양선형 보간법 같은)을 사용해 타깃 차원으로 척도를 맞추는 것이다. 이런 방식으로 네트워크는 업샘플링 artifact를 제거하고 손실된 세부 사항들을 복원하는 노이즈 제거 오토인코더로 훈련될 수 있다.

x_noisy = bilinear_upscale(bilinear_downscale(x_train)) # 의사 코드
fcn_8s.fit(x_noisy, x_train)

앞서 언급했듯이 FCN과 U-Net 아키텍처는 컬러 이미지의 깊이 추정, 다음 프레임 예측(일련의 동영상 프레임을 입력으로 받아 다음 이미지의 콘텐츠로 무엇이 나올지 예측), 이미지 분할과 같은 광범위한 작업에 보편적으로 적용된다.

다음 포스팅에서는 많은 실생활 애플리케이션에서 매우 중요한 이미지 분할 작업을 개발할 것이다.

Leave a comment