Back-End

3 ago, 2016

Como estar entre os 15 melhores da competição Kaggle usando Python – Parte 01

Publicidade

Competições Kaggle são uma maneira fantástica para aprender a ciência de dados e construir o seu portfólio. Eu, pessoalmente, usei o Kaggle para aprender muitos conceitos de ciência de dados. Eu comecei com o Kaggle poucos meses após aprender programação, e mais tarde ganhei várias competições.

Ir bem em uma competição Kaggle exige mais do que apenas saber algoritmos de machine learning. Ela exige uma mentalidade certa, vontade de aprender, e um monte de exploração de dados. Embora muitos desses aspectos não são normalmente enfatizados em tutoriais sobre como começar com Kaggle. Neste artigo, vou falar sobre como começar com a competição de recomendações de hotéis Kaggle Expedia, incluindo estabilizar a mentalidade correta, a criação da infraestrutura de testes, explorando dados, criando recursos e fazendo previsões.

No final, nós vamos gerar um arquivo de apresentação usando as técnicas deste artigo. Como descrito aqui, a apresentação seria classificada entre as 15 melhores.

kaggle-1

Onde esta apresentação estaria rankeada conforme escrito neste artigo

A competição Expedia Kaggle

A competição Expedia desafia você a prever qual hotel um usuário irá reservar com base em alguns atributos sobre a busca que o usuário está realizando na Expedia. Antes de mergulhar em qualquer código, vamos precisar investir um tempo para entender o problema e os dados.

Um rápido olhar nas colunas

O primeiro passo é olhar para a descrição das colunas no conjunto de dados. Você pode encontrar isso aqui. Para a parte inferior da página, você verá uma descrição de cada coluna nos dados. Olhando para isso, parece que temos um monte de dados sobre as buscas que os usuários estão realizando no Expedia, juntamente com os dados sobre o cluster de hotel que eles eventualmente reservaram em test.csv e train.csv. destinations.csv contém informações sobre as regiões de busca em que os usuários procuram os hotéis. Nós não vamos nos preocupar com o que estamos prevendo ainda, vamos nos concentrar em entender as colunas.

Expedia

Já que a competição é composta de dados de eventos de usuários fazendo reservas de hotéis na Expedia, vamos precisar passar algum tempo entendendo o site Expedia. Olhar para o fluxo de reserva vai nos ajudar a contextualizar os campos nos dados, e como eles se amarram usando Expedia.

kaggle-2

A página que você vê inicialmente quando reserva um hotel

A caixa chamada como Going To mapeia os campos srch_destination_type_id, hotel_continent, hotel_country e hotel_market nos dados.

A caixa chamada Check-in mapeia para o campo srch_ci nos dados, e a caixa Check out mapeia para o campo srch_co nos dados.

A caixa chamada Guests mapeia para os campos srch_adults_cnt, srch_children_cnt e srch_rm_cnt nos dados.

A caixa chamada Add a Flight mapeia para o campo is_package nos dados.

site_name é o nome do site que você visitou, seja o site principal Expedia.com, ou outro.

user_location_country, user_location_region, user_location_city, is_mobile, channel is_booking e cnt são todos os atributos que são determinados por onde o usuário esteve, qual o dispositivo dele, ou a sessão dele no site da Expedia.

Só de olhar para uma tela, podemos imediatamente contextualizar todas as variáveis. Brincar com a tela, preencher os valores e passar pelo processo de reserva pode ajudar ainda mais a contextualizar.

Explorando os dados Kaggle em Python

Agora que temos as mãos nos dados em um nível elevado, podemos fazer alguma exploração para dar uma olhada ainda mais profunda.

Baixando os dados

Você pode baixar os dados aqui. Os conjuntos de dados são bem grandes, por isso você vai precisar de uma boa quantidade de espaço em disco. Você precisará descompactar os arquivos para obter os arquivos puros .csv em vez de .csv.gz.

Explorando os dados com Pandas

