Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/tutorials/generative/deepdream.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

DeepDream

Este tutorial contém uma implementação mínima do DeepDream, conforme descrito nesta postagem de blog por Alexander Mordvintsev.

O DeepDream é um experimento que visualiza os padrões aprendidos por uma rede neural. Similar a quando uma criança observa nuvens e tenta interpretar os formatos aleatórios, o DeepDream interpreta e aprimora os padrões que vê em uma imagem.

Para fazer isso, ele encaminha uma imagem pela rede e depois calcula o gradiente da imagem quanto às ativações de uma camada específica. Em seguida, a imagem é modificada para aumentar essas ativações, aprimorando os padrões vistos pela rede, o que resulta em uma imagem parecida com a de um sonho. Esse processo foi chamado de “Inceptionism” (uma referência a InceptionNet e ao filme A Origem, cujo título em inglês é Inception).

Vamos demonstrar como você pode fazer uma rede neural “sonhar” e aprimorar os padrões surreais que ela vê em uma imagem.

Dogception

import tensorflow as tf
import numpy as np import matplotlib as mpl import IPython.display as display import PIL.Image

Escolha de uma imagem para transformar em uma imagem de sonho

Neste tutorial, vamos usar a imagem de um labrador.

url = 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'
# Download an image and read it into a NumPy array. def download(url, max_dim=None): name = url.split('/')[-1] image_path = tf.keras.utils.get_file(name, origin=url) img = PIL.Image.open(image_path) if max_dim: img.thumbnail((max_dim, max_dim)) return np.array(img) # Normalize an image def deprocess(img): img = 255*(img + 1.0)/2.0 return tf.cast(img, tf.uint8) # Display an image def show(img): display.display(PIL.Image.fromarray(np.array(img))) # Downsizing the image makes it easier to work with. original_img = download(url, max_dim=500) show(original_img) display.display(display.HTML('Image cc-by: <a "href=https://commons.wikimedia.org/wiki/File:Felis_catus-cat_on_snow.jpg">Von.grzanka</a>'))

Preparar o modelo de extração de características

Baixe e prepare um modelo de classificação de imagens pré-treinado. Você usará o InceptionV3, similar ao modelo usado originalmente no DeepDream. Qualquer modelo pré-treinado funcionará, embora você precise ajustar os nomes das camadas abaixo, se alterar isto.

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')

A ideia do DeepDream é escolher uma camada (ou camadas) e maximizar a “perda” de forma que a imagem “estimule” cada vez mais as camadas. A complexidade das características incorporadas depende das camadas escolhidas: camadas mais baixas produzem pinceladas ou padrões simples, enquanto camadas profundas proporcionam características sofisticadas nas imagens ou até mesmo objetos inteiros.

A arquitetura do InceptionV3 é bem grande (para ver um grafo da arquitetura do modelo, confira o repositório de pesquisas do TensorFlow). Para o DeepDream, as camadas de interesse são aquelas em que as convoluções estão concatenadas. Há 11 camadas dessas no InceptionV3, chamadas de 'mixed0' a 'mixed10'. O uso de camadas diferentes resultará em imagens diferentes de um sonho. As camadas mais profundas produzem características de alto nível (como olhos e rostos), enquanto as camadas anteriores produzem características mais simples (como contornos, formatos e texturas). Fique à vontade para fazer experimentos com as camadas selecionadas abaixo, mas lembre-se de que camadas mais profundas (aquelas com um índice maior) levam mais tempo para serem treinadas, já que a computação dos gradientes é mais profunda.

# Maximize the activations of these layers names = ['mixed3', 'mixed5'] layers = [base_model.get_layer(name).output for name in names] # Create the feature extraction model dream_model = tf.keras.Model(inputs=base_model.input, outputs=layers)

Calcular a perda

A perda é a soma das ativações nas camadas escolhidas. A perda é normalizada em cada camada para que a contribuição de camadas maiores não seja superior à de camadas menores. Geralmente, a perda é a quantidade que você deseja minimizar pelo método do gradiente descendente. No DeepDream, você maximizará essa perda pelo método do gradiente ascendente.

