fbpx

Categoria: Classificação

Será Que Seu Cliente Vai Te Pagar? Usando Machine Learning Para Prever Inadimplência

Uma das áreas mais perturbadoras para os empresários, sejam grandes ou pequenos, é a inadimplência de alguns clientes. Principalmente num cenário de crise, esta é uma parte que deve ser bem gerenciada pelos administradores do negócio, ou pode levar o mesmo à falência.

Imagine conseguir saber quais clientes vão deixar de pagar apenas observando o comportamento e as características de seus perfis. Tendo esta informação, o gestor pode ajustar seu risco, implementar ações e focar os seus esforços nos clientes com maior chance de causar problemas.

É aí que entra o Machine Learning. Neste artigo quero exemplificar a criação de um sistema simples para prever quais clientes de uma operadora de cartão de crédito deixarão de pagar a fatura do próximo mês.

Estes dados estão disponíveis originalmente no link: https://archive.ics.uci.edu/ml/datasets/default+of+credit+card+clients

Este é o link para o Jupyter Notebook com o código. Ele usa scikit-learn, numpy e XGBoost.

O Banco de Dados

Este banco de dados, em formato XLS, possui 30 mil registros, e já contém várias informações prontas para aplicarmos Machine Learning. Num caso real, é parte do trabalho do cientista de dados extrair as variáveis que possam ser importantes para a previsão. Aqui este trabalho está basicamente feito para nós.

Temos dados do perfil, como idade, sexo e escolaridade, e também dados comportamentais, como os registros de pagamentos dos últimos meses. Apesar de dados do perfil influenciarem, normalmente estes dados comportamentais que podem ser extraídos de bancos de dados transacionais acabam dando origem às variáveis mais importantes.

Como Saberemos se o Modelo Funciona?

Claro que, na prática, o mais importante é saber se este modelo diminui o prejuízo da empresa. Em alguns casos é possível extrair esta informação dos dados históricos e usar como métrica de avaliação do modelo mas, nestes dados, não temos essa possibilidade. Além disso, é importante avaliar o modelo de vários ângulos diferentes.

Neste caso, escolhi a métrica ROC AUC. Estou assumindo que o objetivo da empresa é saber quais são os clientes com maior chance de inadimplência para poder tomar atitudes específicas (como ligar cobrando ou enviar uma carta) que procurem aumentar a chance dele pagar.

Então, esta métrica, que vai de 0,5 a 1, é melhor quando os clientes que realmente são inadimplentes na amostra recebem uma pontuação prevista maior do que aqueles que pagaram no prazo. Ou seja, em vez de nos preocuparmos em acertar a probabilidade de um cliente pagar ou não, queremos apenas que os inadimplentes sejam rankeados com uma pontuação maior do que os adimplentes.

Fora isso, a decisão de como separar os dados entre treino e teste é muito importante. Originalmente estes são divididos entre usuários. Ou seja, treinamos num grupo de usuários diferentes daqueles que vamos prever, mas todos no mesmo período de tempo.

Na prática eu gosto de levar em conta a característica temporal da tarefa, pois esta maneira é mais próxima do modo como o modelo será usado em produção: separar um período anterior como treino e um posterior como teste. Desta maneira conseguiríamos saber, por exemplo, como o modelo reage a diferentes cenários econômicos e épocas do ano.

Seria possível transformar estes dados de maneira que estivessem em ordem cronológica, e prevermos outras variáveis, mas neste artigo vou me focar na parte de machine learning, então quero apenas deixar este comentário sobre este detalhe.

Precisei somar 2 aos valores de algumas variáveis categóricas porque o OneHotEncoder do scikit-learn não funciona com números negativos, mas isso não afetará nossa previsão.

É importante dividir os dados entre treino e teste antes de começarmos, e só usar o teste quando tivermos o modelo final. Toda a nossa validação será feita utilizando os dados originalmente separados para treino. Assim teremos um conjunto de dados não utilizados durante a criação do modelo que poderá nos dar uma estimativa confiável da performance.

Um Modelo Simples como Base

Para estabelecer uma base, e ver se modelos mais complexos apresentam alguma melhora significativa, vou criar um dos modelos mais simples: a regressão logística.

Para isso, preciso codificar as variáveis categóricas (como sexo, escolaridade, estado civil) com one hot. Isso simplesmente significa que cada valor da variável se tornará uma coluna com o valor igual a um, caso aquele valor esteja presente naquele exemplo, e zero, caso negativo.

Notem que usei o argumento “class_weight” como “balanced”. Como temos menos exemplos da classe positiva, o modelo pode dar mais importância à classe negativa, mas este argumento faz com que o modelo aplique uma penalidade proporcional ao número de exemplos da classe aos erros, tornando-as equivalentes neste sentido.

A regressão logística fica com AUC por volta de 0,73.

Aplicando Modelos mais Complexos

A regressão logística é um modelo bem simples, capaz de capturar padrões lineares. Mas e se tivermos interações não-lineares entre nossas variáveis? Um exemplo seria pessoas abaixo de 25 anos e solteiras oferecerem um risco diferente de inadimplência. Se quisermos que uma regressão logística capture este padrão, precisamos criar uma variável específica.

Uma outra maneira de capturar estes padrões é usar um modelo não-linear, com maior capacidade de capturar padrões complexos. Neste caso, escolhi um modelo que, em geral, sem muitos ajustes, já dá uma boa performance: a Random Forest.

A Random Forest é um conjunto de árvores de decisão criadas em partes aleatórias dos dados e das variáveis. Cada árvore é um modelo fraco, mas quando calculamos a média das previsões, elas se tornam um modelo poderoso.

Uma das vantagens de árvores é que, em casos de variáveis categóricas que não possuem alta cardinalidade (muitos valores diferentes), elas conseguem aproximar padrões sem que precisemos transformar para one-hot, podemos usar o formato ordinal. Então, vamos usar o formato original destas variáveis.

A Random Forest com os parâmetros padrões do Scikit-learn atingiu AUC 0,763. Após ajustar os parâmetros, consegui chegar a um AUC de 0,777. Parece uma melhora pequena, mas quando falamos de milhares de exemplos, isso faz diferença.

É Possível Melhorar Mais?

Um dos modelos mais poderosos de conjuntos de árvores é o Gradient Boosted Trees. Em vez de criar várias árvores aleatoriamente, este modelo cria árvores dando mais peso a exemplos nos quais o conjunto atual comete mais erros.

Ele é mais complicado de usar do que a Random Forest, então temos mais parâmetros para ajustar. Mas quando bem utilizado, costuma apresentar uma performance melhor.

A implementação que utilizei foi o XGBoost, que junto com o LightGBM, são ferramentas de código aberto, que rodam em paralelo, escalam para grandes volumes de dados, e oferecem uma performance muito boa.

Este modelo, com os parâmetros originais, atingiu o AUC de 0,775. Após ajustar os parâmetros o AUC foi para 0,781.

Verificando os Resultados nos Dados de Teste

Agora que temos nosso modelo final, o Gradient Boosted Trees com os parâmetros que atingiram o melhor resultado em nossa validação cruzada, vamos treinar este modelo em todos os nossos dados de treino e ver qual a performance nos dados de teste, que não utilizamos até agora.

Caso os resultados sejam muito diferentes, é sinal que fizemos algo errado durante a construção do modelo, ou que os padrões presentes no treino não são tão fortes no teste.

No teste temos o AUC 0,789, bastante próximo do que vimos na validação cruzada, o que é um forte indicador que nosso modelo vai funcionar para novos dados, desde que eles sejam distribuídos da mesma maneira como separamos treino e teste.

Nenhum modelo é perfeito, mas o mais interessante é que, se olharmos os 100 exemplos do teste com maior pontuação indicando inadimplência, 83 deles realmente não fizeram o pagamento. Se olharmos os 100 exemplos com menor pontuação, apenas 3 foram inadimplentes.

Os Próximos Passos

Neste artigo iniciamos com um modelo simples de regressão logística, e avançamos até o modelo mais complexo, que ofereceu uma melhora significativa. Estes modelos poderiam ser usados em produção sem grandes esforços de engenharia.

