https://a.storyblok.com/f/270183/42283/808de29e7d/blog_sickit-learn_python_1200x600.png

Criação de um modelo de previsão de rotatividade de usuários com o Scikit-Learn e a Vonage

Tempo de leitura: 13 minutos

A Conversation API da Vonage permite que os desenvolvedores criem sua própria solução de central de atendimento. Com recursos de voz, mensagens de texto e integrações com outras soluções, a Vonage permite que você crie uma solução abrangente, sem que o processo de desenvolvimento seja complicado.

Ao desenvolver sua solução de central de atendimento, é fundamental que ela seja inteligente. As soluções mais avançadas utilizam IA para direcionar chamadas, traduzir textos, recomendar produtos e assim por diante. O melhor de tudo é que você não precisa ser um pesquisador de IA com doutorado para integrar a IA ao seu aplicativo. Você também não precisa depender de um sistema de terceiros. Isso pode parecer impossível à primeira vista, mas houve muitos avanços em diversas bibliotecas de aprendizado de máquina, de modo que você, como desenvolvedor, pode incorporar um sistema de aprendizado de máquina à sua solução. Nesta postagem, vamos ver como adicionar uma funcionalidade para prever a probabilidade de um cliente cancelar sua assinatura.

O que é a rotatividade de clientes?

A rotatividade, por definição, é o número de clientes que deixaram de usar seu produto, dividido pelo número total de clientes. Por exemplo, uma empresa com uma rotatividade de 1% ao mês e 1.000 clientes significa que 10 em cada 1.000 clientes deixam de usar o serviço da empresa a cada mês. Esse indicador é usado para avaliar o desempenho da empresa. Uma empresa com baixa rotatividade de clientes geralmente significa que os clientes estão se mantendo fiéis. Uma rotatividade mais alta significa que os clientes estão usando o produto ou serviço, mas depois vão embora.

Um centro de atendimento é um dos locais onde os clientes interagem diretamente com a empresa, especialmente com o atendimento ao cliente. E perder um cliente devido a um atendimento insatisfatório pode ter um impacto na rotatividade e, consequentemente, na saúde da sua empresa.

Nesta postagem, vamos explicar como criar um aplicativo que simula uma conversa entre um cliente e um agente e é capaz de prever a probabilidade de cancelamento de contrato, utilizando a Conversation API da Vonage.

Video showing demo of churn

Visão geral

Em nossa demonstração, temos duas personas de usuário: um cliente e um atendente. Para este exemplo, vamos supor que a empresa seja uma prestadora de serviços de TV e que o cliente tenha uma dúvida sobre o serviço. Também supomos que esse cliente já seja cliente da empresa há algum tempo e que tenhamos dados que comprovem isso.

No nosso exemplo, temos algumas informações sobre o usuário. Podem ser, por exemplo:

  • Há quantos meses o cliente utiliza o serviço?

  • A forma de pagamento atual deles. (Cheque, cartão de crédito...)

  • Serviços que eles estão utilizando. (TV ao vivo, decodificador, serviços de streaming...)

  • E muitos outros.

Em nossa demonstração, quando o cliente interage com o atendente, exibimos na tela do atendente a probabilidade de o usuário cancelar o serviço, assim que a interação ocorre. Isso pode ser útil para o atendente antes de iniciar a conversa, para que ele possa dedicar mais atenção ao cliente, dependendo da probabilidade de cancelamento.

Pré-requisitos

Vamos usar o excelente trabalho, postagem de blog como ponto de partida. Vamos adicionar nossa funcionalidade de previsão de cancelamento a essa aplicação.

Para executar nosso aplicativo localmente, vamos clonar o repositórioe precisaremos usar o ngrok. Se você não estiver familiarizado com o Ngrok, consulte nosso tutorial do Ngrok antes de continuar.

Antes de abordarmos o aplicativo, precisamos primeiro construir nosso modelo. E, para isso, vamos usar Jupyter Notebooks rodando no Google Colab. Um Jupyter Notebook é uma forma interativa de executar código e é amplamente utilizado em ciência de dados e aprendizado de máquina. O Google Colab é um serviço gratuito que permite executar esses notebooks na nuvem. O código para criar o notebook está localizado aqui. Para executar este notebook, faça o upload dele no Google Colab.

Para este tutorial, vamos partir do princípio de que você tem um conhecimento básico sobre o que é aprendizado de máquina, mas não será necessário entender tudo ao detalhe para acompanhar o conteúdo.

Para construir um modelo, precisamos primeiro de dados. E, para este exemplo, usaremos o conjunto de dados sobre rotatividade no setor de telecomunicações da IBM. Esse conjunto de dados contém 7.043 linhas de dados anônimos de usuários do setor de telecomunicações. Para entender melhor o conjunto de dados, vamos dar uma olhada nas primeiras 10 linhas usando o Pandas. O Pandas é uma biblioteca do Python para processamento e análise de dados. Para cada usuário, temos 23 colunas, também conhecidas como características. Elas incluem o gênero do cliente (masculino, feminino), tempo de contrato (há quanto tempo é cliente) e se ele possui diferentes serviços, incluindo telefone, internet e TV.

Para construir nosso modelo, precisamos primeiro nos certificar de que não há valores nulos no conjunto de dados. Se não verificarmos isso e tentarmos construir nosso modelo, ocorrerão erros.

Vamos ler nosso conjunto de dados e remover todos os valores vazios.

df = pd.read_csv("/content/WA_Fn-UseC_-Telco-Customer-Churn.csv")
df = df.dropna(axis='columns', inplace=True)

Em seguida, use df.head() para visualizar as primeiras 10 linhas dos dados. Ao analisarmos esses dados, percebemos que muitas das linhas contêm cadeias de caracteres. (YES, NO). Agora precisamos converter essas sequências de caracteres em números, pois os modelos de aprendizado de máquina só sabem lidar com números.

Para cada linha que contenha uma sequência de caracteres, precisamos verificar se as sequências são únicas. Para visualizar todos os valores possíveis nessa coluna, o pandas possui uma função chamada unique() que faz isso por nós.

df.Partner.unique()

retorna: array(['Yes', 'No'], dtype=object)

Isso significa que a linha contém apenas os valores YES e NO. Para essa coluna, podemos converter essas strings em valores booleanos (1, 0). No entanto, se observarmos as outras linhas, digamos PaymentMethod, há mais valores do que YES ou NO.

df.PaymentMethod.unique()

retornos array(['Electronic check', 'Mailed check', 'Bank transfer (automatic)', 'Credit card (automatic)'], dtype=object)

Portanto, para essa coluna, precisamos nos esforçar um pouco mais. Sempre que um valor é YES ou NO, podemos convertê-lo para 1 ou 0, respectivamente. Quando for qualquer outra sequência de caracteres, vamos defini-la como -1. Mais uma vez, o modelo de aprendizado de máquina só pode usar números, e é por isso que definimos como -1.

Se analisarmos as outras colunas, PhoneService, MultipleLines, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, e StreamingMovies, elas parecem ser semelhantes. Então, vamos escrever uma função que percorra cada coluna e converta nossas strings em inteiros.

numeric_features = ['Partner', 'Dependents', 'PhoneService', 'MultipleLines','OnlineSecurity', 'OnlineBackup','DeviceProtection', 'TechSupport','StreamingTV', 'StreamingMovies', 'PaperlessBilling', 'Churn']
def to_numeric(s):
  if s == "Yes":
    return 1
  elif s == "No":
    return 0
  else: return -1

for feature in numeric_features:
  df[feature] = df[feature].apply(to_numeric)

Numeric_features é uma lista de todas as colunas que precisamos atualizar. to_numeric é uma função que recebe o valor de cada linha e converte a string em um inteiro. Por fim, vamos percorrer todos os itens em to_numeric e chamaremos a função do pandas apply para chamar nossa função. Vamos dar uma olhada nas primeiras 10 linhas para Verify.

Parece que essas linhas agora estão corretas, mas precisamos lidar com as outras colunas. Vamos primeiro verificar Contact e ver os valores exibidos.

df.Contract.unique()

o que retorna: array(['Month-to-month', 'One year', 'Two year'], dtype=object)

Esses valores ainda são strings, mas não é tão fácil quanto convertê-los para 1's e 0'. Existem outras colunas neste conjunto de dados que são semelhantes, incluindo PhoneService, MultipleLines, InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovies, Contract, PaperlessBilling, e PaymentMethod.

Felizmente, no pandas, podemos converter esses valores usando get_dummies(). Essa função, quando aplicada a uma coluna, criará uma nova coluna para cada valor possível. E cada valor em cada uma dessas novas colunas será 1 ou 0. Isso também é conhecido como codificação one-hot.

Por exemplo, vamos considerar a Contract coluna, que contém os valores de Month-to-month, One year, e Two year. Usando get_dummies(), vamos criar três novas colunas chamadas Contract_Month-to-month, Contract_One year e Contract_Two year. E cada valor nessas colunas será 1 ou 0.

