Hardware para Machine Learning: Desafios e oportunidades

Um ótimo paper de como o hardware vai exercer função crucial em alguns anos em relação à Core Machine Learning, em especial em sistemas embarcados.

Hardware for Machine Learning: Challenges and Opportunities

Abstract—Machine learning plays a critical role in extracting meaningful information out of the zetabytes of sensor data collected every day. For some applications, the goal is to analyze and understand the data to identify trends (e.g., surveillance, portable/wearable electronics); in other applications, the goal is to take immediate action based the data (e.g., robotics/drones, self-driving cars, smart Internet of Things). For many of these applications, local embedded processing near the sensor is preferred over the cloud due to privacy or latency concerns, or limitations in the communication bandwidth. However, at the sensor there are often stringent constraints on energy consumption and cost in addition to throughput and accuracy requirements. Furthermore, flexibility is often required such that the processing can be adapted for different applications or environments (e.g., update the weights and model in the classifier). In many applications, machine learning often involves transforming the input data into a higher dimensional space, which, along with programmable weights, increases data movement and consequently energy consumption. In this paper, we will discuss how these challenges can be addressed at various levels of hardware design ranging from architecture, hardware-friendly algorithms, mixed-signal circuits, and advanced technologies (including memories and sensors).

Conclusions: Machine learning is an important area of research with many promising applications and opportunities for innovation at various levels of hardware design. During the design process, it is important to balance the accuracy, energy, throughput and cost requirements. Since data movement dominates energy consumption, the primary focus of recent research has been to reduce the data movement while maintaining performance accuracy, throughput and cost. This means selecting architectures with favorable memory hierarchies like a spatial array, and developing dataflows that increase data reuse at the low-cost levels of the memory hierarchy. With joint design of algorithm and hardware, reduced bitwidth precision, increased sparsity and compression are used to minimize the data movement requirements. With mixed-signal circuit design and advanced technologies, computation is moved closer to the source by embedding computation near or within the sensor and the memories. One should also consider the interactions between these different levels. For instance, reducing the bitwidth through hardware-friendly algorithm design enables reduced precision processing with mixed-signal circuits and non-volatile memory. Reducing the cost of memory access with advanced technologies could result in more energy-efficient dataflows.

Hardware para Machine Learning: Desafios e oportunidades

Um novo operador Softmax para Aprendizado por Reforço

Em alguns problemas de classificação para fugir do espaço restrito dos scores da probabilidade (suma que vai até um) de uma determinada tupla pertencer a uma classe, o operador softmax faz o mapeamento de um vetor para uma probabilidade de uma determinada classe em problemas de classificação.

No Quora tem uma ótima definição dessa função, e de como ela é utilizada como função de ativação de forma combinada em uma rede neural através da transmissão via axiônios.

A função Softmax Logística é definida como:

Onde o θ representa um vetor de pesos, e x é um vetor de valores de input, em que essa função produz um output escalar definido por hθ(x),0<hθ(x)<1. Para quem quiser se aprofundar mais essa explicação está definitivamente matadora.

Essa pequena introdução foi para mostrar esse artigo abaixo que tem uma ótima abordagem para o Softmax no contexto de aprendizado por reforço. Enjoy.

A New Softmax Operator for Reinforcement Learning

Abstract:  A softmax operator applied to a set of values acts somewhat like the maximization function and somewhat like an average. In sequential decision making, softmax is often used in settings where it is necessary to maximize utility but also to hedge against problems that arise from putting all of one’s weight behind a single maximum utility decision. The Boltzmann softmax operator is the most commonly used softmax operator in this setting, but we show that this operator is prone to misbehavior. In this work, we study an alternative softmax operator that, among other properties, is both a non-expansion (ensuring convergent behavior in learning and planning) and differentiable (making it possible to improve decisions via gradient descent methods). We provide proofs of these properties and present empirical comparisons between various softmax operators.

Conclusions: We proposed the mellowmax operator as an alternative for the Boltzmann operator. We showed that mellowmax has several desirable properties and that it works favorably in practice. Arguably, mellowmax could be used in place of Boltzmann throughout reinforcement-learning research. Important future work is to expand the scope of investigation to the function approximation setting in which the state space or the action space is large and abstraction techniques are used. We expect mellowmax operator and its non-expansion property to behave more consistently than the Boltzmann operator when estimates of state–action values can be arbitrarily inaccurate. Another direction is to analyze the fixed point of planning, reinforcement-learning, and game-playing algorithms when using softmax and mellowmax operators. In particular, an interesting analysis could be one that bounds the suboptimality of fixed points found by value iteration under each operator. Finally, due to the convexity (Boyd & Vandenberghe, 2004) of mellowmax, it is compelling to use this operator in a gradient ascent algorithm in the context of sequential decision making. Inverse reinforcement-learning algorithms is a natural candidate given the popularity of softmax in this setting.

Um novo operador Softmax para Aprendizado por Reforço

Medição de tempo de tempo de jogo em aplicativo móvel usando Análise de Sobreviência

A algum tempo eu postei no Github (sorry por sonegar amigos, em breve postarei por aqui) uma proposta de análise de sobrevivência para Telecom e esse paper vem em boa hora para jogar mais luz sobre o tema.

Em momentos em que temos estreitamento de margens de lucro em aplicativos móveis é de fundamental importância o entendimento em relação à dinâmica relativa à saída dos usuários da base ativa.

Esse artigo mostra uma ótima perspectiva em relação à aplicação de análise de sobrevivência para maximização do tempo em que os jogadores permanecem no aplicativo.

Playtime Measurement with Survival Analysis – Markus Viljanen, Antti Airola, Jukka Heikkonen, Tapio Pahikkala

Abstract: Maximizing product use is a central goal of many businesses, which makes retention and monetization two central analytics metrics in games. Player retention may refer to various duration variables quantifying product use: total playtime or session playtime are popular research targets, and active playtime is well-suited for subscription games. Such research often has the goal of increasing player retention or conversely decreasing player churn. Survival analysis is a framework of powerful tools well suited for retention type data. This paper contributes new methods to game analytics on how playtime can be analyzed using survival analysis without covariates. Survival and hazard estimates provide both a visual and an analytic interpretation of the playtime phenomena as a funnel type nonparametric estimate. Metrics based on the survival curve can be used to aggregate this playtime information into a single statistic. Comparison of survival curves between cohorts provides a scientific AB-test. All these methods work on censored data and enable computation of confidence intervals. This is especially important in time and sample limited data which occurs during game development. Throughout this paper, we illustrate the application of these methods to real world game development problems on the Hipster Sheep mobile game.

Conclusions: In this study, we demonstrated that survival analysis can be used to measure retention in games. Positive, skewed and censored duration data make it a very natural and powerful tool for this purpose. Duration variables quantifying retention such as playtime, session time and subscription time, even game progression, may be analyzed with the methods of survival analysis. In this study we used a real world game development example with focus on total playtime. We presented the basic foundation of survival analysis, which argued that the phenomena may be analyzed in a simple way through the churn rate or its complement, the retention rate. The study focused on three key motivations for survival analysis based measurement: computing survival curves, deriving survival metrics and comparing survival data. These methods contribute towards scientific data analysis by presenting methods new to game analytics, which are also able to deal with censoring and utilize statistical significance tests. For computing survival curves and cumulative hazards, we presented the Kaplan-Meier and the Nelson-Aalen estimate. Kernel methods may be used to compute the churn rate and produce smooth nonparametric survival curves. For metrics, we discussed how the hazard is an improvement over using the survival curve as a funnel type estimate. Utilized widely in reliability engineering, adopting it for game analytics is especially useful in retention and progression analysis to detect deviations from the natural pattern of constant rates. Furthermore, the mean and the median playtime metrics were derived from the survival curve with confidence intervals. For survival comparison, we used the log-rank statistical test to perform a test of the null hypothesis that the survival curves are equal. The test may be extended to stratify over covariates and compare multiple cohorts. This method enables scientific AB testing of game version quality, for example The reader may take advantage of Table 8 to use the methods for applications. It lists the methods we have presented and the R software functions implementing them. In summary, survival analysis motivated functions, metrics and comparisons provide multiple tools to utilize for retention and progression measurement in game development. We think that the field has a large potential to contribute to scientific game analytics and anticipate further research on this topic.


Medição de tempo de tempo de jogo em aplicativo móvel usando Análise de Sobreviência

Churn-at-Risk: Aplicação de Survival Analysis no controle de churn de assinaturas em Telecom


Um dos assuntos mais recorrentes em qualquer tipo de serviço de assinatura é como reduzir o Churn (saída de clientes), dado que conquistar novos clientes é bem mais difícil (e caro) do que manter os antigos.

Cerca de 70% das empresas sabem que é mais barato manter um cliente do que ter que ir atrás de um novo.

Fazendo uma analogia simples, o lucro dos serviços de assinatura são como uma espécie de sangue na corrente sanguínea de uma empresa e uma interrupção de qualquer natureza prejudica todo o negócio, dado que esse é um modelo de receita que se baseia na recorrência de tarifação e não no desenvolvimento, ou mesmo venda de outros produtos.

Em modelos de negócios baseados no volume de pessoas que estão dispostas a terem uma cobrança recorrente o negócio fica bem mais complicado, dado que diferentemente de produtos que tem uma elasticidade maior o fluxo de receita é extremamente sujeito aos sabores do mercado e dos clientes.

Dentro desse cenário, para todas as empresas que tem o seu fluxo de receita baseado nesse tipo de business, saber quando um cliente entrará em uma situação de saída através do cancelamento do serviço (Churn) é fundamental para criar mecanismos de retenção mais efetivos, ou mesmo criação de réguas de contato com os clientes para evitar ou minimizar a chance de um cliente sair da base de dados.

