Como Venci a Competição de Machine Learning da Maior Empresa de Telecomunicações da Austrália em Apenas 19 Dias

A Telstra, maior empresa de telecomunicações e mídia da Austrália, patrocinou uma competição no Kaggle buscando cientistas de dados que se destacassem para participar de um processo de recrutamento para integrar seu time de Big Data.

Foram disponibilizados dados de logs de serviço de sua rede, e a tarefa era criar um modelo que pudesse prever a gravidade de uma falha num determinado local e horário, através dos dados destes relatórios.

No total, 974 competidores do mundo todo participaram, e esta vitória acabou me colocando no 12º lugar (de 500.000+ usuários) no ranking global do Kaggle.

Dados

Eram cerca de 7400 exemplos para treino e 11200 para teste. Uma quantidade pequena, o que requer uma atenção especial durante a criação e validação do modelo.

Cada exemplo possuía um ID, que representava um par entre local e horário do evento. Além disso, foram disponibilizados outros arquivos que continham, para cada exemplo: informações sobre sinais obtidos pelo sistema atual ao processar os logs, o tipo de evento, e a gravidade de mensagens enviadas pelo sistema naquele momento.

A gravidade do problema tinha três categorias: 0, significando que não havia problemas; 1, significando que alguns problemas foram detectados na rede; e 2, representando muitos problemas reportados. Portanto, trata-se de classificação com múltiplas classes.

Validação

Apesar de haver uma dependência temporal e espacial entre os dados, já que são de locais diferentes, em momentos de tempo diferentes, os dados foram particionados de maneira aleatória. Era possível saber a localização, mas não o momento em que o registro foi obtido.

Com isso, uma validação cruzada simples foi o melhor ambiente que encontrei para testar meu modelo. A validação cruzada é simplesmente o procedimento de dividir seus dados em K partes, e fazer um ciclo de treino, usando K – 1 para treinar e avaliando na divisão não utilizada para teste.

Neste caso usei 5 divisões, que forneceram uma estimativa bastante confiável da performance nos dados de teste revelados após o fim da competição.

Variáveis

No total eu tinha 242 variáveis. Vou citar aqui as informações que acredito serem mais importantes para um modelo que fosse implementado em produção.

Variável “mágica”

Havia um padrão nos dados, que mais tarde seria chamado de “variável mágica” nos fóruns, que permitia explorar o que parecia ser um erro durante o preparo dos dados, e conseguir uma boa melhora na precisão do modelo.

Apesar dos arquivos de treino e teste possuírem IDs aleatórios, o ordenamento dos registros dos outros arquivos possuía valor preditivo. Com isso, você poderia usar o próprio ID destes arquivos, ou criar variáveis relacionadas a ele, para explorar a falha.

Algumas pessoas criticaram o fato dos melhores colocados usarem este erro para conseguir uma pontuação melhor na competição, mas eu acredito que seja importante conseguir encontrar este tipo de erro em dados, pois a única coisa que mudaria fora de uma competição é que ele deveria ser corrigido em vez de explorado.

Por isso, é sempre importante buscar erros nos dados. Existem diversos tipos de vazamentos de informação, alguns muito sutis, que podem fazer seu modelo parecer melhor do que realmente é.

Localização

Havia uma variável indicando o local daquele registro. Esta, em tese, é uma variável categórica, ou seja, não tem ordem natural, mas mesmo assim utilizei-a como ordinal, em vez de criar uma coluna para cada valor, por dois motivos: árvores conseguem aproximar bem os padrões de variáveis categóricas mesmo usando este formato (em alguns casos é melhor usar one hot), e poderia haver algum padrão de proximidade entre os locais.

Por exemplo, pode ser que o local 201 e 202 estivessem sob as mesmas condições climáticas, o que poderia influenciar a ocorrência de problemas nestas duas estações, então talvez houvesse um agrupamento implícito ali.

Variáveis do Log

Os registros dos logs de serviço possuíam o que parecia ser uma linha para cada sinal de aviso, e um valor indicando quantas vezes o aviso foi disparado naquela ocasião. Os organizadores não deixaram claro se era este o significado, mas é a explicação mais plausível.

Com isso, criei uma coluna para cada um destes sinais, e o valor para cada exemplo era o número de avisos.

Estas colunas eram bastante esparsas (mais zeros do que outros valores), então a maioria delas acabava sendo inútil, mas algumas com bastante frequência possuíam valor preditivo.

Solução Final

Após ficar satisfeito com o desempenho de meu melhor modelo individual, decidi partir para o ensemble. Ou seja, criar vários modelos que capturem padrões diferentes nos dados e se complementem.

Dentre os 15 modelos que compuseram a solução final, dois se destacaram: Gradient Boosted Trees (na implementação do XGBoost) e Redes Neurais (usando Keras).

Gradient Boosted Trees – XGBoost

Quem está acostumado com o Kaggle sabe que boa parte das soluções de competições envolvem Boosted Trees. Este modelo é bastante poderoso por si só, mas a implementação paralela no pacote XGBoost tem se mostrado muito boa para resolver tarefas de machine learning em geral.

Neste caso, meu melhor modelo “individual” foi um destes, e finalizou em 18º lugar. Ainda tinha bastante coisa que poderia ser melhorada nele, mas como eu tinha apenas uma semana para o fim da competição, e sabia que sem um ensemble ficaria bem mais difícil vencer, decidi encerrar a otimização neste ponto.

Redes Neurais

Um modelo que encontra boas soluções, mas usando uma estratégia diferente das árvores de decisão é uma rede neural. Apesar dela não ter demonstrado uma performance comparável à do XGBoost, foi um ótimo complemento a ele no ensemble.

Usei a biblioteca Keras, que é fantástica para criar redes neurais em Python.

Testei normalizar os dados com todos os transformadores do scikit-learn, mas o melhor foi o padrão (StandardScaler), que subtrai a média e divide pelo desvio padrão. Além disso usei duas camadas de neurônios e o otimizador Adam. Para encontrar uma boa arquitetura, coeficiente de dropout e parâmetros em geral, fiz uma busca aleatória.

Redes neurais parecem bem sensíveis a “tudo”, então acredito que existam combinações diferentes de arquitetura, normalização de dados e codificação de variáveis que dariam resultados iguais ou melhores.

Um dos motivos pelos quais ela também não ficou tão boa foi porque eu não criei um conjunto de variáveis específico para a rede neural. Usei o mesmo conjunto de variáveis das árvores. Se este fosse meu modelo principal, eu teria feito diferente, mas como era apenas um componente do ensemble, esta maneira já resolvia.

Conclusão

Este foi um breve resumo de 3 semanas de trabalho nesta competição.

Meu plano inicial era criar apenas um modelo e ver até onde dava pra chegar, mas ao ver que o modelo tinha ficado bom, decidi tentar alcançar a minha primeira vitória individual.

Um detalhe interessante é que enviei a solução vencedora meia hora antes do fim da competição, após testar algumas últimas ideias.

  1. Paulo Silveira

    Cai no seu site e artigos só agora. Parabéns pelo resultado *impressionante* no kaggle e por compartilhar os desafios com a gente

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *