LightGBM 实现基于内容的个性化推荐

大家好,本文中,我将和大家一起学习如何训练 LightGBM 模型来估计电子商务广告的点击率的推荐系统的例子。将在Criteo数据集上训练一个基于LightGBM的模型。

LightGBM是一个基于树的梯度提升学习算法框架。是基于分布式框架设计的,因而非常高效,具有以下优点:

  • 训练速度快,效率高。

  • 低内存的使用。

  • 伟大的准确性。

  • 支持并行和GPU学习。

  • 能够处理大规模数据。

LightGBM梯度提升树算法,推荐原理是基于内容的过滤,用于在基于内容的问题中进行快速训练和低内存使用。

接下来我们一起看个具体的案例,更加深刻的理解LightGBM是如何被用于推荐系统中的。

技术提升

本文由技术群粉丝分享,项目源码、数据、技术交流提升,均可加交流群获取,群友已超过2000人,添加时最好的备注方式为:来源+兴趣方向,方便找到志同道合的朋友

方式①、添加微信号:dkl88191,备注:来自CSDN +研究方向
方式②、微信搜索公众号:Python学习与数据挖掘,后台回复:加群

导入模块

import sys  
import os  
import numpy as np  
import lightgbm as lgb  
import papermill as pm  
import scrapbook as sb  
import pandas as pd  
import category_encoders as ce  
from tempfile import TemporaryDirectory  
from sklearn.metrics import roc_auc_score, log_loss  
  
import recommenders.models.lightgbm.lightgbm_utils as lgb_utils  
import recommenders.datasets.criteo as criteo  
  
print("System version: {}".format(sys.version))  
print("LightGBM version: {}".format(lgb.__version__))  
System version: 3.6.7 |Anaconda, Inc.| (default, Oct 23 2018, 19:16:44)   
[GCC 7.3.0]  
LightGBM version: 2.2.1  

参数设置

现在设置 LightGBM 的主要相关参数。基本上,该任务是一个二分类 (预测点击或不点击),因此目标函数设置为二分类 logloss,并使用 AUC 指标作为数据集类中不平衡的影响较小的指标。

通常,我们可以调整模型中的

  • 叶子数量(MAX_LEAF)

  • 每个叶子的最小数据数量(MIN_DATA)

  • 树的最大数量(NUM_OF_TREES)

  • 树的学习率(TREE_LEARNING_RATE)

  • 避免过拟合的早停轮数(EARLY_STOPPING_ROUNDS)

以获得更好的模型性能。

可以在中找到一些关于如何调优这些参数的建议。

MAX_LEAF = 64  
MIN_DATA = 20  
NUM_OF_TREES = 100  
TREE_LEARNING_RATE = 0.15  
EARLY_STOPPING_ROUNDS = 20  
METRIC = "auc"  
SIZE = "sample"  
  
params = {  
    'task': 'train',  
    'boosting_type': 'gbdt',  
    'num_class': 1,  
    'objective': "binary",  
    'metric': METRIC,  
    'num_leaves': MAX_LEAF,  
    'min_data': MIN_DATA,  
    'boost_from_average': True,  
    #set it according to your cpu cores.  
    'num_threads': 20,  
    'feature_fraction': 0.8,  
    'learning_rate': TREE_LEARNING_RATE,  
}  

数据准备

这里使用 CSV 作为示例数据输入。我们的示例数据是来自 Criteo数据集。Criteo数据集是一个著名的行业基准数据集,用于开发CTR预测模型,它经常被研究论文采用作为评估数据集。原始数据集对于轻量级演示来说太大了,因此我们从其中抽取一小部分作为演示数据集。

Criteo中有39列特征,其中13列是数值特征(I1-I13),其他26列是类别特征(C1-C26)。

数据传送门:https://www.kaggle.com/c/criteo-display-ad-challenge

nume_cols = ["I" + str(i) for i in range(1, 14)]  
cate_cols = ["C" + str(i) for i in range(1, 27)]  
label_col = "Label"  
  
header = [label_col] + nume_cols + cate_cols  
with TemporaryDirectory() as tmp:  
    all_data = criteo.load_pandas_df(size=SIZE, local_cache_path=tmp, header=header)  
display(all_data.head())                        