Sendo assim, qualquer mecanismo ou mesmo esforço para minimizar esse efeito é de grande valia. Nos baseamos na teoria estatística buscar respostas para as seguintes perguntas:

  • Como diminuir o Churn?
  • Como identificar um potencial cliente que irá entrar em uma situação de Churn? Quais estratégias seguir para minimizar esse Churn?
  • Quais réguas de comunicação com os clientes devemos ter para entender os motivos que estão fazendo um assinante cancelar o serviço e quais são as estratégias de customer winback possíveis nesse cenário?

E pra responder essa pergunta, fomos buscar as respostas na análise de sobrevivência dado que essa área da estatística é uma das que lidam melhor em termos de probabilidade de tempo de vida com dados censurados, seja de materiais (e.g. tempo de falha de algum sistema mecânico) ou no tempo de vida de pessoas propriamente ditas (e.g. dado uma determinada posologia qual é a estimativa de um paciente sobreviver a um câncer), e no nosso caso quanto tempo de vida um assinante tem até deixar cancelar a sua assinatura.

Análise de Sobrevivência

A análise de sobreviência é uma técnica estatístisca que foi desenvolvida na medicina e tem como principal finalidade estimar o tempo de sobrevivência ou tempo de morte de um determinado paciente dentro de um horizonte do tempo.

O estimador de Kaplan-Meier (1958) utiliza uma função de sobrevivência que leva em consideração uma divisão entre o número de observações que não falharam no tempo t pelo número total de observações no estudo em que cada intervalo de tempo tem-se o número de falhas/mortes/churn distintos bem como é calculado o risco de acordo com o número de indivíduos restantes no tempo subsequente.

Já o estimador Nelson-Aalen (1978) é um estimador que tem as mesmas características do Kaplan-Meier, com a diferença que esse estimador trabalha com uma função de sobrevivência que é a cumulative hazard rate function.

Os elementos fundamentais para caracterização de um estudo que envolve análise de sobrevivência são, o (a) tempo inicial, (b)escala de medida do intervalo de tempo e (c) se o evento de churn ocorreu.

Os principais artigos são de Aalen (1978), Kaplan-Meier (1958) e Cox (1972).

Esse post não tem como principal objetivo dar algum tipo de introdução sobre survival analysis, dado que tem muitas referências na internet sobre o assunto e não há nada a ser acrescentado nesse sentido por este pobre blogueiro.

Assim como a análise de cohort, a análise de sobrevivência tem como principal característica ser um estudo de natureza longitudinal, isto é, os seus resultados tem uma característica de temporalidade seja em aspectos de retrospecção, quanto em termos de perspectivas, isso é, tem uma resposta tipicamente temporal para um determinado evento de interesse.

O que vamos usar como forma de comparação amostral é o comportamento longitudinal, de acordo com determinadas características de amostragens diferentes ao longo do tempo, e os fatores que influenciam no churn.

Devido a questões óbvias de NDA não vamos postar aqui características que possam indicar qualquer estratégia de negócios ou mesmo caracterização de alguma informação de qualquer natureza.

Podemos dizer que a análise de sobrevivência aplicada em um caso de telecom, pode ajudar ter uma estimativa em forma de probabilidade em relação ao tempo em que uma assinatura vai durar até o evento de churn (cancelamento) e dessa forma elaborar estratégias para evitar esse evento, dado que adquirir um novo cliente é mais caro do que manter um novo e entra totalmente dentro de uma estratégia de Customer Winback (Nota: Esse livro Customer Winback do Jill Griffin e do Michael Lowenstein é obrigatório para todos que trabalham com serviços de assinaturas ou negócios que dependam de uma recorrência muito grande como comércio).

No nosso caso o tempo de falha ou tempo de morte, como estamos falando de serviços de assinaturas, o nosso evento de interesse seria o churn, ou cancelamento da assinatura. Em outras palavras teríamos algo do tipo Time-to-Churn ou um Churn-at-Risk. Guardem esse termo.


