基于机器学习的电商优惠券核销预测

1. 项目简介

随着移动互联网的快速发展,O2O(Online to Offline)模式已成为电商领域的一大亮点。优惠券作为一种有效的营销工具,被广泛应用于吸引新客户和激活老用户。然而,传统的随机投放方式往往效率低下,不仅对用户造成干扰,还可能损害品牌形象。因此,个性化优惠券投放成为提高营销效果的关键。本文将详细介绍如何利用机器学习技术进行电商优惠券使用预测,以实现优惠券的精准投放。

2. 数据准备

2.1 数据来源与收集

本研究使用的数据集包括线下和线上两个部分。线下数据集包含用户ID、商户ID、优惠券ID、折扣率、距离、领券日期和消费日期等信息。线上数据集则包含用户ID、商户ID、行为类型、优惠券ID、折扣率、领券日期和消费日期等信息。

2.2 数据预处理

数据预处理是机器学习中的关键步骤。首先,我们需要处理缺失值,例如将字符串类型的缺失值替换为np.nan。其次,对于异常值,如距离字段中的null值,我们将其替换为-1,并转换为整数类型。最后,我们需要对数据类型进行转换,确保所有数值字段都是正确的数据类型。

# 处理缺失值和异常值
t2.replace('null', -1, inplace=True)
t2.distance = t2.distance.astype('int')
t2.replace(-1, np.nan, inplace=True)

3. 特征工程

特征工程是机器学习中提高模型性能的重要环节。我们从以下几个方面构建特征:

3.1 优惠券相关特征

  • 优惠券类型(直接优惠为0,满减为1)

  • 优惠券折率

  • 满减优惠券的最低消费

  • 历史出现次数

  • 历史核销次数

  • 历史核销率

  • 历史核销时间率

  • 领取优惠券是一周的第几天

  • 领取优惠券是一月的第几天

  • 历史上用户领取该优惠券次数

  • 历史上用户消费该优惠券次数

  • 历史上用户对该优惠券的核销率

    def get_coupon_related_feature(dataset3, filename='coupon3_feature'):
    # 计算折扣率函数
    def calc_discount_rate(s):
    s = str(s)
    s = s.split(':')
    if len(s) == 1:
    return float(s[0])
    else:
    return 1.0 - float(s[1]) / float(s[0])

      # 提取满减优惠券中,满对应的金额
      def get_discount_man(s):
          s = str(s)
          s = s.split(':')
          if len(s) == 1:
              return 'null'
          else:
              return int(s[0])
    
      # 提取满减优惠券中,减对应的金额
      def get_discount_jian(s):
          s = str(s)
          s = s.split(':')
          if len(s) == 1:
              return 'null'
          else:
              return int(s[1])
    
      # 是不是满减卷
      def is_man_jian(s):
          s = str(s)
          s = s.split(':')
          if len(s) == 1:
              return 0
          else:
              return 1.0
    
      # 周几领取的优惠券
      dataset3['day_of_week'] = dataset3.date_received.astype('str').apply(
          lambda x: date(int(x[0:4]), int(x[4:6]), int(x[6:8])).weekday() + 1)
    
      # 每月的第几天领取的优惠券
      dataset3['day_of_month'] = dataset3.date_received.astype('str').apply(
          lambda x: int(x[6:8]))
    
      # 领取优惠券的时间与当月初距离多少天
      dataset3['days_distance'] = dataset3.date_received.astype('str').apply(
          lambda x: (date(int(x[0:4]), int(x[4:6]), int(x[6:8])) - date(2016, 6, 30)).days)
    
      # 满减优惠券中,满对应的金额
      dataset3['discount_man'] = dataset3.discount_rate.apply(get_discount_man)
    
      # 满减优惠券中,减对应的金额
      dataset3['discount_jian'] = dataset3.discount_rate.apply(get_discount_jian)
    
      # 优惠券是不是满减卷
      dataset3['is_man_jian'] = dataset3.discount_rate.apply(is_man_jian)
    
      # 优惠券的折扣率(满减卷进行折扣率转换)
      dataset3['discount_rate'] = dataset3.discount_rate.apply(calc_discount_rate)
    
      # 特定优惠券的总数量
      d = dataset3[['coupon_id']]
      d['coupon_count'] = 1
      d = d.groupby('coupon_id').agg('sum').reset_index()
      dataset3 = pd.merge(dataset3, d, on='coupon_id', how='left')
    
      dataset3.to_csv(os.path.join('features', filename + '.csv'), index=None)
      return dataset3
    

