Ana içeriğe geç

TensorFlow 2.0 ile Nöral Stil Transferi Uygulaması

Bu eğitimde, TensorFlow 2.0 kullanarak Prisma gibi güçlü uygulamaları nasıl hayata geçireceğinizi öğreneceksiniz.
· 11 dk. oku

Yapay Zeka ile Keşfedin

Evrimli sinir ağları (CNN), görüntü sınıflandırma ve analizinde en güçlü sinir ağı türüdür. Uygulamaları pek çok sınırı aşmış ve bugün gördüğümüz birçok derin öğrenme destekli uygulamanın kritik unsuru olduklarını kanıtlamıştır. Yüksek düzeyde bakıldığında, CNN'ler kendilerine verilen görüntülerin içsel özellik düzeyi temsillerini öğrenebilir. Onları bu kadar güçlü kılan da budur. CNN'lerin bu belirgin özelliğinin yalnızca görüntü sınıflandırma gibi görevler için değil, aynı zamanda görüntü oluşturma için de yararlı olduğu ortaya çıktı. Deep Dream ve Nöral Stil Transferi gibi uygulamalar, CNN'lerin katman aktivasyonlarına ve çıkardıkları özelliklere dayanarak görüntüler üretir.

Bu eğitimde, Nöral Stil Transferi'nin nasıl çalıştığını ve TensorFlow 2.0 kullanılarak nasıl uygulanabileceğini inceleyeceksiniz.

Not: Bu eğitimi adım adım takip edebilmek için CNN'lerin nasıl çalıştığını bilmeniz gerekir. CNN'lerle ilgili hızlı bir tazeleme için aşağıdaki kaynaklara göz atabilirsiniz:

Nöral Stil Transferi nedir?

Bilgisayarlı görü ve derin öğrenme alanına yeni adım atanlar için nöral stil transferi terimi biraz göz korkutucu olabilir. Terimin her bileşenini anlamak için aşağıdaki iki görüntüyü düşünün:

neural style transfer 1

Nöral stil transferi bağlamında, soldaki görüntü içerik görüntüsü, sağdaki görüntü ise stil görüntüsü olarak adlandırılır. Bir görüntüyü (bu örnekte soldaki) başka bir görüntünün (sağdaki) stilini kullanarak biçimlendirmek istiyorsunuz. Terimdeki son iki kelimeyi oluşturan şey budur: stil transferi. Süreci yürütmek için bir sinir ağı (CNN) özel bir kayıp fonksiyonunu optimize edecek şekilde eğitilir; bu da ilk kelimeyi açıklar: nöral. Yukarıdaki iki görüntü nöral stil transferiyle birleştirildiğinde nihai çıktı (sağdaki) şöyle görünür:

neural style transfer 2

“Nöral stil transferinin ne faydası var?” diye sorabilirsiniz. Diyelim ki Vincent van Gogh tarafından yapılmış bir çizimin görüntüsüne sahipsiniz. Bu çizimin sizin kendi çizimlerinizden birine nasıl yansıyacağını görmek istiyorsunuz. Nöral stil transferinin kullanıldığı uygulamalardan biri budur. Bir diğer örnek de Prisma gibi, pürüzsüz bir kullanıcı arayüzü aracılığıyla nöral stil transferi yapmanızı sağlayan pek çok fotoğraf filtresi uygulamasıdır.

İçerik ve Stil Arasındaki Yalıtım

Şimdiye kadar nöral stil transferi hakkında edindiğiniz fikir, sürecin bir görüntünün içeriğiyle başka bir görüntünün stilini birleştirmekten ibaret olduğu yönündedir. Nitekim bu %100 doğrudur. Bu bölümde, CNN'ler bağlamında içerik ve stil ile tam olarak ne kastedildiğini anlayalım.

