네. 글을 날리고 다시 쓰는 GAN 입니다.
역시 생성적 적대 신경망이라고 하면 어색하군요. GAN.... 처음 접했을 때 어려웠던 부분입니다. 신기하긴한데 이론이랑 코드가 어려웠습니다. 다시 실습해보면서 공부해보도록 하겠습니다. 많아요 많을거에요.... 재밌으면서 신기한데 어려워요....
GAN(Generative Adversarial Networks)은 딥러닝의 원리를 활용해 가상의 이미지를 생성하는 알고리즘입니다. 음 예를 들자면 얼굴 이미지를 만들기, Midjourney, Dalle 같은 이미지 생성, 그리고 이미지 복원 등이 있습니다.
GAN을 설명하는 데에는 위조지폐범과 경찰의 예를 가장 많이 듭니다.
위조지폐범은 위조지폐를 진짜 지폐처럼 만들려고 할 것이고 경찰을 이를 진짜 지폐와 비교하여 잡아낼 것입니다.
계속 생성하고 비교하는 경합 과정에서 위조지폐가 더욱 정교해진다는 원리이죠.
위조지폐범인 가짜를 만들어 내는 파트를 '생성자(Generator)'이라 하고 경찰인 진위를 가려내는 파트를 '판별자(Discriminator)'라고 합니다. 이런 기본 구조에 여러가지를 합성하여 GAN이 만들어지고 있는데요 이번 실습은 GAN에 CNN을 적용한 것인 DCGAN(Deep Convolutional GAN)을 해보려 합니다.
1. 생성자
생성자(Generator)는 가상의 이미지를 만들어 내는 공장이라고 할 수 있습니다. 랜덤한 픽셀 값으로 채워진 가짜 이미지로 시작해서 판별자의 결과에 따라 지속적으로 업데이트하며 점차 원하는 이미지가 만들어집니다.
실습해 볼 DCGAN에 쓰이는 CNN은 원래의 CNN과 차이가 있습니다.
- optimizer를 사용하는 최적화 과정이나 컴파일하는 과정이 없습니다.
음? 그러면 판별이나 학습을 어디서 할까요. 일단 이건 나중에 알아보겠습니다.
- 풀링(Pooling: 일부 매개변수를 삭제) 과정이 없고 Padding 과정이 있습니다.
입력 크기와 출력 크기를 맞추어 주기 위해서 Padding이 필요합니다. 판별자가 비교할 때는 진짜와 똑같은 크기가 되어야 하기 때문입니다.
- 배치 정규화(Batch Normalization) 과정이 있습니다.
배치 정규화는 입력 데이터의 평균이 0, 분산이 1이 되도록 재배치하는 것인데, 다음 층으로 입력될 값을 일정하게 재배치하는 역할을 하게 됩니다. 이것으로 층이 늘어나도 안정적으로 학습할 수 있게 됩니다.
활성화 함수 : ReLU, tanh
다음은 생성자의 코드입니다.
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'))
2번 줄에서 128은 노드의 수, 7*7은 이미지 최초 크기, input_dim은 n차원 크기의 랜덤 벡터입니다.
7*7 부분이 중요한데요, 5번 줄과 9번 줄에 UpSampling2D로 가로세로 크기를 2배씩 늘려주었습니다. 따라서 7*7 → 14*14 → 28*28 이 되어 MNIST 데이터 크기와 같아집니다.
DCGAN은 이렇게 이미지가 커지면서 합성곱 층을 지나가는 것이 특징입니다.
6, 10번 줄에서 컨볼루션 과정을 처리하고 각각 활성화함수를 LeakyReLU와 tanh를 썼습니다.
3, 7번 줄에서 배치 정규화를 진행했습니다.
2. 판별자
판별자(Discriminator)는 생성자에서 넘어온 이미지의 진위 여부를 판별해 주는 장치입니다. 이 부분은 무언가를 구별하는 데 최적화된 알고리즘인 CNN의 구조를 그대로 가져오면 됩니다. 비교해서 진짜, 가짜를 구별하는 목적과 일치하죠?
중요한 점은 자신이 학습하면 안됩니다. 판별만 해야한다는 것입니다. 판별자가 얻은 가중치는 생성자로 넘겨서 업데이트된 이미지를 만들 수 있도록 해야합니다.
판별자 코드는 다음과 같습니다.
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
2, 5번 줄에서 컨볼루션 과정을 진행해 준 것을 볼 수 있습니다.
처음 보는 'strides'가 나왔는데 이 친구는 커널 윈도를 몇 칸씩 이동시킬지를 결정하는 옵션입니다. 그러면 2이니까 두 칸을 움직이라는 말이겠죠? 이것으로 크기가 더 줄어들어 새로운 특징을 뽑아주는 효과를 얻었습니다.
8, 9번 줄에서 2차원을 1차원으로 바꿔주기 위해 Flatten을 써주었고, 이진 판별을 위해 sigmoid를 써주었습니다.
마지막 줄에선 학습 기능을 꺼주었구요.
3. 실행
드디어 실행 코드에 왔습니다.
먼저 생성자와 판별자를 연결해주어야 합니다. 생성자와 판별자를 함수로 나타내어보겠습니다.
생성자 G()에 입력값을 넣어주고 판별자 D()에 넣습니다. 생성자는 D(G(input))이 참(1)이라 주장하지만, 판별자는 실제 데이터인 x로 만든 D(x)만 참이라고 여깁니다. 학습이 진행될수록 D(G(input))이 진짜와 비슷해져 잘 구별하지 못하게 됩니다. 정확도가 0.5, 즉 1/2에 가까워지면 판별자가 구별을 잘 하지 못하게 된 것을 의미하므로 학습은 종료됩니다.
코드는 다음과 같습니다.
ginput = Input(shape=(100,))
dis_output = discriminator(generator(ginput))
gan = Model(ginput, dis_output)
gan.compile(loss='binary_crossentropy', optimizer='adam')
gan.summary()
판별 결과인 dis_output 은 위의 D(G(input))을 의미합니다.
gan이라는 모델을 만들어 생성자에 입력할 ginput, dis_output을 넣습니다.
컴파일까지 해서 모델을 만들었습니다.
학습을 진행해 보겠습니다.
def gan_train(epoch, batch_size, saving_interval):
# MNIST 데이터를 불러오기
(X_train, _), (_, _) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
X_train = (X_train - 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)
함수 안에서 4번 줄에서는 모두 참이라는 배열을 만들고 반복문 안에서 1번 줄에서 랜덤하게 실제 이미지를 선택해 불러옵니다. 0부터 X_train 사이의 숫자를 선택해 batch_size 만큼 반복해서 가져오는 것입니다.
imgs에서 이미지를 불러오고 train_on_batch로 판별합니다. 만든 이미지를 imgs에, 만든 배열 true를 넣어준 것입니다.
함수 안에서 5번 줄에서는 모두 거짓이라는 배열을 만들고 반복문안의 noise에서 가상 이미지를 만듭니다. 0에서 1까지 실수에서 선택하는 것을 batch_size만큼 반복합니다. gen_imgs에 결과값이 저장되고 d_loss_fake에서 gen_imgs가 모두 거짓 레이블이 붙습니다.
d_loss_real과 d_loss_fake가 판별자에서 번갈아 가며 진위를 판단합니다. 그 오차는 d_loss에 저장됩니다.
마지막 입니다.
gan 모델로 생성자 오차 g_loss를 구합니다. 생성자의 레이블은 무조건 참으로 해서 판별자로 넘기죠.
학습이 진행되는 동안 오차가 출력되도록 합니다.
실행해 볼까요?
...
...
흠 뭔가 무섭게 생겼습니다. 그래도 신기합니다..
역시 GAN은 어렵습니다...... 원리가 신기한데 뭔가 상상이 안되는 느낌인데 생성이 돼.....
이번 글은 좀 길었던거 같습니다. 끝까지 봐주셔서 감사합니다.
다음 글에서는 이미지를 만드는 다른 알고리즘에 대해서 알아보겠습니다.
참고: [모두의 딥러닝]
'프로그래밍 > 인공지능' 카테고리의 다른 글
전이 학습(transfer learning) - 1 (0) | 2023.08.05 |
---|---|
오토인코더(Auto-Encoder) (0) | 2023.08.04 |
RNN - LSTM & CNN (0) | 2023.08.01 |
RNN - LSTM 활용 (0) | 2023.07.31 |
순환 신경망(Recurrent Neural Network) (0) | 2023.07.30 |