Ga naar hoofdinhoud

Neural Style Transfer implementeren met TensorFlow 2.0

In deze tutorial leer je hoe je krachtige applicaties zoals Prisma kunt implementeren met TensorFlow 2.0.
Bijgewerkt 2 okt 2026  · 11 min lezen

Verken met AI

ChatGPTClaudePerplexity

Convolutionele neurale netwerken zijn de krachtigste soort neurale netwerken voor beeldclassificatie en -analyse. Hun toepassingen hebben vele grenzen overschreden en zijn bewezen als het kritieke element van veel deep-learningtoepassingen die we vandaag de dag zien. Op hoog niveau kunnen CNN’s de interne representaties op kenmerkeniveau leren van de beelden die je ze aanbiedt. Dat maakt ze zo krachtig. Het blijkt dat deze opvallende eigenschap van CNN’s niet alleen nuttig is voor taken als beeldclassificatie, maar ook voor beeldconstructie. Toepassingen zoals Deep Dream en Neural Style Transfer componeren beelden op basis van laagactivaties binnen CNN’s en de daaruit geëxtraheerde features.

In deze tutorial ga je bekijken hoe Neural Style Transfer werkt en hoe je dit kunt implementeren met TensorFlow 2.0.

Let op: om deze tutorial te kunnen volgen, moet je weten hoe CNN’s werken. Als je op zoek bent naar een korte opfrisser over CNN’s, probeer dan het volgende:

Wat is Neural Style Transfer?

Voor nieuwkomers in het veld van computervisie en deep learning kan de term neural style transfer wat overweldigend zijn. Om elk onderdeel van de term te begrijpen, bekijk de volgende twee afbeeldingen:

neural style transfer 1

In de context van neural style transfer wordt de linkerafbeelding de content-afbeelding genoemd en de afbeelding rechts de style-afbeelding. Je wilt één afbeelding (in dit geval de linker) stijlen met behulp van een andere afbeelding (de rechter). Dit vormt de laatste twee woorden van de term: style transfer. Om dit proces uit te voeren, wordt een neuraal netwerk (CNN) getraind om een aangepaste verliesfunctie te optimaliseren — vandaar het eerste woord: neural. Wanneer de bovenstaande twee afbeeldingen worden samengevoegd met neural style transfer, ziet de uiteindelijke output er zo uit (rechts) —

neural style transfer 2

Je kunt je afvragen wat het nut is van neural style transfer. Stel dat je een afbeelding had van een tekening die oorspronkelijk door Vincent van Gogh is gemaakt. Je wilt zien hoe deze tekening eruit zou zien in een van je eigen schetsen. Dit is een van de toepassingen waar neural style transfer tot zijn recht komt. Een ander voorbeeld zijn verschillende fotofilter-apps zoals Prisma, waarmee je neural style transfer kunt uitvoeren via een gebruiksvriendelijke interface.

Isolatie tussen content en stijl

Tot nu toe is het idee dat je waarschijnlijk hebt gekregen over neural style transfer dat het proces draait om het combineren van de content van de ene afbeelding met de stijl van een andere. Dat is inderdaad 100% waar. In deze sectie bekijken we wat er precies wordt bedoeld met content en stijl in de context van CNN’s.

Een CNN is vaak een verzameling van meerdere convolutionele lagen en poolinglagen. Convolutionele lagen zijn verantwoordelijk voor het extraheren van zeer complexe kenmerken uit een afbeelding, terwijl poolinglagen gedetailleerde ruimtelijke informatie weggooien die niet relevant is voor een beeldclassificatieprobleem. Het effect hiervan is dat het de CNN helpt om de content van een afbeelding te leren in plaats van iets specifieks zoals kleur, textuur, enzovoort. Naarmate we dieper in een CNN gaan, neemt de complexiteit van de kenmerken toe en worden de diepere convolutionele lagen vaak contentrepresentaties genoemd.

