Como prever o valor de vida útil do cliente com o AI Platform: treinamento dos modelos

Este artigo é a segunda parte de uma série de quatro partes que discute como prever o valor de vida útil do cliente (CLV, na sigla em inglês) usando o AI Platform no Google Cloud.

Os artigos desta série incluem:

O código para implementar o sistema está em um repositório do GitHub. Nesta série, discutimos para que serve o código e como ele é usado.

Introdução

Este artigo é a continuação da parte 1, que ensina sobre dois modelos diferentes para prever o valor de vida útil do cliente (CLV):

  • modelos probabilísticos
  • Modelos de rede neural profunda (DNN, na sigla em inglês), um tipo de modelo de aprendizado de máquina

Conforme observado na parte 1, um dos objetivos desta série é comparar esses modelos de previsão do CLV. Neste artigo, veja como preparar os dados, além de criar e treinar ambos os tipos de modelo. Também fornecemos algumas informações para fins de comparação.

Como instalar o código

Se você quiser seguir o processo descrito neste artigo, precisará instalar a amostra de código no GitHub.

  1. Se a CLI gcloud estiver instalada, abra uma janela de terminal no computador para executar estes comandos. Se a CLI gcloud não estiver instalada, abra uma instância do Cloud Shel.

    ACESSAR o Cloud Shell

  2. Clone o repositório do código de amostra:

    git clone
    https://github.com/GoogleCloudPlatform/tensorflow-lifetime-value
    
  3. Siga as instruções de instalação na seção Install do arquivo README para configurar o ambiente.

Preparação de dados

Veja nesta seção como conseguir e apurar os dados.

Como conseguir e apurar o conjunto de dados de origem

Antes de calcular o CLV, verifique se os dados de origem contêm pelo menos:

  • o ID de cliente usado para diferenciar clientes individuais;
  • o valor de compra por cliente, que mostra quanto cada cliente gastou em um horário específico;
  • Data de cada compra

Neste artigo, discutimos como treinar modelos usando dados históricos de vendas do Conjunto de dados de varejo on-line disponível publicamente no repositório de aprendizado de máquina da UCI.[1]

A primeira etapa é copiar o conjunto de dados como um arquivo CSV para o Cloud Storage. Use uma das ferramentas de carregamento do BigQuery para criar uma tabela chamada data_source. Esse nome é facultativo, mas é o mesmo utilizado pelo código no repositório do GitHub. O conjunto de dados está disponível em um bucket público associado a essa série e já foi convertido para o formato CSV.

  1. No seu computador ou no Cloud Shell, execute os comandos documentados na seção "Configuração" do arquivo README no repositório do GitHub.

O conjunto de dados de exemplo do Kaggle contém os campos listados na tabela a seguir. Para a abordagem que descrevemos neste artigo, você usa apenas os campos em que a coluna Usado está definida como Sim. Alguns campos não são usados diretamente, mas ajudam a criar novos campos. Por exemplo, UnitPrice e Quantity criam order_value.

Usado Campo Tipo Descrição
Não InvoiceNo STRING Nominal. Um número inteiro de seis dígitos atribuído exclusivamente a cada transação. Se esse código começar com a letra c, ele indica um cancelamento.
Não StockCode STRING Código do produto (item). Nominal, um número integral de 5 dígitos atribuído exclusivamente a cada produto distinto.
Não Description STRING Nome do produto (item). Nominal.
Sim Quantity INTEGER As quantidades de cada produto (item) por transação. Numérico.
Sim InvoiceDate STRING Data e hora da fatura no formato mm/dd/aa hh:mm. O dia e a hora em que cada transação foi gerada.
Sim UnitPrice FLOAT Preço unitário. Numérico. O preço do produto por unidade em libras esterlinas.
Sim CustomerID STRING Número do cliente. Nominal. Um número integral de 5 dígitos atribuído exclusivamente a cada cliente.
Não Country STRING Nome do país. Nominal. O nome do país em que cada cliente reside.

Como apurar os dados