Para melhorar, seria interessante ter acesso a mais usuários e a outros bancos de dados dos quais pudéssemos extrair mais variáveis que podem ser importantes. Além disso, se os recursos computacionais disponíveis suportarem, podemos criar vários modelos e usar o conjunto de suas previsões, atingindo uma performance ainda melhor.

Mas este é um ponto de partida.

Detectando Anúncios Duplicados com Machine Learning

Em sites de classificados online é comum ver pessoas postando anúncios quase idênticos, mudando apenas uma palavra, ou as fotos, para tentar fazer com que mais usuários vejam o anúncio e respondam.

Isso acaba sendo um problema para quem quer encontrar o melhor negócio, já que precisa tolerar vários anúncios irrelevantes até encontrar o que deseja. Uma das soluções é ter uma equipe de revisores para avaliar os anúncios, mas dá pra perceber que os custos se tornariam altos ao recebermos milhões de anúncios por dia.

Esta é uma tarefa na qual machine learning pode ajudar.

A Avito é uma empresa de classificados online bastante popular no Leste Europeu. Não é a primeira vez que eles oferecem competições no Kaggle, e desta vez querem um sistema que possa detectar anúncios duplicados automaticamente.

O time do qual fiz parte terminou no terceiro lugar, e este artigo trata de um modelo que seria o bastante para terminar em 15º lugar.

Quais eram os dados disponíveis

Os arquivos principais eram aqueles que descreviam os pares de anúncios. Continham o ID dos dois anúncios, um indicador sobre a duplicidade do par, e o método utilizado para gerar aquele indicador. Esta terceira variável só estava disponível nos dados de treino.

Três métodos foram usados para gerar os dados: em um deles uma pessoa avaliava um par de anúncios e determinava se era duplicado, em outro um sistema automaticamente determinava se os anúncios eram duplicados, e no terceiro, uma pessoa avaliava manualmente os anúncios, mas apenas de um usuário.

No geral, pouco mais de 40% dos pares eram anúncios duplicados.

Como dados auxiliares tínhamos: informações textuais como título, descrição, atributos do anúncio (marca do carro, em anúncios de automóveis, por exemplo). Informações visuais, que eram as imagens dos anúncios. E informações gerais, como a categoria do anúncio, a latitude e longitude de onde foi postado, um indicador da região geográfica, e a estação de metrô mais próxima.

Os pares totalizavam mais de 4 milhões de linhas.

Como modelar esta tarefa

Uma das habilidades mais importantes do cientista de dados é saber traduzir uma tarefa para o mundo do machine learning. Neste caso, como estamos tratando de detectar duplicações, mais importante do que o conteúdo dos anúncios, é a similaridade entre as características do par.

Similaridade entre imagens

Mais importante que o próprio texto dos anúncios eram as imagens. Dá pra raciocinarmos o seguinte: mudar uma ou outra palavra do texto para não dar a impressão de anúncio duplicado é fácil, mas poucas pessoas vão ter o trabalho de tirar fotos diferentes, então a tendência é que as fotos de anúncios duplicados sejam as mesmas, ou muito parecidas.

Tive a oportunidade de aprender uma nova maneira de calcular a similaridade entre imagens, que é simples e tem um resultado muito bom. Basicamente consiste em reduzir a imagem, computar uma função sobre os pixels da imagem redimensionada e determinar bits para representar o valor da função computada.

Mais informações sobre estes métodos podem ser encontradas neste link: http://www.hackerfactor.com/blog/?/archives/529-Kind-of-Like-That.html

Similaridade entre o texto dos anúncios

Apesar de importantes, estas variáveis não são tão boas quanto a similaridade das imagens, já que é fácil modificar apenas algumas palavras e ter anúncios diferentes.

Nós não tínhamos informações temporais, como o momento em que o anúncio foi feito, então haviam anúncios que tinham o mesmo texto e não eram duplicados, e imagino que seja o caso de anúncios que expiraram e foram colocados novamente.

Um passo importante foi fazer a limpeza dos dados: transformar o texto para caixa baixa, remover a pontuação e fazer stemming.

Algumas métricas de similaridade funcionaram bem, como a Jaccard e a Cosine.

Similaridade entre outros campos

Também criei variáveis para indicar se outros campos, como o da localização, possuíam o mesmo valor. Algumas destas acabaram ajudando, mas foram minoria.

Como tínhamos latitude e longitude, calculei a distância geográfica entre os anúncios, que acabou contribuindo com o modelo.

Além disso, diferenças entre o preço dos dois produtos foram importantes. Não apenas comparar se o preço era exatamente igual, mas qual a magnitude da diferença.

Outras variáveis

Variáveis como o preço de um produto e a região onde ele se encontra também foram importantes. Uma das razões pode ser que em cidades maiores, ou bairros com maior população, exista uma probabilidade maior de duplicação.

O modelo utilizado

Apesar da solução final do time contar com vários modelos, eu foquei em criar apenas um que fosse capaz de capturar os padrões através das variáveis sobre os dados originais.

Este modelo foi um Gradient Boosted Trees, com a implementação do XGBoost. Um detalhe é que usei uma quantidade baixa de árvores, mas com uma profundidade grande em cada árvore, o que ajudou a capturar os padrões. Acredito que uma quantidade maior de árvores, e mais superficiais, teria atingido uma performance melhor.

Este modelo, por si só, terminaria em 15º lugar.

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.

Usando Machine Learning Para Prever o Primeiro Destino de 60 mil Usuários da AirBnB

A AirBnB é uma empresa de tecnologia que oferece um ambiente virtual onde os usuários podem reservar locais para se hospedar em diversos lugares do mundo, e também anunciar seus imóveis para a comunidade de viajantes.

Visando encontrar candidatos para compor seu time de cientistas de dados, eles decidiram patrocinar uma competição na qual o objetivo era prever qual o primeiro país em que um novo usuário fará sua reserva de hospedagem.

A parte mais interessante, para mim, desta competição, é que foi possível chegar a uma boa posição (Top 7%) com apenas um modelo, que poderia ser utilizado em produção.

Informações dos Dados

Foram disponibilizados dados anônimos sobre o perfil do usuário, como idade, gênero, data de criação da conta, idioma, e o canal utilizado para chegar ao site. Estes dados vinham desde 2010.

Além disso, dados sobre as sessões dos usuários, identificadas pelo id, que descreviam qual a ação executada (clique, mensagem para o dono do imóvel, visualização de resultados de busca, etc) e quantos segundos se passaram entre aquela ação e a anterior. Estes dados existiam apenas a partir de janeiro de 2014.

Existiam outros dois arquivos, com dados sobre as características dos países, mas não encontrei utilidade para eles.

A métrica utilizada era o NDCG. Basicamente vai de 0 a 1 e mede a relevância dos resultados do modelo ordenados pelo ranking. Mais informações neste link (em inglês).

Um usuário poderia escolher entre 12 países, mas quase 90% deles ia para os Estados Unidos ou não marcava viagem.

Logo ficou claro, devido à métrica utilizada, que valeria mais a pena se concentrar em modelar o fato do usuário marcar ou não a viagem, ficando o destino em segundo plano.

Validação

Os dados disponibilizados para treino e validação se referiam a usuários que criaram suas contas antes de 1 de julho de 2014. E os dados de teste, na leaderboard, eram os três meses após esta data.

Vários participantes utilizaram validação cruzada, sorteando aleatoriamente os exemplos, mas por uma questão de tempo para rodar o modelo durante o desenvolvimento, e também porque as características dos dados dependiam do tempo, decidi usar dados de maio e junho de 2014 como validação.

Algumas características dos dados, como a proporção de usuários que possuíam dados sobre sessões mudavam com o tempo, então decidi usar um período bastante próximo do teste para validar. E este período se mostrou bastante robusto.

Variáveis

Perfil do Usuário

Utilizei as variáveis padrões do perfil do usuário já descritas acima. Como eu planejava usar um modelo baseado em árvores de decisão, transformei cada variável categórica em ordinal, pois estes modelos conseguem aproximar bem os padrões mesmo que estas variáveis não tenham um ordenamento real.

Além disso, procurei computar o tempo que um usuário gastava entre eventos das sessões, e quantos tipos diferentes de ações ele executou no site.

Datas

Extraí informações básicas das datas, criando colunas individuais para o dia, mês, ano e dia da semana. Isto ajuda o modelo a capturar efeitos sazonais da série temporal.

Comportamento do Usuário

Esta é uma parte essencial na maior parte dos modelos para e-commerce. Neste caso específico, criei uma coluna com o tempo que o usuário passou em cada ação executada por ele no site. Além disso, fiz o mesmo procedimento calculando quantas vezes o usuário executou uma determinada ação.

Modelo

Utilizei uma Random Forest com 300 árvores que foi boa o bastante para conquistar um lugar no Top 7% (91 de 1463).

No total eu tinha cerca de 1100 colunas, e a maior parte delas bastante esparsa (a maioria dos valores eram zero). Algumas pessoas dizem que estes modelos baseados em árvores de decisão não se dão bem com este formato de dados, mas a minha experiência sugere que é uma questão de ajuste de parâmetros.

Infelizmente não tive tempo de treinar um bom modelo de Gradient Boosted Trees, que certamente teria uma performance melhor, e pela diferença pequena de scores entre a Random Forest e o topo da tabela, é quase certo que seria bom o bastante para pegar um lugar no Top 10.

Classificando Visitas de Clientes ao Walmart em 37 Categorias Usando Machine Learning

Definir qual é a intenção de um cliente ao visitar uma loja, seja ela real ou virtual, pode ajudar uma empresa a oferecer uma experiência personalizada. Por isso é importante utilizar ferramentas que possam ajudar a categorizar e identificar estas visitas.

O Walmart disponibilizou dados anônimos sobre viagens de seus clientes a algumas de suas lojas. Dentre os dados estavam os itens comprados e suas respectivas quantidades, além de informações descritivas sobre os produtos e o dia da semana. A tarefa era utilizar estas informações para categorizar a intenção da visita daquele cliente.

Apesar de não nos dar uma tabela de referência para cada código, os organizadores informaram que alguns exemplos de tipos de visitas são: compra semanal de alimentos, presentes para uma data comemorativa, ou compra de roupas para a nova estação.

Dados

Existiam cerca de 95 mil viagens para treino, e 95 mil para teste. O arquivo disponibilizado continha basicamente uma linha para cada produto comprado durante uma visita, e era responsabilidade do competidor transformá-lo adequadamente para unir os dados de cada viagem.

Para definir os produtos existiam: o código UPC, com mais de 100 mil tipos diferentes; o código Fineline, que é uma categoria refinada criada pelo Walmart, com cerca de 5 mil tipos; e o Departamento, que continha cerca de 60 tipos diferentes.

Além disso, a variável ScanCount definia quantos produtos daquele tipo foram comprados, sendo que, se fosse um número negativo, o cliente estava devolvendo os produtos.

Transformando as Variáveis

Para conseguir treinar um modelo era necessário, no mínimo, fazer o agrupamento das informações básicas por viagem. Vários relatos no fórum falavam de transformações resultando em milhares de variáveis.

Como eu tive apenas 7 dias para trabalhar nesta competição, meu objetivo foi criar um modelo simples e compacto, com o menor número de variáveis possível, mas com um bom desempenho para me posicionar entre os 10% melhores.

Variáveis Básicas

Dentre as variáveis básicas, baseadas em estatísticas de cada visita, alguns exemplos são: a média da quantidade de cada produto comprado na visita, indicação se houve devolução de produto, soma da quantidade de todos os produtos comprados e o departamento com maior quantidade de itens comprados.

Contagens e Proporções

Decidi agregar a quantidade de produtos comprados por departamento, e usar tanto a contagem de cada departamento, quanto a proporção que este departamento ocupava nesta compra. Isso criou cerca de 120 variáveis.

SVD + TF-IDF

Para tentar utilizar o Fineline e o UPC sem aumentar muito o número de variáveis, decidi somar a quantidade de produtos em cada um deles e fazer duas transformações.

Primeiro a TF-IDF, que substitui as quantidade por pesos relativos à proporção de um item naquela viagem, e quão frequente é a presença deste item em outras viagens.

Depois, apliquei o SVD, que tenta encontrar as direções que possuem maior variação nos dados.

Estas transformações normalmente são usada com texto, aplicadas à contagem de palavras em cada documento, e conhecidas como Latent Semantic Analysis.

Além de reduzir a dimensão dos dados, espera-se que ela descarte boa parte do ruído, e encontre categorias estruturais às quais as viagens pertencem.

Na prática isso ajudou bastante com o Fineline, mas não ajudou muito com o UPC.

Regressão Logística L1 e L2

Uma outra maneira de reduzir a dimensão é treinar um modelo mais simples e usar as previsões do mesmo como variável no modelo principal.

Para isso, treinei duas regressões logísticas: uma com penalidade L2 e outra com penalidade L1. Isso gerou cerca de 70 variáveis, 37 para cada regressão, com a probabilidade de um exemplo pertencer a cada classe.

Modelos

Gradient Boosted Trees – XGBoost

A maior parte do meu tempo foi focada em construir um bom modelo com o XGBoost. Este modelo já era bom o bastante para ficar nos 10% melhores.

Redes Neurais

Para complementar o XGBoost, e tentar uma posição melhor, decidi treinar uma rede neural nas mesmas variáveis. Ela possuía 2 camadas ocultas e dropout.

Outros competidores reportaram bons resultados com redes neurais, mas não passei muito tempo mexendo com elas. A intenção era apenas conseguir uma leve melhora sobre o resultado do XGBoost.

Resultado e Melhorias Possíveis

A solução final foi um ensemble simples entre uma rede neural e o XGBoost, que foi o bastante para garantir uma posição entre as 7% melhores. Ela foi obtida em cerca de 8 horas de trabalho.

Utilizando apenas o modelo GBT, sem ensemble, era possível ficar no Top 9%. Este modelo tinha apenas 200 variáveis. A maioria dos modelos melhores tinham mais de 5000 variáveis. Certamente aumentando o número de variáveis este modelo poderia ficar acima do Top 5%, mas ia demorar muito para treinar.

Outras possíveis melhorias seriam: aumentar o ensemble, criar um conjunto de variáveis otimizado para a rede neural, utilizar diretamente as variáveis com contagens do Fineline, tunar melhor os parâmetros das regressões logísticas e das transformações.

Determinando a Relevância de Produtos num Sistema de Buscas Usando Machine Learning

Uma das tarefas mais importantes para um site de comércio eletrônico é garantir que o usuário encontre o produto que está buscando. Isto inclui um sistema de buscas que retorne produtos relevantes. Nesta competição a CrowdFlower, uma plataforma especializada na coleta de dados, disponibilizou dados sobre buscas por produtos, e a mediana da pontuação de relevância dada por três pessoas para cada um.

O objetivo era criar um modelo que pudesse calcular a relevância de um produto para uma determinada frase de busca.

Dados

Foram disponibilizados cerca de 10 mil registros para treino, e 20 mil para teste. Nestes registros havia a frase de busca, o título e a descrição do produto, a mediana da relevância e a variância da relevância atribuída pelos usuários.

Nos exemplos de teste havia uma parcela de registros “falsos”, que não eram utilizados para calcular a performance, mas foram colocados para evitar classificação humana.

Métrica

A métrica de avaliação foi o Quadratic Weighted Kappa. Ela mede a discordância entre dois “avaliadores”. Neste caso, media a discordância entre a pontuação dada pelo modelo e a pontuação dada por humanos. Ela é sensível não apenas à precisão, mas também à distribuição da pontuação. Em tese, uma atribuição de pontos aleatória resultaria num Kappa de 0, e uma concordância total, em Kappa igual a 1. Existem casos em que o Kappa se torna negativo.

Processamento dos Dados

Texto

A transformação básica do texto do título e da descrição é fazer uma bag of words. Em sua forma mais simples é uma matriz cujas linhas são os exemplos (documentos) e as colunas são palavras (termos). O valor de cada célula é o número de vezes que a palavra aparece naquele documento.

Uma transformação mais avançada é pegar esta matriz e fazer algumas operações utilizando a frequência dos termos em cada documento, e também no corpo de documentos. Esta é chamada de TF-IDF. Neste caso, os termos com maior presença dentro de um documento específico recebem um peso maior, mas que é descontado por um fator inversamente proporcional à presença dele em outros documentos.

Simplificando: palavras raras que aparecem bastante em um documento recebem maior peso, e palavras frequentes, que aparecem em todos os documentos, se tornam menos importantes.

Fora isso, é importante determinar um limite mínimo de vezes que a palavra deve aparecer no corpo de documentos, para diminuir o ruído.

Neste caso, a melhor representação que encontrei foi treinar uma matriz baseada apenas nos títulos, e outra apenas na descrição. Alguns participantes sugeriram treinar nos dois ao mesmo tempo, mas como se trata de um problema de busca, acredito que seja importante diferenciá-los.

SVD – Latent Semantic Analysis

Como você já deve imaginar, a matriz de documentos fica enorme, e é bastante esparsa. Isso dificulta o uso com alguns modelos não lineares. Neste caso existe a opção de reduzir a dimensionalidade usando o SVD. Este algoritmo de álgebra linear basicamente encontra os componentes principais da matriz de documentos, onde há maior variação. Isso reduz a dimensionalidade do problema, mas pode causar perda de informação.

Nestes casos de NLP eles acabam funcionando bem, porque filtram um pouco o ruído dos dados. Aplicar o SVD à matriz TF-IDF te dá uma representação conhecida como Latent Semantic Analysis. A interpretação é que cada componente obtido se refere a um “conceito” representado pelos documentos. Ele também é bastante usado para visualizar os documentos num espaço com menores dimensões.

Na competição utilizei SVD para reduzir a dimensionalidade da matriz TF-IDF antes de alimentá-la aos modelos.

Outros atributos

Além dos atributos acima, criei outros que pareciam fazer sentido, e alguns que encontrei em trabalhos acadêmicos relacionados. O mais importante deles foi a porcentagem de palavras da busca que estavam no título. Além disso, criei atributos baseados na similaridade média entre um produto e os outros da mesma frase de busca, e também a contagem de vezes que a frase de busca aparece nos dados.

Uma parte importante foi codificar cada frase de busca com one hot. Ou seja, para cada uma delas criei uma coluna que possuía o número 1 caso ela estivesse presente, e o número 0 caso contrário. Fiz isso para ajudar o modelo a capturar variações próprias de cada frase de busca.

Regressão ou Classificação?

Existiam duas maneiras de encarar essa tarefa: considerar cada nível da mediana como uma classe, sendo assim um problema de classificação, ou considerar como um problema de regressão e arredondar as previsões.

Num primeiro momento achei mais adequado tratar como regressão, mas como o Kaggle queria as previsões arredondadas, acabei criando vários modelos de classificação e não tive tempo de testar a outra alternativa.

Com a classificação temos duas opções: usar a classe prevista pelo modelo, ou utilizar as probabilidades de cada classe para fazer uma média ponderada para obter a pontuação. A primeira alternativa se mostrou melhor neste caso.

Modelos

SVM

Os melhores modelos foram obtidos usando SVM. Ele é um dos modelos mais utilizados para classificação de texto, então não é surpresa que tenha boa performance.

O melhor modelo individual, utilizando os componentes do SVD e os outros atributos, atingia um Kappa de 0,672 na validação cruzada, e 0,654 na LB.

Random Forest

Um dos meus modelos preferidos, mas normalmente não são a melhor opção para dados esparsos em altas dimensões. De qualquer maneira, após vários testes, consegui 0,6695 na validação cruzada, e 0,6508 na LB usando uma matriz TF-IDF junto com os outros atributos.

Uma razão que pode ajudar a explicar o fato dela se comportar melhor com a matriz TF-IDF do que com a matriz do SVD é que este é um método que reduz a variância, então acaba sendo robusto diante de muitos atributos, desde que uma parcela deles seja relevante para a previsão.

Gradient Boosted Trees

Apesar deste modelo também não ser muito recomendado para uso com dados esparsos em altas dimensões, decidi treinar vários modelos “fracos” e fazer o stacking usando as previsões deles e os outros atributos, que não envolviam a representação das palavras.

Os modelos do stacking tinham uma média de 0,60 Kappa na validação.

A pontuação deste modelo ficava por volta de 0,624 na LB. Mas a intenção real com ele era ter um modelo diferente do SVM para fazer o ensemble.

Pós Processamento

Em todas as soluções de Top 10 que eu li, havia um detalhe em comum: o pós processamento das previsões. As equipes usaram métodos especiais para fazer arredondamento, multiplicando por coeficientes, ou determinando pontos de corte diferentes para a previsão de cada “classe”.

Em um dos casos, o time ordenou as previsões e, de acordo com a proporção de cada classe nos dados de treino, arredondou as previsões. Kappa é uma métrica preocupada com a distribuição das previsões, então isso acabou ajudando.

O mais próximo que cheguei de um ajuste nas previsões foi utilizar a média geométrica em vez da média simples para o ensemble. Os modelos costumavam superestimar a relevância dos produtos, e ela puxava as previsões para a classes mais baixas.

Ensemble e Overfitting

No fim, fiz uma média geométrica com meus melhores modelos. O ensemble era formado por vários SVMs, uma Random Forest e um GBT.

Apesar de não ser uma prática recomendável, principalmente em casos com uma quantidade de dados pequena como esta competição, utilizei a LB para validar o ensemble. Isso acabou fazendo com que eu escolhesse o meu segundo melhor ensemble, mesmo assim acabei no Top 5% da competição.

Como Consegui o 6º lugar Entre 985 Participantes na Competição de Machine Learning do Facebook

Nesta competição de recrutamento, o Facebook disponibilizou dados anônimos sobre usuários de um site de leilões. A tarefa era criar um modelo que determinasse qual usuário é humano e qual é robô. Os usuários humanos de um site de leilões não querem ter que competir com máquinas. Um site com muitos robôs pode sofrer um êxodo de usuários humanos, então é importante ter um sistema robusto de detecção de usuários suspeitos.

Segundo os administradores, estes dados não vinham do Facebook, mas acredito que o interesse era criar um ambiente parecido com o sistema de leilões de publicidade deles.

Esta competição tornou-se bastante especial para mim, já que foi meu primeiro Top 10 no Kaggle, e acabei me tornando Kaggle Master. Este era meu primeiro objetivo, mas também tem o bônus de ser considerado para uma entrevista para a vaga de Data Scientist no Facebook.

Dados

Uma das partes mais interessantes desta competição, e que a tornava bastante próxima de um caso real de ciência de dados, era o fato de não termos um arquivo bruto com variáveis relacionadas ao objetivo, que pudesse ser utilizado diretamente com algum modelo de machine learning.

Foram disponibilizados dois arquivos: o primeiro continha cerca de 2000 linhas com informações anônimas dos usuários, como ID e endereço. O segundo continha mais de 7 milhões de lances dados no site.

As variáveis do segundo arquivo incluíam: ID do lance, ID do usuário, ID do leilão, categoria do leilão, dispositivo do usuário, tempo, país, IP e URL. O tempo estava codificado, de maneira que não era possível descobrir a data exata dos lances.

Validação

Ter um ambiente de validação robusto é essencial para qualquer tarefa de machine learning. Neste caso havia o desafio de termos apenas 2000 exemplos para treinar e validar. Após testar várias combinações, decidi utilizar uma validação cruzada estratificada, com 5 divisões, e repetir essa validação por 5 vezes.

A repetição acaba fazendo com que os exemplos sejam colocados, aleatoriamente, em divisões diferentes, o que ajuda a estabilizar a estimativa de erro, já que um ou outro exemplo particular terá menor influência na média.

Criação de variáveis

A parte mais importante de qualquer trabalho de ciência de dados é garantir que você tenha os dados corretos para fazer a previsão. Um erro muito comum é achar que é só colocar os dados no algoritmo, e ele vai aprender o que você quiser. Esta foi, de longe, a parte mais importante da minha solução.

Contagens

O primeiro passo foi criar variáveis com a contagem de quantos lances cada usuário tinha feito. Estendendo esta ideia, criei variáveis para a contagem de quantos dispositivos, IPs, categorias, países e URLs diferentes um mesmo usuário tinha utilizado. Com estas variáveis simples, uma Random Forest, sem tuning, atingia 0,86 AUC na validação cruzada com 5 divisões.

