Tuning via hiper-parametrização para Máquinas de Vetor de Suporte (Support Vector Machines) por estimação de distribuição de algoritmos

Em épocas de Deep Learning, é sempre bom ver um paper com as boas e velhas Máquinas de Vetor de Suporte (Support Vector Machines). Em breve teremos um post sobre essa técnica aqui no blog.

Hyper-Parameter Tuning for Support Vector Machines by Estimation of Distribution Algorithms

Abstract: Hyper-parameter tuning for support vector machines has been widely studied in the past decade. A variety of metaheuristics, such as Genetic Algorithms and Particle Swarm Optimization have been considered to accomplish this task. Notably, exhaustive strategies such as Grid Search or Random Search continue to be implemented for hyper-parameter tuning and have recently shown results comparable to sophisticated metaheuristics. The main reason for the success of exhaustive techniques is due to the fact that only two or three parameters need to be adjusted when working with support vector machines. In this chapter, we analyze two Estimation Distribution Algorithms, the Univariate Marginal Distribution Algorithm and the Boltzmann Univariate Marginal Distribution Algorithm, to verify if these algorithms preserve the effectiveness of Random Search and at the same time make more efficient the process of finding the optimal hyper-parameters without increasing the complexity of Random Search.

Tuning via hiper-parametrização para Máquinas de Vetor de Suporte (Support Vector Machines) por estimação de distribuição de algoritmos

Deep Dive com Gradient Boosting Machine com H2O + R (Mais Grid Search!)

Dando sequência a alguns tutoriais sobre o uso do R como linguagem de programação junto H2O como backend de processamento e memória (duas principais limitações do R) vamos falar um pouco de Gradient Boosting Machine e usar uma base de dados de crédito de um banco fictício chamado “Layman Brothers”.

Gradient Boosting Machine é um meta-algoritmo de aprendizado supervisionado que é geralmente utilizado em problemas de classificação e regressão. O principio algorítmico por trás do GBM é a produção de previsões/classificações derivadas de modelos preditivos fracos (Weak Learners), em especial árvores de decisão essas que por sua vez combinadas via ensemble learning para redução de vieses dos algoritmos.

Essas previsões são geradas através da combinação da meta-heurística de gradiente descendente para otimização paramétrica face a minimização de uma função de custo (loss function), e do Boosting que é combinação de diversos classificadores fracos (Weak Learners) em série para (ou meta-classificador) para combinação de resultados desses algoritmos.

Como podemos supor, com essa combinação heurística de algoritmos, em especial dos weak learners (que dão uma robustez substancial ao modelo) é de se esperar uma determinada insensibilidade á distribuição de cauda longa que pode ser espessa e detonar as suas previsões (e.g. distribuição da renda mundial em que poucos (20%) tem muito dinheiro e muitos (80%) tem pouco) , outliers (i.e. eventos extremos, também conhecidos como cisnes negros), além de uma boa resposta a não-linearidade. (Nota: Se você não entendeu nada do que está aqui, uma boa pedida são dois livros do Nassim Taleb que são Black Swan (A lógica do cisne negro) e Antifragile (Antifrágil)).

Como dito anteriormente, a base de dados que será usada aqui é de um banco fictício chamado “Layman Brothers”, que é uma alusão simpática ao Lehman Brothers; e o nosso objetivo é ter um sistema de crédito um pouco mais confiável do que o deles o que não é uma tarefa que demande muita inteligência ou stamina intelectual. (Nota: Essa base é originalmente do repositório do UCI, mas estou rebatizando para dar um tom cênico mais descontraído aqui no post).