Independentemente do modelo usado, é necessário executar as mesmas etapas de preparação e apuração em todos os modelos. As operações a seguir são obrigatórias para ter um conjunto de campos e registros viáveis:

  1. Agrupe os pedidos por dia em vez de usar InvoiceNo, porque a unidade tempo mínimo usada pelos modelos probabilísticos nesta solução é um dia.
  2. Mantenha apenas os campos que são úteis para modelos probabilísticos.
  3. Mantenha apenas os registros com quantidades de pedidos e valores monetários em número positivo, como compras.
  4. Mantenha apenas registros com quantidades de pedido negativas, como devoluções.
  5. Mantenha apenas os registros com um ID de cliente.
  6. Mantenha apenas os clientes que compraram algo nos últimos 90 dias.
  7. Mantenha apenas os clientes que compraram pelo menos duas vezes no período usado para criar atributos.

É possível realizar todas essas operações usando a consulta do BigQuery a seguir. Como nos comandos anteriores, execute o código onde você clonou o repositório do GitHub. Como os dados são antigos, a data de 12 de dezembro de 2011 é considerada a data de hoje para os fins deste artigo.

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

Essa consulta realiza duas tarefas. Primeiro, se o conjunto de dados em funcionamento for grande, a consulta o reduzirá. O conjunto de dados em funcionamento desta solução é muito pequeno, mas essa consulta pode reduzir um conjunto de dados extremamente grande em duas ordens de magnitude em alguns segundos.

Depois, a consulta cria um conjunto de dados de base com que você vai trabalhar, semelhante a este:

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

O conjunto de dados apurado também contém o campo order_qty_articles. Esse campo é incluído apenas para ser usado pela rede neural profunda (DNN), descrita na próxima seção.

Como definir os intervalos desejados e de treinamento

Para se preparar para treinar os modelos, escolha uma data limite. Essa data dividirá os pedidos em duas partes:

  • Os pedidos antes da data limite serão usados para treinar o modelo.
  • Os pedidos após a data limite serão usados para calcular o valor desejado.

Histórico de vendas de quatro clientes mostrando a data limite

A biblioteca Lifetimes inclui métodos para o pré-processamento dos dados. No entanto, os conjuntos de dados utilizados para o CLV podem ser muito grandes. Isso torna praticamente impossível realizar o pré-processamento dos dados em uma única máquina. A abordagem que descrevemos neste artigo usa consultas executadas diretamente no BigQuery para dividir os pedidos em dois conjuntos. Os modelos probabilísticos e de ML usam as mesmas consultas, garantindo que ambos os tipos operem com os mesmos dados.

A data limite ideal pode ser diferente em modelos probabilísticos e de ML. É possível atualizar o valor da data diretamente na instrução SQL. Pense na data limite ideal como um hiperparâmetro. Para encontrar o valor mais adequado, explore os dados e execute alguns treinamentos de teste.

A data limite é usada na cláusula WHERE da consulta SQL que seleciona os dados de treinamento na tabela de dados apurados, conforme mostrado no exemplo a seguir:

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

Como agregar os dados

Depois de dividir os dados em intervalos desejados e de treinamento, agregue-os para criar os verdadeiros atributos e valores desejados para cada cliente. No caso dos modelos probabilísticos, a agregação é limitada aos campos de tempo transcorrido desde a última visita, frequência e valor monetário (RFM, na sigla em inglês). Já no caso dos modelos de DNN, os atributos RFM também são utilizados, mas é possível usar atributos adicionais para fazer previsões melhores.

A consulta a seguir mostra como criar atributos para modelos probabilísticos e de DNN ao mesmo tempo:

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

A tabela a seguir lista os atributos criados pela consulta.