Bir CNN genellikle birkaç evrişim katmanı ve havuzlama katmanının bir araya gelmesinden oluşur. Evrişim katmanları verilen bir görüntüden son derece karmaşık özellikler çıkarmaktan sorumluyken, havuzlama katmanları bir görüntü sınıflandırma problemi için gerekli olmayan ayrıntılı uzamsal bilgileri eler. Bunun etkisi, CNN'in belirli bir görüntünün renginden, dokusundan vb. ziyade içeriğini öğrenmesine yardımcı olmasıdır. Bir CNN'in derinlerine indikçe özelliklerin karmaşıklığı artar ve daha derin evrişim katmanları sıklıkla içerik temsilleri olarak adlandırılır.

Stile örnek olarak ise doku, renk vb. gibi görüntü özelliklerine özgü her şey verilebilir — örneğin bir çizimde belirli bir rengin baskın kullanımı. Peki bir görüntünün stilini nasıl çıkarırsınız? Bu, evrişim katmanları arasındaki korelasyonlar hesaplanarak yapılır. Korelasyonlar, iki veya daha fazla değişkenin ne kadar benzer/ilişkili olduğunun bir ölçüsünü verir. Bunu anlamak için, birkaç özellik haritasından oluşan öğrenilmiş bir evrişim katmanını düşünün. Her bir özellik haritası için, tespit ettiği özelliklerin aynı katmandaki diğer özellik haritalarıyla ne kadar güçlü ilişkili olduğunu ölçebilirsiniz. Bu da size şunların bir tahminini verir: İlk özellik haritasında tespit edilen belirli bir renk, başka bir haritadaki renge benziyor mu? Özellik haritaları arasında ortak şekiller var mı? Bu özellikler/benzerlikler bir görüntünün stilini tanımlar. Bir evrişim katmanı içindeki birçok özellik haritasının içerikleri arasındaki benzerliği ölçme süreci, ağların doku ve renk gibi uzamsal özelliklere odaklanan çok ölçekli bir görüntü temsili öğrenmesine yardımcı olur.

Akılda tutulması gereken hayati bir nokta, nöral stil transferini uygularken, bir görüntünün içeriğinin de başka bir görüntünün istenen stiliyle birlikte korunduğundan emin olmanız gerektiğidir. Bunun nasıl yapıldığını ilerleyen bir bölümde göreceksiniz. Bir sonraki bölümde, bir görüntüden (içerik görüntüsü) özelliklerin nasıl çıkarılacağını ve içerik kaybının nasıl hesaplanacağını öğreneceksiniz.

İçerik Görüntüsünü Nicemleme ve İçerik Kaybını Hesaplama

Nöral stil transferi algoritması ilk olarak Gatys ve ark. tarafından 2015 tarihli Sanatsal Stil için Bir Nöral Algoritma makalesinde tanıtıldı. Ancak bu eğitim, bir önceki çalışmanın devamı niteliğindeki Evrişimli Sinir Ağları Kullanarak Görüntü Stil Transferi makalesinden referans alır.

Evrişimli Sinir Ağları Kullanarak Görüntü Stil Transferi makalesine göre, hem içerik hem de stil özelliklerini sırasıyla içerik ve stil görüntülerinden çıkarmak için VGG-19 CNN mimarisi kullanılır. İçerik özelliklerini elde etmek için dördüncü bloğun (evrişim katmanlarından) ikinci evrişim katmanı kullanılır. Makalenin yazarları bunu kolaylık olması için conv4_2 olarak adlandırmıştır. İçerik özelliklerini elde ettikten sonra, içerik kaybını ölçmek için bunları bir hedef görüntüyle karşılaştırmanız gerekir. Hedef görüntü nedir? Burada içerik kaybı neden hesaplanmalıdır? Bu iki soruya odaklanmak için bir adım geri atalım.

