0. 들어가기에 앞서
pix2pix관련 포스팅이 굉장히 많이 되어 있지만, 도저히 내용을 이해할 수 없어서 소스 코드를 통해 내용을 이해하는 것을 목적으로하는 포스팅이다. 우선 간단히 pix2pix에 대해 설명하자면, 지금까지의 이미지 생성과 같이 파라미터부터 이미지로부터 하지 않고, 이미지로부터 이미지를 생성하는 모델이다. DCGAN이라고 불리는 이미지 생성의 기술에 이용되고 있으며, 야간촬영한 이미지로부터 한 낮에 촬영한 이미지로의 상호 교환, 흑백이미지에 채색하는 것, 스케치를 이미지로 변환하는 등의 모든 사례에 있어서 Pix2Pix는 우수한 결과를 냈다.
pix2pix는 넓은 의미로 CGAN(Conditional GAN)의 한 종류이다. CGAN에서는 '조건 벡터와 이미지의 짝'을 학습 데이터로써 그 대응관계를 학습하지만, pix2pix에서는 '조건 이미지와 이미지의 짝'를 학습데이터로써 그 대응관계를 학습한다. 즉, pix2pix는 조건 벡터 대신에 조건 이미지를 사용해, 이미지로부터 이미지로의 변환 문제를 취급하는 CGAN이라고 말할 수 있다. 여기서의 '변환'은 '번역'에 가까운 뉘앙스이다.
pix2pix의 기본구성은 CGAN과 비슷하며 아래의 그림과 같다.
G : Generator D : Discriminator x : 조건 이미지(학습 데이터) y : 실제 이미지(학습 데이터) z : 노이즈 벡터 G(x,z) : Generator가 생성한 가짜 이미지 |
Generator은 조건 이미지 x와 노이즈 벡터 z로부터 이미지 G(x,z)를 생성한다. 한편 Discriminator은 '조건 이미지 x와 실제이미지 y의 짝'과 '조건 이미지x와 생성이미지G(x,z)의 짝'이 각각 진짜인지 아닌지를 판별한다. 이러한 구성에 의해, Generator이 조건 이미지로부터 진짜와 같은 이미지를 생성할 수 있도록 이미지 짝의 관계를 학습한다. 조금 더 자세한 설명은 링크를 참조하길 바란다.
1. 이번에 해 볼 것
이번에는 어떤 이미지로부터 특정영역을 발견하는 것을 해 볼 생각이다. 원본의 이미지 중와 그 안에 어떤 영역을 표시하는 마스크 이미지를 이용해 학습시켜 테스트용의 이미지를 건냈을 때 어떤 영역을 발견해내는 느낌이다.
2. 준비
먼저 학습 데이터를 준비한다. 이번에는 hdf5(Hierarchical Data Format Version5)형식으로 구성하고 있다. 이미지를 네 종류 (학습용 원본, 학습용 결과, 테스용 원본, 테스트 결과)로 나눠 폴더에 저장해놓는다.
- 학습용 원본 : train_data_raw
- 학습용 결과: train_data_gen
- 테스트용 원본: val_data_raw
- 테스트용 결과: val_data_gen
3. 각 종 선언
import numpy as np
import glob
import h5py
from keras.preprocessing.image import load_img, img_to_array
이미지를 numpy의 배열로 변환하여 hdf5로 보존하기 위해 필요한 패키지를 선언한다.
4. 입출력 폴더
inpath = '.\\input'
outpath = '.\\output'
학습용의 이미지 데이터를 넣는 폴더와 작성한 hdf5파일의 보존처를 지정한다. 사실 어디든 괜찮지만, 인수 지정할 수 있도록 하는 것이 좋다.
5. 폴더 읽어들이기
orgs = []
masks = []
print('original img')
files = glob.glob(inpath+'\\org\\*.jpg')
for imgfile in files:
print(imgfile)
img = load_img(imgfile, target_size=(64,64))
imgarray = img_to_array(img)
orgs.append(imgarray)
print('mask img')
files = glob.glob(inpath+'\\mask\\*.jpg')
for imgfile in files:
print(imgfile)
img = load_img(imgfile, target_size=(64,64))
imgarray = img_to_array(img)
masks.append(imgarray)
입력 폴더에 'org'와 'mask'라는 폴더가 있으며, 그 안에는 원본 이미지(JPEG)와 마스크 이미지(JPEG)가 들어 있다는 전제하에 작성됐다. 또한, 원본 이미지와 마스크 이미지의 조합은 같은 폴더명으로 매칭되어 있다. 이 부분은 각자 환경에 맞춰 수정하면 된다고 생각한다.
glob에서 파일리스트를 취득해, for문을 돌려 각각의 파일을 읽어 들인다. 이미지는 64x64로 리사이즈해, numpy의 배열로 변환후, 모아두고 있다. 이미지 사이즈는 각각의 환경에 맞게 조정하면 된다.
6. 분할
perm = np.random.permutation(len(orgs))
orgs = np.array(orgs)[perm]
masks = np.array(masks)[perm]
threshold = len(orgs)//10*9
imgs = orgs[:threshold]
gimgs = masks[:threshold]
vimgs = orgs[threshold:]
vgimgs = masks[threshold:]
print('shapes')
print('org imgs : ', imgs.shape)
print('mask imgs : ', gimgs.shape)
print('test org : ', vimgs.shape)
print('test tset : ', vgimgs.shape)
여기서는 읽어인 이미지를 학습용과 테스트용으로 분할한다. 먼저 이미지의 순서를 랜덤으로 배치한 뒤, 학습용과 테스트용을 9:1의 비율로 분할한다. (원본/마스크용)
최종적으로 numpy의 배열은 아래와 같은 (이미지수, 세로, 가로, RGB)의 4차원 배열이 된다. 마스크의 이미지도 RGB이다.
org imgs : (900, 64, 64, 3)
7. hdf5파일의 보존
outh5 = h5py.File(outpath+'.hdf5', 'w')
outh5.create_dataset('train_data_raw', data=imgs)
outh5.create_dataset('train_data_gen', data=gimgs)
outh5.create_dataset('val_data_raw', data=vimgs)
outh5.create_dataset('val_data_gen', data=vgimgs)
outh5.flush()
outh5.close()
출력 폴더에 'datasetimages.hdf5'라는 파일을 만든다. 분할한 이미지를 폴더에 담은 형식으로 보존된다.
8. pix2pix 본체
DCGAN의 이미지는 이런 느낌이다. G(Generator)은 이미지를 생성하고, D(Discriminator)에서 물체가 진짜인지 가짜인지 판단한다. Generator에서 생성된 것은 가짜이고, 원본의 조합은 진짜가 된다.
* 이번에는 실제 이미지로부터 마스크를 생성하므로, 위의 이미지와는 x와 G(x) 또는 y가 반대로 되는 느낌이다.
9. 각 종 선언
import numpy as np
import h5py
import time
import matplotlib.pylab as plt
import keras.backend as K
from keras.utils import generic_utils
from keras.optimizers import Adam, SGD
from keras.models import Model
from keras.layers.core import Flatten, Dense, Dropout, Activation, Lambda, Reshape
from keras.layers.convolutional import Conv2D, Deconv2D, ZeroPadding2D, UpSampling2D
from keras.layers import Input, Concatenate
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.layers.pooling import MaxPooling2D
import keras.backend as K
%matplotlib inline
사용할 패키지를 한 번에 선언하였다. 기본적으로 필요한 것은 keras이다. time은 시간 측정용이다. 또한, 주피터 노트북에서 matplotlib를 표시하기 위해서 '%matplotlib inline'를 입력했다.
10. 변수의 선언
datasetpath = '.\\output\\datasetimages.hdf5'
patch_size = 32
batch_size = 12
epoch = 1000
학습용 데이터의 파일이나 이미지를 분할한 배치의 사이즈, 미니 배치의 사이즈, 반복 에포크 수를 지정한다. 이와 비슷한 인수로 하는 것이 좋다고 생각된다.
11. 학습 데이터 읽어들이기
def normalization(X):
return X / 127.5 - 1
def load_data(datasetpath):
with h5py.File(datasetpath, "r") as hf:
X_full_train = hf["train_data_raw"][:].astype(np.float32)
X_full_train = normalization(X_full_train)
X_sketch_train = hf["train_data_gen"][:].astype(np.float32)
X_sketch_train = normalization(X_sketch_train)
X_full_val = hf["val_data_raw"][:].astype(np.float32)
X_full_val = normalization(X_full_val)
X_sketch_val = hf["val_data_gen"][:].astype(np.float32)
X_sketch_val = normalization(X_sketch_val)
return X_full_train, X_sketch_train, X_full_val, X_sketch_val
사전에 작성한 학습 데이터의 파일을 읽어들인다. 폴더 각각 읽어들여, 0~255를 -1~1의 값(float32)로 변환하여 보존한다.
- 학습용 원본 이미지 : X_full_train
- 학습용 마스크 이미지 : X_sketch_train
- 테스트용 원본 이미지 : X_full_val
- 테스트용 마스크 이미지 : X_sketch_val
12. 모델
Generator, Discriminator을 작성한다.
1) 공통 블록
def conv_block_unet(x, f, name, bn_axis, bn=True, strides=(2,2)):
x = LeakyReLU(0.2)(x)
x = Conv2D(f, (3,3), strides=strides, name=name, padding='same')(x)
if bn: x = BatchNormalization(axis=bn_axis)(x)
return x
def up_conv_block_unet(x, x2, f, name, bn_axis, bn=True, dropout=False):
x = Activation('relu')(x)
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(f, (3,3), name=name, padding='same')(x)
if bn: x = BatchNormalization(axis=bn_axis)(x)
if dropout: x = Dropout(0.5)(x)
x = Concatenate(axis=bn_axis)([x, x2])
return x
Generator이나 Discriminator에서 공통으로 사용하는 블록을 작성한다. 'conv_block_unet'은 아래와 같은 처리를 한다.
- 활성화함수 'LeakyReLU'
- Convolution
- 정규화 'Batch Normalization'
디폴트의 stride가 (2,2)로 되어있으므로 pooling이 아닌 Conv2D에서 화소수를 반으로 하고 있다.
'up_conv_block_unet'에서는 아래와 같은 처리가 실행된다.
- 활성화 함수 'ReLU'
- UpSampling
- Convolution
- 정규화 'Batch Normalization'
- Dropout (지정되어 있다면)
- 합성
Dropout은 학습시의 노이즈가 되어 고정의 이미지만 생성하는 것을 방지하는 역할을 한다. 그리고 여기서 '합성'이 이번의 포인트가 될 것이라고 생각한다. pix2pix에서는 UNET모델이라는 네트워크를 사용하여, Encode-Decode모델과 달리Convolution을 했을 때의 정보를 Upsampling할 때 사용하고 있다.
이러한 '블록'의 중간에 관하여는 궁리한 보람이 있다는 생각이든다. ( 잘못하면, 전혀 괜찮지 않은 생성을 할 수 있게 될 가능성이 있지만)
2) Generator
def generator_unet_upsampling(img_shape, disc_img_shape, model_name="generator_unet_upsampling"):
filters_num = 64
axis_num = -1
channels_num = img_shape[-1]
min_s = min(img_shape[:-1])
unet_input = Input(shape=img_shape, name="unet_input")
conv_num = int(np.floor(np.log(min_s)/np.log(2)))
list_filters_num = [filters_num*min(8, (2**i)) for i in range(conv_num)]
# Encoder
first_conv = Conv2D(list_filters_num[0], (3,3), strides=(2,2), name='unet_conv2D_1', padding='same')(unet_input)
list_encoder = [first_conv]
for i, f in enumerate(list_filters_num[1:]):
name = 'unet_conv2D_' + str(i+2)
conv = conv_block_unet(list_encoder[-1], f, name, axis_num)
list_encoder.append(conv)
# prepare decoder filters
list_filters_num = list_filters_num[:-2][::-1]
if len(list_filters_num) < conv_num-1:
list_filters_num.append(filters_num)
# Decoder
first_up_conv = up_conv_block_unet(list_encoder[-1], list_encoder[-2],
list_filters_num[0], "unet_upconv2D_1", axis_num, dropout=True)
list_decoder = [first_up_conv]
for i, f in enumerate(list_filters_num[1:]):
name = "unet_upconv2D_" + str(i+2)
if i<2:
d = True
else:
d = False
up_conv = up_conv_block_unet(list_decoder[-1], list_encoder[-(i+3)], f, name, axis_num, dropout=d)
list_decoder.append(up_conv)
x = Activation('relu')(list_decoder[-1])
x = UpSampling2D(size=(2, 2))(x)
x = Conv2D(disc_img_shape[-1], (3,3), name="last_conv", padding='same')(x)
x = Activation('tanh')(x)
generator_unet = Model(inputs=[unet_input], outputs=[x])
return generator_unet
UNET을 사용해 이미지를 생성하는 처리를 한다.
필터의 계층 수는 동적으로 결정한다.
Generator의 입출력 이미지는 배치로 나누지 않은 원본의 사이즈의 것을 사용한다. 한편 Encoder에서 Convolution을 실시하여, Decoder에서 Upsampling한다. Decoder의 블록에서는 Upsampling하는 데이터와 대응하는 Encoder데이터를 전달하여 중간에서 결합한다.
Decoder에서는 초기에 추가한 블록과 그 이후 2블록만 Dropout을 지정한다. 최종 계층의 Upsamping에서는 활성화함수로 'tanh'을 사용한다. (그러므로, 학습 데이터를 -1~1로 변환하는 것이다.)
이 UNET의 부분을 다른 것으로 치환하는 것도 가능하다. 여기까지의 내용의 요점은 어떠한 방법으로 입력데이터를 바탕으로 이미지를 생성하는 것이라고 할 수 있다.
3) Discriminator
def DCGAN_discriminator(img_shape, disc_img_shape, patch_num, model_name='DCGAN_discriminator'):
disc_raw_img_shape = (disc_img_shape[0], disc_img_shape[1], img_shape[-1])
list_input = [Input(shape=disc_img_shape, name='disc_input_'+str(i)) for i in range(patch_num)]
list_raw_input = [Input(shape=disc_raw_img_shape, name='disc_raw_input_'+str(i)) for i in range(patch_num)]
axis_num = -1
filters_num = 64
conv_num = int(np.floor(np.log(disc_img_shape[1])/np.log(2)))
list_filters = [filters_num*min(8, (2**i)) for i in range(conv_num)]
# First Conv
generated_patch_input = Input(shape=disc_img_shape, name='discriminator_input')
xg = Conv2D(list_filters[0], (3,3), strides=(2,2), name='disc_conv2d_1', padding='same')(generated_patch_input)
xg = BatchNormalization(axis=axis_num)(xg)
xg = LeakyReLU(0.2)(xg)
# First Raw Conv
raw_patch_input = Input(shape=disc_raw_img_shape, name='discriminator_raw_input')
xr = Conv2D(list_filters[0], (3,3), strides=(2,2), name='raw_disc_conv2d_1', padding='same')(raw_patch_input)
xr = BatchNormalization(axis=axis_num)(xr)
xr = LeakyReLU(0.2)(xr)
# Next Conv
for i, f in enumerate(list_filters[1:]):
name = 'disc_conv2d_' + str(i+2)
x = Concatenate(axis=axis_num)([xg, xr])
x = Conv2D(f, (3,3), strides=(2,2), name=name, padding='same')(x)
x = BatchNormalization(axis=axis_num)(x)
x = LeakyReLU(0.2)(x)
x_flat = Flatten()(x)
x = Dense(2, activation='softmax', name='disc_dense')(x_flat)
PatchGAN = Model(inputs=[generated_patch_input, raw_patch_input], outputs=[x], name='PatchGAN')
x = [PatchGAN([list_input[i], list_raw_input[i]]) for i in range(patch_num)]
if len(x)>1:
x = Concatenate(axis=axis_num)(x)
else:
x = x[0]
x_out = Dense(2, activation='softmax', name='disc_output')(x)
discriminator_model = Model(inputs=(list_input+list_raw_input), outputs=[x_out], name=model_name)
return discriminator_model
※PatchGAN부분
먼저 원본 이미지와 마스크 이미지를 1회 Convolution한다. (pooling을 사용하지 않고 Conx2D의 stride로 반으로 한다) 그 이후, 위에서 구한 각 배치마다의 데이터를 결합하여 다시 Convolution하여 전결합층에서 1차원으로 변환되어 softmax로 boolean값(True, False)분류한다.
그 결과는 전 배치에서 결합되어 최종적으로 softmax에서 boolean값이 된다.
4) DCGAN
def DCGAN(generator, discriminator, img_shape, patch_size):
raw_input = Input(shape=img_shape, name='DCGAN_input')
genarated_image = generator(raw_input)
h, w = img_shape[:-1]
ph, pw = patch_size, patch_size
list_row_idx = [(i*ph, (i+1)*ph) for i in range(h//ph)]
list_col_idx = [(i*pw, (i+1)*pw) for i in range(w//pw)]
list_gen_patch = []
list_raw_patch = []
for row_idx in list_row_idx:
for col_idx in list_col_idx:
raw_patch = Lambda(lambda z: z[:, row_idx[0]:row_idx[1], col_idx[0]:col_idx[1], :])(raw_input)
list_raw_patch.append(raw_patch)
x_patch = Lambda(lambda z: z[:, row_idx[0]:row_idx[1], col_idx[0]:col_idx[1], :])(genarated_image)
list_gen_patch.append(x_patch)
DCGAN_output = discriminator(list_gen_patch+list_raw_patch)
DCGAN = Model(inputs=[raw_input],
outputs=[genarated_image, DCGAN_output],
name='DCGAN')
return DCGAN
Generator에서 생성된 이미지는 Discriminator에 전달되어 판별된다.
이미지는 배치로 분활되어 전달된다. 그것을 'PatchGAN'이라고 불리는 구성이 된다.
작은 영역에서 진짜/가짜를 평가하는 것으로 고주파성분의 특징을 얻을 수 있다.
5) 호출
def load_generator(img_shape, disc_img_shape):
model = generator_unet_upsampling(img_shape, disc_img_shape)
return model
def load_DCGAN_discriminator(img_shape, disc_img_shape, patch_num):
model = DCGAN_discriminator(img_shape, disc_img_shape, patch_num)
return model
def load_DCGAN(generator, discriminator, img_shape, patch_size):
model = DCGAN(generator, discriminator, img_shape, patch_size)
return model
호출하는 것일뿐 기본적으로 의미는 없다.
13. 학습
여기서는 모델을 호출하여, 실제로 학습처리한다.
1) 공통 처리
def l1_loss(y_true, y_pred):
return K.sum(K.abs(y_pred - y_true), axis=-1)
def inverse_normalization(X):
return (X + 1.) / 2.
def to3d(X):
if X.shape[-1]==3: return X
b = X.transpose(3,1,2,0)
c = np.array([b[0],b[0],b[0]])
return c.transpose(3,1,2,0)
L1정규화를 설정하거나, 정규화 ( -1~1 → 0~1 )을 돌리거나, 배열의 요소의 순서를 바꾸거나한다.
2) 결과 표시
def plot_generated_batch(X_proc, X_raw, generator_model, batch_size, suffix):
X_gen = generator_model.predict(X_raw)
X_raw = inverse_normalization(X_raw)
X_proc = inverse_normalization(X_proc)
X_gen = inverse_normalization(X_gen)
Xs = to3d(X_raw[:5])
Xg = to3d(X_gen[:5])
Xr = to3d(X_proc[:5])
Xs = np.concatenate(Xs, axis=1)
Xg = np.concatenate(Xg, axis=1)
Xr = np.concatenate(Xr, axis=1)
XX = np.concatenate((Xs,Xg,Xr), axis=0)
plt.imshow(XX)
plt.axis('off')
plt.savefig("current_batch_"+suffix+".png")
plt.clf()
plt.close()
이미지를 생성하여, 정답과 비교하기 위한 표시를 실시한다. 원본 이미지, 생성 이미지, 마스크 이미지의 순서로 최대 5장 표시한다.
화면에 이미지를 표시하지 않고, 파일에 보존하고 있다.
def extract_patches(X, patch_size):
list_X = []
list_row_idx = [(i*patch_size, (i+1)*patch_size) for i in range(X.shape[1] // patch_size)]
list_col_idx = [(i*patch_size, (i+1)*patch_size) for i in range(X.shape[2] // patch_size)]
for row_idx in list_row_idx:
for col_idx in list_col_idx:
list_X.append(X[:, row_idx[0]:row_idx[1], col_idx[0]:col_idx[1], :])
return list_X
def get_disc_batch(procImage, rawImage, generator_model, batch_counter, patch_size):
if batch_counter % 2 == 0:
# produce an output
X_disc = generator_model.predict(rawImage)
y_disc = np.zeros((X_disc.shape[0], 2), dtype=np.uint8)
y_disc[:, 0] = 1
else:
X_disc = procImage
y_disc = np.zeros((X_disc.shape[0], 2), dtype=np.uint8)
X_disc = extract_patches(X_disc, patch_size)
return X_disc, y_disc
batch_counter이 짝수일때는 생성한 이미지를 반환하고, 홀수일 때는 입력된 이미지를 반환한다. 이 때, 이미지를 배치로 분할하고 또한 정답/오답 데이터 (2값)도 작성하여 반환한다.
※ 홀수일 때, 왜 정답/오답 라벨이 전체 0이 되는지 잘 모르겠다.
'y_disc[:, 1] = 1'이 없어도 왜 잘 작동되는지 이유를 모르겠다.
3) 학습
def train():
# load data
rawImage, procImage, rawImage_val, procImage_val = load_data(datasetpath)
img_shape = rawImage.shape[-3:]
patch_num = (img_shape[0] // patch_size) * (img_shape[1] // patch_size)
disc_img_shape = (patch_size, patch_size, procImage.shape[-1])
# train
opt_dcgan = Adam(lr=1E-3, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
opt_discriminator = Adam(lr=1E-3, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
# load generator model
generator_model = load_generator(img_shape, disc_img_shape)
# load discriminator model
discriminator_model = load_DCGAN_discriminator(img_shape, disc_img_shape, patch_num)
generator_model.compile(loss='mae', optimizer=opt_discriminator)
discriminator_model.trainable = False
DCGAN_model = load_DCGAN(generator_model, discriminator_model, img_shape, patch_size)
loss = [l1_loss, 'binary_crossentropy']
loss_weights = [1E1, 1]
DCGAN_model.compile(loss=loss, loss_weights=loss_weights, optimizer=opt_dcgan)
discriminator_model.trainable = True
discriminator_model.compile(loss='binary_crossentropy', optimizer=opt_discriminator)
# start training
print('start training')
for e in range(epoch):
starttime = time.time()
perm = np.random.permutation(rawImage.shape[0])
X_procImage = procImage[perm]
X_rawImage = rawImage[perm]
X_procImageIter = [X_procImage[i:i+batch_size] for i in range(0, rawImage.shape[0], batch_size)]
X_rawImageIter = [X_rawImage[i:i+batch_size] for i in range(0, rawImage.shape[0], batch_size)]
b_it = 0
progbar = generic_utils.Progbar(len(X_procImageIter)*batch_size)
for (X_proc_batch, X_raw_batch) in zip(X_procImageIter, X_rawImageIter):
b_it += 1
X_disc, y_disc = get_disc_batch(X_proc_batch, X_raw_batch, generator_model, b_it, patch_size)
raw_disc, _ = get_disc_batch(X_raw_batch, X_raw_batch, generator_model, 1, patch_size)
x_disc = X_disc + raw_disc
# update the discriminator
disc_loss = discriminator_model.train_on_batch(x_disc, y_disc)
# create a batch to feed the generator model
idx = np.random.choice(procImage.shape[0], batch_size)
X_gen_target, X_gen = procImage[idx], rawImage[idx]
y_gen = np.zeros((X_gen.shape[0], 2), dtype=np.uint8)
y_gen[:, 1] = 1
# Freeze the discriminator
discriminator_model.trainable = False
gen_loss = DCGAN_model.train_on_batch(X_gen, [X_gen_target, y_gen])
# Unfreeze the discriminator
discriminator_model.trainable = True
progbar.add(batch_size, values=[
("D logloss", disc_loss),
("G tot", gen_loss[0]),
("G L1", gen_loss[1]),
("G logloss", gen_loss[2])
])
# save images for visualization
if b_it % (procImage.shape[0]//batch_size//2) == 0:
plot_generated_batch(X_proc_batch, X_raw_batch, generator_model, batch_size, "training")
idx = np.random.choice(procImage_val.shape[0], batch_size)
X_gen_target, X_gen = procImage_val[idx], rawImage_val[idx]
plot_generated_batch(X_gen_target, X_gen, generator_model, batch_size, "validation")
print("")
print('Epoch %s/%s, Time: %s' % (e + 1, epoch, time.time() - starttime))
학습처리의 전체이다. 먼저 학습 데이터를 불러들인다. 다음 Generator, Discriminator, DCGAN의 각 모델을 로드하여, 각각 목적 함수를 설정하여 컴파일해 사용할 수 있다.
- Generator : 평균절대오차
- Discriminator : 크로스 엔트로피 (2분류)
- DCGAN : L1정규화를 실시한 크로스 엔트로피 (2분류)
여기 L1정규화를 실시하는 것이 pix2pix의 또 다른 포인트이다. 여기서 흐림을 더함으로써 저주파 성분의 특징을 파악할 수 있게 되는 것 같다. 한편 학습은 먼저 에포크로 반복된다. 그 중간에 미니 배치용으로 학습 데이터를 작성(순서는 매번 랜덤)하여, 이번에는 미니 배치 단위로 실행된다. 또한 중간에 배치를 나눈 이미지를 작성하여, 원본의 이미지와 진짜의 마스크 이미지 혹은 가짜의 마스크 이미지(Generator에서 생성)의 조합으로 Discriminaotr을 학습시킨다.
※ 여기서는 Discriminator만 학습되도록 한다.
최종적으로 원본의 이미지와 가짜의 마스크 이미지(Generator에서 생성)의 조합을 사용하여 Discriminator을 갱신하지 않도록하면서 DCGAN을 학습시킨다.
※ 여기서는 Generator만 학습되도록 한다.
학습후, 테스트 데이터를 사용하여 정기적으로 이미지를 생성해 보존한다.
14. 실행
train()
학습처리를 호출할 뿐이다.
(cf) Tensorflow버전은 아래의 링크를 참조
https://www.tensorflow.org/tutorials/generative/pix2pix
참고자료
https://spjai.com/pix2pix-image-generation/#1_pix2pix
'IT > AI\ML' 카테고리의 다른 글
[python] PCA와 ICA의 개요와 차이점 (2) | 2020.04.30 |
---|---|
Manifold Learning(多様体学習)과 알고리즘 (0) | 2020.04.30 |
[논문] GAN ; BigGAN (Large Scale GAN Training for High Fidelity Natural Image Synthesis) (0) | 2020.04.28 |
[연구동향] CNN(Convolution Neural Networks)의 최신 연구 동향(~2017년) (0) | 2020.04.28 |
[연구동향] GAN의 주요 연구 역사(~2019년 11월) (0) | 2020.04.24 |