https://d226lax1qjow5r.cloudfront.net/blog/blogposts/building-a-user-churn-prediction-model-with-scikit-learn-and-vonage-dr/Blog_Sickit-Learn_Python_1200x600.png

Construire un modèle de prédiction de désabonnement avec Scikit-Learn et Vonage

Temps de lecture : 14 minutes

L'API Conversation de Vonage permet aux développeurs de créer leur propre solution de centre de contact. Avec la voix, le texte et les intégrations à d'autres solutions, Vonage vous permet de construire une solution complexe, sans que le développement soit complexe.

Lorsque vous créez votre solution de centre de contact, vous avez besoin qu'elle soit intelligente. Les solutions les plus performantes utilisent l'IA pour acheminer les appels, traduire les textes, recommander des produits, etc. Ce qui est génial, c'est que vous n'avez pas besoin d'être un chercheur en IA avec un doctorat pour intégrer l'IA dans votre application. Vous n'avez pas non plus besoin de dépendre d'un système tiers. Cela semble impossible à première vue, mais il y a eu beaucoup de progrès sur de nombreuses bibliothèques d'apprentissage automatique afin que vous, en tant que développeur, puissiez intégrer un système d'apprentissage automatique dans votre solution. Dans cet article, nous allons nous pencher sur l'ajout d'un moyen de prédire la probabilité de désabonnement d'un client.

Qu'est-ce que le désabonnement ?

Par définition, le taux de désabonnement est le nombre de clients qui ont cessé d'utiliser votre produit, divisé par le nombre total de clients. Par exemple, une entreprise ayant un taux de désabonnement de 1 % par mois avec 1 000 clients signifie que 10 clients sur 1 000 cessent d'utiliser le service de l'entreprise chaque mois. Le taux de désabonnement est utilisé comme indicateur de la performance de l'entreprise. Une entreprise dont le taux de désabonnement est faible signifie généralement que les clients restent fidèles. Un taux de désabonnement plus élevé signifie que les clients utilisent le produit ou le service, mais qu'ils le quittent ensuite.

Un centre de contact est un endroit où les clients interagissent directement avec l'entreprise, en particulier avec le service client. La perte d'un client en raison d'une mauvaise assistance peut avoir un impact sur le taux de désabonnement et, par conséquent, sur la santé de l'entreprise.

Dans ce billet, nous verrons comment créer une application qui simule une conversation entre un client et un agent et qui peut prédire la probabilité d'un désabonnement, à l'aide de l'API Conversation de Vonage.

Video showing demo of churn

Vue d'ensemble

Dans notre démo, nous avons deux personas d'utilisateurs : un client et un agent. Pour cet exemple, nous supposerons que l'entreprise est un fournisseur de services de télévision et que le client a une question sur son service. Nous supposons également que ce client est client de l'entreprise depuis un certain temps, et nous disposons de données à l'appui.

Dans notre exemple, nous disposons d'informations sur l'utilisateur. Il peut s'agir de

  • Depuis combien de mois le client utilise le service.

  • Leur mode de paiement actuel. (Chèque, carte de crédit..)

  • Les services qu'ils utilisent. (télévision en direct, décodeur, services de diffusion en continu...)

  • Et bien d'autres encore.

Dans notre démo, lorsque le client interagit avec l'agent, nous affichons la probabilité de désabonnement de l'utilisateur sur l'écran de l'agent, dès qu'il interagit. Cela pourrait être utile à l'agent avant d'entamer la conversation, afin qu'il accorde plus d'attention au client, en fonction de la probabilité de désabonnement.

Conditions préalables

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Nous utiliserons Hui Jing Chende Hui Jing Chen de Hui Jing Chen comme point de départ. Nous ajouterons notre fonctionnalité de prédiction du désabonnement à cette application.

