[GAN] 적대적 생성 신경망 <숫자생성AI> 제작

2021. 2. 21. 17:26Artificial_Intelligence/Computer Vision

숫자 생성 인공지능 원리

생성 신경망 중 GAN을 이용함.

GAN (Generative Adversarial Network)

  • 적대적 생성 신경망.

2개의 신경망으로 이루어져 있음. 판별자 신경망 생성자 신경망.

  • 생성자는 진짜같은 가짜를 생성함.
  • 판별자는 진짜그림과 생성자가 만든 가짜 그림을 구별 가능.

두 신경망은 목표가 있음.

  • 생성자 : 판별자가 자신이 만든 그림을 진짜처럼 생각할 정도의 그림을 그림.
  • 판별자 : 생성자가 만든 그림을 진짜인지 가짜인지 전부 구별해야함.

이제 두 신경망은 서로 이기기 위해 학습을 시작함.

이렇게 두 개의 신경망을 사용하여 이러한 원리로 생성해 내는 기법이 GAN(적대적 생성 신경망)임.

 

개발 환경 세팅

from keras.models import Model, Sequential
from keras.layers import Dense, Input
from keras.layers.advanced_activations import LeakyReLU
from keras.optimizers import Adam
from keras.datasets import mnist

from tqdm import tqdm
import numpy
import matplotlib.pyplot as plt

from keras.models import Model, Sequential 

모델 도구중에 모델과 시퀀셜 모델함수를 불러옴.GAN은 두개의 신경망이 필요하고, 이 두 신경망은 모두 시퀀셜 형태이기에 필요함.

from keras.layers.advanced_activations import LeakyReLU

렐루함수와 Leaky렐루함수의 차이점

  • 렐루함수 : 0보다 작은 값 들어오면 0을 반환.
  • Leaky렐루 : 0보다 작은 값 들어오면 음수의 값 반환.

from keras.optimizers import Adam

학습한 모델의 오차 줄이기 위해 경사하강법 사용. 이때 사용하는 옵티마이저는 아담 옵티마이저.

데이터 불러오기.

(x_train, y_train), (x_test, y_test) = mnist.load_data()
print(len(x_train))
print(len(y_train))
print(len(x_test))
print(len(y_test))

x_train = (x_train.astype(numpy.float32) - 127.5)/127.5
mnist_data = x_train.reshape(60000,784)
print(mnist_data.shape)
len(mnist_data)

mnist의 데이터에는 훈련용데이터와 검증용 데이터가 있고 각 6만개, 1만개가 있음.

훈련용데이터 6만개를 data로 사용할 것임.

 

x_train = (x_train.astype(numpy.float32) - 127.5)/127.5

일단 데이터의 정규화를 위해 -1 ~ 1 사이의 값으로 만들어줌. mnist 데이터셋의 그림은 0~ 255까지의 숫자로 이루어져 있어서 127.5를 빼고 127.5로 나누어주면 0은 -1로, 255는 1로 만들어줌. 정규화끝

mnist_data = x_train.reshape(60000,784)

mnist데이터는 28* 28개의 픽셀데이터로 이루어져 있음. 그래서 한줄로 넣기위해 784 * 1로 설정함.

생성자 신경망 제작

def create_generator():
    generator = Sequential()
    generator.add(Dense(units=256, input_dim=100))
    generator.add(LeakyReLU(0.2))
    generator.add(Dense(units=512))
    generator.add(LeakyReLU(0.2))
    generator.add(Dense(units=784, activation='tanh'))
    return generator
g= create_generator()
g.summary()

input_dim 은 입력 형태. 시퀀셜모델을 사용함.(순차적으로 레이어를 쌓음) 

 

generator.add(Dense(units=256, input_dim=100))

input_dim 입력뉴런의 수를 100개로 설정. 신경망의 첫번째 층은 256개의 노드로 설정. 100을 설정해서 100개의 픽셀값이 랜덤한 값을 가짐.(노이즈값)

 generator.add(LeakyReLU(0.2))

활성화함수를 leakyRelu로 설정. 음수값은 특정한 기울기를 보이는데, 이 기울기의 값을 0.2로 설정하는것.

tanh : 하이퍼볼릭탄젠트 함수. 마지막 출력층은 현재 mnist 데이터 모습이 1*784로 되어있어서 784개의 픽셀을 나열한 모습으로 나타내야하기에 784로 설정.

 

판별자 신경망 제작

