预测下一次购买

在本教程中,构建一个机器学习应用程序,预测客户是否会在下一个购物周期内购买产品。此应用程序分为三个重要步骤

  • 预测工程

  • 特征工程

  • 机器学习

第一步,使用 Compose 从数据中生成新标签。第二步,使用 Featuretools 为标签生成特征。第三步,使用 EvalML 寻找最佳机器学习管道。完成这些步骤后,您应该理解如何构建用于预测消费者支出等现实问题的机器学习应用程序。

注意:为了运行此示例,您应该安装 Featuretools 1.4.0 或更高版本以及 EvalML 0.41.0 或更高版本。

[1]:
from demo.next_purchase import load_sample
from matplotlib.pyplot import subplots
import composeml as cp
import featuretools as ft
import evalml

使用由 Instacart 提供的此在线杂货订单历史数据。

[2]:
df = load_sample()
df.head()
[2]:
order_id product_id add_to_cart_order reordered product_name aisle_id department_id department user_id order_time
id
24 623 33120 1 1 有机蛋白 86 16 乳制品和蛋 37804 2015-01-04 12:00:00
25 623 40706 3 1 有机葡萄番茄 123 4 农产品 37804 2015-01-04 12:00:00
26 623 38777 5 1 有机无籽绿葡萄 123 4 农产品 37804 2015-01-04 12:00:00
27 623 34126 9 1 有机意大利欧芹捆 16 4 农产品 37804 2015-01-04 12:00:00
28 623 19678 4 1 有机褐皮土豆 83 4 农产品 37804 2015-01-04 12:00:00

预测工程

客户会在下一个购物周期内购买产品吗?

在此预测问题中,有两个参数

  • 客户可以购买的产品。

  • 购物周期的长度。

您可以更改这些参数以创建不同的预测问题。例如,客户会在接下来的 3 天内购买香蕉,还是在接下来的三周内购买牛油果?只需微调参数即可完成这些变化。这有助于您探索对于做出更好决策至关重要的不同场景。

定义标注函数

首先定义一个标注函数,用于检查客户是否购买了指定产品。将产品作为函数的参数。我们的标注函数由标签生成器用于提取训练样本。

[3]:
def bought_product(ds, product_name):
    return ds.product_name.str.contains(product_name).any()

表示预测问题

通过创建具有以下参数的标签生成器来表示预测问题

  • target_dataframe_index 作为客户 ID 的列,因为您希望处理每个客户的订单。

  • labeling_function 作为您之前定义的函数。

  • time_index 作为订单时间的列。购物周期基于此时间索引。

  • window_size 作为购物周期的长度。您可以轻松更改此参数以创建预测问题的变体。

[4]:
lm = cp.LabelMaker(
    target_dataframe_index='user_id',
    time_index='order_time',
    labeling_function=bought_product,
    window_size='3d',
)

寻找训练样本

使用以下参数运行搜索以获取训练样本

  • 按订单时间排序的杂货订单,因为搜索期望订单按时间顺序排序。否则,将引发错误。

  • num_examples_per_instance 用于查找每个客户的训练样本数量。在此示例中,搜索返回所有现有样本。

  • product_name 作为要检查购买情况的产品。此参数直接传递给我们的标注函数。

  • minimum_data 作为用于为第一个训练样本创建特征的数据量。

[5]:
lt = lm.search(
    df.sort_values('order_time'),
    num_examples_per_instance=-1,
    product_name='Banana',
    minimum_data='3d',
    verbose=False,
)

lt.head()
[5]:
user_id 时间 购买了产品
0 6851 2015-02-05 22:00:00 True
1 8167 2015-01-14 11:00:00 False
2 8167 2015-01-20 11:00:00 False
3 19114 2015-01-13 13:00:00 True
4 31606 2015-01-06 09:00:00 False

搜索的输出是一个带有三列的标签时间表

  • 与订单关联的客户 ID。每个客户都可以生成许多训练样本。

  • 购物周期的开始时间。这也是构建特征的截止时间。只有之前存在的数据才有效用于预测。

  • 产品是否在购物周期窗口期间购买。这由我们的标注函数计算。

作为有用的参考,您可以打印出用于生成这些标签的搜索设置。描述还向我们展示了标签分布,我们可以检查是否存在不平衡标签。

