귀퉁이 서재

컴퓨터 비전 - 13. 딥드림(Deep Dream)이란? 본문

컴퓨터 비전

컴퓨터 비전 - 13. 딥드림(Deep Dream)이란?

Baek Kyun Shin 2023. 6. 5. 23:30

딥드림이란 무엇인가?

딥드림(Deep Dream)이란 입력 이미지를 환영이나 꿈 같은 이미지로 만들어주는 알고리즘입니다. 마치 어린 아이들이 구름을 보며 구름과 비슷한 동물이나 사물을 떠올리는 것과 같습니다. 이미 훈련된 신경망 모델이 원본 이미지에서 특정 패턴을 더 부각해 몽환적이고 새로운 이미지를 만들어주는 방식이죠. 아래 그림을 보시죠. 왼쪽이 원본이고, 오른쪽이 딥드림 모델로 만든 그림입니다. 

출처-https://www.telegraph.co.uk/technology/google/11730050/deep-dream-best-images.html

딥드림을 이해하려면 먼저 기존 딥러닝 신경망을 알아야 합니다. 기본적으로 딥러닝 신경망은 경사 하강법으로 손실값을 최소화하는 방향으로 훈련을 합니다. 여러 계층(레이어)을 거쳐 전결합층에 도달해 최종 분류값을 결과물로 출력합니다. 이어서 최종 분류값과 실제값 사이의 오차(손실)를 계산하죠. 오차값이 줄어드는 방향으로 역전파를 적용해 경사하강법으로 계층(레이어) 내 뉴런의 가중치를 수정합니다. 이런 방식이 일반적인 신경망 학습 방식입니다.

그런데 딥드림 신경망의 학습 방법은 이와는 반대입니다. 경사 하강법이 아니라 경사 상승법(gradient ascent)으로 훈련하기 때문입니다. 곧, 손실을 최소화하는 방향으로 훈련하는 게 아니라 최대화하는 방향으로 훈련을 합니다. 게다가 가중치를 수정하는 게 아니라 이미지의 픽셀을 직접 수정하면서 훈련을 합니다. 차이점을 정리하면 아래와 같습니다.

  • 일반적인 분류 신경망 모델 : 경사 하강법으로 손실을 '최소화'하는 방향으로 뉴런의 '가중치'를 수정
  • 딥드림 신경망 모델 : 경사 상승법으로 손실을 '최대화'하는 방향으로 이미지의 '픽셀'을 수정

그런데 딥드림의 모든 계층마다 손실을 최대화하도록 훈련하면 이미지가 너무 엉뚱해질 겁니다. 특정 계층만 선택해서 그 계층 위주로 이미지 픽셀을 수정하도록 훈련해야겠죠. 그래야 원본 이미지의 형태도 어느 정도 유지를 하면서 환상적인 이미지를 만들어줍니다. 다시 말하면, 딥드림은 활성화(activate)할 하나 이상의 계층을 선택해 손실이 커지도록 이미지 픽셀을 수정하며 선택한 계층을 "과잉해석"하게끔 하는 원리입니다. 딥드림이 얼마나 화려하게 만들어질지는 어떤 계층을 선택했냐에 따라 다릅니다. 만약 입력층과 가까운 앞쪽 계층을 중심으로 훈련한다면 획이나 코너, 또는 간단한 패턴이 생성됩니다. 중간 부분을 선택한다면 기본적인 모양(문이나 나뭇잎)이 생성될 겁니다. 출력층과 가까운 깊은 계층을 선택한다면 복잡한 패턴이나 물체의 전체 모습(건물이나 나무)을 생성할 수 있습니다.

딥드림 논문에서는 InceptionV3 모델을 바탕으로 설명합니다. InceptionV3는 꽤 큰 모델이죠. 여러 층이 있는데, 그 가운데 합성곱 계층이 서로 합쳐진(concatenated) 계층에 주목해야 합니다. 이를 mixed 계층이라고 합니다. InceptionV3에는 'mixed0'부터 'mixed10'까지 11개의 mixed 계층이 있습니다. 이 가운데 어떤 mixed 계층을 훈련하느냐에 따라 딥드림 결과 이미지가 달라지죠. 앞서 얘기했듯이, 뒤쪽 계층을 훈련하면 고차원 특성(higher-level features)에 반응하고, 앞쪽 계층을 훈련하면 선이나 단순한 패턴 처럼 저차원 특성에 반응합니다. 다만, 뒤쪽 층을 선택하면 그레디언트를 계산하는 데 시간이 오래 걸릴 수 있습니다.

딥드림 실습

지금까지 딥드림에 관해 살펴봤습니다. 이제는 간단한 실습으로 딥드림 이미지를 만들어보겠습니다.

실습은 구글 Colab을 바탕으로 설명합니다.

1. 사전 훈련 모델 불러오기

우선 필요한 라이브러리를 불러오죠.

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

다음으로 사전 훈련된 이미지 분류 모델을 불러오겠습니다. InceptionV3 모델을 사용해보겠습니다.

