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.

Deixe uma resposta

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