A nossa base de dados de créditos tem as seguintes colunas:

  • ID: Número da transação
  • LIMIT_BAL: Crédito concedido em dólares
  • SEX: Sexo (1 = masculino; 2 = feminino).
  • EDUCATION: Nível escolar d@ cliente (1 = ensino médio; 2 = universidade; 3 = ensino superior completo; 4 = outros)
  • MARRIAGE: Estado civil (1 = casad@; 2 = solteir@; 3 = outros).
  • AGE: Idade d@ cliente
  • PAY_X: Histórico do pagamento passado. Foi rastreado o pagamento passado mensal (de abril até setembro de 2005) da seguinte forma: PAY_1 o status de repagamento do mês de setembro de 2005, PAY_2: o status do repagamento mês de agosto de 2005, etc. A escala de medida do repagamento é :-1 = Pago em dia, 1 = pago com um mês de atraso, 2 = pagamento atrasado por 2 meses, 8 = pagamento atrasado por 8 meses, etc.
  • BILL_AMTX: Montante do saldo ainda não amortizado dos meses anteriores. BILL_AMT1 = Saldo ainda não amortizado em setembro de 2005, BILL_AMT2 = saldo ainda não amortizado em agosto de 2005, etc.
  • PAY_AMTX: Montante pago anteriormente (em dólares) relativos ao mês anterior. PAY_AMT1 = valor pago em setembro de 2005, PAY_AMT2 = valor pago em agosto de 2005, etc.
  • DEFAULT: Se @ cliente deixou de pagar o empréstimo no mês seguinte.

Base de dados apresentada, vamos ao código.

Primeiramente, se você não instalou o H2O via R ou está com a versão desatualizada, é só executar esse código abaixo que ele vai remover a versão antiga, instalar todas as dependências, e instalar o H2O:

# The following two commands remove any previously installed H2O packages for R.
if ("package:h2o" %in% search()) { detach("package:h2o", unload=TRUE) }
if ("h2o" %in% rownames(installed.packages())) { remove.packages("h2o") }

# Next, we download packages that H2O depends on.
if (! ("methods" %in% rownames(installed.packages()))) { install.packages("methods") }
if (! ("statmod" %in% rownames(installed.packages()))) { install.packages("statmod") }
if (! ("stats" %in% rownames(installed.packages()))) { install.packages("stats") }
if (! ("graphics" %in% rownames(installed.packages()))) { install.packages("graphics") }
if (! ("RCurl" %in% rownames(installed.packages()))) { install.packages("RCurl") }
if (! ("jsonlite" %in% rownames(installed.packages()))) { install.packages("jsonlite") }
if (! ("tools" %in% rownames(installed.packages()))) { install.packages("tools") }
if (! ("utils" %in% rownames(installed.packages()))) { install.packages("utils") }

# Now we download, install and initialize the H2O package for R.
install.packages("h2o", type="source", repos=(c("http://h2o-release.s3.amazonaws.com/h2o/rel-turing/8/R")))

Agora vamos carregar a biblioteca e iniciar o nosso cluster (que nesse caso ainda estará no meu notebook) com o tamanho máximo de memória de 8 gigas, e vai usar todos os processadores (-1):

# Load library
library(h2o)


# Start instance with all cores
h2o.init(nthreads = -1, max_mem_size = "8G")

# Info about cluster
h2o.clusterInfo()

# Production Cluster (Not applicable because we're using in the same machine)
#localH2O <- h2o.init(ip = '10.112.81.210', port =54321, nthreads=-1) # Server 1
#localH2O <- h2o.init(ip = '10.112.80.74', port =54321, nthreads=-1) # Server 2

Cluster iniciado, vamos buscar os nossos dados que estão no repositório remoto do Github e na sequência vamos carregar no nosso objeto .hex (extensão do H2O):

# URL with data
LaymanBrothersURL = "https://raw.githubusercontent.com/fclesio/learning-space/master/Datasets/02%20-%20Classification/default_credit_card.csv"

# Load data 
creditcard.hex = h2o.importFile(path = LaymanBrothersURL, destination_frame = "creditcard.hex")

Com os dados carregados, vamos realizar a transformação das variáveis categóricas, e em seguida vamos ver o sumário dessas variáveis:

# Convert DEFAULT, SEX, EDUCATION, MARRIAGE variables to categorical
creditcard.hex[,25] <- as.factor(creditcard.hex[,25]) # DEFAULT
creditcard.hex[,3] <- as.factor(creditcard.hex[,3]) # SEX
creditcard.hex[,4] <- as.factor(creditcard.hex[,4]) # EDUCATION
creditcard.hex[,5] <- as.factor(creditcard.hex[,5]) # MARRIAGE

# Let's see the summary
summary(creditcard.hex)

Como podemos ver pelo summary() temos algumas estatísticas descritivas básicas interessantes sobre essa base de dados, como:

screen-shot-2017-01-15-at-12-17-16-pm

  • A maioria dos empréstimos foram feitos por pessoas que se declararam do sexo feminino (60%);
  • 63% de todos os empréstimos foram feitos para a população classificada como universitária ou que tem curso superior completo;
  • Há um equilíbrio entre o estado civil em relação aos empréstimos concedidos;
  • Com um terceiro quartil de 41 e uma média e medianas bem próximas (35 e 34), podemos ver que grande parte dos empréstimos foram feitos por pessoas na idade adulta que estão na meia idade; e
  • Temos muitas pessoas que pegaram empréstimos altos (acima de 239 mil dólares), porém, a média do valor concedido é de 167 mil dólares.

Óbvio que caberiam mais algumas análises de perfil, correlações, e até mesmo alguns gráficos para exemplificar melhor a composição demográfica dessa base, mas como esse não é o objetivo desse post, fica aberto para que algum dos 5 leitores desse site blog faça isso e compartilhe.

Com essas análises feitas, vamos dividir a nossa base nos conjuntos de treinamento, teste e validação usando o comando splitFrame:

# We'll get 3 dataframes Train (60%), Test (20%) and Validation (20%)
creditcard.split = h2o.splitFrame(data = creditcard.hex
                                  ,ratios = c(0.6,0.2)
                                  ,destination_frames = c("creditcard.train.hex", "creditcard.test.hex", "creditcard.validation.hex")
                                  ,seed = 12345)


# Get the train dataframe(1st split object)
creditcard.train = creditcard.split[[1]]

# Get the test dataframe(2nd split object)
creditcard.test = creditcard.split[[2]]

# Get the validation dataframe(3rd split object)
creditcard.validation = creditcard.split[[3]]

Para checarmos a real proporção de cada base, podemos usar o comando table para ver a composição de cada base de dados (e principalmente ver se elas estão balanceadas):

# See datatables from each dataframe
h2o.table(creditcard.train$DEFAULT)

# DEFAULT Count
# 1       0 14047
# 2       1  4030

h2o.table(creditcard.test$DEFAULT)

# DEFAULT Count
# 1       0  4697
# 2       1  1285

h2o.table(creditcard.validation$DEFAULT)

# DEFAULT Count
# 1       0  4620
# 2       1  1321

Agora vamos criar dois objetos para passar ao nosso algoritmo: um objeto para definir quem será a nossa variável dependente (Y) e outro para definir as nossas variáveis independentes (X):

# Set dependent variable
Y = "DEFAULT"

# Set independent variables
X = c("LIMIT_BAL","EDUCATION","MARRIAGE","AGE"
      ,"PAY_0","PAY_2","PAY_3","PAY_4","PAY_5","PAY_6"
      ,"BILL_AMT1","BILL_AMT2","BILL_AMT3","BILL_AMT4","BILL_AMT5","BILL_AMT6"
      ,"PAY_AMT1","PAY_AMT3","PAY_AMT4","PAY_AMT5","PAY_AMT6")

# I intentionally removed sex variable from the model, to avoid put any gender bias inside the model. Ethics first guys! 😉

Os mais atentos podem verificar que eu removi a variável SEX. Fiz isso intencionalmente dado que não vamos colocar nenhum tipo de viés discriminatório no modelo (Atenção amigos: esse é um bom tempo para considerar seriamente essas questões de discriminação/ética em modelos de Machine Learning como etnia, gênero, etc).