İçerik ve stil özelliklerini tek bir görüntüde birleştirmek için, ya boş bir görüntüyle ya da içerik görüntüsünün bir kopyasıyla başlayan bir hedef görüntüye ihtiyacınız olacak. Artık bir CNN kullanarak hem içerik hem de stil özelliklerini etkili bir şekilde öğrenmek için, optimize ederek içerik ve stil görüntülerinden oluşturulacak pürüzsüz bir stillendirilmiş görüntü elde edeceğiniz özel bir kayıp fonksiyonuna ihtiyacınız var. Bu özel kayıp fonksiyonu, esasen iki farklı kaybın bir araya gelmesinden oluşur:

  • Net içeriğin korunduğundan emin olan içerik kaybı.
  • Hedef görüntüye aktarılan stil miktarını kontrol eden stil kaybı.

Artık yukarıdaki sorular hakkında bir fikir edindiğinize göre içerik kaybına geri dönelim ve bunu tanımlayalım. İçerik kaybı şu şekilde tanımlanır -

burada $T_c$ hedef görüntüyü, $C_c$ ise içerik görüntüsünü ifade eder. Yani bu kayıp fonksiyonunun yaptığı şey, içerik ve hedef görüntülerin özelliklerinin birbirinden ne kadar uzak olduğunu ölçmektir. Ağ bu kaybı en aza indirmeye çalışacaktır. Bir sonraki bölümde stil kaybının nasıl belirleneceğini göreceksiniz.

Stil Kaybı ve Gram Matrisi

Stil kaybını belirlemek için, makale sizi aşağıdaki katmanlardan temsilleri (özünde sayıları) almaya ve bu katmanlardaki özellik haritalarının gram matrislerini elde etmeye yönlendirir.

'conv1_1'
'conv2_1'
'conv3_1'
'conv4_1'
'conv5_1'

Stil kaybını tanımlamak için çok sayıda katman kullanmanın avantajı, bir görüntüde yer alan stilin çok ölçekli bir temsilinin öğrenilmesine olanak tanımasıdır. Gram matrisleri, tek bir evrişim katmanı içindeki özellik haritaları arasında özelliklerin ne kadar benzer olduğunu belirlemenize yardımcı olur. Bu da görüntünün stilini şekillendiren yerelleştirilmemiş bilgileri ortaya koyar. Bu bağlamda gram matrisi şu şekilde hesaplanır.

Evrişmiş bir görüntünün boyutunun 8x8x16 olduğunu varsayalım; bu öncelikle 16 özellik haritasına sahip olduğunu belirtir. Şimdi bu özellik haritaları arasındaki özelliklerin benzerliklerini bir gram matrisiyle bulmakla ilgileniyorsunuz. Bunun için, evrişmiş görüntünün ilk iki boyutunu düzleştirip 1B bir vektöre dönüştürürsünüz. Bu durumda düzleştirildiğinde boyutlar 1B vektörde 64 öğe içerir. Bu işlem tüm özellik haritaları için tekrarlanır. Böylece 16x64 boyutunda nihai bir matris elde edersiniz (A matrisi diyelim). Bu matris daha sonra (64x16 olacak şekilde) transpoze edilir ve A matrisi ile çarpılır. Bu durumda gram matrisi 16x16 boyutunda olur. Gram matrisindeki belirli bir değer, özellik haritaları arasındaki benzerlikleri ifade eder. Bu süreç görsel olarak aşağıdakine benzer:

style loss and gram matrix

Hedef ve stil görüntüleri arasındaki stil kaybını hesaplamak için, her bir evrişim katmanı bloğunda gram matrisleri arasındaki ortalama kare farklarını alırsınız. Resmî olarak şu şekilde tanımlanabilir:

burada, T(s,i) i. blokta hesaplanan hedef görüntünün gram matrisi, S(s,i) ise i. blokta hesaplanan stil görüntüsünün gram matrisidir. wi ile stile ayrıntılı bir temsil kazandırmak için farklı evrişim bloklarına özel ağırlıklar verebilirsiniz. Son olarak, $a$ bloklar içindeki her bir katmandaki değerlere karşılık gelen bir sabittir. Nöral stil transferi sürecinde ağın optimize ettiği toplam kaybı tanımlamak için iki kaybı bir araya getirelim.

