Parte III: Como utilizar modelos de Machine Learning para reduzir o Churn

Como comentado no nosso último artigo, o Churn é um indicativo de gestão da empresa e controlá-lo se faz necessário para evitar problemas futuros.
Uma das maneiras que podemos diminuir o Churn é prever quais clientes têm mais risco de evasão. Assim, conseguimos tomar medidas preventivas.
Modelos de Machine Learning são técnicas poderosa para esse fim, ao utilizar essa ferramenta, você poderá descobrir com facilidade aqueles clientes que têm um risco maior de Churn.
Nesse artigo iremos continuar a análise do conjunto de dados de uma agência bancária, apresentado no último artigo.
Essa série de artigo foi dividida em 6 etapas:
- Entendimento do Problema
- Obtenção dos dados
- Análise Exploratória dos dados
- Pré-processamento
- Modelos de Machine Learning
- Avaliação do modelo
O entendimento do problema foi abordado no primeiro artigo dessa série de artigos. No parte II, dessa série, aprensentamos os dados e análise exploratória de dados, afim de identificar o perfil dos clientes que cancelaram o contrato com a agência bancária.
Se você não leu os outros artigos, corre lá para ver, eles são sequenciais e vão auxiliar no entendimento desse tutorial.
Agora, iremos mostrar o passo a passo da implementação de modelos de Machine Learning para prever o Churn. Começando pelo pré-processamento, etapa onde vamos preparar os dados para utilizar nos modelos de Machine Learning.
Recordando
Os dados utilizados aqui foram extraídos do Kaggle e são dados de clientes de uma agência bancária. Utilizaremos o Google Colab para fazer as análises.
No conjunto de dados temos as seguinte variáveis:
- CustomerId: identificação do cliente;
- Surname: sobrenome do clientes;
- CreditScore: pontuação de credito, 0 alto risco de inadimplência e 1000 clientes com baixo risco de inadimplência;
- Geography: país que o serviço é oferecido;
- Gender: sexo do cliente;
- Age: idade do cleinte;
- Tenure: um indicativo de estabilidade no emprego, em que 0 significa pouca estabilidade e 10 muita estabilidade.
- Balance: saldo da conta corrente;
- NumOfProducts: número de produtos bancários adquiridos;
- HasCrCard: se tem cartão de credito ou não, (Sim = 1 e Não = 0);
- IsActiveMember: se é um cliente com conta ativa, (Ativo = 1) ;
- EstimatedSalary: salário estimado;
- Exited: cliente deixou de ser cliente do banco ou não (Churn = 1).
No artigo anterior concluimos que clientes da Alemanha, do sexo feminino, que não são membro ativo, com alto número de produtos, score de crédito baixo, com saldo bancário elevado e com idade entre 50 e 60 anos têm uma taxa de Churn maior que os outros clientes.
Assim, clientes com essas características possui um risco maior de cancelarem o serviço bancário.
Nesse artigo iremos implementar um modelo de Churn para prever os clientes com um alto risco de Churn.
4. Pré-Processamento dos dados
Nessa seção precisamos transformar as variáveis para utilizá-las nos modelos de Machine Learning. Teremos as seguintes etapas
- Eliminar as variáveis que não serão utilizadas;
- Identificação de dados missing;
- Separação das variáveis categóricas, numéricas e resposta;
- Processamento variáveis categóricas;
- Processamento variáveis numéricas.
A. Eliminar as variáveis que não serão utilizadas
Em conjunto de dados reais é comum existir variáveis que não tem utilidade prática, por exemplo, as variáveis ‘RowNumber’ (número da linha) e ‘CustomerId'(ID do Cliente) não trazem informações relevantes para entender o Churn de clientes. Assim, iremos eliminar essas variáveis utilizando a função drop().
df1 = df.drop(columns = ['RowNumber','CustomerId','Surname'])
B. Identificando dados faltantes
Nada é perfeito nesse mundo e na análise de dados isso também é verdade. Dados faltantes, ou dados missing, são comuns em dados reais, isso pode ocorrer por vários motivos.
Por exemplo, a pessoa que fez o cadastro esqueceu de preencher essa informação, ou a dona da conta não tinha essa informação quando fez o cadastro, ou simplesmente essa informação era desconhecida.
Assim para identificar a presença de dados faltantes no conjunto de dados, utilizamos os comandos a seguir
#Contato número de observações faltantes no dataset para cada variável df.isnull().sum() RowNumber 0 CustomerId 0 Surname 0 CreditScore 0 Geography 0 Gender 0 Age 0 Tenure 0 Balance 0 NumOfProducts 0 HasCrCard 0 IsActiveMember 0 EstimatedSalary 0 Exited 0 dtype: int64
Nenhuma variável em estudo apresenta dados faltantes (dados missing). Assim, não teremos o processo de tratamento dos dados faltantes.
C. Separando as variáveis
No modelos de Machine Learning temos a variável dependente (variável resposta) que queremos “prever”, no nosso caso é a variável ‘Exited’. E as covariáveis (variáveis independentes) são as variáveis que irão explicar o Churn.
Para não modificar a variável resposta (target) durante o pré-processamento, iremos separá-la das outras variáveis do dataset.
#Criando variáveis independentes e dependentes y = df1['Exited'] X = df1 X = df1.drop('Exited',axis = 1)
Separando as variáveis numéricas das categóricas
Variáveis numéricas são aquelas variáveis que assumem valores numéricos, por exemplo a variável idade. As variáveis numéricas são classificadas como variáveis contínuas ou discreta.
As variáveis contínuas assumem valores na reta real, como a variável Salário Estimado. E as variáveis discretas são aquelas que assumem valores inteiros, como a variável número de produtos.
Variáveis categóricas são variáveis que não assumem valores numéricos. Por exemplo, a variável país.
As variáveis categóricas são classificadas como nominais e ordinais. As variáveis categóricas nominais são aquelas que não tem nenhuma ordem envolvida, por exemplo, a variável sexo e ordinais quando temos uma ordem envolvida, como a variável grau de escolaridade.
No pré processamento dos dados separamos as variáveis entre categóricas e numéricas, pois para cada tipo de variável utilizamos técnicas de processamento diferentes.
#Variáveis Continuas x_cont = ['CreditScore','Balance','Age','EstimatedSalary','Tenure']
#Variáveis Categóricas
x_cat = list(set(X) - set(x_cont)) x_dummies = X[x_cat] x_dummies
D. Processamento das variáveis categóricas
Algoritmos de machine learning não aceitam variáveis categóricas e por isso precisamos utilizar técnicas para converter as classes em números.
Ou seja, convertemos as colunas categóricas em numéricas simplesmente atribuindo números inteiros a classes distintas.
Para as variáveis categóricas com apenas duas classes utilizaremos a função Label Encoder para converte colunas categóricas em numéricas simplesmente atribuindo números inteiros a valores distintos.
Por exemplo, a coluna sexo tem dois valores: Feminino e Masculino. Após aplicar a função, os valores serão transformados em 1 e 0.
##Substituindo a variável sexo para 0 e 1 from sklearn.preprocessing import LabelEncoder le = LabelEncoder() X['Gender'] = le.fit_transform(X['Gender']) X.head(10)
Para as variáveis categóricas com mais categorias, utilizaremos o método get_dummies(). Esse método transforma uma variável em variável dummy, ou seja, ele cria novas colunas de variáveis transformando a variável em binária, ou seja, atribui 0 e 1s.
Por exemplo, a variável país possuí três categorias, França, Espanha e Alemanha. A função get_dummies() transforma cada categoria em uma nova variável binária, ou seja,
- Variável França: se o cliente for da França 1 e 0 caso contrário.
- Variável Alemanha: se o cliente for da Alemanha 1 e 0 caso contrário.
- Variável Espanha; se o cliente for da Espanha 1 e 0 caso contrário.
Observe o conjunto de dados após aplicar a função get_dummies().
x_final = pd.get_dummies (data = X, columns = ['Geography','NumOfProducts'] ) x_final
Observe que a variável país agora tem 3 colunas, para cada país temos uma coluna com valores 0 ou 1. Onde 1, significa que pertence aquele país.
E. Variáveis Numéricas
Observe que as variáveis numéricas Balance, Estimated Salary, Age e Credit Score estão em diferentes escalas e isso pode causar problemas no treinamento dos modelos de machine learning.
Para resolver esse problemas utilizaremos o método MiniMax para padronizar as variáveis contínuas, ou seja, vamos deixar todas as variáveis contínuas numa escala entre 0 e 1.
O método MiniMax é definido como
valor_padronizado = ( valor — Coluna.min) / (Coluna.max — Coluna.min)
Aqui utilizaremos uma função MinMaxScaler() do pacote sklearn.
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() x_final[x_cont] = scaler.fit_transform(x_final[x_cont]) x_final[x_cont]
5. Modelo de Churn
Após o pré processamento, o conjunto de dados está pronto para a modelagem. Prever o Churn de clientes é um problema de classificação binária, 1 se Churn e 0 caso contrário.
Para prever os clientes em risco de Churn iremos utilizar o modelo KNN, Random Forest e XGBost. Lembre-se a variável resposta Exited tem apenas 20% de valores 1. E por isso escolhemos modelos que não sofrem tanta influência dos dados desbalanceado.
Validação Hold-Out
Para analisar a capacidade de generalização do modelo é importante separar o dataset em conjunto de dados de treino e dados de teste. O conjunto de dados de treino utilizamos para treinar o modelo e utilizaremos 75% dos conjunto de dados para isso.
O conjunto de dados de teste utilizamos para analisar o quanto o modelo está acertando para estimar as classes, 1 para Churn e 0 caso contrário. Essa etapa é necessária, para analisar como nosso modelo se comporta em dados desconhecidos.
A figura, exemplifica o método Holdout, onde temos os dados e dividimos em duas partes, uma para treinar o modelo e outra para testar o modelo em dados desconhecidos.
# Separando em dados teste e treinamento from sklearn.model_selection import train_test_split x_treino, x_teste, y_treino, y_teste = train_test_split(x_final, y, test_size = 0.25, random_state = 1)
Modelo KNN
Modelo mais simples de aprendizado supervisiona, mas nem por isso ele perde em relação ao seu desempenho.
KNN ou k-vizinhos mais próximo utiliza como base para prever as classes, os vizinhos mais próximo, ou seja, as observações mais próximas serão a base para prever essa nova observação. Para entender melhor esse algoritmo leia nosso artigo sobre o modelo KNN.
from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier() knn.fit(x_treino, y_treino)
A matriz de confusão.
resultado_knn = knn.predict(x_teste) print (pd.crosstab(y_teste, resultado_knn, rownames = ['Real'], colnames = ['Predito'], margins = True)) Predito 0 1 All Real 0 1866 114 1980 1 323 197 520 All 2189 311 2500
Primeiramente utilizamos o K=5 que é default da função Knn do pacote “scikit learn” , o valor de k influência bastante a estimação das classe. Observe, que utilizando k=5, temos muitos erros de predição.
Assim precisamos encontrar o melhor k para esses dados.
- Encontrando o melhor K Para encontrar o melhor K, ajustaremos vários modelos variando o número de K, aquele modelo que retornar o menor erro de predição é o modelo com K ideal
Na função acima, ajustei o modelo para diferentes k, variando k de 1 a 40. E depois calculei o número de erros de classificação e tirei a média, erro médio. O gráfico a seguir apresenta os erros de predição para os diferentes k e aquele k com o menor erro, será o k ideal.
import matplotlib.pyplot as plt plt.figure(figsize=(12, 6)) plt.plot(range(1, 40), error, color = 'red', linestyle = 'dashed', marker =' o', markerfacecolor = 'blue', markersize = 10) plt.title('Taxa de erro do valor K') plt.xlabel('Valor de K') plt.ylabel('Erro Médio')![]()
Para k igual 17 obtemos o menor erro médio, assim iremos ajustar novamente o modelo KNN considerando K = 17. Mesmo escolhendo o melhor K, o modelo KNN está classificando bem os clientes que não deram Churn, classe 0, porém temos muitos falsos negativos no modelo (quando o modelo prevê 0, mas o correto era 1). Ou seja, o modelo não consegue prever bem o Churn, que é o objetivo principal na análise.
resultado_knn = knn.predict(x_teste)
print (pd.crosstab(y_teste,resultado_knn, rownames=['Real'], colnames=['Predito'], margins=True))
Predito 0 1 All
Real
0 1914 66 1980
1 361 159 520
All 2275 225 2500
Vamos tentar o modelo Random Forest para buscar um resultado melhor.
Modelo Random Forest
O modelo Random Forest é um modelo baseado em árvores de decisão, onde combinamos diversas árvores para obter a melhor predição.
Para ajustar o modelo Random Forest utilizamos o pacote “scikit learn”
from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier (n_estimators = 1000, random_state = 42) rf.fit (x_treino, y_treino); # Treine o modelo nos dados de treinamento
Validação do modelo nos dados de teste
from sklearn.metrics import classification_report,confusion_matrix
print(classification_report(y_teste, y_rf)) precision recall f1-score support 0 0.88 0.97 0.92 1980 1 0.79 0.49 0.60 520 accuracy 0.87 2500 macro avg 0.84 0.73 0.76 2500 weighted avg 0.86 0.87 0.85 2500
Para analisar a importância de cada variáveis utilizamos os comandos a seguir
#Analisando a importância de cada variável rf.feature_importances_ feature_importances = pd.DataFrame(rf.feature_importances_, index = x_treino.columns, columns=['importance']).sort_values('importance', ascending = False) feature_importances
O modelo de Random Forest conseguiu melhores resultados, porém ainda temos bastante falsos negativos. Vamos tentar um modelo mais robusto e que não sofre tanta influência em amostras desbalanceada.
Como o modelo XGBoost funciona
O modelo XGBoost (eXtreme Gradient Boosting), é uma categoria de modelos baseada nos algoritmos de árvores de decisão em combinação com os algoritmos de Gradient Boosting (gradiente aumentado). Além de ser um modelo robusto que não sofre tanta influência com dados desbalanceado.
Os modelos padrões em aprendizado supervisionado, como a Árvore de Decisão, simplesmente treina um único modelo nos dados de treinamento. Mesmo quando utilizamos o método Ensemble para combinar os modelos, estamos ajustando os modelos e fazendo previsões individualmente.
Os algoritmos de Boosting, mesmo sendo uma técnica de Ensemble, pois combina muitos modelos, utiliza um método mais inteligente. Onde os novos modelos adicionados tem o objetivo de corrigir os erros dos modelos anteriores. O algoritmo para quando não existe mais melhorias a ser feita.
Os algoritmos de Gradient Boosting são treinados para prever os resíduos dos modelos anteriores e tentar reduzir o mesmo, ou seja, utiliza o algoritmo Gradient Descent para minimizar os erros dos modelos anteriores.
Para ajustar o algoritmo XGBoost utilizamos a biblioteca xgboost. Não se assuste com a função do XGBClassifier( ). O modelo XGBoost tem muitos parâmetros e posteriormente faremos um artigo para explicar esses parâmetros.
No momento o que você precisa entender é a função, os parâmetros podem ser omitidos e o algoritmo utilizará os parâmetros default.
Porém após fazer alguns testes, os parâmetros apresentados a seguir, foram o que retornaram os melhores resultados.
#Importando as bibliotecas necessárias from numpy import loadtxt from xgboost import XGBClassifier from sklearn.metrics import accuracy_score # ajuste do modelo nos dados de treino xgb = XGBClassifier(learning_rate =0.1, n_estimators=1000, max_depth=6, min_child_weight=1, gamma=0, subsample=0.8, colsample_bytree=0.8, objective= 'binary:logistic', nthread=4, scale_pos_weight=1.0, seed=27) xgb.fit(x_treino, y_treino)
Validação do modelo nos dados de teste
#fazendo as predições no dados de teste preditos_xgb = xgb.predict(x_teste) from sklearn.metrics import classification_report,confusion_matrix print(classification_report(y_teste, preditos_xgb)) precision recall f1-score support 0 0.88 0.95 0.92 1980 1 0.75 0.51 0.61 520 accuracy 0.86 2500 macro avg 0.81 0.73 0.76 2500 weighted avg 0.85 0.86 0.85 2500
Usando o XGBoost alcançamos uma acurácia de 86%. Apesar do Random Forest ter uma acurácia de 87%, escolhemos o XGBoost como o melhor modelo. Pois, o XGBoost foi o modelo que conseguiu prever melhor o Churn, a classe 1.
Observe que apesar da acurácia ser menor no XGBoost, todas as medidas referentes a classe 1 foi melhor no XGBoost. Lembre-se, a acurácia é uma medida geral do acerto do modelo, e o modelo XGBoost não conseguiu prever a classe 0 tão bem quanto o modelo Random Forest, por isso a acurácia foi menor.
Porém como o nosso objetivo era prever o Churn, o modelo XGBoost teve uma melhor performance nesse quesito.
Analisando a importância das variáveis
#Analisando a importância de cada variável
import matplotlib.pyplot as plt from xgboost
import plot_importance
fig, ax = plt.subplots(figsize=(10,8))
plot_importance(xgb, ax=ax) As variáveis mais importante para estimar o Churn dos clientes foram: idade, saldo bancário, número de produtos, pontuação de crédito, estimativa de salário, membro ativo e país Alemanha.
Métodos Ensemble
Para tentar melhorar a previsão do Churn, vamos combinar os dois melhores modelos, Random Forest e XGBoost, utilizando o método Ensemble.
from sklearn.ensemble import VotingClassifier # Voting Classifier with soft voting voto = VotingClassifier(estimators=[('rf', rf),('xgb',xgb)], voting='soft') voto = voto.fit(x_treino,y_treino) y_predito = voto.predict(x_teste) print(classification_report(y_teste, y_predito))
precision recall f1-score support 0 0.88 0.96 0.92 1980 1 0.78 0.49 0.61 520 accuracy 0.87 2500 macro avg 0.83 0.73 0.76 2500 weighted avg 0.86 0.87 0.85 2500
Probabilidade de Churn
Finalmente, a melhor maneira de usar esse modelo é atribuir probabilidade de evasão para cada cliente, criar segmentos e criar estratégias sobre isso. Para obter a probabilidade de Churn para cada cliente, utilize o bloco de código abaixo:
df['proba'] = voto.predict_proba(x_final[x_treino.columns])[:,1] df[['CustomerId','proba']]
Agora temos a probabilidade do cliente dar Churn e podemos criar ações com base nisso.
Conclusão
O modelo XGBost e Random Forest se mostraram eficiente para prever a probabilidade do Churn dos clientes.
As variáveis mais importante para prever o Churn foram:
- a idade do cliente,
- o saldo bancário
- o número de produtos, ou seja, quanto maior o produto maior o risco de Chun.
O modelo final, foi a combinação dos modelos XGBoost e Random Forest. Com o modelo de Churn podemos prever clientes com a maior chance de cancelar o serviço e assim tomar medidas preventivas, afim de evitar essa saída.
Podemos melhorar a predição desse modelo categorizando as variáveis contínuas e balanceando as amostras, mas isso é assunto para um próximo artigo.
Ficou com alguma dúvida? Deixe suas dúvidas ou opinião nos comentários.
Não é simplesmente escrever qualquer coisa e publicar na
internet. Obrigado pela informação. Vou compartilhar o seu
artigo no meu instagram.
Bom dia, doutora Juliana.
Acompanhei a saga do ‘churn’ nos 3 posts e, como iniciante na área, baixei o mesmo dataset e montei minha própria versão, comparando com outros 7 modelos de ML. Só posso te agradecer por disponibilizar não só os resultados, como também o código, pois aprendi muito contigo.
Muito obrigado!
Olá, Willian
Obrigada por acompanhar nosso Blog, ficamos felizes por colaborar com seu aprendizado.
Continue acompanhando nossos conteúdos aqui no Blog da FLAI e em nossas outras redes:
LinkedIn | FLAI
Instagram | FLAI
YouTube | Canal FLAI
Atenciosamente,
Equipe da FLAI