3.2 商户相关特征

  • 商家优惠券被领取次数

  • 商家优惠券被领取后不核销次数

  • 商家优惠券被领取后核销次数

  • 商家优惠券被领取后核销率

  • 商家优惠券核销的平均/最小/最大消费折率

  • 核销商家优惠券的不同用户数量,及其占领取不同的用户比重

  • 商家优惠券平均每个用户核销多少张

  • 商家被核销过的不同优惠券数量

  • 商家被核销过的不同优惠券数量占所有领取过的不同优惠券数量的比重

  • 商家平均每种优惠券核销多少张

  • 商家被核销优惠券的平均时间率

  • 商家被核销优惠券中的平均/最小/最大用户-商家距离

    def get_merchant_related_feature(feature3, filename='merchant3_feature'):
    merchant3 = feature3[['merchant_id', 'coupon_id', 'distance', 'date_received', 'date']]

      # 提取不重复的商户集合
      t = merchant3[['merchant_id']]
      t.drop_duplicates(inplace=True)
    
      # 商户的总销售次数
      t1 = merchant3[merchant3.date != 'null'][['merchant_id']]
      t1['total_sales'] = 1
      t1 = t1.groupby('merchant_id').agg('sum').reset_index()
    
      # 商户被核销优惠券的销售次数
      t2 = merchant3[(merchant3.date != 'null') & (merchant3.coupon_id != 'null')][['merchant_id']]
      t2['sales_use_coupon'] = 1
      t2 = t2.groupby('merchant_id').agg('sum').reset_index()
    
      # 商户发行优惠券的总数
      t3 = merchant3[merchant3.coupon_id != 'null'][['merchant_id']]
      t3['total_coupon'] = 1
      t3 = t3.groupby('merchant_id').agg('sum').reset_index()
    
      # 商户被核销优惠券的用户-商户距离,转化为int数值类型
      t4 = merchant3[(merchant3.date != 'null') & (merchant3.coupon_id != 'null')][['merchant_id', 'distance']]
      t4.replace('null', -1, inplace=True)
      t4.distance = t4.distance.astype('int')
      t4.replace(-1, np.nan, inplace=True)
    
      # 商户被核销优惠券的最小用户-商户距离
      t5 = t4.groupby('merchant_id').agg('min').reset_index()
      t5.rename(columns={'distance': 'merchant_min_distance'}, inplace=True)
    
      # 商户被核销优惠券的最大用户-商户距离
      t6 = t4.groupby('merchant_id').agg('max').reset_index()
      t6.rename(columns={'distance': 'merchant_max_distance'}, inplace=True)
    
      # 商户被核销优惠券的平均用户-商户距离
      t7 = t4.groupby('merchant_id').agg('mean').reset_index()
      t7.rename(columns={'distance': 'merchant_mean_distance'}, inplace=True)
    
      # 商户被核销优惠券的用户-商户距离的中位数
      t8 = t4.groupby('merchant_id').agg('median').reset_index()
      t8.rename(columns={'distance': 'merchant_median_distance'}, inplace=True)
    
      # 合并上述特征
      merchant3_feature = pd.merge(t, t1, on='merchant_id', how='left')
      merchant3_feature = pd.merge(merchant3_feature, t2, on='merchant_id', how='left')
      merchant3_feature = pd.merge(merchant3_feature, t3, on='merchant_id', how='left')
      merchant3_feature = pd.merge(merchant3_feature, t5, on='merchant_id', how='left')
      merchant3_feature = pd.merge(merchant3_feature, t6, on='merchant_id', how='left')
      merchant3_feature = pd.merge(merchant3_feature, t7, on='merchant_id', how='left')
      merchant3_feature = pd.merge(merchant3_feature
    

4. 数据集可视化分析

4.1 预测标签的类别分布

可以看出,标签为 1 的占比非常少,是一个类别极度不均衡的二分类问题。

4.2 特征相关性分析

4.3 商户的总销售次数分布情况

4.4 领取优惠券的时间与当月初距离天数分布

由于特征太多篇幅有限,此处只列出部分特征的分布可视化。

5. 训练集和验证集切分

由于比赛已结束,所以此处将手动切分出训练集、验证集、测试集,测试集用于不同模型的性能对比。

df_columns = dataset12_x.columns.values
print('===> feature count: {}'.format(len(df_columns)))

X_train, X_valid, y_train, y_valid = train_test_split(dataset12_x, dataset12_y, test_size=0.1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X_train, y_train, test_size=0.1, random_state=42)
print('train: {}, valid: {}, test: {}'.format(X_train.shape[0], X_valid.shape[0], X_test.shape[0]))
复制代码
===> feature count: 53
train: 327856, valid: 40477, test: 36429

6. Xgboost 建模预测

Xgboost是一种高效的梯度提升框架,它可以用来解决分类、回归等多种机器学习任务。Xgboost通过集成多个弱学习器(通常是决策树),并优化损失函数来提高模型的准确性。

xgb_params = {
    'eta': 0.01,
    'min_child_weight': 20,
    'colsample_bytree': 0.5,
    'max_depth': 15,
    'subsample': 0.9,
    'lambda': 2.0,
    'eval_metric': 'auc',
    'objective': 'binary:logistic',
    'nthread': -1,
    'silent': 1,
    'booster': 'gbtree'
}

pre_xgb_model = xgb.train(dict(xgb_params),
                      dtrain,
                      evals=watchlist,
                      verbose_eval=50)

交叉验证获取最佳迭代次数:

print('---> cv train to choose best_num_boost_round')
cv_result = xgb.cv(dict(xgb_params),
                   dtrain,
                   num_boost_round=5000,
                   early_stopping_rounds=100,
                   verbose_eval=100,
                   show_stdv=False,
                   )
best_num_boost_rounds = len(cv_result)
mean_train_logloss = cv_result.loc[best_num_boost_rounds-11 : best_num_boost_rounds-1, 'train-auc-mean'].mean()
mean_test_logloss = cv_result.loc[best_num_boost_rounds-11 : best_num_boost_rounds-1, 'test-auc-mean'].mean()
print('best_num_boost_rounds = {}'.format(best_num_boost_rounds))

print('mean_train_auc = {:.7f} , mean_test_auc = {:.7f}\n'.format(mean_train_logloss, mean_test_logloss))
复制代码
[0]	train-auc:0.87954	test-auc:0.87309
[100]	train-auc:0.90277	test-auc:0.89217
[200]	train-auc:0.90981	test-auc:0.89533
[300]	train-auc:0.91590	test-auc:0.89786
[400]	train-auc:0.92089	test-auc:0.89978
[500]	train-auc:0.92522	test-auc:0.90138
[600]	train-auc:0.92873	test-auc:0.90252
[700]	train-auc:0.93169	test-auc:0.90334
[800]	train-auc:0.93411	test-auc:0.90396
[900]	train-auc:0.93610	test-auc:0.90444
[1000]	train-auc:0.93786	test-auc:0.90482
[1100]	train-auc:0.93937	test-auc:0.90512
[1200]	train-auc:0.94078	test-auc:0.90540
[1300]	train-auc:0.94218	test-auc:0.90564
[1400]	train-auc:0.94347	test-auc:0.90583
[1500]	train-auc:0.94468	test-auc:0.90595
[1600]	train-auc:0.94578	test-auc:0.90607
[1700]	train-auc:0.94686	test-auc:0.90616
[1800]	train-auc:0.94787	test-auc:0.90626
[1900]	train-auc:0.94886	test-auc:0.90632
[2000]	train-auc:0.94986	test-auc:0.90636

6.1 特征重要程度分析

6.2 性能评估

# predict train
predict_train = xgb_model.predict(dtrain)
after_xgb_train_auc = evaluate_score(predict_train, y_train)

# predict validate
predict_valid = xgb_model.predict(dvalid)
after_xgb_valid_auc = evaluate_score(predict_valid, y_valid)

dtest = xgb.DMatrix(X_test, feature_names=df_columns)
predict_test = xgb_model.predict(dtest)
after_xgb_test_auc = evaluate_score(predict_test, y_test)

print('训练集 auc = {:.7f} , 验证集 auc = {:.7f} , 测试集 auc = {:.7f}\n'.format(
    after_xgb_train_auc, after_xgb_valid_auc, after_xgb_test_auc))

训练集 auc = 0.9042264 , 验证集 auc = 0.8958611 , 测试集 auc = 0.8960916

6.3 调参前后模型性能对比

可以看出,调参后,训练集、验证集和测试集的 AUC 都得到了不同程度的提升、

6.4 预测性能 ROC 曲线

7. 随机森林(RandomForest)建模预测

随机森林是一种集成学习方法,它通过构建多个决策树并将它们的预测结果进行汇总来提高整体模型的性能。随机森林在处理高维数据时表现出色,并且对于过拟合具有一定的抵抗力。

用RandomSearch+CV选取超参数:

# 建立一个分类器或者回归器
rf_clf = RandomForestClassifier()

# 给定参数搜索范围:list or distribution
param_dist = {
    "n_estimators": [100, 500, 1000, 1500, 2000],
    "max_depth": [3, 5, 8, 12, 15],
    "max_features": [2, 5, 10,],
    "min_samples_split": [2, 4, 6, 8, 10, 12],
    "bootstrap": [True, False],
    "criterion": ["gini", "entropy"],
}

n_iter_search = 20
random_search_cv = RandomizedSearchCV(rf_clf, param_distributions=param_dist, n_iter=n_iter_search, cv=5, n_jobs=-1, verbose=1)

最佳参数训练 RF 模型:

rf_model = RandomForestClassifier(
    n_estimators=3000, criterion='gini', max_depth=12, 
    min_samples_split=1000, min_samples_leaf=6, min_weight_fraction_leaf=0.0, 
    max_features='sqrt', max_leaf_nodes=None, min_impurity_decrease=0.0, 
    bootstrap=True, n_jobs=-1, random_state=42, 
    verbose=1, warm_start=False, 
    max_samples=None
)
复制代码
训练集 auc = 0.6242629 , 验证集 auc = 0.6232508 , 测试集 auc = 0.6194697

8. Stochastic Gradient Descent(SGD算法)

SGD是一种优化算法,它通过随机选择样本来更新模型参数,从而减少计算量并加快收敛速度。SGD适用于大规模和在线机器学习任务。

同样的方法,测试 SGD 算法建模预测性能,此处省略。

9. 模型对比

import matplotlib.pyplot as plt
import numpy as np

species = ['训练集', '验证集', '测试集']
penguin_means = {
    'Xgboost': (xgb_train_auc, xgb_valid_auc, xgb_test_auc),
    'RandomForest': (rf_train_auc, rf_valid_auc, rf_test_auc),
    'SGD': (sgd_train_auc, sgd_valid_auc, sgd_test_auc),
}
xgb_train_auc
x = np.arange(len(species))
width = 0.25
multiplier = 0

plt.figure(figsize=(40, 20))
fig, ax = plt.subplots(layout='constrained', figsize=(30, 15))

for attribute, measurement in penguin_means.items():
    offset = width * multiplier
    rects = ax.bar(x + offset, measurement, width, label=attribute)
    ax.bar_label(rects, padding=3, fontsize=26)
    multiplier += 1

ax.set_ylabel('数据集', fontsize=26)
ax.set_title('不同模型的评测性能对比', fontsize=40)
ax.set_xticks(x + width, species, fontsize=26)
ax.legend(loc='upper left', fontsize=26)
ax.set_ylim(0, 1.5)

plt.show()

我们比较了Xgboost、随机森林和SGD三种模型的性能。结果显示,Xgboost模型在训练集、验证集和测试集上的AUC值均高于其他两种模型。

10. 结论

通过对用户行为和优惠券使用情况的分析,我们构建了一个基于机器学习的优惠券使用预测模型。该模型能够有效地预测用户是否会核销他们收到的优惠券,从而帮助企业更精准地进行营销活动。未来的工作可以进一步优化特征选择、调整模型参数,或者尝试其他类型的机器学习算法以提升预测准确性。

相关推荐
loongloongz7 分钟前
联合条件概率 以及在语言模型中的应用
人工智能·语言模型·自然语言处理·概率论
lijfrank9 分钟前
情感计算领域期刊与会议
人工智能·人机交互
sp_fyf_202412 分钟前
计算机人工智能前沿进展-大语言模型方向-2024-09-18
人工智能·语言模型·自然语言处理
sp_fyf_202414 分钟前
计算机人工智能前沿进展-大语言模型方向-2024-09-14
人工智能·语言模型·自然语言处理
ybdesire19 分钟前
nanoGPT用红楼梦数据从头训练babyGPT-12.32M实现任意问答
人工智能·深度学习·语言模型
AI极客菌25 分钟前
Stable Diffusion绘画 | 生成高清多细节图片的各个要素
人工智能·ai·ai作画·stable diffusion·aigc·midjourney·人工智能作画
FOUR_A27 分钟前
【机器学习导引】ch2-模型评估与选择
人工智能·机器学习
lupai1 小时前
盘点实用的几款汽车类接口?
大数据·人工智能·汽车
geekrabbit2 小时前
机器学习和深度学习的区别
运维·人工智能·深度学习·机器学习·浪浪云
Java追光着2 小时前
扣子智能体实战-汽车客服对话机器人(核心知识:知识库和卡片)
人工智能·机器人·汽车·智能体