top of page
  • jalesbussinguer

Classificação de dados estruturados derivados de imagens de satélite com machine learning

Atualizado: 15 de fev. de 2021

Desenvovendo um modelo de machine learning com 91% de acurácia como solução de um desafio de hackaton utilizando o Random Forest Classifier


Mas antes, um breve contexto de como tudo surgiu...


Este estudo começou em setembro de 2020. Em um dia comum de quarentena, me deparei com o anúncio de um evento acadêmico do INPE: o WorCAP 2020. Além das palestras e minicursos, me chamou a atenção o fato de haver uma hackaton na programação do evento.


Nessa época eu já estava totalmente imerso nos estudos de programação em Python e buscando ainda mais me inteirar sobre a integração entre as geotecnologias e as técnicas de data science e machine learning. Pimba! Era exatamente sobre isso que se tratava a hackaton!


Mandei uma mensagem no grupo do Telegram da Comunidade do Fascinante Mundo do Sensoriamento Remoto, comandada pelo professor Gustavo Baptista, para convidar os participantes a formarmos um time. Minha expectativa era reunir o máximo de pessoas possível para debatermos o problema e combinar expertises no assunto. Pra minha grata surpresa, os que toparam o desafio foram o João Ataíde e o Marcelo Vieira, CEO da GreenBug.


No dia do evento, nos reunimos remotamente para nos comunicarmos e trabalharmos no problema. Tudo ocorreu bem no processo até o momento da submissão dos resultados. Por diversos problemas de estruturação dos resultados e formatação do arquivo, não conseguimos pontuar na competição. Putz, foi frustrante! Trabalhamos tanto e não conseguimos sequer saber qual era a nossa real pontuação, pois a plataforma nos retornava um zero bem redondo.


Alguns meses se passaram e eu não conseguia esquecer daquele evento. Lá no fundo eu não conseguia aceitar o fato de não saber qual foi o desempenho do time, se ficaríamos bem colocados no ranking ou não. Foi então que decidi revisitar o problema, como uma forma de praticar a programação e aprofundar meus conhecimentos em data science e machine learning, além de descobrir se eu conseguiria obter um resultado satisfatório para o desafio sozinho.


Foi difícil, mas consegui! A seguir, vou mostrar o passo a passo de como construí a solução para o desafio e como obtive um resultado surpreendente.

 

O desafio


O desafio apresentado durante o hackaton do WorCAP 2020 na plataforma Kaggle baseia-se no artigo "Using geographically weighted variables for image classification", publicado por Johnson, Tateishi & Xie (2012).


Neste estudo, os autores utilizaram o método de interpolação espacial IDW (em português, Ponderação pelo Inverso da Distância) para realizar uma previsão dos valores espectrais de amostras de duas classes de cobertura do solo em difentes locais da cena.


Para cada amostra de pixel da imagem, foi calculado um índice de similaridade entre os valores dos pixels medidos pelo sensor e pelos valores preditos com a técnica IDW para uma determinada classe, utilizado como dado adicional para compor o conjunto de dados de treinamento para a classificação.


Para a classificação, foram utilizados dados do sensor ASTER, a bordo do satélite Terra, nos períodos de Setembro de 2010, Março de 2011 e Maio de 2011, respectivamente. As imagens ortorretificadas apresentam resolução espacial de 15 m e são referentes à uma área florestal na região da Prefeitura de Ibaraki no Japão, indicada pelo polígono de bordas pretas na imagem abaixo.


Fonte: modificado de Johnson, Tateishi & Xie (2012)


A paisagem é composta predominantemente por florestas plantadas das espécies Cryptomeria japonica (Sugi, ou Cedro Japonês) e Chamaecyparis obtusa (Hinoki, ou Cipreste Japonês), floresta decidual mista e uma pequena porção de outros tipos de uso da terra como agricultura, estradas, edificações, etc).


