{"metadata":{"colab":{"provenance":[]},"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":30698,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"    ### Copying images to training, validation, and test directories\n\nimport os.path\nfrom os import path\nimport shutil\n\nimport zipfile\n \n# Substitua 'nome_do_arquivo.zip' pelo nome do arquivo zip que você carregou\nwith zipfile.ZipFile('../input/dogs-vs-cats/train.zip', 'r') as zip_ref:\n    zip_ref.extractall('/kaggle/working/')  # Especifica o diretório onde você deseja extrair os arquivos\n    \n    \n\noriginal_dataset_dir = '/kaggle/working/train'\nbase_dir = './cats_and_dogs_small'\nos.makedirs(base_dir, exist_ok=True)\n\ntrain_dir = os.path.join(base_dir, 'train')\nos.makedirs(train_dir, exist_ok=True)\nvalidation_dir = os.path.join(base_dir, 'validation')\nos.makedirs(validation_dir, exist_ok=True)\ntest_dir = os.path.join(base_dir, 'test')\nos.makedirs(test_dir, exist_ok=True)\n\ntrain_cats_dir = os.path.join(train_dir, 'cats')\nos.makedirs(train_cats_dir, exist_ok=True)\n\ntrain_dogs_dir = os.path.join(train_dir, 'dogs')\nos.makedirs(train_dogs_dir, exist_ok=True)\n\nvalidation_cats_dir = os.path.join(validation_dir, 'cats')\nos.makedirs(validation_cats_dir, exist_ok=True)\n\nvalidation_dogs_dir = os.path.join(validation_dir, 'dogs')\nos.makedirs(validation_dogs_dir, exist_ok=True)\n\ntest_cats_dir = os.path.join(test_dir, 'cats')\nos.makedirs(test_cats_dir, exist_ok=True)\n\ntest_dogs_dir = os.path.join(test_dir, 'dogs')\nos.makedirs(test_dogs_dir, exist_ok=True)\n\nfnames = ['cat.{}.jpg'.format(i) for i in range(1000)]\nfor fname in fnames:\n  src = os.path.join(original_dataset_dir, fname)\n  dst = os.path.join(train_cats_dir, fname)\n  # print(src, dst)\n  shutil.copyfile(src, dst)\n\nfnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]\nfor fname in fnames:\n  src = os.path.join(original_dataset_dir, fname)\n  dst = os.path.join(validation_cats_dir, fname)\n  shutil.copyfile(src, dst)\n\nfnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]\nfor fname in fnames:\n  src = os.path.join(original_dataset_dir, fname)\n  dst = os.path.join(test_cats_dir, fname)\n  shutil.copyfile(src, dst)","metadata":{"id":"2R6R1KMxwi6C","execution":{"iopub.status.busy":"2024-05-03T13:02:38.897352Z","iopub.execute_input":"2024-05-03T13:02:38.897895Z","iopub.status.idle":"2024-05-03T13:02:52.744744Z","shell.execute_reply.started":"2024-05-03T13:02:38.897861Z","shell.execute_reply":"2024-05-03T13:02:52.743782Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Construindo a rede**\n\nJá construímos uma pequena convnet para a base de dados MNIST no exemplo anterior, então você deve estar familiarizado com tais convnets. Reutilizaremos a mesma estrutura geral: a convnet será um empilhamento alternado de camadas `Conv2D` (com ativação `relu`) e `MaxPooling2D`.\n\nPorém, como você está lidando com imagens maiores e um problema mais complexo, você fará sua rede maior, consequentemente: ela terá mais uma etapa `Conv2D + MaxPooling2D`.\nIsso serve tanto para aumentar a capacidade da rede quanto para reduzir ainda mais o tamanho dos mapas de características para que não fiquem excessivamente grandes quando você atingir a camada `Flatten`.\nAqui, pelo fato de se começar com entradas de tamanho 150 × 150 (uma escolha um tanto arbitrária), termina-se com mapas de características de tamanho 7 × 7 imediatamente antes da camada `Flatten`.","metadata":{"id":"-Gv7bNddULXH"}},{"cell_type":"code","source":"### Instantiating a small convnet for dogs vs. cats classification\n\nfrom keras import layers\nfrom keras import models\nmodel = models.Sequential()\nmodel.add(layers.Conv2D(32, (3, 3), activation='relu',\ninput_shape=(150, 150, 3)))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(64, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(128, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(128, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Flatten())\nmodel.add(layers.Dense(512, activation='relu'))\nmodel.add(layers.Dense(1, activation='sigmoid'))\nprint(model.summary())","metadata":{"id":"f7n6LgR7VBoG","execution":{"iopub.status.busy":"2024-05-03T13:03:16.560343Z","iopub.execute_input":"2024-05-03T13:03:16.560739Z","iopub.status.idle":"2024-05-03T13:03:31.137622Z","shell.execute_reply.started":"2024-05-03T13:03:16.560711Z","shell.execute_reply":"2024-05-03T13:03:31.13631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Configuring the model for training\n\nfrom keras import optimizers\nmodel.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(learning_rate=1e-4), metrics=['acc'])","metadata":{"id":"POiNu5YkVLim","execution":{"iopub.status.busy":"2024-05-03T13:03:47.732958Z","iopub.execute_input":"2024-05-03T13:03:47.733388Z","iopub.status.idle":"2024-05-03T13:03:47.745839Z","shell.execute_reply.started":"2024-05-03T13:03:47.733358Z","shell.execute_reply":"2024-05-03T13:03:47.744525Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Pré-processamento dos dados**\n\nComo já sabemos, os dados devem ser formatados em tensores de ponto flutuante devidamente pré-processados antes de serem alimentados na rede. Atualmente, os dados estão armazenados em arquivos JPEG no disco, então as etapas para inseri-los na rede são aproximadamente as seguintes:\n\n1. Ler os arquivos de imagem.\n2. Decodificar o conteúdo JPEG para grades RGB de pixels.\n3. Converter essas grades em tensores de ponto flutuante.\n4. Reescalar os valores dos pixels (entre 0 e 255) para o intervalo [0, 1] (como sabemos, as redes neurais preferem lidar com valores de entrada pequenos).\n\nPode parecer um difícil, mas felizmente o Keras possui ferramentas para cuidar automaticamente dessas etapas. O Keras possui um módulo com ferramentas auxiliares de processamento de imagem, localizado em `keras.preprocessing.image`. Em particular, ele contém a classe `ImageDataGenerator`, que permite configurar rapidamente geradores em Python que podem transformar automaticamente arquivos de imagem no disco em lotes de tensores pré-processados. É isso que você usará aqui.","metadata":{"id":"bi4jpcYpVRt1"}},{"cell_type":"code","source":"### Using ImageDataGenerator to read images from directories\n\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\ntrain_datagen = ImageDataGenerator(rescale=1./255)\ntest_datagen = ImageDataGenerator(rescale=1./255)\ntrain_generator = train_datagen.flow_from_directory(train_dir, target_size=(150, 150),\n  batch_size=20, class_mode='binary')\nvalidation_generator = test_datagen.flow_from_directory(validation_dir,\n  target_size=(150, 150), batch_size=20, class_mode='binary')","metadata":{"id":"T3zmww5GVskU","execution":{"iopub.status.busy":"2024-05-03T13:04:02.305075Z","iopub.execute_input":"2024-05-03T13:04:02.30556Z","iopub.status.idle":"2024-05-03T13:04:02.388047Z","shell.execute_reply.started":"2024-05-03T13:04:02.305522Z","shell.execute_reply":"2024-05-03T13:04:02.386623Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Vamos dar uma olhada na saída de um desses geradores: ele produz lotes de imagens RGB de 150 × 150 (formato `(20, 150, 150, 3)`) e rótulos binários (formato `(20,)`). Há 20 amostras em cada lote (o tamanho do lote). Observe que o gerador produz esses lotes indefinidamente: ele faz um loop interminável sobre as imagens na pasta de destino. Por esse motivo, é preciso interromper o loop de iteração em algum momento:","metadata":{"id":"xQAIi-TbWKEM"}},{"cell_type":"code","source":"for data_batch, labels_batch in train_generator:\n  print('data batch shape:', data_batch.shape)\n  print('labels batch shape:', labels_batch.shape)\n  break","metadata":{"id":"tJsUqk_qWZEl","execution":{"iopub.status.busy":"2024-05-03T13:04:04.957421Z","iopub.execute_input":"2024-05-03T13:04:04.957902Z","iopub.status.idle":"2024-05-03T13:04:05.021016Z","shell.execute_reply.started":"2024-05-03T13:04:04.957866Z","shell.execute_reply":"2024-05-03T13:04:05.019513Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Vamos ajustar o modelo aos dados usando o gerador. Isso é feito usando o método `fit_generator`, o equivalente ao `fit` para geradores de dados. Ele espera como seu primeiro argumento um gerador Python que produzirá lotes de entradas e alvos indefinidamente. Como os dados estão sendo gerados indefinidamente, o modelo do Keras precisa saber quantas amostras extrair do gerador antes de declarar uma época concluída. Isso é feito pelo argumento `steps_per_epoch`: após extrair `steps_per_epoch` lotes do gerador, ou seja, após executar `steps_per_epoch` passos de descida de gradiente, o processo de ajuste passará para a próxima época. Neste caso, os lotes têm 20 amostras, então levará 100 lotes até atingir sua meta de 2.000 amostras.\n\nAo usar `fit_generator`, pode-se passar um argumento `validation_data`, da mesma forma que com o método `fit`. É importante observar que esse argumento pode ser um gerador de dados, mas também pode ser uma tupla de arrays Numpy. Se passarmos um gerador como `validation_data`, esse gerador deverá produzir lotes de dados de validação indefinidamente; portanto, também devemos especificar o argumento `validation_steps`, que informa ao processo quantos lotes extrair do gerador de validação para avaliação.","metadata":{"id":"ePYJV3LdWjb1"}},{"cell_type":"code","source":"### Fitting the model using a batch generator\n\nhistory = model.fit(train_generator, steps_per_epoch=100,\nepochs=30, validation_data=validation_generator, validation_steps=50)\n\nmodel.save('cats_and_dogs_small_1.h5')","metadata":{"id":"nXGLjrSPW-sd","execution":{"iopub.status.busy":"2024-05-03T13:04:08.757223Z","iopub.execute_input":"2024-05-03T13:04:08.757628Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Displaying curves of loss and accuracy during training\n\nimport matplotlib.pyplot as plt\nacc = history.history['acc']\nval_acc = history.history['val_acc']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\n\nepochs = range(1, len(acc) + 1)\n\nplt.plot(epochs, acc, 'bo', label='Training acc')\nplt.plot(epochs, val_acc, 'b', label='Validation acc')\nplt.title('Training and validation accuracy')\nplt.legend()\n\nplt.figure()\n\nplt.plot(epochs, loss, 'bo', label='Training loss')\nplt.plot(epochs, val_loss, 'b', label='Validation loss')\nplt.title('Training and validation loss')\nplt.legend()\n\nplt.show()","metadata":{"id":"pI0443ckXH99","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Esses gráficos são característicos de *overfitting*. A precisão do treinamento aumenta linearmente ao longo do tempo, até atingir quase 100%, enquanto a precisão da validação estagna em 70–72%. A perda de validação atinge seu mínimo após apenas cinco épocas e depois estagna, enquanto a perda de treinamento continua diminuindo linearmente até atingir quase 0.\n\nComo temos relativamente poucas amostras de treinamento (2.000), o *overfitting* será a nossa maior preocupação. Já conhecemos várias técnicas que podem ajudar a mitigar o *overfitting*, como dropout e regularização de pesos (regularização L2). Agora vamos trabalhar com uma nova técnica, específica para visão computacional e usada quase universalmente ao processar imagens com modelos de *deep learning*: **aumento de dados**.","metadata":{"id":"Ks-VXQq4XYUF"}},{"cell_type":"markdown","source":"**Usando aumento de dados**\n\nO *overfitting* é causado por ter poucas amostras para aprender, tornando impossível treinar um modelo que possa generalizar para novos dados. Com dados infinitos, seu modelo seria exposto a todos os aspectos possíveis da distribuição de dados em questão: você nunca teria *overfitting*. O aumento de dados adota a abordagem de gerar mais dados de treinamento a partir das amostras de treinamento existentes, aplicando transformações aleatórias que produzem imagens realistas. O objetivo é garantir que, durante o treinamento, o modelo nunca veja a mesma imagem exata duas vezes. Isso ajuda a expor o modelo a mais aspectos dos dados e a generalizar melhor.\n\nNo Keras, isso pode ser feito configurando uma série de transformações aleatórias a serem realizadas nas imagens lidas pela instância do ImageDataGenerator. Vamos começar com um exemplo.","metadata":{"id":"KlAPixpDX_5N"}},{"cell_type":"code","source":"### Setting up a data augmentation configuration via ImageDataGenerator\n\ndatagen = ImageDataGenerator(\n  rotation_range=40,\n  width_shift_range=0.2,\n  height_shift_range=0.2,\n  shear_range=0.2,\n  zoom_range=0.2,\n  horizontal_flip=True,\n  fill_mode='nearest')","metadata":{"id":"bkb8b9_dYidn","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Aqui estão apenas algumas das opções disponíveis (para mais informações, consulte a documentação do Keras). Vamos dar uma olhada rápida neste código:\n\n- `rotation_range` é um valor em graus (0–180), um intervalo no qual girar aleatoriamente as imagens.\n- `width_shift` e `height_shift` são intervalos (como frações da largura ou altura total) nos quais transladar aleatoriamente as imagens vertical ou horizontalmente.\n- `shear_range` serve para aplicar aleatoriamente transformações de cisalhamento.\n- `zoom_range` serve para realizar zoom aleatório nas imagens.\n- `horizontal_flip` serve para inverter aleatoriamente metade das imagens horizontalmente, sendo relevante quando não há suposições de assimetria horizontal (por exemplo, em imagens do mundo real).\n- `fill_mode` é a estratégia usada para preencher pixels recém-criados, que podem aparecer após uma rotação ou um deslocamento de largura/altura.\n\nVamos dar uma olhada nas imagens aumentadas.","metadata":{"id":"gKBsoBmNYo-w"}},{"cell_type":"code","source":"### Displaying some randomly augmented training images\n\nfrom keras.preprocessing import image\nfnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]\nimg_path = fnames[3]\nimg = image.load_img(img_path, target_size=(150, 150))\n\nx = image.img_to_array(img)\nx = x.reshape((1,) + x.shape)\ni = 0\n\nfor batch in datagen.flow(x, batch_size=1):\n  plt.figure(i)\n  imgplot = plt.imshow(image.array_to_img(batch[0]))\n  i += 1\n  if i % 4 == 0:\n    break\n\nplt.show()","metadata":{"id":"Z0gQYXayY9U0","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Se treinarmos uma nova rede usando essa configuração de aumento de dados, a rede nunca verá a mesma entrada duas vezes. No entanto, as entradas que ela vê ainda estão fortemente intercorrelacionadas, porque vêm de um pequeno número de imagens originais - as operações que fizemos não podem produzir novas informações, só podem remixar informações existentes. Como tal, isso pode não ser suficiente para eliminar completamente o *overfitting*. Para mitigar ainda mais o *overfitting*,  também adicionaremos uma camada de Dropout ao modelo, logo antes do classificador densamente conectado.","metadata":{"id":"eSm7QBU7ZYJ3"}},{"cell_type":"code","source":"### Defining a new convnet that includes dropout\n\nmodel = models.Sequential()\nmodel.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(64, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(128, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(128, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Flatten())\nmodel.add(layers.Dropout(0.5))\nmodel.add(layers.Dense(512, activation='relu'))\nmodel.add(layers.Dense(1, activation='sigmoid'))\n\nmodel.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])","metadata":{"id":"1B1KUowXZ4NV","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Training the convnet using data-augmentation generators\n\ntrain_datagen = ImageDataGenerator(\n  rescale=1./255,\n  rotation_range=40,\n  width_shift_range=0.2,\n  height_shift_range=0.2,\n  shear_range=0.2,\n  zoom_range=0.2,\n  horizontal_flip=True,)\n\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_generator = train_datagen.flow_from_directory(\n  train_dir,\n  target_size=(150, 150),\n  batch_size=32,\n  class_mode='binary')\n\nvalidation_generator = test_datagen.flow_from_directory(\n  validation_dir,\n  target_size=(150, 150),\n  batch_size=32,\n  class_mode='binary')\n\nhistory = model.fit_generator(\n  train_generator,\n  steps_per_epoch=100,\n  epochs=100,\n  validation_data=validation_generator,\n  validation_steps=50)\n\nmodel.save('cats_and_dogs_small_2.h5')\n","metadata":{"id":"gcWBK9IKaBSU","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Exercício 1**\n\n1. Plote os gráficos relativos à precisão e à perda de validação.\n\n2. Compare os resultados com o modelo anterior.","metadata":{"id":"F0NmDrBqaTbE"}},{"cell_type":"markdown","source":"Se utilizarmos técnicas de regularização de maneira mais intensiva e ajustarmos os parâmetros da rede (como o número de filtros por camada de convolução ou o número de camadas na rede), podemos obter uma precisão ainda melhor, provavelmente até 86% ou 87%. No entanto, pode ser difícil alcançar uma precisão significativamente maior apenas treinando sua própria convnet do zero, devido à quantidade limitada de dados.\nComo próximo passo para melhorar a precisão nesse problema, será necessário usar um modelo pré-treinado, que é o nosso foco a seguir. Antes disso:\n\n**Exercício 2**\n1. Utilize técnicas de regularização e ajuste os parâmetros da rede de modo a se aproximar de uma precisão de 86%.","metadata":{"id":"TiNZTtjvam0U"}},{"cell_type":"markdown","source":"**Usando uma convnet pré-treinada**\n\nUma abordagem comum e altamente eficaz para aprendizado profundo em conjuntos de dados pequenos de imagens é usar uma rede pré-treinada. Uma rede pré-treinada é uma rede salva que foi treinada anteriormente em um grande conjunto de dados, geralmente em uma tarefa de classificação de imagens em grande escala. Se este conjunto de dados original for grande e abrangente o suficiente, a hierarquia espacial de características aprendidas pela rede pré-treinada pode agir efetivamente como um modelo genérico do mundo visual. Portanto, suas características podem ser úteis para muitos problemas diferentes de visão computacional, mesmo que esses novos problemas envolvam classes completamente diferentes em relação à tarefa original. Por exemplo, você pode treinar uma rede no ImageNet (onde as classes são principalmente animais e objetos do dia a dia) e depois reutilizar essa rede treinada para algo tão distante quanto identificar itens de mobília em imagens. Essa portabilidade de características aprendidas entre diferentes problemas é uma vantagem fundamental da aprendizagem profunda em comparação com muitas abordagens mais antigas e rasas, tornando a aprendizagem profunda muito eficaz para problemas de dados pequenos.\n\nNeste caso, consideremos uma convnet grande treinada no conjunto de dados ImageNet (1.4 milhão de imagens rotuladas e 1000 classes diferentes). O ImageNet contém muitas classes de animais, incluindo diferentes espécies de gatos e cachorros, e, portanto, você pode esperar um bom desempenho no problema de classificação entre cães e gatos.\n\nUsaremos a arquitetura VGG16, desenvolvida por Karen Simonyan e Andrew Zisserman em 2014; é uma arquitetura de convnet simples e amplamente utilizada para o ImageNet. Embora seja um modelo mais antigo, longe do estado da arte atual e um pouco mais pesado do que muitos modelos mais recentes, sua arquitetura é semelhante ao que já estamos familiarizado e é fácil de entender sem introduzir novos conceitos.\n\nEsta pode ser a sua primeira experiência com um desses nomes de modelos \"simpáticos\" - VGG, ResNet, Inception, Inception-ResNet, Xception, e assim por diante. Você se acostumará com eles, porque eles surgirão com frequência se continuar a trabalhar com aprendizagem profunda para visão computacional.\n\nExistem duas maneiras de usar uma rede pré-treinada: extração de características (*feature extraction*) e ajuste fino (*fine-tuning*). Vamos abordar ambas abaixo.","metadata":{"id":"3678p9xqgYg1"}},{"cell_type":"markdown","source":"**1. Feature extraction**\n\nA extração de características consiste em usar as representações aprendidas por uma rede anterior para extrair características interessantes de novas amostras. Essas características são então passadas por um novo classificador, que é treinado do zero.\n\nComo vimos anteriormente, convnets usadas para classificação de imagens são compostas por duas partes: começam com uma série de camadas de convolução e pooling e terminam com um classificador densamente conectado. A primeira parte é chamada de **base convolucional** do modelo. No caso das convnets, a extração de características consiste em pegar a base convolucional de uma rede previamente treinada, fazer passar os novos dados por ela e treinar um novo classificador com base na saída.\n\nPor que reutilizar apenas a base convolucional? Será que também poderíamos reutilizar o classificador densamente conectado? Em geral, isso deve ser evitado. A razão é que as representações aprendidas pela base convolucional são mais propensas a serem genéricas e, portanto, mais reutilizáveis: os mapas de características de uma convnet são mapas de presença de conceitos genéricos em uma imagem, o que provavelmente será útil independentemente do problema de visão computacional em questão. No entanto, as representações aprendidas pelo classificador serão necessariamente específicas para o conjunto de classes no qual o modelo foi treinado - elas conterão apenas informações sobre a probabilidade de presença desta ou daquela classe em toda a imagem. Além disso, as representações encontradas em camadas densamente conectadas não contêm mais informações sobre onde os objetos estão localizados na imagem de entrada: essas camadas eliminam a noção de espaço, enquanto a localização do objeto ainda é descrita pelos mapas de características convolucionais. Para problemas em que a localização do objeto é importante, as características densamente conectadas são em grande parte inúteis.\n\nObserve que o nível de generalidade (e, portanto, reusabilidade) das representações extraídas por camadas específicas de convolução depende da profundidade da camada no modelo. Camadas que estão no início do modelo extraem mapas de características locais e altamente genéricos (como bordas visuais, cores e texturas), enquanto camadas mais elevadas extraem conceitos mais abstratos (como \"orelha de gato\" ou \"olho de cachorro\"). Portanto, se seu novo conjunto de dados diferir muito do conjunto de dados no qual o modelo original foi treinado, pode ser mais vantajoso usar apenas as primeiras camadas do modelo para extração de características, em vez de usar a base de convolução inteira.\n\nNeste caso, como o conjunto de classes do ImageNet contém várias classes de cães e gatos, é provável que seja benéfico reutilizar as informações contidas nas camadas densamente conectadas do modelo original. No entanto, optaremos por não fazer isso, a fim de abranger o caso mais geral em que o conjunto de classes do novo problema não se sobrepõe ao conjunto de classes do modelo original. Vamos colocar isso em prática usando a base convolucional da rede VGG16, treinada no ImageNet, para extrair características interessantes de imagens de gatos e cachorros, e depois treinar um classificador de cães versus gatos com base nessas características.\n\nO modelo VGG16, entre outros, vem com o Keras. Podemos importá-lo do módulo `keras.applications`. Aqui está a lista de modelos de classificação de imagens (todos pré-treinados no conjunto de dados ImageNet) que estão disponíveis como parte do `keras.applications`:\n\n- Xception\n- Inception V3\n- ResNet50\n- VGG16\n- VGG19\n- MobileNet\n\nVamos instanciar o modelo VGG16.","metadata":{"id":"vZwHoyCWkU8P"}},{"cell_type":"code","source":"### Instantiating the VGG16 convolutional base\n\nfrom keras.applications import VGG16\n  conv_base = VGG16(weights='imagenet',\n  include_top=False,\n  input_shape=(150, 150, 3))","metadata":{"id":"B-A1yLIXmRVS"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Você passa três argumentos para o construtor:\n- `weights` especifica o *checkpoint* de pesos a partir do qual inicializar o modelo.\n- `include_top` refere-se a incluir (ou não) o classificador densamente conectado no topo da rede. Por padrão, este classificador densamente conectado corresponde às 1.000 classes do ImageNet. Como você pretende usar seu próprio classificador densamente conectado (com apenas duas classes: gato e cachorro), não é necessário incluí-lo.\n- `input_shape` é a forma dos tensores de imagem que você alimentará à rede. Este argumento é totalmente opcional: se você não o passar, a rede poderá processar entradas de qualquer tamanho.\n\nAqui está em detalhes a arquitetura da base convolucional do VGG16. É semelhante às convnets simples com as quais você já está familiarizado:","metadata":{"id":"YJpo2HaVmdxW"}},{"cell_type":"code","source":"print(conv_base.summary())","metadata":{"id":"iE2_uRQYmv16"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"O mapa de características final tem formato `(4, 4, 512)`. Essa é a característica no topo da qual você vai colocar um classificador densamente conectado.\n\nNeste ponto, existem duas maneiras de prosseguir:\n- Executar a base convolucional sobre seu conjunto de dados, gravando sua saída em uma matriz Numpy no disco, e depois usar esses dados como entrada para um classificador densamente conectado independente. Essa solução é rápida e econômica, porque requer apenas a execução da base convolucional uma vez para cada imagem de entrada, e a base convolucional é de longe a parte mais cara do *pipeline*. No entanto, por esse motivo, essa técnica não permitirá o uso de aumento de dados.\n\n- Estender o modelo que você possui (conv_base) adicionando camadas Dense no topo e executando tudo de ponta a ponta nos dados de entrada. Isso permitirá o uso de aumento de dados, porque cada imagem de entrada passa pela base convolucional sempre que é vista pelo modelo. No entanto, por esse motivo, essa técnica é muito mais cara que a primeira.\n\nVamos abordar ambas as técnicas. Vamos passar pelo código necessário para configurar a primeira: gravar a saída de `conv_base` nos seus dados e usar essas saídas como entradas para um novo modelo.","metadata":{"id":"JQSWO5pFmynR"}},{"cell_type":"markdown","source":"**1.1 Rápida extração de características sem aumento de dados**\n\nComeçaremos executando instâncias do `ImageDataGenerator` anteriormente introduzido para extrair imagens como arrays Numpy, assim como seus rótulos. Extrairemos características dessas imagens chamando o método `predict` do modelo `conv_base`.\n\n","metadata":{"id":"1dnedX6WngZZ"}},{"cell_type":"code","source":"### Extracting features using the pretrained convolutional base\n\nimport os\nimport numpy as np\nfrom keras.preprocessing.image import ImageDataGenerator\n\nbase_dir = './cats_and_dogs_small'\ntrain_dir = os.path.join(base_dir, 'train')\nvalidation_dir = os.path.join(base_dir, 'validation')\ntest_dir = os.path.join(base_dir, 'test')\n\ndatagen = ImageDataGenerator(rescale=1./255)\nbatch_size = 20\n\ndef extract_features(directory, sample_count):\n  features = np.zeros(shape=(sample_count, 4, 4, 512))\n  labels = np.zeros(shape=(sample_count))\n  generator = datagen.flow_from_directory(\n    directory,\n    target_size=(150, 150),\n    batch_size=batch_size,\n    class_mode='binary')\n  i = 0\n  for inputs_batch, labels_batch in generator:\n    features_batch = conv_base.predict(inputs_batch)\n    features[i * batch_size : (i + 1) * batch_size] = features_batch\n    labels[i * batch_size : (i + 1) * batch_size] = labels_batch\n    i += 1\n    if i * batch_size >= sample_count:\n      break\n  return features, labels\n\ntrain_features, train_labels = extract_features(train_dir, 2000)\nvalidation_features, validation_labels = extract_features(validation_dir, 1000)\ntest_features, test_labels = extract_features(test_dir, 1000)","metadata":{"id":"A2eaxEtdny6L"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As características extraídas atualmente têm formato `(amostras, 4, 4, 512)`. Você as alimentará a um classificador densamente conectado, então primeiro você precisa achatá-las para `(amostras, 8192)`:","metadata":{"id":"h9bB-jN-oFf6"}},{"cell_type":"code","source":"train_features = np.reshape(train_features, (2000, 4 * 4 * 512))\nvalidation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))\ntest_features = np.reshape(test_features, (1000, 4 * 4 * 512))","metadata":{"id":"hsDcRv1toOlx"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Neste ponto, você pode definir seu classificador densamente conectado (observe o uso de *dropout* para regularização) e treiná-lo nos dados e rótulos que você acabou de gravar.","metadata":{"id":"WbSzs-wtoPrZ"}},{"cell_type":"code","source":"### Defining and training the densely connected classifier\n\nfrom keras import models\nfrom keras import layers\nfrom keras import optimizers\n\nmodel = models.Sequential()\nmodel.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))\nmodel.add(layers.Dropout(0.5))\nmodel.add(layers.Dense(1, activation='sigmoid'))\n\nmodel.compile(optimizer=optimizers.RMSprop(lr=2e-5),\n  loss='binary_crossentropy',\n  metrics=['acc'])\n\nhistory = model.fit(train_features, train_labels,\n  epochs=30,\n  batch_size=20,\n  validation_data=(validation_features, validation_labels))","metadata":{"id":"3ZxtA79goXTo"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Exercício 3**\n\n1. Plote os resultados e compare com os resultados dos modelos anteriores.","metadata":{"id":"vL6ljOLcojz6"}},{"cell_type":"markdown","source":"**1.2 Extração de características com aumento de dados**\n\nAgora, vamos revisar a segunda técnica para fazer extração de características, que é muito mais lenta e cara, mas que permite o uso de aumento de dados durante o treinamento: estender o modelo `conv_base` e executá-lo de ponta a ponta nos inputs.\n\nOBSERVAÇÃO IMPORTANTE: Pode ser que esta técnica não rode no Google Colab, devido às suas limitações. Caso isso ocorra, simplesmente leia a Seção. Normalmente, usaríamos uma GPU de última geração para usar essa técnica.","metadata":{"id":"_PJe32iZorPw"}},{"cell_type":"code","source":"### Adding a densely connected classifier on top of the convolutional base\n\nfrom keras import models\nfrom keras import layers\n\nmodel = models.Sequential()\nmodel.add(conv_base)\nmodel.add(layers.Flatten())\nmodel.add(layers.Dense(256, activation='relu'))\nmodel.add(layers.Dense(1, activation='sigmoid'))\n\nprint(model.summary())","metadata":{"id":"Xlvc9Ha8pFU7"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Como podemos ver, a base convolucional do VGG16 tem 14714688 parâmetros, o que é uma quantidade muito grande. O classificador que estamos adicionando no topo tem 2 milhões de parâmetros.\n\nAntes de compilar e treinar o modelo, é muito importante congelar a base convolucional. **Congelar** uma camada ou conjunto de camadas significa impedir que seus pesos sejam atualizados durante o treinamento. Se não fizermos isso, as representações que foram aprendidas anteriormente pela base convolucional serão modificadas durante o treinamento. Como as camadas `Dense` no topo são inicializadas aleatoriamente, atualizações de peso muito grandes seriam propagadas pela rede, destruindo  as representações aprendidas anteriormente.\n\nNo Keras, você congela uma rede definindo seu atributo `trainable` como `False`:\n\n\n\n","metadata":{"id":"9sQpqTabpXDr"}},{"cell_type":"code","source":"print('This is the number of trainable weights '\n'before freezing the conv base:', len(model.trainable_weights))\nconv_base.trainable = False\nprint('This is the number of trainable weights '\n'after freezing the conv base:', len(model.trainable_weights))\n","metadata":{"id":"Kd49h-lcprv6"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Com essa configuração, apenas os pesos das duas camadas `Dense` que foram adicionadas serão treinados. Isso totaliza quatro tensores de peso: dois por camada (a matriz de pesos principal e o vetor de viés). Observe que, para que essas alterações tenham efeito, deve-se primeiro compilar o modelo. Se  a treinabilidade dos pesos é modificada após a compilação, deve-se recompilar o modelo, ou essas alterações serão ignoradas.\n\nAgora podemos começar a treinar seu modelo, com a mesma configuração de aumento de dados que usamos no exemplo anterior.","metadata":{"id":"_x_AAL2-n1o-"}},{"cell_type":"code","source":"### Listing 5.21 Training the model end to end with a frozen convolutional base\n\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras import optimizers\n\ntrain_datagen = ImageDataGenerator(\n  rescale=1./255,\n  rotation_range=40,\n  width_shift_range=0.2,\n  height_shift_range=0.2,\n  shear_range=0.2,\n  zoom_range=0.2,\n  horizontal_flip=True,\n  fill_mode='nearest')\n\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_generator = train_datagen.flow_from_directory(\n  train_dir,\n  target_size=(150, 150),\n  batch_size=20,\n  class_mode='binary')\n\nvalidation_generator = test_datagen.flow_from_directory(\n  validation_dir,\n  target_size=(150, 150),\n  batch_size=20,\n  class_mode='binary')\n\nmodel.compile(loss='binary_crossentropy',\n  optimizer=optimizers.RMSprop(lr=2e-5),\n  metrics=['acc'])\n\nhistory = model.fit_generator(\n  train_generator,\n  steps_per_epoch=100,\n  epochs=30,\n  validation_data=validation_generator,\n  validation_steps=50)","metadata":{"id":"I7Dwty2JoOc_"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Exercício 4**\n\n1. Plote os resultados e compare com os resultados dos modelos anteriores.","metadata":{"id":"VssA2mZVoglk"}},{"cell_type":"markdown","source":"**2. Fine-tuning**\n\nOutra técnica amplamente utilizada para reutilização de modelos, complementar à extração de características, é o **ajuste fino** (*fine-tuning*). O ajuste fino consiste em descongelar algumas das camadas superiores de uma base de modelo congelada usada para extração de características, e treinar em conjunto tanto a parte recém-adicionada do modelo (neste caso, o classificador totalmente conectado) quanto essas camadas superiores. Isso é chamado de ajuste fino porque ajusta levemente as representações mais abstratas do modelo sendo reutilizado, a fim de torná-las mais relevantes para o problema em questão.\n\nVimos anteriormente que é necessário congelar a base convolucional do VGG16 para poder treinar um classificador inicializado aleatoriamente no topo. Por esse mesmo motivo, só é possível ajustar finamente as camadas superiores da base convolucional depois que o classificador no topo já foi treinado. Se o classificador ainda não estiver treinado, então o sinal de erro propagado pela rede durante o treinamento será muito grande, e as representações anteriormente aprendidas pelas camadas sendo ajustadas finamente serão destruídas. Assim, as etapas para ajuste fino de uma rede são as seguintes:\n\n1. Adicione sua rede personalizada no topo de uma base de rede já treinada.\n2. Congele a base da rede.\n3. Treine a parte que você adicionou.\n4. Descongele algumas camadas na base da rede.\n5. Treine em conjunto tanto essas camadas quanto a parte que você adicionou.\n\nJá completamos as três primeiras etapas ao fazer a extração de características. Vamos prosseguir com a etapa 4: descongelaremos a `conv_base` e, em seguida, congelaremos camadas individuais dentro dela.\nComo lembrete, é assim que é a nossa base convolucional:","metadata":{"id":"hvGV2KWhorD_"}},{"cell_type":"code","source":"print(conv_base.summary())","metadata":{"id":"vJCHr5A1p5Z9"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ajustaremos finamente as últimas três camadas convolucionais, o que significa que todas as camadas até `block4_pool` devem ser congeladas, e as camadas `block5_conv1`, `block5_conv2` e `block5_conv3` devem ser treináveis.\nPor que não ajustar mais camadas? Por que não ajustar toda a base convolucional? Seria possível fazer isso. Mas é preciso considerar o seguinte:\n- Camadas anteriores na base convolucional codificam características mais genéricas e reutilizáveis, enquanto camadas mais altas codificam características mais especializadas. É mais útil ajustar finamente as características mais especializadas, pois são essas que precisam ser reutilizadas no novo problema.\n- Quanto mais parâmetros estivermos treinando, mais teremos risco de *overfitting*. A base convolucional possui 15 milhões de parâmetros, então seria arriscado tentar treiná-la para nosso pequeno conjunto de dados.\n\nAssim, nesta situação, é uma boa estratégia ajustar finamente apenas as duas ou três camadas superiores na base convolucional. Vamos configurar isso, começando de onde paramos no exemplo anterior.","metadata":{"id":"Eg3-lwUPqDTl"}},{"cell_type":"code","source":"### Freezing all layers up to a specific one\n\nconv_base.trainable = True\n\nset_trainable = False\nfor layer in conv_base.layers:\n  if layer.name == 'block5_conv1':\n    set_trainable = True\n  if set_trainable:\n    layer.trainable = True\n  else:\n    layer.trainable = False","metadata":{"id":"LFv3dTc5qa1_"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Agora podemos começar o ajuste fino da rede. Faremos isso com o otimizador RMSProp, usando uma taxa de aprendizado muito baixa. A razão para usar uma baixa taxa de aprendizado é que desejamos limitar a magnitude das modificações que são feitas nas representações das três camadas que estão sendo ajustadas finamente. Atualizações muito grandes podem prejudicar essas representações.","metadata":{"id":"RuBhsST9qzbO"}},{"cell_type":"code","source":"model.compile(loss='binary_crossentropy',\n  optimizer=optimizers.RMSprop(lr=1e-5),\n  metrics=['acc'])\n\nhistory = model.fit_generator(\n  train_generator,\n  steps_per_epoch=100,\n  epochs=100,\n  validation_data=validation_generator,\n  validation_steps=50)","metadata":{"id":"MG3MTs9Iq-2F"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Exercício 5**\n\n1. Plote os resultados e compare com os resultados dos modelos anteriores. Se o gráfico estiver muito \"quebrado\", use a seguinte função para suavizá-lo:\n\n","metadata":{"id":"cV26-nKPrFtb"}},{"cell_type":"code","source":"def smooth_curve(points, factor=0.8):\n  smoothed_points = []\n  for point in points:\n  if smoothed_points:\n    previous = smoothed_points[-1]\n    smoothed_points.append(previous * factor + point * (1 - factor))\n  else:\n    smoothed_points.append(point)\n  return smoothed_points","metadata":{"id":"ElQHqbUPri5O"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"2. Avalie finalmente os modelos nos dados de teste.","metadata":{"id":"iTa-qveUrq1w"}},{"cell_type":"markdown","source":"**Sumário**\n\n- ConvNets são os melhores modelos de aprendizado de máquina para tarefas de visão computacional. É possível treinar um do zero, mesmo em um conjunto de dados muito pequeno, com resultados decentes.\n- Em um conjunto de dados pequeno, o *overfitting* será o principal problema. Aumento de dados é uma maneira poderosa de combater o *overfitting* ao trabalhar com dados de imagem.\n- É fácil reutilizar uma ConvNet existente em um novo conjunto de dados por meio da extração de características. Esta é uma técnica valiosa para lidar com conjuntos de dados pequenos de imagem.\n- Como complemento à extração de características, pode-se usar o ajuste fino, que adapta a um novo problema algumas das representações aprendidas anteriormente por um modelo existente. Isso eleva o desempenho do modelo.\n\nAgora temos um conjunto sólido de ferramentas para lidar com problemas de classificação de imagem, especialmente com conjuntos de dados pequenos.","metadata":{"id":"1YQSXwepsbSO"}},{"cell_type":"markdown","source":"**Visualizando ativações intermediárias**\n\nA visualização de ativações intermediárias consiste em exibir os mapas de características que são gerados por várias camadas de convolução e *pooling* em uma rede, dado uma determinada entrada (a saída de uma camada é frequentemente chamada de **ativação**, que é a saída da função de ativação). Isso proporciona uma visão de como uma entrada é decomposta nos diferentes filtros aprendidos pela rede.\n\nQueremos visualizar mapas de características com três dimensões: largura, altura e profundidade (canais). Cada canal codifica características relativamente independentes, então a maneira adequada de visualizar esses mapas de características é plotar independentemente o conteúdo de cada canal como uma imagem 2D.\n\nVamos começar carregando o modelo salvo anteriormente:","metadata":{"id":"bTe1WW1_uPpj"}},{"cell_type":"code","source":"from keras.models import load_model\nmodel = load_model('cats_and_dogs_small_2.h5')\nprint(model.summary())","metadata":{"id":"Tso5Fp0eu4Gb"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Em seguida, pegaremos uma imagem de entrada - uma foto de um gato, que não faz parte das imagens nas quais a rede foi treinada.","metadata":{"id":"iueC_LqwvV7I"}},{"cell_type":"code","source":"### Preprocessing a single image\n\nimg_path = './cats_and_dogs_small/test/cats/cat.1700.jpg'\n\nfrom keras.preprocessing import image\nimport numpy as np\n\nimg = image.load_img(img_path, target_size=(150, 150))\nimg_tensor = image.img_to_array(img)\nimg_tensor = np.expand_dims(img_tensor, axis=0)\nimg_tensor /= 255.\n\nprint(img_tensor.shape)","metadata":{"id":"AT92LEpivaiD"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Displaying the test picture\n\nimport matplotlib.pyplot as plt\n\nplt.imshow(img_tensor[0])\nplt.show()","metadata":{"id":"Dve-T4vQvsqC"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Para extrair os mapas de características que queremos visualizar, criaremos um modelo Keras que recebe lotes de imagens como entrada e gera as ativações de todas as camadas de convolução e *pooling*. Para fazer isso, usaremos a classe `Model` do Keras. Um modelo é instanciado usando dois argumentos: um tensor de entrada (ou lista de tensores de entrada) e um tensor de saída (ou lista de tensores de saída). A classe resultante é um modelo Keras, assim como os modelos `Sequential` com os quais estamos familiarizado, mapeando as entradas especificadas para as saídas especificadas. O que diferencia a classe `Model` é que ela permite modelos com múltiplas saídas, ao contrário do `Sequential`.","metadata":{"id":"PFY9iMIMv2M7"}},{"cell_type":"code","source":"### Instantiating a model from an input tensor and a list of output tensors\n\nfrom keras import models\n\n#Extracting the outputs of the top eight layers\nlayer_outputs = [layer.output for layer in model.layers[:8]]\n\n#Creating a model that will return these outputs, given the model input\nactivation_model = models.Model(inputs=model.input, outputs=layer_outputs)","metadata":{"id":"dciVvr3IwTsN"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Quando alimentado com uma entrada de imagem, este modelo retorna os valores das ativações das camadas no modelo original. Esta é a primeira vez que encontramos um modelo com várias saídas: até agora, os modelos que vimos tinham exatamente uma entrada e uma saída. No caso geral, um modelo pode ter qualquer número de entradas e saídas. Este tem uma entrada e oito saídas: uma saída por ativação de camada.","metadata":{"id":"oj0rQ2aawvXz"}},{"cell_type":"code","source":"###   Running the model in predict mode\n\n#Returns a list of five Numpy arrays: one array per layer activation\nactivations = activation_model.predict(img_tensor)\n\n#For instance, this is the activation of the first convolution layer for the cat image input\nfirst_layer_activation = activations[0]\nprint(first_layer_activation.shape)","metadata":{"id":"W5_kSPcYw9n7"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### Visualizing the fourth channel\n\nimport matplotlib.pyplot as plt\n\nplt.matshow(first_layer_activation[0, :, :, 4], cmap='viridis')","metadata":{"id":"Zc3FArESxOui"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Esse canal parece codificar um detector de borda diagonal.\n\n**Exercício 6**\n1. Vizualize o sétimo e o décimo canal e tente descobrir que informação o canal parece estar codificando.","metadata":{"id":"2GmKk_n1xZQz"}},{"cell_type":"code","source":"### Visualizing every channel in every intermediate activation\n\n#Names of the layers, so you can have them as part of your plot\nlayer_names = []\nfor layer in model.layers[:8]:\n  layer_names.append(layer.name)\n\nimages_per_row = 16\n\n#Displays the feature maps\nfor layer_name, layer_activation in zip(layer_names, activations):\n  #Number of features in the feature map\n  n_features = layer_activation.shape[-1]\n  #The feature map has shape (1, size, size, n_features).\n  size = layer_activation.shape[1]\n  #Tiles the activation channels in this matrix\n  n_cols = n_features // images_per_row\n  display_grid = np.zeros((size * n_cols, images_per_row * size))\n  #Tiles each filter into a big horizontal grid\n  for col in range(n_cols):\n    for row in range(images_per_row):\n      channel_image = layer_activation[0,:, :, col * images_per_row + row]\n      #Post-processes the feature to make it visually palatable\n      channel_image -= channel_image.mean()\n      channel_image /= channel_image.std()\n      channel_image *= 64\n      channel_image += 128\n      channel_image = np.clip(channel_image, 0, 255).astype('uint8')\n      display_grid[col * size : (col + 1) * size, row * size : (row + 1) * size] = channel_image\n  scale = 1. / size\n  plt.figure(figsize=(scale * display_grid.shape[1], scale * display_grid.shape[0]))\n  plt.title(layer_name)\n  plt.grid(False)\n  plt.imshow(display_grid, aspect='auto', cmap='viridis')","metadata":{"id":"0gqg5Nc2x-Sc"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Há algumas coisas a serem observadas aqui:\n- A primeira camada atua como uma coleção de vários detectores de borda. Nessa fase, as ativações retêm quase todas as informações presentes na imagem inicial.\n- À medida que subimos nas camadas, as ativações se tornam cada vez mais abstratas e menos visualmente interpretáveis. Elas começam a codificar conceitos de nível superior, como \"orelha de gato\" e \"olho de gato\". Apresentações mais altas contêm cada vez menos informações sobre o conteúdo visual da imagem e cada vez mais informações relacionadas à classe da imagem.\n- A dispersão das ativações aumenta com a profundidade da camada: na primeira camada, todos os filtros são ativados pela imagem de entrada; mas nas camadas seguintes, cada vez mais filtros ficam em branco. Isso significa que o padrão codificado pelo filtro não é encontrado na imagem de entrada.\n\nAcabamos de constatar uma característica universal importante das representações aprendidas por redes neurais profundas: as características extraídas por uma camada se tornam cada vez mais abstratas com a profundidade da camada. As ativações de camadas mais altas carregam menos informações sobre a entrada específica sendo vista e mais informações sobre o alvo (neste caso, a classe da imagem: gato ou cachorro). Uma rede neural profunda atua efetivamente como um **pipeline de destilação de informações**, com dados brutos entrando (neste caso, imagens RGB) e sendo transformados repetidamente para que informações irrelevantes sejam filtradas (por exemplo, a aparência visual específica da imagem), e informações úteis sejam ampliadas e refinadas (por exemplo, a classe da imagem).\n\nIsso é análogo à maneira como humanos e animais percebem o mundo: depois de observar uma cena por alguns segundos, um humano pode lembrar quais objetos abstratos estavam presentes (bicicleta, árvore), mas não consegue lembrar da aparência específica desses objetos.","metadata":{"id":"AojNvL7lzEI0"}},{"cell_type":"markdown","source":"**Vizualizando filtros convnet**\n\nOutra maneira fácil de inspecionar os filtros aprendidos por convnets é exibir o padrão visual ao qual cada filtro deve responder. Isso pode ser feito com ascensão de gradiente no espaço de entrada: aplicando *gradient descent* ao valor da imagem de entrada de uma convnet para maximizar a resposta de um filtro específico, começando de uma imagem de entrada em branco. A imagem de entrada resultante será aquela à qual o filtro escolhido responde maximamente.\n\nO processo é simples: construiremos uma função perda que maximize o valor de um determinado filtro em uma camada de convolução específica, e então usaremos o gradiente descendente estocástico para ajustar os valores da imagem de entrada de forma a maximizar esse valor de ativação. Por exemplo, abaixo encontramos uma função de perda para a ativação do filtro 0 na camada `block3_conv1` da rede VGG16, pré-treinada no ImageNet:","metadata":{"id":"J-x6-lpg0ZCz"}},{"cell_type":"code","source":"### Defining the loss tensor for filter visualization\n\nfrom keras.applications import VGG16\nfrom keras import backend as K\n\nmodel = VGG16(weights='imagenet', include_top=False)\nlayer_name = 'block3_conv1'\nfilter_index = 0\n\nlayer_output = model.get_layer(layer_name).output\nloss = K.mean(layer_output[:, :, :, filter_index])","metadata":{"id":"9nti43al1EMK"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Para implementar o *gradient descent*, precisaremos do gradiente dessa perda em relação à entrada do modelo. Para fazer isso, usaremos a função `gradients` contida no módulo `backend` do Keras.","metadata":{"id":"X-8oJ4Z91OQB"}},{"cell_type":"code","source":"### Obtaining the gradient of the loss with regard to the input\n\n#The call to gradients returns a list of tensors (of size 1 in this case). Hence,\n#you keep only the first element— which is a tensor.\ngrads = K.gradients(loss, model.input)[0]","metadata":{"id":"xNTriypQ1av6"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Um truque não óbvio que ajuda no processo de descida do gradiente é normalizar o tensor de gradiente dividindo-o pela sua norma L2 (a raiz quadrada da média dos quadrados dos valores no tensor). Isso garante que a magnitude das atualizações feitas na imagem de entrada esteja sempre dentro da mesma faixa.","metadata":{"id":"NHdV7qkf1hXa"}},{"cell_type":"code","source":"### Gradient-normalization trick\n\n#Add 1e–5 before dividing to avoid accidentally dividing by 0.\ngrads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)","metadata":{"id":"jwrGDcSA1m_L"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Agora precisamos de uma maneira de calcular o valor do tensor de perda e do tensor de gradiente, dado uma imagem de entrada. Podemos definir uma função de backend do Keras para fazer isso: `iterate` é uma função que recebe um tensor Numpy (como uma lista de tensores de tamanho 1) e retorna uma lista de dois tensores Numpy: o valor da perda e o valor do gradiente.","metadata":{"id":"xAemc-uU14ph"}},{"cell_type":"code","source":"### Fetching Numpy output values given Numpy input values\n\niterate = K.function([model.input], [loss, grads])\n\nimport numpy as np\nloss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])","metadata":{"id":"IjlU9sfp2FQB"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Neste ponto, podemos definir um loop em Python para realizar a descida estocástica do gradiente.","metadata":{"id":"saRlF6hQ2MKT"}},{"cell_type":"code","source":"### Loss maximization via stochastic gradient descent\n\n#Starts from a gray image with some noise\ninput_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128.\n\nstep = 1. #Magnitude of each gradient update\n\n#Runs gradient ascent for 40 steps\nfor i in range(40):\n  #Computes the loss value and gradient value\n  loss_value, grads_value = iterate([input_img_data])\n  #Adjusts the input image in the direction that maximizes the loss\n  input_img_data += grads_value * step\n","metadata":{"id":"hM8QXdk22Q7b"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"O tensor de imagem resultante é um tensor de ponto flutuante com formato (1, 150, 150, 3), com valores que podem não ser inteiros dentro do intervalo `[0, 255]`. Portanto, precisamos pós-processar esse tensor para transformá-lo em uma imagem exibível. Isso é feito com a seguinte função simples de utilidade.","metadata":{"id":"uuQPbUOv2s_a"}},{"cell_type":"code","source":"### Utility function to convert a tensor into a valid image\n\ndef deprocess_image(x):\n  #Normalizes the tensor: centers on 0, ensures that std is 0.1\n  x -= x.mean()\n  x /= (x.std() + 1e-5)\n  x *= 0.1\n\n  #Clips to [0, 1]\n  x += 0.5\n  x = np.clip(x, 0, 1)\n\n  #Converts to an RGB array\n  x *= 255\n  x = np.clip(x, 0, 255).astype('uint8')\n  return x","metadata":{"id":"PwEoyMwm24bE"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Agora vamos criar uma função Python que recebe como entrada um nome de camada e um índice de filtro, e retorna um tensor de imagem válido representando o padrão que maximiza a ativação do filtro especificado.","metadata":{"id":"yTyT-Gt0xYn7"}},{"cell_type":"code","source":"### Function to generate filter visualizations\n\ndef generate_pattern(layer_name, filter_index, size=150):\n  #Builds a loss function that maximizes the activation of\n  #the nth filter of the layer under consideration\n  layer_output = model.get_layer(layer_name).output\n  loss = K.mean(layer_output[:, :, :, filter_index])\n\n  #Computes the gradient of the input picture with regard to this loss\n  grads = K.gradients(loss, model.input)[0]\n\n  #Normalization trick: normalizes the gradient\n  grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)\n\n  #Returns the loss and grads given the input picture\n  iterate = K.function([model.input], [loss, grads])\n\n  #Starts from a gray image with some noise\n  input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.\n\n  #Runs gradient ascent for 40 steps\n  step = 1.\n  for i in range(40):\n    loss_value, grads_value = iterate([input_img_data])\n    input_img_data += grads_value * step\n\n  img = input_img_data[0]\n  return deprocess_image(img)","metadata":{"id":"hK-0HACMxekC"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.imshow(generate_pattern('block3_conv1', 0))","metadata":{"id":"dUHjw2Co0hWo"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Parece que o filtro 0 na camada `block3_conv1` responde a um padrão de *polka-dot* (bolinhas).\n\n**Exercício 7**\n1. Plote os padrões dos primeiros 64 filtros da primeira camada de cada bloco de convolução (`block1_conv1`, `block2_conv1`, `block3_conv1`, `block4_conv1`, `block5_conv1`). Organize as saídas em uma grade de 8 × 8 com padrões de filtro de 64 × 64, com algumas margens pretas entre cada padrão de filtro.\n\n2. Discuta as informações codificadas, em geral, pelos padrões de cada camada.","metadata":{"id":"UN2Bu7Kl0yA5"}},{"cell_type":"markdown","source":"**Visualizando mapas de calor de ativação de classe**\n\nVamos ver agora mais uma técnica de visualização, uma que é útil para entender quais partes de uma determinada imagem levaram uma convnet à sua decisão final de classificação. Isso é útil para depurar o processo de decisão de uma convnet, especialmente no caso de um erro de classificação. Também permite localizar objetos específicos em uma imagem.\n\nEssa categoria geral de técnicas é chamada de **visualização de mapas de ativação de classe** (CAM - *class activation map*), e consiste na produção de mapas de calor de ativação de classe sobre imagens de entrada. Um mapa de calor de ativação de classe é uma grade 2D de pontuações associadas a uma classe específica de saída, calculada para cada localização em qualquer imagem de entrada, indicando quão importante é cada localização com respeito à classe em consideração. Por exemplo, ao fornecer uma imagem a uma convnet de gatos e cachorros, a visualização CAM permite gerar um mapa de calor para a classe \"gato\", indicando o quão parecidas diferentes partes da imagem são com gatos, e também um mapa de calor para a classe \"cachorro\", indicando o quão parecidas são as partes da imagem com cachorros.\n\nA implementação específica vamos utilizar é a descrita em \"Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization\" (Ramprasaath R. Selvaraju et al., arXiv (2017), https://arxiv.org/abs/ 1610.02391). É muito simples: consiste em pegar o mapa de características de saída de uma camada de convolução, dado uma imagem de entrada, e ponderar cada canal nesse mapa de características pelo gradiente da classe em relação ao canal. De maneira intuitiva, uma forma de entender esse truque é que você está ponderando um mapa espacial de \"quão intensamente a imagem de entrada ativa diferentes canais\" pelo \"quão importante cada canal é em relação à classe\", resultando em um mapa espacial de \"quão intensamente a imagem de entrada ativa a classe\".\n\nVamos demonstrar essa técnica usando novamente a rede pré-treinada VGG16.\n\n\n","metadata":{"id":"u5cQOBlj3AXX"}},{"cell_type":"code","source":"### Loading the VGG16 network with pretrained weights\n\nfrom keras.applications.vgg16 import VGG16\n\n#Note that we will  include the densely connected classifier\n#on top; in all previous cases, we discarded it.\nmodel = VGG16(weights='imagenet')","metadata":{"id":"cx0yoF6i4_Nv"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Considere a imagem de dois elefantes africanos mostrada na figura `creative_commons_elephant.jpg`, possivelmente uma mãe e seu filhote, passeando na savana. Vamos converter essa imagem em algo que o modelo VGG16 pode ler: o modelo foi treinado em imagens de tamanho 224 × 224, pré-processadas de acordo com algumas regras que estão embutidas na função de utilidade `keras.applications.vgg16.preprocess_input`. Portanto, precisaremos carregar a imagem, redimensioná-la para 224 × 224, convertê-la em um tensor Numpy de ponto flutuante 32 e aplicar essas regras de pré-processamento.\n\n","metadata":{"id":"K837Dd5P6AnR"}},{"cell_type":"code","source":"### Preprocessing an input image for VGG16\n\nfrom keras.preprocessing import image\nfrom keras.applications.vgg16 import preprocess_input, decode_predictions\nimport numpy as np\n\nimg_path = './creative_commons_elephant.jpg'\n\n#Python Imaging Library (PIL) image of size 224 × 224\nimg = image.load_img(img_path, target_size=(224, 224))\n\nx = image.img_to_array(img)     #float32 Numpy array of shape (224, 224, 3)\nx = np.expand_dims(x, axis=0)   #Adds a dimension to transform the array into a batch of size (1, 224, 224, 3)\nx = preprocess_input(x)         #Preprocesses the batch (this does channel-wise color normalization)\n\n#We can now run the pretrained network on the image and decode its prediction\n#vector back to a human-readable format\npreds = model.predict(x)\nprint('Predicted:', decode_predictions(preds, top=3)[0])","metadata":{"id":"eoLzbZhj6WJv"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As três principais classes previstas para esta imagem são as seguintes:\n\n- Elefante africano (com 92.5% de probabilidade)\n- Tusker (com 7% de probabilidade)\n- Elefante indiano (com 0.4% de probabilidade)\n\nA rede reconheceu a imagem como contendo uma quantidade indeterminada de elefantes africanos. A entrada no vetor de previsão que foi maximamente ativada é aquela correspondente à classe \"Elefante africano\", no índice 386:","metadata":{"id":"XByNpdp77X7D"}},{"cell_type":"code","source":"print(np.argmax(preds[0]))","metadata":{"id":"4At3O88H7jAW"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Para visualizar quais partes da imagem são mais semelhantes a um elefante africano, vamos configurar o processo Grad-CAM.","metadata":{"id":"w4E2Ehn87mD_"}},{"cell_type":"code","source":"### Setting up the Grad-CAM algorithm\n\n#“African elephant” entry in the prediction vector\nafrican_e66lephant_output = model.output[:, 386]\n\n#Output feature map of the block5_conv3 layer, the last convolutional layer in VGG16\nlast_conv_layer = model.get_layer('block5_conv3')\n\n#Gradient of the “African elephant” class with regard to the output feature map of block5_conv3\ngrads = K.gradients(african_elephant_output, last_conv_layer.output)[0]\n\n#Vector of shape (512,), where each entry is the mean intensity of the gradient\n# over a specific feature-map channel\npooled_grads = K.mean(grads, axis=(0, 1, 2))\n\n#Lets you access the values of the quantities you just defined: pooled_grads and the\n# output feature map of block5_conv3, given a sample image\niterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])\n\n#Values of these two quantities, as Numpy arrays, given the sample image of two elephants\npooled_grads_value, conv_layer_output_value = iterate([x])\n\n#Multiplies each channel in the feature-map array by “how important this channel\n#is” with regard to the “elephant” class\nfor i in range(512):\n  conv_layer_output_value[:, :, i] *= pooled_grads_value[i]\n\n#The channel-wise mean of the resulting feature map is the heatmap of the class activation\nheatmap = np.mean(conv_layer_output_value, axis=-1)\n\n#For visualization purposes, you’ll also normalize the heatmap between 0 and 1\nheatmap = np.maximum(heatmap, 0)\nheatmap /= np.max(heatmap)\nplt.matshow(heatmap)","metadata":{"id":"K6tPrCfs7xIH"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Finalmente, usaremos o OpenCV para gerar uma imagem que sobreponha a imagem original ao mapa de calor que acabamos de obter.","metadata":{"id":"fSMNgMXi9ao3"}},{"cell_type":"code","source":"### Superimposing the heatmap with the original picture\n\nimport cv2\n\n#Uses cv2 to load the original image\nimg = cv2.imread(img_path)\n\n#Resizes the heatmap to be the same size as the original image\nheatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))\n\n#Converts the heatmap to RGB\nheatmap = np.uint8(255 * heatmap)\n\n#Applies the heatmap to the original image\nheatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)\n\nsuperimposed_img = heatmap * 0.4 + img #0.4 here is a heatmap intensity factor.\n\n#Saves the image to disk\ncv2.imwrite('./elephant_cam.jpg', superimposed_img)\n","metadata":{"id":"BuLX2uhG9gXX"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Essa técnica de visualização responde a duas perguntas importantes:\n\n- Por que a rede neural achou que esta imagem continha um elefante africano?\n\n- Onde está localizado o elefante africano na imagem?\n\nEm particular, é interessante observar que as orelhas do filhote de elefante estão fortemente ativadas: provavelmente é assim que a rede consegue diferenciar elefantes africanos de indianos.\n\n**Exercício 8**\n1. Selecione uma imagem da Internet que contenha animais e repita o processo acima.\n2. Discuta os resultados.","metadata":{"id":"qnXeljmF-uhP"}}]}