Toplam Kaybı Tanımlama

Toplam kayıp artık özel ağırlıklarla bir toplama işleminden ibarettir -

burada $\alpha$ içerik ağırlığını, $\beta$ ise stil ağırlığını ifade eder. Bu, hedef görüntüye aktarılan stil miktarı ile içerik arasında iyi bir denge kurmanıza yardımcı olur. $\alpha$ ve $\beta$’nın farklı kombinasyonlarının etkisi makalede gösterilmektedir; bunu incelemeniz tavsiye edilir. Artık nöral stil transferini uygulamak için tüm parçalar elinizde. Bir sonraki bölümde doğrudan uygulamaya geçelim.

Uygulamada Nöral Stil Transferi

Uygulama kısmı için TensorFlow 2.0 kullanacaksınız. Kütüphaneye bugüne kadarki en kapsamlı güncellemelerden birini getiren pek çok olağanüstü yenilik içerir. Bunlardan bazılarını öğrenmek isterseniz bu yazıya göz atabilirsiniz.

Bu eğitimin yazıldığı sırada TensorFlow’un en güncel sürümü 2.0.0-beta0’dır. Bunu henüz kurmadıysanız, lütfen önce buradaki talimatları izleyerek kurun. Gerekli paketleri ve içerik ile stil görüntülerini içe aktararak başlayacaksınız.

# Packages
import tensorflow as tf
from tensorflow.keras.applications.vgg19 import preprocess_input
from tensorflow.keras.models import Model
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(7)

%matplotlib inline

print(tf.__version__)
2.0.0-beta0
# Load the content and style images
content = plt.imread('Content.jpeg')
style = plt.imread('Style.jpg')

# Display the images
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))

# Content and style images side-by-side
ax1.imshow(content)
ax1.set_title('Content Image')
ax2.imshow(style)
ax2.set_title('Style Image')
plt.show()
content and style images

Bir sonraki adımda, iki görüntüyü sayı dizilerine yükleyecek (çünkü bilgisayarlar yalnızca sayıları anlar) ve modeliyle uyumlu hale getirmek için yeniden boyutlandıracak bir yardımcı işlev yazacaksınız.

def load_image(image):
  image = plt.imread(image)
  img = tf.image.convert_image_dtype(image, tf.float32)
  img = tf.image.resize(img, [400, 400])
  # Shape -> (batch_size, h, w, d)
  img = img[tf.newaxis, :]
  return img
# Use load_image of content and style images
content = load_image('Content.jpeg')
style = load_image('Style.jpg')

# Verify the shapes
content.shape, style.shape
(TensorShape([1, 400, 400, 3]), TensorShape([1, 400, 400, 3]))

Görüntüler yeniden şekillendirildi. Şimdi, özellikleri çıkarmak için önceden eğitilmiş bir VGG19 modeli yükleyeceksiniz. Modeli özellik çıkarmak için kullanacağınızdan, modelin sınıflandırıcı kısmına ihtiyaç duymayacaksınız.

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
vgg.trainable = False
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5
80142336/80134624 [==============================] - 5s 0us/step

Yazarlar, içerik ve stil özelliklerini çıkarmak için kullandıkları blok adlarını belirtmişler. Az önce yüklediğiniz VGG19 modeli aynı adlandırma kuralını kullanıyor. Bu avantajdan yararlanmak için önce ağdaki tüm katmanların adlarını yazdıracaksınız.

# Print the layer names for convenience
for layer in vgg.layers:
  print(layer.name)
