Lewati ke konten utama

Mengimplementasikan Neural Style Transfer Menggunakan TensorFlow 2.0

Dalam tutorial ini, Anda akan mempelajari cara mengimplementasikan aplikasi andal seperti Prisma menggunakan TensorFlow 2.0.
· 11 mnt Baca

Jelajahi bersama AI

Convolutional neural network adalah jenis jaringan saraf yang paling kuat untuk klasifikasi dan analisis gambar. Aplikasinya telah melampaui banyak batasan dan terbukti menjadi elemen krusial dari banyak aplikasi deep learning yang kita lihat saat ini. Secara garis besar, CNN dapat mempelajari representasi fitur internal dari gambar yang diberikan. Inilah yang membuatnya sangat kuat. Ternyata fitur menonjol CNN ini tidak hanya berguna untuk tugas seperti klasifikasi gambar, tetapi juga untuk konstruksi gambar. Aplikasi seperti Deep Dream dan Neural Style Transfer menyusun gambar berdasarkan aktivasi layer di dalam CNN dan fitur-fitur yang diekstraksinya.

Dalam tutorial ini, Anda akan mempelajari cara kerja Neural Style Transfer dan bagaimana mengimplementasikannya menggunakan TensorFlow 2.0.

Catatan bahwa untuk mengikuti tutorial ini, Anda perlu memahami cara kerja CNN. Jika Anda mencari sumber untuk penyegaran singkat tentang CNN, coba yang berikut ini:

Apa itu Neural Style Transfer?

Bagi pendatang baru di bidang computer vision dan deep learning, istilah neural style transfer bisa terasa membingungkan. Untuk memahami setiap komponen istilah tersebut, perhatikan dua gambar berikut:

neural style transfer 1

Dalam konteks neural style transfer, gambar di sebelah kiri disebut sebagai gambar konten dan gambar di sebelah kanan disebut sebagai gambar gaya. Anda ingin menstylisasi satu gambar (yang kiri dalam kasus ini) menggunakan gambar lain (yang kanan). Inilah yang membentuk dua kata terakhir dalam istilah tersebut - style transfer. Untuk menjalankan prosesnya, sebuah jaringan saraf (CNN) dilatih untuk mengoptimalkan fungsi loss kustom, sehingga muncullah kata pertama - neural. Ketika dua gambar di atas digabung menggunakan neural style transfer, keluaran akhirnya tampak seperti ini (yang kanan) -

neural style transfer 2

Anda mungkin bertanya apa kegunaan neural style transfer? Bayangkan Anda memiliki gambar sebuah lukisan yang aslinya dibuat oleh Vincent van Gogh. Anda ingin melihat bagaimana lukisan itu akan tampak jika diterapkan pada salah satu gambar Anda sendiri. Ini adalah salah satu aplikasi di mana neural style transfer bermanfaat. Contoh lain adalah beberapa aplikasi filter foto seperti Prisma, yang memungkinkan Anda melakukan neural style transfer melalui antarmuka pengguna yang mulus.

Isolasi antara Konten dan Gaya

Sejauh ini, gagasan yang mungkin Anda tangkap tentang neural style transfer adalah bahwa prosesnya bertujuan menggabungkan konten dari satu gambar dengan gaya dari gambar lain. Faktanya, ini 100% benar. Pada bagian ini, mari pahami apa yang sebenarnya dimaksud dengan konten dan gaya dalam konteks CNN.

Sebuah CNN umumnya terdiri dari beberapa layer konvolusi dan pooling. Layer konvolusi bertanggung jawab mengekstraksi fitur yang sangat kompleks dari gambar tertentu, sedangkan layer pooling membuang informasi spasial terperinci yang tidak relevan untuk masalah klasifikasi gambar. Dampaknya, ini membantu CNN mempelajari konten dari sebuah gambar, bukan hal-hal spesifik seperti warna, tekstur, dan seterusnya. Semakin dalam ke CNN, kompleksitas fiturnya meningkat dan layer konvolusi yang lebih dalam sering disebut sebagai representasi konten.