首先,从原始的所有数据中切割三个集合

  • train_data (前80%)

  • valid_data (中间10%)

  • test_data (最后10%)

值得注意的是,考虑到Criteo是一种时间序列流数据,这在推荐场景中也很常见,我们按其顺序对数据进行了拆分。

# split data to 3 sets      
length = len(all_data)  
train_data = all_data.loc[:0.8*length-1]  
valid_data = all_data.loc[0.8*length:0.9*length-1]  
test_data = all_data.loc[0.9*length:]  

基本用法

Ordinal Encoding

考虑到 LightGBM 可以自行处理低频特征和缺失值,在基本使用中,我们只使用序号编码器对类字符串的分类特征进行编码。

ord_encoder = ce.ordinal.OrdinalEncoder(cols=cate_cols)  
  
def encode_csv(df, encoder, label_col, typ='fit'):  
    if typ == 'fit':  
        df = encoder.fit_transform(df)  
    else:  
        df = encoder.transform(df)  
    y = df[label_col].values  
    del df[label_col]  
    return df, y  
  
train_x, train_y = encode_csv(train_data, ord_encoder, label_col)  
valid_x, valid_y = encode_csv(valid_data, ord_encoder, label_col, 'transform')  
test_x, test_y = encode_csv(test_data, ord_encoder, label_col, 'transform')  
  
print('Train Data Shape: X: {trn_x_shape}; Y: {trn_y_shape}.\nValid Data Shape: X: {vld_x_shape}; Y: {vld_y_shape}.\nTest Data Shape: X: {tst_x_shape}; Y: {tst_y_shape}.\n'  
      .format(trn_x_shape=train_x.shape,  
              trn_y_shape=train_y.shape,  
              vld_x_shape=valid_x.shape,  
              vld_y_shape=valid_y.shape,  
              tst_x_shape=test_x.shape,  
              tst_y_shape=test_y.shape,))  
train_x.head()  
Train Data Shape: X: (80000, 39); Y: (80000,).  
Valid Data Shape: X: (10000, 39); Y: (10000,).  
Test Data Shape: X: (10000, 39); Y: (10000,).  

构建模型

当超参数和数据都准备就绪时,我们可以创建一个模型:

lgb_train = lgb.Dataset(train_x, train_y.reshape(-1), params=params, categorical_feature=cate_cols)  
lgb_valid = lgb.Dataset(valid_x, valid_y.reshape(-1), reference=lgb_train, categorical_feature=cate_cols)  
lgb_test = lgb.Dataset(test_x, test_y.reshape(-1), reference=lgb_train, categorical_feature=cate_cols)  
lgb_model = lgb.train(params,  
                      lgb_train,  
                      num_boost_round=NUM_OF_TREES,  
                      early_stopping_rounds=EARLY_STOPPING_ROUNDS,  
                      valid_sets=lgb_valid,  
                      categorical_feature=cate_cols)  
[1]	valid_0's auc: 0.728695  
Training until validation scores don't improve for 20 rounds.  
[2]	valid_0's auc: 0.742373  
[3]	valid_0's auc: 0.747298  
[4]	valid_0's auc: 0.747969  
...  
[39]	valid_0's auc: 0.756966  
Early stopping, best iteration is:  
[19]	valid_0's auc: 0.763092  

现在看看模型的性能如何:

test_preds = lgb_model.predict(test_x)  
auc = roc_auc_score(np.asarray(test_y.reshape(-1)), np.asarray(test_preds))  
logloss = log_loss(np.asarray(test_y.reshape(-1)), np.asarray(test_preds), eps=1e-12)  
res_basic = {"auc": auc, "logloss": logloss}  
print(res_basic)  
sb.glue("res_basic", res_basic)  
{'auc': 0.7674356153037237,   
'logloss': 0.466876775528735}  

模型优化

Label-encoding and Binary-encoding

接下来,由于 LightGBM 对密集的数值特征有较好的有效处理能力,我们尝试将原始数据中的所有分类特征转换为数值特征,通过标签编码和二分类编码。同样由于Criteo的序列特性,我们所采用的标签编码是一个一个执行的,也就是说我们是根据每个样本之前的前一个样本的信息(sequence label-encoding和sequence count-encoding)对样本进行有序编码。此外,我们还对低频分类特征进行了筛选,并用数值特征对应列的均值来填补缺失的值。