Agora com esses objetos prontos, vamos treinar o nosso modelo:

# Train model
creditcard.gbm <- h2o.gbm(y = Y
                          ,x = X
                          ,training_frame = creditcard.train
                          ,validation_frame = creditcard.validation                      
                          ,ntrees = 100
                          ,seed = 12345
                          ,max_depth = 100
                          ,min_rows = 10
                          ,learn_rate = 0.2
                          ,distribution= "bernoulli"
                          ,model_id = 'gbm_layman_brothers_model'
                          ,build_tree_one_node = TRUE
                          ,balance_classes = TRUE
                          ,score_each_iteration = TRUE
                          ,ignore_const_cols = TRUE
                          )

Explicando alguns desses parâmetros:

  • x: Vetor que contém os nomes das variáveis independentes do modelo;
  • y: índice ou objeto que representa a variável dependente do modelo;
  • training frame: Um objeto de dados do H2O (H2OFrame) que contém as variáveis do modelo;
  • validation frame: Um objeto de dados do H2O (H2OFrame) que contém as variáveis do modelo para validação do modelo. Se estiver vazia os dados de treinamento são usados por padrão;
  • ntrees: Um inteiro não negativo que define o número de árvores. O valor default é 50;
  • seed: Semente dos números aleatórios a serem gerados. É usado para reprodutibilidade amostral;
  • max depth: Valor definido pelo usuário do número máximo da profundidade das árvores. O valor default é 5;
  • min rows: O número mínimo de linhas a serem designadas para cada nó terminal. O padrão é 10;
  • learn rate: Um inteiro que define a taxa de aprendizado do modelo. Vai de 0.1 até 1.0;
  • distribution: Escolhe uma distribuição de probabilidade entre AUTO, bernoulli, multinomial, gaussian, poisson, gamma ou tweedie. O default é AUTO;
  • model id: ID único que identifica o modelo. Se não especificado é gerado automaticamente;
  • build tree one node: Especifica se o modelo será processado em um nó apenas. Isso serve para evitar overhead de rede e com isso menos CPUs são usadas no processo. É ideal para pequenos datasets, e o default é FALSE;
  • balance classes: Faz o balanceamento de classes do conjunto de treinamento, caso os dados estejam com subamostragem ou desbalanceados. O default é falso;
  • score each iteration: Um binário que indica se haverá o processo de scoring durante cada interação do modelo. O default é falso; e
  • ignore const cols: Um binário que indica se colunas com constantes serão ignoradas. O Default é TRUE.

Alguns conselhos práticos de quem já sofreu (muito) na pele para parametrizar GBM que você não vai ter do seu professor na faculdade:

a) O H2O oferece e a opção validation_frame, porém, se você for mais purista o ideal é checar na etapa de prediction e ver o bias do modelo através da análise dos erros (sim gente, vai ter que rolar estatística aqui, ok?). Isso além de dar um ajuste mais fino, te dá o maior entendimento dos erros modelo. Se fosse em minas, o pessoal lá diria que isso faz bem pra saúde e forma o caráter. Faça o mesmo.;

b) Tenha bastante parcimônia para ajustar o número ideal de árvores (ntrees) dado que isso eleva demais o custo computacional (processamento + memória) do modelo. Via de regra, eu gosto de usar intervalos de 50 árvores para cada step até o limite de 300; e assim que eu chego em um meio termo eu vou ajustando na unha via grid search até chegar em uma árvore que eu tenha um bom desempenho sem overfitting. Isso é necessário pois grande parte das vezes você tem uma elevação ridícula de até 8 horas no tempo de treinamento pra ganhar no máximo 0.01 no AUC, ou uma redução de 0.005% nos falsos positivos. Em resumo: Vai com calma no ajuste. Faz bem pra saúde e forma o caráter; e além do mais economiza mais de 20 dólares na Amazon pra treinar um modelo caso você esteja usando máquinas on-demand fora da sua infra;

