{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Competição: Cassava Leaf Classification\n\nO objetivo é identificar doenças comuns em plantações de mandioca, usando Aprendizado de Máquina, para tal, existe um conjunto de dados rotulados por especialistas, onde há registro de quatro doenças e também, da folha saudável, conforme segue: \n* 0 Cassava Bacterial Blight (CBB)\n* 1 Cassava Brown Streak Disease (CBSD)\n* 2 Cassava Green Mottle (CGM)\n* 3 Cassava Mosaic Disease (CMD)\n* 4 Healthy"},{"metadata":{},"cell_type":"markdown","source":"## 1. Identificação\n\nEstudante: Leandro Camargo\n\nData: 12 de dezembro de 2020\n\nVersão: 2.0"},{"metadata":{},"cell_type":"markdown","source":"## 2. Preparação do Ambiente\n\nInstalação e importação das bibliotecas "},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install ../input/pytorch-image-models/timm-0.3.1-py3-none-any.whl","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport os\nimport pandas as pd\nfrom   fastai.vision.all import *\nimport seaborn as sns\nimport matplotlib.pyplot as plt \nimport plotly.express as px\nimport cv2\nfrom   PIL import Image\nimport keras\nfrom   sklearn.model_selection import train_test_split\nfrom   sklearn.metrics import accuracy_score\nimport tensorflow as tf\nfrom   tensorflow.keras import models, layers\nfrom   tensorflow.keras.preprocessing import image\nfrom   tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom   tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nfrom   tensorflow.keras.applications import EfficientNetB0, Xception\nfrom   tensorflow.keras.optimizers import Adam","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 3. Organização dos dados\n\n### 3.1 Pastas"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# Caminho\ncaminho = Path('../input/cassava-leaf-disease-classification')\n\n# Estrutura\nos.listdir(caminho)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"###  3.2 Distribuição dos dados de Treinamento"},{"metadata":{"trusted":true},"cell_type":"code","source":"train =  pd.read_csv(caminho/'train.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Recorte dos primeiros registros:\\n\", train.head(10))\n\nprint(f\"\\n\\nTrabalhando com: {train.shape[0]} imagens nos dados de treinamento: \")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 3.3 Distribuição dos valores nas classes"},{"metadata":{"trusted":true},"cell_type":"code","source":"import json \n\nwith open(caminho/'label_num_to_disease_map.json') as temp:\n    categorias = json.load(temp)\n\nrotulos ={int(chave):valor for chave,valor in categorias.items()}\n\nprint(\"Rotulo das classes:\")\nfor x in rotulos:\n    print (x, f\"- {rotulos [x]};\")\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(10,5))\nsns.set_style('whitegrid')\nax=sns.countplot(x='label', data=train, palette='Pastel1')\n\nplt.title('Distribuição dos dados nas Classes', fontsize=15)\nplt.xlabel('Classificação das Folhas')\nplt.ylabel('Ocorrências')\nplt.show(ax)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Neste caso, o rótulo 3, Doença do Mosaico da Mandioca (DMC) é o rótulo mais comum. Esse desequilíbrio será tratado com uma função de perda ponderada ou sobreamostragem."},{"metadata":{},"cell_type":"markdown","source":"## 4 Tratamento nos dados\n\n### 4.1 Ajuste no nome dos arquivos de imagem \n\nProcesso realizado para acelerar o acesso aos arquivos de imagem"},{"metadata":{"trusted":true},"cell_type":"code","source":"train['path'] = train['image_id'].map(lambda x:caminho/'train_images'/x)\ntrain = train.drop(columns=['image_id'])\ntrain = train.sample(frac=1).reset_index(drop=True) #misturando o conjunto de dados\ntrain.head(10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 4.2 Tratamento das imagens"},{"metadata":{"trusted":true},"cell_type":"code","source":"from PIL import Image\n\nfigura = Image.open(train['path'][1])\nwidth, height = figura.size\n\nprint(f\"Resolução da imagem:\\n Largura: {width} x Altura: {height}\") \n\nfigura","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### 4.2.1 Transformações de item e lote\n\nAs transformações de item realizam um corte bastante grande em cada uma das imagens, enquanto as transformações de lote realizam corte redimensionado aleatório para 224 e também aplicam outros aumentos padrão (em aug_tranforms) no nível de lote na GPU. O tamanho do lote é definido para 32 aqui.\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"#Definição de parâmetros do DataLoaders\nset_seed(999)\nitem_tfms = RandomResizedCrop(460, min_scale=0.75, ratio=(1.,1.))\nbatch_tfms = [*aug_transforms(size=224, max_warp=0), Normalize.from_stats(*imagenet_stats)]\nbs=32","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 4.3 Criando um objeto 'DataLoaders'\n\n> Permite o carregamento de dados personalizados que facilitam a classificação de imagens, com a configuração de várias informações necessárias para a criação de um objeto DataLoaders."},{"metadata":{"trusted":true},"cell_type":"code","source":"dls = ImageDataLoaders.from_df(train,         #conjunto de dados de treinamento\n                               valid_pct=0.2, #80-20 train-validation random split\n                               seed=999,      #seed\n                               label_col=0,   #label está na primeira coluna do conjunto\n                               fn_col=1,      #filename/path está na segunda coluna\n                               bs=bs,         #batch size\n                               item_tfms=item_tfms,   #item_tfms\n                               batch_tfms=batch_tfms) #batch_tfms","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Para confirmar a criação bem-sucedida do dataloader, o comando `show_batch`, mostra um subconjunto do lote"},{"metadata":{"trusted":true},"cell_type":"code","source":"dls.show_batch()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 5. Treinamento do Modelo"},{"metadata":{},"cell_type":"markdown","source":"Treinar um modelo EfficientNet-B3, com adoção do pacote timm para definição do modelo."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Fazendo pesos pré-treinados funcionarem sem a necessidade de encontrar o nome do arquivo padrão\nif not os.path.exists('/root/.cache/torch/hub/checkpoints/'):\n        os.makedirs('/root/.cache/torch/hub/checkpoints/')\n        \n!cp '../input/timmefficientnet/tf_efficientnet_b3_ns-9d44bf68.pth' '/root/.cache/torch/hub/checkpoints/tf_efficientnet_b3_ns-9d44bf68.pth'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Ao treinar modelos CNN comuns como ResNets, é possível utilizar a função cnn_learner que cria um objeto Learner, permitindo treinar um modelo carregando dados fornecidos."},{"metadata":{"trusted":true},"cell_type":"code","source":"from timm import create_model\nfrom fastai.vision.learner import _update_first_layer\n\n\ndef create_timm_body(arch:str, pretrained=True, cut=None, n_in=3):\n    \"Creates a body from any model in the `timm` library.\"\n    model = create_model(arch, pretrained=pretrained, num_classes=0, global_pool='')\n    _update_first_layer(model, n_in, pretrained)\n    \n    if cut is None:\n        ll = list(enumerate(model.children()))\n        cut = next(i for i,o in reversed(ll) if has_pool_type(o))\n    if isinstance(cut, int): \n        return nn.Sequential(*list(model.children())[:cut])\n    elif callable(cut): \n        return cut(model)\n    else: \n        raise NamedError(\"cut must be either integer or function\")\n        \n        \ndef create_timm_model(arch:str, n_out, cut=None, pretrained=True, n_in=3, init=nn.init.kaiming_normal_, custom_head=None,\n                     concat_pool=True, **kwargs):\n    \"Create custom architecture using `arch`, `n_in` and `n_out` from the `timm` library\"\n    body = create_timm_body(arch, pretrained, None, n_in)\n    \n    if custom_head is None:\n        nf = num_features_model(nn.Sequential(*body.children())) * (2 if concat_pool else 1)\n        head = create_head(nf, n_out, concat_pool=concat_pool, **kwargs)\n    else: \n        head = custom_head\n    \n    model = nn.Sequential(body, head)\n    \n    if init is not None: \n        apply_init(model[1], init)\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def timm_learner(dls, arch:str, loss_func=None, pretrained=True, cut=None, splitter=None,\n                y_range=None, config=None, n_out=None, normalize=True, **kwargs):\n    \"Build a convnet style learner from `dls` and `arch` using the `timm` library\"\n    if config is None: \n        config = {}\n    if n_out is None: \n        n_out = get_c(dls)\n    assert n_out, \"`n_out` is not defined, and could not be inferred from data, set `dls.c` or pass `n_out`\"\n    \n    if y_range is None and 'y_range' in config: \n        y_range = config.pop('y_range')\n    \n    model = create_timm_model(arch, n_out, default_split, pretrained, y_range=y_range, **config)\n    learn = Learner(dls, model, loss_func=loss_func, splitter=default_split, **kwargs)\n    \n    if pretrained: \n        learn.freeze()\n    \n    return learn","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 5.1 Criação de um Objeto Learner\n\nSerão utilizadas técnicas disponíveis no fastai, como: label smoothing e Ranger optimizer."},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = timm_learner(dls, \n                    'tf_efficientnet_b3_ns', \n                     opt_func = ranger,\n                     loss_func=LabelSmoothingCrossEntropy(),\n                     metrics = [accuracy]).to_native_fp16()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Com o objeto Learner, apenas os pesos da cabeça do modelo podem ser atualizados. Para treinar um modelo, é necessário encontrar a taxa de aprendizagem ideal, o que pode ser feito com o localizador de taxa de aprendizagem do fastai"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Congela o modelo para um única época\nlearn.freeze()\nlearn.fit_flat_cos(1,1e-1, wd=0.5, cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Descongela o modelo e localiza uma boa taxa de aprendizado\nlearn.unfreeze()\nlearn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Treina por 10 épocas com o modelo descongelado.\nlearn.unfreeze()\nlearn.fit_flat_cos(10,2e-3,pct_start=0,cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.recorder.plot_loss()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Traça a perda, coloca o modelo de volta no fp32\nlearn = learn.to_native_fp32()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.save('stage-2')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# carregando os melhores pesos\nlearn = learn.load('best_model')\n\n# carregando os últimos pesos\nlearn = learn.load('last_model')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#exportar o modelo para utilizar mais tarde\nlearn.export('./export.pth')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission = pd.read_csv(caminho/'sample_submission.csv')\n\ntest_dtp = submission['image_id'].apply(lambda x: caminho/'test_images'/x)\ntest_dls = learn.dls.test_dl(test_dtp)\npredictions = learn.tta(dl = test_dls, n=10)\n\nsubmission['label'] = np.argmax(predictions[0],axis=1)\n\nsubmission.to_csv('submission.csv',index=False)\nsubmission","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 5.2 Matriz de Confusão - fastai"},{"metadata":{"trusted":true},"cell_type":"code","source":"#verificar a matriz de confusão\ninterp = ClassificationInterpretation.from_learner(learn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp.plot_confusion_matrix()\nplt.title('Matriz de Confusão', fontsize=20)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 5.3 Inferencia\n\nA função `dls.test_dl` permite a criação de um dataloader de teste usando o mesmo pipeline definido anteriormente."},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df = pd.read_csv(caminho/'sample_submission.csv')\nsample_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"_sample_df = sample_df.copy()\n_sample_df['path'] = _sample_df['image_id'].map(lambda x:caminho/'test_images'/x)\n_sample_df = _sample_df.drop(columns=['image_id'])\ntest_dl = dls.test_dl(_sample_df)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Verificação test_dl está correto:\ntest_dl.show_batch()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Passando o dataloader para o modelo, visando obter previsões - 8x TTA"},{"metadata":{"trusted":true},"cell_type":"code","source":"preds, _ = learn.tta(dl=test_dl, n=8, beta=0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 6. Submissão das previsões"},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df['label'] = preds.argmax(dim=-1).numpy()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df.to_csv('submission.csv',index=False)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}