def calc_loss(img, model): # Pass forward the image through the model to retrieve the activations. # Converts the image into a batch of size 1. img_batch = tf.expand_dims(img, axis=0) layer_activations = model(img_batch) if len(layer_activations) == 1: layer_activations = [layer_activations] losses = [] for act in layer_activations: loss = tf.math.reduce_mean(act) losses.append(loss) return tf.reduce_sum(losses)

Método do gradiente ascendente

Após calcular a perda para as camadas escolhidas, basta calcular os gradientes quanto à imagem e adicioná-los à imagem original.

Ao adicionar os gradientes à imagem, os padrões vistos pela rede são aprimorados. Em cada passo, você terá criado uma imagem que estimula cada vez mais as ativações de determinadas camadas da rede.

O método que faz isso, mostrado abaixo, é encapsulado em uma função tf.function por questões de desempenho. Ele usa um input_signature para garantir que não seja feito trace novamente da função para tamanhos de imagem diferentes ou valores de steps/step_size diferentes. Confira mais detalhes no guia Funções concretas.

class DeepDream(tf.Module): def __init__(self, model): self.model = model @tf.function( input_signature=( tf.TensorSpec(shape=[None,None,3], dtype=tf.float32), tf.TensorSpec(shape=[], dtype=tf.int32), tf.TensorSpec(shape=[], dtype=tf.float32),) ) def __call__(self, img, steps, step_size): print("Tracing") loss = tf.constant(0.0) for n in tf.range(steps): with tf.GradientTape() as tape: # This needs gradients relative to `img` # `GradientTape` only watches `tf.Variable`s by default tape.watch(img) loss = calc_loss(img, self.model) # Calculate the gradient of the loss with respect to the pixels of the input image. gradients = tape.gradient(loss, img) # Normalize the gradients. gradients /= tf.math.reduce_std(gradients) + 1e-8 # In gradient ascent, the "loss" is maximized so that the input image increasingly "excites" the layers. # You can update the image by directly adding the gradients (because they're the same shape!) img = img + gradients*step_size img = tf.clip_by_value(img, -1, 1) return loss, img
deepdream = DeepDream(dream_model)

Loop principal

def run_deep_dream_simple(img, steps=100, step_size=0.01): # Convert from uint8 to the range expected by the model. img = tf.keras.applications.inception_v3.preprocess_input(img) img = tf.convert_to_tensor(img) step_size = tf.convert_to_tensor(step_size) steps_remaining = steps step = 0 while steps_remaining: if steps_remaining>100: run_steps = tf.constant(100) else: run_steps = tf.constant(steps_remaining) steps_remaining -= run_steps step += run_steps loss, img = deepdream(img, run_steps, tf.constant(step_size)) display.clear_output(wait=True) show(deprocess(img)) print ("Step {}, loss {}".format(step, loss)) result = deprocess(img) display.clear_output(wait=True) show(result) return result
dream_img = run_deep_dream_simple(img=original_img, steps=100, step_size=0.01)

Uso de oitavas

Muito bem, mas essa primeira tentativa tem alguns problemas:

  1. A saída tem ruído (isso pode ser tratado com uma perda tf.image.total_variation).

  2. A imagem tem resolução baixa.

  3. Os padrões aparecem como se estivessem todos acontecendo com a mesma granularidade.

Uma estratégia que trata todos esses problemas é aplicar o método do gradiente ascendente em diferentes escalas. Isso permitirá que padrões gerados com escalas menores sejam incorporados aos padrões com escalas maiores e preenchidos com detalhes adicionais.

Para fazer isso, você pode usar o método do gradiente ascendente anterior, aumentar o tamanho da imagem (o que é chamado de oitava) e repetir esse processo para diversas oitavas.

import time start = time.time() OCTAVE_SCALE = 1.30 img = tf.constant(np.array(original_img)) base_shape = tf.shape(img)[:-1] float_base_shape = tf.cast(base_shape, tf.float32) for n in range(-2, 3): new_shape = tf.cast(float_base_shape*(OCTAVE_SCALE**n), tf.int32) img = tf.image.resize(img, new_shape).numpy() img = run_deep_dream_simple(img=img, steps=50, step_size=0.01) display.clear_output(wait=True) img = tf.image.resize(img, base_shape) img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8) show(img) end = time.time() end-start

