Pular para o conteúdo principal

Implementando neural style transfer com TensorFlow 2.0

Neste tutorial, você vai aprender a implementar aplicativos poderosos como o Prisma usando o TensorFlow 2.0.
Atualizado 17 de set. de 2026  · 11 min lido

Explorar com IA

ChatGPTClaudePerplexity

Redes neurais convolucionais são a classe mais poderosa de redes neurais para classificação e análise de imagens. Suas aplicações já ultrapassaram muitos limites e se provaram elemento essencial de várias soluções de deep learning que vemos hoje. Em linhas gerais, CNNs conseguem aprender representações internas de alto nível das imagens que recebem como entrada. É isso que as torna tão poderosas. E esse recurso não serve só para tarefas como classificação de imagens, mas também para construção de imagens. Aplicativos como Deep Dream e Neural Style Transfer compõem imagens com base nas ativações de camadas dentro de CNNs e nos recursos extraídos por elas.

Neste tutorial, você vai entender como o Neural Style Transfer funciona e como implementá-lo usando o TensorFlow 2.0.

Observação: para acompanhar este tutorial, é importante saber como CNNs funcionam. Se você quiser um reforço rápido, confira estes recursos:

O que é neural style transfer?

Para quem está começando em visão computacional e deep learning, o termo neural style transfer pode soar um pouco intimidante. Para entender cada parte do conceito, considere as duas imagens a seguir:

neural style transfer 1

No contexto de neural style transfer, a imagem da esquerda é chamada de imagem de conteúdo, e a da direita é a imagem de estilo. A ideia é estilizar uma imagem (a da esquerda, neste caso) usando outra (a da direita). Isso explica as duas últimas palavras do termo: style transfer. Para realizar o processo, uma rede neural (CNN) é treinada para otimizar uma função de loss personalizada — daí o primeiro termo: neural. Ao fundir as duas imagens acima usando neural style transfer, o resultado final fica assim (a da direita) –

neural style transfer 2

Você pode se perguntar: qual é a utilidade do neural style transfer? Imagine que você tenha uma imagem de um desenho originalmente feito por Vincent van Gogh e queira ver como esse estilo ficaria aplicado a um desenho seu. Esse é um dos usos do neural style transfer. Outro exemplo são vários apps de filtro de fotos, como o Prisma, que permitem aplicar neural style transfer com uma interface super amigável.

Isolando conteúdo e estilo

Até aqui, a ideia é que o processo consiste em combinar o conteúdo de uma imagem com o estilo de outra. E isso é 100% verdade. Nesta seção, vamos entender o que significam conteúdo e estilo no contexto de CNNs.

Uma CNN geralmente é composta por várias camadas convolucionais e camadas de pooling. As camadas convolucionais extraem recursos altamente complexos de uma imagem, enquanto as camadas de pooling descartam informações espaciais detalhadas que não são relevantes para um problema de classificação. Com isso, a CNN aprende o conteúdo de uma imagem, em vez de características específicas como cor, textura etc. Quanto mais fundo vamos em uma CNN, maior a complexidade dos recursos; por isso, camadas convolucionais mais profundas costumam ser chamadas de representações de conteúdo.

Um exemplo de estilo seria algo específico às propriedades da imagem, como textura, cor e afins — por exemplo, o uso predominante de uma determinada cor em um quadro. Mas como extrair o estilo de uma imagem? Isso é feito calculando correlações entre as camadas convolucionais. Correlações medem o quanto duas ou mais variáveis são semelhantes/relacionadas. Para entender, considere uma camada convolucional aprendida composta por vários feature maps. Para cada feature map, você pode medir quão fortemente os recursos detectados por ele se relacionam com os de outros feature maps na mesma camada. Isso dá uma estimativa de coisas como: uma certa cor detectada no primeiro feature map é semelhante a uma cor em outro mapa? Existem formas comuns entre os feature maps? Esses traços/semelhanças definem o estilo de uma imagem. Medir a similaridade entre conteúdos de vários feature maps dentro de uma camada convolucional ajuda a rede a aprender uma representação multiescala da imagem, focada em aspectos espaciais como textura e cor.

Um ponto essencial: ao aplicar neural style transfer, você também precisa garantir que o conteúdo da imagem seja preservado junto com o estilo desejado da outra imagem. Você verá como isso é feito mais adiante. Na próxima seção, vamos aprender a extrair os recursos de uma imagem (de conteúdo) e calcular o content loss.

Quantificando a imagem de conteúdo e calculando o content loss

O algoritmo de neural style transfer foi apresentado por Gatys et al. no paper de 2015, A Neural Algorithm of Artistic Style. Este tutorial, porém, toma como referência Image Style Transfer Using Convolutional Neural Networks, uma continuação do trabalho anterior.