def create_discriminator():
    discriminator = Sequential()
    discriminator.add(Dense(units=512, input_dim=784))
    discriminator.add(LeakyReLU(0.2))
    discriminator.add(Dense(units=256))
    discriminator.add(LeakyReLU(0.2))
    discriminator.add(Dense(units=1, activation='sigmoid'))
    discriminator.compile(loss = 'binary_crossentropy', optimizer = Adam(lr=0.002, beta_1=0.5))
    return discriminator
d = create_discriminator()
d.summary()

입력뉴런의 수, input_dim은 생성자가 만든 글씨가 784픽셀로 이루어져있기에 입력값을 784로 설정.

촤종출력 노드는 1개. 진짜글씨면 1, 생성자가 만든 가짜글씨면 0.

 

discriminator.compile(loss = 'binary_crossentropy', optimizer = Adam(lr=0.002, beta_1=0.5))

둘 중 하나 고르는 문제이니 오차값(loss)은 이항교차 엔트로피 사용. 학습속도(학습률)인 lr 은 0.002로, 베타 최적화 값은 0.5로 설정.

(여기서 학습속도, 학습률은 경사하강법에서 오차가 작아지는 방향으로 가중치의 값을 이동할 때, 한번에 얼만큼의 크기로 이동할 것인지를 뜻함.)

 

GAN 생성 함수 제작

생성자 신경망과 판별자 신경망을 제작했으니 GAN을 만듬.

def create_gan(discriminator, generator) :
    discriminator.trainable = False
    gan_input = Input(shape = (100,))
    x = generator(gan_input)
    gan_output = discriminator(x)
    gan = Model(inputs = gan_input, outputs = gan_output)
    gan.compile(loss='binary_crossentropy', optimizer = 'adam')
    return gan

gan = create_gan(d,g)
gan.summary()

create_gan 함수는 인자값 두개, 판별자와 생성자를 입력받음.

discriminator.trainable = False 이건 판별자가 학습하지 못하게 막는 코드임.

input은 입력할 데이터의 모습을 정하는 코드임. 콤마 , 뒤에 빈 곳은 자동으로 실제 총 데이터의 개수를 자동으로 넣어줌. 아까 60000개 설정했으니 6만을 자동으로 넣어줌.

입력값은 생성자 신경망이 만든 그림, 출력값은 판별자 신경망이 판단한 결과임.

  • 입력층은 노이즈값이 100개의 픽셀값이 들어감.
  • 두번째 레이어는 생성자 신경망에서 출력된 값의 모습
  • 세번째 출력층은 가짜인지 진짜인지 판단한 결과.

결과 확인 함수 제작

만든 신경망들을 훈련시키고 정확도를 확인해야함.

def plot_generated_images(generator) :
    noise = numpy.random.normal(loc=0, scale=1, size=[100,100])
    generated_images = generator.predict(noise)
    generated_images = generated_images.reshape(100,28,28)

    plt.figure(figsize=(10,10))
    for i in range(generated_images.shape[0]) :
        plt.subplot(10,10, i+1)
        plt.imshow(generated_images[i], interpolation='nearest')
        plt.axis('off')
    plt.tight_layout()

def plot_generated_images(generator) 파라미터는 어떤 생성자로 그릴지 설정.

 

noise = numpy.random.normal(loc=0, scale=1, size=[100,100]) 생성자에 넣어줄 노이즈 값.

균일한 값을 생성하도록 정규 분포 함수 normal사용.

첫번째 인자값은 평균이 0, 두번째 인자값 1은 평균에서 얼만큼 떨어져 있는지 (-1 ~ 1 사이의 값), 마지막 인자값은 노이즈 100개 생성하고 각 노이즈는 숫자 100개씩으로 구성되어있다는 뜻.

즉, 함수를 호출할 때마다 100개의 그림을 그려달라는 명령이다.

 

generated_images = generator.predict(noise) generated도 신경망 모델이기에 앞에서 만든 노이즈값을 신경망에 넣어서 예측하라는 명령.

 

generated_images = generated_images.reshape(100,28,28) 그림의 형태가 1 * 784의 형태이기에 28 * 28 사이즈로 바꿈.

 

plt.figure(figsize=(10,10)) 그림의 크기는 10 * 10으로 설정.

 

for i in range(generated_images.shpae[0]) 100개의 그림이니 100번 반복.

 

plt.subplot(10,10, i+1) 위치를 지정해주고,

 

plt.imshow(generated_images[i], interpolation='nearest') 이미지를 출력함. interpolation함수는 그림을 출력할 때, 각 픽셀을 어떻게 나타낼지 결정함. i번째 그림의 위치는 generated_images[i]에 넣음

 

plt.axis('off') 그림 이름은 넣지 않음. plt.tight_layout() 그림 화면에 출력.

 