Dependendo da quantidade de memória no seu sistema, pode ou não ser viável ler todos os dados. Se não for, você deve considerar criar uma máquina no EC2 ou no DigitalOcean para processar os dados. Aqui está um tutorial sobre como começar com isso.

Quando tivermos feito o download dos dados, podemos lê-los usando Pandas:

import pandas

destinations = pd.read_csv("destinations.csv")
test = pd.read_csv("test.csv")
train = pd.read_csv("train.csv")

Vamos primeiro ver a quantidade de dados que temos:

train.shape

(37670293, 24)

test.shape

(2528243, 22)

Temos aproximadamente 37 milhões de linhas no conjunto de treinamento e 2 milhões de linhas de testes, o que pode tornar esse problema um pouco difícil de trabalhar.

Podemos explorar as primeiras filas dos dados:

id date_time site_name posa_continent user_location_country user_location_region user_location_city orig_destination_distance user_id is_mobile srch_ci srch_co srch_adults_cnt srch_children_cnt srch_rm_cnt srch_destination_id srch_destination_type_id hotel_continent hotel_country hotel_market
0 0 2015-09-03 17:09:54 2 3 66 174 37449 5539.0567 1 1 2016-05-19 2016-05-23 2 0 1 12243 6 6 204 27
1 1 2015-09-24 17:38:35 2 3 66 174 37449 5873.2923 1 1 2016-05-12 2016-05-15 2 0 1 14474 7 6 204 1540
2 2 2015-06-07 15:53:02 2 3 66 142 17440 3975.9776 20 0 2015-07-26 2015-07-27 4 0 1 11353 1 2 50 699
3 3 2015-09-14 14:49:10 2 3 66 258 34156 1508.5975 28 0 2015-09-14 2015-09-16 2 0 1 8250 1 2 50 628
4 4 2015-07-17 09:32:04 2 3 66 467 36345 66.7913 50 0 2015-07-22 2015-07-23 2 0 1 11812 1 2 50 538

Existem algumas coisas que saltam aos olhos imediatamente:

date_time pode ser útil em nossas previsões, então vamos precisar convertê-lo.

A maioria das colunas são integers ou floats, por isso não podemos fazer um monte de engenharia de recursos. Por exemplo, user_location_country não é o nome de um país, é um número inteiro. Isso torna mais difícil criar novos recursos, porque nós não sabemos exatamente o que significa cada valor.

test.head(5)

Confira a tabela no artigo original.

Há algumas coisas que podemos perceber ao olhar para test.csv:

  • Parece que todas as datas em test.csv estão mais tardias do que as datas em train.csv, e a página de dados confirma isso. O conjunto de testes contém datas a partir de 2015, e o conjunto de treinamento contém as datas de 2013 e 2014.
  • Parece que os IDs de usuário no test.csv são um subconjunto dos IDs de usuário que estão em train.csv, dada a quantia de números inteiros que se sobrepõem. Nós podemos confirmar isso mais tarde.
  • A coluna is_booking sempre parece ser 1 em test.csv. A página de dados confirma isso.

Descobrindo o que prever

O que estamos prevendo

Nós vamos prever qual hotel_cluster um usuário vai reservar após uma determinada pesquisa. De acordo com a descrição, existem 100 clusters no total.

Como iremos marcar pontos

A página de avaliação diz que vamos receber pontos usando Mean Average Precision (MAP) @ 5, o que significa que vamos precisar fazer 5 previsões de cluster para cada linha, e será pontuado se a previsão correta aparecer em nossa lista ou não. Se a previsão correta estiver primeiro na lista, nós conseguiremos mais pontos.

Por exemplo, se o cluster “correto” é o 3, e nossa previsão for 4, 43, 60, 3, 20, nossa pontuação será menor do que se a previsão for 3, 4, 43, 60, 20. Nós devemos colocar as previsões que temos mais certeza estarem corretas na frente, em nossa lista de previsões.

Explorando os clusters de hotéis

Agora que sabemos o que estamos prevendo, é hora de mergulhar e explorar o hotel_cluster. Podemos usar o método value_counts em Series para fazer isso:

