预测下一次购买¶
在本教程中,构建一个机器学习应用程序,预测客户是否会在下一个购物周期内购买产品。此应用程序分为三个重要步骤
预测工程
特征工程
机器学习
第一步,使用 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)

特征工程¶
在上一步中,您生成了标签。下一步是生成特征。
表示数据¶
首先使用 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]:
计算特征¶
现在您可以使用称为深度特征合成 (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]:
通过评估保留集上的预测来评分模型性能。
[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);

进行预测¶
您已准备好使用训练好的模型进行预测。首先使用特征定义计算同一组特征。此外,使用基于数据集中最新信息的截止时间。
[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 文档。