vous avez entendu parler de machine learning partout, mais avez vous vu comment cela fonctionnait? Nous allons voir avec un exemple comment ça marcce à quoi ça sert et vous comprendrez à la fin pourquoi il est très intéressant d’en faire dans les entreprises.
Le contexte : avec des données d’abandon de souscription à un forfait mobile (churn), nous allons établir un modèle mathématique pour prédire les futur churn. L’intérêt de faire confiance au machine learning est qu’il y a un grand nombre de paramètres qui influence le fait qu’un client part à la concurrence, il est quasi impossible de faire le diagnostic à la main pour savoir si le client va partir ou non, plus exactement il y a 18 paramètres.
Le principe du machine learning est de prendre ces données, les travailler et les faire passer dans un algorithme qui va générer un modèle mathématique qui servira aux futures données, pour prédire si le client va churner ou non. En gros se servir du passé pour prédire le futur.
Contenu
- 1 Le dataset à télécharger
- 2 Installation des paquets
- 3 Utiliser l’éditeur de code Jupyter Notebook
- 4 Commençons à explorer les caractéristiques de notre jeu de données
- 5 Analyse exploratoire des données
- 6 Préparation des données pour la modélisation
- 7 Construction du modèle de prédiction du churn
- 8 Evaluation du modèle
- 9 Sauvegarder le modèle
- 10 Chargement du modèle et des données sauvegardées
Le dataset à télécharger
Allez sur Kaggle et téléchargez le dataset (il vous faudra créer un compte). On va explorer ce dataset, mais ce qu’il faut savoir c’est que dans ce dataset, 30% des lignes concernent les clients ayant churné, et 70% sont des clients qui n’ont pas churné. On n’a pas une distribution 50/50. En machine learning, entrainer sur ce dataset va produire un modèle mathématique qui va prédire de façon biaisé, quelquesoit l’entrée donnée, les probabilités que la prédiction soit en faveur d’un non churn sera anormalement haute.
Pour éviter ce déséquilibre dans les données (et ce déséquilibre importe car le type d’issue d’une prédiction est unique : le churn), on va créer artificiellement (oui artificiellement) des données pour compenser ce déséquilibre, donc on va générer des données ayant pour issue le churn, afin d’avoir un rapport 50/50 (churn/non-churn)
Installation des paquets
Pour faire du traitement de machine learning, il vous faudra installer les paquets suivants : numpy, pandas, matplotlib, seaborn,sklearn
Note: lors de l’installation de sklearn, vous aurez une erreur de paquet déprécié, en fait dans l’import de bibliothèque, c’est bien sklearn qu’il faut importer, mais lors que vous installez le paquet avec pip, c’est scikit-learn qu’il faut installer ! (Lien pour en savoir plus)
Utiliser l’éditeur de code Jupyter Notebook
Jupyter noteboopk est un éditeur de code progressif, très adapté à des non codeur pur et dur. Pour les datascientistes, c’est très pertinent car ils peuvent voir comment évolue leur traitement de données. Et pour tout le monde, c’est très visuel lors de l’apprentissage du machine learning, même pour un codeur pur et dur comme moi.
Vous pouvez décomposer le code en étape et refaire exécuter une étape à tout moment, pratique si vous vous trompez dans votre code et qu’il y a une erreur.
Pour installer Jupyter Notebook, faites la commande
# installation de jupyterlab pip install jupyterlab pour les macs pip3 install jupyterlab pour démarrer jupyter lab pip install notebook ou pour les mac pip3 install notebook puis démarrer en faisant : jupyter notebook
Note sur Jupyterlab : jupyterlab est la version moderne de jupyter notebook, et possède un système d eplugin plus simple à utiliser et un terminal et un éditeur de texte.
Commençons à explorer les caractéristiques de notre jeu de données
Notre objectif est d’entrainer un modèle sur le jeu de données, pour ensuite prédire la probabilité d’un client à passer à la concurrencer (churner). customerChurn.csv est le fichier de chez Kaggle.
import pandas as pd
df = pd.read_csv('customerChurn.csv')
df.head()
La commande df.head() affiche les 5 premières ligne du dataframe. Le dataframe est la version de Pandas d’un fichier Excel en gros.