No caso dos países, além de integrar a variável própria para contar o número de países diferentes, fiz variáveis individuais para cada um deles, contando quantos lances o usuário tinha dado vindos de cada país. Isto ajuda o modelo a identificar “países de risco”, onde pode ser mais fácil hospedar robôs para dar os lances.

Estatísticas simples

Depois de ver que as contagens possuíam um grande valor informativo, decidi testar estatísticas simples, como médias e desvios. Exemplos de variáveis importantes:

– Média e desvio padrão de lances por leilão;
– Número máximo de lances dados num mesmo leilão, com a mesma timestamp;
– Média de tempo entre lances.

Temporais

A variável que indicava o tempo nos dados originais estava codificada de maneira que não dava para saber a data exata em que os lances ocorreram. Mas, após analisar os dados, percebi que mesmo não sabendo a data exata, era possível identificar o que seriam dias, horas e minutos.

Isto me permitiu criar variáveis relativas a médias, desvios, proporções e contagens baseadas em unidades de tempo. Algumas variáveis importantes:

– Desvio padrão de lances por dia;
– Média de lances por dia;
– Média de lances na mesma timestamp;
– Contagem de lances por hora do dia;
– Média de leilões que o usuário participou no mesmo dia.

Parece que todos os participantes que se posicionaram no topo da tabela conseguiram identificar estes padrões temporais.

Proporções

Algumas proporções baseadas nas contagens e no tempo também foram importantes. Alguns exemplos são:

– Proporção máxima do total de lances em um dia atribuídos ao usuário;
– Densidade de lances por hora do dia

Modelos

Quando você tem variáveis que contêm bastante informação sobre o evento, a parte de seleção e ajuste de parâmetros do modelo se torna mais simples. Neste caso, apesar de testar brevemente modelos lineares, acabei optando por explorar melhor modelos baseados em ensembles de decision trees.

Random Forest

O primeiro modelo que testei, e o que se provou mais útil neste caso.

Acredito que sua performance superior se deu por ser um modelo mais voltado para a diminuição da variância. Por ter poucos dados para treinar, era importante que o modelo conseguisse lidar bem com valores extremos, e estabilizasse as previsões.

Uma Random Forest com 500 árvores e parâmetros ajustados através da validação cruzada apresentava 0,9112 AUC na medição local, e 0,9203 na LB.

Gradient Boosted Trees

Este é um dos modelos mais utilizados em competições de ciência de dados porque reconhecidamente oferece uma boa performance em diversos tipos de tarefas. Neste caso não foi assim. Os meus melhores modelos ficaram por volta dos 0,90-0,91 AUC na LB apesar de alcançarem a 0,94 na validação.

Isso levanta a seguinte questão: temos um modelo baseado em decision trees que nos dá uma boa performance (Random Forest) e, em tese, boosting melhoraria a pontuação, por que este não é o caso aqui?

A minha resposta é: Random Forest aumenta o bias, ou seja, torna o modelo mais rígido, com menor variância, enquanto o GBT aumenta a variância, sendo mais sensível ao ruído presente nos dados. Neste caso temos menos de 2000 exemplos, e um forte desequilíbrio entre as classes. Estas duas razões combinadas geram dados com uma boa quantidade de variância, aumentá-la ainda mais vai causar overfitting.

A solução seria fazer o tuning e regularizar mais o GBT, mas com poucos dados para validar fica difícil confiar nos resultados. Por isso, decidi confiar nas razões teóricas e continuar trabalhando com a Random Forest para ter um modelo mais estável.

Ensembles

O toque final de boa parte das melhores soluções de competições é fazer um ensemble com os melhores modelos.

Ensemble Aleatório

Devido ao pequeno número de exemplos, era difícil confiar nos parâmetros encontrados através da validação. Por isso, decidi criar um ensemble com Random Forests “aleatórias” nos parâmetros. Cada uma delas tinha uma seed e alguns parâmetros diferentes. Este ensemble atingiu uma AUC de 0,92 na estimativa out-of-bag e na LB.

AdaBoost Random Forest

Apesar da instabilidade do GBT, decidi aplicar boosting à Random Forest. Talvez aplicando este método em modelos mais estáveis que as decision trees pudesse ajudar, mesmo com a pequena quantidade de exemplos. E foi isso mesmo que aconteceu.

Na validação obtive 0,925 de AUC, e 0,928 na LB.

Ainda assim havia uma diferença entre a pontuação da validação e da LB quando eu adicionava novas variáveis.

Estabilizando as seeds

Para estabilizar as previsões decidi rodar o modelo de Boosted Random Forest usando várias seeds diferentes, e fazer a média. Isso não deu um bom resultado na LB, mas apresentava estabilidade na validação cruzada, o que me fez confiar no modelo.

Conclusão

No fim, utilizei um modelo de Boosted Random Forest com um conjunto de atributos que apresentava AUC 0,94 na validação cruzada, e foi basicamente minha pontuação no test set final da competição, que me garantiu o 6º lugar.

Classificando 150 Mil Produtos em Categorias Diferentes Usando Machine Learning

O grupo Otto é uma das maiores empresas de comércio online do mundo.
Segundo eles, devido à diversidade da infraestrutura global da empresa, muitos produtos similares acabam classificados em categorias incorretas. Por isso eles disponibilizaram dados sobre aproximadamente 200 mil produtos, pertencentes a nove categorias. O objetivo era criar um modelo probabilístico que classificasse corretamente os produtos dentro de cada categoria.

Dados

Para treino foram disponibilizados cerca de 60 mil produtos e, para teste, cerca de 150 mil.

Os 93 atributos não foram identificados, a única informação dada sobre eles é o fato de serem variáveis numéricas. Isso dificulta bastante para entendermos a melhor maneira de trabalhar com os dados, e também o trabalho de criação de novos atributos.

Além disso, a tarefa envolve múltiplas classes, ou seja, temos que gerar probabilidades sobre 9 classes.

Métrica

A métrica escolhida para esta competição foi a log loss multiclasse.

Esta métrica pune probabilidades muito confiantes em previsões erradas, e é sensível ao desequilíbrio entre as classes. Algumas delas representavam 20% dos dados de treino, enquanto outras, 9%. Neste caso, tentar equilibrar as classes, seja com subsampling ou penalizando as classes menores, não vai ajudar.

Criação de Atributos

Mesmo com as variáveis anônimas, decidi explorar a possibilidade de integrar interações entre elas, que talvez não fossem capturadas pelos modelos.

Básico

Procurei criar novos atributos baseados em somas, subtrações, razões e multiplicações entre os originais. O único atributo que contribuiu significativamente foi a soma de todos os atributos para um determinado produto. A maioria dos atributos era bastante esparsa (tinha mais zeros do que outros valores), então talvez isso tenha contribuído para a falta de sucesso.

Usando um GBM/RF para Selecionar Atributos

Como nós estamos falando de um espaço gigantesco de possibilidades de combinações de atributos, decidi usar uma técnica usada por um participante de outra competição e publicada neste link: http://trevorstephens.com/post/98233123324/armchair-particle-physicist

Basicamente consiste em criar alguns datasets com as interações desejadas e treinar um modelo de Gradient Boosted Trees em cada um. Após isso, verifica-se quais são os atributos mais importantes em cada dataset. A lógica é: se um atributo for importante em vários datasets diferentes, ele deve ser importante no geral.

Eu fiz isso nestes dados, usando 2 divisões estratificadas, mas apesar deles concordarem na importância de algumas interações, elas não melhoraram meu modelo base. Como eu já havia passado dias me dedicando a essa parte, eu decidi que ia me focar mais em ajustar modelos para um ensemble.

Caso eu tivesse continuado, talvez selecionar as X melhores interações e reajustar os hiperparâmetros de algum modelo pudesse extrair valor destas variáveis.

Modelos

Gradient Boosted Trees (XGBoost)

O meu modelo individual, com a menor log loss, foi criado com o XGBoost, que é uma implementação rápida e paralela das poderosas Gradient Boosted Trees. Esse modelo já esteve em soluções vencedoras de boa parte das competições e normalmente apresenta uma performance superior.