Usamos dados de dois produtos antigos em que os dados foram anonimizados e aplicados um hash de embaralhamento uniforme (que obedece uma distribuição específica) nos atributos (por questões de privacidade) que são:

  • id = Identificador do registro;
  • product = produto;
  • channel = canal no qual o cliente entrou na base de dados;
  • free_user = flag que indica se o cliente entrou na base em gratuidade ou não;
  • user_plan = se o usuário é pré-pago ou pós-pago;
  • t = tempo que o assinante está na base de dados; e
  • c = informa se o evento de interesse (no caso o churn (cancelamento da assinatura) ocorreu ou não.

Eliminamos o efeito de censura à esquerda retirando os casos de reativações, dado que queríamos entender a jornada do assinante como um todo sem nenhum tipo de viés relativo a questões de customer winback. Em relação à censura à direita temos alguns casos bem específicos que já se passaram alguns meses desde que essa base de dados foi extraída.

Um aspecto técnico importante a ser considerado é que esses dois produtos estão em categorias de comparabilidade, dado que sem isso nenhum tipo de caractericação seria nula.

No fim dessa implementação teremos uma tabela de vida em relação a esses produtos.


Primeiramente vamos importar as bibliotecas: Pandas (para manipulação de dados), matplotlib (para a geração de gráficos), e lifelines para aplicação da análise de sobrevivência:

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import lifelines

Após realizar a importação das bibliotecas, vamos ajustar o tamanho das imagens para uma melhor visualização:

%pylab inline
pylab.rcParams['figure.figsize'] = (14, 9)

Vamos realizar o upload da nossa base de dados criando um objecto chamado df e usando a classe read_csv do Pandas:

df = pd.read_csv('https://raw.githubusercontent.com/fclesio/learning-space/master/Datasets/07%20-%20Survival/survival_data.csv')

Vamos checar a nossa base de dados:

id product channel free_user user_plan t c
0 3315 B HH 1 0 22 0
1 2372 A FF 1 1 16 0
2 1098 B HH 1 1 22 0
3 2758 B HH 1 1 4 1
4 2377 A FF 1 1 29 0

Então como podemos ver temos as 7 variáveis na nossa base de dados.

Na sequência vamos importar a biblioteca do Lifelines, em especial o estimador de KaplanMaier:

from lifelines import KaplanMeierFitter

kmf = KaplanMeierFitter()

Após realizar a importação da classe relativa ao estimador de Kaplan Meier no objeto kmf, vamos atribuir as nossas variáveis de tempo (T) e evento de interesse (C)

T = df["t"]

C = df["c"]

O que foi feito anteriormente é que buscamos no dataframe df o array t e atribuímos no objeto T, e buscamos o array da coluna c no dataframe e atribuímos no objeto C.

Agora vamos chamar o método fit usando esses dois objetos no snippet abaixo:

kmf.fit(T, event_observed=C )
<lifelines.KaplanMeierFitter: fitted with 10000 observations, 6000 censored>
Objeto ajustado, vamos agora ver o gráfico relativo a esse objeto usando o estimador de Kaplan Meier.
plt.title('Survival function of Service Valued Add Products');
plt.ylabel('Probability of Living (%)')
plt.xlabel('Lifespan of the subscription (in days)')
<matplotlib.text.Text at 0x101b24a90>

Como podemos ver no gráfico, temos algumas observações pertinentes, quando tratamos a probabilidade de sobrevivência desses dois produtos no agregado que são:

  • Logo no primeiro dia há uma redução substancial do tempo de sobrevivência da assinatura em aproximadamente 22%;
  • Há um decaimento quase que linear depois do quinto dia de assinatura; e
  • Depois do dia número 30, a probabilidade de sobrevivência de uma assinatura é de aproximadamente de 50%. Em outras palavras: depois de 30 dias, metade dos novos assinantes já estarão fora da base de assinantes.

No entanto, vamos plotar a mesma função de sobrevivência considerando os intervalos de confiança estatística.

plt.title('Survival function of Service Valued Add Products - Confidence Interval in 85-95%');
plt.ylabel('Probability of Living (%)')
plt.xlabel('Lifespan of the subscription')
<matplotlib.text.Text at 0x10ad8e0f0>

Contudo nesse modelo inicial temos duas limitações claras que são:

  • Os dados no agregado não dizem muito em relação à dinâmicas que podem estar na especificidade de alguns atributos/dimensões;
  • Não são exploradas as dimensões (ou quebras) de acordo com os atributos que vieram na base de dados; e
  • Não há a divisão por produto.

Para isso, vamos começar a entrar no detalhe em relação a cada uma das dimensões e ver o que cada uma tem de influência em relação à função de sobrevivência.

Vamos começar realizando a quebra pela dimensão que determina se o cliente entrou via gratuidade ou não (free_user).

ax = plt.subplot(111)

free = (df["free_user"] == 1)
kmf.fit(T[free], event_observed=C[free], label="Free Users")
kmf.plot(ax=ax, ci_force_lines=True)
kmf.fit(T[~free], event_observed=C[~free], label="Non-Free Users")
kmf.plot(ax=ax, ci_force_lines=True)
plt.title("Lifespans of different subscription types");
plt.ylabel('Probability of Living (%)')
<matplotlib.text.Text at 0x10ad8e908>

Este gráfico apresenta algumas informações importantes para os primeiros insights em relação a cada uma das curvas de sobrevivência em relação ao tipo de gratuidade oferecida como fator de influência para o churn que são:

  • Os assinantes que entram como não gratuitos (i.e. não tem nenhum tipo de gratuidade inicial) após o 15o dia apresenta um decaimento brutal de mais de 40% da chance de sobrevivência (tratando-se do intervalo de confiança);
  • Após o 15o dia os assinantes que não desfrutam de gratuidade tem a sua curva de sobrevivência em uma relativa estabilidade em torno de 60% na probabilidade de sobrevivência até o período censurado;
  • Ainda nos usuários sem gratuidade, dado o grau de variabilidade do intervalo de confiança podemos tirar como conclusão que muitos cancelamentos estão ocorrendo de forma muito acelerada, o que deve ser investigado com mais calma pelo time de produtos; e
  • Já os usuários que entram via gratuidade (i.e. ganham alguns dias grátis antes de serem tarifados) apresenta um nível de decaimento do nível de sobrevivência maior seja no período inicial, quando ao longo do tempo, contudo uma estabilidade é encontrada ao longo de toda a série sem maiores sobressaltos.

Dado essa análise inicial das curvas de sobrevivência, vamos avaliar agora as probabilidades de sobrevivência de acordo com o produto.

ax = plt.subplot(111)

product = (df["product"] == "A")
kmf.fit(T[product], event_observed=C[product], label="Product A")
kmf.plot(ax=ax, ci_force_lines=True)
kmf.fit(T[~product], event_observed=C[~product], label="Product B")
kmf.plot(ax=ax, ci_force_lines=True)

plt.title("Survival Curves of different Products");
plt.ylabel('Probability of Living (%)')
<matplotlib.text.Text at 0x10aeaabe0>

Este gráfico apresenta a primeira distinção entre os dois produtos de uma forma mais clara.

Mesmo com os intervalos de confiança com uma variação de 5%, podemos ver que o produto A (linha azul) tem uma maior probabilidade de sobrevivência com uma diferença percentual de mais de 15%; diferença essa amplificada depois do vigésimo dia.

Em outras palavras: Dado um determinada safra de usuários, caso o usuário entre no produto A o mesmo tem uma probabilidade de retenção de cerca de 15% em relação a um usuário que por ventura entre no produto B, ou o produto A apresenta uma cauda de retenção superior ao produto B.

Empiricamente é sabido que um dos principais fatores de influência de produtos SVA são os canais de mídia os quais esses produtos são oferecidos.

O canal de mídia é o termômetro em que podemos saber se estamos oferencendo os nossos produtos para o público alvo correto.

No entanto para um melhor entendimento, vamos analisar os canais nos quais as assinaturas são originadas.

A priori vamos normalizar a variável channel para realizar a segmentação dos canais de acordo com o conjunto de dados.

df['channel'] = df['channel'].astype('category');
channels = df['channel'].unique()

Após normalização e transformação da variável para o tipo categórico, vamos ver como está o array.

[HH, FF, CC, AA, GG, ..., BB, EE, DD, JJ, ZZ]
Length: 11
Categories (11, object): [HH, FF, CC, AA, ..., EE, DD, JJ, ZZ]

Aqui temos a representação de 11 canais de mídia os quais os clientes entraram no serviço.

Com esses canais, vamos identificar a probabilidade de sobrevivência de acordo com o canal.

for i,channel_type in enumerate(channels):
    ax = plt.subplot(3,4,i+1)
    ix = df['channel'] == channel_type
    kmf.fit( T[ix], C[ix], label=channel_type )
    kmf.plot(ax=ax, legend=True)
    if i==0:
        plt.ylabel('Probability of Survival by Channel (%)')
Fazendo uma análise sobre cada um desses gráficos temos algumas considerações sobre cada um dos canais:
  • HH, DD: Uma alta taxa de mortalidade (churn) logo antes dos primeiros 5 dias, o que indica uma característica de efemeridade ou atratividade no produto para o público desse canal de mídia.
  • FF: Apresenta menos de 10% de taxa de mortalidade nos primeiros 20 dias, e tem um padrão muito particular depois do 25o dia em que praticamente não tem uma mortalidade tão alta. Contém um intervalo de confiança com uma oscilação muito forte.
  • CC: Junto com o HH apesar de ter uma taxa de mortalidade alta antes do 10o dia, apresenta um grau de previsibilidade muito bom, o que pode ser utilizado em estratégias de incentivos de mídia que tenham que ter uma segurança maior em termos de retenção a médio prazo.
  • GG, BB: Apresentam uma boa taxa de sobrevivência no inicio do período, contudo possuem oscilações severas em seus respectivos intervalos de confiança. Essa variável deve ser considerada no momento de elaboração de uma estratégia de investimento nesses canais.
  • JJ: Se houvesse uma definição de incerteza em termos de sobrevivência, esse canal seria o seu melhor representante. Com os seus intervalos de confiança oscilando em mais de 40% em relação ao limite inferior e superior, esse canal de mídia mostra-se extremamente arriscado para os investimentos, dado que não há nenhum tipo de regularidade/previsibilidade de acordo com esses dados.
  • II: Apesar de ter um bom grau de previsibilidade em relação à taxa de sobrevivência nos primeiros 10 dias, após esse período tem uma curva de hazard muito severa, o que indica que esse tipo de canal pode ser usado em uma estratégia de curto prazo.
  • AA, EE, ZZ: Por haver alguma forma de censura nos dados, necessitam de mais análise nesse primeiro momento. (Entrar no detalhe dos dados e ver se é censura à direita ou algum tipo de truncamento).

Agora que já sabemos um pouco da dinâmica de cada canal, vamos criar uma tabela de vida para esses dados.

A tabela de vida nada mais é do que uma representação da função de sobrevivência de forma tabular em relação aos dias de sobrevivência.

Para isso vamos usar a biblioteca utils do lifelines para chegarmos nesse valor.

from lifelines.utils import survival_table_from_events

Biblioteca importada, vamos usar agora as nossas variáveis T e C novamente para realizar o ajuste da tabela de vida.

lifetable = survival_table_from_events(T, C)

Tabela importada, vamos dar uma olhada no conjunto de dados.

print (lifetable)
          removed  observed  censored  entrance  at_risk
0            2250      2247         3     10000    10000
1             676       531       145         0     7750
2             482       337       145         0     7074
3             185       129        56         0     6592
4             232        94       138         0     6407
5             299        85       214         0     6175
6             191        73       118         0     5876
7             127        76        51         0     5685
8             211        75       136         0     5558
9            2924        21      2903         0     5347
10            121        27        94         0     2423
11             46        27        19         0     2302
12             78        26        52         0     2256
13            111        16        95         0     2178
14             55        35        20         0     2067
15            107        29        78         0     2012
16            286        30       256         0     1905
17            156        23       133         0     1619
18            108        18        90         0     1463
19             49        11        38         0     1355
20             50        17        33         0     1306
21             61        13        48         0     1256
22            236        23       213         0     1195
23             99         6        93         0      959
24            168         9       159         0      860
25            171         7       164         0      692
26             58         6        52         0      521
27             77         2        75         0      463
28             29         6        23         0      386
29            105         1       104         0      357
30             69         0        69         0      252
31            183         0       183         0      183

Diferentemente do R que possuí a tabela de vida com a porcentagem relativa à probabilidade de sobrevivência, nesse caso vamos ter que fazer um pequeno ajuste para obter a porcentagem de acordo com o atributo entrance e at_risk.

O ajuste se dará da seguinte forma:

survivaltable = lifetable.at_risk/np.amax(lifetable.entrance)

Ajustes efetuados, vamos ver como está a nossa tabela de vida.

0     1.0000
1     0.7750
2     0.7074
3     0.6592
4     0.6407
5     0.6175
6     0.5876
7     0.5685
8     0.5558
9     0.5347
10    0.2423
11    0.2302
12    0.2256
13    0.2178
14    0.2067
15    0.2012
16    0.1905
17    0.1619
18    0.1463
19    0.1355
20    0.1306
21    0.1256
22    0.1195
23    0.0959
24    0.0860
25    0.0692
26    0.0521
27    0.0463
28    0.0386
29    0.0357
30    0.0252
31    0.0183
Name: at_risk, dtype: float64

Vamos transformar a nossa tabela de vida em um objeto do pandas para melhor manipulação do conjunto de dados.

survtable = pd.DataFrame(survivaltable)

Para casos de atualização de Churn-at-Risk podemos definir uma função que já terá a tabela de vida e poderá fazer a atribuição da probabilidade de sobrevivência de acordo com os dias de sobrevivência.

Para isso vamos fazer uma função simples usando o próprio python.

def survival_probability( int ):
   print ("The probability of Survival after", int, "days is", survtable["at_risk"].iloc[int]*100, "%") 

Nesse caso vamos ver a chance de sobrevivência usando o nosso modelo Kaplan-Meier já ajustado para uma assinatura que tenha 22 dias de vida.

In [22]:
The probability of Survival after 22 days is 11.95 %

Ou seja, essa assinatura tem apenas 11.95% de probabilidade de estar ativa, o que significa que em algum momento muito próximo ela pode vir a ser cancelada.


Como podemos ver acima, usando análise de sobrevivência podemos tirar insights interessantes em relação ao nosso conjunto de dados, em especial para descobrirmos a duração das assinaturas em nossa base de dados, e estimar um tempo até o evento de churn.

Os dados utilizados refletem o comportamento de dois produtos reais, porém, que foram anonimizados por questões óbvias de NDA. Contudo nada impede a utilização e a adaptação desse código para outros experimentos. Um ponto importante em relação a essa base de dados é que como pode ser observado temos uma censura à direita muito acentuada o que limita um pouco a visão dos dados a longo prazo, principalmente se houver algum tipo de cauda longa no evento de churn.

Como coloquei no São Paulo Big Data Meetup de Março há uma série de arquiteturas que podem ser combinadas com esse tipo de análise, em especial métodos de Deep Learning que podem ser um endpoint de um pipeline de predição.

Espero que tenham gostado e quaisquer dúvidas mandem uma mensagem para flavioclesio at gmail.com

PS: Agradecimentos especiais aos meus colegas e revisores Eiti Kimura, Gabriel Franco e Fernanda Eleuterio.

Churn-at-Risk: Aplicação de Survival Analysis no controle de churn de assinaturas em Telecom

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

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

# Info about cluster

# Production Cluster (Not applicable because we're using in the same machine)
#localH2O <- h2o.init(ip = '', port =54321, nthreads=-1) # Server 1
#localH2O <- h2o.init(ip = '', 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

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


  • 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

# 1       0 14047
# 2       1  4030


# 1       0  4697
# 2       1  1285


# 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

# Set independent variables

# 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

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

# 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

# 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 

# 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!)

Uma abordagem híbrida de aprendizado supervisionado com Machine Learning para composição de melodias de forma algorítmica

A hybrid approach to supervised machine learning for algorithmic melody composition

Abstract: In this work we present an algorithm for composing monophonic melodies similar in style to those of a given, phrase annotated, sample of melodies. For implementation, a hybrid approach incorporating parametric Markov models of higher order and a contour concept of phrases is used. This work is based on the master thesis of Thayabaran Kathiresan (2015). An online listening test conducted shows that enhancing a pure Markov model with musically relevant context, like count and planed melody contour, improves the result significantly.

Conclusions: Even though Markov models alone are seen as no proper method for algorithmic composition, we successfully showed that when combined with further methods they can yield much better results in terms of being closer to human composed melodies. This can be seen when comparing our results with the ones of Kathiresan [Kat15], whose basic algorithm solely relies on Markov models. Apart from the previous works, our algorithm outperforms a random guessing baseline, meaning that humans are not able to clearly distinguish its compositions from humans anymore.

Uma abordagem híbrida de aprendizado supervisionado com Machine Learning para composição de melodias de forma algorítmica

Modelagem de tópicos criminais usando Machine Learning

Com o aumento da violência no nosso país (em que temos mais de 60 mil assassinatos por ano) é de fundamental importância que todas as secretarias e demais departamentos burocráticos do estado estejam um passo a frente do crime e não só isso: façam o mapeamento correto das ocorrências para que medidas preventivas  (e.g. patrulhamento, inteligência, et cetera) tenham o máximo de assertividade possível.

E não só isso: com um mapeamento correto, além de questões de policiamento que podem ser corrigidas, mas também questões de tomada de decisão para criação/alteração da legislação podem ser tomadas em bases mais sólidas descartando todo o proselitismo que é feito sobre essa questão.

Crime Topic Modeling – Da Kuang, P. Jeffrey Brantingham, Andrea L. Bertozzi

Abstract: The classification of crime into discrete categories entails a massive loss of information. Crimes emerge out of a complex mix of behaviors and situations, yet most of these details cannot be captured by singular crime type labels. This information loss impacts our ability to not only understand the causes of crime, but also how to develop optimal crime prevention strategies. We apply machine learning methods to short narrative text descriptions accompanying crime records with the goal of discovering ecologically more meaningful latent crime classes. We term these latent classes “crime topics” in reference to text-based topic modeling methods that produce them. We use topic distributions to measure clustering among formally recognized crime types. Crime topics replicate broad distinctions between violent and property crime, but also reveal nuances linked to target characteristics, situational conditions and the tools and methods of attack. Formal crime types are not discrete in topic space. Rather, crime types are distributed across a range of crime topics. Similarly, individual crime topics are distributed across a range of formal crime types. Key ecological groups include identity theft, shoplifting, burglary and theft, car crimes and vandalism, criminal threats and confidence crimes, and violent crimes. Crime topic modeling positions behavioral situations as the focal unit of analysis for crime events. Though unlikely to replace formal legal crime classifications, crime topics provide a unique window into the heterogeneous causal processes underlying crime. We discuss whether automated procedures could be used to cross-check the quality of official crime classifications.

Objectives The classification of crime into discrete categories entails a massive loss of information. Crimes emerge out of a complex mix of behaviors and situations, yet most of these details cannot be captured by singular crime type labels. This information loss impacts our ability to not only understand the causes of crime, but also how to develop optimal crime prevention strategies.
Methods We apply machine learning methods to short narrative text descriptions
accompanying crime records with the goal of discovering ecologically more meaningful latent crime classes. We term these latent classes ‘crime topics’ in reference to text-based topic modeling methods that produce them. We use topic distributions to measure clustering among formally recognized crime types.
Results Crime topics replicate broad distinctions between violent and property crime, but also reveal nuances linked to target characteristics, situational conditions and the tools and methods of attack. Formal crime types are not discrete in topic space. Rather, crime types are distributed across a range of crime topics. Similarly, individual crime topics are distributed across a range of formal crime types. Key ecological groups include identity theft, shoplifting, burglary and theft, car crimes and vandalism, criminal threats and confidence crimes, and violent crimes.
Conclusions Crime topic modeling positions behavioral situations as the focal unit of analysis for crime events. Though unlikely to replace formal legal crime classifications, crime topics provide a unique window into the heterogeneous causal processes underlying crime. 


Modelagem de tópicos criminais usando Machine Learning

Deep Learning para análise de séries temporais

Por mais que problemas de reconhecimento de imagens, ou mesmo de segmentação sonora estejam em alta em Deep Learning, 90% dos problemas do mundo quando falamos de dados, passam por dados estruturados, em especial séries temporais. Esse paper mostra uma metodologia pouco convencional (a transformação de séries temporais em uma ‘imagem’ para o uso de uma Rede Coevolucionária) mas que pode mostrar que o céu é o limite quando falamos de arranjos para solução de problemas de predição usando dados estruturados.

Deep Learning for Time-Series Analysis – John Cristian Borges Gamboa

Abstract: In many real-world application, e.g., speech recognition or sleep stage classification, data are captured over the course of time, constituting a Time-Series. Time-Series often contain temporal dependencies that cause two otherwise identical points of time to belong to different classes or predict different behavior. This characteristic generally increases the difficulty of analysing them. Existing techniques often depended on hand-crafted features that were expensive to create and required expert knowledge of the field. With the advent of Deep Learning new models of unsupervised learning of features for Time-series analysis and forecast have been developed. Such new developments are the topic of this paper: a review of the main Deep Learning techniques is presented, and some applications on Time-Series analysis are summaried. The results make it clear that Deep Learning has a lot to contribute to the field.

Conclusions: When applying Deep Learning, one seeks to stack several independent neural network layers that, working together, produce better results than the already existing shallow structures. In this paper, we have reviewed some of these modules, as well the recent work that has been done by using them, found in the literature. Additionally, we have discussed some of the main tasks normally performed when manipulating Time-Series data using deep neural network structures. Finally, a more specific focus was given on one work performing each one of these tasks. Employing Deep Learning to Time-Series analysis has yielded results in these cases that are better than the previously existing techniques, which is an evidence that this is a promising field for improvement.


Deep Learning para análise de séries temporais

DeepStack: Sistema Especialista de Inteligência Artificial para o jogo de Poker

Esse paper to DeepStack, caso seja reprodutível, pode representar um avanço significativo em relação a todo eixo em que a Inteligência Artificial está hoje, em especial em problemas de informação assimétrica.

Como os autores salientam, jogos de Damas, Xadrez e Go partem de um princípio básico de que a informação é simétrica entre os jogadores; em outras palavras, há um determinado determinismo em relação às ações dos adversários.

O Poker por sua vez tem como principal característica ser um jogo em que há um algo grau de não-determinismo seja no River, na mão (cartas) dos oponentes, bem como no tão famigerado blefe (que não passa de um bom problema estocástico).

De qualquer maneira, para quem é especialista ou não em AI ou Machine Learning vale a pena conferir a modelagem e os resultados do Deep Stack.

DeepStack: Expert-Level Artificial Intelligence in No-Limit Poker

Abstract: Artificial intelligence has seen a number of breakthroughs in recent years, with games often serving as significant milestones. A common feature of games with these successes is that they involve information symmetry among the players, where all players have identical information. This property of perfect information, though, is far more common in games than in real-world problems. Poker is the quintessential game of imperfect information, and it has been a longstanding challenge problem in artificial intelligence. In this paper we introduce DeepStack, a new algorithm for imperfect information settings such as poker. It combines recursive reasoning to handle information asymmetry, decomposition to focus computation on the relevant decision, and a form of intuition about arbitrary poker situations that is automatically learned from selfplay games using deep learning. In a study involving dozens of participants and 44,000 hands of poker, DeepStack becomes the first computer program to beat professional poker players in heads-up no-limit Texas hold’em. Furthermore, we show this approach dramatically reduces worst-case exploitability compared to the abstraction paradigm that has been favored for over a decade

Conclusions: DeepStack is the first computer program to defeat professional poker players at heads-up nolimit Texas Hold’em, an imperfect information game with 10160 decision points. Notably it achieves this goal with almost no domain knowledge or training from expert human games. The implications go beyond just being a significant milestone for artificial intelligence. DeepStack is a paradigmatic shift in approximating solutions to large, sequential imperfect information games. Abstraction and offline computation of complete strategies has been the dominant approach for almost 20 years (29,36,37). DeepStack allows computation to be focused on specific situations that arise when making decisions and the use of automatically trained value functions. These are two of the core principles that have powered successes in perfect information games, albeit conceptually simpler to implement in those settings. As a result, for the first time the gap between the largest perfect and imperfect information games to have been mastered is mostly closed. As “real life consists of bluffing… deception… asking yourself what is the other man going to think” (9), DeepStack also has implications for seeing powerful AI applied more in settings that do not fit the perfect information assumption. The old paradigm for handling imperfect information has shown promise in applications like defending strategic resources (38) and robust decision making as needed for medical treatment recommendations (39). The new paradigm will hopefully open up many more possibilities.


DeepStack: Sistema Especialista de Inteligência Artificial para o jogo de Poker

Previsão de retornos em Hedge Funds e seleção de fundos: Uma abordagem com Machine Learning

Apesar dos bons resultados o maior diferencial desse artigo é a metodologia em que os autores dividiram os fundos em quatro categorias que são equity, event-driven, macro, e relative value e realizaram análises do tipo cross-sectional para mensuração de performance. Sem dúvidas um bom artigo para quem queira trabalhar com esse tipo de fundo, ou mesmo ter o próprio fundo particular usando Machine Learning.

Hedge Fund Return Prediction and Fund Selection: A Machine-Learning Approach – Jiaqi Chen, Wenbo Wu, and Michael L. Tindall – Federal Reserve Bank of Dallas 

Abstract: A machine-learning approach is employed to forecast hedge fund returns and perform individual hedge fund selection within major hedge fund style categories. Hedge fund selection is treated as a cross-sectional supervised learning process based on direct forecasts of future returns. The inputs to the machine-learning models are observed hedge fund characteristics. Various learning processes including the lasso, random forest methods, gradient boosting methods, and deep neural networks are applied to predict fund performance. They all outperform the corresponding style index as well as a benchmark model, which forecasts hedge fund returns using macroeconomic variables. The best results are obtained from machine-learning processes that utilize model averaging, model shrinkage, and nonlinear interactions among the factors.

Conclusions: We propose a supervised machine-learning approach to forecast hedge fund returns and select hedge funds quantitatively. The framework is based on cross-sectional forecasts of hedge fund returns utilizing a set of 17 factors. The approach allows the investor to identify funds that are likely to perform well and to construct the corresponding portfolios. We find that our method is applicable across hedge fund style categories. Focusing on factors constructed from characteristics idiosyncratic to individual funds, our models offer distinctive perspectives when compared to models that are driven by macroeconomic variables. Retrospectively, when benchmarked against a traditional factor model, our machine-learning approach generates portfolios with large alphas. The relatively low explanatory power of the regressions indicates that most of the performance of the algorithm-generated portfolios is due to success in identifying funds likely to deliver good performance. Our approach is flexible enough to incorporate new developments both in risk-factor research field and in the machine-learning field.


Previsão de retornos em Hedge Funds e seleção de fundos: Uma abordagem com Machine Learning

Learning Pulse: Uma abordagem de Machine Learning para previsão de performance em regimes auto-regulados de aprendizado usando dados multimodais

Todo mundo sabe que educação é um assunto muito atual nos dias de hoje, e o principal: como usar os smartphones para que os mesmos saiam de vilões da atenção para uma ferramenta de monitoramento e acompanhamento do desempenho acadêmico?

Esse artigo trás uma resposta interessante sobre esse tema.

Learning Pulse: a machine learning approach for predicting performance in self-regulated learning using multimodal data


Abstract: Learning Pulse explores whether using a machine learning approach on multimodal data such as heart rate, step count, weather condition and learning activity can be used to predict learning performance in self-regulated learning settings. An experiment was carried out lasting eight weeks involving PhD students as participants, each of them wearing a Fitbit HR wristband and having their application on their computer recorded during their learning and working activities throughout the day. A software infrastructure for collecting multimodal learning experiences was implemented. As part of this infrastructure a Data Processing Application was developed to pre-process, analyse and generate predictions to provide feedback to the users about their learning performance. Data from different sources were stored using the xAPI standard into a cloud-based Learning Record Store. The participants of the experiment were asked to rate their learning experience through an Activity Rating Tool indicating their perceived level of productivity, stress, challenge and abilities. These self-reported performance indicators were used as markers to train a Linear Mixed Effect Model to generate learner-specific predictions of the learning performance. We discuss the advantages and the limitations of the used approach, highlighting further development points.


Conclusions: This paper described Learning Pulse, an exploratory study whose aim was to use predictive modelling to generate timely predictions about learners’ performance during self-regulated learning by collecting multimodal data about their body, activity and context. Although the prediction accuracy with the data sources and experimental setup chosen in Learning Pulse led to modest results, all the research questions have been answered positively and have lead towards new insights on the storing, modelling and processing multimodal data. We raise some of the unsolved challenges that can be considered a research agenda for future work in the field of Predictive Learning Analytics with “beyond-LMS” multimodal data. The ones identified are: 1) the number of self-reports vs unobtrusiveness; 2) the homogeneity of the learning task specifications; 3) the approach to model random effects; 4) alternative machine learning techniques. There is a clear trade-off between the frequency of selfreports and the seamlessness of the data collection. The number of self-reports cannot be increased without worsening the quality of the learning process observed. On the other side, having a high number of labels is essential to make supervised machine learning work correctly. In addition, a more robust way of modelling random effects must be found. The found solution to group them manually into categories is not scalable. Learning is inevitably made up by random effects, i.e. by voluntary and unpredictable actions taken by the learners. The sequence of such events is also important and must be taken into account with appropriate models. As an alternative to supervised learning techniques, also unsupervised methods can be investigated, as with those methods fine graining the data into small intervals does not generate problems with matching the corresponding labels also the amount of labels is no longer needed. Regarding the experimental setup, it would be best to have a set of coherent learning tasks that the participants of the experiment need to accomplish, contrarily to as it was done in Learning Pulse, where the participants had completely different tasks, topics and working rhythms. It would be also useful to have a baseline group of participants, which do not have access to the visualisations while another group does have access; that would allow to see the difference of performance, whether there is an actual increase. To conclude, Learning Pulse set the first steps towards a new and exciting research direction, the design and the development of predictive learning analytics systems exploiting multimodal data about the learners, their contexts and their activities with the aim to predict their current learning state and thus being able to generate timely feedback for learning support.