[6]:
lt.describe()
Label Distribution
------------------
False      7
True       6
Total:    13


Settings
--------
gap                                    None
maximum_data                           None
minimum_data                             3d
num_examples_per_instance                -1
target_column                bought_product
target_dataframe_index              user_id
target_type                        discrete
window_size                              3d


Transforms
----------
No transforms applied

您可以通过绘制标签随时间的分布和累计计数来更好地查看标签。

[7]:
%matplotlib inline
fig, ax = subplots(nrows=2, ncols=1, figsize=(6, 8))
lt.plot.distribution(ax=ax[0])
lt.plot.count_by_time(ax=ax[1])
fig.tight_layout(pad=2)
../_images/examples_predict_next_purchase_13_0.png

特征工程

在上一步中,您生成了标签。下一步是生成特征。

表示数据

首先使用 EntitySet 表示数据。这样,您可以根据数据集的关系结构生成特征。您目前有一个包含订单的单一表,其中一个客户可以有多个订单。这种一对多关系可以通过规范化客户数据框来表示。其他一对多关系(如 aisle-to-products)也可以采用同样的方式。因为您希望基于客户进行预测,所以应该使用此客户数据框作为生成特征的目标。

[8]:
es = ft.EntitySet('instacart')

es.add_dataframe(
    dataframe=df.reset_index(),
    dataframe_name='order_products',
    time_index='order_time',
    index='id',
)

es.normalize_dataframe(
    base_dataframe_name='order_products',
    new_dataframe_name='orders',
    index='order_id',
    additional_columns=['user_id'],
    make_time_index=False,
)

es.normalize_dataframe(
    base_dataframe_name='orders',
    new_dataframe_name='customers',
    index='user_id',
    make_time_index=False,
)

es.normalize_dataframe(
    base_dataframe_name='order_products',
    new_dataframe_name='products',
    index='product_id',
    additional_columns=['aisle_id', 'department_id'],
    make_time_index=False,
)

es.normalize_dataframe(
    base_dataframe_name='products',
    new_dataframe_name='aisles',
    index='aisle_id',
    additional_columns=['department_id'],
    make_time_index=False,
)

es.normalize_dataframe(
    base_dataframe_name='aisles',
    new_dataframe_name='departments',
    index='department_id',
    make_time_index=False,
)

es.add_interesting_values(dataframe_name='order_products',
                          values={'department': ['produce'],
                                  'product_name': ['Banana']})
es.plot()
[8]:
../_images/examples_predict_next_purchase_15_0.svg

计算特征

现在您可以使用称为深度特征合成 (DFS) 的方法生成特征。该方法通过在实体集的关系中堆叠和应用称为原语的数学操作来自动构建特征。实体集结构越完善,DFS 就越能利用这些关系生成更好的特征。让我们使用以下参数运行 DFS

  • entity_set 作为我们之前构建的实体集。

  • target_dataframe_name 作为客户数据框。

  • cutoff_time 作为我们之前生成的标签时间。标签值被附加到特征矩阵。

[9]:
fm, fd = ft.dfs(
    entityset=es,
    target_dataframe_name='customers',
    cutoff_time=lt,
    cutoff_time_in_index=True,
    include_cutoff_time=False,
    verbose=False,
)