Os autores utilizaram o algoritmo SVM (em português, Máquina de Vetores Suporte) como classificador e obtiveram uma precisão de 85,9% e um valor do índice kappa de 0,795 com a utilização dos dados geograficamente ponderados.


Nesse contexto, o objetivo do desafio era desenvolver um modelo de aprendizado de máquina em um contexto de classificação multiclasse capaz de realizar a predição das seguintes classes temáticas:

  • s - abreviação de Sugi - Cedro Japonês

  • h - abreviação de Hinoki - Cipreste Japonês

  • d - abreviação de mixed deciduous forest - Floresta natural mista de folhas largas

  • o - abreviação de Others - Outros tipos de uso do solo

O modelo deveria apresentar a maior acurácia possível, isto é, a porcentagem de acertos que o modelo de classificação foi capaz de predizer. De acordo com os organizadores do evento, qualquer modelo poderia ser utilizado, desde que avaliado de acordo com a métrica proposta.

 

Os dados


Os dados disponibilizados pelos organizadores da competição consistem em três conjuntos distintos: o conjunto de treinamento, o conjunto de teste e o conjunto de verificação.


O conjunto de treinamento, composto por 209 amostras, apresenta três categorias distintas de informações: os valores espectrais de amostras de pixels da cena, os valores espectrais preditos para as classes s e h e os respectivos rótulos dos pixels. Abaixo está uma pequena amostra do conjunto de treino.



O conjunto de teste contém 314 amostras e apresenta as mesmas informações que o conjunto de treino, exceto os rótulos dos pixels. Além disso, o conjunto de teste contém amostras de pixels diferentes do conjunto de treino, denotando estes são pixels em outros locais da cena. Abaixo está uma pequena amostra dos dados de teste.



Por último, o conjunto de verificação contém os rótulos das amostras de pixels do conjunto de dados de teste. Estes serão utilizados para verificar a acurácia do modelo e podem ser interpretados como sendo a verdade terrestre do problema, uma vez que são observações diretas dos rótulos dos pixels de teste.


Dicionário de atributos:

  • id: Identificação única da linha, referente à uma amostra de pixel;

  • b1: Banda do verde correspondente ao mês de setembro de 2010;

  • b2: Banda do vermelho correspondente ao mês de setembro de 2011;

  • b3: Banda do infravermelho próximo correspondente ao mês de setembro de 2011;

  • b4: Banda do verde correspondente ao mês de Março de 2011;

  • b5: Banda do vermelho correspondente ao mês de Março de 2011;

  • b6: Banda do infravermelho próximo correspondente ao mês de Março de 2011;

  • b7: Banda do verde correspondente ao mês de Maio de 2011;

  • b8: Banda do vermelho correspondente ao mês de Maio de 2011;

  • b9: Banda do infravermelho próximo correspondente ao mês de Maio de 2011;

  • pred_minus_obs_S_b1 até pred_minus_obs_S_b9: Valores espectrais previstos com base na interpolação espacial subtraídos dos valores espectrais reais para a classe 's';

  • pred_minus_obs_S_b1 até pred_minus_obs_H_b9: Valores espectrais previstos com base na interpolação espacial subtraídos dos valores espectrais reais para a classe 'h';

  • label: Rótulos das classes temáticas: 's' (floresta Sugi), 'h' (floresta Hinoki), 'd' (floresta decidual mista), 'o' (outros).


A coleção de dados completa pode ser acessada abaixo:


 

A solução


Por este ser o meu primeiro projeto de machine learning, a primeira coisa que fiz foi procurar a literatura especializada para entender quais eram as etapas necessárias para chegar a um modelo capaz de retornar bons resultados e me habituar com os termos técnicos. Para isso, recorri a dois livros que ganhei recentemente, sendo eles:



O conteúdo de ambos os livros serviram de base para a construção do meu modelo. O primeiro, por ter um conteúdo estilo passo-a-passo, me ajudou na implementação da solução, seja com exemplos de código ou com aspectos práticos da construção do modelo.