Para encontrar o melhor grupo de atributos usei uma busca aleatória. Fixei a learning rate em 0,05 e variei atributos como a profundidade das árvores, mínimo de exemplos que deveriam compor um nó, e a proporção de exemplos e variáveis que o algoritmo deveria selecionar aleatoriamente para criar cada árvore.

Normalmente quanto menor a learning rate, melhor a precisão, mas são necessárias mais árvores, o que aumenta o tempo de treino. No fim, deixei este valor em 0,01 e treinei 2000 árvores.

Estes atributos servem para controlar o overfitting. Após encontrar os melhores atributos, a log loss da validação cruzada em 3 divisões era 0,4656, e na leaderboard, 0,4368.

Random Forests

Apesar de não ter muito sucesso inicial com as Random Forests nesta competição. Uma sugestão dada no fórum foi de calibrar as previsões. Recentemente a equipe desenvolvedora do scikit-learn disponibilizou uma nova ferramenta que nos permite ajustar os valores de saída de um modelo de maneira que eles se tornem mais próximos das probabilidades reais.

Uma Random Forest normalmente faz uma votação entre seus componentes, e a proporção de cada classe é dada como a probabilidade do exemplo pertencer àquela classe. Essas proporções não correspondem às probabilidades reais dos eventos, então só teremos probabilidades relevantes se fizermos o ajuste.

Como esta competição pedia para prevermos a probabilidade de um produto pertencer a uma categoria, era muito importante ter as probabilidades ajustadas corretamente. Neste caso, utilizei a nova ferramenta do scikit-learn com 5 divisões dos dados. Em 4 delas ele treinava a Random Forest e, na restante, uma regressão isotônica. Isso era repetido 5 vezes, e a média da probabilidade das 5 regressões era o resultado.

Isso melhorou bastante a previsão da Random Forest e, apesar de também usarem decision trees, como o GBM, elas acabaram contribuindo significativamente para um ensemble.

Redes Neurais

Tive a oportunidade de conhecer dois módulos ótimos para treinar redes neurais de maneira simples em Python. Eles são Lasagne e NoLearn. Com eles é simples criar e treinar redes neurais de última geração, usando inclusive GPUs para processamento.

Mas não se engane, apesar deles facilitarem a implementação, uma rede neural exige uma grande quantidade de decisões para se tornar útil. Temos que determinar desde a arquitetura, até os métodos de inicialização dos pesos e regularização. Alguns pesquisadores sugerem fazer uma busca aleatória por parâmetros iniciais e continuar a ajustar manualmente.

Neste caso, a arquitetura que me serviu bem foi a seguinte: 3 layers de neurônios com o seguinte número de unidades em cada uma 768-512-256. Eu treinei duas versões, uma delas com dropout de 0.5 entre os hidden layers. E outra com dropout de 0.5 apenas no primeiro hidden layer. As unidades dos hidden layers eram ReLu, e a unidade de saída, softmax.

Uma interpretação interessante, de um outro competidor que chegou a uma arquitetura parecida, é de que o primeiro hidden layer permite que a rede faça projeções aleatórias dos dados, e os layers menores, que vêm após este, buscam uma sintetização das informações. Redes neurais são naturalmente difíceis de interpretar, mas eu achei este um ponto de vista bem interessante.

Por fim, como a mínima de uma rede neural é local, e dependente dos pesos iniciais (e aí temos muitos pesos iniciais), decidi fazer a média de 5 redes neurais com pesos iniciais diferentes. Esta média me deu um score de 0.4390 na LB, comparável ao XGBoost.

Outros

Ainda tentei treinar: SVM, Nearest Neighbors, Regressão Logística, mas nenhum deles apresentava uma boa performance sozinho, ou contribuía significativamente para o ensemble.

Ajustando os Hiperparâmetros

Já que não tínhamos acesso ao conteúdo das variáveis, era vital ajustarmos os parâmetros para termos os melhores modelos. No início fiz uma busca aleatória com valores razoáveis, mas que permitissem aos modelos explorar um pouco o espaço.

Normalmente os hiperparâmetros de um modelo não são independentes, ou seja, ajustar um de cada vez não vai te levar à melhor combinação. Por isso é importante fazer uma exploração com combinações de parâmetros. Infelizmente na maioria das vezes é impossível testar todas as combinações possíveis, mas quando fazemos a busca aleatória podemos ter uma ideia do espaço de soluções, sem ter que explorá-lo exaustivamente.

Depois de feita esta exploração, é bom variar manualmente alguns deles, e ver se é possível melhorar a performance em alguma combinação vizinha.

Resultado

No fim minha solução era formada por uma média ponderada dos seguintes modelos:

– Gradient Boosted Trees (XGBoost) sobre as variáveis originais e a soma das colunas.
– Random Forest sobre as variáveis originais, log(X+1) delas, e soma das colunas.
– Rede neural com 3 layers e dropout de 0.5 em cada um, sobre as variáveis originais, log(X+1) delas, e soma das colunas.
– Rede neural com 3 layers com dropout de 0.5 no primeiro hidden layer, sem dropout no segundo, sobre as variáveis originais, log(X+1) delas, e soma das colunas.

Além desses modelos, adicionei um bias, simplesmente prevendo a proporção de cada classe como probabilidade de um exemplo pertencer a elas.

Esta solução atingiu a log loss de 0,4080, e me garantiu a posição 35 entre 3514 times (Top 1%).

Soluções vencedoras

A solução do time vencedor consistia de um ensemble de três camadas:

Na primeira camada foram treinados 33 modelos diferentes, variando tanto o tipo do modelo, quanto as variáveis utilizadas para treinar cada um. Alguns deles eram múltiplos modelos treinados com bagging.

Na segunda camada, as previsões destes modelos eram utilizadas para alimentar um XGBoost, uma rede neural, e um ExtraTrees com Adaboost.

Na terceira, e última, camada, foi feito o bagging dos três modelos da segunda camada (totalizando 1100 modelos) e depois uma média ponderada entre os três.

Além disso, foram criadas novas variáveis, baseadas na distância do exemplo para os exemplos mais próximos de cada classe, e também outras baseadas em transformações TF-IDF e T-SNE do dataset original.

Usando Machine Learning Para Identificar Motoristas Através de Dados GPS

Nos últimos anos a indústria de seguros tem buscado maneiras de aprimorar seus modelos usando Machine Learning. Uma delas é utilizar dados que vão além de um formulário preenchido pelo segurado para determinar o risco de acidentes.

Um dos métodos utilizados é usar dados comportamentais do motorista, obtidos através de rastreamento via GPS. Desta maneira acredita-se ser possível capturar informações e padrões de perfil que vão além dos métodos tradicionais.

O que torna um motorista diferente do outro? Identificar o motorista que está conduzindo um veículo durante uma viagem é o primeiro passo para conseguirmos um bom modelo.

Descrição da Competição

A seguradora AXA decidiu disponibilizar viagens anônimas de aproximadamente 2700 motoristas. Para cada motorista são 200 viagens, sendo que algumas delas não são do motorista em questão, e a tarefa era criar um modelo que identificasse quais eram as viagens atribuídas a este motorista incorretamente.

Para evitar o vazamento de informações, os dados foram centralizados e rotacionados de maneira aleatória, além de ter alguns trechos das viagens removidos do início e do fim.

Dados

No total foram disponibilizadas 200 viagens de 2736 motoristas. Dentro da pasta de cada motorista havia uma quantidade aleatória de viagens falsamente atribuídas ao motorista em questão. Uma informação importante é que tínhamos a garantia que a maioria das viagens pertencia verdadeiramente ao motorista.

Cada viagem era descrita em um arquivo CSV com um valor numérico para a posição x e y, que seria a distância da origem, em metros, e cada linha correspondia a 1 segundo de viagem.

Métrica

A métrica utilizada para avaliação foi a área sob a curva ROC, abreviada por AUC.

Atributos

Os atributos que utilizei se baseiam principalmente nas três primeiras derivadas do deslocamento: velocidade, aceleração e arrancada. Outras derivadas acabavam por prejudicar a performance.

Frequências – FFT

Procurei extrair frequências, tanto dos dados originais brutos, quanto das derivadas. Fiz isto com a função FFT (Fast Fourier Transform) disponível no Numpy. Dividi o intervalo de frequências por 20, e o valor dos atributos era a soma das frequências do respectivo intervalo.