Pour exécuter notre application localement, nous allons cloner le fichier repoet nous devrons utiliser ngrok. Si vous n'êtes pas familier avec Ngrok, veuillez vous référer à notre tutoriel Ngrok avant de continuer.

Avant de passer à l'application, nous devons d'abord construire notre modèle. Et pour le construire, nous utiliserons les Jupyter Notebooks fonctionnant sur Google Colab. Un carnet Jupyter est un moyen interactif d'exécuter du code et est largement utilisé dans la science des données et l'apprentissage automatique. Google Colab est un service gratuit qui vous permet d'exécuter ces carnets dans le nuage. Le code pour construire le notebook se trouve iciPour exécuter ce bloc-notes, téléchargez-le sur Google Colab.

Pour ce tutoriel, nous supposerons que vous avez une compréhension de base de ce qu'est l'apprentissage automatique, mais vous n'aurez pas besoin de tout comprendre pour suivre le cours.

Pour construire un modèle, nous avons d'abord besoin de données. Pour cet exemple, nous utiliserons L'ensemble de données sur le désabonnement aux services de télécommunications d'IBM. Ce jeu de données contient 7043 lignes de données anonymes d'utilisateurs de télécommunications. Pour mieux comprendre le jeu de données, nous allons examiner les 10 premières lignes des données à l'aide de Pandas. Pandas est une bibliothèque python permettant de traiter et de comprendre les données. Pour chaque utilisateur, nous disposons de 23 colonnes, également appelées caractéristiques. Il s'agit notamment du sexe du client (homme, femme), de son ancienneté (depuis combien de temps est-il client) et du fait qu'il dispose de différents services, notamment le téléphone, l'internet et la télévision.

Pour construire notre modèle, nous devons d'abord nous assurer qu'il n'y a pas de valeurs vides dans l'ensemble de données. Si nous ne vérifions pas cela et que nous essayons de construire notre modèle, nous aurons des erreurs.

Lisons notre jeu de données et supprimons les valeurs vides.

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

Ensuite, utilisez df.head() pour afficher les 10 premières lignes des données. Lorsque nous examinons ces données, nous constatons que de nombreuses lignes contiennent des chaînes de caractères. (YES, NO). Nous devons maintenant convertir ces chaînes en nombres, car les modèles d'apprentissage automatique ne savent traiter que les nombres.

Pour chaque ligne contenant une chaîne, nous devons vérifier si les chaînes sont uniques. Pour visualiser toutes les valeurs possibles dans cette colonne, pandas dispose d'une fonction, appelée unique() pour le faire à notre place.

df.Partner.unique()

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

Cela signifie que la ligne ne contient que les valeurs YES et NO. Pour cette colonne, nous pouvons convertir ces chaînes en booléens (1,0). Cependant, si nous regardons les autres lignes, disons PaymentMethodil y a plus de valeurs que YES ou NO.

df.PaymentMethod.unique()

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

Pour cette colonne, nous devons donc faire un peu plus de travail. Chaque fois qu'une valeur est YES ou NOnous pouvons la convertir en 1 ou 0respectivement. Lorsqu'il s'agit d'une autre chaîne de caractères, nous la convertissons en -1. Là encore, le modèle d'apprentissage automatique ne peut utiliser que des nombres, c'est pourquoi nous lui attribuons la valeur -1.

Si nous examinons les autres colonnes, PhoneService, MultipleLines, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTVet StreamingMoviesils semblent être similaires. Écrivons donc une fonction qui parcourt chaque colonne et convertit nos chaînes en ints.

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 est une liste de toutes les colonnes que nous devons mettre à jour. to_numeric est une fonction qui prend la valeur de chaque ligne et convertit la chaîne en un nombre entier. Enfin, nous allons parcourir en boucle tous les éléments de to_numeric et appeler la fonction pandas apply pour appeler notre fonction. Jetons un coup d'œil aux 10 premières lignes pour vérifier.

Il semble que ces lignes soient désormais valides, mais nous devons nous occuper des autres colonnes. Commençons par inspecter Contact et voyons les valeurs affichées.

