본문 바로가기

basic_ML_models

basic_GAN

잠재 공간 보간법을 수행해 실존 인물이 아닌 새로운 유명 인사의 얼굴을 생성할 수 있다는 사실로 증명되었다.

GAN은 생성기와 판별기라고 하는 두 적대 관계의 네트워크를 훈련시킴으로써 입력 분포를 모델링하는 방법을 학습할 수 있다. 

생성적 적대 신경망 Generative Adversarial Network, GAN 

GAN 원리

생성기의 역할을 판별기를 속일 수 있는 위조 데이터 또는 신호를 생성한느 방법을 계속해서 알아내는 것이다. 

판별기는 위조와 실제 신호를 구분하도록 훈련된다.

훈련이 진행될수록 판별기는 생성된 데이터와 실제 데이터의 차이를 구분할 수 없게 될 것이다. 

그림에서 보듯이 GAN은 생성기와 판별기라는 두 네트워크로 구성된다. 

생성기의 입력은 노이즈고, 출력은 합성된 신호다.

판별기의 입력은 진짜 신호이거나 합성된 신호다. 진짜 신호는 샘플 데이터에서 샘플링하지만 가짜 신호는 생성기로부터 받는다. 

정기적으로 생성기는 자기가 만든 출력이 진짜 신호인 척 하면서 GAN에게 그 출력에 1 로 레이블을 붙일 것을 요청한다. 그런 다음 가짜 신호를 판별기에게 보이면 자연적으로 그 신호는 0에 가까운 레이블을 붙여 가짜 신호로 분류된다. 또한 이 새로운 데이터에 훈련할 때 자신이 예측한 값을 포함시킨다. 즉, 판별기가 자신의 예측을 의심하고 있으므로 GAN은 예측이 빗나갈 가능성을 염두한다. 이때 GAN은 이 경사를 판별기의 마지막 계층에서 생성기의 첫 번째 계층으로 역전파시킨다. 그렇지만 이 단계에서 판별기 매개변수는 일시적으로 고정되고 생성기는 이 경사를 활용해 매개변수를 업데이트하고 가짜 신호를 합성하는 능력을 개선한다.

전체 프로세스를 종합적으로 보면, 두 네트워크가 동시에 협업하는 과정과 비슷하다. GAN 훈련이 수렴하면 최종 결과로 신호를 합성할 수 있는 생성기를 얻게 된다. 판별기는 이 합성된 신호가 진짜라고 생각하고 1에 가까운 레이블을 부여한다. 

가장 중요한 부분은 생성기-판별기 네트워크를 안정적으로 훈련시키는 방법, 

손실 함수는 판별기의 출력에서 계산될 수 있기 때문에 매개변수 업데이트가 빠르다. 두 수렴속도의 차이는 수렴에 실패하게 되는 원인이된다.

def build_generator(inputs, image_size):
    """생성기 모델 구현

    가짜 이미지 생성을 위한 BN-ReLU-Conv2DTranpose 스택
    출력 계층 활성화 함수로 sigmoid 사용, 
    Args:
        inputs (Layer): 생성기의 입력 계층
        image_size (tensor): 이미지 한 변의 목표 크기
    Returns:
        generator (Model): 생성기 모델
    """

    image_resize = image_size // 4
    # 네트워크 파라미터
    kernel_size = 5
    layer_filters = [128, 64, 32, 1]

    x = Dense(image_resize * image_resize * layer_filters[0])(inputs)
    x = Reshape((image_resize, image_resize, layer_filters[0]))(x)

    for filters in layer_filters:
        # first two convolution layers use strides = 2
        # the last two use strides = 1
        if filters > layer_filters[-2]:
            strides = 2
        else:
            strides = 1
        x = BatchNormalization()(x)
        x = Activation('relu')(x)
        x = Conv2DTranspose(filters=filters,
                            kernel_size=kernel_size,
                            strides=strides,
                            padding='same')(x)

    x = Activation('sigmoid')(x)
    generator = Model(inputs, x, name='generator')
    return

생성기 네트워크 모델

판별기는 CNN 기반의 분류 모델과 유사, 입력은 1이나 0 둘 중 하나로 분류된다. 

결과는 베르누이 분포로 모델링된다. 따라서 이진 교차-엔트로피 손실 함수가 사용된다.