input_1
block1_conv1
block1_conv2
block1_pool
block2_conv1
block2_conv2
block2_pool
block3_conv1
block3_conv2
block3_conv3
block3_conv4
block3_pool
block4_conv1
block4_conv2
block4_conv3
block4_conv4
block4_pool
block5_conv1
block5_conv2
block5_conv3
block5_conv4
block5_pool

Bununla birlikte stil özelliklerini almak için aşağıdakilerle ilgileneceksiniz:

'conv1_1'
'conv2_1'
'conv3_1'
'conv4_1'
'conv5_1'

İçerik özellikleri için conv4_2 gereklidir. Bunları uygun değişkenlerde saklayacaksınız.

# Content layer
content_layers = ['block4_conv2']

# Style layer
style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1',
                'block4_conv1',
                'block5_conv1']


num_content_layers = len(content_layers)
num_style_layers = len(style_layers)

Şimdi, belirtilen katmanlardan oluşan özel bir VGG modeli oluşturun. Bu, görüntüler üzerinde ileri geçişler çalıştırmanıza ve yolda gerekli özellikleri çıkarmanıza yardımcı olacaktır.

def mini_model(layer_names, model):

  outputs = [model.get_layer(name).output for name in layer_names]

  model = Model([vgg.input], outputs)
  return model

TensorFlow'da gram matrisi tanımlamak son derece kolaydır ve bunu şu şekilde yapabilirsiniz:

# Gram matrix
def gram_matrix(tensor):
  temp = tensor
  temp = tf.squeeze(temp)
  fun = tf.reshape(temp, [temp.shape[2], temp.shape[0]*temp.shape[1]])
  result = tf.matmul(temp, temp, transpose_b=True)
  gram = tf.expand_dims(result, axis=0)
  return gram

Şimdi mini_model() işlevini kullanarak özel bir model tanımlayacaksınız. Bu, ilgili görüntülerden içerik ve stil özelliklerini döndürmek için kullanılacaktır.

 class Custom_Style_Model(tf.keras.models.Model):
  def __init__(self, style_layers, content_layers):
    super(Custom_Style_Model, self).__init__()
    self.vgg =  mini_model(style_layers + content_layers, vgg)
    self.style_layers = style_layers
    self.content_layers = content_layers
    self.num_style_layers = len(style_layers)
    self.vgg.trainable = False

  def call(self, inputs):
    # Scale back the pixel values
    inputs = inputs*255.0
    # Preprocess them with respect to VGG19 stats
    preprocessed_input = preprocess_input(inputs)
    # Pass through the mini network
    outputs = self.vgg(preprocessed_input)
    # Segregate the style and content representations
    style_outputs, content_outputs = (outputs[:self.num_style_layers],
                                      outputs[self.num_style_layers:])

    # Calculate the gram matrix for each layer
    style_outputs = [gram_matrix(style_output)
                     for style_output in style_outputs]

    # Assign the content representation and gram matrix in
    # a layer by layer fashion in dicts
    content_dict = {content_name:value
                    for content_name, value
                    in zip(self.content_layers, content_outputs)}

    style_dict = {style_name:value
                  for style_name, value
                  in zip(self.style_layers, style_outputs)}

    return {'content':content_dict, 'style':style_dict}

Artık özel modeli tanımladığınıza göre, bunu görüntüler üzerinde kullanarak içerik ve stil özelliklerini sırasıyla elde edelim -

# Note that the content and style images are loaded in
# content and style variables respectively
extractor = Custom_Style_Model(style_layers, content_layers)
style_targets = extractor(style)['style']
content_targets = extractor(content)['content']


In the paper, optimization was done using the L-BFGS algorithm, but you can use Adam also.
opt = tf.optimizers.Adam(learning_rate=0.02)

Şimdi genel içerik ve stil ağırlıklarını ve daha önce tartışıldığı gibi her bir stil temsilinin ağırlıklarını tanımlayalım. Not: Bunlar hiperparametrelerdir ve üzerinde oynamanız önerilir.

 # Custom weights for style and content updates