Learning Pulse: Uma abordagem de Machine Learning para previsão de performance em regimes auto-regulados de aprendizado usando dados multimodais

Comparação entre um modelo de Machine Learning e EuroSCOREII na previsão de mortalidade após cirurgia cardíaca eletiva

Mais um estudo colocando  alguns algoritmos de Machine Learning contra métodos tradicionais de scoring, e levando a melhor.

A Comparison of a Machine Learning Model with EuroSCORE II in Predicting Mortality after Elective Cardiac Surgery: A Decision Curve Analysis

Abstract: The benefits of cardiac surgery are sometimes difficult to predict and the decision to operate on a given individual is complex. Machine Learning and Decision Curve Analysis (DCA) are recent methods developed to create and evaluate prediction models.

Methods and finding: We conducted a retrospective cohort study using a prospective collected database from December 2005 to December 2012, from a cardiac surgical center at University Hospital. The different models of prediction of mortality in-hospital after elective cardiac surgery, including EuroSCORE II, a logistic regression model and a machine learning model, were compared by ROC and DCA. Of the 6,520 patients having elective cardiac surgery with cardiopulmonary bypass, 6.3% died. Mean age was 63.4 years old (standard deviation 14.4), and mean EuroSCORE II was 3.7 (4.8) %. The area under ROC curve (IC95%) for the machine learning model (0.795 (0.755–0.834)) was significantly higher than EuroSCORE II or the logistic regression model (respectively, 0.737 (0.691–0.783) and 0.742 (0.698–0.785), p < 0.0001). Decision Curve Analysis showed that the machine learning model, in this monocentric study, has a greater benefit whatever the probability threshold.