df.Contract.unique()

qui renvoie : array(['Month-to-month', 'One year', 'Two year'], dtype=object)

Ces valeurs sont toujours des chaînes de caractères, mais ce n'est pas aussi simple que de les convertir en 1et en 0's. D'autres colonnes de cet ensemble de données sont similaires, notamment PhoneService, MultipleLines, InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovies, Contract, PaperlessBillinget PaymentMethod.

Heureusement, dans pandas, nous pouvons convertir ces valeurs en utilisant get_dummies(). Cette fonction, lorsqu'elle est appliquée à une colonne, crée une nouvelle colonne pour chaque valeur possible. Et chaque valeur dans chacune de ces nouvelles colonnes sera 1 ou 0. Cette méthode est également connue sous le nom de codage à un coup.

Prenons par exemple la colonne Contract qui contient les valeurs de Month-to-month, One year, et Two year. En utilisant get_dummies()nous créerons 3 nouvelles colonnes appelées Contract_Month-to-month, Contract_One year et Contract_Two year. Et chaque valeur dans ces colonnes sera soit 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)

Ici, nous créons une liste de ces caractéristiques que nous voulons convertir en caractéristiques catégorielles et nous appelons la fonction get_dummies en utilisant le DataFrame(df) et la liste des colonnes (categorical_features). Visualisons à nouveau les 10 premières lignes pour vérifier notre travail. Étant donné que le DataFrame compte désormais 41 caractéristiques, nous établirons un lien vers la cellule ici plutôt que de montrer une capture d'écran du DataFrame

Il semble que toutes les colonnes soient numériques. Construisons notre modèle. Pour construire notre modèle, nous allons utiliser un autre paquetage appelé scikit-learn. Scikit-Learn possède de nombreuses fonctions intégrées permettant de traiter et d'entraîner un modèle à partir de nos données.

Tout d'abord, nous avons besoin de 2 matrices, X et y. X est une matrice qui comprend toutes nos caractéristiques, à l'exception de celle que nous utilisons pour faire des prédictions (Churn). Y est simplement la valeur de Churnqui est un 1 ou 0.

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

Le résultat est le suivant : ((7043, 40), (7043,)) Ce qui signifie qu'il y a 7043 lignes et 40 colonnes dans X. Pour Ynous avons 7043 lignes.

Ensuite, nous devons diviser nos données en un ensemble de formation et un ensemble de test. Lors de la formation de notre modèle, nous n'utilisons qu'une partie de l'ensemble de données. Le reste des données est utilisé pour le test. Cela permet de voir si notre modèle a bien appris les données. Avec Scikit-Learn, nous pouvons diviser notre ensemble de données en utilisant la fonction train-test-split() à l'aide de la fonction 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)

Enfin, nous pouvons construire un modèle.

Pour cet exemple, nous utiliserons un modèle simple. Mais dans le monde réel, nous devons tester différents modèles avec notre ensemble de données pour voir lequel est le meilleur. C'est là que réside une grande partie du travail dans l'apprentissage automatique. Il n'y a pas de règle absolue pour sélectionner un modèle pour vos données. Cela dépend toujours de l'ensemble de données.

Pour cet exemple, nous utiliserons Régression logistique pour entraîner notre modèle. C'est un bon modèle pour commencer car il fonctionne généralement bien pour faire des prédictions sur des valeurs booléennes. Scikit-learn rend l'implémentation de ce modèle très simple.

from sklearn.linear_model import LogisticRegression
from sklearn import metrics

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

Une fois que le modèle a été entraîné sur l'ensemble d'entraînement, nous pouvons maintenant l'utiliser sur l'ensemble de test en appelant la fonction predict en lui passant notre ensemble de test (X_test).

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