# Load pretrained image classification model
base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')

두 가지 파라미터를 전달했습니다.

  • include_top=False :  전결합층(fully-connected layers)은 제외하고 오직 합성곱 계층(convolutional layers)과 풀링 계층(pooling layers)만 가져오겠다는 뜻입니다.
  • weights='imagenet' : 이미지넷 데이터셋으로 사전 훈련된 모델을 불러오겠다는 말입니다.

이렇게 하면 이미지넷 데이터셋으로 사전 훈련된 InceptionV3 모델의 합성곱, 풀링 계층만 가져오게 됩니다. 그것이 base_model 변수에 저장되죠. 모델이 어떻게 이루어졌는지 보려면 base_model.summary()를 실행해보세요. len(base_model.layers)을 출력해보면 계층(레이어)의 개수를 알 수 있습니다. 총 311개네요. 

이어서 딥드림 모델을 생성해보겠습니다. 앞서서는 이미지 분류 모델의 계층을 불러왔는데, 이를 바탕으로 딥드림 모델을 만들어 보려는 겁니다. 우선 mixed9와 mixed10을 선택해서 딥드림을 적용해보겠습니다. 앞쪽 계층을 선택할수록 선이나 원 등의 이미지가 생성되고, 뒤쪽 계층을 선택할수록 구체적인 물체나 특성이 생성됩니다. 여기서는 뒤쪽 계층을 선택해봤습니다.

# There are concatednated layers in inceptionV3 network, called 'mixed layers'
# Maximize the activations of these mixed layers to generate deep dream image
layer_names = ['mixed9', 'mixed10']
layers_to_train = [base_model.get_layer(layer_name).output for layer_name in layer_names]

# Create the feature extraction model
dream_model = tf.keras.Model(inputs=base_model.input, outputs=layers_to_train)

base_model.input은 입력계층을, layers_to_train은 출력계층을(즉, mixed9와 mixed10) 뜻합니다. 생성된 딥드림 모델이 dream_model에 저장됩니다. 일반적인 신경망 모델에서는 최종 출력값이 하나지만, 딥드림 모델은 출력 계층이 여러 개일 수 있다.

2. 이미지를 불러오고, 전처리 하기

구글 드라이브에 저장된 이미지를 불러오기 위해 구글 드라이브를 마운트합니다.

from google.colab import drive
drive.mount('/content/drive')

unsplash에서 다운로드한 타지마할 이미지를 구글 드라이브에 저장한 뒤 불러오겠습니다. tf.keras.preprocessing.image.load()는 이미지를 불러오는 메서드입니다. target_size=(225, 375)는 인셉션 모델에서 사용한 이미지 크기에 따라 설정했습니다.

image_path = '/content/drive/MyDrive/colab/Computer-Vision-Course/Data/Images/tajmahal.jpg'

image = tf.keras.preprocessing.image.load_img(image_path, target_size=(225, 375))

불러온 이미지를 출력해보겠습니다.

plt.imshow(image);

이렇게 불러온 이미지는 PIL 이미지 타입입니다. 텐서플로로 작업하려면 PIL 이미지를 numpy 타입으로 바꿔야 합니다. 바꿔보시죠.

# Convert PIL image to numpy type to work with TensorFlow
image = tf.keras.preprocessing.image.img_to_array(image)

image가 numpy.ndarray로 바뀌었습니다. 다음으로 인셉션 모델에서 사용한 전처리를 적용해보겠습니다.

# Apply the preprocessing used in the InceptionV3 model as it is
image = tf.keras.applications.inception_v3.preprocess_input(image)

전처리 전에는 이미지 픽셀이 0 ~ 255까지의 값을 갖는데, 전처리 후에는 -1에서 1사이의 값을 갖습니다.

3. 딥드림 모델의 출력값(활성화값) 구하기

지금까지 만든 딥드림 모델에 이미지를 전달해 출력값을 한번 구해보겠습니다. 먼저 이미지 형상이 어떤지 보시죠.

# widht, height, number of channels
image.shape

(225, 375, 3)

순서대로 너비, 높이, 채널수입니다.

이 이미지를 딥드림 모델에 넣으려면 배치 크기를 추가해야 합니다.

image_batch = tf.expand_dims(image, axis=0)

image_batch.shape

TensorShape([1, 225, 375, 3])

이 이미지를 딥드림 모델에 전달해 결괏값을 얻어보겠습니다.

activations = dream_model(image_batch)

len(activations)을 출력해보면 2가 나옵니다. 즉, 딥드림 출력계층이 두 개라는 말입니다. 출력계층이 뭔지 보려면 다음을 실행해보세요.

dream_model.outputs

[<KerasTensor: shape=(None, None, None, 2048) dtype=float32 (created by layer 'mixed9')>, 
 <KerasTensor: shape=(None, None, None, 2048) dtype=float32 (created by layer 'mixed10')>]

처음에 설정한 mixed9과 mixed10 계층이 출력됐네요.