Segundo o paper Image Style Transfer Using Convolutional Neural Networks, é usada a arquitetura VGG-19 para extrair tanto os recursos de conteúdo quanto os de estilo das respectivas imagens. Para obter os recursos de conteúdo, utiliza-se a segunda camada convolucional do quarto bloco (de camadas convolucionais). Para conveniência, os autores a nomearam conv4_2. Depois de extrair os recursos de conteúdo, é preciso compará-los com uma imagem alvo para medir o content loss. O que é a imagem alvo? Por que precisamos calcular o content loss aqui? Vamos dar um passo atrás e focar nessas duas perguntas.

Para combinar os recursos de conteúdo e de estilo em uma única imagem, você começa com uma imagem alvo, que pode ser em branco ou uma cópia da imagem de conteúdo. Para que a CNN aprenda de forma eficaz tanto conteúdo quanto estilo, você precisa de uma função de loss personalizada, que será otimizada para gerar uma imagem estilizada suave a partir das imagens de conteúdo e de estilo. Essa loss personalizada é, essencialmente, a combinação de duas perdas diferentes:

  • Content loss, que garante que a quantidade de conteúdo seja preservada.
  • Style loss, que controla a quantidade de estilo transferido para a imagem alvo.

Agora que isso está claro, vamos voltar ao content loss e defini-lo. O content loss é definido assim –

onde $T_c$ é a imagem alvo e $C_c$ é a imagem de conteúdo. Essa função de loss mede o quão distantes estão os recursos das imagens de conteúdo e alvo. A rede tentará minimizar essa perda. Na próxima seção, veremos como determinar o style loss.

O style loss e a matriz de Gram

Para determinar o style loss, o paper orienta a pegar as representações (valores numéricos) das camadas abaixo e obter as matrizes de Gram dos feature maps dentro dessas camadas.

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

Usar várias camadas para definir o style loss permite aprender uma representação multiescala do estilo presente em uma imagem. As matrizes de Gram ajudam a medir o quão semelhantes são os recursos entre os feature maps dentro de uma mesma camada convolucional. Isso captura informações não localizadas da imagem que moldam seu estilo. Neste contexto, uma matriz de Gram é calculada assim:

Considere uma imagem convoluída de dimensão 8x8x16, indicando principalmente que há 16 feature maps. Queremos encontrar as similaridades entre os recursos desses mapas usando uma matriz de Gram. Para isso, achatamos as duas primeiras dimensões da imagem convoluída em um vetor 1D. Se achatadas, as dimensões terão 64 entradas no vetor 1D. Repetimos isso para cada feature map. Assim, obtemos uma matriz final de dimensão 16x64 (chame-a de matriz A). Em seguida, transposta (vira 64x16) e multiplicada pela matriz A. A matriz de Gram, neste caso, terá dimensão 16x16. Cada valor na matriz de Gram indica a similaridade entre feature maps. Visualmente, o processo se parece com isto:

style loss and gram matrix

Para calcular o style loss entre as imagens alvo e de estilo, calcula-se a distância média quadrática entre suas matrizes de Gram em cada um dos blocos de camadas convolucionais. Formalmente, podemos definir assim:

onde T(s,i) é a matriz de Gram da imagem alvo calculada no bloco i e S(s,i) é a matriz de Gram da imagem de estilo calculada no bloco i. Com wi, você pode atribuir pesos diferentes aos blocos convolucionais para obter uma representação de estilo mais detalhada. Por fim, $a$ é uma constante que normaliza os valores de cada camada nos blocos. Vamos juntar as duas perdas para definir a perda total otimizada pela rede no processo de neural style transfer.

Definindo a perda total

A perda total é a soma ponderada simples –

onde $\alpha$ representa o peso do conteúdo e $\beta$ representa o peso do estilo. Isso ajuda a manter um bom equilíbrio entre o conteúdo e a quantidade de estilo transferido para a imagem alvo. O efeito de diferentes combinações de $\alpha$ e $\beta$ é mostrado no paper — vale conferir. Agora você tem todas as peças para implementar neural style transfer. Vamos colocar a mão na massa na próxima seção.

Neural style transfer na prática

Para a implementação, vamos usar o TensorFlow 2.0. Ele traz muitas adições extraordinárias e é uma das atualizações mais abrangentes da biblioteca até hoje. Se quiser conhecer algumas delas, confira este artigo.

A versão mais recente do TensorFlow (na época em que este tutorial foi escrito) é a 2.0.0-beta0. Se você ainda não a instalou, siga as instruções aqui. Vamos começar importando os pacotes necessários e carregando as imagens de conteúdo e de estilo.

# 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

No próximo passo, vamos escrever uma função auxiliar para carregar as duas imagens em arrays numéricos (computadores entendem números) e redimensioná-las para ficarem compatíveis com o modelo.

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