En utilisant metrics.accuracy_score nous pouvons imprimer notre précision, 0.8135352579271179, soit ~89%. Cela signifie que lorsque notre modèle reçoit des données de l'ensemble de test pour faire une prédiction, son résultat est correct dans 89 % des cas. Notez que cette précision n'est qu'une des nombreuses mesures utilisées pour l'évaluation d'un modèle.

Une fois que nous avons notre modèle, nous devons le sauvegarder pour l'utiliser dans notre application de centre de contact. Pour sauvegarder le modèle, nous utilisons la fonction joblib dump.

Nous devrons également enregistrer les noms des colonnes que nous avons utilisées pour l'entraînement, car nous les utiliserons lorsque nous construirons notre serveur pour faire des prédictions.

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

Ensuite, nous allons construire un simple serveur Python pour servir ce modèle.

Modélisation du service à l'aide d'un flacon

Ensuite, nous allons créer un Flask afin de créer une application serveur de base pour héberger notre modèle. Nous avons besoin d'un moyen d'héberger notre modèle et de faire des prédictions. S'il s'agissait d'une application de production, nous aurions besoin d'enregistrer les informations de l'utilisateur, qui contiendraient les mêmes informations que celles que nous avons utilisées pour la formation dans notre ensemble de données. Il s'agirait notamment de savoir depuis combien de temps l'utilisateur travaille pour l'entreprise, s'il a des antécédents professionnels, s'il a des antécédents de travail, etc. InternetService, PhoneService, OnlineBackupetc... Ainsi, lorsque nous faisons notre prédiction, nous interrogeons notre base de données pour obtenir les informations sur les utilisateurs, nous faisons une prédiction à partir de notre modèle et nous obtenons la probabilité de changement. Cependant, pour cet exemple, nous allons générer les informations pour un utilisateur et renvoyer cette prédiction à l'application.

En server.pynous allons créer une application Flask simple, qui n'a qu'un point de terminaison appelé /predict. Ce point de terminaison générera les données de l'utilisateur, invoquera notre modèle et renverra la prédiction sous la forme d'une réponse JSON.

Avant d'effectuer la prédiction, nous devons d'abord charger le modèle sauvegardé au démarrage du serveur.

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

Nous utilisons ici joblib pour charger notre modèle et les colonnes que nous avons utilisées pour l'entraînement. Nous devons nous assurer que nous avons copié model.pkl et model_columns.pkl dans notre application serveur. Dans la fonction predict() nous allons générer les données d'un utilisateur aléatoire, créer un nouveau DataFrame à partir des données en utilisant les noms des colonnes sauvegardées.

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

La fonction generate_data() crée un nouveau dictionnaire contenant les mêmes colonnes que notre ensemble de données d'apprentissage et attribue une valeur aléatoire à chacune d'entre elles.

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

Pour InternetService, Contract et PaymentMethodnous codons en dur les valeurs possibles qui peuvent être utilisées pour chacune d'entre elles et nous choisissons une valeur aléatoire. Pour les autres caractéristiques, s'il n'y avait qu'un Yes ou No dans l'ensemble d'apprentissage, nous attribuons une valeur de 1 ou 0Pour les caractéristiques qui ont utilisé Yes, No et une autre chaîne, nous utiliserons 1, 0 et -1respectivement.

Ensuite, passons en revue notre fonction predict, qui est appelée lorsqu'une requête est adressée au point de terminaison/predict est sollicitée.

@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})

Ici, nous générons des données pour un utilisateur aléatoire, puis nous créons un DataFrame qui ressemble au DataFrame que nous avons utilisé pour l'entraînement de notre modèle. Il aura les mêmes colonnes mais ne comportera qu'une seule ligne de l'ensemble de données.

Enfin, nous appellerons predict_proba au modèle pour qu'il renvoie un vecteur de la probabilité d'abandon de l'utilisateur. [[0.79329917 0.20670083]] Cette matrice contient la probabilité que l'utilisateur change (0,79329917) et que l'utilisation ne change pas (0,20670083). Nous prendrons le dernier élément du vecteur, l'arrondirons à deux décimales et le convertirons en pourcentage.