Opcional: aumentar a escala com blocos

Um aspecto a se considerar é que, à medida que o tamanho da imagem aumenta, o tempo e a memória necessários para realizar o cálculo de gradientes também aumentam. A implementação de oitava acima não funcionará para imagens muito grandes ou para muitas oitavas.

Para evitar esse problema, você pode dividir a imagem em blocos e computar o gradiente para cada bloco.

A aplicação de mudanças aleatórias à imagem antes da computação de cada bloco evita que apareçam marcas entre os blocos.

Comece implementando a mudança aleatória:

def random_roll(img, maxroll): # Randomly shift the image to avoid tiled boundaries. shift = tf.random.uniform(shape=[2], minval=-maxroll, maxval=maxroll, dtype=tf.int32) img_rolled = tf.roll(img, shift=shift, axis=[0,1]) return shift, img_rolled
shift, img_rolled = random_roll(np.array(original_img), 512) show(img_rolled)

Veja abaixo um equivalente da função deepdream com blocos definida anteriormente:

class TiledGradients(tf.Module): def __init__(self, model): self.model = model @tf.function( input_signature=( tf.TensorSpec(shape=[None,None,3], dtype=tf.float32), tf.TensorSpec(shape=[2], dtype=tf.int32), tf.TensorSpec(shape=[], dtype=tf.int32),) ) def __call__(self, img, img_size, tile_size=512): shift, img_rolled = random_roll(img, tile_size) # Initialize the image gradients to zero. gradients = tf.zeros_like(img_rolled) # Skip the last tile, unless there's only one tile. xs = tf.range(0, img_size[1], tile_size)[:-1] if not tf.cast(len(xs), bool): xs = tf.constant([0]) ys = tf.range(0, img_size[0], tile_size)[:-1] if not tf.cast(len(ys), bool): ys = tf.constant([0]) for x in xs: for y in ys: # Calculate the gradients for this tile. with tf.GradientTape() as tape: # This needs gradients relative to `img_rolled`. # `GradientTape` only watches `tf.Variable`s by default. tape.watch(img_rolled) # Extract a tile out of the image. img_tile = img_rolled[y:y+tile_size, x:x+tile_size] loss = calc_loss(img_tile, self.model) # Update the image gradients for this tile. gradients = gradients + tape.gradient(loss, img_rolled) # Undo the random shift applied to the image and its gradients. gradients = tf.roll(gradients, shift=-shift, axis=[0,1]) # Normalize the gradients. gradients /= tf.math.reduce_std(gradients) + 1e-8 return gradients
get_tiled_gradients = TiledGradients(dream_model)

Ao juntar tudo, você tem uma implementação do DeepDream escalável e que usa oitavas.

def run_deep_dream_with_octaves(img, steps_per_octave=100, step_size=0.01, octaves=range(-2,3), octave_scale=1.3): base_shape = tf.shape(img) img = tf.keras.utils.img_to_array(img) img = tf.keras.applications.inception_v3.preprocess_input(img) initial_shape = img.shape[:-1] img = tf.image.resize(img, initial_shape) for octave in octaves: # Scale the image based on the octave new_size = tf.cast(tf.convert_to_tensor(base_shape[:-1]), tf.float32)*(octave_scale**octave) new_size = tf.cast(new_size, tf.int32) img = tf.image.resize(img, new_size) for step in range(steps_per_octave): gradients = get_tiled_gradients(img, new_size) img = img + gradients*step_size img = tf.clip_by_value(img, -1, 1) if step % 10 == 0: display.clear_output(wait=True) show(deprocess(img)) print ("Octave {}, Step {}".format(octave, step)) result = deprocess(img) return result
img = run_deep_dream_with_octaves(img=original_img, step_size=0.01) display.clear_output(wait=True) img = tf.image.resize(img, base_shape) img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8) show(img)

Muito melhor! Faça experimentos com o número de oitavas, a escala das oitavas e as camadas ativadas para mudar a aparência da imagem do DeepDream.

Os leitores também podem se interessar pelo TensorFlow Lucid, que expande as ideias apresentadas neste tutorial para visualizar e interpretar redes neurais.