생성기와 판별기 모델을 구성한 다음, 생성기와 판별기 네트워크를 결합해 적대적 모델을 구성한다. 두 네트워크 모두 RMSprop 최적화 기법을 사용한다. 

적대적 신경망의 학습속도를 판별기 학습 속도의 반으로 설정하면 더 안정적으로 훈련시킬 수 있다.

def build_generator(inputs, image_size):
    """Build a Generator Model
    Stack of BN-ReLU-Conv2DTranpose to generate fake images
    Output activation is sigmoid instead of tanh in [1].
    Sigmoid converges easily.
    Arguments:
        inputs (Layer): Input layer of the generator 
            the z-vector)
        image_size (tensor): Target size of one side
            (assuming square image)
    Returns:
        generator (Model): Generator Model
    """

    image_resize = image_size // 4
    # network parameters 
    kernel_size = 5
    layer_filters = [128, 64, 32, 1]

    x = Dense(image_resize * image_resize * layer_filters[0])(inputs)
    x = Reshape((image_resize, image_resize, layer_filters[0]))(x)

    for filters in layer_filters:
        # first two convolution layers use strides = 2
        # the last two use strides = 1
        if filters > layer_filters[-2]:
            strides = 2
        else:
            strides = 1
        x = BatchNormalization()(x)
        x = Activation('relu')(x)
        x = Conv2DTranspose(filters=filters,
                            kernel_size=kernel_size,
                            strides=strides,
                            padding='same')(x)

    x = Activation('sigmoid')(x)
    generator = Model(inputs, x, name='generator')
    return

판별기 네트워크, 생성기 모델이 인스턴스화된다. 적대적 모델은 생성기와 판별기를 결합하기만 하면 된다. GAN에서 배치 크기를 64로 설정하는 것이 가장 일반적이다.

DCGAN 모델을 구성하는데 있어 어려운 점은 네트워크 설계를 조금만 변경해도 훈련 중 수렴이 쉽게 깨질 수 있다는 것

def train(models, x_train, params):
    """Train the Discriminator and Adversarial Networks
    Alternately train Discriminator and Adversarial networks by batch.
    Discriminator is trained first with properly real and fake images.
    Adversarial is trained next with fake images pretending to be real
    Generate sample images per save_interval.
    Arguments:
        models (list): Generator, Discriminator, Adversarial models
        x_train (tensor): Train images
        params (list) : Networks parameters
    """
    # the GAN component models
    generator, discriminator, adversarial = models
    # network parameters
    batch_size, latent_size, train_steps, model_name = params
    # the generator image is saved every 500 steps
    save_interval = 500
    # noise vector to see how the generator output evolves during training
    noise_input = np.random.uniform(-1.0, 1.0, size=[16, latent_size])
    # number of elements in train dataset
    train_size = x_train.shape[0]
    for i in range(train_steps):
        # train the discriminator for 1 batch
        # 1 batch of real (label=1.0) and fake images (label=0.0)
        # randomly pick real images from dataset
        rand_indexes = np.random.randint(0, train_size, size=batch_size)
        real_images = x_train[rand_indexes]
        # generate fake images from noise using generator 
        # generate noise using uniform distribution
        noise = np.random.uniform(-1.0,
                                  1.0,
                                  size=[batch_size, latent_size])
        # generate fake images
        fake_images = generator.predict(noise)
        # real + fake images = 1 batch of train data
        x = np.concatenate((real_images, fake_images))
        # label real and fake images
        # real images label is 1.0
        y = np.ones([2 * batch_size, 1])
        # fake images label is 0.0
        y[batch_size:, :] = 0.0
        # train discriminator network, log the loss and accuracy
        loss, acc = discriminator.train_on_batch(x, y)
        log = "%d: [discriminator loss: %f, acc: %f]" % (i, loss, acc)

        # train the adversarial network for 1 batch
        # 1 batch of fake images with label=1.0
        # since the discriminator weights 
        # are frozen in adversarial network
        # only the generator is trained
        # generate noise using uniform distribution
        noise = np.random.uniform(-1.0,
                                  1.0, 
                                  size=[batch_size, latent_size])
        # label fake images as real or 1.0
        y = np.ones([batch_size, 1])
        # train the adversarial network 
        # note that unlike in discriminator training, 
        # we do not save the fake images in a variable
        # the fake images go to the discriminator input of the adversarial
        # for classification
        # log the loss and accuracy
        loss, acc = adversarial.train_on_batch(noise, y)
        log = "%s [adversarial loss: %f, acc: %f]" % (log, loss, acc)
        print(log)
        if (i + 1) % save_interval == 0:
            # plot generator images on a periodic basis
            plot_images(generator,
                        noise_input=noise_input,
                        show=False,
                        step=(i + 1),
                        model_name=model_name)
   
    # save the model after training the generator
    # the trained generator can be reloaded for 
    # future MNIST digit generation
    generator.save(model_name + ".h5")

