Prédire la valeur du client avec AI Platform : Entraîner les modèles

Cet article constitue la deuxième partie d'une série en quatre parties qui explique comment prédire la valeur du client à l'aide d'AI Platform (AI Platform) sur Google Cloud.

Cette série est constituée des articles suivants :

Le code employé pour la mise en œuvre de ce système se trouve dans un dépôt GitHub. Cette série explique à quoi sert le code et comment il est utilisé.

Introduction

Cet article fait suite à la partie 1, dans laquelle vous avez découvert deux types de modèles différents permettant de prédire la CLV :

  • Des modèles probabilistes
  • Des modèles de réseau de neurones profond (DNN, Deep Neural Network), qui constituent un type de modèle de ML

Comme indiqué dans la partie 1, l'un des objectifs de cette série est de comparer ces modèles concernant la prédiction de la CLV. La présente partie de la série décrit comment vous pouvez préparer les données, et créer et entraîner les deux types de modèles afin de prédire la CLV. Elle fournit également des éléments de comparaison.

Installer le code

Si vous souhaitez suivre le processus décrit dans cet article, vous devez installer l'exemple de code depuis GitHub.

  1. Si gcloud CLI est installé, ouvrez une fenêtre de terminal sur votre ordinateur pour exécuter ces commandes. Si gcloud CLI n'est pas installé, ouvrez une instance de Cloud Shell.

    Accéder à Cloud Shell

  2. Clonez l'exemple de dépôt de code :

    git clone
    https://github.com/GoogleCloudPlatform/tensorflow-lifetime-value
    
  3. Suivez les instructions d'installation figurant dans la section concernant l'installation du fichier README pour configurer votre environnement.

Préparer les données

Cette section décrit comment vous pouvez obtenir les données et les nettoyer.

Obtenir et nettoyer l'ensemble de données source

Pour être en mesure de calculer la CLV, vous devez vous assurer que les données source contiennent au moins les éléments suivants :

  • Un numéro client, qui permet de différencier les clients individuels
  • Un montant d'achat par client, qui indique la somme dépensée par un client à un moment donné
  • La date de chaque achat

Cet article explique comment entraîner des modèles en utilisant l'historique des données de vente de Online Retail Data Set (ensemble de données concernant l'e-commerce) de l'UCI Machine Learning Repository (dépôt de machine learning de l'Université de Californie à Irvine).[1]

La première étape consiste à copier l'ensemble de données sous forme d'un fichier CSV dans Cloud Storage. À l'aide de l'un des outils de chargement de BigQuery, vous créez ensuite une table nommée data_source. (Il s'agit d'un nom arbitraire, mais qui est employé par le code du dépôt GitHub.) L'ensemble de données est disponible dans un bucket public associé à cette série. Il a déjà été converti au format CSV.

  1. Sur votre ordinateur ou dans Cloud Shell, exécutez les commandes décrites dans la section de configuration du fichier README du dépôt GitHub.

L'exemple d'ensemble de données contient les champs répertoriés dans le tableau suivant. Dans le cas de l'approche décrite dans cet article, vous n'utiliserez que les champs dont la colonne Utilisé est définie sur Oui. Certains champs ne sont pas utilisés directement, mais permettent de créer d'autres champs. Par exemple, UnitPrice et Quantity permettent de créer order_value.

Utilisé Champ Type Description
Non InvoiceNo STRING Nominal. Nombre entier à six chiffres attribué de manière unique à chaque transaction. Lorsque ce code commence par la lettre c, cela indique une annulation.
No StockCode STRING Code produit (article). Nominal, nombre entier à cinq chiffres attribué de manière unique à chaque produit distinct.
Non Description STRING Nom du produit (article). Nominal.
Oui Quantity INTEGER Quantité de chaque produit (article) par transaction. Numérique.
Oui InvoiceDate STRING Date et heure de facture au format mm/jj/aa hh:mm. Correspond au jour et à l'heure de génération de chaque transaction.
Oui UnitPrice FLOAT Prix unitaire. Numérique. Correspond au prix du produit par unité en livres sterling.
Oui CustomerID STRING Numéro client. Nominal. Correspond à un nombre entier à cinq chiffres attribué de manière unique à chaque client.
Non Country STRING Nom du pays. Nominal. Correspond au nom du pays où réside le client.

Nettoyer les données