Nome do recurso Descrição Probabilístico DNN
monetary_dnn A soma dos valores monetários de todos os pedidos por cliente durante o período dos atributos. x
monetary_btyd A média dos valores monetários de todos os pedidos de cada cliente durante o período dos atributos. Os modelos probabilísticos presumem que o valor do primeiro pedido é zero. Isso é imposto pela consulta. x
recency O tempo decorrido entre o primeiro e o último pedido feito por um cliente durante o período dos atributos. x
frequency_dnn O número de pedidos feitos por um cliente durante o período dos atributos. x
frequency_btyd O número de pedidos feitos por um cliente durante o período dos tributos, exceto o primeiro. x
T O tempo decorrido entre o primeiro pedido feito por um cliente e o fim do período dos atributos. x x
time_between O tempo médio entre pedidos de um cliente durante o período dos atributos. x
avg_basket_value O valor monetário médio da cesta de compras do cliente durante o período dos atributos. x
avg_basket_size O número de itens em média que o cliente tem na cesta de compras durante o período dos atributos. x
cnt_returns O número de pedidos que o cliente devolveu durante o período dos atributos. x
has_returned Se o cliente devolveu pelo menos um pedido durante o período dos atributos. x
frequency_btyd_clipped O mesmo que frequency_btyd, mas recortado por outliers de limite. x
monetary_btyd_clipped O mesmo que monetary_btyd, mas recortado por outliers de limite. x
target_monetary_clipped O mesmo que target_monetary, mas recortado por outliers de limite. x
target_monetary O valor total gasto por um cliente, incluindo o período desejado e o de treinamento. x

Essas colunas são selecionadas no código. No caso dos modelos probabilísticos, use um DataFrame do Pandas para selecionar as colunas:

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']]

Para os modelos DNN, os atributos do TensorFlow são definidos no arquivo context.py. Nesses modelos, os itens a seguir são ignorados como atributos:

  • customer_id. É um valor exclusivo que não é útil como atributo.
  • target_monetary. É o valor de predição desejado para o modelo e, portanto, não é usado como entrada.

Como criar conjuntos de treinamento, avaliação e teste para DNN

Esta seção se aplica apenas aos modelos de DNN. Para treinar um modelo ML, é necessário usar três conjuntos de dados não coincidentes:

  • O conjunto de dados de treinamento (70% a 80%) é usado no aprendizado dos pesos para reduzir uma função de perda. O treinamento continua até que a função de perda não seja mais reduzida.

  • O conjunto de dados de avaliação (10% a 15%) é usado durante a fase de treinamento para evitar o overfitting, que é quando um modelo tem um bom desempenho com os dados de treinamento, mas não generaliza bem.

  • O conjunto de dados de teste (10 a 15%) será usado apenas uma vez, após a conclusão do treinamento e da avaliação, para medir o desempenho do modelo pela última vez. Trata-se de um conjunto de dados não visto pelo modelo durante o processo de treinamento e, portanto, fornece uma medida estatisticamente válida da precisão do modelo.

A consulta a seguir cria um conjunto de treinamento com cerca de 70% dos dados. Ela separa os dados usando a técnica a seguir:

  • Um hash de ID do cliente é calculado, produzindo um número inteiro.
  • Uma operação módulo é usada para selecionar os valores de hash que estão abaixo de um determinado limite.
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

O mesmo conceito é usado para os conjuntos de avaliação e de teste, mantendo os dados acima do limite.

Treinamento

Como você viu na seção anterior, é possível usar modelos diferentes para tentar prever o CLV. O código usado neste artigo foi criado para permitir que você decida qual modelo usar. Você escolhe o modelo usando o parâmetro model_type que passa para o script de shell de treinamento seguinte. O código cuida do resto.

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

O primeiro objetivo do treinamento é que ambos os modelos consigam superar um comparativo ingênuo, que definimos abaixo. Se ambos os tipos de modelos puderem superar esse comparativo (como deveriam), será possível comparar o desempenho de cada um.

Como fazer a comparação dos modelos

Para fins desta série, o comparativo ingênuo é definido com os seguintes parâmetros:

  • Valor médio da cesta de compras. Esse valor é calculado em todos os pedidos feitos antes da data limite.
  • Contagem de pedidos. Esse valor é calculado para o intervalo de treinamento em todos os pedidos feitos antes da data limite.
  • Multiplicador da contagem. Esse valor é calculado com base na razão entre o número de dias antes da data limite e o número de dias entre a data limite e a data atual.

De maneira simplista, o comparativo presume que a taxa de compras estabelecida por um cliente durante o intervalo de treinamento permanece constante durante o intervalo desejado. Portanto, se um cliente comprou seis vezes em 40 dias, a pressuposição é que ele comprará nove vezes em 60 dias (60/40 * 6 = 9). Multiplicar o multiplicador da contagem, a contagem de pedidos e o valor médio da cesta de compras de cada cliente fornece um valor desejado previsto ingênuo para esse cliente.