categorical_features = [
 'PhoneService',
 'MultipleLines',
 'InternetService',
 'OnlineSecurity',
 'OnlineBackup',
 'DeviceProtection',
 'TechSupport',
 'StreamingTV',
 'StreamingMovies',
 'Contract',
 'PaperlessBilling',
 'PaymentMethod']
df = pd.get_dummies(df, columns=categorical_features)

Aqui, criamos uma lista dessas características que queremos converter em categóricas e chamamos a get_dummies função usando o DataFrame(df) e a lista de colunas(categorical_features). Vamos visualizar novamente as primeiras 10 linhas para conferir nosso trabalho. Como o DataFrame agora tem 41 características, vamos criar um link para a célula aqui em vez de mostrar uma captura de tela do DataFrame

Parece que todas as colunas são numéricas. Vamos construir nosso modelo. Para isso, usaremos outro pacote chamado scikit-learn. O Scikit-Learn possui muitas funções integradas para processar e treinar um modelo com nossos dados.

Primeiro, precisamos de duas matrizes, X e y. X é uma matriz que inclui todas as nossas características, exceto aquela que estamos usando para fazer previsões (Churn). Y é simplesmente o valor de Churn, que é uma 1 ou 0.

X = df.drop(labels='Churn',axis=1)
Y = df.Churn
print(X.shape, Y.shape)

Isso retorna: ((7043, 40), (7043,)) O que significa que há 7.043 linhas e 40 colunas em X. Portanto Y, temos 7.043 linhas.

Em seguida, precisamos dividir nossos dados em um conjunto de treinamento e um conjunto de teste. Ao treinar nosso modelo, usamos apenas uma parte do conjunto de dados. O restante dos dados é utilizado para o teste. Isso serve para verificar o quão bem nosso modelo aprendeu com os dados. Usando o Scikit-Learn, vamos dividir nosso conjunto de dados com a função train-test-split() .

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,Y,test_size=0.3, random_state=42)

Finalmente, podemos construir um modelo.

Para este exemplo, usaremos um modelo simples. Mas, na prática, precisamos testar diferentes modelos com nosso conjunto de dados para ver qual é o melhor. É aí que reside grande parte do trabalho no aprendizado de máquina. Não há regras rígidas ou infalíveis para selecionar um modelo para seus dados. Isso sempre depende do conjunto de dados.

Para este exemplo, usaremos LogisticRegression para treinar nosso modelo. É um bom modelo para começar, já que geralmente funciona bem na previsão de valores booleanos. O Scikit-learn facilita muito a implementação desse modelo.

from sklearn.linear_model import LogisticRegression
from sklearn import metrics

model = LogisticRegression()
model.fit(X_train, y_train)

Depois que o modelo for treinado no conjunto de treinamento, poderemos usá-lo no conjunto de teste chamando a predict função, passando nosso conjunto de teste (X_test).

model.predict(X_test)
# Print the prediction accuracy
print (metrics.accuracy_score(y_test, prediction_test))

Usando metrics.accuracy_score , podemos exibir nossa precisão, 0,8135352579271179, que corresponde a aproximadamente 89%. Isso significa que, quando nosso modelo recebe dados do conjunto de teste para fazer uma previsão, seu resultado está correto em cerca de 89% das vezes. Observe que essa precisão é apenas uma das muitas métricas utilizadas ao avaliar um modelo.

Assim que tivermos nosso modelo, precisaremos salvá-lo para usá-lo em nosso aplicativo de central de atendimento. Para salvar o modelo, usamos a função do joblib dump.

Também precisaremos salvar os nomes das colunas que usamos para o treinamento, pois vamos utilizá-los quando montarmos nosso servidor para fazer previsões.

model_columns = list(df.columns)
model_columns.remove('Churn')
joblib.dump(model_columns, 'model_columns.pkl')

A seguir, vamos criar um servidor simples em Python para hospedar esse modelo.

Serviço de modelo usando o Flask