fm.head()
[9]:
COUNT(orders) COUNT(order_products) MAX(order_products.add_to_cart_order) MAX(order_products.reordered) MEAN(order_products.add_to_cart_order) MEAN(order_products.reordered) MIN(order_products.add_to_cart_order) MIN(order_products.reordered) MODE(order_products.department) NUM_UNIQUE(order_products.department) ... SUM(orders.MIN(order_products.add_to_cart_order)) SUM(orders.MIN(order_products.reordered)) SUM(orders.NUM_UNIQUE(order_products.department)) SUM(orders.SKEW(order_products.add_to_cart_order)) SUM(orders.SKEW(order_products.reordered)) SUM(orders.STD(order_products.add_to_cart_order)) SUM(orders.STD(order_products.reordered)) COUNT(order_products WHERE product_name = Banana) COUNT(order_products WHERE department = produce) 购买了产品
user_id 时间
6851 2015-02-05 22:00:00 2 14 14.0 1.0 7.500000 0.928571 1.0 0.0 农产品 5 ... 1.0 0.0 5.0 0.0 -3.741657 4.183300 0.267261 0 8 True
8167 2015-01-14 11:00:00 3 9 9.0 1.0 5.000000 1.000000 1.0 1.0 农产品 3 ... 1.0 1.0 3.0 0.0 0.000000 2.738613 0.000000 0 5 False
2015-01-20 11:00:00 3 17 9.0 1.0 4.764706 0.941176 1.0 0.0 农产品 4 ... 2.0 1.0 7.0 0.0 -2.828427 5.188103 0.353553 0 7 False
19114 2015-01-13 13:00:00 2 25 25.0 0.0 13.000000 0.000000 1.0 0.0 农产品 9 ... 1.0 0.0 9.0 0.0 0.000000 7.359801 0.000000 0 10 True
31606 2015-01-06 09:00:00 4 9 9.0 1.0 5.000000 1.000000 1.0 1.0 乳制品和蛋 3 ... 1.0 1.0 3.0 0.0 0.000000 2.738613 0.000000 0 3 False

5 行 × 94 列

DFS 有两个输出:特征矩阵和特征定义。特征矩阵是包含特征值以及基于截止时间的相应标签的表格。特征定义是列表中的特征,可以存储并在以后重复使用,以便在未来数据上计算同一组特征。

机器学习

在之前的步骤中,您生成了标签和特征。最后一步是构建机器学习管道。

拆分数据

首先从特征矩阵中提取标签,并将数据拆分为训练集和保留集。

[10]:
fm.reset_index(drop=True, inplace=True)
y = fm.ww.pop('bought_product')

splits = evalml.preprocessing.split_data(
    X=fm,
    y=y,
    test_size=0.2,
    random_seed=0,
    problem_type='binary',
)

X_train, X_holdout, y_train, y_holdout = splits

寻找最佳模型

在训练集上运行搜索以找到最佳机器学习模型。在搜索过程中,会评估来自几个不同管道的预测。

[11]:
automl = evalml.AutoMLSearch(
    X_train=fm,
    y_train=y,
    problem_type='binary',
    objective='f1',
    random_seed=0,
    allowed_model_families=['catboost', 'random_forest'],
    max_iterations=3,
)

automl.search()
        High coefficient of variation (cv >= 0.5) within cross validation scores.
        Logistic Regression Classifier w/ Label Encoder + Replace Nullable Types Transformer + Imputer + One Hot Encoder + Standard Scaler may not perform as estimated on unseen data.
        High coefficient of variation (cv >= 0.5) within cross validation scores.
        Random Forest Classifier w/ Label Encoder + Replace Nullable Types Transformer + Imputer + One Hot Encoder may not perform as estimated on unseen data.
[11]:
{1: {'Logistic Regression Classifier w/ Label Encoder + Replace Nullable Types Transformer + Imputer + One Hot Encoder + Standard Scaler': '00:06',
  'Random Forest Classifier w/ Label Encoder + Replace Nullable Types Transformer + Imputer + One Hot Encoder': '00:03',
  'Total time of batch': '00:10'}}

搜索完成后,您可以打印出找到的最佳管道的信息,例如每个组件中的参数。

[12]:
automl.best_pipeline.describe()
automl.best_pipeline.graph()
**************************************************************************************************************************************
* Logistic Regression Classifier w/ Label Encoder + Replace Nullable Types Transformer + Imputer + One Hot Encoder + Standard Scaler *
**************************************************************************************************************************************

Problem Type: binary
Model Family: Linear
Number of features: 95

Pipeline Steps
==============
1. Label Encoder
         * positive_label : None
2. Replace Nullable Types Transformer
3. Imputer
         * categorical_impute_strategy : most_frequent
         * numeric_impute_strategy : mean
         * boolean_impute_strategy : most_frequent
         * categorical_fill_value : None
         * numeric_fill_value : None
         * boolean_fill_value : None
4. One Hot Encoder
         * top_n : 10
         * features_to_encode : None
         * categories : None
         * drop : if_binary
         * handle_unknown : ignore
         * handle_missing : error
