CNN — Como fazer uma Transferência de Estilo facilmente

https://www.techleer.com/articles/466-insight-into-fast-style-transfer-in-tensorflow

Considerações iniciais

Neste tutorial, aprenderemos como usar o Deep Learning para compor imagens no estilo de outra imagem (quem nunca pensou em fazer uma pintura como Picasso ou Van Gogh?). Isso é conhecido como transferência de estilo neural!

Então, a transferência de estilo é uma maneira divertida e interessante de mostrar as capacidades das redes neurais. Mas antes de realizar um processo de transferência de estilo, vamos deixar claro que de fato isto é.

Transferência de estilo é um processo de modificar o estilo de uma imagem e, ao mesmo tempo, preservar o seu conteúdo.

Esta técnica é descrita no artigo, “A Neural Algorithm of Artistic Style”, que é uma ótima leitura, vale a pena dar uma checada. Desta forma, a ideia é simples. Há uma imagem de entrada e uma imagem de estilo. A saída será a sua imagem de entrada estilizada.

Entrada + Estilo -> Entrada estilizada

O conteúdo deste artigo pressupõe que o leitor já possua conhecimento na área, como o que é pré-processamento de dados e o funcionamento de Redes Neurais Convolucionais e Transfer Learning.


Implementação

Vamos programar uma rede neural, passo a passo, para fazer a transferência de estilo. Vamos começar importando algumas bibliotecas, o PIL para manejar as imagens, TensorFlow e Keras para a CNN, Numpy para as álgebra linear e Scipy para o nosso otimizador. O código inteiro comentado juntamente com um script está disponível no GitHub.

import numpy as np
from PIL import Image
import tensorflow as tf
from keras import backend as K
from keras.models import Model
from keras.applications.vgg16 import VGG16
from scipy.optimize import fmin_l_bfgs_b

Vamos então, definir nossas imagens de entrada e estilo:

No caso, escolhi como entrada o famoso Wallpaper do Windows XP:

Wallpaper do WIndows XP

e como estilo, o quadro de Paul Cezanne:

https://mymodernmet.com/wp/wp-content/uploads/2018/05/paul-cezanne-paintings-11.jpg

Pré-processamento das imagens

Como já sabem, antes de entrar com dados em algum método qualquer de Machine Learning é sempre bom realizar um pré-processamento nestes dados. Faremos uma normalização pela média, o que é simplesmente subtrair a média dos dados de treinamento da rede. No contexto da transferência de estilo, vamos usar uma CNN já treinada em imagens, no caso com as imagens da ImageNet. Por fim, invertemos a ordem de cores de BGR para RGB (PIL usa BGR e Numpy usa RGB).

Modelo da CNN

Vamos usar um modelo pré-treinado de Rede Neural Convolucional (CNN), a VGG-16. Este modelo é a escolha certa para o processamento de imagens. Além disso, nos permite extrair separadamente o conteúdo e o estilo de uma imagem, e é exatamente isso que queremos. Então, passaremos as duas imagens pelo VGG e inicializaremos a imagem a ser gerada em uma imagem aleatória.

VGG-16 e suas camadas

Tenha em mente que não usaremos camadas totalmente conectadas (azul) e Softmax (amarelo). Eles agem como um classificador que não precisamos aqui. Vamos usar apenas extratores de características, ou seja, camadas convolucionais (preto) e de MaxPooling (vermelho). Neste modelo, as informações são importantes e, usando o MaxPooling na CNN, estamos jogando fora um grande número de valores de pixels da camada anterior e estamos mantendo apenas os valores mais altos.

entrada = K.variable(img_entrada_arr)
estilo = K.variable(img_estilo_arr)
imagem_combinada = K.placeholder((1, largura_imagem, altura_imagem, canais))
tensor_entrada = K.concatenate([entrada, estilo, imagem_combinada], axis=0)
model = VGG16(input_tensor=tensor_entrada, include_top=False, weights='imagenet')

Este modelo contêm apenas os extratores de características, ou seja, as camadas convolucionais e MaxPooling da VGG-16. Desta forma é a rede que queremos utilizar toda já pré-treinada, realmente ideal para poupar o tempo de treinamento!

O artigo recomenda a camada de conteúdo sendo:

block2_conv2

E as camadas de estilo sendo:

[block1_conv2, block2_conv2, block3_conv3, block4_conv3, block5_conv3]

O problema de Otimização

As CNNs são feitas para resolver problemas de otimização. Desta forma, vamos definir qual é a função que deve ser minimizada. Para tal, devemos minimizar três funções:

  • Custo do conteúdo (distância entre as imagens de entrada e saída);
  • Custo de estilo (distância entre o estilo e a imagens de saída);
  • Custo de variação total (regularização — suavidade espacial para minimizar a imagem de saída).

Alguns modelos de transferência de estilo não fazem uso desta função de variação total, não há problema nenhum nisto, já que é uma função para a regularização do modelo, logo totalmente opcional.

Custo de conteúdo

O custo de conteúdo é definido pela equação abaixo. Pode se ver que é simples mente uma soma quadrática da diferença entre a entrada e a saída (combinação entre entrada e estilo).

O código que implementa esta função pode ser visto abaixo:

Custo de estilo

O custo de estilo (distância entre estilo e imagem de saída) é definido pela equação abaixo.

Nesta fórmula podemos ver que o que multiplica o somatório é um número apenas relativo as dimensões das imagens. E também G é um valor relativo a Matriz de Gram, definido pela equação:

O código que faz esta implementação pode ser visto abaixo, pode-se ver que na ultima linha, o custo é somado com ele mesmo mais o custo de estilo, ou seja o custo de conteúdo é somado com o custo de estilo.

Custo de variação total

Esta função de custo serve como regularizadora para suavizar os gradientes no treinamento e impedir o aumento de ruído.

Otimização

Agora que temos nossas funções de custo definidas, podemos definir nosso processo de transferência de estilo como um problema de otimização onde minimizaremos nossa perda global (que é uma combinação de perda de conteúdo, estilo e variação total). Em cada iteração, vamos criar uma imagem de saída para que a distância (diferença) entre saída e entrada / estilo nas camadas de recursos correspondentes seja minimizada.

Otimizador

No aprendizado de transferência de estilo, vamos usar um otimizador determinístico l-bfgs em vez do gradiente descendente ou Adam. Mas por que isto?

Ao contrario de um classificador, neste caso, o otimizador não recebe várias amostras diferentes e tenta generalizar todas. Na transferência de estilo, o otimizador recebe várias vezes a mesma imagem. Também, l-bfgs determina a direção e distância ideal a ser percorrida, fazendo uma pesquisa de linha. Em problemas estocásticos como classificações e regressões é uma abordagem cara computacionalmente, no entanto é uma boa abordagem para transferência de estilo. Desta forma, o l-bfgs aprende mais rápido que o Adam no problema em questão.

Resultados

No código acima, coloquei 10 iterações, o que é suficiente para gerar uma imagem nítida estilizada, mas idealmente é até melhor que sejam 15 iterações. Abaixo sequem algumas imagens que mostram o processo iterativo da transferência de estilo.

Imagens com respectivamente, uma, duas e cinco iterações
Imagem de entrada, estilo, e estilizada

Podemos ver claramente que a imagem final preserva o conteúdo do Wallpaper do Windows XP e faz com que a saída tem o estilo do Cezanne.

Considerações finais

Sucesso! Agora podemos treinar uma rede para transformar qualquer imagem em uma versão estilizada dela, com base no estilo de nossa imagem escolhida.

Se você achou legal e deseja “brincar” um pouco com isto, tente alguma(s) destas ideias:

  • Use pesos diferentes para o conteúdo, estilo e regularização;
  • Tente usar outra arquitetura, como a VGG-19, InceptionV3, ResNet, etc;
  • Usar outras camadas de conteúdo, isto pode manter o conteúdo mais (ou menos) coerente com a imagem original.

Apenas relembrando, o código deste projeto está disponível no GitHub juntamente com um script para testar em sua máquina facilmente. Não tenha medo de compartilhar seus resultados!

Caso gostou do artigo, aprendeu algo legal, ou qualquer coisa do gênero, deixe seus aplausos ;D

Outros exemplos

Uma citação legal só para finalizar mesmo:

“Há apenas uma maneira de evitar críticas: não falar, não fazer e não ser nada.” (Aristóteles)