본문 바로가기

데이터 다루기/Vision (Tensorflow)

[Vision] DeepDream (딥드림)

728x90
반응형

이번 포스팅에서는 DeepDream 모델에 대해서 실습해보도록 하겠습니다.

DeepDream 모델은 신경망이 학습한 패턴을 시각화하는 기능을 가지고 있습니다.

예를 들어서, 아래와 같은 사진을 봅시다.

 

 

강아지 사진을 베이스로 초현실적인 패턴이 입혀져 있습니다.

즉 강아지 사진에 신경망이 학습한 패턴을 입혀서 시각화할 수 있습니다.

1. 사용할 패키지 불러오기.

 

import tensorflow as tf
import numpy as np
import matplotlib as mpl
import IPython.display as display
import PIL.Image
from tensorflow.keras.preprocessing import image

 

import를 활용하여 사용할 패키지를 불어왔습니다.

 

2. 데이터 불러오기.

 

url = 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'

 

우선 변환에 사용할 이미지를 가져오겠습니다.

실습에서 사용할 이미지는 레브라도에서 가져오겠습니다.

 

https://commons.wikimedia.org/wiki/File:YellowLabradorLooking_new.jpg

 

File:YellowLabradorLooking new.jpg - Wikimedia Commons

 

commons.wikimedia.org

 

# 이미지를 내려받아 넘파이 배열로 변환합니다.
def download(url, max_dim=None):
  name = url.split('/')[-1]
  image_path = tf.keras.utils.get_file(name, origin=url)
  img = PIL.Image.open(image_path)
  if max_dim:
    img.thumbnail((max_dim, max_dim))
  return np.array(img)

# 이미지를 정규화합니다.
def deprocess(img):
  img = 255*(img + 1.0)/2.0
  return tf.cast(img, tf.uint8)

# 이미지를 출력합니다.
def show(img):
  display.display(PIL.Image.fromarray(np.array(img)))


# 이미지의 크기를 줄여 작업이 더 용이하도록 만듭니다.
original_img = download(url, max_dim=500)
show(original_img)
display.display(display.HTML('Image cc-by: <a "href=https://commons.wikimedia.org/wiki/File:Felis_catus-cat_on_snow.jpg">Von.grzanka</a>'))

 

3. 특성 추출 모델 정의

 

사전 훈련된 모델을 가져오도록 하겠습니다. DeepDream에서 실제로 제안된 모델과 유사한 InceptionV3를 사용하여 실습하도록 하겠습니다.

 

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')

 

DeepDream 모델의 원리에 대해서 설명드리자면, 전체 네트워크에서 하나 혹은 그 이상의 층을 선택 한 후, 선택된 층들의 "손실"을 최대화하도록 이미지를 수정합니다.

이 경우, 선택된 층들을 "흥분"시킨다는 표현이 적합합니다.

사실 선택된 층에 따라서 결과는 다른 패턴을 보여줍니다.

예를 들어서, 낮은 층을 선택한다면 간단한 패턴이 학습되고, 깊은 층을 선택한다면 이미지 내의 복잡한 패턴이 학습될 수 있습니다.

InceptionV3는 mixed 0 ~ mixed 10에 해당하는 11개의 합성곱층으로 구성됩니다.

 

# 선택한 층들의 활성화값을 최대화합니다.
names = ['mixed3', 'mixed5']
layers = [base_model.get_layer(name).output for name in names]

# 특성 추출 모델을 만듭니다.
dream_model = tf.keras.Model(inputs=base_model.input, outputs=layers)

 

특성 추출 모델의 손실 (Loss)는 선택한 층들의 활성화값의 총 합으로 정의됩니다.

이 때, 층의 크기와 상관 없이 모든 활성화값이 동일하게 고려될 수 있도록 하기 위해서 각 층의 손실을 정규화하줍시다. 기존 네트워크 학습과는 다르게, 우리는 손실을 최대화하는 것이 목적이기 때문에. 경사상승법을 이용합니다.

 

def calc_loss(img, model):
  # 이미지를 순전파시켜 모델의 활성화값을 얻습니다.
  # 이미지의 배치(batch) 크기를 1로 만듭니다.
  img_batch = tf.expand_dims(img, axis=0)
  layer_activations = model(img_batch)
  if len(layer_activations) == 1:
    layer_activations = [layer_activations]

  losses = []
  for act in layer_activations:
    loss = tf.math.reduce_mean(act)
    losses.append(loss)

  return  tf.reduce_sum(losses)

 

