- 딥러닝의 원리를 활용해 가상의 이미지를 생성하는 알고리즘
- 적대적(Adversarial) : 진짜 같은 가짜를 만들기 위해 GAN 알고리즘 내부에서는 ‘적대적’인 경합을 진행
- ex) 이안 굿펠로의 위조지폐범과 경찰
- 한쪽은 가짜를 만들고 한쪽은 진짜와 비교하는 경합의 과정을 이용하는 것이 바로 GAN의 원리
- 생성자 : 판별자가 구별할 수 없을 만큼 정교한 가짜를 만드는게 목표
- 옵티마이저 사용, 컴파일 과정이 없음 ➡️ 판별과 학습은 생성자에서 이루어지지 않음
- 풀링(pooling) 없음 : 일부 매개변수를 삭제하는 과정 없음
- 패딩 과정 포함됨 : 입력, 출력 크기를 맞춰야함 ➡️ 생성자의 결과물이 실제데이터와 같은 크기여야 하므로 차원축소문제 해결 필요
ex) MNIST 손글씨 인식 : 모든 손글씨 사진 28x28이라면 생성자에서 만들어질 이미지도 28x28이어야 함
✔️ padding = 'same' : 입력과 출력의 크기가 다를 경우 자동으로 크기를 확장해 주고, 확장된 공간에 0을 채워 넣을 수 있음.
✔️ 배치 정규화(Batch Normalization)
✔️ 활성화 함수
generator = Sequential()
generator.add(Dense(128*7*7, input_dim=100, activation=LeakyReLU(0.2))) … ➊
generator.add(BatchNormalization()) … ➋
generator.add(Reshape((7, 7, 128))) … ➌
generator.add(UpSampling2D()) … ➍
generator.add(Conv2D(64, kernel_size=5, padding='same')) … ➎
generator.add(BatchNormalization()) … ➏
generator.add(Activation(LeakyReLU(0.2))) … ➐
generator.add(UpSampling2D()) … ➑
generator.add(Conv2D(1, kernel_size=5, padding='same', activation='tanh')) … ➒
generator.add(Dense(128*7*7, input_dim=100, activation=LeakyReLU(0.2)))
generator.add(Reshape((7, 7, 128)))
: 컨볼루션 레이어가 받아줄 수 있는 형태로 바꿔줌generator.add(UpSampling2D()); generator.add(Conv2D(64, kernel_size=5, padding='same'))
generator.add(BatchNormalization())
: 데이터 배치를 정규 분포로 만듦generator.add(Conv2D(1, kernel_size=5, padding=‘same’, activation=‘tanh’))
: 한 번 더 컨볼루션 과정을 거친 후 판별자로 넘길 준비를 마침
- 학습하는 과정 없음
- 판별자는 판별만 진행함.
- 판별자가 얻은 가중치 ➡️ 생성자에게로 넘겨줘서 업데이트된 이미지 생성
✔️ 가중치를 저장하는 학습 기능을 꺼줘야함
discriminator = Sequential()
discriminator.add(Conv2D(64, kernel_size=5, strides=2, input_shape=(28,28,1), padding=“same”)) … ➊
discriminator.add(Activation(LeakyReLU(0.2))) … ➋
discriminator.add(Dropout(0.3)) … ➌
discriminator.add(Conv2D(128, kernel_size=5, strides=2, padding=“same”)) … ➍
discriminator.add(Activation(LeakyReLU(0.2))) … ➎
discriminator.add(Dropout(0.3)) … ➏
discriminator.add(Flatten()) … ➐
discriminator.add(Dense(1, activation=‘sigmoid’)) … ➑
discriminator.compile(loss=‘binary_crossentropy’, optimizer=‘adam’) … ➒
discriminator.trainable = False
discriminator.add(Conv2D(64, kernel_size=5, strides=2, input_shape=(28,28,1), padding=“same”))
- stride : 가로 세로 크기가 줄어들어 새로운 특징을 뽑아주는 효과 발생 ➡️ 드롭아웃, 풀링 사용한 효과와 비슷
- 생성자 : 출력수를 28로 맞추어야했기 때문에 UpSampling을 통해 가로 세로 크기를 늘려줌
- 판별자 : 진짜, 가짜만 구분하면 됨
discriminator.add(Flatten())
: 가로x세로(2차원) ➡️ 1차원discriminator.add(Dense(1, activation=‘sigmoid’))
: 판별결과는 진짜(1) / 가짜(0) 뿐이므로 sigmoid 사용discriminator.trainable = False
: 판별 종료 시 판별자가 스스로 학습되지 않게 학습 기능을 꺼둠.🔆GAN 기본 구조
- G() : 생성자 모델
- D() : 판별자 모델
- x : 실제 데이터
- input : 입력값
- G(input) : 생성자에 입력값 대입
- D(G(input)) : 위의 값을 판별자에 대입
- D(G(input)) = 1 : 생성자
- D(x) = 1 : 판별자 - 실제 데이터 대입시에만 참으로 판단함
➡️ 정확도가 0.5가 되면 학습 종료
- 너무나 유사해진 D(G(input))와 D(x)를 판별자가 더는 구별하지 못하게 되어 정확도가 0.5가 됨
✅ gan모델 생성
ginput = Input(shape=(100,)) … ➊
dis_output = discriminator(generator(ginput)) … ➋
gan = Model(ginput, dis_output) … ➌
gan.compile(loss='binary_crossentropy', optimizer='adam') … ➍
ginput = Input(shape=(100,))
: 생성자에 입력할 input 생성dis_output = discriminator(generator(ginput))
: 생성자 모델에 input값 대입 후 그 결과값을 판별자 모델에 대입gan = Model(ginput, dis_output)
: 생성자와 판별자를 연결한 모델 생성✅ gan_train() 함수 생성
def gan_train(epoch, batch_size, saving_interval):
(X_train, _), (_, _) = mnist.load_data() # 앞서 불러온 적 있는 MNIST를 재사용. 단, 테스트과정은 필요없고 이미지만 사용할 것이기 때문에 X_train만 불러옴.
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
X_train = (X_train - 127.5) / 127.5 # 픽셀값은 0에서 255사이의 값.
#X_train.shape, Y_train.shape, X_test.shape, Y_test.shape
(X_train, _), (_, _) = mnist.load_data()
: 판별자에서 사용할 mnist 데이터 불러옴X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
: 28 x 28 사이즈의 흑백 사진이므로 1로 설정X_train = (X_train - 127.5) / 127.5
: 생성자 코드에서 tanh를 사용한 이유(-1~1 사이의 값을 가짐)✅ 판별자에 데이터 입력 : 실제 데이터 입력
true = np.ones((batch_size, 1))
for i in range(epoch):
# 실제 데이터를 판별자에 입력하는 부분입니다.
idx = np.random.randint(0, X_train.shape[0], batch_size)
imgs = X_train[idx]
d_loss_real = discriminator.train_on_batch(imgs, true)
true = np.ones((batch_size, 1))
: 모든 값이 1인 배열 생성. idx = np.random.randint(0, X_train.shape[0], batch_size)
: random 함수를 통해 실제 이미지를 랜덤하게 선택imgs = X_train[idx]
: 랜덤하게 선택된 이미지discriminator.train_on_batch(imgs, true)
✅ 판별자에 데이터 입력 : 가짜 데이터 입력
fake = np.zeros((batch_size, 1))
for i in range(epoch):
#가상 이미지를 판별자에 입력하는 부분
noise = np.random.normal(0, 1, (batch_size, 100))
gen_imgs = generator.predict(noise)
d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
fake = np.zeros((batch_size, 1))
: 모든 값이 0인 배열 생성.noise = np.random.normal(0, 1, (batch_size, 100))
✅ 오차 계산
#판별자와 생성자의 오차를 계산합니다.
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
g_loss = gan.train_on_batch(noise, true)
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
: 판별자 오차g_loss = gan.train_on_batch(noise, true)
: 생성자 오차✅
from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Input, Dense, Reshape, Flatten, Dropout
from tensorflow.keras.layers import BatchNormalization, Activation, LeakyReLU, UpSampling2D, Conv2D
from tensorflow.keras.models import Sequential, Model
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
#이미지가 저장될 폴더가 없다면 만듭니다.
import os
if not os.path.exists("./gan_images"):
os.makedirs("./gan_images")
np.random.seed(3)
tf.random.set_seed(3)
#생성자 모델을 만듭니다.
generator = Sequential()
generator.add(Dense(128*7*7, input_dim=100, activation=LeakyReLU(0.2)))
generator.add(BatchNormalization())
generator.add(Reshape((7, 7, 128)))
generator.add(UpSampling2D())
generator.add(Conv2D(64, kernel_size=5, padding='same'))
generator.add(BatchNormalization())
generator.add(Activation(LeakyReLU(0.2)))
generator.add(UpSampling2D())
generator.add(Conv2D(1, kernel_size=5, padding='same', activation='tanh'))
#판별자 모델을 만듭니다.
discriminator = Sequential()
discriminator.add(Conv2D(64, kernel_size=5, strides=2, input_shape=(28,28,1), padding="same"))
discriminator.add(Activation(LeakyReLU(0.2)))
discriminator.add(Dropout(0.3))
discriminator.add(Conv2D(128, kernel_size=5, strides=2, padding="same"))
discriminator.add(Activation(LeakyReLU(0.2)))
discriminator.add(Dropout(0.3))
discriminator.add(Flatten())
discriminator.add(Dense(1, activation='sigmoid'))
discriminator.compile(loss='binary_crossentropy', optimizer='adam')
discriminator.trainable = False
#생성자와 판별자 모델을 연결시키는 gan 모델을 만듭니다.
ginput = Input(shape=(100,))
dis_output = discriminator(generator(ginput))
gan = Model(ginput, dis_output)
gan.compile(loss='binary_crossentropy', optimizer='adam')
gan.summary()
#신경망을 실행시키는 함수를 만듭니다.
def gan_train(epoch, batch_size, saving_interval):
# MNIST 데이터 불러오기
(X_train, _), (_, _) = mnist.load_data() # 앞서 불러온 적 있는 MNIST를 다시 이용합니다. 단, 테스트과정은 필요없고 이미지만 사용할 것이기 때문에 X_train만 불러왔습니다.
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
X_train = (X_train - 127.5) / 127.5 # 픽셀값은 0에서 255사이의 값입니다. 이전에 255로 나누어 줄때는 이를 0~1사이의 값으로 바꾸었던 것인데, 여기서는 127.5를 빼준 뒤 127.5로 나누어 줌으로 인해 -1에서 1사이의 값으로 바뀌게 됩니다.
#X_train.shape, Y_train.shape, X_test.shape, Y_test.shape
true = np.ones((batch_size, 1))
fake = np.zeros((batch_size, 1))
for i in range(epoch):
# 실제 데이터를 판별자에 입력하는 부분입니다.
idx = np.random.randint(0, X_train.shape[0], batch_size)
imgs = X_train[idx]
d_loss_real = discriminator.train_on_batch(imgs, true)
#가상 이미지를 판별자에 입력하는 부분입니다.
noise = np.random.normal(0, 1, (batch_size, 100))
gen_imgs = generator.predict(noise)
d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
#판별자와 생성자의 오차를 계산합니다.
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
g_loss = gan.train_on_batch(noise, true)
print('epoch:%d' % i, ' d_loss:%.4f' % d_loss, ' g_loss:%.4f' % g_loss)
# 이부분은 중간 과정을 이미지로 저장해 주는 부분입니다. 본 장의 주요 내용과 관련이 없어
# 소스코드만 첨부합니다. 만들어진 이미지들은 gan_images 폴더에 저장됩니다.
if i % saving_interval == 0:
#r, c = 5, 5
noise = np.random.normal(0, 1, (25, 100))
gen_imgs = generator.predict(noise)
# Rescale images 0 - 1
gen_imgs = 0.5 * gen_imgs + 0.5
fig, axs = plt.subplots(5, 5)
count = 0
for j in range(5):
for k in range(5):
axs[j, k].imshow(gen_imgs[count, :, :, 0], cmap='gray')
axs[j, k].axis('off')
count += 1
fig.savefig("gan_images/gan_mnist_%d.png" % i)
gan_train(4001, 32, 200) #4000번 반복되고(+1을 해 주는 것에 주의), 배치 사이즈는 32, 200번 마다 결과가 저장되게 하였습니다.
시작이미지:
epochs 4000:
- GAN vs AE
- 좌 : GAN ➡️ 진짜 같아보여도 실제로는 존재하지 않는 이미지 생성
- 우 : AE ➡️ 특징을 유추할 수 있는 것들을 모아 이미지로 생성
autoencoder = Sequential()
#인코딩
autoencoder.add(Conv2D(16, kernel_size=3, padding='same', input_shape=(28,28,1), activation='relu'))
autoencoder.add(MaxPooling2D(pool_size=2, padding='same')) #입력크기 축소
autoencoder.add(Conv2D(8, kernel_size=3, padding='same', activation='relu'))
autoencoder.add(MaxPooling2D(pool_size=2, padding='same'))
autoencoder.add(Conv2D(8, kernel_size=3, strides=2, padding='same', activation='relu'))
#디코딩
autoencoder.add(Conv2D(8, kernel_size=3, padding='same', activation='relu'))
autoencoder.add(UpSampling2D()) #크기 확대
autoencoder.add(Conv2D(8, kernel_size=3, padding='same', activation='relu'))
autoencoder.add(UpSampling2D())
autoencoder.add(Conv2D(16, kernel_size=3, activation='relu'))
autoencoder.add(UpSampling2D())
autoencoder.add(Conv2D(1, kernel_size=3, padding='same', activation='sigmoid'))
#전체 구조 확인
autoencoder.summary()
autoencoder.add(Conv2D(16, kernel_size=3, activation='relu'))
: 패딩이 없으므로 마스크가 적용되면서 크기가 줄어듦from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Input, Dense, Conv2D, MaxPooling2D, UpSampling2D, Flatten, Reshape
import matplotlib.pyplot as plt
import numpy as np
(X_train, _), (X_test, _) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32') / 255
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1).astype('float32') / 255
#생성자 모델 만들기
autoencoder = Sequential()
#인코딩 부분
autoencoder.add(Conv2D(16, kernel_size=3, padding='same', input_shape=(28,28,1), activation='relu'))
autoencoder.add(MaxPooling2D(pool_size=2, padding='same'))
autoencoder.add(Conv2D(8, kernel_size=3, padding='same', activation='relu'))
autoencoder.add(MaxPooling2D(pool_size=2, padding='same'))
autoencoder.add(Conv2D(8, kernel_size=3, strides=2, padding='same', activation='relu'))
autoencoder.add(Conv2D(8, kernel_size=3, padding='same', activation='relu'))
autoencoder.add(UpSampling2D())
autoencoder.add(Conv2D(8, kernel_size=3, padding='same', activation='relu'))
autoencoder.add(UpSampling2D())
autoencoder.add(Conv2D(16, kernel_size=3, activation='relu'))
autoencoder.add(UpSampling2D())
autoencoder.add(Conv2D(1, kernel_size=3, padding='same', activation='sigmoid'))
autoencoder.summary()
autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
autoencoder.fit(X_train, X_train, epochs=50, batch_size=128, validation_data=(X_test, X_test))
# 컴파일 및 학습을 하는 부분
random_test = np.random.randint(X_test.shape[0], size=5)
ae_img = autoencoder.predict(X_test)
plt.figure(figsize=(7, 2))
for i, img_idx in enumerate(random_test):
ax = plt.subplot(2, 7, i+1)
plt.imshow(X_test[img_idx].reshape(28,28))
ax.axis('off')
ax = plt.subplot(2, 7, 7 + i + 1)
plt.imshow(ae_img[img_idx].reshape(28,28))
ax.axis('off')
plt.show()