Een voorbeeld van stijl is alles wat specifiek is voor afbeeldings­eigenschappen zoals textuur, kleur, enzovoort — bijvoorbeeld een opvallend gebruik van een bepaalde kleur in een tekening. De vraag is nu: hoe extraheer je de stijl van een afbeelding? Dit gebeurt door correlaties tussen de convolutionele lagen te berekenen. Correlaties geven ons een maat voor hoe sterk twee of meer variabelen op elkaar lijken/gerelateerd zijn. Om dit te begrijpen, neem een geleerde convolutionele laag die is opgebouwd uit meerdere feature maps. Voor elke feature map kun je meten hoe sterk de gedetecteerde kenmerken zich verhouden tot de andere feature maps in dezelfde laag. Dit geeft je een schatting van zaken als: lijkt een bepaalde kleur die in de eerste feature map wordt gedetecteerd op een kleur in een andere map? Zijn er vormen die gemeen zijn tussen de feature maps? Deze eigenschappen/overeenkomsten definiëren de stijl van een afbeelding. Het proces van het meten van de overeenkomst tussen de inhoud van verschillende feature maps binnen een convolutionele laag helpt netwerken een multi-scale representatie van de afbeelding te leren die focust op ruimtelijke kenmerken zoals textuur en kleur.

Een belangrijk punt om in gedachten te houden: bij het toepassen van neural style transfer moet je er ook voor zorgen dat de content van een afbeelding behouden blijft, samen met de gewenste stijl van een andere afbeelding. Je ziet later hoe dit wordt gedaan. In de volgende sectie leer je hoe je de features uit een afbeelding (content-afbeelding) extraheert en het contentverlies berekent.

De content-afbeelding kwantificeren en het contentverlies berekenen

Het neural style transfer-algoritme werd voor het eerst geïntroduceerd door Gatys et al. in hun paper uit 2015, A Neural Algorithm of Artistic Style. Deze tutorial verwijst echter naar Image Style Transfer Using Convolutional Neural Networks, wat een soort vervolg is op de eerder genoemde paper.

Volgens de paper Image Style Transfer Using Convolutional Neural Networks wordt een VGG-19-CNN-architectuur gebruikt om zowel de content- als de stijleigenschappen respectievelijk uit de content- en stijl­afbeeldingen te extraheren. Om de contentfeatures te krijgen, wordt de tweede convolutionele laag uit het vierde blok (van convolutionele lagen) gebruikt. Voor het gemak noemden de auteurs van de paper deze laag conv4_2. Zodra je de contentfeatures hebt, moet je ze vergelijken met een doelafbeelding om het contentverlies te meten. Wat is een doelafbeelding? Waarom is het nodig om hier contentverlies te berekenen? Laten we een stap terug doen en ons op deze twee vragen richten.

Om de content- en stijleigenschappen in één afbeelding te combineren, begin je met een doelafbeelding, die ofwel leeg is of een kopie van de content-afbeelding. Om zowel de content- als de stijleigenschappen effectief te leren met een CNN, heb je een aangepaste verliesfunctie nodig die je optimaliseert om een vloeiende, gestileerde afbeelding te construeren uit de content- en stijl­afbeeldingen. Deze aangepaste verliesfunctie is in wezen een samenvoeging van twee verschillende verliezen:

  • Contentverlies, dat ervoor zorgt dat de netto hoeveelheid content behouden blijft.
  • Stijlverlies, dat bepaalt hoeveel stijl er naar de doelafbeelding wordt overgebracht.

Nu je een idee hebt van de bovenstaande vragen, gaan we terug naar het contentverlies en definiëren we dit. Het contentverlies wordt als volgt gedefinieerd —

waarbij $T_c$ verwijst naar de doelafbeelding en $C_c$ naar de content-afbeelding. Wat deze verliesfunctie doet, is een maat geven voor hoe ver de features van de content- en doelafbeeldingen van elkaar verwijderd zijn. Het netwerk zal dit verlies proberen te minimaliseren. In de volgende sectie zie je hoe het stijlverlies wordt bepaald.

Het stijlverlies en de Gram-matrix

Voor het bepalen van het stijlverlies instrueert de paper je om de representaties (in feite getallen) uit de volgende lagen te nemen en de Gram-matrices van de feature maps binnen deze lagen te verkrijgen.

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