판별기와 적대적 네트워크를 훈련시키는 함수, 맞춤 훈련을 위해 일반적인 fit() 함수를 사용하지 않는다. 대신 데이터 배치가 주어졌을 때 단일 경사 업데이트를 실행하기 위해 train_on_batch() 가 호출된다. 그런 다음 생성기는 적대적 네트워크를 통해 훈련된다. 

훈련할 때 가장 먼저 임의로 데이터세트에서 실제 이미지로 구성된 배치를 선택한다. 여기에는 레이블이 1로 붙는다. 그런 다음 생성기에서 가짜 이미지로 구성된 배치가 생성된다. 여기에는 0으로 레이블이 붙고 이 두 배치를 연결해 판별기 훈련에 사용된다. 

이후 생성기아 새로운 가짜 이미지 배치를 생성하고 거기에 1의 레이블을 붙인다. 이 배치는 적대적 네트워크를 훈련시키기 위해 사용된다. 

주기적 간격으로 특정 노이즈 벡터를 기반으로 생성된 MNIST 숫자는 파일 시스템에 저장된다. 마지막 훈련 단계에서 이 네트워크가 수렴된다. 생성기 모델도 파일에 저장되기 때문에 쉽게 재사용이 가능하지만, GAN 에서 새로운 숫자를 생성하는 데 있어 유용한 부분은 생성기 모델이므로 생ㅅ어기 모델만 저장,

def build_and_train_models():
    # load MNIST dataset
    (x_train, _), (_, _) = mnist.load_data()

    # reshape data for CNN as (28, 28, 1) and normalize
    image_size = x_train.shape[1]
    x_train = np.reshape(x_train, [-1, image_size, image_size, 1])
    x_train = x_train.astype('float32') / 255

    model_name = "dcgan_mnist"
    # network parameters
    # the latent or z vector is 100-dim
    latent_size = 100
    batch_size = 64
    train_steps = 40000
    lr = 2e-4
    decay = 6e-8
    input_shape = (image_size, image_size, 1)

    # build discriminator model
    inputs = Input(shape=input_shape, name='discriminator_input')
    discriminator = build_discriminator(inputs)
    # [1] or original paper uses Adam, 
    # but discriminator converges easily with RMSprop
    optimizer = RMSprop(lr=lr, decay=decay)
    discriminator.compile(loss='binary_crossentropy',
                          optimizer=optimizer,
                          metrics=['accuracy'])
    discriminator.summary()

    # build generator model
    input_shape = (latent_size, )
    inputs = Input(shape=input_shape, name='z_input')
    generator = build_generator(inputs, image_size)
    generator.summary()

    # build adversarial model
    optimizer = RMSprop(lr=lr * 0.5, decay=decay * 0.5)
    # freeze the weights of discriminator during adversarial training
    discriminator.trainable = False
    # adversarial = generator + discriminator
    adversarial = Model(inputs, 
                        discriminator(generator(inputs)),
                        name=model_name)
    adversarial.compile(loss='binary_crossentropy',
                        optimizer=optimizer,
                        metrics=['accuracy'])
    adversarial.summary()

    # train discriminator and adversarial networks
    models = (generator, discriminator, adversarial)
    params = (batch_size, latent_size, train_steps, model_name)
    train(models, x_train, params)

 

 

'basic_ML_models' 카테고리의 다른 글

basic_DCGAN  (0) 2022.10.25
basic_CGAN  (1) 2022.10.23
basic_color_autoencoder  (0) 2022.10.23
basic_DAE  (0) 2022.10.23
basic_autoencoder  (0) 2022.10.23