Conclusions: According to ROC and DCA, machine learning model is more accurate in predicting mortality after elective cardiac surgery than EuroSCORE II. These results confirm the use of machine learning methods in the field of medical prediction.

Comparação entre um modelo de Machine Learning e EuroSCOREII na previsão de mortalidade após cirurgia cardíaca eletiva

Prevendo recessões econômicas usando algoritmos de Machine Learning

Paper bem atual que fala como os autores erraram a crise apenas em relação ao ano mostrando o potencial das Random Forests.


Predicting Economic Recessions Using Machine Learning Algorithms – Rickard Nyman and Paul Ormerod

Abstract Even at the beginning of 2008, the economic recession of 2008/09 was not being predicted by the economic forecasting community. The failure to predict recessions is a persistent theme in economic forecasting. The Survey of Professional Forecasters (SPF) provides data on predictions made for the growth of total output, GDP, in the United States for one, two, three and four quarters ahead, going back to the end of the 1960s. Over a three quarters ahead horizon, the mean prediction made for GDP growth has never been negative over this period. The correlation between the mean SPF three quarters ahead forecast and the data is very low, and over the most recent 25 years is not significantly different from zero. Here, we show that the machine learning technique of random forests has the potential to give early warning of recessions. We use a small set of explanatory variables from financial markets which would have been available to a forecaster at the time of making the forecast. We train the algorithm over the 1970Q2-1990Q1 period, and make predictions one, three and six quarters ahead. We then re-train over 1970Q2-1990Q2 and make a further set of predictions, and so on. We did not attempt any optimisation of predictions, using only the default input parameters to the algorithm we downloaded in the package R. We compare the predictions made from 1990 to the present with the actual data. One quarter ahead, the algorithm is not able to improve on the SPF predictions. Three and six quarters ahead, the correlations between actual and predicted are low, but they are very significantly different from zero. Although the timing is slightly wrong, a serious downturn in the first half of 2009 could have been predicted six quarters ahead in late 2007. The algorithm never predicts a recession when one did not occur. We obtain even stronger results with random forest machine learning techniques in the case of the United Kingdom.

Conclusions: We have tried, as far as it is possible, to replicate an actual forecasting situation starting for the United States in 1990Q2 and moving forward a quarter at a time through to 2016. We use a small number of lags on a small number of financial variables in order to make predictions. In terms of one step ahead predictions of real GDP growth, we have not been able to improve upon the mean forecasts made by the Society of Professional Forecasters. However, even just three quarters ahead, the SPF track record is very poor. A regression of actual GDP growth on the mean prediction made three quarters previously has zero explanatory power, and the SPF predictions never indicated a single quarter of negative growth. The random forest approach improves very considerably on this. Even more strikingly, over a six period ahead horizon, the random forest approach would have predicted, during the winter of 2007/08, a severe recession in the United States during 2009, ending in 2009Q4. Again to emphasise, we have not attempted in any way to optimise these results in an ex post manner. We use only the default values of the input parameters into the machine learning algorithm, and use only a small number of explanatory variables. We obtain qualitatively similar results for the UK, though the predictive power of the random forest algorithm is even better than it is for the United States. As Ormerod and Mounfield (2000) show, using modern signal processing techniques, the time series GDP growth data is dominated by noise rather than by signal. So there is almost certainly a quite restrictive upper bound on the degree of accuracy of prediction which can be achieved. However, machine learning techniques do seem to have considerable promise in extending useful forecasting horizons and providing better information to policy makers over such horizons.

