[딥러닝]5. 딥러닝 활용 <4>GAN

4. GAN(생성적 적대 신경망, Generative Adversarial Networks)

  • 딥러닝의 원리를 활용해 가상의 이미지를 생성하는 알고리즘
  • 적대적(Adversarial) : 진짜 같은 가짜를 만들기 위해 GAN 알고리즘 내부에서는 ‘적대적’인 경합을 진행
    • ex) 이안 굿펠로의 위조지폐범과 경찰
      - 한쪽은 가짜를 만들고 한쪽은 진짜와 비교하는 경합의 과정을 이용하는 것이 바로 GAN의 원리
      - 생성자 : 판별자가 구별할 수 없을 만큼 정교한 가짜를 만드는게 목표
  • 생성자(Generator) : 가짜를 만들어내는 파트
  • 판별자(Discriminator) : 진위를 가려내는 파트

✔️ DCGAN(Deep Convolutional GAN) :

  • 컨볼루션 신경망을 GAN에 적용

1) 생성자

  • 가상의 이미지를 만들어냄
  • 랜덤한 픽셀 값으로 채워진 가짜 이미지 생성 ➡️ 판별자의 판별 결과에 따라 지속적으로 업데이트 ➡️ 점점 원하는 이미지로 가까워짐
    • 옵티마이저 사용, 컴파일 과정이 없음 ➡️ 판별과 학습은 생성자에서 이루어지지 않음
    • 풀링(pooling) 없음 : 일부 매개변수를 삭제하는 과정 없음
    • 패딩 과정 포함됨 : 입력, 출력 크기를 맞춰야함 ➡️ 생성자의 결과물이 실제데이터와 같은 크기여야 하므로 차원축소문제 해결 필요
      ex) MNIST 손글씨 인식 : 모든 손글씨 사진 28x28이라면 생성자에서 만들어질 이미지도 28x28이어야 함

      ✔️ padding = 'same' : 입력과 출력의 크기가 다를 경우 자동으로 크기를 확장해 주고, 확장된 공간에 0을 채워 넣을 수 있음.

✔️ 배치 정규화(Batch Normalization)

  • 입력 데이터의 평균이 0, 분산이 1이 되도록 재배치하는 것
  • 다음 층으로 입력될 값을 일정하게 재배치하는 역할 ➡️ 층이 증가해도 안정적인 학습 가능
  • BatchNormalization() 함수 제공

✔️ 활성화 함수

  • 생성자 활성화 함수 : ReLU()
  • 판별자로 넘겨주기 직전 : tanh()
    • 출력되는 값 -1~1 사이로 만듦
    • 판별자에 손글씨 픽셀 범위도 -1~1 사이로 맞추면 판별 조건이 됨
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)))
    • 노드 수 : 128(임의 지정)
    • 이미지 최초 크기 : 7*7
      • 기존 이미지 크기 : 28*28
    • input_dim : 100
      • 100차원 크기의 랜덤한 벡터 준비해서 집어 넣음
    • activation : LeackReLU(0.2)
      • 0보다 작을 경우 0.2를 곱하라는 의미
  • generator.add(Reshape((7, 7, 128))) : 컨볼루션 레이어가 받아줄 수 있는 형태로 바꿔줌
    • Conv2D 함수의 input_shape에 들어갈 부분의 형태를 정해줌
  • generator.add(UpSampling2D()); generator.add(Conv2D(64, kernel_size=5, padding='same'))
    • UpSampling2D() : 가로, 세로 크기 2배로 늘려줌. ➡️ 2번 거치면서 각각 1414, 2828로 변경됨
    • Conv2D : 컨볼루션 레이어
      • 이전 작은 크기였던 이미지를 사이즈 점점 크게 늘려 컨볼루션 레이어를 지나게 함
    • Conv2DTranspose() : UpSampling2D() + Conv2D
    • kernel_size : 마스크 사이즈
  • generator.add(BatchNormalization()) : 데이터 배치를 정규 분포로 만듦
  • generator.add(Conv2D(1, kernel_size=5, padding=‘same’, activation=‘tanh’)) : 한 번 더 컨볼루션 과정을 거친 후 판별자로 넘길 준비를 마침