c) É o seed que vai garantir que os seus números estão corretos quando você for passar para alguém fazer o code review ou mesmo antes do deployment. Então use sempre que puder por questões óbvias de reprodutibilidade;

d) O parâmetro max depth costuma ser o que eu chamo de cemitério do malandro em Machine Learning. Isso devido ao fato de que qualquer iniciante em seu primeiro contato com esse parâmetro vai colocar o maior número possível em geral quase o mesmo número de instâncias da base de treinamento (isso é quando o malandro não coloca cross-validation pra coisa ficar ainda mais bonita) o que deixa a árvore mais específica e leva na maioria das vezes aquele overfittingTem iniciantes que conseguem a proeza de fazer overfitting mesmo usando max depth com leave-one-out cross validation. (Pequena dica empírica: pessoalmente eu nunca consegui resultados bacanas com uma profundidade de níveis que excedam 0.005% do número de registros no conjunto de treinamento (100/((30000/100)*70 =0.005%). Ainda estou tentando saber se isso está correto ou não, mas ao menos pra mim funciona bem;

e)  Quanto menor o valor do min rows, mais específica será a árvore e pode ocorrer que ela generalize menos. Por isso muita parcimônia com esse parâmetro;

f) Desnecessário dizer que um número muito pequeno pode influenciar no tempo de processamento e convergência do modelo, e um número alto pode cair em um mínimo local e estragar todo o trabalho. Dica prática: tá com pouco tempo? Vai de 0.35 até 0.75 com incremento de 0.1. Tá com tempo de sobra? Vai de 0.1 até 0.5 com incremento de 0.03;

g) Realmente vale a pena gastar um pouco de neurônios para entender melhor as distribuições de probabilidade (distribution) para escolher a correta. Se você não tiver tempo, escolha a AUTO e seja feliz;

h) A não ser que você esteja enfrentando uma situação de concorrência de rede e de processamento, o parâmetro build tree one node sempre deve estar desligado;

i) Se você está usando o parâmetro balance classes significa que o seu trabalho de amostragem está um lixo e você precisa da ferramenta pra fazer algo básico pode não ser o mais correto. Eu recomendo fortemente uma seriedade no processo de amostragem que é o coração de qualquer treinamento de machine learning. Caso sejam situações amostrais muito esquisitas (e.g. modelagem de sistemas de combate á fraudes, classificador de reclamações em Call Center, et cetera) ou por falta de tempo, vale a pena usar esse parâmetro (Dica prática: caso haja uma situação de desbalanceamento muito grave de classes (algo na proporção 9:1) o ideal é esquecer as outras métricas de avaliação de modelos e ir direto para o coeficiente de matthews que é bem mais consistente para lidar com esse tipo de caso);

j) Se você está usando o parâmetro ignore const cols é porque o seu trabalho de pré-processamento (Feature Extraction e Feature Engineering) está um lixo pode não estar sendo o melhor.

Modelo treinado e parâmetros explicados, vamos ver a performance do modelo usando os dados de validação:

# See algo performance
h2o.performance(creditcard.gbm, newdata = creditcard.validation)

# H2OBinomialMetrics: gbm

# MSE:  0.1648487
# RMSE:  0.4060157
# LogLoss:  0.8160863
# Mean Per-Class Error:  0.3155595
# AUC:  0.7484422
# Gini:  0.4968843

# Confusion Matrix for F1-optimal threshold:
#   0    1    Error        Rate
# 0      3988  632 0.136797   =632/4620
# 1       653  668 0.494322   =653/1321
# Totals 4641 1300 0.216294  =1285/5941

# We have an AUC of 74,84%, not so bad!

Com esse modelo tivemos um AUC de 74,84%. Razoável, considerando que usamos um conjunto de parametrizações simples.

A seguir, vamos conferir a importância de cada uma de nossas variáveis:

# Variable importance
imp <- h2o.varimp(creditcard.gbm)