Prevendo recessões econômicas usando algoritmos de Machine Learning

Abordagem de Machine Learning para descoberta de regras para performance de guitarra jazz

Um estudo muito interessante de padrões de guitarra Jazz.

A Machine Learning Approach to Discover Rules for Expressive Performance Actions in Jazz Guitar Music

Expert musicians introduce expression in their performances by manipulating sound properties such as timing, energy, pitch, and timbre. Here, we present a data driven computational approach to induce expressive performance rule models for note duration, onset, energy, and ornamentation transformations in jazz guitar music. We extract high-level features from a set of 16 commercial audio recordings (and corresponding music scores) of jazz guitarist Grant Green in order to characterize the expression in the pieces. We apply machine learning techniques to the resulting features to learn expressive performance rule models. We (1) quantitatively evaluate the accuracy of the induced models, (2) analyse the relative importance of the considered musical features, (3) discuss some of the learnt expressive performance rules in the context of previous work, and (4) assess their generailty. The accuracies of the induced predictive models is significantly above base-line levels indicating that the audio performances and the musical features extracted contain sufficient information to automatically learn informative expressive performance patterns. Feature analysis shows that the most important musical features for predicting expressive transformations are note duration, pitch, metrical strength, phrase position, Narmour structure, and tempo and key of the piece. Similarities and differences between the induced expressive rules and the rules reported in the literature were found. Differences may be due to the fact that most previously studied performance data has consisted of classical music recordings. Finally, the rules’ performer specificity/generality is assessed by applying the induced rules to performances of the same pieces performed by two other professional jazz guitar players. Results show a consistency in the ornamentation patterns between Grant Green and the other two musicians, which may be interpreted as a good indicator for generality of the ornamentation rules.

Algumas das regras encontradas

3.1.2. Duration Rules

• D1: IF note is the final note of a phrase AND the note appears in the third position of an IP (Narmour) structure THEN shorten note
• D2: IF note duration is longer than a dotted half note AND tempo is Medium (90–160 BPM) THEN shorten note
• D3: IF note duration is less than an eighth note AND note is in a very strong metrical position THEN lengthen note.
3.1.3. Onset Deviation Rules

• T1: IF the note duration is short AND piece is up-tempo (≥ 180 BPM) THEN advance note
• T2: IF the duration of the previous note is nominal AND the note’s metrical strength is very strong THEN advance note
• T3: IF the duration of the previous note is short AND piece is up-tempo (≥ 180 BPM) THEN advance note
• T4: IF the tempo is medium (90–160 BPM) AND the note is played within a tonic chord AND the next note’s duration is not short nor long THEN delay note
3.1.4. Energy Deviation Rules

• E1: IF the interval with next note is ascending AND the note pitch not high (lower than B3) THEN play piano
• E2: IF the interval with next note is descending AND the note pitch is very high (higher than C5) THEN play forte
• E3: IF the note is an eight note AND note is the initial note of a phrase THEN play forte.

Conclusões do estudo

Concretely, the obtained accuracies (over the base-line) for the ornamentation, duration, onset, and energy models of 70%(67%), 56%(50%), 63%(54%), and 52%(43%), respectively. Both the features selected and model rules showed musical significance. Similarities and differences among the obtained rules and the ones reported in the literature were discussed. Pattern similarities between classical and jazz music expressive rules were identified, as well as expected dissimilarities expected by the inherent particular musical aspects of each tradition. The induced rules specificity/generality was assessed by applying them to performances of the same pieces performed by two other professional jazz guitar players. Results show a consistency in the ornamentation patterns between Grant Green and the other two musicians, which may be interpreted as a good indicator for generality of the ornamentation rules.


Abordagem de Machine Learning para descoberta de regras para performance de guitarra jazz

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

Hibridização de modelos de Machine Learning pessoais e impessoais para reconhecimento de atividades nos dispositivos móveis

Para quem ainda tem dúvidas que em breve termos modelos de Machine Learning em nossos dispositivos móveis para identificar diversos comportamentos como andar, estar movimento em um veículo automotor, ou mesmo em situações de buffer (i.e. filas, ou outras situações que estamos parados) esse paper mostra um ótimo caminho de implementação.

Hybridizing Personal and Impersonal Machine Learning Models for Activity Recognition on Mobile Devices

Abstract: Recognition of human activities, using smart phones and wearable devices, has attracted much attention recently. The machine learning (ML) approach to human activity recognition can broadly be classified into two categories: training an ML model on (i) an impersonal dataset or (ii) a personal dataset. Previous research shows that models learned from personal datasets can provide better activity recognition accuracy compared to models trained on impersonal datasets. In this paper, we develop a hybrid incremental (HI) method with logistic regression models. This method uses incremental learning of logistic regression to combine the advantages of the impersonal and personal approaches. We investigate two essential issues for this method, which are the selection of the learning rate schedule and the class imbalance problem. Our experiments show that the models learned using our HI method give better accuracy than the models learned from personal or impersonal data only. Besides, the techniques of adaptive learning rate and cost-sensitive learning generally give faster updates and more robust ML models in incremental learning. Our method also has potential bene- fits in the area of privacy preservation.

Conclusions: In this paper, we propose a novel hybrid incremental (HI) method for activity recognition. Traditionally, activity recognition models have been trained on either impersonal or personal datasets. Our HI method effectively combines the advantages of these two approaches. After learning a model on an impersonal dataset in servers, the mobile devices can apply incremental learning on the model using personal data. We focus on logistic regression due to its several benefits, including its small model size that saves bandwidth, good performance in activity recognition, and easy incremental update. We address two important problems that are likely to arise in practical implementations of this incremental learning task. The first problem is associated with user diversity, making it very difficult to tune the learning-rate for each user. The second issue is related to personal data being so imbalanced at times that it may spoil the impersonal model. To overcome those problems, we applied an adaptive learning rate and a cost-sensitive technique. Finally, experimental results are used to validate our solutions.

Hibridização de modelos de Machine Learning pessoais e impessoais para reconhecimento de atividades nos dispositivos móveis

Implementação de GLM com Grid Search no R usando o H2O.ai como backend

Para quem usa o R não existe nada mais irritante do que ter que lidar com o péssimo gerenciamento de memória da ferramenta, o que limita e muito o uso do R como uma ferramenta séria para a construção de modelos que possam ir para produção e possam permitir a construção de plataformas/sistemas inteligentes.

Vamos aqui em algumas linhas mostrar como usar o H2O.ai como backend de processamento (o que abstraí todos esses problemas de memória e processamento) para a criação de um modelo usando GLM.

O pulo do gato aqui é que o H2O faz todo o gerenciamento de memória, e independente da sua fonte de dados ele faz todo o pipeline do buffer de memória de forma que não há estouro de memória; ou mesmo uma lentidão generalizada no sistema.

Esse exemplo é baseado totalmente na documentação do H2O e tem o objetivo somente de mostrar como essa ferramenta funciona.

Nesse caso eu vou usar em um notebook, mas poderia ser utilizado por exemplo em uma máquina na Amazon usando o comando abaixo no momento da inicialização do cluster:

#Production Cluster (Not applicable)
#localH2O <- h2o.init(ip = '', port =54321, nthreads=-1) # Máquina 1
#localH2O <- h2o.init(ip = '', port =54321, nthreads=-1) # Máquina 2 - Sim, aqui usamos um cluster com dois nós para processamento! 😉

Primeiramente vamos remover qualquer instalação antiga do H2O.ai da máquina em questão:

# 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") }

Em seguida vamos fazer o download e instalação de todos os pacotes dos quais o H2O tem alguma dependência direta ou indireta.

# 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") }

Em seguida faremos a instalação da lib do H2O e o instanciamento da lib no R Studio.

# 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")))
# Load library

Instalação feita e biblioteca carregada, vamos agora para algumas configurações.

No próprio R Studio você pode escolher o número de processadores no qual o cluster (nesse caso o seu notebook/desktop) vai utilizar. Lembrando que quanto maior for o número de cores utilizados, mais processamento o H2O vai consumir e menos recursos estarão disponíveis para outras tarefas. O padrão é a utilização de 2 cores, mas no meu caso eu vou usar todos os processadores.

# Start instance with all cores. 
# The -1 is the parameter to use with all cores. Use this carefully.
# The default parameter is 2 cores. 
h2o.init(nthreads = -1)

Agora vamos ver as informações do nosso cluster:

# Cluster Info

# R is connected to the H2O cluster: 
#   H2O cluster uptime:         3 seconds 267 milliseconds 
# H2O cluster version: 
# H2O cluster version age:    2 months and 26 days  
# H2O cluster name:           H2O_started_from_R_flavio.clesio_udy929 
# H2O cluster total nodes:    1 
# H2O cluster total memory:   1.78 GB 
# H2O cluster total cores:    4 
# H2O cluster allowed cores:  4 
# H2O cluster healthy:        TRUE 
# H2O Connection ip:          localhost 
# H2O Connection port:        54321 
# H2O Connection proxy:       NA 
# R Version:                  R version 3.3.2 (2016-10-31) 

Como podemos ver dos 4 processadores no total, estou usando todos eles (allowed cores) para o processamento.

Outro fato que podemos ver aqui é o que o H2O também está instanciado para usar a GUI na Web. Para isso, basta entrar no endereço no navegador com o endereço http://localhost:54321/flow/index.html.