GAN 훈련

batch_size = 128
epochs = 5000
for e in tqdm(range(epochs)) :
    noise = numpy.random.normal(0,1,[batch_size, 100])
    generated_images = g.predict(noise)
    image_batch = mnist_data[numpy.random.randint(low=0, high= mnist_data.shape[0], size = batch_size)]
    
    x = numpy.concatenate([image_batch, generated_images])

    y_dis = numpy.zeros( 2* batch_size)
    y_dis[:batch_size] = 1

    d.trainable = True
    d.train_on_batch(x, y_dis)
    
    noise = numpy.random.normal(0,1, [batch_size, 100])
    y_gen = numpy.ones(batch_size)
    d.trainable = False
    gan.train_on_batch(noise, y_gen)
    if e==0 or e % 1000 == 0 :
        plot_generated_images(g)   

이런식으로 증가함

 

batch_size = 128로 설정. epochs = 총 5000번 돌도록 설정.

tqdm()은 진행과정을 시각화하여 보여주는 라이브러리. 진행표시바이다.

 

noise = numpy.random.normal(0,1,[batch_size, 100])

생성자에게 줄 노이즈값 정규분포함수사용하여 batch_size만큼의 개수만큼 생성. 생성된 각 데이터는 숫자 100개로 구성되어 있다는 의미.

generated_images = g.predict(noise)

생성자 신경망 모델에 노이즈를 입력하여 그림을 그리도록 만드는 명령어.(학습)

image_batch = mnist_data[numpy.random.randint(low=0, high= mnist_data.shape[0], size = batch_size)]

mnist 데이터셋이 현재 6만개로 설정해 놓았는데 이중에서 batch_size 개수(128개)만 랜덤으로 뽑아서 가져옴. 학습할 때 마다 다양한 모양의 손글씨를 학습할 수 있게 설정한 것. 첫번째(low=0)부터 mnist_data.shape[0](mnist데이터셋 개수) 에서 batch_size만큼만 랜덤으로 고름.

x = numpy.concatenate([image_batch, generated_images])

numpy라이브러리에서 concatenate는 합치는 함수임 진짜그림과 가짜그림을 한줄로 붙게 만들어서 x에 넣음. 각 그림은 128개만큼 있으니 x는 총 256개의 데이터로 이루어져있음. 각 데이터에는 -1 ~ 1 사이의 값이 784개씩 들어있음.

y_dis = numpy.zeros( 2* batch_size)로 256개의 데이터 값을 0으로 만들어주고,

 y_dis[:batch_size] = 1로 이중 첫번째로 들어온 데이터 128개만 값을 1로 설정해줌.

이제 판별자는 처음나오는 128개의 데이터는 진짜, 나중에 들어온 128개는 가짜라는 것을 알 수 있음. (distinguish = 구별하다)

 

d.trainable = True 판별자 신경망이 학습할 수 있도록(True) 설정.

 

d.train_on_batch(x, y_dis) 신경망을 학습시킴.

입력데이터값은 x, 출력데이터값은 y_dis로 설정. 이제 입력데이터를 주면 판별자를 통해 나온 출력값과 정답 데이터의 결과값을 비교하여 오차를 줄일 수 있음.

 

판별자는 학습을 시켰으니, 이제 생성자를 학습시킴.

 

noise = numpy.random.normal(0,1, [batch_size, 100]) 새로운 노이즈값 생성.

 

y_gen = numpy.ones(batch_size) gan에 넣어줄 값을 모두 1로 설정.

가짜그림과 진짜그림 모두 진짜로 오해하도록 만듬.

 

d.trainable = False 판별자의 학습을 그만시킴. 학습하지 않고 판단만 함.

 

gan.train_on_batch(noise, y_gen) gan에 노이즈값을 입력데이터로 넣고 출력값을 모두 진짜라고 하고 넣어서 학습시킴. 이러면 GAN은 판별자가 진짜 그림이라고 생각할수 있게 생성자를 훈련시킴.

판별자 신경망을 거쳐서 나온 값과 y_gen과 비교하면서 판별결과가 1이 나올 때 까지 생성자를 학습시킴.

 

if e==0 or e % 1000 == 0 : plot_generated_images(g) 첫번째 에포크와 각 1000단위의 에포크일때 생성자가 만든 그림을 출력함.

 

#후기

생각보다 생성된 숫자가 그저그런것 같다. 6만개중에 배치사이즈를 128개만 선택했을 때, 여기서 깔끔한 데이터들이 안걸리면 결과값이 안좋아지는 것 같다. 조금만 수정을 하면 결과가 좋을 것 같다.

728x90