Het voordeel van het gebruik van meerdere lagen om het stijlverlies te definiëren, is dat dit toelaat een multi-scale representatie van de stijl in een afbeelding te leren. De Gram-matrices helpen je te bepalen hoe vergelijkbaar de features zijn over de feature maps binnen één convolutionele laag. Dit toont verder niet-gelokaliseerde informatie over de afbeelding die de stijl ervan vormgeeft. Een Gram-matrix wordt in deze context als volgt berekend.

Stel dat de dimensie van een geconvolveerde afbeelding 8x8x16 is, wat aangeeft dat hij 16 feature maps heeft. Je wilt nu de overeenkomsten tussen de features over deze feature maps vinden met een Gram-matrix. Daarvoor vlakken we de eerste twee dimensies van de geconvolveerde afbeelding af tot een 1D-vector. De dimensies bevatten in dit geval 64 elementen in de 1D-vector. Dit herhaal je over de feature maps. Zo krijg je een uiteindelijke matrix met dimensie 16x64 (noem deze matrix A). Deze matrix wordt vervolgens getransponeerd (wordt 64x16) en vermenigvuldigd met matrix A. Een Gram-matrix heeft in dit geval de dimensie 16x16. Een bepaalde waarde in de Gram-matrix geeft de overeenkomsten tussen de feature maps aan. Visueel lijkt dit proces op het volgende:

style loss and gram matrix

Om het stijlverlies tussen de doel- en stijl­afbeeldingen te berekenen, neem je de gemiddeld-kwadratische afstanden tussen hun Gram-matrices bij elk van de convolutionele blokken. Formeel kan dit als volgt worden gedefinieerd:

waarbij T(s,i) de Gram-matrix is van de doelafbeelding berekend in blok i en S(s,i) de Gram-matrix is van de stijl­afbeelding berekend in blok i. Met wi kun je aangepaste gewichten toekennen aan de verschillende convolutionele blokken om een gedetailleerde representatie van de stijl te verkrijgen. Ten slotte is $a$ een constante die rekening houdt met de waarden in elke laag binnen de blokken. Laten we de twee verliezen samenvoegen om het totale verlies te definiëren dat het netwerk optimaliseert tijdens neural style transfer.

Het totale verlies definiëren

Het totale verlies is nu simpelweg een optelsom met aangepaste gewichten —

waarbij $\alpha$ het contentgewicht aanduidt en $\beta$ het stijlgewicht. Dit helpt je een goede balans te bewaren tussen de content en de hoeveelheid stijl die wordt overgebracht naar de doelafbeelding. Het effect van verschillende combinaties van $\alpha$ en $\beta$ is te zien in de paper; bekijk dat vooral. Je hebt nu alle onderdelen om neural style transfer te implementeren. Laten we er in de volgende sectie direct mee aan de slag gaan.

Neural Style Transfer in de praktijk

Voor de implementatie gebruik je TensorFlow 2.0. Deze versie heeft veel uitzonderlijke toevoegingen en is een van de meest uitgebreide updates van de bibliotheek tot nu toe. Als je hierover meer wilt leren, bekijk dan dit artikel.

De nieuwste versie van TensorFlow (ten tijde van het schrijven van deze tutorial) is 2.0.0-beta0. Als je die nog niet hebt geïnstalleerd, volg dan eerst de instructies zoals hier gespecificeerd. Je begint met het importeren van de benodigde pakketten en de content- en stijl­afbeeldingen.

# 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

In de volgende stap schrijf je een helperfunctie die de twee afbeeldingen laadt als arrays met getallen (computers begrijpen immers alleen getallen) en ze reshaped om ze compatibel te maken met het 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]))

De afbeeldingen zijn gereshaped. Nu laad je een voorgetraind VGG19-model om de features te extraheren. Omdat je het model gebruikt om features te extraheren, heb je het classificatiegedeelte van het model niet nodig.

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

