- 데이터 준비하기
- Generator 구성하기
- Generator 재구성하기
- Discriminator 구성하기(현재 page)
- 학습 및 테스트하기
Discriminator 구성하기
Generator만으로 좋은 결과를 도출하기에는 부족할 것입니다. 조금 더 사실적인 이미지를 생성하기 위한 Pix2Pix를 완성시키기 위해서는 Discriminator가 필요한데, 이번 스탭에서는 Discriminator를 만들어 Pix2Pix 구조를 완성시켜 보는 시간을 가져봅시다.
Discriminator의 구성요소 알아보기
아래의 사진은 Pix2Pix 논문에서 Discriminator를 구성하는데 필요한 정보이니, 알아보도록 합시다.
Generator의 구성 요소와 똑같이 "C64" 등으로 표기되어 있습니다. 진짜인지 가짜 이미지를 판별하기 위해 최종 출력에 sigmoid를 사용하는 것을 제외하면 특별한 변경 사항은 없는 것 같습니다.
아래의 보기 쉽게 표현한 그림을 통해 Discriminator의 구조를 머릿속에 조금 더 자세히 그려보도록 합시다.
Discriminator는 2개 입력(위 그림의 "in", "unknown")을 받아 연결(CONCAT) 한 후, ENCODE라고 쓰인 5개의 블록을 통과하게 됩니다. 이 중 마지막 블록을 제외한 4개 블록은 위 논문에서 표기된 "C64-C128-C256-C512"에 해당하며, 마지막은 1(채널) 차원 출력을 위한 블록이 추가되었습니다.
최종적으로 출력되는 크기는 (30, 30, 1)이며, 위 그림의 출력 이전의 2개의 ENCODE 블록을 보면 각각의 출력 크기가 32, 31, 30으로 1씩 감소하는 것을 알 수 있습니다. Generator에서도 사용했던 2 stride convolution
에 패딩을 이용하면 (width, height) 크기가 절반씩 감소할 것입니다.
1 stride convolution
에 패딩을 하지 않는다면 (width, height) 크기는 (필터 크기가 4이므로) 3씩 감소할 텐데, 그림과 같이 1씩 감소하도록 하려면 무언가 다른 방법을 써야 할 것 같군요.
추가적으로 위 그림에서 최종 출력 크기가 (30, 30, 1)이 되어야 하는 이유는 앞서 Discriminator에 대해 알아봤던 70 * 70 PatchGAN을 사용했기 때문이다. 최종 (30, 30) 출력에서 각 픽셀의 receptive field 크기를 (70, 70)으로 맞추기 위해 Discriminator의 출력 크기를 (30, 30) 크기로 강제로 맞추는 과정이 필요합니다.
Convolution Layer에서 kernel크기, stride, padding 등을 토대로 출력 크기를 계산했던 방식을 그대로 사용하면 계산할 수 있는데, 자세한 계산 과정은 아래 링크를 참고하고 내용을 보시길 바랍니다.
Discriminator 구현하기
마찬가지로 Discriminator에 사용할 기본적인 블록부터 만들어보도록 합시다.
class DiscBlock(layers.Layer):
def __init__(self, n_filters, stride=2, custom_pad=False, use_bn=True, act=True):
super(DiscBlock, self).__init__()
self.custom_pad = custom_pad
self.use_bn = use_bn
self.act = act
if custom_pad:
self.padding = layers.ZeroPadding2D()
self.conv = layers.Conv2D(n_filters, 4, stride, "valid", use_bias=False)
else:
self.conv = layers.Conv2D(n_filters, 4, stride, "same", use_bias=False)
self.batchnorm = layers.BatchNormalization() if use_bn else None
self.lrelu = layers.LeakyReLU(0.2) if act else None
def call(self, x):
if self.custom_pad:
x = self.padding(x)
x = self.conv(x)
else:
x = self.conv(x)
if self.use_bn:
x = self.batchnorm(x)
if self.act:
x = self.lrelu(x)
return x
__init__()에서
필요한 만큼 많은 설정을 가능하게끔 했습니다. 필터의 수(n_filters
), 필터가 순회하는 간격(stride
), 출력 feature map의 크기를 조절할 수 있도록 하는 패딩 설정(custom_pad
), BatchNorm의 사용 여부(use_bn
), 활성화 함수 사용 여부(act
)가 설정 가능합니다.
Question
위에서 만든 DiscBlock의 설정을 다음과 같이 하여 DiscBlock(n_filters=64, stride=1, custom_pad=True, use_bn=True, act=True)으로 생성된 블록에 (width, height, channel) = (128, 128, 32) 크기가 입력된다면, 블록 내부에서 순서대로 어떠한 레이어를 통과하는지, 그리고 각 레이어를 통과했을 때 출력 크기는 어떻게 될까?
1. 패딩 레이어 통과 layers.ZeroPadding2D() → (130, 130, 32)
2. Convolution 레이어 통과 layers.Conv2D(64, 4, 1,"valid") → (127, 127, 64)
3. BatchNormalization 레이어 통과 layers.BatchNormalization() → (127, 127, 64)
4. LeakyReLU 활성화 레이어 통과 layers.LeakyReLU(0.2) → (127, 127, 64)
위에서 Discriminator를 표현한 그림에서 마지막 2개 블록의 출력은 입력에 비해 (width, height) 크기가 1씩 감소한다는 것을 언급하였습니다. 1씩 감소시키기 위한 방법이 위 질문의 답이며, 이에 대해 자세히 알아봅시다.
- (128, 128, 32) 크기의 입력이
layers.ZeroPadding2D()
를 통과하면, width 및 height의 양쪽 면에 각각 1씩 패딩 되어 총 2만큼 크기가 늘어난다. 출력 : (130, 130, 32) - 패딩 하지 않고 필터 크기 4 및 간격(stride) 1의 convolution 레이어를 통과하면 width 및 height 가 3씩 줄어든다. 이는
OutSize=(InSize+2∗PadSize−FilterSize)/Stride+1의
식으로 계산할 수 있다. 채널 수는 사용한 필터 개수와 같다. 출력 : (127, 127, 64) - 이 외 다른 레이어(BatchNorm, LeakyReLU)는 출력의 크기에 영향을 주지 않는다.
간단하게 코드를 작성하여 각 출력의 크기가 맞는지 확인해봅시다.
inputs = Input((128,128,32))
out = layers.ZeroPadding2D()(inputs)
out = layers.Conv2D(64, 4, 1, "valid", use_bias=False)(out)
out = layers.BatchNormalization()(out)
out = layers.LeakyReLU(0.2)(out)
Model(inputs, out).summary()
이러한 코드와 비슷한 설정으로 (width, height) 크기를 1씩 감소시킬 수 있습니다. 마지막 2개 블록은 출력의 크기가 1씩 감소하므로 이런 방식을 적용하면 됩니다.
Pix2Pix의 Discriminator가 70 * 70 PatchGAN을 사용하기 때문에 최종 출력을 (30, 30) 크기로 맞추는 과정을 진행하였습니다.
Discriminator 모델 구조
사용할 기본적인 블록을 만들었으니 이를 이용해 바로 Discriminator를 만들어보자.
class Discriminator(Model):
def __init__(self):
super(Discriminator, self).__init__()
self.block1 = layers.Concatenate()
self.block2 = DiscBlock(n_filters=64, stride=2, custom_pad=False, use_bn=False, act=True)
self.block3 = DiscBlock(n_filters=128, stride=2, custom_pad=False, use_bn=True, act=True)
self.block4 = DiscBlock(n_filters=256, stride=2, custom_pad=False, use_bn=True, act=True)
self.block5 = DiscBlock(n_filters=512, stride=1, custom_pad=True, use_bn=True, act=True)
self.block6 = DiscBlock(n_filters=1, stride=1, custom_pad=True, use_bn=False, act=False)
self.sigmoid = layers.Activation("sigmoid")
# filters = [64,128,256,512,1]
# self.blocks = [layers.Concatenate()]
# for i, f in enumerate(filters):
# self.blocks.append(DiscBlock(
# n_filters=f,
# strides=2 if i<3 else 1,
# custom_pad=False if i<3 else True,
# use_bn=False if i==0 and i==4 else True,
# act=True if i<4 else False
# ))
def call(self, x, y):
out = self.block1([x, y])
out = self.block2(out)
out = self.block3(out)
out = self.block4(out)
out = self.block5(out)
out = self.block6(out)
return self.sigmoid(out)
def get_summary(self, x_shape=(256,256,3), y_shape=(256,256,3)):
x, y = Input(x_shape), Input(y_shape)
return Model((x, y), self.call(x, y)).summary()
__init__()
내부에서 사용할 블록들을 정의했는데, 이전의 구현들처럼 (위 코드의 주석 처리된 부분과 같이) for loop로 간편하게 블록을 만들 수도 있지만, 쉽게 코드를 읽게끔 총 6개 블록을 각각 따로 만들었다.
첫 번째 블록은 단순한 연결(concat
)을 수행하며, Discriminator의 최종 출력은 sigmoid
활성화를 사용하였다.
Discriminator 내부 구조 확인
각 블록의 출력 크기가 알맞게 되었는지 아래 코드로 확인해 보자.
Discriminator().get_summary()
두 개의 (256, 256, 3) 크기 입력으로 최종 (30, 30, 1) 출력을 만들었고, 아래의 Discriminator를 나타낸 그림과 각 출력 크기가 일치하는 것을 알 수 있습니다.
임의의 (256, 256, 3) 크기의 입력을 넣어 나오는 (30, 30) 출력을 시각화해보자.
이전 PatchGAN에 대해 설명했던 것처럼, 위 (30, 30) 크기를 갖는 결과 이미지의 각 픽셀 값은 원래 입력의 (70, 70) 패치에 대한 분류 결과입니다. 전체 입력의 크기가 (256, 256) 이므로, 각각의 (70, 70) 패치는 원래 입력상에서 많이 겹쳐있을 것입니다. 각각의 픽셀 값은 sigmoid 함수의 결괏값이므로 0~1 사이의 값을 가지며, 진짜 및 가짜 데이터를 판별해 내는 데 사용하게 됩니다.
이로써 Pix2Pix를 구성하는 Generator와 Discriminator의 구현이 끝났습니다.
다음 투고에서 본격적인 모델 학습 및 테스트를 진행해보는 시간을 가져봅시다. 😀
'인공지능' 카테고리의 다른 글
Segmentation map-도로 이미지 만들기|Pix2Pix (0) | 2022.03.24 |
---|---|
[Part5]Sketch2Pokemon-학습 및 테스트하기|Pix2Pix (2) | 2022.03.23 |
[Part3]Sketch2Pokemon-UNet Generator|Pix2Pix (0) | 2022.03.23 |
[Part2]Sketch2Pokemon-Generator 구성하기|Pix2Pix (0) | 2022.03.23 |
[Part1]Sketch2Pokemon-데이터 준비하기|Pix2Pix (0) | 2022.03.23 |