train["hotel_cluster"].value_counts()
91    1043720
41     772743
48     754033
64     704734
65     670960
5      620194
       ...
53     134812
88     107784
27     105040
74      48355

A saída acima é truncada, mas mostra que o número de hotéis em cada cluster são bem distribuídos. Não parece haver qualquer relação entre o número do cluster e o número de itens.

Explorando o IDs de usuário no train e no test

Por fim, vamos confirmar a nossa hipótese de que todos os IDs de usuário test são encontrados no DataFrame train. Podemos fazer isso encontrando os valores exclusivos para user_id no test, e ver se todos eles existem no train.

No código abaixo, vamos:

  • Criar um conjunto de todos os IDs de usuários únicos do test.
  • Criar um conjunto de todos os IDs de usuários únicos do train.
  • Descobrir quantos IDs de usuários do test estão nos IDs de usuários do train.
  • Ver se a contagem corresponde ao número total de IDs de usuários do test.
test_ids = set(test.user_id.unique())
train_ids = set(train.user_id.unique())
intersection_count = len(test_ids & train_ids)
intersection_count == len(test_ids)
True

Parece que a nossa hipótese está correta, o que tornará o trabalho com esses dados muito mais fácil!

Baixando exemplos de dados no nosso Kaggle

Todo o conjunto de dados train.csv contém 37 milhões de linhas, o que torna difícil a experimentação com diferentes técnicas. Idealmente, nós queremos um conjunto de dados pequeno o suficiente que nos permita interagir rapidamente através de diferentes abordagens, mas que ainda seja representativo de todos os dados do treinamento.

Podemos fazer isso primeiro fazendo uma amostragem aleatória das linhas dos nossos dados e, em seguida, selecionar um novo conjunto de treinamento e teste do train.csv. Ao selecionar ambos os conjuntos de train.csv, teremos o hotel_cluster verdadeiro de cada linha, e vamos ser capazes de calcular a nossa precisão conforme nós vamos testando novas técnicas.

Adicionar horários e datas

O primeiro passo é adicionar os campos month e year no train. Como os dados do train e do test são diferenciados por data, vamos precisar adicionar campos de data para permitir segmentar os nossos dados em dois conjuntos da mesma maneira. Se adicionarmos os campos year e month, podemos dividir nossos dados em conjuntos de treinamento e teste e usá-los.

O código abaixo vai:

  • Converter a coluna date_time no train de um objeto para um valor datetime. Isso faz com que seja mais fácil de trabalhar com ele como uma data.
  • Extrair year e month a partir de date_time, e atribuir a eles as suas próprias colunas.
train["date_time"] = pd.to_datetime(train["date_time"])
train["year"] = train["date_time"].dt.year
train["month"] = train["date_time"].dt.month

Escolha 10.000 usuários

Porque os IDs de usuário em test são um subconjunto dos IDs de usuário em train, vamos precisar fazer a nossa amostragem aleatória de uma forma que preserve os dados completos de cada usuário. Podemos fazer isso selecionando um certo número de usuários de forma aleatória, e então escolher somente as linhas do train onde user_id está em nossa amostra aleatória de IDs de usuário.

import random

unique_users = train.user_id.unique()

sel_user_ids = [unique_users[i] for i in sorted(random.sample(range(len(unique_users)), 10000)) ]
sel_train = train[train.user_id.isin(sel_user_ids)]

O código acima cria um DataFrame chamado sel_train que contém apenas dados de 10.000 usuários.

Escolha um novo conjunto de treinamento de testes

Vamos agora precisa escolher novos conjuntos de treinamento e testes de sel_train. Vamos chamar esses conjuntos de t1 e t2.

t1 = sel_train[((sel_train.year == 2013) | ((sel_train.year == 2014) & (sel_train.month < 8)))]
t2 = sel_train[((sel_train.year == 2014) & (sel_train.month >= 8))]

Nos DataFrames originais train e test, test continha dados a partir de 2015, e train continha dados de 2013 e 2014. Nós dividimos esses dados de modo que nada depois de julho de 2014 esteja em t2, e nada anterior esteja em t1. Isso nos dá conjuntos menores de treinamento e testes com características semelhantes em train e test.