As imagens foram redimensionadas. Agora, vamos carregar um modelo VGG19 pré-treinado para extrair os recursos. Como vamos usá-lo apenas para extração, não precisamos da parte classificadora do modelo.

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

Os autores especificaram os nomes dos blocos usados para extrair recursos de conteúdo e estilo. O VGG19 que você acabou de carregar usa a mesma convenção. Para aproveitar isso, vamos primeiro imprimir os nomes de todas as camadas da rede.

# 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

Para os recursos de estilo, estamos interessados nas seguintes camadas:

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

Para o conteúdo, você vai precisar de conv4_2. Vamos guardar isso em variáveis.

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

Agora crie um VGG personalizado composto pelas camadas especificadas. Isso vai permitir fazer forward passes nas imagens e extrair os recursos necessários no caminho.

def mini_model(layer_names, model):

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

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

Definir uma matriz de Gram no TensorFlow é super simples. Você pode fazer assim:

# 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

Agora vamos definir um modelo customizado usando a função mini_model(). Ele será usado para retornar os recursos de conteúdo e de estilo das respectivas imagens.

 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}

Com o modelo customizado definido, vamos usá-lo nas imagens para obter os recursos de conteúdo e de estilo, respectivamente:

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

Agora vamos definir os pesos gerais de conteúdo e estilo e também os pesos de cada representação de estilo, como discutido antes. Observação: esses são hiperparâmetros — vale a pena experimentar.

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

Agora vem a parte mais crucial — e que torna o processo bem divertido: a função de 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

Agora vamos escrever outra função que vai:

  • Calcular os gradientes da função de loss que acabamos de definir.
  • Usar esses gradientes para atualizar a imagem alvo.

Com o GradientTape, você aproveita a diferenciação automática, que calcula gradientes de uma função com base na sua composição. Também vamos usar o decorador tf.function para acelerar as operações. Saiba mais aqui.

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

O último passo antes de treinar a rede é definir a imagem alvo. Aqui, vamos usar a própria imagem de conteúdo.

target_image = tf.Variable(content)

Agora sim: hora de treinar a rede.

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 

Brinque com os pesos de conteúdo e estilo para observar as mudanças na imagem alvo.

Considerações finais

O paper usado como referência aqui não mencionou otimizações adicionais de qualidade da imagem gerada. Como você deve ter notado nos resultados acima, ainda é preciso suavizar a relação espacial entre o conteúdo geral e o estilo da imagem construída. Para resolver isso, foi introduzido o total_variational_loss, análogo ao uso de regularização. Em alto nível, o total_variational_loss penaliza artefatos de alta frequência introduzidos pelo algoritmo original de neural style transfer. Confira este tutorial se quiser implementá-lo. Abaixo estão alguns recursos que serviram de referência para escrever este tutorial:

Se você quer aprimorar suas habilidades em deep learning, vale conferir estes cursos e tutoriais da DataCamp:

Tópicos
Python
Aprendizagem profunda

Saiba mais sobre Python e deep learning

Curso

Introdução ao TensorFlow em Python

4 h
56.5K
Aprenda os fundamentos das redes neurais e como criar modelos de aprendizado profundo usando TensorFlow.
Ver detalhesRight Arrow
Iniciar Curso
Ver maisRight Arrow
Relacionado

Tutorial

Tutorial do modelo de transformador no PyTorch: Da teoria ao código

Saiba como criar um modelo Transformer usando o PyTorch, uma ferramenta poderosa do machine learning moderno.
Arjun Sarkar's photo

Arjun Sarkar

15 min

Tutorial

Tutorial do Adam Optimizer: Intuição e implementação em Python

Compreender e implementar o otimizador Adam em Python. Com o PyTorch, você aprenderá a intuição, a matemática e as aplicações práticas do machine learning

Tutorial

Como treinar um LLM com o PyTorch

Domine o processo de treinamento de grandes modelos de linguagem usando o PyTorch, desde a configuração inicial até a implementação final.
Zoumana Keita 's photo

Zoumana Keita

8 min

Tutorial

Dominando a retropropagação: Um guia abrangente para redes neurais

Mergulhe nos fundamentos da retropropagação em redes neurais com um guia prático para treinar e avaliar um modelo para um cenário de uso de classificação de imagens.
Zoumana Keita 's photo

Zoumana Keita

14 min

Tutorial

Introdução ao Q-learning: um tutorial para iniciantes

Aprenda o algoritmo de aprendizado por reforço sem modelo mais popular com um tutorial em Python.
Abid Ali Awan's photo

Abid Ali Awan

11 min

Tutorial

Como criar aplicativos LLM com o tutorial LangChain

Explore o potencial inexplorado dos modelos de linguagem grandes com o LangChain, uma estrutura Python de código aberto para criar aplicativos avançados de IA.
Moez Ali's photo

Moez Ali

12 min

Ver MaisVer Mais