Contoh gaya adalah hal-hal yang spesifik terhadap properti gambar seperti tekstur, warna, dan sebagainya—misalnya penggunaan menonjol warna tertentu dalam sebuah gambar. Sekarang pertanyaannya, bagaimana Anda mengekstraksi gaya sebuah gambar? Ini dilakukan dengan menghitung korelasi antar layer konvolusi. Korelasi memberi kita ukuran seberapa mirip/terkait dua atau lebih variabel. Untuk memahaminya, pertimbangkan sebuah layer konvolusi terlatih yang terdiri dari beberapa feature map. Untuk setiap feature map, Anda dapat mengukur seberapa kuat fitur yang dideteksinya berkaitan dengan feature map lainnya dalam layer yang sama. Ini memberi Anda estimasi hal-hal seperti—apakah warna tertentu yang terdeteksi pada feature map pertama mirip dengan warna pada peta lain? Adakah bentuk yang umum di antara feature map? Sifat/kemiripan ini mendefinisikan gaya sebuah gambar. Proses mengukur kemiripan antara isi beberapa feature map dalam sebuah layer konvolusi membantu jaringan mempelajari representasi multi-skala dari gambar yang berfokus pada fitur spasial seperti tekstur dan warna.

Satu poin penting yang perlu diingat adalah saat menerapkan neural style transfer, Anda juga perlu memastikan bahwa konten suatu gambar tetap terjaga bersama dengan gaya yang diinginkan dari gambar lain. Anda akan melihat bagaimana hal ini dilakukan di bagian berikutnya. Pada bagian selanjutnya, Anda akan belajar bagaimana mengekstraksi fitur dari sebuah gambar (gambar konten) dan menghitung content loss.

Mengukur Gambar Konten dan Menghitung Content Loss

Algoritma neural style transfer pertama kali diperkenalkan oleh Gatys dkk. dalam makalah tahun 2015 berjudul A Neural Algorithm of Artistic Style. Namun, tutorial ini merujuk pada Image Style Transfer Using Convolutional Neural Networks, yang merupakan kelanjutan dari makalah sebelumnya.

Menurut makalah Image Style Transfer Using Convolutional Neural Networks, digunakan arsitektur CNN VGG-19 untuk mengekstraksi fitur konten dan gaya dari gambar konten dan gaya masing-masing. Untuk mendapatkan fitur konten, digunakan layer konvolusi kedua dari blok keempat (dari layer konvolusi). Demi kemudahan, penulis makalah menamainya conv4_2. Setelah Anda memperoleh fitur konten, Anda harus membandingkannya dengan sebuah gambar target untuk mengukur content loss. Apa itu gambar target? Mengapa perlu menghitung content loss di sini? Mari mundur selangkah dan fokus pada dua pertanyaan ini.

Untuk menggabungkan fitur konten dan gaya ke dalam satu gambar, Anda akan mulai dengan sebuah gambar target, yang bisa berupa gambar kosong atau salinan gambar konten. Sekarang, untuk mempelajari fitur konten dan gaya secara efektif menggunakan CNN, Anda memerlukan fungsi loss kustom yang akan dioptimalkan untuk mendapatkan gambar bergaya yang halus yang dibangun dari gambar konten dan gaya. Fungsi loss kustom ini pada dasarnya merupakan gabungan dari dua loss yang berbeda:

  • Content loss, yang memastikan jumlah konten bersih tetap terjaga.
  • Style loss, yang mengatur seberapa banyak gaya ditransfer ke gambar target.

Sekarang setelah Anda memahami pertanyaan di atas, mari kembali ke content loss dan mendefinisikannya. Content loss didefinisikan sebagai berikut -

di mana, $T_c$ mengacu pada gambar target dan $C_c$ mengacu pada gambar konten. Jadi, apa yang dilakukan fungsi loss ini adalah memberi ukuran seberapa jauh fitur gambar konten dan target satu sama lain. Jaringan akan berupaya meminimalkan loss ini. Pada bagian selanjutnya, Anda akan melihat bagaimana style loss ditentukan.

Style Loss dan Gram Matrix