A seguir, vamos criar um aplicativo Flask para criar um aplicativo de servidor básico que hospede nosso modelo. Precisamos de uma maneira de hospedar o modelo e fazer previsões. Se esta fosse uma aplicação em produção, precisaríamos salvar as informações do usuário, que conteriam os mesmos dados que usamos para o treinamento em nosso conjunto de dados. Isso incluiria há quanto tempo o usuário está na empresa (tempo de serviço), se ele InternetService, PhoneService, OnlineBackup, etc... Dessa forma, ao fazermos nossa previsão, faríamos uma consulta ao nosso banco de dados para recuperar as informações do usuário, faríamos uma previsão com base em nosso modelo e retornaríamos a probabilidade de cancelamento. No entanto, para este exemplo, vamos gerar as informações para um usuário e enviar essa previsão de volta para o aplicativo.

Neste server.py, vamos criar um aplicativo Flask simples, que possui apenas um endpoint chamado /predict. Esse endpoint irá gerar os dados do usuário, chamar nosso modelo e retornar a previsão como uma resposta JSON.

Antes de fazer a previsão, precisamos primeiro carregar o modelo salvo no momento em que o servidor for iniciado.

app = Flask(__name__)

@app.route('/predict', methods=['GET'])
@cross_origin()
def predict():
  #will return prediction
  return

if __name__ == '__main__':
     model = joblib.load('model/model.pkl')
     model_columns = joblib.load('model/model_columns.pkl')
     app.run()

Aqui, usamos joblib para carregar nosso modelo e as colunas que usamos para o treinamento. Precisamos nos certificar de que copiamos model.pkl e model_columns.pkl para o nosso aplicativo de servidor. Na predict() função, vamos gerar os dados de um usuário aleatório e criar um novo DataFrame a partir desses dados, utilizando os nomes das colunas salvos.

def predict():
    random_user_data = generate_data()
    #https://towardsdatascience.com/a-flask-api-for-serving-scikit-learn-models-c8bcdaa41daa
    query = pd.get_dummies(pd.DataFrame(random_user_data, index=[0]))
    query = query.reindex(columns=model_columns, fill_value=0)

    #return prediction as probability in percent
    prediction = round(model.predict_proba(query)[:,1][0], 2)* 100
    return jsonify({'churn': prediction})

A generate_data() função cria um novo dicionário que contém as mesmas colunas do nosso conjunto de dados de treinamento e atribui um valor aleatório a cada uma delas.

def random_bool():
    return random_number()

def random_number(low=0, high=1):
    return random.randint(low,high)

def generate_data():
    internetServices = ['DSL', 'Fiber optic', 'No']
    contracts = ['Month-to-month', 'One year', 'Two year']
    paymentMethods = ['Electronic check', 'Mailed check', 'Bank transfer (automatic)','Credit card (automatic)']

    random_data = {
            'name':'customer',
            'Partner': random_bool(),
            'Dependents': random_bool(),
            'tenure': random_number(0,50),
            'PhoneService': random_bool(),
            'MultipleLines': random_number(-1),
            'InternetService': random.choice(internetServices),
            'OnlineSecurity': random_number(-1),
            'OnlineBackup': random_number(-1),
            'DeviceProtection': random_number(-1),
            'TechSupport': random_number(-1),
            'StreamingTV': random_number(-1),
            'StreamingMovies': random_number(-1),
            'Contract': random.choice(contracts),
            'PaperlessBilling': random_bool(),
            'PaymentMethod': random.choice(paymentMethods)
        }
    return random_data

Para InternetService, Contract e PaymentMethod, codificamos manualmente os valores possíveis que podem ser usados para cada um e escolhemos um valor aleatório. Para as outras características, se elas contivessem apenas um Yes ou No valor no conjunto de treinamento, atribuiremos 1 ou 0, aleatoriamente. Para as características que usavam Yes, No e alguma outra sequência de caracteres, usaremos 1, 0 e -1, respectivamente.

A seguir, vamos examinar nossa função `predict`, que é chamada quando há uma solicitação ao/predict ponto de extremidade.

@app.route('/predict', methods=['GET'])
@cross_origin()
def predict():

    random_user_data = generate_data()
    query = pd.get_dummies(pd.DataFrame(random_user_data, index=[0]))
    query = query.reindex(columns=model_columns, fill_value=0)

    #return prediction as probability in percent
    prediction = round(model.predict_proba(query)[:,1][0], 2)* 100
    return jsonify({'churn': prediction})

Aqui, geramos dados para um usuário aleatório e, em seguida, criamos um DataFrame com a mesma estrutura do DataFrame que usamos para treinar nosso modelo. Ele terá as mesmas colunas, mas conterá apenas uma linha do conjunto de dados.