Remova os eventos de clique

Se is_booking é 0, ele representa um clique, e 1 representa uma reserva. test contém apenas eventos de reserva, por isso vamos que o t2 contenha apenas reservas também.

t2 = t2[t2.is_booking == True]

Um algoritmo simples

A técnica mais simples que podemos tentar com esses dados é encontrar os clusters mais comuns em todos os dados, e então usá-los como previsões.

Podemos novamente usar o método value_counts para nos ajudar aqui:

most_common_clusters = list(train.hotel_cluster.value_counts().head().index)

O código acima irá nos dar uma lista dos 5 clusters mais comuns no train. Isso ocorre porque o método head retorna as primeiras 5 linhas por padrão, e a propriedade index retornará o índice do DataFrame, que é o cluster de hotel depois de executar o método value_counts.

Gerando previsões

Podemos transformar o most_common_clusters em uma lista de previsões, fazendo a mesma previsão para cada linha.

predictions = [most_common_clusters for i in range(t2.shape[0])]

Isso irá criar uma lista com tantos elementos quanto as linhas em t2. Cada elemento será igual a most_common_clusters.

Avaliando erro

A fim de avaliar erros, primeiro temos que descobrir como calcular Mean Average Precision. Felizmente, Ben Hamner escreveu uma implementação que pode ser encontrada aqui. Ele pode ser instalado como parte do pacote ml_metrics, e você pode encontrar instruções de instalação para ele aqui.

Podemos calcular a nossa métrica de erro com o método mapk em ml_metrics:

import ml_metrics as metrics
target = [[l] for l in t2["hotel_cluster"]]
metrics.mapk(target, predictions, k=5)
0.058020770920711007

Nossa meta precisa estar em lista de formato de listas de mapk para funcionar, por isso convertemos a coluna hotel_cluster de t2 em uma lista de listas. Em seguida, chamamos o método mapk com o nosso alvo, as nossas previsões, e o número de previsões que queremos avaliar (5).

Nosso resultado aqui não é bom, mas apenas geramos o nosso primeiro conjunto de previsões, e avaliamos o nosso erro! O framework que construímos nos permitirá testar rapidamente uma variedade de técnicas e ver como eles pontuam. Estamos bem em nosso caminho para a construção de uma boa solução de desempenho para o leaderboard.

Encontrando correlações

Antes de passarmos para a criação de um algoritmo melhor, vamos ver se alguma coisa se correlaciona bem com hotel_cluster. Isso vai nos dizer se devemos mergulhar mais em quaisquer colunas específicas.

Podemos encontrar correlações lineares no conjunto de treinamento utilizando o método corr:

train.corr()["hotel_cluster"]
site_name                   -0.022408
posa_continent               0.014938
user_location_country       -0.010477
user_location_region         0.007453
user_location_city           0.000831
orig_destination_distance    0.007260
user_id                      0.001052
is_mobile                    0.008412
is_package                   0.038733
channel                      0.000707

Isso nos diz que não há colunas correlacionadas linearmente com hotel_cluster. Isso faz sentido, porque não há nenhuma ordenação linear para hotel_cluster. Por exemplo, ter um número mais elevado de cluster não está ligado a ter um maior srch_destination_id.

Infelizmente, isso significa que técnicas como a regressão linear e a regressão logística não vão funcionar bem em nossos dados, porque dependem de correlações lineares entre preditores e metas.

***

Na próxima parte, você verá como criar melhores previsões para a entrada Kaggle, conhecer os principais clusters com base em hotel_cluster e aprender a fazer um arquivo de apresentação Kaggle, entre outros tópicos.

***

Vik Paruchuri é um cientista de dados e desenvolvedor que fundou a Dataquest. Besides Além disso, ele faz parte do time de colunistas internacionais do iMasters. A tradução deste artigo foi feita pela Redação iMasters, com autorização do autor. Este artigo foi escrito originalmente no blog Dataquest: https://www.dataquest.io/blog/kaggle-tutorial/