Como complemento ao primeiro, o segundo livro me ajudou a compreender o significado e a relevância de cada etapa da construção da solução, uma vez que apresenta um conteúdo teórico robusto acerca dos conceitos e ferramentas utilizadas no problema.

 

Para o desenvolvimento da solução do desafio, utilizei a plataforma Google Colab. O código foi implementado em forma de notebook, a fim de facilitar sua construção e reprodutibilidade. O notebook pode ser acessado abaixo:



As bibliotecas utilizadas foram pandas, numpy, matplotlib, seaborn, yellowbrick e os módulos do scikit-learn. Após a importação das bibliotecas, fiz uma checagem da integridade dos três conjuntos de dados. Nesse processo, não foram identificados quaisquer tipos de problemas, sejam dados faltando ou tipos inadequados para a natureza dos dados.


Em seguida, ao fazer a verificação do balanceamento das classes, tanto no conjunto de treino quanto no conjunto de verificação, constatou-se que há um claro desbalanceamento. Nesse caso, determinados algoritmos poderiam distorcer as previsões, privilegiando as classes mais frequentes no conjunto de dados.



Especula-se de que o desbalanceamento das classes seja em função da proporção em área das classes na cena. A metodologia de amostragem utilizada no estudo original foi a stratified systematic unaligned sampling scheme (Jensen, 2005), que subdividiu a cena por meio da sobreposição de uma grade com células de 1 km x 1 km. Em cada célula, as amostras de pixels foram coletadas em dois pontos aleatórios. Com isso, os autores garantiram que a amostragem fosse feita cobrindo toda a imagem. Dessa forma, o desbalanceamento das classes é justificado estatisticamente por se tratar de uma cena com predominância de feições florestais.


Para lidar com esse cenário, mais uma vez me voltei à literatura. O capítulo 09 do livro Machine Learning: Guia de Referência Rápida apresenta diversos métodos para lidar com classes desbalanceadas. O método escolhido foi a utilização de algoritmos de classificação baseados em árvores e ensembles.


Segundo o autor, modelos baseados em árvore de decisão podem ter um melhor desempenho conforme a distribuição da classe menos frequente. Além disso, caso os dados apresentem uma tendência de estarem agrupados, estes poderão ser mais facilmente classificados.


Dessa forma, foi escolhido o algoritmo Random Forest Classifier como modelo-base para a solução do desafio. A escolha foi reforçada pelo fato desse algoritmo utilizar bagging para corrigir a tendência das árvores de decisão à superadequação (overfitting).

 

Definido o algoritmo de aprendizado de máquina, comecei então a trabalhar na construção do modelo. Primeiramente fiz uma preparação dos dados, separando as variáveis preditoras (atributos) das variáveis alvo (rótulos) do conjunto de dados de treino. Além disso, retirei também a coluna 'id' dos conjuntos de treino e teste. Ao todo, são 27 atributos utilizados para fazer a previsão de 4 classes.


Feito isto, passei então a trabalhar no modelo, de acordo com o fluxograma abaixo:

Primeiro, estabeleci um modelo-padrão (default) do Random Forest com todos os valores dos hiperparâmetros originais. Com isso realizei o treinamento, as predições e as medidas de acurácia e índice kappa para ter uma linha de base do desempenho e do poder do algoritmo para classificar dados dessa natureza.



# Modelo padrão (default)

RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None, criterion='gini', max_depth=None,
max_features='auto', max_leaf_nodes=None, max_samples=None,
min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=100,                        n_jobs=None, oob_score=False, random_state=42, verbose=0, warm_start=False)

O modelo padrão retornou uma acurácia de 89,5% e um índice kappa de 0,854. Este já é um resultado excelente, haja vista que supera ambos os resultados do estudo original de 85,9% de acurácia e índice kappa de 0,795. Tal fato endossa a escolha do algoritmo de classificação como uma boa solução para o problema.