Para este exemplo, vamos usar a base de dados Airlines que contém diversas informações reais de voos nos EUA e todas as causas de atraso de 1987 até 2008. A versão completa com 12Gb pode ser encontrada aqui.

Seguindo adiante, vamos agora fazer o carregamento dos dados direto de uma URL e importar em um objeto do R.

# GLM Demo Deep Dive
# Path of normalized archive. Can be a URL or a local path 
airlinesURL = "https://s3.amazonaws.com/h2o-airlines-unpacked/allyears2k.csv"
# We'll create the object .hex (extention of data files in H2O) 
# and using the importFile property, we'll set the path and the destination frame.
# As default behaviour H2O parse the file automatically.
airlines.hex = h2o.importFile(path = airlinesURL, destination_frame = "airlines.hex")

Neste caso o objeto airlines.hex é será o dataframe no qual o H2O irá aplicar os algoritmos.

Esse formato .hex é exclusivo do H2O e pode ser usado para inúmeros algoritmos dentro da plataforma, dado que ele já é otimizado para lidar com objetos esparsos e/ou colunas do tipo texto.

Para ver as estatísticas descritivas desse arquivo, basta usar o mesmo summary() do R.

# Let's see the summary

Para o nosso experimento, vamos dividir a base de treino e teste na proporção 70%/30%.

Uma coisa necessária a se dizer nesse ponto é que devido ao fato do H2O ser uma plataforma projetada para Big Data é utilizado o método de amostragem probabilística. Isso se faz necessário (em termos computacionais), dado que em muitas vezes a operação de seleção/estratificação pode ser custoso.

# Construct test and train sets using sampling
# A small note is that H2O uses probabilistic splitting, witch means that resulting splits
# can deviate for the exact number. This is necessary when we're talking about a platform that 
# deals with big data. If you need a exact sampling, the best way is to do this in your RDBMS
airlines.split = h2o.splitFrame(data = airlines.hex,ratios = 0.70, seed = -1)

Após criar o objeto do tipo splitFrame, vamos alocar as partições para cada conjunto de dados, sendo que o objeto na primeira posição sempre será a nossa base de treinamento, e na segunda posição a nossa base de teste.

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

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

Vamos sumarizar abaixo cada um desses frames para verificarmos a distribuição de voos cancelados:

# Display a summary using table-like in some sumarized way
# Cancelled Count
# 1         0 29921
# 2         1   751

# Cancelled Count
# 1         0 12971
# 2         1   335

Com as nossas amostras separadas, vamos agora escolher as variáveis que vão entrar no nosso modelo.

Primeiramente, vamos criar dois objetos para passar como parâmetro ao nosso algoritmo.

Como queremos prever se as partidas do voos estão atrasadas, então o objeto Y (variável dependente) será a variável IsDepDelayed (Se o voo de partida está atrasado) e o objeto X (variáveis independentes) serão todos os outros campos do conjunto de dados.

# Set dependent variable (Is departure delayed)
Y = "IsDepDelayed"
# Set independent variables
X = c("Origin", "Dest", "DayofMonth", "Year", "UniqueCarrier", "DayOfWeek", "Month", "DepTime", "ArrTime", "Distance")

Agora vamos realizar a criação do modelo usando GLM:

# Define the data for the model and display the results
airlines.glm <- h2o.glm(training_frame=airlines.train
                        ,family = "binomial"
                        ,alpha = 0.5
                        ,max_iterations = 300
                        ,beta_epsilon = 0
                        ,lambda = 1e-05
                        ,lambda_search = FALSE
                        ,early_stopping = FALSE
                        ,nfolds = 0
                        ,seed = NULL
                        ,intercept = TRUE
                        ,gradient_epsilon = -1
                        ,remove_collinear_columns = FALSE
                        ,max_runtime_secs = 10000
                        ,missing_values_handling = c("Skip"))

O significado dos parâmetros do modelo são:

x: vetor que contém os nomes das variáveis independentes;

y: índice que contém a variável dependente;

training_frame: Um frame do H2O que contém das variáveis do modelo;

family: Especificação da distribuição do modelo que pode ser gaussiana, binomial, poisson, gamma, e tweedie. Uma ótima explicação de como esses parâmetros podem ser escolhidos está aqui nesse link;

alpha: Um número em [0, 1] especificando a mistura do parâmetro de regularização do elastic-net. Ele que dá o grau de mistura entre os regularizadores Lasso e Ridge. making alpha = 1 penalização via LASSO, alpha = 0 penalização via ridge;

max_iterations: Um inteiro não negativo que especifica o número máximo de interações do modelo;

beta_epsilon: Um inteiro não negativo que especifica a magnitude da diferença máxima entre as estimativas dos coeficientes através de sucessivas interações. Em outras palavras: É esse parâmetro que define a velocidade da convergência do modelo GLM;

lambda: Um parâmetro não negativo para encolhimento do valor da variável através da Elastic-Net, o qual multiplica P(α, β) na função objetivo. Quando lambda = 0, nenhuma penalização é aplicada e o modelo já fica ajustado;

lambda_search: Um valor lógico que indica se haverá algum critério de busca no espaço dos valores de lambda definidos por um parâmetro de mínimo e máximo;

early_stopping: Um valor que indica se haverá uma parada antecipada durante o lambda_search caso o fator de verosimilhança pare de ser alterado na medida que ocorram mais interações;

nfolds: Número de partições em Cross-Validation;

seed: Especifica a semente do random number generator (RNG) para Cross-Validation (garante a reprodutibilidade do experimento);

intercept: Termo constante do modelo que a grosso modo significa o grau de fatores endógenos do modelo;

gradient_epsilon: Critério de convergência. Converge se o gradiente da norma I-Infinito é abaixo de um determinado limite. Se lambda_search = FALSE e lambda = 0, o valor default do gradient_epsilon é igual a .000001, se não for, o valor default é .0001. Se lambda_search = TRUE, os valores condicionais acima são 1E-8 e 1E-6 respectivamente.

remove_collinear_columns: Se não houver nenhum tipo de fator de regularização aplicado, o modelo ignora colunas colineares (o coeficiente será 0);

max_runtime_secs: Número máximo permitido em segundos para o treinamento do modelo. Use 0 para desabilitar; e

missing_values_handling: Contra o que é feito com valores faltantes. Podem ser “MeanImputation” ou “Skip”. MeanImputation substituí os valores faltantes com a média para os atributos numéricos e categórico com a maior frequência. É aplicado durante o treinamento do modelo.

Notem que aqui o céu é o limite em temos de ajustes e/ou parametrizações. O ideal é ter o perfeito entendimento da mecânica de cada um dos parâmetros e utilizar a melhor combinação possível.

Com o nosso modelo ajustado, vamos ver algumas das estatísticas básicas desse modelo.

# View model information: training statistics, performance, important variables

# Model Details:
#   ==============
#   H2OBinomialModel: glm
# Model Key:  GLM_model_R_1484053333586_1 
# GLM Model: summary
# family  link                              regularization number_of_predictors_total number_of_active_predictors number_of_iterations  training_frame
# 1 binomial logit Elastic Net (alpha = 0.5, lambda = 1.0E-5 )                        283                         272                    5 RTMP_sid_a6c9_1
# H2OBinomialMetrics: glm
# ** Reported on training data. **
#   MSE:  0.2098326
# RMSE:  0.4580749
# LogLoss:  0.607572
# Mean Per-Class Error:  0.3720209
# AUC:  0.7316312
# Gini:  0.4632623
# R^2:  0.1602123
# Null Deviance:  41328.6
# Residual Deviance:  36240.45
# AIC:  36786.45
# Confusion Matrix for F1-optimal threshold:
#   NO   YES    Error          Rate
# NO     5418  9146 0.627987   =9146/14564
# YES    1771 13489 0.116055   =1771/15260
# Totals 7189 22635 0.366047  =10917/29824
# Maximum Metrics: Maximum metrics at their respective thresholds
# metric threshold    value idx
# 1                       max f1  0.363651 0.711915 294
# 2                       max f2  0.085680 0.840380 389
# 3                 max f0point5  0.539735 0.683924 196
# 4                 max accuracy  0.521518 0.673887 207
# 5                max precision  0.987571 1.000000   0
# 6                   max recall  0.040200 1.000000 398
# 7              max specificity  0.987571 1.000000   0
# 8             max absolute_mcc  0.521518 0.348709 207
# 9   max min_per_class_accuracy  0.513103 0.672412 212
# 10 max mean_per_class_accuracy  0.521518 0.674326 207

Aqui neste modelo já temos um resultado de 73,16% de AUC. Nada mal para um modelo que contém poucos ajustes.

Vamos analisar agora a importância de cada uma das variáveis no modelo:

# Get the variable importance of the models

# Standardized Coefficient Magnitudes: standardized coefficient magnitudes
# names coefficients sign
# 1 Origin.TLH     3.233673  NEG
# 2 Origin.CRP     2.998012  NEG
# 3 Origin.LIH     2.859198  NEG
# 4   Dest.LYH     2.766090  POS
# 5 Origin.KOA     2.461819  NEG
# ---
#   names coefficients sign
# 278   Dest.JAN     0.000000  POS
# 279   Dest.LIT     0.000000  POS
# 280   Dest.SJU     0.000000  POS
# 281 Origin.LAN     0.000000  POS
# 282 Origin.SBN     0.000000  POS
# 283 Origin.SDF     0.000000  POS

Alguns valores de atributos são determinantes no atraso dos vôos de partida, principalmente se os aeroportos de origem são TLH (Tallahassee International Airport), CRP (Corpus Christi International Airport), LIH (Lihue Airport), e KOA (Kona International Airport).

Agora vamos usar a função predict para saber como o modelo realizou as classificações.

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

Após isso, vamos ver o resultado do nosso modelo.

# Look at summary of predictions: probability of TRUE class (p1)