Quel que soit le modèle employé, vous devez exécuter un ensemble d'étapes de préparation et de nettoyage communes à tous les modèles. Les opérations suivantes sont requises pour obtenir un ensemble de champs et d'enregistrements exploitables :

  1. Regroupez les commandes par jour plutôt que d'utiliser InvoiceNo, car l'unité de temps minimale utilisée par les modèles probabilistes dans cette solution correspond à un jour.
  2. Ne conservez que les champs utiles pour les modèles probabilistes.
  3. Ne conservez que les enregistrements contenant des valeurs monétaires et des volumes de commande positifs, tels que les achats.
  4. Ne conservez que les enregistrements comportant des volumes de commande négatifs, tels que les retours.
  5. Ne conservez que les enregistrements comportant un numéro client.
  6. Ne conservez que les clients ayant effectué un achat au cours des 90 derniers jours.
  7. Ne conservez que les clients ayant effectué au moins deux achats au cours de la période employée pour créer des caractéristiques.

Vous pouvez effectuer toutes ces opérations à l'aide de la requête BigQuery ci-après. (Comme pour les commandes précédentes, vous exécutez ce code là où vous avez cloné le dépôt GitHub.) Étant donné que les données sont anciennes, la date du 12 décembre 2011 est considérée comme la date du jour aux fins du présent article.

WHERE
  -- Bought in the past 3 months
  DATE_DIFF(DATE('{{ dag_run.conf['predict_end'] }}'), latest_order, DAY) <= 90
  -- Make sure returns are consistent.
  AND (
    (order_qty_articles > 0 and order_Value > 0) OR
    (order_qty_articles < 0 and order_Value < 0)
  )