Em seguida, comecei a trabalhar na otimização do modelo-padrão ajustando os hiperparâmetros utilizando o GridSearchCV com uma validação-cruzada do tipo KFold. Após muitos experimentos e leitura, os hiperparâmetros escolhidos para a otimização foram o número de árvores na floresta aletória (n_estimators), o critério de separação das amostras (criterion) e o seed aleatório (random_state).


Em geral, o seed aleatório não é utilizado na otimização do modelo, uma vez que este é um hiperparâmetro utilizado para garantir que os mesmos resultados serão retornados toda vez que executar o código. A grande maioria de exemplos e projetos que tive contato utilizam o famoso número 42 como seed a fim de eliminar a aleatoriedade dos resultados e possibilitar a comparação com outros modelos. Contudo, após a leitura de um artigo que utiliza o número diferente como seed, fiquei curioso se a mudança do valor desse hiperparâmetro exerceria algum tipo de influência nos resultados do modelo e decidi fazer uma verificação.


Para entender melhor essa questão, fiz a plotagem de uma curva de validação a fim de avaliar como o desempenho do modelo-padrão responde a mudanças no valor desse hiperparâmetro. Foram testados valores entre 0 e 50.


As pontuações apresentadas pela curva "Cross Validation Score" explicitam como o modelo responderia a dados não vistos anteriormente, nos permitindo inferir que o modelo é sensível a esse hiperparâmetro.


Os valores que retornaram as melhores pontuações nessa curva foram utilizados para a otimização do modelo default.


Sendo assim, a configuração da busca em grade foi estabelecida da seguinte forma:


# Listas de valores candidatos

# n_estimators (quantidade de árvores na floresta aleatória)
n_estimators = [100, 250, 500, 750, 1000, 1250, 1500]
# random state
random_state = [0, 20, 23, 37, 38, 42, 46]
# Criterion
criterion = ['gini', 'entropy']

# Dicionário de parâmetros a serem otimizados
rf_parametros = {'n_estimators': n_estimators,'criterion': criterion, 'random_state': random_state}

# Busca em grade (Grid Search)
RFtuning = GridSearchCV(rf_default, # modelo
                             param_grid=rf_parametros, # parâmetros
                             cv=KFold(n_splits=10), # validação cruzada
                             scoring= 'accuracy', # métrica de ajuste
                             n_jobs=-1).fit(X, y)

cv_res = RFtuning.cv_results_ # Dicionário de resultados
 
# loop para retornar cada combinação
for mean_score, params in zip(cv_res['mean_test_score'], cv_res['params']):
 print(f'Acurácia: {mean_score} para os parâmetros {params}\n')

# Imprime a melhor acurácia e a melhor combinação de hiperparâmetros
print(f'Melhor resultado: {RFtuning.best_score_} para {RFtuning.best_params_}')

O melhor resultado obtido pela busca foi uma acurácia média de 87,05% para o conjunto de dados de treino. Contudo, ao analisar a acurácia de cada combinação, verifiquei que tal performance foi desempenhada por três configurações de modelos diferentes, com os seguintes valores de hiperparâmetros:

  1. Modelo 1 = {'criterion': 'entropy', 'n_estimators': 500, 'random_state': 37}

  2. Modelo 2 = {'criterion': 'entropy', 'n_estimators': 750, 'random_state': 37}

  3. Modelo 3 = {'criterion': 'entropy', 'n_estimators': 750, 'random_state': 42}

Nesse contexto, treinei, realizei as predições com os dados de teste e verifiquei a acurácia do modelo Random Forest com as três configurações de hiperparâmetros utilizando. Com isso, obtive os seguintes resultados:



Embora os três modelos retornem a mesma acurácia para os dados de treino, estes apresentam performances diferentes em um conjunto de dados desconhecido. O Modelo 2 apresentou a melhor performance, com uma acurácia de 91,08% e índice kappa de 0,876, sendo este então escolhido como o modelo final otimizado para o problema. De forma geral, a diferença de perfomance se deu principalmente em função da quantidade de árvores e do seed aleatório.



# Modelo Final (otimizado)

RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None, criterion='entropy', max_depth=None,
max_features='auto', max_leaf_nodes=None, max_samples=None,
min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=750,                        n_jobs=-1, oob_score=False, random_state=37, verbose=0, warm_start=False)

Ao comparar o modelo padrão e o modelo otimizado, percebemos que o ganho de performance é bastante relevante. Pelas matrizes de confusão podemos observar que, em geral, o modelo otimizado aumentou a taxa de acerto para todas as classes, exceto a classe 'h'. Consequentemente, houve uma diminuição de casos do tipo "falso-positivo" em todas as classes, exceto a classe 'o'.



Dadas as características dos conjuntos de dados, que apresentam atributos com dados medidos direta e indiretamente e classes desbalanceadas, as performances de ambos os modelos foram excelentes. Além disso, com a pontuação alcançada com o modelo otimizado, ficaríamos empatados com o terceiro lugar geral no ranking da competição.

 

Conclusões


Neste projeto, executei uma série de procedimentos para a construção de um modelo de aprendizado de máquina capaz de prever múltiplas classes temáticas a partir de dados estruturados derivados de imagens de satélite e interpolação espacial.


Tal modelo pode ser utilizado dentro de uma rotina automatizada com a finalidade de monitorar a região. Segundo Johnson, Tateishi & Xie (2012), cada um dos três tipos de florestas possuem diferentes usos econômicos e valores de conservação ambiental. Com isso, um mapeamento preciso da distribuição espacial dessas feições é importante para estudos econômicos e ecológicos.


Em todo o processo de desenvolvimento da solução do desafio tive um primeiro contato com alguns conceitos e as ferramentas de aprendizado de máquina, além de aprofundar meus conhecimentos de técnicas geoestatísticas.


Durante a execução do projeto, identifiquei algumas questões que poderiam ser melhor exploradas como o teste de diversas famílias de algoritmos a fim de determinar o melhor algoritmo de classificação e o ajuste de modelos de penalização ou utilização de upsampling/downsampling para lidar com o desbalanceamento das classes.


Por fim, uma importante lição que aprendi com este projeto é sempre atentar para a sensibilidade do modelo utilizado em relação a seus hiperparâmetros, especialmente quanto ao seed aleatório. Creio que esta é uma boa prática que pode trazer benefícios para projetos futuros, m

e dando mais segurança na confiabilidade do modelo.

 

Agradecimentos


Meus sinceros agradecimentos à equipe organizadora do hackaton do WorCAP 2020: Felipe Carvalho, Felipe Menino e Adriano Almeida por proporem o desafio e pelas lições do minicurso de Data Science e Machine Learning.


Um agradecimento especial dedico ao Adriano Almeida por gentilmente me ceder o conjunto de dados de verificação e me ajudar em algumas questões desse projeto.

 

Referências Bibliográficas









1 comentario


marcelo.vieira
09 feb 2021

Olá Jales! Realmente foi um grande aprendizado! E o que achei mais legal foi o nosso trabalho em equipe e o nascimento de amizades! O João, com mais experiência na área, foi fundamental e ia nos explicando os conceitos ao demonstrar os códigos no compartilhamento de tela. Depois, a contagem regressiva e a nossa correria para modificar o formato do arquivo para ver se o Kaggle aceitava o nosso arquivo foi uma demonstração para trabalhos remotamente "pressionados"rsrrs. Aliás, faltou você contar o segredo do formato do arquivo... E vamos que vamos!! um abraço meu amigo e parabéns pelo post!

Me gusta
bottom of page