head(imp, 20)

# Variable Importances: 
#   variable relative_importance scaled_importance percentage
# 1  EDUCATION        17617.437500          1.000000   0.380798
# 2   MARRIAGE         9897.513672          0.561802   0.213933
# 3      PAY_0         3634.417480          0.206297   0.078557
# 4        AGE         2100.291992          0.119217   0.045397
# 5  LIMIT_BAL         1852.831787          0.105170   0.040049
# 6  BILL_AMT1         1236.516602          0.070187   0.026727
# 7   PAY_AMT5         1018.286499          0.057800   0.022010
# 8  BILL_AMT3          984.673889          0.055892   0.021284
# 9  BILL_AMT2          860.909119          0.048867   0.018608
# 10  PAY_AMT6          856.006531          0.048589   0.018502
# 11  PAY_AMT1          828.846252          0.047047   0.017915
# 12 BILL_AMT6          823.107605          0.046721   0.017791
# 13 BILL_AMT4          809.641785          0.045957   0.017500
# 14  PAY_AMT4          771.504272          0.043792   0.016676
# 15  PAY_AMT3          746.101196          0.042350   0.016127
# 16 BILL_AMT5          723.759521          0.041082   0.015644
# 17     PAY_3          457.857758          0.025989   0.009897
# 18     PAY_5          298.554657          0.016947   0.006453
# 19     PAY_4          268.133453          0.015220   0.005796
# 20     PAY_2          249.107925          0.014140   0.005384

Nesse modelo podemos ver que o nível educacional tem um papel essencial na definição de quem vai entrar em default (38%), seguindo do estado civil (21%) e fechando com o pagamento anterior relativo ao mês de setembro de 2008 (7%) e da idade do tomador de crédito e o saldo emprestado (4%).

Em outras palavras: essas variáveis acima respondem por 74% do comportamento de crédito.

Com isso algumas questões hipóteses (Hx) e ações (Ax) podem ser tomadas pelo Layman Brothers:

H1: O nível educacional está muito relacionado com o default,  isso acontece de forma positiva ou não em relação à inadimplência?

H2: Será que universitários que tradicionalmente são pessoas com menos poder aquisitivo tem maiores dificuldades (ou facilidades) para o pagamento?

H3: De que forma o estado civil influencia na capacidade de pagamento do crédito emprestado?

H4: Porque o saldo não amortizado exerce efeito tão grande em relação às outras variáveis financeiras?

H5: Porque a pontualidade no pagamento não é tão determinante, com exceção da primeira parcela?

H6: O perfil educacional influencia o quanto em relação à capacidade de pagamento?

A1: De acordo com a escolaridade, ter diferentes taxas de juros para empréstimos.

A2: Ter ações de cobrança efetivas/intensas já no primeiro mês de atraso.

A3: Ter linhas de crédito mais específicas para cada perfil educacional com taxas e saldos correspondentes ao risco de default.

