import keras
keras.__version__
Using TensorFlow backend.
'2.2.4'
이 노트북은 케라스 창시자에게 배우는 딥러닝 책의 8장 5절의 코드 예제입니다. 책에는 더 많은 내용과 그림이 있습니다. 이 노트북에는 소스 코드에 관련된 설명만 포함합니다. 이 노트북의 설명은 케라스 버전 2.2.2에 맞추어져 있습니다. 케라스 최신 버전이 릴리스되면 노트북을 다시 테스트하기 때문에 설명과 코드의 결과가 조금 다를 수 있습니다.
[...]
이 절에서는 케라스에서 가장 기본적인 형태의 GAN을 구현하는 방법을 설명하겠습니다. GAN은 수준 높은 기술이기 때문에 기술적인 내용을 깊이 설명하는 것은 이 책의 범위를 벗어납니다. 구체적인 구현은 심층 합성곱 GAN(DCGAN)입니다. 생성자와 판별자가 심층 컨브넷입니다. 특히 생성자에서 이미지 업샘플링을 위해 Conv2DTranspose
층을 사용합니다.
CIFAR10 데이터셋의 이미지로 GAN을 훈련하겠습니다. 이 데이터셋은 32 × 32 크기의 RGB 이미지 50,000개로 이루어져 있고 10개의 클래스를 가집니다(클래스마다 5,000개의 이미지가 있습니다). 문제를 간단하게 만들기 위해 “frog” 클래스의 이미지만 사용하겠습니다.
GAN 구조는 다음과 같습니다:
generator
네트워크는 (latent_dim,)
크기의 벡터를 (32, 32, 3)
크기의 이미지로 매핑합니다.discriminator
네트워크는 (32, 32, 3) 크기의 이미지가 진짜일 확률을 추정하여 이진 값으로 매핑합니다.gan
네트워크를 만듭니다. gan(x) = discriminator(generator(x))
입니다. 이 gan
네트워크는 잠재 공간의 벡터를 판별자의 평가로 매핑합니다. 판별자는 생성자가 잠재 공간의 벡터를 디코딩한 것이 얼마나 현실적인지를 평가합니다.gan
모델의 손실에 대한 생성자 가중치의 그래디언트를 사용합니다. 이 말은 매 단계마다 생성자에 의해 디코딩된 이미지를 판별자가 “진짜"로 분류하도록 만드는 방향으로 생성자의 가중치를 이동한다는 뜻입니다. 다른 말로하면 판별자를 속이도록 생성자를 훈련합니다.GAN을 훈련하고 튜닝하는 과정은 어렵기로 유명합니다. 알아두어야 할 몇 가지 유용한 기법이 있습니다. 딥러닝의 대부분이 그렇듯이 이는 과학보다는 연금술에 가깝습니다. 이런 기법들은 이론에 바탕을 둔 지침이 아니고 경험을 통해 발견된 것입니다. 실제 일어난 현상을 직관적으로 이해하는 수준에서 검증되었습니다. 모든 문제에 반드시 적용해야 것은 아니지만 경험상 잘 작동한다고 알려져 있습니다.
다음은 이 절에서 GAN 생성자와 판별자를 구현하는 데 사용할 몇 가지 기법입니다. 이 목록이 GAN에 관련된 전체 팁이 아닙니다. GAN 논문들에서 더 많은 방법을 볼 수 있습니다.
sigmoid
대신 tanh
함수를 사용합니다.LeakyReLU
층을 사용하세요. ReLU와 비슷하지만 음수의 활성화 값을 조금 허용하기 때문에 희소가 조금 완화됩니다.Conv2DTranpose
나 Conv2D
를 사용할 때 스트라이드 크기로 나누어질 수 있는 커널 크기를 사용합니다.먼저 벡터(훈련하는 동안 잠재 공간에서 무작위로 샘플링됩니다)를 후보 이미지로 변환하는 generator
모델을 만들어 보죠. GAN에서 발생하는 많은 문제 중 하나는 생성자가 노이즈 같은 이미지를 생성하는 데서 멈추는 것입니다. 판별자와 생성자 양쪽에 모두 드롭아웃을 사용하는 것이 해결 방법이 될 수 있습니다.
import keras
from keras import layers
import numpy as np
latent_dim = 32
height = 32
width = 32
channels = 3
generator_input = keras.Input(shape=(latent_dim,))
# 입력을 16 × 16 크기의 128개 채널을 가진 특성 맵으로 변환합니다
x = layers.Dense(128 * 16 * 16)(generator_input)
x = layers.LeakyReLU()(x)
x = layers.Reshape((16, 16, 128))(x)
# 합성곱 층을 추가합니다
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
# 32 × 32 크기로 업샘플링합니다
x = layers.Conv2DTranspose(256, 4, strides=2, padding='same')(x)
x = layers.LeakyReLU()(x)
# 합성곱 층을 더 추가합니다
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
# 32 × 32 크기의 1개 채널을 가진 특성 맵을 생성합니다
x = layers.Conv2D(channels, 7, activation='tanh', padding='same')(x)
generator = keras.models.Model(generator_input, x)
generator.summary()
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 32) 0 _________________________________________________________________ dense_1 (Dense) (None, 32768) 1081344 _________________________________________________________________ leaky_re_lu_1 (LeakyReLU) (None, 32768) 0 _________________________________________________________________ reshape_1 (Reshape) (None, 16, 16, 128) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 16, 16, 256) 819456 _________________________________________________________________ leaky_re_lu_2 (LeakyReLU) (None, 16, 16, 256) 0 _________________________________________________________________ conv2d_transpose_1 (Conv2DTr (None, 32, 32, 256) 1048832 _________________________________________________________________ leaky_re_lu_3 (LeakyReLU) (None, 32, 32, 256) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 32, 32, 256) 1638656 _________________________________________________________________ leaky_re_lu_4 (LeakyReLU) (None, 32, 32, 256) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 32, 32, 256) 1638656 _________________________________________________________________ leaky_re_lu_5 (LeakyReLU) (None, 32, 32, 256) 0 _________________________________________________________________ conv2d_4 (Conv2D) (None, 32, 32, 3) 37635 ================================================================= Total params: 6,264,579 Trainable params: 6,264,579 Non-trainable params: 0 _________________________________________________________________
다음은 후보 이미지(진짜 혹은 가짜)를 입력으로 받고 두 개의 클래스로 분류하는 discriminator
모델을 만들겠습니다. 이 클래스는 '생성된 이미지' 또는 '훈련 세트에서 온 진짜 이미지'입니다.
discriminator_input = layers.Input(shape=(height, width, channels))
x = layers.Conv2D(128, 3)(discriminator_input)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Flatten()(x)
# 드롭아웃 층을 넣는 것이 아주 중요합니다!
x = layers.Dropout(0.4)(x)
# 분류 층
x = layers.Dense(1, activation='sigmoid')(x)
discriminator = keras.models.Model(discriminator_input, x)
discriminator.summary()
# 옵티마이저에서 (값을 지정하여) 그래디언트 클리핑을 사용합니다
# 안정된 훈련을 위해서 학습률 감쇠를 사용합니다
discriminator_optimizer = keras.optimizers.RMSprop(lr=0.0008, clipvalue=1.0, decay=1e-8)
discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy')
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_2 (InputLayer) (None, 32, 32, 3) 0 _________________________________________________________________ conv2d_5 (Conv2D) (None, 30, 30, 128) 3584 _________________________________________________________________ leaky_re_lu_6 (LeakyReLU) (None, 30, 30, 128) 0 _________________________________________________________________ conv2d_6 (Conv2D) (None, 14, 14, 128) 262272 _________________________________________________________________ leaky_re_lu_7 (LeakyReLU) (None, 14, 14, 128) 0 _________________________________________________________________ conv2d_7 (Conv2D) (None, 6, 6, 128) 262272 _________________________________________________________________ leaky_re_lu_8 (LeakyReLU) (None, 6, 6, 128) 0 _________________________________________________________________ conv2d_8 (Conv2D) (None, 2, 2, 128) 262272 _________________________________________________________________ leaky_re_lu_9 (LeakyReLU) (None, 2, 2, 128) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 512) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 512) 0 _________________________________________________________________ dense_2 (Dense) (None, 1) 513 ================================================================= Total params: 790,913 Trainable params: 790,913 Non-trainable params: 0 _________________________________________________________________
마지막으로 생성자와 판별자를 연결하여 GAN을 설정합니다. 훈련할 때 생성자가 판별자를 속이는 능력이 커지도록 학습합니다. 이 모델은 잠재 공간의 포인트를 “진짜" 또는 “가짜"의 분류 결정으로 변환합니다. 훈련에 사용되는 타깃 레이블은 항상 '진짜 이미지'입니다. gan
을 훈련하는 것은 discriminator
가 가짜 이미지를 보았을 때 진짜라고 예측하도록 만들기 위해 generator
의 가중치를 업데이트하는 것입니다. 훈련하는 동안 판별자를 동결(학습되지 않도록)하는 것이 아주 중요합니다. gan
을 훈련할 때 가중치가 업데이트되지 않습니다. 판별자의 가중치가 훈련하는 동안 업데이트되면 판별자는 항상 “진짜"를 예측하도록 훈련됩니다. 이것이 우리가 원하는 바는 아니죠!
# 판별자의 가중치가 훈련되지 않도록 설정합니다(gan 모델에만 적용됩니다)
discriminator.trainable = False
gan_input = keras.Input(shape=(latent_dim,))
gan_output = discriminator(generator(gan_input))
gan = keras.models.Model(gan_input, gan_output)
gan_optimizer = keras.optimizers.RMSprop(lr=0.0004, clipvalue=1.0, decay=1e-8)
gan.compile(optimizer=gan_optimizer, loss='binary_crossentropy')
이제 훈련을 시작합니다. 훈련 반복의 내용을 요약 정리해 보겠습니다.
매 반복마다 다음을 수행합니다:
1.잠재 공간에서 무작위로 포인트를 뽑습니다(랜덤 노이즈).
2.이 랜덤 노이즈를 사용해 `generator`에서 이미지를 생성합니다.
3.생성된 이미지와 진짜 이미지를 섞습니다.
4.진짜와 가짜가 섞인 이미지와 이에 대응하는 타깃을 사용해 `discriminator`를 훈련합니다. 타깃은 “진짜"(실제 이미지일 경우) 또는 “가짜"(생성된 이미지일 경우)입니다.
5.잠재 공간에서 무작위로 새로운 포인트를 뽑습니다.
6.이 랜덤 벡터를 사용해 `gan`을 훈련합니다. 모든 타깃은 “진짜"로 설정합니다. 판별자가 생성된 이미지를 모두 “진짜 이미지"라고 예측하도록 생성자의 가중치를 업데이트합니다(`gan` 안에서 판별자는 동결되기 때문에 생성자만 업데이트합니다). 결국 생성자는 판별자를 속이도록 훈련합니다.
실제로 만들어 보죠:
import os
from keras.preprocessing import image
# CIFAR10 데이터를 로드합니다
(x_train, y_train), (_, _) = keras.datasets.cifar10.load_data()
# 개구리 이미지를 선택합니다(클래스 6)
x_train = x_train[y_train.flatten() == 6]
# 데이터를 정규화합니다
x_train = x_train.reshape(
(x_train.shape[0],) + (height, width, channels)).astype('float32') / 255.
iterations = 10000
batch_size = 20
save_dir = './datasets/gan_images/'
if not os.path.exists(save_dir):
os.mkdir(save_dir)
# 훈련 반복 시작
start = 0
for step in range(iterations):
# 잠재 공간에서 무작위로 포인트를 샘플링합니다
random_latent_vectors = np.random.normal(size=(batch_size, latent_dim))
# 가짜 이미지를 디코딩합니다
generated_images = generator.predict(random_latent_vectors)
# 진짜 이미지와 연결합니다
stop = start + batch_size
real_images = x_train[start: stop]
combined_images = np.concatenate([generated_images, real_images])
# 진짜와 가짜 이미지를 구분하여 레이블을 합칩니다
labels = np.concatenate([np.ones((batch_size, 1)),
np.zeros((batch_size, 1))])
# 레이블에 랜덤 노이즈를 추가합니다. 아주 중요합니다!
labels += 0.05 * np.random.random(labels.shape)
# discriminator를 훈련합니다
d_loss = discriminator.train_on_batch(combined_images, labels)
# 잠재 공간에서 무작위로 포인트를 샘플링합니다
random_latent_vectors = np.random.normal(size=(batch_size, latent_dim))
# 모두 “진짜 이미지"라고 레이블을 만듭니다
misleading_targets = np.zeros((batch_size, 1))
# generator를 훈련합니다(gan 모델에서 discriminator의 가중치는 동결됩니다)
a_loss = gan.train_on_batch(random_latent_vectors, misleading_targets)
start += batch_size
if start > len(x_train) - batch_size:
start = 0
# 중간 중간 저장하고 그래프를 그립니다
if step % 100 == 0:
# 모델 가중치를 저장합니다
gan.save_weights('gan.h5')
# 측정 지표를 출력합니다
print('스텝 %s에서 판별자 손실: %s' % (step, d_loss))
print('스텝 %s에서 적대적 손실: %s' % (step, a_loss))
# 생성된 이미지 하나를 저장합니다
img = image.array_to_img(generated_images[0] * 255., scale=False)
img.save(os.path.join(save_dir, 'generated_frog' + str(step) + '.png'))
# 비교를 위해 진짜 이미지 하나를 저장합니다
img = image.array_to_img(real_images[0] * 255., scale=False)
img.save(os.path.join(save_dir, 'real_frog' + str(step) + '.png'))
/home/haesun/anaconda3/envs/deep-learning-with-python/lib/python3.6/site-packages/keras/engine/training.py:490: UserWarning: Discrepancy between trainable weights and collected trainable weights, did you set `model.trainable` without calling `model.compile` after ? 'Discrepancy between trainable weights and collected trainable'
스텝 0에서 판별자 손실: 0.6859887 스텝 0에서 적대적 손실: 0.6648078 스텝 100에서 판별자 손실: 0.98458844 스텝 100에서 적대적 손실: 15.942385 스텝 200에서 판별자 손실: 0.45530224 스텝 200에서 적대적 손실: 3.9116611 스텝 300에서 판별자 손실: 0.70805806 스텝 300에서 적대적 손실: 0.758634 스텝 400에서 판별자 손실: 0.7023996 스텝 400에서 적대적 손실: 0.76458657 스텝 500에서 판별자 손실: 0.66910684 스텝 500에서 적대적 손실: 0.8703934 스텝 600에서 판별자 손실: 0.7044946 스텝 600에서 적대적 손실: 0.7595641 스텝 700에서 판별자 손실: 0.70370543 스텝 700에서 적대적 손실: 0.7494671 스텝 800에서 판별자 손실: 0.6888744 스텝 800에서 적대적 손실: 0.72338116 스텝 900에서 판별자 손실: 0.70839185 스텝 900에서 적대적 손실: 0.95430785 스텝 1000에서 판별자 손실: 0.68738854 스텝 1000에서 적대적 손실: 0.73358965 스텝 1100에서 판별자 손실: 0.6951221 스텝 1100에서 적대적 손실: 0.76310116 스텝 1200에서 판별자 손실: 0.69487345 스텝 1200에서 적대적 손실: 0.75073826 스텝 1300에서 판별자 손실: 0.70006037 스텝 1300에서 적대적 손실: 0.7711214 스텝 1400에서 판별자 손실: 0.69141567 스텝 1400에서 적대적 손실: 0.762586 스텝 1500에서 판별자 손실: 0.692662 스텝 1500에서 적대적 손실: 0.766914 스텝 1600에서 판별자 손실: 0.70417833 스텝 1600에서 적대적 손실: 0.7402885 스텝 1700에서 판별자 손실: 0.7007116 스텝 1700에서 적대적 손실: 0.788754 스텝 1800에서 판별자 손실: 0.6862414 스텝 1800에서 적대적 손실: 0.90469587 스텝 1900에서 판별자 손실: 0.68944335 스텝 1900에서 적대적 손실: 0.753205 스텝 2000에서 판별자 손실: 0.73440367 스텝 2000에서 적대적 손실: 0.7881855 스텝 2100에서 판별자 손실: 0.68764937 스텝 2100에서 적대적 손실: 0.77248967 스텝 2200에서 판별자 손실: 0.69836366 스텝 2200에서 적대적 손실: 0.77662754 스텝 2300에서 판별자 손실: 0.685547 스텝 2300에서 적대적 손실: 0.75267434 스텝 2400에서 판별자 손실: 0.7113415 스텝 2400에서 적대적 손실: 0.7478822 스텝 2500에서 판별자 손실: 0.698676 스텝 2500에서 적대적 손실: 0.74612296 스텝 2600에서 판별자 손실: 0.6922611 스텝 2600에서 적대적 손실: 0.7469531 스텝 2700에서 판별자 손실: 0.6905508 스텝 2700에서 적대적 손실: 0.7141514 스텝 2800에서 판별자 손실: 0.6967103 스텝 2800에서 적대적 손실: 0.7513309 스텝 2900에서 판별자 손실: 0.69222534 스텝 2900에서 적대적 손실: 0.784592 스텝 3000에서 판별자 손실: 0.6932871 스텝 3000에서 적대적 손실: 0.76088995 스텝 3100에서 판별자 손실: 0.68727434 스텝 3100에서 적대적 손실: 0.6500176 스텝 3200에서 판별자 손실: 0.72109354 스텝 3200에서 적대적 손실: 0.7601031 스텝 3300에서 판별자 손실: 0.69486094 스텝 3300에서 적대적 손실: 0.7491412 스텝 3400에서 판별자 손실: 0.6895932 스텝 3400에서 적대적 손실: 0.7161566 스텝 3500에서 판별자 손실: 0.6968385 스텝 3500에서 적대적 손실: 0.7591454 스텝 3600에서 판별자 손실: 0.6983391 스텝 3600에서 적대적 손실: 0.75471365 스텝 3700에서 판별자 손실: 0.6767294 스텝 3700에서 적대적 손실: 0.8817333 스텝 3800에서 판별자 손실: 0.6919452 스텝 3800에서 적대적 손실: 0.75868195 스텝 3900에서 판별자 손실: 0.7031524 스텝 3900에서 적대적 손실: 0.7505781 스텝 4000에서 판별자 손실: 0.6838635 스텝 4000에서 적대적 손실: 0.75076956 스텝 4100에서 판별자 손실: 0.6887736 스텝 4100에서 적대적 손실: 0.7528058 스텝 4200에서 판별자 손실: 0.69331276 스텝 4200에서 적대적 손실: 0.79471785 스텝 4300에서 판별자 손실: 0.68512636 스텝 4300에서 적대적 손실: 0.75867164 스텝 4400에서 판별자 손실: 0.6883174 스텝 4400에서 적대적 손실: 0.7720972 스텝 4500에서 판별자 손실: 0.7012172 스텝 4500에서 적대적 손실: 0.67885685 스텝 4600에서 판별자 손실: 0.69478226 스텝 4600에서 적대적 손실: 0.7391167 스텝 4700에서 판별자 손실: 0.70749074 스텝 4700에서 적대적 손실: 0.8058073 스텝 4800에서 판별자 손실: 0.7214985 스텝 4800에서 적대적 손실: 1.0094569 스텝 4900에서 판별자 손실: 0.6968532 스텝 4900에서 적대적 손실: 0.76501334 스텝 5000에서 판별자 손실: 0.70068586 스텝 5000에서 적대적 손실: 0.8418042 스텝 5100에서 판별자 손실: 0.6901635 스텝 5100에서 적대적 손실: 0.73663354 스텝 5200에서 판별자 손실: 0.69752437 스텝 5200에서 적대적 손실: 0.72524816 스텝 5300에서 판별자 손실: 0.6945907 스텝 5300에서 적대적 손실: 0.7393595 스텝 5400에서 판별자 손실: 0.69078934 스텝 5400에서 적대적 손실: 0.74214107 스텝 5500에서 판별자 손실: 0.6939332 스텝 5500에서 적대적 손실: 0.7277419 스텝 5600에서 판별자 손실: 0.6989276 스텝 5600에서 적대적 손실: 0.74151313 스텝 5700에서 판별자 손실: 0.6865061 스텝 5700에서 적대적 손실: 0.7271993 스텝 5800에서 판별자 손실: 0.7233262 스텝 5800에서 적대적 손실: 0.99431247 스텝 5900에서 판별자 손실: 0.70499647 스텝 5900에서 적대적 손실: 1.0466232 스텝 6000에서 판별자 손실: 0.6851308 스텝 6000에서 적대적 손실: 0.7371407 스텝 6100에서 판별자 손실: 0.68779993 스텝 6100에서 적대적 손실: 0.7095089 스텝 6200에서 판별자 손실: 0.69324887 스텝 6200에서 적대적 손실: 0.7428337 스텝 6300에서 판별자 손실: 0.68435943 스텝 6300에서 적대적 손실: 0.6888761 스텝 6400에서 판별자 손실: 0.7009553 스텝 6400에서 적대적 손실: 0.7570043 스텝 6500에서 판별자 손실: 0.69860137 스텝 6500에서 적대적 손실: 0.8652438 스텝 6600에서 판별자 손실: 0.6948483 스텝 6600에서 적대적 손실: 0.81759083 스텝 6700에서 판별자 손실: 0.6920208 스텝 6700에서 적대적 손실: 0.71276224 스텝 6800에서 판별자 손실: 0.7139355 스텝 6800에서 적대적 손실: 0.7371713 스텝 6900에서 판별자 손실: 0.6853081 스텝 6900에서 적대적 손실: 0.73708713 스텝 7000에서 판별자 손실: 0.69366324 스텝 7000에서 적대적 손실: 0.79389805 스텝 7100에서 판별자 손실: 0.7064244 스텝 7100에서 적대적 손실: 0.7813763 스텝 7200에서 판별자 손실: 0.6926437 스텝 7200에서 적대적 손실: 0.7462541 스텝 7300에서 판별자 손실: 0.699135 스텝 7300에서 적대적 손실: 0.75480306 스텝 7400에서 판별자 손실: 0.69808084 스텝 7400에서 적대적 손실: 0.7792807 스텝 7500에서 판별자 손실: 0.6886368 스텝 7500에서 적대적 손실: 0.7415221 스텝 7600에서 판별자 손실: 0.698572 스텝 7600에서 적대적 손실: 0.77041376 스텝 7700에서 판별자 손실: 0.6963496 스텝 7700에서 적대적 손실: 0.7734369 스텝 7800에서 판별자 손실: 0.6873445 스텝 7800에서 적대적 손실: 0.78740144 스텝 7900에서 판별자 손실: 0.6971999 스텝 7900에서 적대적 손실: 0.78107464 스텝 8000에서 판별자 손실: 0.6985973 스텝 8000에서 적대적 손실: 0.7444295 스텝 8100에서 판별자 손실: 0.6955716 스텝 8100에서 적대적 손실: 0.7179367 스텝 8200에서 판별자 손실: 0.69686717 스텝 8200에서 적대적 손실: 0.86814547 스텝 8300에서 판별자 손실: 0.7090989 스텝 8300에서 적대적 손실: 0.8461113 스텝 8400에서 판별자 손실: 0.6789621 스텝 8400에서 적대적 손실: 0.7891714 스텝 8500에서 판별자 손실: 0.6828822 스텝 8500에서 적대적 손실: 0.744535 스텝 8600에서 판별자 손실: 0.73256254 스텝 8600에서 적대적 손실: 0.8267523 스텝 8700에서 판별자 손실: 0.6953441 스텝 8700에서 적대적 손실: 0.83365214 스텝 8800에서 판별자 손실: 0.67792505 스텝 8800에서 적대적 손실: 0.7299119 스텝 8900에서 판별자 손실: 0.69003236 스텝 8900에서 적대적 손실: 0.77839434 스텝 9000에서 판별자 손실: 0.68872595 스텝 9000에서 적대적 손실: 0.7916759 스텝 9100에서 판별자 손실: 0.6975969 스텝 9100에서 적대적 손실: 0.7680103 스텝 9200에서 판별자 손실: 0.68702763 스텝 9200에서 적대적 손실: 0.7282267 스텝 9300에서 판별자 손실: 0.7355779 스텝 9300에서 적대적 손실: 0.8837675 스텝 9400에서 판별자 손실: 0.68756074 스텝 9400에서 적대적 손실: 0.9133609 스텝 9500에서 판별자 손실: 0.6581781 스텝 9500에서 적대적 손실: 0.75360084 스텝 9600에서 판별자 손실: 0.6929166 스텝 9600에서 적대적 손실: 0.8556153 스텝 9700에서 판별자 손실: 0.6904553 스텝 9700에서 적대적 손실: 1.0184231 스텝 9800에서 판별자 손실: 0.65708625 스텝 9800에서 적대적 손실: 0.6254333 스텝 9900에서 판별자 손실: 0.68810195 스텝 9900에서 적대적 손실: 0.884272
가짜 이미지 몇개를 출력해 보죠:
import matplotlib.pyplot as plt
# 잠재 공간에서 랜덤한 포인트를 샘플링합니다
random_latent_vectors = np.random.normal(size=(10, latent_dim))
# 가짜 이미지로 디코딩합니다
generated_images = generator.predict(random_latent_vectors)
for i in range(generated_images.shape[0]):
img = image.array_to_img(generated_images[i] * 255., scale=False)
plt.figure()
plt.imshow(img)
plt.show()
픽셀 경계가 두드러진 개구리처럼 보이는 이미지를 얻었습니다.