style_weight=100
content_weight=10

# Custom weights for different style layers
style_weights = {'block1_conv1': 1.,
                 'block2_conv1': 0.8,
                 'block3_conv1': 0.5,
                 'block4_conv1': 0.3,
                 'block5_conv1': 0.1}

Sırada süreci çok daha eğlenceli hale getiren en kritik kısım var: kayıp fonksiyonu.

# The loss function to optimize
def total_loss(outputs):
    style_outputs = outputs['style']
    content_outputs = outputs['content']
    style_loss = tf.add_n([style_weights[name]*tf.reduce_mean((style_outputs[name]-style_targets[name])**2)
                           for name in style_outputs.keys()])
    # Normalize
    style_loss *= style_weight / num_style_layers

    content_loss = tf.add_n([tf.reduce_mean((content_outputs[name]-content_targets[name])**2)
                             for name in content_outputs.keys()])
    # Normalize
    content_loss *= content_weight / num_content_layers
    loss = style_loss + content_loss
    return loss

Şimdi aşağıdaki işlemleri yapacak bir başka işlev yazacaksınız:

  • Az önce tanımladığınız kayıp fonksiyonunun gradyanlarını hesaplar.
  • Bu gradyanları hedef görüntüyü güncellemek için kullanır.

GradientTape ile otomatik türev alma özelliğinden yararlanabilir, bileşiminden hareketle bir fonksiyonun gradyanlarını hesaplayabilirsiniz. İşlemleri hızlandırmak için tf.function dekoratörünü de kullanacaksınız. Bununla ilgili daha fazlasını buradan okuyun.

@tf.function()
def train_step(image):
  with tf.GradientTape() as tape:
    # Extract the features
    outputs = extractor(image)
    # Calculate the loss
    loss = total_loss(outputs)
  # Determine the gradients of the loss function w.r.t the image pixels
  grad = tape.gradient(loss, image)
  # Update the pixels
  opt.apply_gradients([(grad, image)])
  # Clip the pixel values that fall outside the range of [0,1]
  image.assign(tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0))

Ağı eğitmeden önce kalan tek adım hedef görüntüyü tanımlamaktır. Hedef görüntü olarak içerik görüntüsünü kullanacaksınız.

target_image = tf.Variable(content)

Artık ağı eğitmeye tamamen hazırsınız.

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(target_image)
  plt.imshow(np.squeeze(target_image.read_value(), 0))
  plt.title("Train step: {}".format(step))
  plt.show()
WARNING: Logging before flag parsing goes to stderr.
W0617 16:21:34.491543 140709216896896 deprecation.py:323] From /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_grad.py:1205: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where 

Hedef görüntüdeki değişiklikleri gözlemlemek için içerik ve stil ağırlıklarıyla oynamanız önerilir.

Son Sözler

Bu eğitimde başvurduğunuz makale, oluşturulan görüntünün kalitesini daha fazla iyileştirmekten bahsetmiyordu. Yukarıdaki sonuçlarda muhtemelen gözlemlediğiniz gibi, oluşturulan görüntünün genel içeriği ile stili arasındaki uzamsal ilişkide bir miktar daha pürüzsüzlüğe ihtiyaç var. Bu sorunu gidermek için, düzenlileştirmeye (regülerizasyona) benzer total_variantional_loss tanıtılmıştır. Üst düzeyde, total_variational_loss orijinal nöral stil transferi algoritmasının ortaya çıkardığı yüksek frekanslı yapıları cezalandırır. Bunu uygulamakla ilgileniyorsanız bu eğitime göz atın. Bu eğitimi yazarken referans alınan bazı kaynaklar şunlardır:

Derin öğrenme becerilerinizi geliştirmek istiyorsanız, aşağıdaki DataCamp ders ve eğitimlerine de göz atabilirsiniz:

Konular

İlgili