A4: Entender e criar linhas de financiamento de acordo com cada objetivo de acordo com o estado civil (e.g. entender se o gasto é para investimento (voltado para a geração de mais receita como cursos, maquinário, ou outros fatores que aumentem a produtividade; ou para despesas como consumo, contas de inúmeras naturezas, outros empréstimos, et cetera) .

Adiante, podemos agora usar o nosso modelo treinado para fazer previsões:

# Predict using GLM model
pred = h2o.predict(object = creditcard.gbm, newdata = creditcard.test)

# See predictions
head(pred, 5)

# predict        p0           p1
# 1       0 0.9990856 0.0009144487
# 2       0 0.9945627 0.0054373206
# 3       0 0.9997726 0.0002273775
# 4       0 0.9968271 0.0031728833
# 5       0 0.9991758 0.0008242144

Agora, vamos para um ajuste mais fino no nosso modelo com o objetivo de melhorar o nosso AUC (que é atualmente de 74,84%), e para isso vamos usar Grid Search.

Primeiramente vamos gerar uma lista de valores para os nossos hiper-parâmetros (hyper parameters) do modelo GBM. Os parâmetros que vamos usar serão ntrees (número de árvores), max_depth (profundidade das árvores) e learn_rate (taxa de aprendizado). Após isso vamos jogar dentro de uma meta lista que vamos usar para ajustar o nosso objeto de grid.

# Set hyparameters (Did not work using sequence. :o( )
ntrees_list <- list(50,100,150,200)

max_depth_list <- list(1,2,3,4,5,6,7,8,9,10)

learnrate_list <- list(.10,.20,.30,.40,.50,.60,.70,.80,.90)
# Full list of hyper parameters that will be used
hyper_parameters <- list(ntrees = ntrees_list
                         ,max_depth = max_depth_list
                         ,learn_rate = learnrate_list)

# See hyparameters lists
hyper_parameters

Ou seja, teremos uma combinação com 50, 100, 150 e 200 árvores, níveis de profundidade da árvore indo de 1 até 10 e taxa de aprendizado indo de 0.10 até 0.90.

Uma pequena experiência da trincheira deste escriba que não foi muito inteligente é ter uma boa combinação de números de parâmetros na meta lista em relação com a capacidade de processamento disponível para fazer o treinamento.

Isso se faz necessário pois como abaixo vamos usar a estratégia cartesiana para o nosso critério de busca (i.e. vamos usar todas as combinações paramétricas possíveis) vamos ter o seguinte cenário:

ntrees = 4
max_depth = 10
learn_rate = 9

Logo teremos 4 * 10 * 9 = 360 modelos/combinações!

Ou seja: Pode levar bastante tempo para processar (no meu caso levou 11m34min pra acabar, e houve uma porção de erros do H2O por incapacidade de processamento).

Após o processamento do grid vamos ordenar os modelos do melhor para o pior usando o AUC:

# sort the grid models by decreasing AUC
sortedGrid <- h2o.getGrid("depth_grid", sort_by="auc", decreasing = TRUE)    
# Let's see our models
sortedGrid

# H2O Grid Details
# ================
  
# Grid ID: depth_grid 
# Used hyper parameters: 
# -  learn_rate 
# -  max_depth 
# -  ntrees 
# Number of models: 380 
# Number of failed models: 2940 

# Hyper-Parameter Search Summary: ordered by decreasing auc
# learn_rate max_depth ntrees            model_ids                auc
# 1        0.1         6    100 depth_grid_model_200 0.7811807105334736
# 2        0.1         6     50   depth_grid_model_5 0.7811440893197138
# 3        0.2         3    150 depth_grid_model_264 0.7809025695475355
# 4        0.2         3    100 depth_grid_model_174  0.780834324645831
# 5        0.1         6    200 depth_grid_model_380 0.7808292451933633

Agora, vamos pegar o melhor modelo (com menor AUC) e vamos ver algumas das suas características:

# Summary
summary(best_glm)

# Model Details:
# ==============
  
# H2OBinomialModel: gbm
# Model Key:  depth_grid_model_200 
# Model Summary: 
#   number_of_trees number_of_internal_trees model_size_in_bytes min_depth max_depth mean_depth
# 1             100                      100               52783         6         6    6.00000
# min_leaves max_leaves mean_leaves
# 1         12         56    36.93000

# H2OBinomialMetrics: gbm
# ** Reported on training data. **
  
# MSE:  0.1189855
# RMSE:  0.3449427
# LogLoss:  0.3860698
# Mean Per-Class Error:  0.2593832
# AUC:  0.8371354
# Gini:  0.6742709

# Confusion Matrix for F1-optimal threshold:
# 0    1    Error         Rate
# 0      12424 1623 0.115541  =1623/14047
# 1       1625 2405 0.403226   =1625/4030
# Totals 14049 4028 0.179676  =3248/18077

Esse nosso modelo tem 100 árvores, uma profundidade de 6 níveis, e em média 37 instâncias em cada nó folha.

Como podemos ver tivemos um AUC de 83,71%, ou 11% de melhoria em comparação com o antigo AUC que foi de 74,84% em menos de 12 minutos.

Um fato curioso é que olhando a importância das variáveis novamente com esse modelo temos os seguintes resultados:

# Variable importance (again...)
imp2 <- h2o.varimp(best_glm)

head(imp2, 20)

# Variable Importances: 
#   variable relative_importance scaled_importance percentage
# 1      PAY_0         2040.270508          1.000000   0.358878
# 2      PAY_2          902.637390          0.442411   0.158772
# 3  LIMIT_BAL          385.425659          0.188909   0.067795
# 4        AGE          274.609589          0.134595   0.048303
# 5  BILL_AMT1          209.715469          0.102788   0.036888
# 6      PAY_3          168.518372          0.082596   0.029642
# 7  EDUCATION          150.365280          0.073699   0.026449
# 8  BILL_AMT2          146.754837          0.071929   0.025814
# 9      PAY_5          139.303482          0.068277   0.024503
# 10  PAY_AMT5          139.206543          0.068229   0.024486
# 11 BILL_AMT5          133.963348          0.065660   0.023564
# 12     PAY_4          124.926552          0.061230   0.021974
# 13  PAY_AMT6          123.267151          0.060417   0.021682
# 14 BILL_AMT6          114.012253          0.055881   0.020054
# 15  PAY_AMT1          112.402290          0.055092   0.019771
# 16     PAY_6          108.483795          0.053171   0.019082
# 17 BILL_AMT3          103.207893          0.050585   0.018154
# 18  PAY_AMT3           97.335411          0.047707   0.017121
# 19 BILL_AMT4           90.403320          0.044309   0.015902
# 20  MARRIAGE           61.917801          0.030348   0.010891

Ou seja, se antigamente o nível educacional e o estado civil tiveram uma participação importante, nesse modelo (com melhor AUC) a pontualidade, o montante de crédito concedido e a idade exercem mais influência.

Com esse melhor modelo, podemos fazer as nossas previsões e salvar em um arquivo .csv para upload em algum sistema ou isso pode ser feito via API via requisição.

# Get model and put inside a object
model = best_glm

# Prediction using the best model
pred2 = h2o.predict(object = model, newdata = creditcard.validation)

# Frame with predictions
dataset_pred = as.data.frame(pred2)

# Write a csv file
write.csv(dataset_pred, file = "predictions.csv", row.names=TRUE)

 E após finalizado todo o trabalho, podemos desligar o nosso cluster:

# Shutdown the cluster 
h2o.shutdown()

# Are you sure you want to shutdown the H2O instance running at http://localhost:54321/ (Y/N)? Y
# [1] TRUE

Bem pessoal como vocês podem ver, usar um modelo usando Gradient Boosting Machine no R não é nenhum bicho de 7 cabeças no H2O, basta um pouquinho de parcimônia na parametrização que tudo dá certo.

Se tiverem dúvidas deixem o seu comentário inteligente e educado aqui nos comentários ou me mandem por e-mail.

Forte abraço!

 

Deep Dive com Gradient Boosting Machine com H2O + R (Mais Grid Search!)

Auto-WEKA 2.0: Automatic model selection and hyperparameter optimization in WEKA

WEKA is a widely used, open-source machine learning platform. Due to its intuitive interface, it is particularly popular with novice users. However, such users often find it hard to identify the best approach for their particular dataset among the many available. We describe the new version of Auto-WEKA, a system designed to help such users by automatically searching through the joint space of WEKA’s learning algorithms and their respective hyperparameter settings to maximize performance, using a state-of-the-art Bayesian optimization method. Our new package is tightly integrated with WEKA, making it just as accessible to end users as any other learning algorithm. Keywords: Hyperparameter Optimization, Model Selection, Feature Selection

Auto-WEKA 2.0: Automatic model selection and hyperparameter optimization in WEKA