4. 경사 상승법 (Gradient ascent)

 

class DeepDream(tf.Module):
  def __init__(self, model):
    self.model = model

  @tf.function(
      input_signature=(
        tf.TensorSpec(shape=[None,None,3], dtype=tf.float32),
        tf.TensorSpec(shape=[], dtype=tf.int32),
        tf.TensorSpec(shape=[], dtype=tf.float32),)
  )
  def __call__(self, img, steps, step_size):
      print("Tracing")
      loss = tf.constant(0.0)
      for n in tf.range(steps):
        with tf.GradientTape() as tape:
          # `img`에 대한 그래디언트가 필요합니다.
          # `GradientTape`은 기본적으로 오직 `tf.Variable`만 주시합니다.
          tape.watch(img)
          loss = calc_loss(img, self.model)

        # 입력 이미지의 각 픽셀에 대한 손실 함수의 그래디언트를 계산합니다.
        gradients = tape.gradient(loss, img)

        # 그래디언트를 정규화합니다.
        gradients /= tf.math.reduce_std(gradients) + 1e-8 

        # 경사상승법을 이용해 "손실" 최대화함으로써 입력 이미지가 선택한 층들을 보다 더 "흥분" 시킬 수 있도록 합니다.
        # (그래디언트와 이미지의 차원이 동일하므로) 그래디언트를 이미지에 직접 더함으로써 이미지를 업데이트할 수 있습니다.
        img = img + gradients*step_size
        img = tf.clip_by_value(img, -1, 1)

      return loss, img


deepdream = DeepDream(dream_model)

 

원본 이미지에 그래디언트를 더하는 것은 신경망이 보는 이미지 내의 패턴을 향상시키는 일에 해당합니다.

즉 훈련이 진행될수록 신경망에서 선택한 층을 더욱더 활성화시키는 이미지를 생성할 수 있습니다.

 

def run_deep_dream_simple(img, steps=100, step_size=0.01):
  # 이미지를 모델에 순전파하기 위해 uint8 형식으로 변환합니다.
  img = tf.keras.applications.inception_v3.preprocess_input(img)
  img = tf.convert_to_tensor(img)
  step_size = tf.convert_to_tensor(step_size)
  steps_remaining = steps
  step = 0
  while steps_remaining:
    if steps_remaining>100:
      run_steps = tf.constant(100)
    else:
      run_steps = tf.constant(steps_remaining)
    steps_remaining -= run_steps
    step += run_steps

    loss, img = deepdream(img, run_steps, tf.constant(step_size))

    display.clear_output(wait=True)
    show(deprocess(img))
    print ("Step {}, loss {}".format(step, loss))


  result = deprocess(img)
  display.clear_output(wait=True)
  show(result)

  return result
dream_img = run_deep_dream_simple(img=original_img, 
                                  steps=100, step_size=0.01)

 

 

5. 옥타브 올리기

 

지금 생성된 이미지에 만족할 수 있지만, 우리는 몇 가지 문제점을 개선할 수 있습니다.

(1) 생성된 이미지는 노이즈를 많이 가지고 있습니다.

(2) 생성된 이미지는 해상도가 낮습니다.

(3) 패턴들이 모두 균일한 입도로 나타나고 있습니다.

이러한 문제점들을 해결할 수 있는 방법은 바로 경사상승법의 스케일에 변화를 주어 여러 차례 적용하는 것입니다. 이는 작은 스케일에서 생성된 패턴들이 큰 스케일에서 생성된 패턴들에 녹아들어 더 많은 디테일을 형성할 수 있도록 도와줍니다.

 

import time
start = time.time()

OCTAVE_SCALE = 1.30

img = tf.constant(np.array(original_img))
base_shape = tf.shape(img)[:-1]
float_base_shape = tf.cast(base_shape, tf.float32)

for n in range(-2, 3):
  new_shape = tf.cast(float_base_shape*(OCTAVE_SCALE**n), tf.int32)

  img = tf.image.resize(img, new_shape).numpy()

  img = run_deep_dream_simple(img=img, steps=50, step_size=0.01)

display.clear_output(wait=True)
img = tf.image.resize(img, base_shape)
img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8)
show(img)

end = time.time()
end-start

 

 

반응형