Untuk menentukan style loss, makalah tersebut mengarahkan Anda untuk mengambil representasi (pada dasarnya angka) dari layer-layer berikut dan memperoleh gram matrix dari feature map dalam layer-layer tersebut.

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

Keuntungan menggunakan banyak layer untuk mendefinisikan style loss adalah memungkinkan pembelajaran representasi multi-skala dari gaya yang terkandung dalam sebuah gambar. Gram matrix membantu Anda menentukan seberapa mirip fitur-fitur di seluruh feature map dalam satu layer konvolusi. Ini selanjutnya menggambarkan informasi yang tidak terlokalisasi tentang gambar yang membentuk gayanya. Sebuah gram matrix dalam konteks ini dihitung dengan cara berikut.

Pertimbangkan dimensi gambar hasil konvolusi adalah 8x8x16, yang terutama menunjukkan bahwa terdapat 16 feature map. Anda sekarang tertarik menemukan kemiripan antar fitur di seluruh feature map ini menggunakan gram matrix. Untuk itu, Anda meratakan dua dimensi pertama dari gambar terkonvolusi dan mengubahnya menjadi vektor 1D. Dimensi setelah diratakan, dalam kasus ini, akan berisi 64 entri dalam vektor 1D. Ini diulangi di seluruh feature map. Jadi, Anda mendapatkan matriks akhir berdimensi 16x64 (sebut matriks A). Matriks ini kemudian ditranspos (menjadi 64x16) dan dikalikan dengan matriks A. Gram matrix, dalam hal ini, berdimensi 16x16. Nilai tertentu dalam gram matrix akan menunjukkan kemiripan antar feature map. Secara visual proses ini menyerupai berikut:

style loss and gram matrix

Untuk menghitung style loss antara gambar target dan gambar gaya, Anda mengambil jarak kuadrat rata-rata antara gram matrix mereka pada masing-masing blok layer konvolusi. Secara formal, ini dapat didefinisikan sebagai berikut:

di mana, T(s,i) adalah gram matrix dari gambar target yang dihitung pada blok ke-i dan S(s,i) adalah gram matrix dari gambar gaya yang dihitung pada blok ke-i. Dengan wi, Anda dapat memberikan bobot kustom pada berbagai blok konvolusi untuk memperoleh representasi gaya yang lebih terperinci. Terakhir, $a$ adalah konstanta yang memperhitungkan nilai pada setiap layer dalam blok. Mari gabungkan kedua loss untuk mendefinisikan total loss yang dioptimalkan jaringan dalam proses neural style transfer.

Mendefinisikan Total Loss

Total loss kini hanyalah persoalan penjumlahan dengan bobot kustom -

di mana, $\alpha$ menyatakan bobot konten, dan $\beta$ menyatakan bobot gaya. Ini membantu Anda menjaga keseimbangan yang baik antara konten dan jumlah gaya yang ditransfer ke gambar target. Dampak kombinasi berbeda dari $\alpha$ dan $\beta$ dapat dilihat dalam makalah, dan Anda dianjurkan untuk memeriksanya. Sekarang Anda memiliki semua komponen untuk mengimplementasikan neural style transfer. Mari langsung ke bagiannya pada bagian berikut.

Neural Style Transfer dalam Praktik

Untuk implementasinya, Anda akan menggunakan TensorFlow 2.0. Versi ini memiliki banyak tambahan luar biasa dan merupakan salah satu pembaruan paling komprehensif pada pustaka hingga saat ini. Jika Anda tertarik mempelajari beberapa di antaranya, Anda dapat melihat artikel ini.

Versi terbaru TensorFlow (saat penulisan tutorial ini) adalah 2.0.0-beta0. Jika Anda belum memasangnya, silakan pasang terlebih dahulu dengan mengikuti petunjuk yang ditentukan di sini. Anda akan mulai dengan mengimpor paket yang diperlukan serta gambar konten dan gaya.

# 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

Pada langkah berikutnya, Anda akan menulis fungsi pembantu yang akan memuat kedua gambar ke dalam array angka (karena komputer hanya memahami angka) dan mengubah ukurannya agar kompatibel dengan model.

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]))