Nous avons un fichier avec des colonnes, qu’on appelle de feature, puisque chaque colonne est une caractéristique d’un client. Pour voir le nom et le type de la donnée stockée dans chaque colonne ainsi que le nombre de colonnes, on fait
df.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customerID 7043 non-null object 1 gender 7043 non-null object 2 SeniorCitizen 7043 non-null int64 3 Partner 7043 non-null object 4 Dependents 7043 non-null object 5 tenure 7043 non-null int64 6 PhoneService 7043 non-null object 7 MultipleLines 7043 non-null object 8 InternetService 7043 non-null object 9 OnlineSecurity 7043 non-null object 10 OnlineBackup 7043 non-null object 11 DeviceProtection 7043 non-null object 12 TechSupport 7043 non-null object 13 StreamingTV 7043 non-null object 14 StreamingMovies 7043 non-null object 15 Contract 7043 non-null object 16 PaperlessBilling 7043 non-null object 17 PaymentMethod 7043 non-null object 18 MonthlyCharges 7043 non-null float64 19 TotalCharges 7043 non-null object 20 Churn 7043 non-null object dtypes: float64(1), int64(2), object(18) memory usage: 1.1+ MB
Et pour savoir combien il y a d’enregistrements on fait:
df["Churn"].value_counts() Churn No 5174 Yes 1869 Name: count, dtype: int64
donc on a 19 colonnes utile et une colonne inutile d’un point de vue machine learning, c’est la colonne customerID, elle ne comporte aucune information métier. La dernière colonne appelée Churn, prend deux valeuer, elle indique si le client est parti chez le concurrent ou non. Elle prend les valeur No ou Yes. Pour afficher le compte on fait:
df["Churn"].values_count() Churn No 5174 Yes 1869 Name: count, dtype: int64 il y a 1869 personnes partie, on note ici que le nombre de Yes et de No est différent dans un rapport 1 à 3 environ. Cela on le verra plus tard aura son importance dans l'entrainement du modèle. Il y a inégalité dans les 2 camps, cette disparité va avoir une incidence sur la qualité du modèle entrainé. En effet, le modèle résultant va être biaisé avec le No, car ce dernier est majoritaire. On devra équilibrer artificiellement ce dataset pour avoir un bon modèle de prédiction.
Analyse exploratoire des données
Maintenant explorons les relations entre les différentes catégorie et la colonne Churn
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
cols = ['gender','SeniorCitizen',"Partner","Dependents"]
numerical = cols
plt.figure(figsize=(20,4))
for i, col in enumerate(numerical):
ax = plt.subplot(1, len(numerical), i+1)
unique_vals = df[col].nunique()
sns.countplot(x=col, hue=col, data=df, legend=False,
palette=sns.color_palette("husl", df[col].nunique()))
#sns.countplot(x=str(col), data=df, palette=sns.color_palette("husl", unique_vals))
ax.set_title(f"{col}")
L’idée est ici de graphiquement regarder pour chaque colonne la distribution des valeurs. Ici on voit la colonne Genre, SeniorCitizen, Partner, Dependents, des varaible catégorielle relative à la démographie.

On voit qu’il y a autant d’homme que de femmes, mais que les seniors sont minoritaires, qu’il y a autant de célibataires que de mariés, et il y a 1 tier de la population qui est dépendantes.
Maintenant regardons la relation entre le MonthlyCharges et le Churn, on peut se dire que plus le forfait est élevé plus les gens ont tendance à partir
sns.boxplot(x='Churn', y='MonthlyCharges', hue='Churn', data=df, palette='Set2', dodge=False, legend=False)
Effectivement c’est ce que nous constatons, les Yes sont entre 50 et 100 dollars / mois.

Pour comprendre le boxplot je vous joins ce petit schéma pris sur datatab.net

Maintenant analysons la relation entre quelques autres variable catégorielle avec le Churn
cols = ['InternetService',"TechSupport","OnlineBackup","Contract"]
plt.figure(figsize=(14,4))
for i, col in enumerate(cols):
ax = plt.subplot(1, len(cols), i+1)
sns.countplot(x ="Churn", hue = str(col), data = df)
ax.set_title(f"{col}")

