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:

[sourcecode language=”r”] # 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”))) [/sourcecode]

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):

[sourcecode language=”r”] # Load library library(h2o) [sourcecode language=”r”] # 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 [/sourcecode]

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):

[sourcecode language=”r”] # 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”) [/sourcecode]

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:

[sourcecode language=”r”] # 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) [/sourcecode]

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:

[sourcecode language=”r”] # 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) [sourcecode language=”r”] # 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]] [/sourcecode]

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):

[sourcecode language=”r”] # 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 [/sourcecode]

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):

[sourcecode language=”r”] # 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! ;) [/sourcecode]

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:

[sourcecode language=”r”] # 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 ) [/sourcecode]

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 overfitting. Tem 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:

[sourcecode language=”r”] # 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! [/sourcecode]

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:

[sourcecode language=”r”] # 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 [/sourcecode]

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:

[sourcecode language=”r”] # 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 [/sourcecode]

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.

[sourcecode language=”r”] # 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) [/sourcecode] [sourcecode language=”r”] # 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 [/sourcecode]

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:

[sourcecode language=”r”] # sort the grid models by decreasing AUC sortedGrid <- h2o.getGrid(“depth_grid”, sort_by=”auc”, decreasing = TRUE) [/sourcecode] [sourcecode language=”r”] # 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 [/sourcecode]

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

[sourcecode language=”r”] # 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 [/sourcecode]

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:

[sourcecode language=”r”] # 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 [/sourcecode]

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.

[sourcecode language=”r”] # 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) [/sourcecode]

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

[sourcecode language=”r”] # 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 [/sourcecode]

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!