5. Standard Scaler
6. Logistic Regression Classifier
         * penalty : l2
         * C : 1.0
         * n_jobs : -1
         * multi_class : auto
         * solver : lbfgs
[12]:
../_images/examples_predict_next_purchase_23_1.svg

通过评估保留集上的预测来评分模型性能。

[13]:
best_pipeline = automl.best_pipeline.fit(X_train, y_train)

score = best_pipeline.score(
    X=X_holdout,
    y=y_holdout,
    objectives=['f1'],
)

dict(score)
[13]:
{'F1': 1.0}

从管道中,您可以看到哪些特征对预测最重要。

[14]:
feature_importance = best_pipeline.feature_importance
feature_importance = feature_importance.set_index('feature')['importance']
top_k = feature_importance.abs().sort_values().tail(20).index
feature_importance[top_k].plot.barh(figsize=(8, 8), fontsize=14, width=.7);
../_images/examples_predict_next_purchase_27_0.png

进行预测

您已准备好使用训练好的模型进行预测。首先使用特征定义计算同一组特征。此外,使用基于数据集中最新信息的截止时间。

[15]:
fm = ft.calculate_feature_matrix(
    features=fd,
    entityset=es,
    cutoff_time=ft.pd.Timestamp('2015-03-02'),
    cutoff_time_in_index=True,
    verbose=False,
)

fm.head()
[15]:
COUNT(orders) COUNT(order_products) MAX(order_products.add_to_cart_order) MAX(order_products.reordered) MEAN(order_products.add_to_cart_order) MEAN(order_products.reordered) MIN(order_products.add_to_cart_order) MIN(order_products.reordered) MODE(order_products.department) NUM_UNIQUE(order_products.department) ... SUM(orders.MEAN(order_products.reordered)) SUM(orders.MIN(order_products.add_to_cart_order)) SUM(orders.MIN(order_products.reordered)) SUM(orders.NUM_UNIQUE(order_products.department)) SUM(orders.SKEW(order_products.add_to_cart_order)) SUM(orders.SKEW(order_products.reordered)) SUM(orders.STD(order_products.add_to_cart_order)) SUM(orders.STD(order_products.reordered)) COUNT(order_products WHERE product_name = Banana) COUNT(order_products WHERE department = produce)
user_id 时间
19114 2015-03-02 2 43 25.0 1.0 11.534884 0.302326 1.0 0.0 农产品 10 ... 0.722222 2.0 0.0 16.0 0.0 -1.084861 12.698340 0.460889 0 19
31606 2015-03-02 4 40 12.0 1.0 5.625000 0.950000 1.0 0.0 乳制品和蛋 3 ... 3.818182 4.0 3.0 12.0 0.0 -1.922718 12.110279 0.404520 0 12
37804 2015-03-02 5 43 11.0 1.0 5.000000 0.953488 1.0 0.0 农产品 8 ... 4.666667 5.0 4.0 18.0 0.0 -0.968246 13.113964 0.516398 0 30
8167 2015-03-02 3 26 9.0 1.0 4.846154 0.961538 1.0 0.0 农产品 6 ... 2.875000 3.0 2.0 12.0 0.0 -2.828427 7.926715 0.353553 0 10
57362 2015-03-02 3 45 23.0 1.0 9.422222 0.866667 1.0 0.0 农产品 12 ... 2.715942 3.0 1.0 24.0 0.0 -5.340821 13.414713 0.679940 0 10

5 行 × 93 列

预测客户是否会在未来 3 天内购买香蕉。

[16]:
y_pred = best_pipeline.predict(fm)
y_pred = y_pred.values

prediction = fm[[]]
prediction['bought_product (estimate)'] = y_pred
prediction.head()
[16]:
购买了产品 (估计)
user_id 时间
19114 2015-03-02 True
31606 2015-03-02 True
37804 2015-03-02 True
8167 2015-03-02 False
57362 2015-03-02 True

后续步骤

您已完成本教程。您可以回顾每个步骤,使用不同的参数探索和微调模型,直到它准备好投入生产。有关如何使用 Featuretools 生成的特征的更多信息,请参阅 Featuretools 文档。有关如何使用 EvalML 生成的模型 的更多信息,请参阅 EvalML 文档