4. 손실값 계산 함수

이젠 손실값을 계산하는 함수를 만들어보겠습니다. 딥드림에서는 이미지와 활성화값을 서로 비교해서, 손실을 업데이트합니다.

def calculate_loss(image, network):
    # Pass forward the image through the model to retrieve the activations.
    # Converts the image into a batch of size 1.
    image_batch = tf.expand_dims(image, axis=0)
    layer_activations = network(image_batch)

    losses = []
    for layer_activation in layer_activations:
        loss = tf.math.reduce_mean(layer_activation) # average of the activations
        losses.append(loss)

    return tf.reduce_sum(losses)

이 함수를 활용해 손실을 계산해보죠.

loss = calculate_loss(image, dream_model)
loss

<tf.Tensor: shape=(), dtype=float32, numpy=0.3468349>

샘플로 손실값을 구해봤습니다. 0.3468349가 나오네요.

5. 딥드림 실행

다음으로 딥드림 모델을 실행해보겠습니다. 딥드림 모델을 바탕으로 원본 이미지를 딥드림 이미지로 바꿔보죠. 먼저 딥드림 알고리즘을 실행하는 함수를 만들어보겠습니다.

# Compare the activations with the pixels
# Emphasize parts of the image
# Change the pixels of the input image

@tf.function
def deep_dream(network, image, learning_rate):
    with tf.GradientTape() as tape:
        tape.watch(image)
        loss = calculate_loss(image, network)

    gradients = tape.gradient(loss, image)  # Derivate
    gradients /= tf.math.reduce_std(gradients) + 1e-8  # Normalizing
    image = image + gradients * learning_rate  # Gradient Ascent
    image = tf.clip_by_value(image, -1, 1)  # Clip the value less than -1 and greater than 1 (Because in the beginning, it was normalized between -1 and 1)

    return loss, image

가장 먼저 @tf.function이 눈에 띕니다. 텐서플로 1.x 버전에서는 그래프의 생성과 실행을 분리했습니다. 실행할 때는 Session이라는 걸 열어서 실행했었죠. 많은 불편이 있어서 텐서플로 2.x 버전부터는 Session을 열지 않아도 되도록 개선이 됐습니다. 텐서플로 1.x 버전에서는그래프의 생성과 실행을 분리한 까닭은 속도가 조금 더 빨라진다는 이점 때문이었습니다. 텐서플로 2.x 버전에서도 텐서플로 1.x 버전 스타일로 로직이 동작하도록 할 수 있습니다. 바로 @tf.fucntion 어노테이션이죠. 함수 위에 @tf.fucntion를 붙이면 텐서플로 1.x 버전 스타일로 함수 안의 로직이 동작해서 속도가 조금 더 빨라집니다.

deep_dream 함수는 경사 상승법으로 원본 이미지를 딥드림 이미지로 차근차근 바꿔주는 기능을 합니다. 이때 정규화를 한 상태로 업데이트를 합니다. 딥드림 이미지를 시각화하려면 역정규화를 해야겠죠. 역정규화를 하는 inverse_transform() 함수도 정의해봅니다.

def inverse_transform(image):
    image = 255 * (image + 1.0) / 2.0  # Restore to visualize normalized images
    return tf.cast(image, tf.uint8)  # Cast the pixel to int type

이젠 앞서 만든 deep_dream 메서드를 활용해 사용자가 지정한 에폭(epochs)만큼 순회하며, 딥드림을 실행하는 함수를 만들어보겠습니다. 매 5000 에폭마다 중간 딥드림 결괏값도 출력하도록 했습니다.

def run_deep_dream(network, image, epochs, learning_rate):
    for epoch in range(epochs):
        loss, image = deep_dream(network, image, learning_rate)

        if epoch % 5000 == 0:
            plt.figure(figsize=(8,8))
            plt.imshow(inverse_transform(image))
            plt.show()
            print(f'Epoch {epoch}, loss {loss}')

모든 준비는 끝났습니다. 앞서 준비한 타지마할 이미지를 활용해 딥드림을 실행해봅시다.

run_deep_dream(network=dream_model, image=image, epochs=60001, learning_rate=0.003)

중간 결괏값은 제외하고 최종 결괏값만 실어봤습니다. 원본 이미지와 비교해보세요. 특이한 딥드림 이미지가 생성됐네요. 에폭수도 조정하고, 다른 mixed 계층도 선택해보고, 학습률(learning_rate)도 조정해보면 더 색다른 이미지를 얻을 수 있습니다. 

모든 절차를 동일하게 하고 mixed2와 mixed4로만 출력계층을 바꿔 딥드림을 실행해봤습니다. 아래와 같이 타지마할을 못알아볼 정도의 이미지가 생성됐습니다.

참고자료

Gary's Notebook - "DeepDream Explained Clearly"

Google Research - "Inceptionism: Going Deeper into Neural Networks"

Jones Granatyr(Udemy) - "Computer Vision: Master Class"

Comments