lgb_utils.NumEncoder,主要步骤如下。

  • 首先,我们将低频分类特征转换为"LESS",将缺失的分类特征转换为"UNK"

  • 其次,我们将缺失的数值特征转换为相应列的均值。

  • 第三,类似字符串的分类特征是按顺序编码的Ordinal-encoding。

  • 然后,我们对样本中的分类特征逐个进行目标编码。对于每个样本,将其前一个样本的标签和计数信息添加到数据中,并产生新的特征。在形式上,for ,我们添加 作为当前样本的新标签特征,其中 是当前样本中要编码的类别,因此是前样本的数量,是检查前样本是否包含(是否)的指示函数。同时,我们还增加了的计数频率,即,作为一个新的计数特征。

  • 最后,在Ordinal-encoding结果的基础上,将Binary-encoding结果作为新列添加到数据中。

注意,上述过程中使用的统计数据只在拟合训练集时更新,而在转换测试集时保持静态,因为测试数据的标签应该是未知的。

label_col = 'Label'  
num_encoder = lgb_utils.NumEncoder(cate_cols, nume_cols, label_col)  
train_x, train_y = num_encoder.fit_transform(train_data)  
valid_x, valid_y = num_encoder.transform(valid_data)  
test_x, test_y = num_encoder.transform(test_data)  
del num_encoder  
print('Train Data Shape: X: {trn_x_shape}; Y: {trn_y_shape}.\nValid Data Shape: X: {vld_x_shape}; Y: {vld_y_shape}.\nTest Data Shape: X: {tst_x_shape}; Y: {tst_y_shape}.\n'  
      .format(trn_x_shape=train_x.shape,  
              trn_y_shape=train_y.shape,  
              vld_x_shape=valid_x.shape,  
              vld_y_shape=valid_y.shape,  
              tst_x_shape=test_x.shape,  
              tst_y_shape=test_y.shape,))  
2019-04-29 11:26:21,158 [INFO] Filtering and fillna features  
100%|██████████| 26/26 [00:02<00:00, 12.36it/s]  
100%|██████████| 13/13 [00:00<00:00, 711.59it/s]  
2019-04-29 11:26:23,286 [INFO] Ordinal encoding cate features  
2019-04-29 11:26:24,680 [INFO] Target encoding cate features  
100%|██████████| 26/26 [00:03<00:00,  6.72it/s]  
2019-04-29 11:26:28,554 [INFO] Start manual binary encoding  
100%|██████████| 65/65 [00:04<00:00, 15.87it/s]  
100%|██████████| 26/26 [00:02<00:00,  8.17it/s]  
2019-04-29 11:26:35,518 [INFO] Filtering and fillna features  
100%|██████████| 26/26 [00:00<00:00, 171.81it/s]  
100%|██████████| 13/13 [00:00<00:00, 2174.25it/s]  
2019-04-29 11:26:35,690 [INFO] Ordinal encoding cate features  
2019-04-29 11:26:35,854 [INFO] Target encoding cate features  
100%|██████████| 26/26 [00:00<00:00, 53.42it/s]  
2019-04-29 11:26:36,344 [INFO] Start manual binary encoding  
100%|██████████| 65/65 [00:03<00:00, 20.02it/s]  
100%|██████████| 26/26 [00:01<00:00, 17.67it/s]  
2019-04-29 11:26:41,081 [INFO] Filtering and fillna features  
100%|██████████| 26/26 [00:00<00:00, 158.08it/s]  
100%|██████████| 13/13 [00:00<00:00, 2203.78it/s]  
2019-04-29 11:26:41,267 [INFO] Ordinal encoding cate features  
2019-04-29 11:26:41,429 [INFO] Target encoding cate features  
100%|██████████| 26/26 [00:00<00:00, 53.08it/s]  
2019-04-29 11:26:41,922 [INFO] Start manual binary encoding  
100%|██████████| 65/65 [00:03<00:00, 20.10it/s]  
100%|██████████| 26/26 [00:01<00:00, 18.37it/s]  
  
Train Data Shape: X: (80000, 268); Y: (80000, 1).  
Valid Data Shape: X: (10000, 268); Y: (10000, 1).  
Test Data Shape: X: (10000, 268); Y: (10000, 1).  