Neste caso eu extraí as frequências isoladas para cada componente (x e y). Talvez obter as frequências usando a velocidade e aceleração, considerando os dois componentes juntos, seja melhor, mas não testei esta hipótese.

O AUC máximo que consegui, usando frequências das três derivadas, foi 0,7058, com uma regressão logística sem otimização de parâmetros. Apesar de não ter testado, acredito que otimizando os parâmetros do modelo para cada motorista este número poderia ser aumentado até um máximo de 0,73.

Histogramas

Esta é a transformação que encontrei em todos os trabalhos acadêmicos que li sobre o assunto. Consiste em fazer um histograma dos atributos. Neste caso, ganhamos um novo parâmetro, o tamanho dos intervalos.

A melhor maneira que encontrei foi dividir a magnitude da velocidade em 50 intervalos, e a aceleração, em 10.

Dois detalhes que melhoraram a performance: utilizar o atributo “density” da função do Numpy, que calcula a função densidade da probabilidade, e normalizar os resultados para que sua soma fosse 1.

Valores em Porcentagens

Outra sugestão bastante frequente era utilizar o valor dos atributos em determinadas porcentagens. Isso ajuda a descobrir quão extremos são os valores dos mesmos.

Um exemplo pode facilitar o entendimento: no caso da velocidade, para obter o valor presente no percentil 5%, ordenei os valores de maneira crescente e localizei o ponto da lista que representava a fronteira dos primeiros 5% dos dados ordenados.

Se tivermos uma lista com 100 valores de velocidade ordenados, com um índice que vai de 1 a 100, o valor presente na posição 5 seria o desejado. O valor presente na posição 50, por exemplo, seria a mediana.

Isso é importante porque diferencia motoristas que possuem um perfil mais rápido ou mais lento.

Eu utilizei os percentis 5% a 95%, em intervalos de 5%.

Viagens Repetidas

Uma abordagem que foi importante para que os melhores colocados subissem na tabela foi criar um algoritmo para encontrar viagens similares. Era notável que alguns trechos, apesar de possuírem um ângulo diferente com relação à origem, se tratavam do mesmo caminho.

Eu fiz um algoritmo simples, baseado na velocidade do motorista durante a viagem mas, apesar de ter encontrado algumas viagens similares, acabava prejudicando a pontuação.

Os algoritmos que tiveram sucesso eram mais complexos, e em alguns casos envolviam a transformação das variáveis, redução de dimensões, e limites para determinar a similaridade. Dentre todos, dois métodos pareceram essenciais:

Ramer–Douglas–Peucker: como cada viagem tinha uma duração diferente, este método foi utilizado para reduzir, e igualar, o número de pontos.

Dynamic Time Warping: este método foi utilizado para comparar os trechos e determinar a similaridade entre eles.

Amostragem

Como não era possível saber realmente quais eram as viagens de cada motorista. Ou seja, existiam viagens marcadas como positivas, quando na realidade eram negativas, havia a oportunidade de se decidir a melhor maneira de fazer a amostragem dos dados.

Aleatório

Num primeiro momento, com métodos não supervisionados, usei apenas as 200 viagens de cada motorista. Uma sugestão no fórum da competição foi usar as 200 viagens de um motorista como exemplos positivos e usar viagens de outros motoristas como exemplos negativos.

Em geral esta foi uma abordagem bastante positiva. Dentre as maneiras de fazê-la estavam:

– Escolher, aleatoriamente, uma quantidade N de motoristas e tratar todas as viagens deles como negativas.

– Escolher, aleatoriamente, uma quantidade V de viagens de N motoristas diferentes, para usar como negativas.

A abordagem que me serviu melhor foi baseada na segunda opção: selecionar um pequeno número de viagens de vários motoristas diferentes.

Amostras similares/diferentes

Outra alternativa que testei foi selecionar amostras negativas de motoristas que eram mais similares ou diferentes das amostras positivas, de acordo com uma medida como distância euclidiana.

Criei atributos como a média de velocidade, distância percorrida, e duração das viagens para cada motorista e determinei quais eram os mais parecidos, e quais eram os mais diferentes do motorista em questão.

Apesar da abordagem utilizando os motoristas “mais diferentes” mostrar uma melhora durante a validação cruzada, ela não melhorou a pontuação no test set. A razão que atribuo a isto é o fato dos dados da validação conterem viagens que são falsas, mas estão marcadas como verdadeiras, enquanto que no test set temos a realidade absoluta.

Reforçando as previsões

Procurei reforçar as previsões do modelo treinando mais de uma vez, mas agora usando as previsões do modelo anterior no lugar da variável dependente original, da seguinte maneira:

No primeiro passe fiz o procedimento normal, usando as viagens do motorista como positivas, e outras aleatoriamente selecionadas de outros motoristas como negativas para obter previsões.

Nos próximos passes eu utilizei as previsões do modelo anterior como variável dependente. Isso melhorou bastante na validação cruzada, mas apresentou uma melhora muito pequena nos dados de validação do Kaggle.

Modelos e Métodos

Clustering

Num primeiro momento decidi agrupar as viagens de acordo com características similares. Essa ideia foi baseada na suposição que, como a maioria das viagens pertencia ao motorista, seria possível separar as viagens em dois clusters, e aquele que tivesse a minoria das viagens seria o “negativo”.

Outra ideia era aumentar o número de clusters, já que existiam diferenças das viagens entre si, mesmo pertencentes ao mesmo motorista.

De qualquer maneira, estas ideias se mostraram inúteis, com AUC próximo de 0,5.

Outra maneira não supervisionada que tentei foi calcular a similaridade de cosseno entre a média dos atributos das viagens e cada uma delas. Neste caso a “probabilidade” de uma viagem pertencer ao motorista seria um menos este valor. O que também se mostrou inútil.

Regressão Logística

O meu modelo com maior sucesso foi a regressão logística. Isso me surpreendeu, já que o sucesso descrito por vários participantes no fórum envolvia boosted trees.

Utilizei cerca de 1000 exemplos, 200 positivos e 800 negativos. Um detalhe importante foi ativar a opção “class_weight=auto”. Esta opção atribui uma penalidade diferente aos exemplos, de acordo com a distribuição das classes. Neste caso, um erro ou acerto na classe positiva valia mais do que o mesmo na classe negativa. Desta maneira, apesar de termos mais exemplos positivos, a penalização por erros era equilibrada.

Treinei uma regressão para cada motorista, personalizando a penalidade de regularização (L1 ou L2) e o coeficiente da mesma. Cada modelo fazia a validação cruzada de valores para este coeficiente utilizando os dois tipos de regularização, e no fim selecionava o tipo de regularização e o coeficiente que apresentasse a melhor AUC.

O melhor modelo individual atingiu a AUC de 0,86.

Random Forests/Boosted Trees

De acordo com os fóruns, o modelo mais utilizado foi Gradient Boosted Trees. Basicamente ele consiste em treinar uma sequência de decision trees pouco complexas, atribuindo um peso maior a exemplos que foram classificados incorretamente pelas árvores anteriores. A probabilidade de um exemplo específico é uma combinação linear das previsões de cada árvore.

No meu caso as tentativas de utilizar este modelo não foram bem sucedidas. Dois motivos podem ter contribuído:

– Por desconfiança da validação, eu acabei não dando muita atenção à otimização dos parâmetros dele para conseguir uma boa AUC.
– O processo de validação cruzada para encontrar bons parâmetros ia demorar muito, e testes preliminares não haviam apresentado bons resultados.

De qualquer maneira treinei um modelo de Random Forests, com os parâmetros padrões, para utilizar num ensemble. Ele sozinho possuía AUC de 0,847.

Ensemble

Próximo do fim da competição decidi partir para a forma mais “fácil” de se melhorar a pontuação: ensembles.

Para uma parte do ensemble treinei os seguintes modelos:

– Regressão Logística apenas com regularização L1
– Regressão Logística apenas com regularização L2
– SVM RBF
– SVM Linear
– Random Forests

Apesar do SVM não oferecer probabilidades, acreditei que ele seria uma boa contribuição para o ensemble, o que se mostrou verdadeiro.