2) 판별자(discriminator)

  • 생성자로부터 넘어온 이미지의 가짜, 진짜 판별
  • 컨볼루션 신경망 사용 : 분류에 최적화 되어 있음
    • 학습하는 과정 없음
      • 판별자는 판별만 진행함.
      • 판별자가 얻은 가중치 ➡️ 생성자에게로 넘겨줘서 업데이트된 이미지 생성
        ✔️ 가중치를 저장하는 학습 기능을 꺼줘야함
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”))
    • 노드 수 : 64
    • kernel_size : 5x5 마스크 사용
    • strides : 마스크 이동 범위 지정
      • 설정 안함 : 1칸씩 이동
      • strides=2 : 2칸씩 이동
    • stride : 가로 세로 크기가 줄어들어 새로운 특징을 뽑아주는 효과 발생 ➡️ 드롭아웃, 풀링 사용한 효과와 비슷
      • 생성자 : 출력수를 28로 맞추어야했기 때문에 UpSampling을 통해 가로 세로 크기를 늘려줌
      • 판별자 : 진짜, 가짜만 구분하면 됨
  • discriminator.add(Flatten()) : 가로x세로(2차원) ➡️ 1차원
  • discriminator.add(Dense(1, activation=‘sigmoid’)) : 판별결과는 진짜(1) / 가짜(0) 뿐이므로 sigmoid 사용
  • discriminator.trainable = False : 판별 종료 시 판별자가 스스로 학습되지 않게 학습 기능을 꺼둠.

3) 적대적 신경망 실행

  • 생성자와 판별자를 연결시키고 학습을 진행하며 기타 여러 가지 옵션을 설정
    • 생성자와 판별자 연결 : 생성자에서 나온 출력을 판별자에 넣어서 진위여부 확인

🔆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 생성
    • shape=(100,) : 랜덤한 100개의 벡터 생성
  • 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
  • 매개변수 : epoch, batch_size, saving_interval
    • saving_interval : 중간 과정 저장할 때 몇번마다 저장할지 결정
  • (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인 배열 생성.
    • batch_size 길이만큼 만들어서 d_loss_real에서 사용
  • idx = np.random.randint(0, X_train.shape[0], batch_size) : random 함수를 통해 실제 이미지를 랜덤하게 선택
    • np.random.randint(a, b, c) : a부터 b까지의 숫자 중 하나를 랜덤하게 선택하는 과정을 c번 반복
    • 0~X_train 개수 사이의 숫자를 랜덤하게 선택해 batch_size만큼 반복
  • imgs = X_train[idx] : 랜덤하게 선택된 이미지
  • discriminator.train_on_batch(imgs, true)
    • train_on_batch(x,y) : 판별시작 ➡️ 입력값과 레이블을 받아 한번만 학습을 실시하여 모델을 업데이트함
      • x(입력값) : X_train[idx]
      • y(레이블) : 미리 만들어둔 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))
    • np.random.normal(a,b,c) : a부터 b까지의 실수 중 c개를 랜덤하게 선택
    • (batch_size, 100) : 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) : 판별자 오차
    • d_loss_real + d_loss_fake 더해서 둘로 나눈 평균
  • g_loss = gan.train_on_batch(noise, true) : 생성자 오차
    • gan 모델 : 생성자와 판별자를 연결해두었음
    • x : gen_idx
    • y : 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:

✔️ 오토인코더(Auto-Encoder, AE)

  • 입력데이터의 특징을 효율적으로 담아낸 이미지 생성
    • GAN vs AE
      • 좌 : GAN ➡️ 진짜 같아보여도 실제로는 존재하지 않는 이미지 생성
      • 우 : AE ➡️ 특징을 유추할 수 있는 것들을 모아 이미지로 생성
  • 활용 : 영상 의학 분야 등 데이터가 불충분한 곳 ➡️ 부족한 학습데이터 수를 늘려주는 효과
  • 오토인코더 구조 : 입력값 x와 출력값 x' 사이에 차원이 작은 노드 포함됨
    • 입력한 이미지와 같은 크기의 출력층 생성
    • 입력층보다 적은 수의 노드를 가진 은닉층을 넣음으로 차원 축소
    • 소실된 데이터 복원을 위한 학습 진행
    • 입력 데이터의 특징을 효율적으로 응축한 출력이 나오는 원리

1) 인코딩, 디코딩 과정

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()
  • input_shape : 28 x 28
    • MaxPooling2D : 2배씩 크기 축소 ➡️ 2번
    • UpSampling2D() : 2배씩 크기 확대 ➡️ 3번
      • autoencoder.add(Conv2D(16, kernel_size=3, activation='relu')) : 패딩이 없으므로 마스크가 적용되면서 크기가 줄어듦
      • max_pooling2d_1 : 7개의 벡터값 ➡️ padding 적용되어 빈 자리에 0으로 채워줌
      • conv2d_2 : 4개의 벡터를 갖게 됨
      • up_sampling2d_1 : 16개의 벡터
      • conv2d_5 : padding 미적용되고 kernel_size=3이므로 3x3 마스크가 지나가면서 벡터 크기 2 줄임.

2) 전체 코드

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()    

좋은 웹페이지 즐겨찾기