训练和评估

lgb_train = lgb.Dataset(train_x, train_y.reshape(-1), params=params)  
lgb_valid = lgb.Dataset(valid_x, valid_y.reshape(-1), reference=lgb_train)  
lgb_model = lgb.train(params,  
                      lgb_train,  
                      num_boost_round=NUM_OF_TREES,  
                      early_stopping_rounds=EARLY_STOPPING_ROUNDS,  
                      valid_sets=lgb_valid)  
[1]	valid_0's auc: 0.731759  
Training until validation scores don't improve for 20 rounds.  
[2]	valid_0's auc: 0.747705  
[3]	valid_0's auc: 0.751667  
...  
[58]	valid_0's auc: 0.770408  
[59]	valid_0's auc: 0.770489  
Early stopping, best iteration is:  
[39]	valid_0's auc: 0.772136  
test_preds = lgb_model.predict(test_x)  
auc = roc_auc_score(np.asarray(test_y.reshape(-1)), np.asarray(test_preds))  
logloss = log_loss(np.asarray(test_y.reshape(-1)), np.asarray(test_preds), eps=1e-12)  
res_optim = {"auc": auc, "logloss": logloss}  
print(res_optim)  
sb.glue("res_optim", res_optim)  
{'auc': 0.7757371640011422,   
'logloss': 0.4606505068849181}  

模型保存和导入

现在我们完成了LightGBM的基本训练和测试,接下来保存并重新加载模型,然后再次评估它。

with TemporaryDirectory() as tmp:  
    save_file = os.path.join(tmp, r'finished.model')  
    lgb_model.save_model(save_file)  
    loaded_model = lgb.Booster(model_file=save_file)  
  
# eval the performance again  
test_preds = loaded_model.predict(test_x)  
  
auc = roc_auc_score(np.asarray(test_y.reshape(-1)), np.asarray(test_preds))  
logloss = log_loss(np.asarray(test_y.reshape(-1)), np.asarray(test_preds), eps=1e-12)  
print({"auc": auc, "logloss": logloss})  
{'auc': 0.7757371640011422,   
'logloss': 0.4606505068849181}  
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于 HyperOpt 实现 TPE 优化的 LightGBM 实例代码: ```python import lightgbm as lgb from hyperopt import fmin, tpe, hp, Trials # 定义搜索空间 space = { 'boosting_type': hp.choice('boosting_type', ['gbdt', 'dart', 'goss']), 'num_leaves': hp.quniform('num_leaves', 30, 150, 1), 'learning_rate': hp.loguniform('learning_rate', -6, 0), 'subsample_for_bin': hp.quniform('subsample_for_bin', 20000, 300000, 20000), 'min_child_samples': hp.quniform('min_child_samples', 20, 500, 5), 'reg_alpha': hp.uniform('reg_alpha', 0.0, 1.0), 'reg_lambda': hp.uniform('reg_lambda', 0.0, 1.0), 'colsample_bytree': hp.uniform('colsample_by_tree', 0.6, 1.0) } # 定义目标函数 def objective(params): # 加载数据集 train_data = lgb.Dataset('train_data.bin') valid_data = lgb.Dataset('valid_data.bin') # 训练模型 model = lgb.train(params, train_data, num_boost_round=1000, valid_sets=[valid_data], early_stopping_rounds=10, verbose_eval=False) # 获取验证集的损失 loss = model.best_score['valid_0']['rmse'] return {'loss': loss, 'status': 'ok'} # 初始化 Trials 对象 trials = Trials() # 运行优化 best = fmin(fn=objective, space=space, algo=tpe.suggest, max_evals=100, trials=trials) # 输出最佳参数 print(best) ``` 在这个例子中,我们使用 HyperOpt 的 TPE 算法来搜索 LightGBM 模型的超参数,搜索空间包括 boosting_type、num_leaves、learning_rate、subsample_for_bin、min_child_samples、reg_alpha、reg_lambda 和 colsample_bytree 等超参数。目标函数 objective 通过训练 LightGBM 模型并在验证集上计算损失来评估超参数组合的效果,优化过程由 fmin 函数实现,最大评估次数为 100 次。最终输出的是最佳超参数组合 best。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值