Esse post do FastML fez uma análise bem interessante sobre como são as bases de validação/avaliação de torneios de Machine Learning como os que aparecem no Kaggle ou o do Numerai. Ele comenta como em algumas competições, a base de validação (base em que são avaliadas as predições para o cálculo do seu score) possuem comportamento diferente da base utilizada para treino. Nesse post, que é a primeira parte da análise do Zygmund, ele mostra o exemplo de uma competição do Santander, que aconteceu no Kaggle em que as duas bases de treino e teste possuem o mesmo comportamento.

Na parte 2 do post, ele mostra que a base de treino do Numerai, aquela da qual eles deixam a variável target disponível é bem diferente do banco de teste, que eles chamam de tournament dataset. Até o próprio Numerai, recomendou a leitura deste post no twitter.

Resolvi replicar o experimento para verificar o resultado encontrado pelo blog. Utilizei a mesma idéia proposta pelo FastMl: Empilhar a base do torneio e a base de treino e tentar criar um modelo para prever se uma observação é da base de treino. Se o acerto do modelo for perto de 50%, quer dizer que as bases são muito parecidas. Se o modelo conseguir acertar de qual base de dados a observação é proveniente, significa que os bancos de dados possuem características muito diferentes.

Leitura das bases

Usei as bases que estão disponíveis no Numerai neste link. Também as deixei dispoíveis no repositório do blog.

library(dplyr)
## 
## Attaching package: 'dplyr'
## 
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## 
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
train_data <- read.csv("../data/numerai-datasets/numerai_training_data.csv")
test_data <- read.csv("../data/numerai-datasets/numerai_tournament_data.csv")

Empilhando as duas e criando a resposta.

data <- bind_rows(
  train_data %>% select(-target) %>% mutate(TRAIN = TRUE),
  test_data %>% select(-t_id) %>% mutate(TRAIN = FALSE)
)
data$TRAIN <- as.factor(data$TRAIN)

Visualização

Para visualizar as possíveis diferenças entre a base de treino e de torneio, utilizamos componentes principais para reduzir a dimensionalidade. Com isso, das 21 variáveis que possíamos inicialmente, obtivemos 2, que podem ser facilmente ser representadas em um gráfico de dispersão.

pca <- princomp(data %>% select(-TRAIN))
pca_df <- pca$scores %>%
  data.frame()
pca_df$TRAIN <- data$TRAIN
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 3.2.3
pca_df %>% 
  arrange(runif(nrow(.))) %>%
  ggplot(aes(Comp.1, Comp.2)) + geom_point(aes(color = TRAIN), size = 0.2, alpha = 0.3)

plot of chunk unnamed-chunk-4

Note que com essas duas dimensões não é perceptível a diferença entre os dois bancos de dados.

Ajustando o modelo

Aqui usei um modelo de random forest por meio do pacote caret.

library(randomForest)
## randomForest 4.6-12
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
modelo <- randomForest(TRAIN ~ ., data = data, ntree = 100, mtry = sqrt(21))
modelo
## 
## Call:
##  randomForest(formula = TRAIN ~ ., data = data, ntree = 100, mtry = sqrt(21)) 
##                Type of random forest: classification
##                      Number of trees: 100
## No. of variables tried at each split: 5
## 
##         OOB estimate of  error rate: 20.54%
## Confusion matrix:
##       FALSE  TRUE class.error
## FALSE  6007 25770 0.810963905
## TRUE    545 95775 0.005658223

Na tabela acima, as linhas repesentam os valores verdadeiros e as colunas as categorias previstas pelo modelo de random forest. Note que das 96.320 observações da base de treino, o modelo classifica apenas 589 (menos de 1%) como base de teste.

library(ROCR)
## Carregando pacotes exigidos: gplots
## Warning: package 'gplots' was built under R version 3.2.4
## 
## Attaching package: 'gplots'
## 
## The following object is masked from 'package:stats':
## 
##     lowess
## 
## Carregando pacotes exigidos: methods
pred <- prediction(predict(modelo, type = "prob")[,2], data$TRAIN)
as.numeric(performance(pred , "auc")@y.values)
## [1] 0.8364159

Veja também que o AUC (área sobre a curva ROC) foi de 0.84, muito próxima da relatada no post do FastML. Se a base de testes fosse realmente uma amostra aleatória da base de treino, esse número deveria ser próximo de 0.5.

Enfim, esse resultado é importante pois, se as duas bases possuem comportamentos diferentes, é difícil saber qual o score que você teria no final do torneio, apenas avaliando o seu erro na base de treino. A recomendação do FastMl é validar o seu modelo nestas observações da base de treini que o modelo de random forest classificou errôneamente como base de teste, desta forma você teria uma estimativa mais precisa do erro no final do torneio.