A média simples da previsão destes modelos produziu uma AUC de 0,8825.

Por fim, juntei estes com todos os outros que já havia produzido desde o início da competição, para uma AUC final de 0,8915. Isto me garantiu uma colocação entre as 15% melhores soluções da competição.

Métodos das soluções vencedoras

As soluções vencedoras usaram 400 exemplos para cada motorista, selecionando viagens aleatórias de outros motoristas como exemplos negativos. O modelo mais comum foi Gradient Boosted Trees. Além disso, algoritmos complexos para encontrar viagens similares e atribui-las diretamente ao motorista conquistaram os pontos finais necessários para se posicionar entre as 10 melhores soluções.

As Métricas Mais Populares para Avaliar Modelos de Machine Learning

Durante o processo de criação de um modelo de machine learning nós precisamos medir a qualidade dele de acordo com o objetivo da tarefa. Existem funções matemáticas que nos ajudam a avaliar a capacidade de erro e acerto dos nossos modelos, e agora você conhecerá algumas das mais utilizadas. No artigo, usarei a palavra métrica para me referir a essas funções.

Tão importante quanto saber escolher um bom modelo, é saber escolher a métrica correta para decidir qual é o melhor entre eles.

Existem métricas mais simples, outras mais complexas, algumas que funcionam melhor para datasets com determinadas características, ou outras personalizadas de acordo com o objetivo final do modelo.

Ao escolher uma métrica deve-se levar em consideração fatores como a proporção de dados de cada classe no dataset e o objetivo da previsão (probabilidade, binário, ranking, etc). Por isso é importante conhecer bem a métrica que será utilizada, já que isso pode fazer a diferença na prática.

Nenhuma destas funções é melhor do que as outras em todos os casos. É sempre importante levar em consideração a aplicação prática do modelo. O objetivo deste artigo não é ir a fundo em cada uma delas, mas apresentá-las para que você possa pesquisar mais sobre as que achar interessante.

Classificação

Estas métricas são utilizadas em tarefas de classificação, e a maioria delas pode ser adaptada tanto para classificação binária quanto de múltiplas classes. Nas tarefas de classificação buscamos prever qual é a categoria a que uma amostra pertence como, por exemplo, determinar se uma mensagem é spam.

Precisão Geral (Accuracy)

Precis\tilde{a}o\ Geral = \frac{P}{P\ +\ N}

Esta é a métrica mais simples. É basicamente o número de acertos (positivos) divido pelo número total de exemplos. Ela deve ser usada em datasets com a mesma proporção de exemplos para cada classe, e quando as penalidades de acerto e erro para cada classe forem as mesmas.

Em problemas com classes desproporcionais, ela causa uma falsa impressão de bom desempenho. Por exemplo, num dataset em que 80% dos exemplos pertençam a uma classe, só de classificar todos os exemplos naquela classe já se atinge uma precisão de 80%, mesmo que todos os exemplos da outra classe estejam classificados incorretamente.

F1 Score

F1 = \frac{2\ *\ precis\tilde{a}o\ *\ recall}{precis\tilde{a}o\ +\ recall}

O F1 Score é uma média harmônica entre precisão (que, apesar de ter o mesmo nome, não é a mesma citada acima) e recall. Veja abaixo as definições destes dois termos.

Ela é muito boa quando você possui um dataset com classes desproporcionais, e o seu modelo não emite probabilidades. Isso não significa que não possa ser usada com modelos que emitem probabilidades, tudo depende do objetivo de sua tarefa de machine learning.

Em geral, quanto maior o F1 score, melhor.

Precisão (Precision)

Precis\tilde{a}o = \frac{PV}{PV\ +\ FP}

Número de exemplos classificados como pertencentes a uma classe, que realmente são daquela classe (positivos verdadeiros), dividido pela soma entre este número, e o número de exemplos classificados nesta classe, mas que pertencem a outras (falsos positivos).

Recall

Recall = \frac{PV}{P}

Número de exemplos classificados como pertencentes a uma classe, que realmente são daquela classe, dividido pela quantidade total de exemplos que pertencem a esta classe, mesmo que sejam classificados em outra. No caso binário, positivos verdadeiros divididos por total de positivos.

AUC – Area Under the ROC Curve

auc
Imagem retirada da documentação do Scikit-Learn

Esta é uma métrica interessante para tarefas com classes desproporcionais. Nela, mede-se a área sob uma curva formada pelo gráfico entre a taxa de exemplos positivos, que realmente são positivos, e a taxa de falsos positivos.

Uma das vantagens em relação ao F1 Score, é que ela mede o desempenho do modelo em vários pontos de corte, não necessariamente atribuindo exemplos com probabilidade maior que 50% para a classe positiva, e menor, para a classe negativa.

Em sistemas que se interessam apenas pela classe, e não pela probabilidade, ela pode ser utilizada para definir o melhor ponto de corte para atribuir uma ou outra classe a um exemplo. Este ponto de corte normalmente é o ponto que se localiza mais à esquerda, e para o alto, no gráfico, mas depende bastante do custo do erro na previsão de uma determinada classe.

Log Loss

Log\ Loss = -\frac{1}{N}\sum_{i=1}^N {(y_i\log(p_i) + (1 - y_i)\log(1 - p_i))}

A fórmula do exemplo é para o caso binário, neste caso: p é a probabilidade do exemplo pertencer à classe 1, e y é o valor real da variável dependente.

Esta função pune previsões incorretas muito confiantes. Por exemplo, prever uma classe com uma probabilidade de 95%, e na realidade a correta ser outra. Ela pode ser utilizada para problemas binários ou com múltiplas classes, mas eu particularmente não gosto de usar em datasets com classes desproporcionais. O valor dela sempre terá a tendência de melhorar se o modelo estiver favorecendo a maior classe presente.

Tomando os cuidados acima, nas situações em que a probabilidade de um exemplo pertencer a uma classe for mais importante do que classificá-lo diretamente, esta função é preferível a usar simplesmente a precisão geral.

Regressão

Neste parte estão as funções mais comuns utilizadas para avaliar o desempenho de modelos de regressão. Na regressão buscamos prever um valor numérico, como, por exemplo, as vendas de uma empresa para o próximo mês. Nos exemplos abaixo:

y_i = valor\ real\\\hat{y}_i = valor\ previsto

Mean Squared Error – MSE

MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2

Talvez seja a mais utilizada, esta função calcula a média dos erros do modelo ao quadrado. Ou seja, diferenças menores têm menos importância, enquanto diferenças maiores recebem mais peso.

Existe uma variação, que facilita a interpretação: o Root Mean Squared Error. Ele é simplesmente a raiz quadrada do primeiro. Neste caso, o erro volta a ter as unidades de medida originais da variável dependente.

Mean Absolute Error – MAE

MAE = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|

Bastante parecido com MSE, em vez de elevar a diferença entre a previsão do modelo, e o valor real, ao quadrado, ele toma o valor absoluto. Neste caso, em vez de atribuir um peso de acordo com a magnitude da diferença, ele atribui o mesmo peso a todas as diferenças, de maneira linear.

Se imaginarmos um exemplo simples, onde temos apenas a variável que estamos tentando prever, podemos ver um fato interessante que difere o MSE do MAE, e que devemos levar em conta ao decidir entre os dois: o valor que minimizaria o primeiro erro seria a média, já no segundo caso, a mediana.

Mean Absolute Percentage Error – MAPE

MAPE = \frac{1}{n} \sum_{i=1}^{n} |\frac{y_i - \hat{y}_i}{y_i}|

Este erro calcula a média percentual do desvio absoluto entre as previsões e a realidade. É utilizado para avaliar sistemas de previsões de vendas e outros sistemas nos quais a diferença percentual seja mais interpretável, ou mais importante, do que os valores absolutos.

Métricas específicas para tarefas

Em alguns casos, o ideal é usar uma métrica que tenha um significado específico para a tarefa em questão. Por exemplo, na segmentação de anúncios, a taxa de cliques; num sistema para comprar e vender ações, verificar o retorno médio. As métricas acima são importantes e podem ser utilizadas de maneira geral, mas se houver uma alternativa melhor, mais adequada ao contexto, ela deve ser utilizada.

Teste