{"cells":[{"metadata":{},"cell_type":"markdown","source":"\n# Cassava Leaf Disease Classification\n\nNesta competição, tenta-se identificar doenças comuns em plantações de mandioca usando ciência de dados e aprendizado de máquina. Métodos de detecção de doenças exigem que os agricultores solicitem a ajuda de especialistas agrícolas financiados pelo governo para inspecionar visualmente e diagnosticar as plantas. Isso sofre por ser muito trabalhoso, com baixo suprimento e caro. Em vez disso, seria preferível se um pipeline automatizado baseado em fotos de qualidade móvel das folhas de mandioca pudesse ser desenvolvido.\n\nNesse notebook,  será utilizado um conjunto de dados disponível em https://www.kaggle.com/c/cassava-leaf-disease-classification/leaderboard, rotulado por especialistas do National Crops Resources Research Institute (NaCRRI).\n\nNeste kernel, é usado um iniciador fastai."},{"metadata":{},"cell_type":"markdown","source":"## Olhando os dados"},{"metadata":{},"cell_type":"markdown","source":"Para começar, foi configurado o ambiente, instalando e importando os módulos necessários e definindo uma semente aleatória:"},{"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 *","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"set_seed(999)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Verificando o que está disponível:"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"dataset_path = Path('../input/cassava-leaf-disease-classification')\nos.listdir(dataset_path)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Verifica-se que um arquivo csv de treinamento (train.csv) contém os nomes e rótulos de imagem. O csv de envio de amostra, com os nomes de imagem de teste e as pastas de imagem de teste e treinamento. Também há as imagens no formato tfrecords, que é útil para o carregamento rápido de imagens, especialmente para TensorFlow e TPUs. Não usado nesse notebook.\n\nVerificando o arquivo csv de treinamento e removendo a imagens duplicadas de acordo com esta [discussão](https://www.kaggle.com/c/cassava-leaf-disease-classification/discussion/198202)\n\n'1562043567.jpg' e '3551135685.jpg' (rótulo incorreto)\n\n'2252529694.jpg' e '911861181.jpg' (duplicado)"},{"metadata":{"trusted":true},"cell_type":"code","source":"data = pd.read_csv(dataset_path/'train.csv')\ntrain_df = data[~data['image_id'].isin(['1562043567.jpg', '3551135685.jpg', '2252529694.jpg'])]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Executando um processamento rápido dos nomes dos arquivos de imagem para facilitar o acesso:"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['path'] = train_df['image_id'].map(lambda x:dataset_path/'train_images'/x)\ntrain_df = train_df.drop(columns=['image_id'])\ntrain_df = train_df.sample(frac=1).reset_index(drop=True) #mix dataframe\ntrain_df.head(5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Verificando quantas imagens estão disponíveis no conjunto de dados de treinamento:"},{"metadata":{"trusted":true},"cell_type":"code","source":"len_df = len(train_df)\nprint(f\"Dataset contém {len_df} imagens\")\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Há um conjunto com com mais de 21k imagens! Com isso é possível desenvolver um modelo preditivo, robusto e generalizável com este conjunto de dados.\n\nAgora, verificando a distribuição das diferentes classes:"},{"metadata":{"trusted":true},"cell_type":"code","source":"with open(dataset_path/\"label_num_to_disease_map.json\") as f:\n    class_names = json.loads(f.read())\nf.close()\n\ntrain_df[\"label_name\"] = train_df['label'].apply(lambda x: class_names[str(x)])\ntrain_df.label = train_df.label.astype(str)\n\nprint(\"Total exemplos de treino: \", len(train_df))\ntrain_df.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"count = train_df.label.value_counts().sort_index()\nplt.bar(count.keys(), count)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Neste caso, temos 5 rótulos (4 doenças e saudável):\n0. Cassava Bacterial Blight (CBB)\n1. Cassava Brown Streak Disease (CBSD)\n2. Cassava Green Mottle (CGM)\n3. Cassava Mosaic Disease (CMD)\n4. Healthy\n\nNeste caso, o rótulo 3 - Cassava Mosaic Disease (CMD) (https://en.wikipedia.org/wiki/Cassava_mosaic_virus) é o rótulo mais comum. Esse desequilíbrio pode ter que ser tratado com uma função de perda ponderada ou sobreamostragem. E pode-se tentar isso em uma iteração futura deste kernel ou em um novo kernel.\n\nVerificando uma imagem de exemplo para ver como ela se parece:"},{"metadata":{"trusted":true},"cell_type":"code","source":"from PIL import Image\n\nimg_cmd = Image.open(train_df['path'][1])\nwidth, height = img_cmd.size\nprint(width,height) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"img_cmd","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Carregando os dados\n\nDepois de olhar os dados, os dados são carregados no fastai como objetos `DataLoaders`. \n\nPrimeiro, vamos definir as transformações de item e em lote. As 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 512 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."},{"metadata":{"trusted":true},"cell_type":"code","source":"item_tfms = RandomResizedCrop(512, 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":"Embora o fastai forneça várias maneiras de fazer o carregamento de dados personalizado (até mesmo usando PyTorch DataLoaders simples), os problemas tradicionais de classificação de imagens funcionam bem na API de dados de alto nível. Aqui, são passadas todas as informações necessárias para criar um objeto `DataLoaders`"},{"metadata":{"trusted":true},"cell_type":"code","source":"dls = ImageDataLoaders.from_df(train_df, #pass in train DataFrame\n                               valid_pct=0.2, #80-20 train-validation random split\n                               seed=999, #seed\n                               label_col=0, #label is in the first column of the DataFrame\n                               fn_col=1, #filename/path is in the second column of the DataFrame\n                               bs=bs, #pass in batch size\n                               item_tfms=item_tfms, #pass in item_tfms\n                               batch_tfms=batch_tfms) #pass in batch_tfms","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Para confirmar a criação bem-sucedida do dataloader, podemos usar o comando `show_batch`, que mostra um subconjunto do lote:"},{"metadata":{"trusted":true},"cell_type":"code","source":"dls.show_batch()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Treinando o modelo"},{"metadata":{},"cell_type":"markdown","source":"Vamos treinar um modelo EfficientNet-B3 aa. Usando o pacote [timm](https://github.com/rwightman/pytorch-image-models) de Ross Wightman para definir o modelo. Como esta competição não permite acesso à Internet, adicionei os pesos pré-treinados de timm como um conjunto de dados, e a célula de código abaixo permitirá que timm encontre o arquivo:"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Making pretrained weights work without needing to find the default filename\nif not os.path.exists('/root/.cache/torch/hub/checkpoints/'):\n        os.makedirs('/root/.cache/torch/hub/checkpoints/')\n!cp '../input/timmefficientnet/tf_efficientnet_b3-e3bd6955.pth' '/root/.cache/torch/hub/checkpoints/tf_efficientnet_b3-e3bd6955.pth'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"No fastai, a classe de treinamento é o `Learner`, que recebe os dados, modelo, otimizador, função de perda, etc. e permite que você treine modelos, faça previsões, etc.\n\nAo treinar modelos CNN comuns como ResNets, normalmente podemos usar a função `cnn_learner` que cria um objeto `Learner` que nos permite treinar um modelo fornecido com os carregadores de dados fornecidos. No entanto, cnn_learner não oferece suporte aos modelos do timm prontos para uso. Zach Mueller (@muellerzr) [has written some simple functions](https://walkwithfastai.com/vision.external.timm)  escreveu algumas funções simples para tornar muito fácil criar objetos Learner para modelos timm.\n\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"from timm import create_model\nfrom fastai.vision.learner import _update_first_layer\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    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): return nn.Sequential(*list(model.children())[:cut])\n    elif callable(cut): return cut(model)\n    else: raise NamedError(\"cut must be either integer or function\")\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    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: head = custom_head\n    model = nn.Sequential(body, head)\n    if init is not None: 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: config = {}\n    if n_out is None: 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    if y_range is None and 'y_range' in config: y_range = config.pop('y_range')\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    if pretrained: learn.freeze()\n    return learn","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Vamos agora criar nosso objeto `Learner`. Também usando técnicas de treinamento de suavização de rótulos e otimizador `Ranger`, que são fornecidas no fastai. Também  usando a precisão mista com muita facilidade:"},{"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()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Agora temos um objeto `Learner` que tem um modelo congelado (apenas os pesos da cabeça do modelo podem ser atualizados). Para treinar um modelo, precisamos encontrar a taxa de aprendizagem ideal, o que pode ser feito com o localizador de taxa de aprendizagem do fastai `lr_find()`:"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Frequentemente, se usa um modelo pré-treinado congelado para uma única época e, em seguida, treinar todo o modelo pré-treinado para várias épocas. O otimizador `Ranger` tem melhor desempenho com uma programação de taxa de aprendizado de recozimento plana + cosseno. Agora treinaremos o modelo congelado por uma época.\n\nConforme mostrado acima, a taxa de aprendizado ideal para treinar o modelo congelado é onde a perda está diminuindo mais rapidamente: cerca de ~ 1e-1. Por segurança, foi usado um peso elevado para ajudar a prevenir o sobreajuste. Também usaremos outra técnica comum de treinamento de última geração: `mixup`."},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.freeze() \nlearn.fit_flat_cos(1,1e-1, wd=0.5, cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"É fundamental que qualquer preparação de dados realizada em um conjunto de dados de treinamento também seja realizada em um novo conjunto de dados no futuro.\nIsso pode incluir um conjunto de dados de teste ao avaliar um modelo ou novos dados do domínio ao usar um modelo para fazer previsões.\nNormalmente, o modelo ajustado no conjunto de dados de treinamento é salvo para uso posterior. \n\nA solução correta para preparar novos dados para o modelo no futuro é também salvar quaisquer objetos de preparação de dados, como métodos de escalonamento de dados, para arquivar junto com o modelo.\n\nEntão salvamos o modelo usando o `save()` \n\nIsso armazena o modelo junto com os dados de treinamento usados para criá-lo.\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.save('modelo-1')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = learn.load('modelo-1')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.recorder.plot_loss()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Vamos agora descongelar o modelo e encontrar uma boa taxa de aprendizado `lr_find()`:"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.unfreeze()\nlearn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Vamos treinar por 10 épocas com o modelo descongelado."},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.unfreeze()\n#learn.fit_one_cycle(10, max_lr=slice(0.0001737800776027143, 1.3182567499825382e-06))\nlearn.fit_flat_cos(10,2e-3,pct_start=0,cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Traçamos a perda `plot_loss`, colocamos o modelo de volta no [fp32](https://docs.fast.ai/callback.fp16.html).\n\nA precisão anterior era de `fp16` (meia precisão)"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.recorder.plot_loss()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = learn.to_native_fp32()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Salavando e exportando o modelo para usar mais tarde, para possíveis inferências:"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.save('modelo-2')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.export()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"O `fastai`fornece métodos de interpretação para modelos de classificação, como o [ClassificationInterpretation](https://docs.fast.ai/interpret.html), para melhor interpretar as previsões de um modelo, e gerar a matriz de confusão:\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp.plot_confusion_matrix()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Inferência\n\nA função `dls.test_dl` permite que você crie um dataloader de teste usando o mesmo pipeline definido anteriormente."},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df = pd.read_csv(dataset_path/'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:dataset_path/'test_images'/x)\n_sample_df = _sample_df.drop(columns=['image_id'])\ntest_dl = dls.test_dl(_sample_df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Visualizando o `test_dl`"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dl.show_batch()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Para as previsões, foi aplicada técnica Test-Time Augmentation (15x TTA), conforme [Jason Brownlee](https://machinelearningmastery.com/how-to-use-test-time-augmentation-to-improve-model-performance-for-image-classification/#:~:text=Test%2Dtime%20augmentation%2C%20or%20TTA,an%20ensemble%20of%20those%20predictions)."},{"metadata":{"trusted":true},"cell_type":"code","source":"preds, _ = learn.tta(dl=test_dl, n=10, beta=0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Preparando submissão"},{"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}