Gambar-gambar telah diubah ukurannya. Sekarang, Anda akan memuat model VGG19 pra-latih untuk mengekstraksi fitur. Karena Anda akan menggunakan model untuk mengekstraksi fitur, Anda tidak memerlukan bagian classifier dari model.

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

Para penulis telah menentukan nama blok yang mereka gunakan untuk mengekstraksi fitur konten dan gaya. Model VGG19 yang baru saja Anda muat memiliki konvensi penamaan yang sama. Jadi untuk memanfaatkan itu, pertama-tama Anda akan mencetak nama semua layer yang ada dalam jaringan.

# 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

Anda tertarik pada yang berikut untuk mendapatkan fitur gaya, namun demikian:

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

Untuk fitur konten, Anda memerlukan conv4_2. Anda akan menyimpannya dalam variabel masing-masing.

# 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)

Sekarang buat model VGG kustom yang akan terdiri dari layer-layer yang ditentukan. Ini akan membantu Anda menjalankan forward pass pada gambar dan mengekstraksi fitur yang diperlukan di sepanjang proses.

def mini_model(layer_names, model):

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

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

Mendefinisikan gram matrix sangat mudah di TensorFlow, dan Anda dapat melakukannya sebagai berikut:

# 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

Sekarang Anda akan mendefinisikan model kustom menggunakan fungsi mini_model(). Ini akan digunakan untuk mengembalikan fitur konten dan gaya dari masing-masing gambar.

 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}

Sekarang setelah Anda mendefinisikan model kustom, mari gunakan pada gambar untuk mendapatkan fitur konten dan gaya masing-masing -

# 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)

Sekarang mari definisikan bobot keseluruhan untuk konten dan gaya serta bobot untuk masing-masing representasi gaya seperti dibahas sebelumnya. Catatan bahwa ini adalah hiperparameter dan sebaiknya Anda eksplorasi.

 # 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}

Sekarang bagian yang paling krusial, yang membuat proses neural style transfer jauh lebih seru - fungsi loss.

# 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

Sekarang Anda akan menulis fungsi lain yang akan:

  • Menghitung gradien dari fungsi loss yang baru saja Anda definisikan.
  • Menggunakan gradien ini untuk memperbarui gambar target.

Dengan GradientTape, Anda dapat memanfaatkan diferensiasi otomatis, yang dapat menghitung gradien suatu fungsi berdasarkan komposisinya. Anda juga akan menggunakan dekorator tf.function untuk mempercepat operasi. Baca selengkapnya di sini.

@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))

Satu langkah terakhir sebelum Anda dapat melatih jaringan adalah mendefinisikan gambar target. Untuk gambar target, Anda akan menggunakan gambar konten.

target_image = tf.Variable(content)

Sekarang Anda benar-benar siap melatih jaringan.

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..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 

Anda disarankan untuk bereksperimen dengan bobot konten dan gaya guna mengamati perubahan pada gambar target.

Penutup

Makalah yang Anda rujuk di tutorial ini tidak menyebutkan tentang pengoptimalan lebih lanjut terhadap kualitas gambar yang dibangun. Seperti yang mungkin Anda lihat pada hasil di atas, dibutuhkan tingkat kehalusan tertentu dalam hubungan spasial antara keseluruhan konten dan gaya dari gambar yang dibangun. Untuk mengatasi masalah ini, diperkenalkan total_variantional_loss, yang analog dengan penggunaan regularisasi. Secara garis besar, total_variational_loss memberikan penalti pada artefak frekuensi tinggi yang diperkenalkan oleh algoritma neural style transfer asli. Lihat tutorial ini, jika Anda tertarik untuk mengimplementasikannya. Berikut adalah beberapa sumber yang menjadi referensi penulisan tutorial ini:

Jika Anda ingin mengasah keterampilan deep learning, Anda mungkin ingin melihat kursus dan tutorial DataCamp berikut:

Topik

Terkait

blogs

Tutorial Korelasi di R

blogs

Spaghetti Plot dan Jalur Badai