Por fim, vamos chamar predict_proba o modelo para retornar um vetor com a probabilidade de o usuário cancelar a assinatura. [[0.79329917 0.20670083]] Essa matriz contém a probabilidade de o usuário cancelar a assinatura (0,79329917) e a probabilidade de ele não cancelar (0,20670083). Vamos pegar o último item do vetor, arredondar para duas casas decimais e converter em porcentagem.

prediction = round(model.predict_proba(query)[:,1][0], 2)* 100 Isso retornará 21.0, que é a porcentagem de rotatividade dos usuários.

Por fim, retornaremos esse valor como JSON em um churn objeto.

A seguir, vamos executar e implantar nosso modelo localmente.

No seu terminal, acesse a model_server pasta e execute:

pip install flask pandas python server.py

Agora, nosso servidor de teste está em execução, e podemos testá-lo enviando uma solicitação GET para o /predict ponto de extremidade.

An image showing the Model Server in Postman

O servidor retorna uma resposta em JSON que mostra a porcentagem da probabilidade de um usuário aleatório cancelar sua assinatura.

Aplicativo da Web

Depois de criarmos nosso modelo de aprendizado de máquina e desenvolvermos um backend para fornecer nossas previsões, vamos agora integrá-lo a um aplicativo web para exibir as previsões de cancelamento de assinatura a um agente de atendimento ao cliente.

Para mostrar nossa previsão de rotatividade, vamos criar um Evento de Conversação personalizado chamado churn-prediction que será acionado quando o servidor do modelo retornar sua previsão de cancelamento para um determinado usuário.

Na ui_app pasta, acesse o common.js arquivo, dentro da public pasta, e vamos adicionar o seguinte:

function getChurnForUser(conversation) {
  //Send custom event to agent
  if (window.location.pathname == "/") {
    fetch("http://127.0.0.1:3001/predict",{
    mode: 'cors',
    headers: {
      'Access-Control-Allow-Origin':'*'
    }
  })
    .then(response => {return response.json()})
    .then(json => {
      conversation.sendCustomEvent({ type: 'churn-prediction', body: json}).then(() => {
        console.log('custom event was sent');
      }).catch((error)=>{
        console.log('error sending the custom event', error);
      });
    })
    .catch(error => console.log('error', error));
  }
}

Essa função recebe a conversa “activate” e chama o servidor do modelo que criamos anteriormente. Codificamos a URL e a porta diretamente para facilitar o uso.

Em um ambiente de produção, passaríamos as user-id para gerar uma previsão para esse usuário. Mas, para este exemplo, como mostrado anteriormente, geramos as informações de um usuário aleatório para serem usadas no modelo de previsão de rotatividade.

A seguir, vamos adicionar uma h2 tag na tela do agente para mostrar a previsão de cancelamento. Em seguida, vamos adicionar um ouvinte ao churn-prediction evento, que atualizará o texto dentro da h2 tag. Dentro da setupListeners função, vamos adicionar o seguinte:

activeConversation.on('churn-prediction', (sender, message) => {
  if (window.location.pathname == "/agent") {
    document.getElementById("churn_text").innerHTML = "Likelihood of customer churn: " +  message["body"]["churn"] + "%"
    console.log(sender, message);
  }
});

Quando ocorrem churn-prediction evento é disparado, enviamos a previsão de rotatividade na message propriedade e atualizamos o innerHTML texto com a taxa de cancelamento.

Agora, sempre que novos usuários falarem com nosso atendente, poderemos avaliar a probabilidade de eles cancelarem a assinatura. Se a probabilidade de cancelamento for alta, talvez devêssemos ser um pouco mais atenciosos com eles :)

Conclusão

No geral, demonstramos que construir um modelo de aprendizado de máquina não é mágica, e que qualquer pessoa pode criar algo útil e aplicável em um ambiente de produção. Mostramos que nosso modelo é capaz de prever a probabilidade de cancelamento com 89% de precisão. O que é ótimo para o nosso primeiro modelo. Mas há muito mais que podemos fazer para construir um modelo ainda melhor. Uma das melhores maneiras de aprender é praticando, então vamos ver se você consegue construir um modelo que supere este aqui!

Você pode usar o Notebook do Google Colab fornecido como um projeto inicial. Como sempre, ficaremos felizes em ajudar com qualquer dúvida em nossa Slack da comunidade.

Compartilhar:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Tony HungEx-funcionários da Vonage

Desenvolvedor iOS que se tornou entusiasta de Ciência de Dados e Aprendizado de Máquina. Quero que as pessoas entendam o que é o aprendizado de máquina e como podemos usá-lo em nossas Applications.