De auteurs hebben de bloknamen gespecificeerd die ze gebruikten voor het extraheren van de content- en stijleigenschappen. Het VGG19-model dat je zojuist hebt geladen, hanteert dezelfde naamgeving. Om daarvan te profiteren, print je eerst de namen van alle lagen in het netwerk.

# 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

Je bent echter geïnteresseerd in de volgende voor het verkrijgen van de stijleigenschappen:

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

Voor contentfeatures heb je conv4_2 nodig. Je slaat deze overeenkomstig op in variabelen.

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

Maak nu een aangepast VGG-model dat bestaat uit de gespecificeerde lagen. Dit helpt je om forward passes op de afbeeldingen uit te voeren en onderweg de benodigde features te extraheren.

def mini_model(layer_names, model):

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

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

Een Gram-matrix definiëren is supermakkelijk in TensorFlow en kan als volgt:

# 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

Je definieert nu een aangepast model met de functie mini_model(). Dit wordt gebruikt om de content- en stijleigenschappen uit de respectieve afbeeldingen te retourneren.

 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}

Nu je het aangepaste model hebt gedefinieerd, gebruiken we het op de afbeeldingen om respectievelijk de content- en stijleigenschappen te verkrijgen —

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

Laten we nu de algemene content- en stijlgewichten definiëren en ook de gewichten voor elk van de stijlrepresentaties zoals eerder besproken. Let op: dit zijn hyperparameters waar je mee zou moeten spelen.

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

Nu komt het belangrijkste onderdeel, dat het proces van neural style transfer een stuk leuker maakt: de verliesfunctie.

# 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

Je schrijft nu een andere functie die het volgende doet:

  • De gradiënten berekenen van de verliesfunctie die je zojuist hebt gedefinieerd.
  • Deze gradiënten gebruiken om de doelafbeelding bij te werken.

Met GradientTape kun je profiteren van automatische differentiatie, die de gradiënten van een functie kan berekenen op basis van de samenstelling ervan. Je gebruikt ook de decorator tf.function om de operaties te versnellen. Lees er meer over hier.

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

De enige stap die nog resteert voordat je het netwerk kunt trainen, is het definiëren van de doelafbeelding. Voor de doelafbeelding gebruik je de content-afbeelding.

target_image = tf.Variable(content)

Je bent nu helemaal klaar om het netwerk te trainen.

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 

Het is aan te raden om te spelen met de content- en stijlgewichten om de veranderingen in de doelafbeelding te observeren.

Slotopmerkingen

De paper waarnaar je in deze tutorial verwees, noemde geen verdere optimalisatie van de kwaliteit van de geconstrueerde afbeelding. Zoals je waarschijnlijk in de bovenstaande resultaten hebt gezien, is er wat extra gladheid nodig in de ruimtelijke relatie tussen de algehele content en stijl van de geconstrueerde afbeelding. Om dit probleem aan te pakken, werd total_variantional_loss geïntroduceerd, analoog aan het gebruik van regularisatie. Op hoog niveau straft total_variational_loss de hoogfrequente artefacten die door het oorspronkelijke neural style transfer-algoritme worden geïntroduceerd. Bekijk deze tutorial als je geïnteresseerd bent om dit te implementeren. De volgende bronnen zijn geraadpleegd bij het schrijven van deze tutorial:

Als je je deep learning-vaardigheden wilt aanscherpen, bekijk dan de volgende DataCamp-cursussen en -tutorials:

Onderwerpen
Python
Deep Learning

Leer meer over Python en Deep Learning

Cursus

Introductie tot TensorFlow in Python

4 uur
56.6K
Leer de basis van neurale netwerken en hoe je deep learning-modellen kunt bouwen met TensorFlow.
Bekijk detailsRight Arrow
Start Cursus
Meer bekijkenRight Arrow
Gerelateerd

blog

AI vanaf nul leren in 2026: een complete gids van de experts

Ontdek alles wat je moet weten om in 2026 AI te leren, van tips om te beginnen tot handige resources en inzichten van industrie-experts.
Adel Nehme's photo

Adel Nehme

15 min

Meer BekijkenMeer Bekijken