{"cells":[{"metadata":{"_uuid":"dcad846945041e55b1b0ae40929b7a298be1895f"},"cell_type":"markdown","source":"## Classificação de imagens com redes neurais convolucionais"},{"metadata":{"_uuid":"570401c356078ebe4d4cc0cc9c8aab20b57c24f1"},"cell_type":"markdown","source":"Bem-vindos à oficina de Deep Learning - Visão Computacional.     \nVamos usar redes neurais por convolução (CNNs) para ensinar o computador reconhecer imagens.   \nAlgo possível graças ao aprendizado profundo(deep learning)."},{"metadata":{"_uuid":"44649695636d36b09e3b3ce9b8a18be3d89f9dde"},"cell_type":"markdown","source":"## Introdução ao Deep Learning: 'Cães e Gatos'"},{"metadata":{"_uuid":"6404c541278b1867ceb0ce81bd9558f7cacdc850"},"cell_type":"markdown","source":"Vamos criar um modelo para entrar na competição Dogs vs Cats no Kaggle.   \nTemos 25.000 fotos de cães e gatos rotuladas disponíveis para treinamento e   \n12.500 no conjunto de testes que devemos tentar rotular para esta competição.   \nDe acordo com o site da Kaggle, quando esta competição foi lançada (final de 2013): \"Estado da arte: A literatura atual sugere que classificadores de máquinas podem pontuar acima de 80% de precisão nesta tarefa\".  \n  \nEntão, se conseguirmos bater 80%, estaremos na vanguarda a partir de 2013!\n"},{"metadata":{"trusted":true,"_uuid":"237c8bf5f9d7f82b96da5c53bf352cedc373ab22"},"cell_type":"code","source":"# Put these at the top of every notebook, to get automatic reloading and inline plotting\n%reload_ext autoreload\n%autoreload 2\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"67337821a2458ecbea55c4d74694c5e07155eb08"},"cell_type":"markdown","source":"Vamos importar as bibliotecas, com código aberto, que vamos precisar."},{"metadata":{"trusted":true,"_uuid":"403959d86b35ae3c4141e809e45377bbbf98c7c9"},"cell_type":"code","source":"# This file contains all the main external libs we'll use\nfrom fastai.imports import *","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5a51537064ba2ba39da134d97d35564975d21808"},"cell_type":"code","source":"from fastai.transforms import *\nfrom fastai.conv_learner import *\nfrom fastai.model import *\nfrom fastai.dataset import *\nfrom fastai.sgdr import *\nfrom fastai.plots import *","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"874aa5dbddfadf6684ceaf87c7fc9e506dbb0448"},"cell_type":"markdown","source":"`PATH` é o caminho para seus dados - se você usar as abordagens de configuração recomendadas da lição, não precisará alterar isso. `sz` é o tamanho que as imagens serão redimensionadas para garantir que o treinamento seja executado rapidamente. Nós estaremos falando muito sobre este parâmetro durante o curso. Deixe-o em `224` por enquanto.\n"},{"metadata":{"trusted":true,"_uuid":"c3dca1b86dbe1581fc4019f6df0ac3eb8c29399b"},"cell_type":"code","source":"PATH = \"../input/\"\nTMP_PATH = \"/tmp/tmp\"\nMODEL_PATH = \"/tmp/model/\"\nsz=224","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"274a1b14fbf1427f5da5f1258159e397a61b23a6"},"cell_type":"markdown","source":"É importante que você tenha uma GPU NVidia em funcionamento. A estrutura de programação usada nos bastidores para trabalhar com GPUs NVidia é chamada de CUDA. Portanto, você precisa garantir que a linha a seguir retorne `True` antes de prosseguir. Se você tiver problemas com isso, verifique o FAQ e peça ajuda nos [fóruns] (http://forums.fast.ai).\n"},{"metadata":{"trusted":true,"_uuid":"b35cbafa1ed1fd5d8a1fcec18d565adacb2dc047"},"cell_type":"code","source":"torch.cuda.is_available()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a3ca1889aacd7f6b13cd005770aa4f7259cf819f"},"cell_type":"markdown","source":"Além disso, a NVidia oferece funções  especialmente aceleradas para aprendizado profundo em um pacote chamado CuDNN. Embora não seja estritamente necessário, ele melhorará significativamente o desempenho do treinamento e será incluído por padrão em todas as configurações fastai suportadas. Portanto, se o seguinte não retornar True, você pode querer investigar o motivo.\n"},{"metadata":{"trusted":true,"_uuid":"bb51efef2921acf61bad1e38fbc7e79971ad0b80"},"cell_type":"code","source":"torch.backends.cudnn.enabled","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8ce095fef9473825bb45b1c7f0d2dfbdba31e534"},"cell_type":"markdown","source":"## Vamos conhecer as fotos dos gatos"},{"metadata":{"_uuid":"f6c1093aaa4aaea950bfde0862eaf5e4ff5d54c5"},"cell_type":"markdown","source":"A biblioteca Fastai assumirá que você tem diretórios de treino e teste. Ele também assume que cada diretório terá subdiretórios para cada classe que você deseja reconhecer (neste caso, 'cats' e 'dogs').\n"},{"metadata":{"trusted":true,"_uuid":"cc4542d5f415de1e00e893ddc4857f650efc437c"},"cell_type":"code","source":"PATH","execution_count":null,"outputs":[]},{"metadata":{"scrolled":true,"trusted":true,"_uuid":"7ffb64a6b2b376337f85a2a9026c0d1016c0aad6"},"cell_type":"code","source":"os.listdir(PATH)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7fd4abbe1eb6eaf950f3b83ff47d9db8825e3b9e"},"cell_type":"code","source":"fnames = np.array([f'train/{f}' for f in sorted(os.listdir(f'{PATH}train'))])\nlabels = np.array([(0 if 'cat' in fname else 1) for fname in fnames])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e266f065b394f3a55ab2612a8fbdf5f8c03fb5e3"},"cell_type":"code","source":"len(os.listdir(f'{PATH}train'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3c9de1045c85ddbc238ea6d7897c6f30a5c49e3c"},"cell_type":"code","source":"len(os.listdir(f'{PATH}test'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"70799e9b2840e418d52f657bf917361860f62917"},"cell_type":"code","source":"os.listdir(f'{PATH}test')[:5]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2ccd51173e718dd55b105ab844fe58985faa8368"},"cell_type":"code","source":"img = plt.imread(f'{PATH}{fnames[1]}')\nplt.imshow(img);\n#files = os.listdir(f'{PATH}test/cats')[:5]\n#files","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"af25f953788900e72a9c6a41e7d846b5a67502ed"},"cell_type":"code","source":"# img = plt.imread(f'{PATH}valid/cats/{files[1]}')\n# plt.imshow(img);","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3858e404134830c39bf01462953ca7bb5eefd727"},"cell_type":"markdown","source":"Here is how the raw data looks like"},{"metadata":{"trusted":true,"_uuid":"936bca5a41484927ac2d7d9bdb2668571e6b61f8"},"cell_type":"code","source":"img.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3ddf83238d18e2d57216b444d01a51569e980125"},"cell_type":"code","source":"img[:4,:4]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"77017b74e934dfe801b327d14e7c013edb83ae3d"},"cell_type":"markdown","source":"## Nosso primeiro modelo - início rápido."},{"metadata":{"_uuid":"0c3a079efc98ee27edbbe82306953e831976fac6"},"cell_type":"markdown","source":"\nVamos usar um modelo pré-treinado, ou seja, um modelo criado por alguém para resolver um problema diferente. Em vez de construir um modelo a partir do zero para resolver um problema semelhante, usaremos um modelo treinado no ImageNet (1,2 milhões de imagens e 1000 classes) como ponto de partida. O modelo é uma rede neural por convolução (CNN), um tipo de rede neural que constrói modelos de última geração para visão computacional.   \n\nVamos usar o modelo resnet34. O resnet34 é uma versão do modelo que ganhou a competição 2015 ImageNet.  \n\nVeja como treinar e avaliar um modelo de cães vs gatos em 3 linhas de código e em menos de 2 minutos (no laptop):"},{"metadata":{"trusted":true,"_uuid":"031200aa93e2a5c8e795168adc0d0e35e8b8a4a2"},"cell_type":"code","source":"# Uncomment the below if you need to reset your precomputed activations\n# shutil.rmtree(f'{PATH}tmp', ignore_errors=True)","execution_count":null,"outputs":[]},{"metadata":{"scrolled":false,"trusted":true,"_uuid":"7d04e4b10974c206146193c904490ab121361807"},"cell_type":"code","source":"arch=resnet34\n# data = ImageClassifierData.from_paths(PATH, tfms=tfms_from_model(arch, sz))\n# learn = ConvLearner.pretrained(arch, data, precompute=True)\ndata = ImageClassifierData.from_names_and_array(\n    path=PATH, \n    fnames=fnames, \n    y=labels, \n    classes=['dogs', 'cats'], \n    test_name='test', \n    tfms=tfms_from_model(arch, sz)\n)\nlearn = ConvLearner.pretrained(arch, data, precompute=True, tmp_name=TMP_PATH, models_name=MODEL_PATH)\nlearn.fit(0.01, 2)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f2ea37f11585735489b26674960e012233d03272"},"cell_type":"markdown","source":"Será que nosso modelo é bom ?  Bem, como mencionamos, antes desta competição, o estado da arte tinha 80% de precisão. Mas a competição resultou em um enorme salto para 99% de precisão, com o autor de uma popular biblioteca de aprendizagem profunda vencendo a competição. Extraordinariamente, menos de 4 anos depois, podemos agora bater esse resultado em segundos! \n"},{"metadata":{"heading_collapsed":true,"_uuid":"ae40fc4a188dddc5d5989a922d7df1d02c0ce4e3"},"cell_type":"markdown","source":"## Entendendo o código do nosso primeiro modelo"},{"metadata":{"hidden":true,"_uuid":"e148a67d364aa9c57d7b547c49c95228eba5978c"},"cell_type":"markdown","source":"Vamos ver o código Dogs v Cats linha por linha.\n\n**tfms** significa *transformations*. `tfms_from_model` cuida do redimensionamento, recorte de imagem, normalização inicial (criação de dados com (mean,stdev) of (0,1)), e outras coisas mais.\n"},{"metadata":{"collapsed":true,"hidden":true,"scrolled":true,"trusted":false,"_uuid":"3527f698b17e9f73cc07ae4cee119bd451288a3c"},"cell_type":"code","source":"# tfms = tfms_from_model(resnet34, sz)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"1f1978f2ed640b290cf1ec2785444f2f7a973f25"},"cell_type":"markdown","source":"Precisamos de um <b>path</b> que aponte para o conjunto de dados. Nesse caminho, também armazenaremos dados temporários e resultados finais. `ImageClassifierData.from_paths` lê dados de um caminho fornecido e cria um conjunto de dados pronto para treinamento.\n"},{"metadata":{"collapsed":true,"hidden":true,"trusted":false,"_uuid":"d108d2f575e72550b67c6974021853cd752d9d6e"},"cell_type":"code","source":"# data = ImageClassifierData.from_names_and_array(\n#     path=PATH, \n#     fnames=fnames, \n#     y=labels, \n#     classes=['dogs', 'cats'], \n#     test_name='test', \n#     tfms=tfms_from_model(arch, sz)\n# )","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"905cf0a45659f0df9ab4e0e28cb17abf4c054ab5"},"cell_type":"markdown","source":"`ConvLearner.pretrained` constrói um *learner* que contém um modelo pré-treinado. A última camada do modelo precisa ser substituída com a camada de dimensões corretas. O modelo pre-treinado foi treinado para 1000 classes, portanto, a camada final prevê um vetor de 1000 probabilidades. O modelo para gatos e cães precisa produzir um vetor bidimensional. O diagrama abaixo mostra em um exemplo como isso foi feito em uma das primeiras CNNs bem-sucedidas. A camada \"FC8\" aqui seria substituída por uma nova camada com 2 saídas.\n\n<img src=\"images/pretrained.png\" width=\"500\">\n[original image](https://image.slidesharecdn.com/practicaldeeplearning-160329181459/95/practical-deep-learning-16-638.jpg)"},{"metadata":{"collapsed":true,"hidden":true,"trusted":false,"_uuid":"a11616f7a1c5fcde622fbef45846b09259cd90ea"},"cell_type":"code","source":"# learn = ConvLearner.pretrained(resnet34, data, precompute=True)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"40e1a771281655023b898c3843e287e731d80b8f"},"cell_type":"markdown","source":"Os parâmetros são aprendidos ajustando um modelo aos dados. Os hiperparâmetros são outro tipo de parâmetro, que não podem ser aprendidos diretamente do processo de treinamento regular. Esses parâmetros expressam propriedades de “alto nível” do modelo, como sua complexidade ou quão rápido ele deve aprender. Dois exemplos de hiperparâmetros são a taxa de aprendizado(*learning rate*) e o número de épocas(*number of epochs*).\n\nDurante o treinamento iterativo de uma rede neural, um lote(*batch*) ou mini-lote(*mini-batch*) é um subconjunto de amostras de treinamento usado em uma iteração do Stochastic Gradient Descent (SGD). Uma época é uma passagem única por todo o conjunto de treinamento, que consiste em várias iterações de SGD.\n\nAgora podemos treinar(*fit*) o modelo; isto é, use a descida de gradiente(*gradient descent*) para encontrar os melhores parâmetros para a camada totalmente conectada(fully connected layer) que adicionamos, que pode separar as imagens de gatos das fotos de cães. Precisamos passar dois hyperâmetros: a taxa de aprendizado - *learning rate* (geralmente 1e-2 ou 1e-3 é um bom ponto de partida, veremos mais a seguir) e o número de épocas - *number of epochs* (você pode passar em um número maior e simplesmente parar de treinar quando você vê que não está mais melhorando, então execute-o novamente com o número de épocas que você achou que funciona bem.)\n\n"},{"metadata":{"hidden":true,"scrolled":true,"trusted":false,"_uuid":"a5511184082e80cfa2444e32ee1183c78495cb75"},"cell_type":"code","source":"# learn.fit(1e-2, 1)","execution_count":null,"outputs":[]},{"metadata":{"heading_collapsed":true,"_uuid":"f1a6bf7c65910756bf7610d30e1a354655e3f20d"},"cell_type":"markdown","source":"## Analisando resultados: olhando as fotos"},{"metadata":{"hidden":true,"_uuid":"d2688292b587d79e96511d893299b1798510dfae"},"cell_type":"markdown","source":"Além de observar as métricas em geral, também é uma boa ideia ver exemplos de cada uma delas:\n\n1. Algumas classificações corretas aleatoriamente\n2. Algumas classificações incorretas aleatoriamente\n3. Os rótulos mais corretos de cada classe (ou seja, aqueles com maior probabilidade de estarem corretos)\n4. Os rótulos mais incorretos de cada classe (ou seja, aqueles com maior probabilidade de estarem incorretos)\n5. Os rótulos mais incertos (isto é, aqueles com probabilidade mais próxima de 0,5).\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"36a30372fed63b99196089ff471b489f248f3fcc"},"cell_type":"code","source":"# This is the label for a val data\ndata.val_y","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"8dc4c8397facad8f7b7450a73455d9f760941271"},"cell_type":"code","source":"# from here we know that 'cats' is label 0 and 'dogs' is label 1.\ndata.classes","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"6c2b7e088651b8362f489971aeefc117924416e3"},"cell_type":"code","source":"# this gives prediction for validation set. Predictions are in log scale\nlog_preds = learn.predict()\nlog_preds.shape","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"07ae31adcab2b5d1b3acfaf0df23106ff6fbea95"},"cell_type":"code","source":"log_preds[:10]","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"76a3b5943c03c2c12f159ba902373c6ca5de4ef7"},"cell_type":"code","source":"preds = np.argmax(log_preds, axis=1)  # from log probabilities to 0 or 1\nprobs = np.exp(log_preds[:,1])        # pr(dog)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"c09b9b5921557807ffc09ff6d44f30a29715d67e"},"cell_type":"code","source":"def rand_by_mask(mask): return np.random.choice(np.where(mask)[0], 4, replace=False)\ndef rand_by_correct(is_correct): return rand_by_mask((preds == data.val_y)==is_correct)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"a898efbd5ddf61d9a5b697f138350446a1beae48"},"cell_type":"code","source":"def plots(ims, figsize=(12,6), rows=1, titles=None):\n    f = plt.figure(figsize=figsize)\n    for i in range(len(ims)):\n        sp = f.add_subplot(rows, len(ims)//rows, i+1)\n        sp.axis('Off')\n        if titles is not None: sp.set_title(titles[i], fontsize=16)\n        plt.imshow(ims[i])","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"5c09aaf5c2ca718333a838337e95bbc8832f18e9"},"cell_type":"code","source":"def load_img_id(ds, idx): return np.array(PIL.Image.open(PATH+ds.fnames[idx]))\n\ndef plot_val_with_title(idxs, title):\n    imgs = [load_img_id(data.val_ds,x) for x in idxs]\n    title_probs = [probs[x] for x in idxs]\n    print(title)\n    return plots(imgs, rows=1, titles=title_probs, figsize=(16,8))","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"5a5fb62e0529b66b0308cfd6a608c12d79fe6c46"},"cell_type":"code","source":"# 1. A few correct labels at random\nplot_val_with_title(rand_by_correct(True), \"Classificados corretamente\")","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"ef720258a896ed93ebf59cc942eb06371cde28b0"},"cell_type":"code","source":"# 2. A few incorrect labels at random\nplot_val_with_title(rand_by_correct(False), \"Classificados incorretamente\")","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"d5d2274c861a02f325991ca7bb0e0f982c019e2d"},"cell_type":"code","source":"def most_by_mask(mask, mult):\n    idxs = np.where(mask)[0]\n    return idxs[np.argsort(mult * probs[idxs])[:4]]\n\ndef most_by_correct(y, is_correct): \n    mult = -1 if (y==1)==is_correct else 1\n    return most_by_mask(((preds == data.val_y)==is_correct) & (data.val_y == y), mult)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"94cd187d5472c555ed1f2808f75c3fe1a8fe14f1"},"cell_type":"code","source":"plot_val_with_title(most_by_correct(0, True), \"Gatos melhor classificados\")","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"0b7debf3ab0d1bede1d60af2c5e20c10a0db9e2c"},"cell_type":"code","source":"plot_val_with_title(most_by_correct(1, True), \"Cães melhor classificados\")","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"7be08f817a53a413b261eff4ac4aa409af0fd2a8"},"cell_type":"code","source":"plot_val_with_title(most_by_correct(0, False), \"Gatos pior classificados\")","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"c282ffb14036268f78ae23c8a829e813eab9e550"},"cell_type":"code","source":"plot_val_with_title(most_by_correct(1, False), \"Cães pior classificados\")","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"7740cd53c255895bb153707a44f2fc39d1032009"},"cell_type":"code","source":"most_uncertain = np.argsort(np.abs(probs -0.5))[:4]\nplot_val_with_title(most_uncertain, \"Predições mais incertas\")","execution_count":null,"outputs":[]},{"metadata":{"heading_collapsed":true,"_uuid":"1e2daec16c0975beab0cbea5e2139e2bc908a970"},"cell_type":"markdown","source":"## Escolhendo a taxa de aprendizagem"},{"metadata":{"hidden":true,"_uuid":"3bf134c924a5d39844dd05008c8db4877c91c2a0"},"cell_type":"markdown","source":"A taxa de aprendizado determina quão rápido ou quão lento você deseja atualizar os pesos (ou parâmetros). A taxa de aprendizado é um dos parâmetros mais difíceis de definir, porque afeta significativamente o desempenho do modelo.\n\nO método learn.lr_find () ajuda você a encontrar uma taxa de aprendizado ideal. Ele usa a técnica desenvolvida no paper 2015 Cyclical Learning Rates for Training de Redes Neurais, onde nós simplesmente continuamos aumentando a taxa de aprendizado de um valor muito pequeno, até que a perda pare de diminuir. Podemos traçar a taxa de aprendizado entre os lotes(batches) para ver como isso se parece.\n\nPrimeiro criamos um novo learner, pois queremos saber como definir a taxa de aprendizado para um novo modelo (não treinado).\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"a0fbf3deffeffe2ceb7db95dff37edc0b840d50c"},"cell_type":"code","source":"learn = ConvLearner.pretrained(arch, data, precompute=True, tmp_name=TMP_PATH, models_name=MODEL_PATH)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"scrolled":true,"trusted":true,"_uuid":"26b9a209084aa2d8d547ac6d7c364e6710323e89"},"cell_type":"code","source":"lrf=learn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"1c3800655ef4919d167d0b76a70a024ef9b58c7f"},"cell_type":"markdown","source":"Nosso objeto `learn` contém um atributo `sched` que contém nosso scheduler de taxa de aprendizado e possui alguma funcionalidade gráfica interessante, incluindo esta:\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"7f97b41bbb250d7fe329e49cd7071aa110fbe796"},"cell_type":"code","source":"learn.sched.plot_lr()","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"605d48d9d81e22a7ab0ea9cc7b818dc3d611b492"},"cell_type":"markdown","source":"Observe que na iteração de plotagem anterior há uma iteração (ou minibatch) de SGD(Stochastic gradient descent). Em uma época existem (num_train_samples / num_iterations) de SGD.\n\nPodemos ver o gráfico da perda versus taxa de aprendizado para ver onde nossa função de perda para de diminuir:\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"e2017cf0100f1c6a3926e39a0fb87f7f952f8b41"},"cell_type":"code","source":"learn.sched.plot()","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"7081048a055d746cf527620c4e220b70f3918e0f"},"cell_type":"markdown","source":"A perda ainda está claramente melhorando em lr = 1e-2 (0,01), e é isso que usamos. Observe que a taxa de aprendizado ideal pode mudar conforme treinamos o modelo, portanto, você pode querer executar novamente essa função de tempos em tempos.\n"},{"metadata":{"heading_collapsed":true,"_uuid":"8aeea861aaf29277856c48183e5f3ba099f7b320"},"cell_type":"markdown","source":"## Melhorando o modelo"},{"metadata":{"hidden":true,"_uuid":"6d345e26516d5f112fcb17fdb73d2486908f9a7e"},"cell_type":"markdown","source":"### Dados aumentados (data augmentation)"},{"metadata":{"hidden":true,"_uuid":"8a068f651e7479181e142bc6ad6c2876d12bbf44"},"cell_type":"markdown","source":"Se você tentar treinar por mais épocas, perceberá que começamos a ter *overfit*, o que significa que nosso modelo está aprendendo a reconhecer as imagens específicas no conjunto de treinamento, em vez de generalizar, de tal forma que possamos obter bons resultados no conjunto de validação. Uma maneira de corrigir isso é criar, literalmente, mais dados, por meio do *data augmentation*. Isso significa alterar aleatoriamente as imagens de maneira que não venha a afetar sua interpretação, tal como inverter horizontalmente, aplicar zoom e girar.\n\nPodemos fazer isso, passando `aug_tfms` (transformações de aumento-*augmentation transforms*) para `tfms_from_model`, com uma lista de funções a serem aplicadas que alteram aleatoriamente a imagem como desejarmos. Para fotos tiradas em grande parte do lado (por exemplo, a maioria das fotos de cães e gatos, em oposição a fotos tiradas de cima para baixo, como imagens de satélite), podemos usar a lista predefinida de funções `transforms_side_on`. Também podemos especificar o zoom aleatório de imagens até a escala especificada, adicionando o parâmetro `max_zoom`.\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"0cd5055310de9b632f1039ac5a4fede4e84dd12b"},"cell_type":"code","source":"tfms = tfms_from_model(resnet34, sz, aug_tfms=transforms_side_on, max_zoom=1.1)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"6d3585ab782d1acea7ad81b243b449146f68696e"},"cell_type":"code","source":"def get_augs():\n    data = ImageClassifierData.from_names_and_array(\n        path=PATH, \n        fnames=fnames, \n        y=labels, \n        classes=['dogs', 'cats'], \n        test_name='test', \n        tfms=tfms,\n        num_workers=1,\n        bs=2\n    )\n    x,_ = next(iter(data.aug_dl))\n    return data.trn_ds.denorm(x)[1]","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"6e0db078a917c760d1c26bd05d45cbdb751c4bba"},"cell_type":"code","source":"ims = np.stack([get_augs() for i in range(6)])","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"7bb4c725df7337517ab2e7ea2014160f08d579fc"},"cell_type":"code","source":"plots(ims, rows=2)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"301d09f41c93a64dfe323549eedc597ed092d5fd"},"cell_type":"markdown","source":"Vamos criar novos objetos de dados que incluam esta augmentation dentro do transforms.\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"4104d3b29e8eb940747c7304d7df2e6796fb0b6d"},"cell_type":"code","source":"data = ImageClassifierData.from_names_and_array(\n    path=PATH, \n    fnames=fnames, \n    y=labels, \n    classes=['dogs', 'cats'], \n    test_name='test', \n    tfms=tfms\n)\nlearn = ConvLearner.pretrained(arch, data, precompute=True, tmp_name=TMP_PATH, models_name=MODEL_PATH)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"scrolled":false,"trusted":true,"_uuid":"2e79932bcfb051cb1a76e99a5c75b1f423c96822"},"cell_type":"code","source":"learn.fit(1e-2, 1)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"fdb10448e40f8f3648aba21bda8a4537be381eff"},"cell_type":"code","source":"learn.precompute=False","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"19968442c476825eea0e367bdf4cf90dc656ef29"},"cell_type":"markdown","source":"Por padrão, quando criamos um `learner`, ele define todos, exceto a última camada, para *frozen*. Isso significa que está apenas atualizando os pesos na última camada, quando chamamos o `fit`.\n"},{"metadata":{"hidden":true,"scrolled":false,"trusted":true,"_uuid":"48175db5757658bc3e22c4709eac15fe00bf2681"},"cell_type":"code","source":"learn.fit(1e-2, 3, cycle_len=1)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"03b8de608a5d0413b53fbb4677061b32ba5ac86f"},"cell_type":"markdown","source":"O que é o parâmetro cycle_len? O que fizemos aqui foi usar uma técnica chamada descida de gradiente estocástica com reinicializações - *stochastic gradient descent with restarts (SGDR)*, uma variante de *learning rate annealing*, que diminui gradualmente a taxa de aprendizado à medida que o treinamento avança. Isso é útil porque, à medida que nos aproximamos dos pesos ideais, queremos dar passos menores.\n\nNo entanto, podemos nos encontrar em uma parte do espaço de peso que não é muito resiliente - isto é, pequenas mudanças nos pesos podem resultar em grandes mudanças na função de perda. Queremos incentivar nosso modelo a encontrar partes do espaço de peso que sejam precisas e estáveis. Portanto, de tempos em tempos, aumentamos a taxa de aprendizado (isso é o 'restarts' em 'SGDR'), o que forçará o modelo a saltar para uma parte diferente do espaço de peso se a área atual for \"spikey\". Aqui está uma imagem de como isso pode parecer se redefinirmos as taxas de aprendizado 3 vezes (neste documento eles chamam de \"cyclic LR schedule\"):  \n<img src=\"images/sgdr.png\" width=\"80%\">\n(From the paper [Snapshot Ensembles](https://arxiv.org/abs/1704.00109)).\n\nO número de épocas entre a redefinição da taxa de aprendizado é definido por `cycle_len`, e o número de vezes que isso acontece é chamado de *number of cycles*, e é o que estamos passando como o segundo parâmetro para` fit( )`. Então, eis como eram, realmente, as taxas de aprendizado:\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"5a52cddea5006d078b9c80a937f9ae61ef71b47e"},"cell_type":"code","source":"learn.sched.plot_lr()","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"004bcf8668b7cf8f6e615165f7382f2becf3f0d8"},"cell_type":"markdown","source":"Nossa perda(loss) na validação não está melhorando muito, então provavelmente não há necessidade de treinar mais a última camada.\n"},{"metadata":{"hidden":true,"_uuid":"ef915e72943eaa713aff3f3f99633741d3ee8425"},"cell_type":"markdown","source":"Já que temos um modelo muito bom neste momento, podemos querer salvá-lo para que possamos carregá-lo novamente mais tarde sem termos de treiná-lo do zero novamente.\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"d25b4eb50d910d761d59266cae6f2d7fe0421c7e"},"cell_type":"code","source":"learn.save('224_lastlayer')","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"39b42e650565e728d992d64392ac6bf1aade2a50"},"cell_type":"code","source":"learn.load('224_lastlayer')","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"d41d5f4276c7122e74d656ff3124309ee38b1b8e"},"cell_type":"markdown","source":"### Ajuste fino e 'differential learning rate annealing'"},{"metadata":{"hidden":true,"_uuid":"0e3956fe3c4426d9ebbf91f5bd2261f6f3bdde66"},"cell_type":"markdown","source":"Agora que temos uma boa camada final treinada, podemos fazer um ajuste fino das outras camadas. Para dizer ao 'learner' que queremos descongelar(unfreeze) as camadas restantes, basta chamar  `unfreeze()`.\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"419fb4693484e0931a0e2c1948407f8e6941b3a1"},"cell_type":"code","source":"learn.unfreeze()","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"8257cdaee00f90652ba6a53aee752a9f7c00d90e"},"cell_type":"markdown","source":"Observe que as outras camadas já foram treinadas para reconhecer fotos da imagenet (enquanto que nossas camadas finais foram inicializadas aleatoriamente), portanto, queremos ter cuidado para não destruir os pesos, cuidadosamente ajustados, que já estão lá.\n\nDe um modo geral, as camadas anteriores (como vimos) têm mais recursos de propósito geral. Portanto, esperamos que elas precisem de menos ajuste fino para novos conjuntos de dados. Por essa razão, usaremos diferentes taxas de aprendizado para diferentes camadas: as primeiras camadas terão 1e-4, as camadas intermediárias em 1e-3 e nossas camadas FC ficarão em 1e-2 como antes. Referimo-nos a isso como *differential learning rates*, embora não haja um nome padrão para essa técnica na literatura, até onde  estamos cientes.\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"e26f2d0657b2bcb5775618304297295fe25fce6c"},"cell_type":"code","source":"lr=np.array([1e-4,1e-3,1e-2])","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"scrolled":false,"trusted":true,"_uuid":"3e85534c6d74887841ffa11d84e2060b0506fdca"},"cell_type":"code","source":"learn.fit(lr, 3, cycle_len=1, cycle_mult=2)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"e6ed9bf5237ffb3de65fb526c6be446c24d1a96e"},"cell_type":"markdown","source":"Outro truque que usamos aqui, é adicionar o parâmetro cycle_mult. Dê uma olhada no gráfico a seguir e veja se você pode descobrir o que o parâmetro está fazendo:\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"da602cd737e886ee04c74af18e70667fb4edde61"},"cell_type":"code","source":"learn.sched.plot_lr()","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"4638b6218ff2dd27cfc32a850bc77cfa68af61bf"},"cell_type":"markdown","source":"Note que o que está sendo plotado acima é a taxa de aprendizado das camadas finais. As taxas de aprendizado das camadas anteriores são fixadas nos mesmos múltiplos das taxas de camada final como solicitamos inicialmente (ou seja, as primeiras camadas têm 100x menores e as camadas intermediárias 10x menores taxas de aprendizado, pois definimos `lr=np.array([1e-4,1e-3,1e-2]).\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"4c82ec89ecdfd7da43ec8b7453606e69315aa1da"},"cell_type":"code","source":"learn.save('224_all')","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"1a76bc80b11b23be22a5b36d227650fbcf33fa9d"},"cell_type":"code","source":"learn.load('224_all')","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"8423f6709c26d21870b793ac7c480afdc78e343f"},"cell_type":"markdown","source":"Há algo a mais que podemos fazer com o aumento de dados(data augmentation): use-o no *inference* time (também conhecido como *test* time). Não é de surpreender que isso seja conhecido como * aumento do tempo de teste * ou apenas * TTA *.\n\nO TTA simplesmente faz previsões não apenas sobre as imagens em seu conjunto de validação, mas também faz previsões em um número de versões aumentadas aleatoriamente(randomly augmented versions) delas também (por padrão, ele usa a imagem original junto com 4 versões aumentadas aleatoriamente). Em seguida, faz a previsão média dessas imagens e usa isso. Para usar o TTA no conjunto de validação, podemos usar o método `TTA()` method.\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"24c3af32e0ef72bbae634c60de48e083dadb6192"},"cell_type":"code","source":"log_preds,y = learn.TTA()\nprobs = np.mean(np.exp(log_preds),0)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"ca09753de12b6064b683edfacf5dd403041104ce"},"cell_type":"code","source":"accuracy_np(probs, y)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"c2fd61b207ec473283c7b1b0906e7238952b15c1"},"cell_type":"markdown","source":"Geralmente verifica-se uma redução de 10-20% no erro neste conjunto de dados ao usar o TTA neste momento, o que é um resultado incrível para uma técnica tão rápida e fácil!\n"},{"metadata":{"heading_collapsed":true,"_uuid":"8adfbc36fa025d7bfb0383753ee92304f0e38a62"},"cell_type":"markdown","source":"## Analisando os resultados"},{"metadata":{"hidden":true,"_uuid":"c7cd447b0f5e414e1bcff80bc0077ffb9b1880eb"},"cell_type":"markdown","source":"### Matrix de confusão"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"440209c1e4a4658a74fccd023e93c14e17a71e6b"},"cell_type":"code","source":"preds = np.argmax(probs, axis=1)\nprobs = probs[:,1]","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"b4365f00c30a15edeee2074160c733483534398a"},"cell_type":"markdown","source":"Uma maneira comum de analisar o resultado de um modelo de classificação é usar uma [matriz de confusão](http://www.dataschool.io/simple-guide-to-confusion-matrix-terminology/).   \nO Scikit-learn possui uma função interessante que podemos usar para esse propósito:\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"1a9a273fde80551ba6bac3f948f08d8820aa4f58"},"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\ncm = confusion_matrix(y, preds)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"e8bd0cfe2df7acd09130ed17487b149bf8d4ae0b"},"cell_type":"markdown","source":"Podemos apenas imprimir a matriz de confusão, ou podemos mostrar um gráfico (que é principalmente útil para dependentes com um número maior de categorias).\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"38b9209a983ebf0003c81c9025c80abad3ae0930"},"cell_type":"code","source":"plot_confusion_matrix(cm, data.classes)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"c08a4d93d6bd4881d9c7d9839458f384c87d33ff"},"cell_type":"markdown","source":"### Vendo as fotos novamente"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"cb9fc45953c7b2781905b4329038752a1a0a1277"},"cell_type":"code","source":"plot_val_with_title(most_by_correct(0, False), \"Gatos mais incorretos\")","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"de130ae59cd00593fc36612f7898eff0e64f1fc1"},"cell_type":"code","source":"plot_val_with_title(most_by_correct(1, False), \"Cães mais incorretos\")","execution_count":null,"outputs":[]},{"metadata":{"heading_collapsed":true,"_uuid":"663b537902e32ada9e764f6ebc697ec3ef9326e2"},"cell_type":"markdown","source":"## Revisão : passos fáceis para treinar um classificador de imagem de nível mundial\n"},{"metadata":{"hidden":true,"_uuid":"752ac8ff2b129c3087b7cc75abbc003e19ece17e"},"cell_type":"markdown","source":"1. precompute=True\n1. Use `lr_find()` to find highest learning rate where loss is still clearly improving\n1. Train last layer from precomputed activations for 1-2 epochs\n1. Train last layer with data augmentation (i.e. precompute=False) for 2-3 epochs with cycle_len=1\n1. Unfreeze all layers\n1. Set earlier layers to 3x-10x lower learning rate than next higher layer\n1. Use `lr_find()` again\n1. Train full network with cycle_mult=2 until over-fitting"},{"metadata":{"heading_collapsed":true,"_uuid":"f5577e7f0edc2d168a68477c7902a264e7cf88e2"},"cell_type":"markdown","source":"## Analisando os resultados: perda(loss) e acurácia(accuracy)"},{"metadata":{"hidden":true,"_uuid":"5523c079a777cdf21629322832a87bc9d53aed32"},"cell_type":"markdown","source":"Quando executamos `learn.fit`, imprimimos 3 valores de desempenho (ver acima). Aqui, 0,03 é o valor da perda no conjunto de treinamento, 0,0226 é o valor da perda(**loss**) no conjunto de validação e 0,9927 é a precisão na validação. Qual é a perda? O que é precisão? Por que não apenas mostrar precisão?\n\nPrecisão(**Accuracy**) é a razão entre a previsão correta e o número total de previsões.\n\nNo aprendizado de máquina, a função de perda(**loss**) ou função de custo, representa o preço pago pela imprecisão das previsões.\n\nA perda associada a um exemplo na classificação binária é dada por: `-(y * log(p) + (1-y) * log (1-p))` onde y é o verdadeiro rótulo de x e p é a probabilidade estimada por nosso modelo, no caso em que o rótulo é 1.\n"},{"metadata":{"hidden":true,"trusted":true,"_uuid":"65688e2e668ce03194805140a7e1ad033e4e865e"},"cell_type":"code","source":"def binary_loss(y, p):\n    return np.mean(-(y * np.log(p) + (1-y)*np.log(1-p)))","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"trusted":true,"_uuid":"eb9207721770bd6d8984f365eee7ee2fcafbdea9"},"cell_type":"code","source":"acts = np.array([1, 0, 0, 1])\npreds = np.array([0.9, 0.1, 0.2, 0.8])\nbinary_loss(acts, preds)","execution_count":null,"outputs":[]},{"metadata":{"hidden":true,"_uuid":"3faf2da6c23bb583949907825c475664ec370af0"},"cell_type":"markdown","source":"Note que no nosso exemplo acima, nossa precisão é de 100% e nossa perda é de 0,16. Compare isso com uma perda de 0,03 que estamos conseguindo enquanto prevemos cães e gatos. Exercício: jogue com as predições para obter uma perda menor para este exemplo.\n\nExemplo: Aqui está um exemplo de como calcular a perda(loss) para um exemplo de problema de classificação binária. Suponha que para uma imagem x com rótulo 1 e seu modelo forneça uma previsão de 0,9. Para este caso, a perda(loss) deve ser pequena porque nosso modelo está prevendo um rótulo 1 com alta probabilidade.\n\n`loss = -log(0.9) = 0.10`\n\nAgora, suponha que x tenha rótulo 0, mas nosso modelo está prevendo 0,9. Neste caso, nossa perda deve ser muito maior.\n\n`loss = -log(1-0,9) = 2.30`\n\nExercício: observe os outros casos e se convença de que isso faz sentido.\nExercício: como você iria reescrever binary_loss usando 'if' em vez de '*' e '+'?\nPor que não apenas maximizar a precisão? A perda da classificação binária é uma função mais fácil de otimizar.\n"},{"metadata":{"collapsed":true,"hidden":true,"trusted":false,"_uuid":"dcf6b2981d255d561ff4ae75d8d173663ddf5389"},"cell_type":"markdown","source":"## FIM"},{"metadata":{"trusted":true,"_uuid":"7c3159be6781eba268687d07d897eb553c02d3b7"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"toc":{"colors":{"hover_highlight":"#DAA520","navigate_num":"#000000","navigate_text":"#333333","running_highlight":"#FF0000","selected_highlight":"#FFD700","sidebar_border":"#EEEEEE","wrapper_background":"#FFFFFF"},"moveMenuLeft":true,"nav_menu":{"height":"266px","width":"252px"},"navigate_menu":true,"number_sections":true,"sideBar":true,"threshold":4,"toc_cell":false,"toc_section_display":"block","toc_window_display":false,"widenNotebook":false}},"nbformat":4,"nbformat_minor":1}