Cette fois-ci les catégories ont plusieurs valeurs (3). On peut voir que pour InternetService, donc le forfait box de l’abonnement, les churners n’ont pas de forfait box ou très peu, idem pour le service ADSL, par contre ils sont nombreux à être sur fibre optique. Concernant les non churners, ils sont plus équitablement distribué.
InternetService : Il ressort clairement du graphique ci-dessus que les clients utilisant une connexion Internet par fibre optique résilient leur abonnement plus souvent que les autres. Cela pourrait s’expliquer par le fait que la fibre est un service plus coûteux, ou bien que ce fournisseur n’offre pas une bonne couverture.
TechSupport : De nombreux utilisateurs ayant résilié leur abonnement ne s’étaient pas inscrits au service d’assistance technique. Cela pourrait signifier qu’ils n’ont reçu aucune aide pour résoudre leurs problèmes techniques et ont décidé d’arrêter d’utiliser le service.
OnlineBackup : Beaucoup de clients ayant résilié leur abonnement ne s’étaient pas inscrits au service de sauvegarde en ligne pour le stockage de données.
Contract : Les utilisateurs qui ont résilié leur abonnement étaient presque toujours sous contrat mensuel. Cela semble logique, car ces clients paient au mois et peuvent facilement annuler leur abonnement avant le prochain cycle de paiement.
Même sans construire un modèle d’apprentissage automatique sophistiqué, une simple analyse basée sur les données comme celle-ci peut aider les organisations à comprendre pourquoi elles perdent des clients et ce qu’elles peuvent faire pour y remédier.
Par exemple, si l’entreprise se rend compte que la majorité des clients qui résilient leur abonnement ne se sont pas inscrits au service d’assistance technique, elle pourrait inclure ce service gratuitement dans certaines de ses futures offres afin d’éviter que d’autres clients ne partent.
Préparation des données pour la modélisation
Rappel df.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customerID 7043 non-null object 1 gender 7043 non-null object 2 SeniorCitizen 7043 non-null int64 3 Partner 7043 non-null object 4 Dependents 7043 non-null object 5 tenure 7043 non-null int64 6 PhoneService 7043 non-null object 7 MultipleLines 7043 non-null object 8 InternetService 7043 non-null object 9 OnlineSecurity 7043 non-null object 10 OnlineBackup 7043 non-null object 11 DeviceProtection 7043 non-null object 12 TechSupport 7043 non-null object 13 StreamingTV 7043 non-null object 14 StreamingMovies 7043 non-null object 15 Contract 7043 non-null object 16 PaperlessBilling 7043 non-null object 17 PaymentMethod 7043 non-null object 18 MonthlyCharges 7043 non-null float64 19 TotalCharges 7043 non-null object 20 Churn 7043 non-null object dtypes: float64(1), int64(2), object(18) memory usage: 1.1+ MB
Beaucoup de données sont de type Object, des valeurs non numériques, « TotalCharges » qui est un montant est un objet par exemple, on va le convertir en valeur numérique
df['TotalCharges'] = df['TotalCharges'].apply(lambda x: pd.to_numeric(x, errors='coerce')).dropna()
Encodage des variables catégorielles
Les valeurs catégorielles (non numérique) doivent être convertie en valeur numérique pour être modélisée, des colonnes avec pour valeur Yes/No, on peut rendre le Yes en 1 et le No en 0. Avec Sckitlearn on va pouvoir transformer ces valeurs en nombres. Si on regarde le dataframe sans les valeur numériques
cat_features = df.drop(['customerID','TotalCharges','MonthlyCharges','SeniorCitizen','tenure'],axis=1) cat_features.head()

La commande ci-dessus nous permet de voir les valeurs catégorielles des features. Transformaons les en valuer numérique avec ScikitLearn
from sklearn import preprocessing le = preprocessing.LabelEncoder() df_cat = cat_features.apply(le.fit_transform) df_cat.head(1)