prediction = round(model.predict_proba(query)[:,1][0], 2)* 100 Le résultat est le suivant 21.0qui est le pourcentage d'abandon de l'utilisateur.

Enfin, nous renverrons cette valeur sous forme de JSON dans un objet churn objet.

Ensuite, nous allons exécuter et déployer notre modèle localement.

Dans votre terminal, naviguez jusqu'au dossier model_server et exécutez :

pip install flask pandas python server.py

Notre serveur de modèle fonctionne et nous pouvons maintenant le tester en effectuant une requête GET vers le point d'arrivée /predict au point de terminaison.

An image showing the Model Server in Postman

Le serveur renvoie une réponse JSON qui indique le pourcentage de probabilité de désabonnement d'un utilisateur aléatoire.

Applications Web

Après avoir construit notre modèle d'apprentissage automatique et créé un backend pour servir nos prédictions, nous allons maintenant l'intégrer dans une application web pour montrer les prédictions de désabonnement à un agent du service clientèle.

Pour montrer notre prédiction de désabonnement, nous allons créer un événement de conversation personnalisé. événement de conversation personnalisé appelé churn-prediction qui sera appelé lorsque le serveur de modèle renvoie sa prédiction de désabonnement pour un utilisateur donné.

Dans le dossier ui_app naviguez jusqu'au fichier common.js à l'intérieur du dossier public et nous ajouterons ce qui suit :

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));
  }
}

Cette fonction accepte la conversation activate et appelle le serveur de modèle que nous avons construit précédemment. Nous avons codé en dur l'URL et le port pour faciliter l'utilisation.

Dans un environnement de production, nous transmettrions le paramètre user-id pour générer une prédiction pour cet utilisateur. Mais pour cet exemple, comme indiqué précédemment, nous générons des informations aléatoires sur l'utilisateur qui seront utilisées dans le modèle de prédiction du désabonnement.

Ensuite, nous ajouterons une balise h2 sur l'écran de l'agent pour afficher la prédiction de désabonnement. Ensuite, nous ajouterons un écouteur à l'événement churn-prediction qui mettra à jour le texte à l'intérieur de la balise h2 qui mettra à jour le texte à l'intérieur de la balise Dans la fonction setupListeners nous ajouterons ceci :

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);
  }
});

Lorsque l'événement churn-prediction se déclenche, nous envoyons la prédiction de désabonnement dans la propriété message et mettons à jour le innerHTML avec le désistement.

Désormais, chaque fois que de nouveaux utilisateurs parleront à notre agent, nous pourrons voir la probabilité de désabonnement de ces utilisateurs. Si la probabilité de désabonnement est élevée, nous devrions peut-être être un peu plus gentils avec eux :)

Conclusion

Dans l'ensemble, nous avons montré que la construction d'un modèle d'apprentissage automatique n'est pas magique, et que tout le monde peut construire quelque chose d'utile et qui peut être utilisé dans un environnement de production. Nous avons montré que notre modèle est capable de prédire la probabilité de désabonnement avec une précision de 89 %. Ce qui est excellent pour notre premier modèle. Mais nous pouvons faire beaucoup plus pour construire un modèle encore meilleur. L'une des meilleures façons d'apprendre est de le faire, alors voyons si vous pouvez construire un modèle plus performant que celui-ci !

Vous pouvez utiliser le carnet de notes fourni Google Colab Notebook comme projet de départ. Comme toujours, nous sommes heureux de vous aider pour toute question dans notre communauté slack.

Partager:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Tony HungAnciens de Vonage

Développeur IOS devenu passionné de science des données et d'apprentissage automatique. Je veux que les gens comprennent ce qu'est l'apprentissage automatique et comment nous pouvons l'utiliser dans nos Applications.