Cette requête effectue deux tâches. Tout d'abord, si l'ensemble de données de travail est volumineux, la requête le réduit (l'ensemble de données de travail pour cette solution est assez petit, mais cette requête peut réduire un ensemble de données extrêmement volumineux de deux ordres de grandeur en quelques secondes).

Deuxièmement, la requête crée un ensemble de données de base semblable à ce qui suit :

customer_id order_date order_value order_qty_articles
16915 2011-08-04 173.7 6
15349 2011-07-04 107.7 77
14794 2011-03-30 -33.9 -2

L'ensemble de données nettoyé contient également le champ order_qty_articles. Ce champ n'est inclus qu'à des fins d'utilisation par le DNN décrit dans la section suivante.

Définir les intervalles d'entraînement et cibles

Pour préparer l'entraînement des modèles, vous devez choisir une date seuil. Cette date sépare les commandes en deux partitions :

  • Les commandes antérieures à la date seuil servent à l'entraînement du modèle.
  • Les commandes ultérieures à la date seuil servent au calcul de la valeur cible.

Historique des ventes pour quatre clients affichant une date seuil

La bibliothèque Lifetimes comprend des méthodes de prétraitement des données. Toutefois, les ensembles de données que vous utilisez pour la CLV peuvent être assez volumineux, ce qui rend difficile le prétraitement des données sur une seule machine. Dans le cadre de l'approche décrite dans cet article, les commandes sont scindées en deux ensembles à l'aide de requêtes exécutées directement dans BigQuery. Les modèles probabilistes et de ML emploient les mêmes requêtes, garantissant ainsi que les deux types de modèles fonctionnent sur les mêmes données.

La date seuil optimale des modèles de ML peut être différente de celle des modèles probabilistes. Vous pouvez mettre à jour cette valeur de date directement dans l'instruction SQL. Considérez la date seuil optimale comme un hyperparamètre. Pour trouver la valeur la plus appropriée, explorez les données et exécutez des entraînements test.

La date seuil est utilisée dans la clause WHERE de la requête SQL qui sélectionne les données d'entraînement dans la table de données nettoyée, comme illustré dans l'exemple suivant :

order_date <= DATE('{{ dag_run.conf['threshold_date'] }}')

Agréger des données

Une fois que vous avez scindé les données en intervalles d'entraînement et cibles, vous les agrégez afin de créer des caractéristiques et des cibles réelles pour chaque client. Pour les modèles probabilistes, l'agrégation est limitée aux champs de récence, de fréquence et de montant (RFM). Concernant les modèles de DNN, ils emploient également des caractéristiques RFM, mais ils peuvent utiliser des caractéristiques supplémentaires pour améliorer les prédictions.

La requête suivante montre comment créer des caractéristiques pour les modèles de DNN et les modèles probabilistes en même temps :

tf.monetary_dnn,
tf.monetary_btyd,
tf.cnt_orders AS frequency_dnn,
tf.cnt_orders - 1 AS frequency_btyd,
tf.recency,
tf.T,
ROUND(tf.recency/cnt_orders, 2) AS time_between,
ROUND(tf.avg_basket_value, 2) AS avg_basket_value,
ROUND(tf.avg_basket_size, 2) AS avg_basket_size,
tf.cnt_returns,
(CASE
    WHEN tf.cnt_returns > 0 THEN 1
    ELSE 0 END) AS has_returned,

-- Used by BTYD mainly, potentially DNN if clipped improve results
(CASE
    WHEN tf.cnt_orders - 1 > 600 THEN 600
    ELSE tf.cnt_orders - 1 END) AS frequency_btyd_clipped,
(CASE
    WHEN tf.monetary_btyd > 100000 THEN 100000
    ELSE ROUND(tf.monetary_btyd, 2) END) AS monetary_btyd_clipped,
(CASE
    WHEN tt.target_monetary > 100000 THEN 100000
    ELSE ROUND(tt.target_monetary, 2) END) AS target_monetary_clipped,

-- Target calculated for overall period
ROUND(tt.target_monetary, 2) as target_monetary

Le tableau suivant répertorie les caractéristiques créées par la requête.

Nom de la fonctionnalité Description Probabiliste DNN
monetary_dnn Somme des valeurs monétaires de toutes les commandes par client au cours de la période de création des caractéristiques. X
monetary_btyd Moyenne des valeurs monétaires de toutes les commandes pour chaque client au cours de la période de création des caractéristiques. Les modèles probabilistes supposent que la valeur de la première commande est 0. Cela est imposé par la requête. X
recency Délai entre la première et la dernière des commandes passées par un client au cours de la période de création des caractéristiques. X
frequency_dnn Nombre de commandes passées par un client au cours de la période de création des caractéristiques. X
frequency_btyd Nombre de commandes passées par un client au cours de la période de création des caractéristiques, moins la première commande. X
T Délai entre la première commande passée par un client et la fin de la période de création des caractéristiques. X X
time_between Délai moyen entre les commandes d'un client au cours de la période de création des caractéristiques. X
avg_basket_value Valeur monétaire moyenne du panier du client au cours de la période de création des caractéristiques. X
avg_basket_size Nombre d'articles figurant en moyenne dans le panier du client au cours de la période de création des caractéristiques. X
cnt_returns Nombre de commandes que le client a retournées au cours de la période de création des caractéristiques. X
has_returned Indique si le client a retourné au moins une commande au cours de la période de création des caractéristiques. X
frequency_btyd_clipped Caractéristique identique à frequency_btyd, mais dont les valeurs aberrantes sont tronquées. X
monetary_btyd_clipped Caractéristique identique à monetary_btyd, mais dont les valeurs aberrantes sont tronquées. X
target_monetary_clipped Caractéristique identique à target_monetary, mais dont les valeurs aberrantes sont tronquées. X
target_monetary Montant total dépensé par un client, y compris pendant les périodes d'entraînement et cibles. X

La sélection de ces colonnes se déroule dans le code. Pour les modèles probabilistes, la sélection est effectuée à l'aide d'un DataFrame Pandas :

df_ft = pd.read_csv(ft_file)

# Extracts relevant dataframes for RFM:
# - summary has aggregated values before the threshold date
# - actual_df has values of the overall period.
summary = df_ft[['customer_id', 'frequency_btyd', 'recency', 'T',
                 'monetary_btyd']]

Pour les modèles DNN, les fonctionnalités TensorFlow sont définies dans le fichier context.py. Pour ces modèles, les éléments suivants sont ignorés en tant que caractéristiques :

  • customer_id. Il s'agit d'une valeur unique qui ne présente pas d'utilité en tant que fonctionnalité.
  • target_monetary. Il s'agit de la cible que le modèle doit prédire. Elle n'est donc pas utilisée comme entrée.

Créer les ensembles d'entraînement, d'évaluation et de test du DNN

Cette section ne s'applique qu'aux modèles de DNN. Pour entraîner un modèle de ML, vous devez employer trois ensembles de données qui ne se chevauchent pas :

  • L'ensemble de données d'entraînement (70–80 %) permet l'apprentissage des pondérations afin de réduire une fonction de perte. L'entraînement continue jusqu'à ce que la fonction de perte cesse de décliner.

  • L'ensemble de données d'évaluation (10–15 %) est employé pendant la phase d'entraînement pour éviter le surapprentissage, c'est-à-dire une situation où un modèle donne de bons résultats avec les données d'entraînement mais est difficile à généraliser.

  • L'ensemble de données de test (10–15 %) ne doit servir qu'une seule fois, une fois l'entraînement et l'évaluation terminés, pour effectuer une mesure finale des performances du modèle. Il s'agit d'un ensemble de données que le modèle n'a jamais vu pendant le processus d'entraînement. Il fournit donc une mesure statistiquement valide de la précision du modèle.

La requête suivante crée un ensemble d'entraînement comportant environ 70 % des données. Elle sépare les données à l'aide de la technique suivante :

  • Un hachage du numéro client est calculé, ce qui produit un entier.
  • Une opération modulo permet de sélectionner les valeurs de hachage inférieures à un certain seuil.
SELECT
  *
FROM
  `{{ dag_run.conf['project'] }}.{{ dag_run.conf['dataset'] }}.features_n_target`
WHERE
  -- TRAIN
  MOD(ABS(FARM_FINGERPRINT(CAST(customer_id AS STRING))), 100000) <= 70000

Le même concept est appliqué pour l'ensemble d'évaluation et l'ensemble de test, où les données dépassant le seuil sont conservées.

Formation

Comme vous l'avez vu dans la section précédente, vous pouvez utiliser différents modèles pour prédire la CLV. Le code utilisé dans cet article a été conçu pour vous permettre de choisir le modèle à utiliser. Vous choisissez le modèle à l'aide du paramètre model_type que vous transmettez au script shell d'entraînement suivant. Le code s'occupe du reste.

estimator = get_estimator(estimator_name=args.model_type,
                          config=config,
                          params=params,
                          model_dir=model_dir)

L'objectif premier de l'entraînement est de permettre aux deux modèles de dépasser un benchmark naïf, défini ci-dessous. Si les deux types de modèles peuvent le dépasser (ce qui doit être le cas), vous pouvez alors comparer les performances de chaque type l'un par rapport à l'autre.

Procéder au benchmarking des modèles

Pour les besoins de cette série d'articles, un benchmark naïf est défini à l'aide des paramètres suivants :

  • Valeur moyenne du panier. Ce paramètre est calculé sur toutes les commandes passées avant la date seuil.
  • Nombre de commandes. Ce paramètre est calculé pour l'intervalle d'entraînement sur toutes les commandes passées avant la date seuil.
  • Multiplicateur de comptage. Ce paramètre est calculé sur la base du ratio entre le nombre de jours avant la date seuil et le nombre de jours entre la date seuil et la date actuelle.

Le benchmark suppose naïvement que la fréquence d'achat d'un client au cours de l'intervalle d'entraînement reste constante tout au long de l'intervalle cible. Ainsi, si un client effectue six achats sur une période de 40 jours, l'hypothèse est qu'il en effectuera neuf sur une période de 60 jours (60/40 * 6 = 9). La multiplication de la valeur moyenne du panier, du nombre de commandes et du multiplicateur de comptage de chaque client permet d'obtenir pour celui-ci une valeur cible prédite naïve.

L'erreur de benchmark est la racine carrée de l'erreur quadratique moyenne (RMSE, Root Mean Square Error), soit la moyenne pour tous les clients de la différence absolue entre la valeur cible prédite et la valeur cible réelle. La RMSE est calculée à l'aide de la requête suivante dans BigQuery :

SELECT
  ROUND(SQRT( SUM(POW(predicted_monetary - target_monetary, 2)) / COUNT(1) ), 2) as rmse
FROM (
  SELECT
    tf.customer_id,
    avg_basket_value * ( cnt_orders * (1 + target_days/feature_days) ) AS predicted_monetary,
    ROUND(tt.target_monetary, 2) AS target_monetary

Le benchmark renvoie une RMSE d'environ 3760, comme indiqué dans les résultats d'exécution du benchmark ci-après. Les modèles devraient dépasser cette valeur.

Résultats des benchmarks

Modèles probabilistes

Comme indiqué dans la partie 1 de cette série, cette série d'articles utilise une bibliothèque Python appelée "Lifetimes" qui est compatible avec divers modèles, dont les modèles Pareto/Loi binomiale négative (NBD, Negative Binomial Distribution) et bêta-géométrique (BG/NBD). L'exemple de code suivant montre comment employer la bibliothèque Lifetimes pour effectuer des prédictions de la valeur vie client avec des modèles probabilistes.

paretof = ParetoNBDFitter(penalizer_coef=PENALIZER_COEF)
paretof.fit(summary['frequency'], summary['recency'], summary['T'])
return paretof

Pour générer des résultats de CLV à l'aide du modèle probabilité dans votre environnement local, vous pouvez exécuter le script mltrain.sh suivant. Vous fournissez des paramètres pour les dates de début et de fin de l'intervalle d'entraînement et pour la fin de la période de prédiction.

./mltrain.sh local data --model_type paretonbd_model --threshold_date [YOUR_THRESHOLD_DATE] --predict_end [YOUR_END_DATE]

Modèles de DNN

L'exemple de code inclut des mises en œuvre dans TensorFlow de DNN à l'aide de la classe Estimator DNNRegressor prédéfinie, ainsi qu'un modèle Estimator personnalisé. Le DNNRegressor et l'estimateur personnalisé utilisent le même nombre de couches et le même nombre de neurones dans chaque couche. Ces valeurs sont des hyperparamètres qui doivent être ajustés. Dans le fichier task.py suivant, vous trouverez une liste de certains hyperparamètres définis sur des valeurs testées manuellement et ayant donné de bons résultats.

TRAIN_SIZE = 100000
NUM_EPOCHS = 70
BATCH_SIZE = 5
NUM_EVAL = 20

LEARNING_DECAY_RATE = 0.7
HIDDEN_UNITS = '128 64 32 16'
LEARNING_RATE = 0.00135
L1_REGULARIZATION = 0.0216647
L2_REGULARIZATION = 0.0673949
DROPOUT = 0.899732
SHUFFLE_BUFFER_SIZE = 10000

Si vous utilisez AI Platform, vous pouvez employer la fonctionnalité de réglage des hyperparamètres, qui permet de tester une plage de paramètres que vous définissez dans un fichier yaml. AI Platform utilise Optimisation bayésienne pour rechercher dans l'espace des hyperparamètres.

Résultats de la comparaison des modèles

Le tableau suivant montre les valeurs RMSE de chaque modèle, tel qu'il a été entraîné sur l'exemple d'ensemble de données. Tous les modèles sont entraînés sur des données RFM. Les valeurs RMSE varient légèrement entre les exécutions, en raison de l'initialisation aléatoire des paramètres. Le modèle de DNN utilise des caractéristiques supplémentaires telles que la valeur moyenne du panier et le nombre de retours.

Modèle RMSE
DNN 947.9
BG/NBD 1557
Pareto/NBD 1558

Les résultats montrent que pour cet ensemble de données, le modèle de DNN surpasse les modèles probabilistes lors de la prédiction de la valeur monétaire. Cependant, la taille relativement petite de l'ensemble de données de l'UCI limite la validité statistique de ces résultats. Vous devriez appliquer chacune des techniques à votre ensemble de données pour voir laquelle vous permet d'obtenir les meilleurs résultats. Tous les modèles ont été entraînés en utilisant les mêmes données d'origine (y compris le numéro client, la date et la valeur de la commande) sur les valeurs RFM extraites de ces données. Les données d'entraînement du modèle de DNN incluent certaines caractéristiques supplémentaires telles que la taille moyenne du panier et le nombre de retours.

Le modèle de DNN n'affiche que la valeur monétaire globale du client. Si vous souhaitez prédire la fréquence ou la perte d'utilisateurs, vous devez effectuer quelques tâches supplémentaires :

  • Préparez les données différemment pour changer la cible et éventuellement la date seuil.
  • Ré-entraînez un modèle de régression pour prédire la cible qui vous intéresse.
  • Réglez les hyperparamètres.

L'objectif ici était d'effectuer une comparaison avec les mêmes caractéristiques d'entrée pour les deux types de modèles. L'emploi des DNN présente un avantage : vous pouvez améliorer vos résultats en ajoutant des caractéristiques en plus de celles utilisées dans cet exemple. Avec les DNN, vous pouvez tirer parti des données de sources telles que des événements de flux de clics, des profils utilisateur ou des caractéristiques de produit.

Remerciements

Dua, D. and Karra Taniskidou, E. (2017). UCI Machine Learning Repository : http://archive.ics.uci.edu/ml. Irvine, Californie : Université de Californie, School of Information and Computer Science.

Étape suivante