Scikitlearn convertit automatiquement les valeurs catégorielle en valeur numérique. Il n’y a pas de règle particulière, la valeur 0 correspond à la première valeur catégorielle ordonnées dans l’ordre alphabétique. No est avant Yes donc No vaut zéro et Yes vaut 1.
Regardons maintenant les types
df_cat.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 16 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 gender 7043 non-null int64 1 Partner 7043 non-null int64 2 Dependents 7043 non-null int64 3 PhoneService 7043 non-null int64 4 MultipleLines 7043 non-null int64 5 InternetService 7043 non-null int64 6 OnlineSecurity 7043 non-null int64 7 OnlineBackup 7043 non-null int64 8 DeviceProtection 7043 non-null int64 9 TechSupport 7043 non-null int64 10 StreamingTV 7043 non-null int64 11 StreamingMovies 7043 non-null int64 12 Contract 7043 non-null int64 13 PaperlessBilling 7043 non-null int64 14 PaymentMethod 7043 non-null int64 15 Churn 7043 non-null int64 dtypes: int64(16) memory usage: 880.5 KB
On va maintenant fusionner avec le dataframe qui était déjà numérique
num_features = df[['customerID','TotalCharges','MonthlyCharges','SeniorCitizen','tenure']] finaldf = pd.merge(num_features, df_cat, left_index=True, right_index=True)
Oversampling
On avait dit que le Churn Yes était minoritaire dans les données, alors qu’il devait être à 50/50 avec le churn No.
Comme mentionné précédemment, le jeu de données est déséquilibré, ce qui signifie que la majorité des valeurs de la variable cible appartiennent à une seule classe. La plupart des clients du jeu de données n’ont pas résilié leur abonnement — seulement 27 % l’ont fait.
Ce problème de déséquilibre des classes peut entraîner de mauvaises performances d’un modèle d’apprentissage automatique. Certains algorithmes, lorsqu’ils sont entraînés sur un jeu de données déséquilibré, finissent toujours par prédire la classe majoritaire. Dans notre cas, par exemple, le modèle pourrait prédire qu’aucun client n’a résilié. Bien qu’un tel modèle semble très précis (il serait correct dans 73 % des cas), il ne nous est d’aucune utilité puisqu’il prédit toujours le même résultat.
Il existe différentes techniques pour résoudre le problème de déséquilibre des classes en apprentissage automatique. Dans ce tutoriel, nous allons utiliser une technique appelée suréchantillonnage (oversampling). Ce processus consiste à sélectionner aléatoirement des échantillons de la classe minoritaire et à les ajouter au jeu de données d’entraînement. Nous allons suréchantillonner la classe minoritaire jusqu’à ce que le nombre d’exemples soit égal à celui de la classe majoritaire.
Avant de procéder au suréchantillonnage, effectuons une séparation entre le jeu d’entraînement et le jeu de test. Nous appliquerons le suréchantillonnage uniquement sur le jeu d’entraînement, car le jeu de test doit rester représentatif de la population réelle.
On va constituer un dataset pour le training du modèle, et un dataset de test qu’on va soumettre qu modèle entrainé.
from sklearn.model_selection import train_test_split finaldf = finaldf.dropna() finaldf = finaldf.drop(['customerID'],axis=1) X = finaldf.drop(['Churn'],axis=1) y = finaldf['Churn'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
Chaque dataset est en deux parties X (majuscule) et y (minuscule), y représente la dernière colonne qui est le churn. Ainsi Dans le dataset de training, on a X_train et y_train. Et pour le dataset de test on a X_test et y_test.
#y_train X_test.info() <class 'pandas.core.frame.DataFrame'> Index: 2321 entries, 2481 to 5585 Data columns (total 19 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 TotalCharges 2321 non-null float64 1 MonthlyCharges 2321 non-null float64 2 SeniorCitizen 2321 non-null int64 3 tenure 2321 non-null int64 4 gender 2321 non-null int64 5 Partner 2321 non-null int64 6 Dependents 2321 non-null int64 7 PhoneService 2321 non-null int64 8 MultipleLines 2321 non-null int64 9 InternetService 2321 non-null int64 10 OnlineSecurity 2321 non-null int64 11 OnlineBackup 2321 non-null int64 12 DeviceProtection 2321 non-null int64 13 TechSupport 2321 non-null int64 14 StreamingTV 2321 non-null int64 15 StreamingMovies 2321 non-null int64 16 Contract 2321 non-null int64 17 PaperlessBilling 2321 non-null int64 18 PaymentMethod 2321 non-null int64 dtypes: float64(2), int64(17) memory usage: 362.7 KB
C’est avec la librairie imblearn qu’on va construire le suréchantillon sur les données de training
from imblearn.over_sampling import SMOTE oversample = SMOTE(k_neighbors=5) X_smote, y_smote = oversample.fit_resample(X_train, y_train) X_train, y_train = X_smote, y_smote Ensuite consmptez le nombre de rangées : y_train.value_counts() Churn 0 3452 1 3452 Name: count, dtype: int64 On est à 50/50
Construction du modèle de prédiction du churn
Maintenant la partie vraiment importante ! On a passé beaucoup de temps pour nettoyer, changé la valeur catégorielle en valeur numérique, rééquilibré les population, maintenant on va entrainer le modèle !
from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier(random_state=46) rf.fit(X_train,y_train)
Evaluation du modèle
On va évaluer l aprécision du modèle avec les données de test (dont on connait l’issue car ce sont les données d’origine)
#customer churn model evaluation from sklearn.metrics import accuracy_score preds = rf.predict(X_test) print(accuracy_score(preds,y_test)) 0.7733735458853942 On a une précision de 77%
Sauvegarder le modèle
On en va pas à chaque fois réentrainer le modèle quand on va faire une prédiction, donc on va le sauvegarder pour pouvoir le réutiliser.
#save the model import joblib # After training your model (format Pickle) joblib.dump(rf, 'random_forest_model.pkl')
Sauvergarder les données de test et de training
#save training and testing data to file
import pickle
# Save X data
with open('X_train.pkl', 'wb') as f:
pickle.dump(X_train, f)
with open('X_test.pkl', 'wb') as f:
pickle.dump(X_test, f)
# Save y data
with open('y_train.pkl', 'wb') as f:
pickle.dump(y_train, f)
with open('y_test.pkl', 'wb') as f:
pickle.dump(y_test, f)
Chargement du modèle et des données sauvegardées
On va ouvrir un autre fichier Notebook vierge et on va juste charge le modèle et les données pour passer au crible. En effet le modèle que vous avez calculé va être réutilisé, et il n’est pas question de le calculer à chaque fois que vous avez besoin de scorer la probabilité d’un client de patir.
Chargement du modèle
import joblib
#load the model
loaded_rf = joblib.load('random_forest_model.pkl')
print(loaded_rf)
# Now you can use the loaded model to make predictions
#prediction = loaded_rf.predict(new_customer)
#print(prediction)
Chargement des données
#load the datas
import pickle
with open('X_test.pkl', 'rb') as f:
X_test = pickle.load(f)
with open('y_test.pkl', 'rb') as f:
y_test = pickle.load(f)
X_test.head()

On rejoue le test, X_test contient les clients, et on passe en argument de la fonction predict qui va nous retourner la variable prediction, qui est une Serie, contenant des 0 et 1, 0 correspondant à « No » et 1 à « Yes ».
# Now you can use the loaded model to make predictions from sklearn.metrics import accuracy_score prediction = loaded_rf.predict(X_test) #la série qui contient les outcome (churn si 1 , 0 si no churn) print(prediction) [0 0 1 ... 0 1 1] Ce qu'on voit c'est la colonne churn
Test avec un sous ensemble
Ici je simule le fait que j’ai un nouveau client à scorer, c’est donc une ligne (un seul client), je passe cette ligne en argument de la fonction predict, qui va nous retourner un Serie avec un seul élément (on n’a scoré qu’un client), ici on obtient un « Yes ».
# test on row # subset = X_test[X_test['TotalCharges']<100] # extract a line from X_test oneline = X_test.loc[[6125]] #convert to dataframe #oneline = oneline.to_frame() import pandas as pd isinstance(oneline,pd.DataFrame) # test if it's DataFrame print(oneline) oneline.head() prediction = loaded_rf.predict(oneline) print(prediction) [1] // Churn