O erro do comparativo é a raiz do erro médio quadrado (REMQ): a média entre todos os clientes da diferença absoluta entre o valor desejado previsto e o valor desejado real. O REMQ é calculado por meio da consulta a seguir no 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

O comparativo retorna um valor de REMQ de aproximadamente 3.760, como mostrado abaixo nos resultados da execução do benchmark. Os modelos devem superar esse valor.

Resultados dos comparativos

Modelos probabilísticos

Como mencionado na parte 1, esta série usa uma biblioteca Python denominada Lifetimes que é compatível com vários modelos, incluindo os modelos de distribuição de Pareto/distribuição binominal negativa (NBD, na sigla em inglês) e de distribuição binomial negativa beta-geométrica (BG/NBD, na sigla em inglês). A amostra de código a seguir mostra como usar a biblioteca Lifetimes para executar previsões sobre o valor de vida útil com modelos probabilísticos.

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

Para gerar resultados CLV usando o modelo probabilístico no ambiente local, execute o script mltrain.sh a seguir. Forneça os parâmetros para as datas de início e término da divisão de treinamento e para o término do período de previsão.

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

Modelos de DNN

O código de amostra inclui implementações no TensorFlow de DNN usando a classe Estimator DNNRegressor predefinida e também um modelo de Estimator personalizado. O DNNRegressor e o Estimador personalizado usam o mesmo número de camadas e o número de neurônios em cada camada. Esses valores são hiperparâmetros que precisam ser ajustados. No arquivo task.py seguinte, você encontra uma lista de alguns dos hiperparâmetros que foram definidos como valores que foram testados manualmente e deram bons resultados.

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

Se você estiver usando o AI Platform, será possível usar o recurso de ajuste do hiperparâmetro, que será testado em vários parâmetros definidos em um arquivo yaml. O AI Platform usa a otimização bayesiana para pesquisar sobre o espaço dos hiperparâmetros.

Resultados da comparação entre modelos

A tabela a seguir mostra os valores de REMQ de cada modelo treinado com a amostra de conjunto de dados. Todos os modelos são treinados com dados de RFM. Os valores de REMQ variam ligeiramente entre as execuções, devido à inicialização aleatória de parâmetros. O modelo de DNN usa atributos adicionais, como valor médio da cesta de compras e contagem de devoluções.

Modelo REMQ
DNN 947.9
BG/NBD 1557
Pareto/NBD 1558

Os resultados mostram que, nesse conjunto de dados, o modelo de DNN supera os modelos probabilísticos ao predizer o valor monetário. No entanto, o tamanho relativamente pequeno do conjunto de dados da UCI limita a validade estatística desses resultados. É preciso tentar cada uma das técnicas em seu conjunto de dados para ver qual oferece os melhores resultados. Todos os modelos foram treinados com os mesmos dados originais (incluindo ID do cliente, data do pedido e valor do pedido), usando valores de RFM extraídos desses dados. Os dados de treinamento do modelo de DNN incluíram alguns outros atributos, como tamanho médio da cesta de compras e contagem de devoluções.

O modelo de DNN produz como resultado apenas o valor monetário global do cliente. Se você quiser prever a frequência ou o desligamento de usuários, precisará realizar algumas outras tarefas:

  • Prepare os dados de maneira diferente para alterar o valor desejado e, possivelmente, a data limite.
  • Treine novamente um modelo regressor para predizer o valor desejado que você quer.
  • Ajuste os hiperparâmetros.

A nossa intenção era realizar uma comparação entre os dois tipos de modelos usando os mesmos atributos de entrada. Uma vantagem de usar modelos de DNN é que você pode melhorar os resultados adicionando mais atributos do que os utilizados no nosso exemplo. Com os modelos de DNN, é possível aproveitar os dados de fontes como eventos de sequência de cliques, perfis de usuário ou atributos de produtos.

Agradecimentos

Dua, D. e Karra Taniskidou, E. (2017). Repositório de aprendizado de máquina da UCI http://archive.ics.uci.edu/ml (em inglês). Irvine, CA: University of California, School of Information and Computer Science.

A seguir