Derin Öğrenme ile Artistik Stil Transferi
Geçtiğimiz yıllarda van Gogh’un bilinen hikayesini yine van Gogh stili resimlerle anlatan Vincent’ten Sevgilerle‘yi (Loving Vincent, 2017) izleme şansı bulmuştuk. Film, 5.5M dolar gibi bir bütçe ile 125 ressamın çizdiği 65 bin van Gogh stili yağlı boya resim ile üretildi. Yapay zekanın da sanat eseri üretme konusunda ne kadar yetenekli olduğunu biliyoruz. Artistik stil transferi olarak adlandırılan bir derin öğrenme yöntemi gerçek ressamlara gerek kalmadan bu tarz resimler çizebiliyoruz.
Stil transferi aslında basit bir kaç derin öğrenme yönteminin kombinasyonundan oluşmakta: konvolüsyonel nöral ağlar, öğrenim transferi ve autoencoder mimarileri. Örnek kullanım implementasyonuna bugün Keras’ın kaynak kodunda bile ulaşabiliyor olsak da arkasında zorlu bir teori bulunmakta. Bu yazıda stil transferinin arkasındaki matematiğe değinerek adım adım bir uygulama geliştireceğiz.
Öncelikle bu teknik tipik bir nöral ağ işleminden ibaret olacak. Nöral ağlar eğitim sırasında rastgele başlayan ağırlıkları girdi ve çıktı ikilisine göre ayarlamaktadır. Burada önceden eğitilmiş bir ağ kullanıyor olacağız ve ağırlıkları hiç güncellemeyeceğiz (Bknz: öğrenim transferi). Bu yapılar dışında hiç kullanılmayan şekilde ağırlıklar yerine girdimizi güncelleyeceğiz.
Referans çalışma önceden eğitilmiş ağ olarak VGG modelini kullanmaktadır. Bu modeli yüz tanıma uygulamalarından hatırlayacaksınız. Model basitçe aşağıda gösterildiği şekilde bir mimariye sahip.
Resimler
Bu çalışmada bir sanat eserinin stilini bir fotoğrafa aktarmaya çalışıyoruz. Stilini kullanacağımız esere stil resmi, dönüştüreceğimiz resme de içerik (content) resmi diyeceğiz. Stil resminin fırça darbelerinin içerik resmine aktarılmasıyla ortaya çıkan resme de yaratılan (generated) resim diyeceğiz.
İçerik ve stil resimleri zaten var olan resimlerdi. Nöral ağlarda eğitime başlamadan önce ağırlıkları rastgele olarak atadığımızı hatırlarsınız. Burada yaratılacak resmin piksel değerleri rastgele olarak atanacak. Aşağıdaki şekilde içerik, stil ve yaratılacak resimleri oluşturabiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def preprocess_image(image_path): img = load_img(image_path, target_size=(height, weight)) img = img_to_array(img) img = np.expand_dims(img, axis=0) img = preprocess_input(img) return img content_image = preprocess_image("content.jpeg") style_image = preprocess_image("style.jpg") height = 224; weight = 224 #original size of vgg random_pixels = np.random.randint(256, size=(1, height, weight, 3)) generated_image = preprocess_input(random_pixels, axis=0) |
Python resimleri 3 boyutlu olarak (RGB) saklasa da VGG mimarisi 4 boyutlu girdi beklemektedir. Bu sebeple expand_dim fonksiyonunu sanal bir boyut ekleme için kullandık. Bununla birlikte içerik ve stil resimlerini VGG mimarisinin beklediği şekilde 224x224x3 boyutuna dönüştürmemiz gerekti.
Ağ
Şimdi bu resimleri VGG ağına girdi olarak aktaracağız. İhtiyacımız olan ağın çıktısı yerine ağın bazı katmanlarının çıktısı olacak. Autoencoder‘ların verinin özetlenmesi için kullanıldığı hatırlayalım. Burada da VGG ağını bu resimleri özetlemek için kullanacağız.
Şanslıyız ki Keras VGG modelini hazır olarak bize sunmakta.
1 2 3 4 5 6 7 8 9 |
from keras.applications import vgg19 content_model = vgg19.VGG19(input_tensor=K.variable(content_image), weights='imagenet') style_model = vgg19.VGG19(input_tensor=K.variable(content_image), weights='imagenet') generated_model = vgg19.VGG19(input_tensor=K.variable(generated_image), weights='imagenet') |
Kayıp
İçerik ve stil resimleri için iki kayıp değeri saklayacağız. Tipik nöral ağlarda kayıp değeri gerçek çıktı ile ağın çıktısı yani tahmin değerinin farkından hesaplanırdı. Burada VGG modeli ile özetlediğimiz hallerini mukayese edeceğiz. Her bir katmanın çıktılarını aşağıdaki şekilde saklayalım.
1 2 3 4 5 6 7 |
content_outputs = dict([(layer.name, layer.output) for layer in content_model.layers]) style_outputs = dict([(layer.name, layer.output) for layer in style_model.layers]) generated_outputs = dict([(layer.name, layer.output) for layer in generated_model.layers]) |
İçerik kaybı
Rastgele yaratılmış resim ve içerik resmini VGG modeline girdi olarak göndereceğiz. Referans çalışma 5. bloğun 2. konvolüsyonel katmanını (block5_conv2) içerik kaybını hesaplamak için kullanmakta.
Önceki adımda zaten içerik ve rastgele yaratılmış resmi VGG modeline beslemiştik. İçerik kaybını bu katmanın çıktılarının farkının karesi şeklinde hesaplayabiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 |
def content_loss(content, generated): return K.sum(K.square(content - generated)) loss = K.variable(0) content_features = content_outputs['block5_conv2'] generated_features = generated_outputs['block5_conv2'] contentloss = content_loss(content_features, generated_features) |
Stil kaybı
Bu sefer 5 farklı katmanın çıktılarını karşılaştıracağız.
Burada, gram matrislerin uzaklığının bulunması beklenilmekte. Gram matris, matrisin tranpozesi ile çarpılması ile elde edilebilmektedir.
1 2 3 4 5 |
gram = K.dot(features, K.transpose(features)) |
Ancak, gram matris hesabı için 2 boyutlu matrislerle çalışmamız gerekiyor. batch_flatten komutu, n boyutlu bir matrisi 2 boyutlu şekle dönüştürmekte. Örneğin, VGG modelinin 3. konvolüsyonel katmanı (56×56)x256 boyutundadır. Buradaki 256 filtre sayısını ifade etmektedir. Bu katmanın boyutunu 256x56x56 şekline dönüştürürsek, 56×56 boyutundaki matrisleri yanına koyabiliriz. Bunun için de permutasyon fonksiyonu uygulamamız gerekecek.
1 2 3 4 5 6 7 8 9 |
def gram_matrix(x): #put number of filters to 1st dimension first features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1))) gram = K.dot(features, K.transpose(features)) return gram |
Gram matrisin görsel gösterimi aşağıdaki şekildedir.
Artık stil kaybını hesaplayabiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def style_loss(style, generated): style_gram = gram_matrix(style) content_gram = gram_matrix(generated) channels = 3 size = height * weight return K.sum(K.square(style_gram - content_gram)) / (4. * (pow(channels,2)) * (pow(size,2))) #name of first 5 layers. you can check it by running content_model.summary() feature_layers = ['block1_conv1', 'block2_conv1', 'block3_conv1', 'block4_conv1', 'block5_conv1'] styleloss = K.variable(0) for layer_name in feature_layers: style_features = style_outputs[layer_name] generated_features = generated_outputs[layer_name] styleloss = styleloss + style_loss(style_features[0], generated_features[0]) |
Toplam kayıp
Toplam kayıt stil ve içerik kaybının bir fonksiyonu olacak.
1 2 3 4 5 6 |
alpha = 0.025; beta = 0.2 total_loss = alpha * contentloss + beta * styleloss |
Gradient descent
Tipi nöral ağlarda kaybın değerini ağırlıklara yansıtırdık. Bunun için de toplam kaybın her bir ağırlığa göre kısmi türevini hesaplayarak ağırlığa ekleme yapardık. Stil transferinde ise kaybın her bir girdiye göre kısmi türevini hesaplayarak girdiyi güncelleyeceğiz.
1 2 3 4 5 6 7 |
#gradients = K.gradients(total_loss, generated_model.trainable_weights) gradients = K.gradients(total_loss, generated_model.input) print(gradients) |
Böylelikle (1, 224, 224, 3) boyutunda bir tensor’u gradyan olarak hesaplayıp girdiyi güncelleyeceğiz.
1 2 3 4 5 6 |
learning_rate = np.array([0.1]) generated_image = generated_image - learning_rate * gradients[0] |
Şu ana kadar anlatılan aslında basitçe gradyan azalım algoritmasının girdiye uygulanmasından ibarettir.
Test
Van Gogh’un Yıldızlı Gece portresinin stilini İstanbul boğazındaki Galatasaray Üniversitesinin resmine uygulamayı denediğimde aşağıdaki gibi harikulade bir sonuç elde edildi.
Tabi resmin bu hale gelmesi için 250 epoch boyunca eğitmem gerekti. Epoch’lar boyunca değişimi aşağıdaki videodan izleyebilirsiniz.
Bununla birlikte bir video her saniyesi 24 kareden oluşan fotoğraflardan ibarettir. Her bir karesine stil transferi uygulayarak Loving Vincent filmine benzer görüntüler elde edebiliyoruz. Benzer şekilde stil transferi uygulanan video serisine göz atmak isterseniz bu oynatma listesine bakabilirsiniz.
Farklı bir kaç sanatsal çalışma
Sizce de büyüleyici değiller mi?
Bitirirken…
Artistik stil transferi üst seviye bir kaç derin öğrenme yönteminin kombinasyonundan ibarettir. Bu yöntemlere ait çoğu Türkçe olan kaynakları link olarak yazı boyunca eklemeye çalıştım. Konuyu derinlemesine öğrenmek adına linkleri takip etmenizi şiddetle tavsiye ederim.
Bu çalışmaya ait kaynak koduna GitHub‘tan erişebilirsiniz. Repo’yu yıldızlayarak bu çalışmaya desteğinizi göstebilirsiniz 🙂
Bu yazı Artistic Style Transfer with Deep Learning yazısından Türkçe’ye çevrilmiştir.