# predict   NO                YES              
# YES:9798  Min.   :0.01186   Min.   :0.02857  
# NO :3126  1st Qu.:0.33715   1st Qu.:0.37018  
# NA : 382  Median :0.48541   Median :0.51363  
#           Mean   :0.48780   Mean   :0.51220  
#           3rd Qu.:0.62886   3rd Qu.:0.66189  
#           Max.   :0.97143   Max.   :0.98814  
#           NA's   :382       NA's   :382 

Outra forma de usar o modelo GLM no H2O é realizar a escolha de parâmetros via Grid Search.

Grid Search nada mais é do que um método de otimização de parâmetros de um modelo de Machine Learning, sendo que em grande parte das vezes é feita uma combinação com inúmeros parâmetros e a combinação que obtiver o menor erro através de uma determinada função de erro.

O que vamos fazer agora é usar o GLM pra obter o melhor modelo de acordo com uma combinação de parâmetros específica.

Primeiramente, vamos construir uma lista com os parâmetros alpha (recapitulando, esse parâmetro faz uma combinação de Lasso e Ridge via Elastic-Net). Neste caso vamos passar uma lista que vai desde 0.0 até 1 com incremento de 0.05 em cada parâmetro.

# Construct a hyper-parameter space
alpha_opts = c(0,0.05,0.10,0.15,0.20,0.25,0.30,0.35,0.40,0.45,0.50,0.55,0.60,0.65,0.70,0.75,0.80,0.85,0.90,0.95,1)

Agora vamos criar uma lista com esses parâmetros para passar para a função de Grid posteriormente.

# List of hyperparameters
hyper_params_opt = list(alpha = alpha_opts)

Na função de Grid Search, vamos passar alguns parâmetros para o ajuste dos modelos como podemos ver abaixo.

# Grid object with hyperparameters
glm_grid <- h2o.grid("glm"
                     ,grid_id = "glm_grid_1"
                     ,hyper_params = hyper_params_opt
                     ,family = "binomial")

Essa etapa pode demorar bastante tempo dependendo do seu volume de dados, e do número de parâmetros escolhidos na lista de search.

Após a finalização do processamento, vamos ordernar a lista de modelos de acordo com o AUC.

# Sort grids by best performance (lower AUC). Little note: As we're dealing with classification
# in some probabilistc fashion, we'll use AUC as model selection metric.
# If the nature of the problem are cost sensitive (e.g. A delayed departure plane is much expensive for 
# the airport service than a delayed arrival) precision and recall can be the best choice
glm_sorted_grid <- h2o.getGrid(grid_id = "glm_grid_1", sort_by = "auc", decreasing = FALSE)

Para avaliar cada um dos modelos, podemos exibir a ordem dos modelos de acordo com o AUC.

#Print the models

# H2O Grid Details
# ================
#   Grid ID: glm_grid_1 
# Used hyper parameters: 
#   -  alpha 
# Number of models: 21 
# Number of failed models: 0 
# Hyper-Parameter Search Summary: ordered by increasing auc
# alpha          model_ids                auc
# 1 [D@4800a43e glm_grid_1_model_1 0.7076911403181928
# 2 [D@66030470 glm_grid_1_model_2 0.7122987232329416
# 3 [D@6a4a43d3 glm_grid_1_model_3 0.7145455620514375
# 4 [D@17604a1a glm_grid_1_model_4  0.715989429818657
# 5 [D@21e1e99f glm_grid_1_model_5 0.7169797604977775
# ---
# alpha           model_ids                auc
# 16 [D@78833412 glm_grid_1_model_16  0.720595118360825
# 17 [D@44d770f2 glm_grid_1_model_17 0.7207086912177467
# 18 [D@31669527 glm_grid_1_model_18 0.7208228330257134
# 19 [D@5b376f34 glm_grid_1_model_19 0.7209144533220885
# 20 [D@6acad45e glm_grid_1_model_20 0.7209885192412766
# 21 [D@237ad7de  glm_grid_1_model_0 0.7240682725570593

Com esses parâmetros, o melhor modelo é o glm_grid_1_model_0 que teve cerca de 72.40% de AUC. (Nota: Esse modelo está levemente pior do que o modelo padrão, dado que o conjunto de parâmetros do Grid está diferente do que o primeiro modelo).

 Para pegar o melhor modelo, basta executar o comando abaixo:

# Grab the model_id based in AUC
best_glm_model_id <- glm_grid@model_ids[[1]]
# The best model
best_glm <- h2o.getModel(best_glm_model_id)

Vejamos as características desse modelo:

# Summary

# Model Details:
#   ==============
#   H2OBinomialModel: glm
# Model Key:  glm_grid_1_model_0 
# GLM Model: summary
# family  link             regularization number_of_predictors_total number_of_active_predictors number_of_iterations  training_frame
# 1 binomial logit Ridge ( lambda = 7.29E-5 )                        283                         282                    3 RTMP_sid_a6c9_1
# H2OBinomialMetrics: glm
# ** Reported on training data. **
#   MSE:  0.2121424
# RMSE:  0.4605891
# LogLoss:  0.612699
# Mean Per-Class Error:  0.3833898
# AUC:  0.7240683
# Gini:  0.4481365
# R^2:  0.1494395
# Null Deviance:  42448.59
# Residual Deviance:  37585.41
# AIC:  38151.41
# Confusion Matrix for F1-optimal threshold:
#   NO   YES    Error          Rate
# NO     4993  9601 0.657873   =9601/14594
# YES    1751 14327 0.108907   =1751/16078
# Totals 6744 23928 0.370110  =11352/30672
# Maximum Metrics: Maximum metrics at their respective thresholds
# metric threshold    value idx
# 1                       max f1  0.373247 0.716243 296
# 2                       max f2  0.105583 0.846435 391
# 3                 max f0point5  0.551991 0.685249 194
# 4                 max accuracy  0.513313 0.665949 218
# 5                max precision  0.980714 1.000000   0
# 6                   max recall  0.048978 1.000000 399
# 7              max specificity  0.980714 1.000000   0
# 8             max absolute_mcc  0.548278 0.332916 196
# 9   max min_per_class_accuracy  0.524282 0.664324 211
# 10 max mean_per_class_accuracy  0.548278 0.666166 196
# Gains/Lift Table: Extract with `h2o.gainsLift(&lt;model&gt;, &lt;data&gt;)` or `h2o.gainsLift(&lt;model&gt;, valid=&lt;T/F&gt;, xval=&lt;T/F&gt;)`
# Scoring History: 
#   timestamp   duration iteration negative_log_likelihood objective
# 1 2017-01-10 11:11:07  0.000 sec         0             21224.29620   0.69198
# 2 2017-01-10 11:11:07  0.066 sec         1             18857.11178   0.61705
# 3 2017-01-10 11:11:07  0.094 sec         2             18795.11788   0.61562
# 4 2017-01-10 11:11:07  0.126 sec         3             18792.70362   0.61559
# Variable Importances: (Extract with `h2o.varimp`) 
# =================================================
#   Standardized Coefficient Magnitudes: standardized coefficient magnitudes
# names coefficients sign
# 1 Origin.MDW     1.915481  POS
# 2 Origin.HNL     1.709757  NEG
# 3 Origin.LIH     1.584259  NEG
# 4 Origin.HPN     1.476562  POS
# 5 Origin.AUS     1.439134  NEG
# ---
#   names coefficients sign
# 278 Origin.PHX     0.009111  POS
# 279   Dest.PWM     0.008332  POS
# 280 Origin.GEG     0.008087  POS
# 281   Dest.BOS     0.005105  POS
# 282   Dest.MCI     0.003921  NEG
# 283   Dest.CHA     0.000000  POS

Para realizar previsões com esse modelo, basta apenas instanciar esse novo objeto e usar a função predict como está abaixo:

# Get model and put inside a object
model = best_glm

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

# Summary of the best model

# predict    NO                YES              
# YES:10368  Min.   :0.01708   Min.   :0.05032  
# NO : 2938  1st Qu.:0.33510   1st Qu.:0.39258  
#            Median :0.47126   Median :0.52781  
#            Mean   :0.47526   Mean   :0.52474  
#            3rd Qu.:0.60648   3rd Qu.:0.66397  
#            Max.   :0.94968   Max.   :0.98292  

Se após isso, você quiser desligar o cluster do H2O basta usar o comando shutdown.

# Shutdown the cluster 

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

Com isso finalizamos esse post/tutorial de como usar o R com o H2O.ai.

Ao longo das próximas semanas, vamos trazer alguns tutoriais e destrinchar um pouco o poder desse H2O.

Forte abraço!











Implementação de GLM com Grid Search no R usando o H2O.ai como backend

Máquina enviesada: Como um algoritmo está agindo de forma tendenciosa contra negros nos EUA?

Diretamente do ProPublica.

Uma das questões éticas mais delicadas em Machine Learning:

Compare their crime with a similar one: The previous summer, 41-year-old Vernon Prater was picked up for shoplifting $86.35 worth of tools from a nearby Home Depot store.
Prater was the more seasoned criminal. He had already been convicted of armed robbery and attempted armed robbery, for which he served five years in prison, in addition to another armed robbery charge. Borden had a record, too, but it was for misdemeanors committed when she was a juvenile.
Yet something odd happened when Borden and Prater were booked into jail: A computer program spat out a score predicting the likelihood of each committing a future crime. Borden — who is black — was rated a high risk. Prater — who is white — was rated a low risk.
Two years later, we know the computer algorithm got it exactly backward. Borden has not been charged with any new crimes. Prater is serving an eight-year prison term for subsequently breaking into a warehouse and stealing thousands of dollars’ worth of electronics.

Para quem tiver curiosidade de saber quais são os dados que o algoritmo de avaliação de risco usa, o documento abaixo é um claro exemplo disso.


Máquina enviesada: Como um algoritmo está agindo de forma tendenciosa contra negros nos EUA?