앞에서 사용했던 MNIST 손글씨 숫자 데이터셋을 다시 사용한다. 이번에는 DCGAN 구조를 사용하므로 다음 그림처럼 생성자와 판별자를 합성곱 신경망으로 표현한다. 이 변화를 제외하고 신경망의 나머지 구조는 동일하다.
<모듈 임포트와 모델 입력 차원 설정>
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Activation,BatchNormalization,Dense,Dropout,Flatten,Reshape
from tensorflow.keras.layers import LeakyReLU
from tensorflow.keras.layers import Conv2D,Conv2DTranspose
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam
img_rows=28
img_cols=28
channels=1
img_shape=(img_rows,img_cols,channels)
z_dim=100
<생성자 구현>
ConvNet은 전통적으로 이미지 분류 작업에 사용된다. 이 네트워크는 '높이x너비x컬러 채널 수' 차원을 가진 이미지를 입력으로 받아 일련의 합성곱 층을 통과시킨다. 그다음 클래스 점수를 담은 1xn 차원의 벡터 하나를 출력한다. 여기에서 n은 클래스 레이블의 수이다. ConvNet 구조를 사용해 이미지를 생성하려면 이 과정을 거꾸로 하면 된다. 이미지를 받아 처리하여 벡터로 만드는 것이 아니라 벡터를 받아 크기를 늘려서 이미지로 만든다.
이 과정의 핵심 요소는 전치 합성곱(transposed convolution)이다. 일반적인 합성곱은 전형적으로 깊이를 늘리면서 입력 너비와 높이를 줄이기 위해 사용한다. 전치 합성곱은 반대로 동작한다. 깊이를 줄이는 동안 너비와 높이를 증가시킨다. 다음 그림에 있는 생성자 네트워크 그림에서 이 과정이 나타나 있다.
각 전치 합성곱 층 다음에는 배치 정규화와 LeakyReLU 활성화 함수를 적용한다. 마지막 층에서는 배치 정규화를 적용하지 않고 LeakyReLU 대신에 tanh 활성화 함수를 사용한다. 이 과정을 정리하면 다음과 같다.
def build_generator(z_dim):
model=Sequential()
model.add(Dense(256 * 7 * 7,input_dim=z_dim)) # 완전 연결 층을 사용해 입력을 7x7x256 크기 텐서로 바꾼다
model.add(Reshape((7,7,256)))
model.add(Conv2DTranspose(128,kernel_size=3,strides=2,padding='same'))
# 7x7x256 에서 14x14x128 크기 텐서로 바꾸는 전치 합성곱 층
model.add(BatchNormalization()) # 배치 정규화
model.add(LeakyReLU(alpha=0.01)) # LeakyReLU 활성화 함수
model.add(Conv2DTranspose(64,kernel_size=3,strides=1,padding='same'))
# 14x14x128에서 14x14x64 크기 텐서로 바꾸는 전치 합성곱 층
model.add(BatchNormalization())
model.add(LeakyReLU(alpha=0.01))
model.add(Conv2DTranspose(1,kernel_size=3,strides=2,padding='same'))
# 14x14x64 에서 28x28x1 크기 텐서로 바꾸는 전치 합성곱 층
model.add(Activation('tanh'))
return model
<판별자 구현>
판별자는 이미지를 받아 예측 벡터를 출력하는 합성곱 신경망과 비슷한 ConvNet이다. 이 경우 이진 분류기는 입력 이미지가 가짜인지 진짜인지 나타낸다.
판별자 입력은 28x28x1 크기 이미지이다. 합성곱을 적용하여 이미지는 면적(너비 x 높이)이 점차 줄어들고 깊이는 점점 깊어지는 식으로 변환된다. 모든 합성곱 층에 LeakyReLU 활성화 함수를 적용한다. 출력층에는 완전 연결 층과 시그모이드 활성화 함수를 사용한다. 이 과정을 정리하면 다음과 같다.
완전 연결 층과 시그모이드 활성화 함수를 사용해 입력 이미지가 진짜일 확률을 계산한다.
def build_discrimintor(img_shape):
model=Sequential()
model.add( # 28x28x1 텐서에서 14x14x32 크기 텐서로 바꾸는 합성곱 층
Conv2D(32,
kernel_size=3,
strides=2,
input_shape=img_shape,
padding='same')
)
model.add(LeakyReLU(alpha=0.01)) #LeakyReLU 활성화 함수
model.add( # 14x14x32 텐서에서 7x7x64 크기 텐서로 바꾸는 합성곱 층
Conv2D(64,
kernel_size=3,
strides=2,
padding='same'))
model.add(LeakyReLU(alpha=0.01))
model.add( # 7x7x64 텐서에서 3x3x128 크기 텐서로 바꾸는 합성곱 층
Conv2D(128,
kernel_size=3,
strides=2,
padding='same'))
model.add(LeakyReLU(alpha=0.01))
model.add(Flatten()) # 시그모이드 활성화 함수를 사용하는 출력층
model.add(Dense(1,activation='sigmoid'))
return model
<DCGAN 모델 생성과 실행>
생성자와 판별자를 위해 사용한 네트워크 구조를 제외하고 나머지 DCGAN 신경망의 설정과 구현은 앞의 GAN에서 사용했던 것과 동일하다. 이는 GAN 구조의 유연함을 잘 보여준다.
def build_gan(generator,discriminator):
model=Sequential()
model.add(generator)
model.add(discriminator)
return model
discriminator=build_discrimintor(img_shape)
discriminator.compile(loss='binary_crossentropy',
optimizer=Adam(),
metrics=['accuracy'])
generator=build_generator(z_dim)
discriminator.trainable=False # 생성자 훈련시에는 판별자의 파라미터를 고정시킨다
gan=build_gan(generator,discriminator)
gan.compile(loss='binary_crossentropy',optimizer=Adam())
def sample_images(generator,image_grid_rows=4,image_grid_columns=4):
z=np.random.normal(0,1,(image_grid_rows * image_grid_columns,z_dim)) # (16,100) 사이즈의 정규분포
gen_imgs=generator.predict(z)
gen_imgs=0.5 * gen_imgs + 0.5 # 이미지 픽셀 값을 [0,1] 범위로 스케일 조정
print(gen_imgs.shape)
fig,axs=plt.subplots(image_grid_rows,
image_grid_columns,
figsize=(4,4),
sharey=True,
sharex=True)
cnt=0
for i in range(image_grid_rows):
for j in range(image_grid_columns):
axs[i,j].imshow(gen_imgs[cnt,:,:,0],cmap='gray') #이미지 그리드 출력
axs[i,j].axis('off')
cnt+=1
losses=[]
accuracies=[]
iteration_checkpoints=[]
def train(iterations,batch_size,sample_interval):
(X_train,_),(_,_) = mnist.load_data()
X_train=X_train/127.5 -1.0 #[0,255] 흑백 픽셀 값을 [-1,1]로 스케일 조정
X_train=np.expand_dims(X_train,axis=3)
real=np.ones((batch_size,1))
fake=np.zeros((batch_size,1))
for iteration in range(iterations):
idx=np.random.randint(0,X_train.shape[0],batch_size)
imgs=X_train[idx]
z=np.random.normal(0,1,(batch_size,100))
gen_imgs=generator.predict(z)
d_loss_real=discriminator.train_on_batch(imgs,real)
d_loss_fake=discriminator.train_on_batch(gen_imgs,fake)
d_loss,accuracy=0.5 * np.add(d_loss_real,d_loss_fake)
z=np.random.normal(0,1,(batch_size,100))
gen_imgs=generator.predict(z)
g_loss=gan.train_on_batch(z,real)
if (iteration + 1) % sample_interval==0:
losses.append((d_loss,g_loss))
accuracies.append(100.0 * accuracy)
iteration_checkpoints.append(iteration + 1)
print('%d [D 손실: %f, 정확도: %.2f%%] [G 손실: %f]' %
(iteration + 1, d_loss,100.0 * accuracy,g_loss))
sample_images(generator)
이제 다음 코드에서 이 모델을 실행한다.
iterations=20000
batch_size=128
sample_interval=1000
train(iterations,batch_size,sample_interval)
'GAN > 이론' 카테고리의 다른 글
7. 훈련의 어려움 (0) | 2021.06.24 |
---|---|
6. 훈련 평가 (0) | 2021.06.24 |
4. DCGAN - 배치정규화 (0) | 2021.06.23 |
3. GAN 훈련 알고리즘 (0) | 2021.06.22 |
2. 생성자와 판별자 (0) | 2021.06.22 |