rylou新闻推荐
排序模型:LightGBM排序模型、LightGBM分类模型、DIN分类模型
模型融合:简单的加权融合、通过Stacking进行模型融合。
排序模型主要用于将稍大规模的召回排序结果进行精排处理,以获得更优的排序结果。排序模型主要分成三类:
使用优点:
- XGBoost自动利用CPU的多线程,适当的改进了gradient boosting,增加了剪枝步骤,控制了模型复杂度,优化了模型过拟合的问题。
- 传统GBDT以CART树作基分类器,也称梯度提升决策树算法,XGBoost同时支持线性分类器,此时相当于带L1正则和L2正则的Logistics回归(分类问题)或线性回归问题(回归问题)。
- 传统GBDT仅用到一阶导数信息,XGBoost则因为对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。
- XGBoost在代价函数加入了正则项以控制模型的复杂度。正则化项里包含了书的叶子节点个数、每个叶子节点上输出的Score的L2模的平方和。以Bias Variance tradeoff的角度来讲,正则化降低了模型的Variance,使学习出来的模型将更加简单,以防止过拟合,这也是XGBoost由于GBDT的一个特性。
- 实现了一种分裂节点寻找的近似算法,用于加速和减小内存消耗。
- 节点分裂算法可以自动利用特征的稀疏性。
- 样本数据事先排好序并以Block的形式存储,利于并行计算。
- Penalty Function Omega主要是通过对树的叶子节点数和叶子分数做惩罚确保树的简单性。
- 支持分布式计算,可以运行在MPI,YARN上,得益于底层支持容错的分布式通信框架Rabit。
缺点:
- XGBoost的建树方式为Level-wise建树方式,而Level-wise对当前层的所有叶子节点一视同仁,有些叶子节点分裂收益非常小,对结果没影响,但还是要分裂,加重了计算代价。
- 与排序方法空间消耗极大,不仅需要保存特征值,也需要保存特征的排序索引,同时时间消耗也极大,在遍历每个分裂点是都需要计算分裂增益。
使用优点:
- 基于Histogram的决策树算法
- 带深度限制的Leaf-wise的叶子生长策略
- 通过直方图做差加速,一个子节点的直方图可以通过父节点的直方图减去兄弟节点的直方图来得到,从而加速计算。
- 直接支持类别特征,在对离散特征分裂时,每个取值都当作一个同,分裂时的增益算的是是否属于某个类的信息增益,类似于One-Hot编码。
- Cache命中率优化
- 多线程优化
- 使用leaf-wise建树方式减小了XGBoost中level-wise导致的性能损耗。(同时带来的弊端是容易陷入较高的深度中,容易导致过拟合现象的出现)
- 内存上大幅优化:lightGBM只需要保存特征分桶离散化之后的值,而XGBoost使用时Exact决策树算法,既需要保存原始特征的值,也需要保存这个值的顺序索引。
- 缩短计算时间:预排序算法在选择好分裂特征计算分裂收益时需要遍历所欲的样本的特征值,而直方图算法只需要遍历桶就行。
LightGBM是boosting集合模型的新进成员,同XGBoost一样,是基于GBDT的高效集成模型,对训练好的,树模型简单来说就是多个IF-THEN规则的集合。
代码参考学习来源
:ryluo通过召回的操作, 我们已经进行了问题规模的缩减, 对于每个用户, 选择出了N篇文章作为了候选集,并基于召回的候选集构建了与用户历史相关的特征,以及用户本身的属性特征,文章本省的属性特征,以及用户与文章之间的特征,下面就是使用机器学习模型来对构造好的特征进行学习,然后对测试集进行预测,得到测试集中的每个候选集用户点击的概率,返回点击概率最大的topk个文章,作为最终的结果。
排序阶段选择了三个比较有代表性的排序模型,它们分别是:
得到了最终的排序模型输出的结果之后,还选择了两种比较经典的模型集成的方法:
import numpy as np
import pandas as pd
import pickle
from tqdm import tqdm
import gc, os
import time
from datetime import datetime
import lightgbm as lgb
from sklearn.preprocessing import MinMaxScaler
import warnings
warnings.filterwarnings('ignore')
data_path = './data_raw/'
save_path = './temp_results/'
offline = False
# 重新读取数据的时候,发现click_article_id是一个浮点数,所以将其转换成int类型
trn_user_item_feats_df = pd.read_csv(save_path + 'trn_user_item_feats_df.csv')
trn_user_item_feats_df['click_article_id'] = trn_user_item_feats_df['click_article_id'].astype(int)
if offline:
val_user_item_feats_df = pd.read_csv(save_path + 'val_user_item_feats_df.csv')
val_user_item_feats_df['click_article_id'] = val_user_item_feats_df['click_article_id'].astype(int)
else:
val_user_item_feats_df = None
tst_user_item_feats_df = pd.read_csv(save_path + 'tst_user_item_feats_df.csv')
tst_user_item_feats_df['click_article_id'] = tst_user_item_feats_df['click_article_id'].astype(int)
# 做特征的时候为了方便,给测试集也打上了一个无效的标签,这里直接删掉就行
del tst_user_item_feats_df['label']
def submit(recall_df, topk=5, model_name=None):
recall_df = recall_df.sort_values(by=['user_id', 'pred_score'])
recall_df['rank'] = recall_df.groupby(['user_id'])['pred_score'].rank(ascending=False, method='first')
# 判断是不是每个用户都有5篇文章及以上
tmp = recall_df.groupby('user_id').apply(lambda x: x['rank'].max())
assert tmp.min() >= topk
del recall_df['pred_score']
submit = recall_df[recall_df['rank'] <= topk].set_index(['user_id', 'rank']).unstack(-1).reset_index()
submit.columns = [int(col) if isinstance(col, int) else col for col in submit.columns.droplevel(0)]
# 按照提交格式定义列名
submit = submit.rename(columns={
'': 'user_id', 1: 'article_1', 2: 'article_2',
3: 'article_3', 4: 'article_4', 5: 'article_5'})
save_name = save_path + model_name + '_' + datetime.today().strftime('%m-%d') + '.csv'
submit.to_csv(save_name, index=False, header=True)
# 排序结果归一化
def norm_sim(sim_df, weight=0.0):
# print(sim_df.head())
min_sim = sim_df.min()
max_sim = sim_df.max()
if max_sim == min_sim:
sim_df = sim_df.apply(lambda sim: 1.0)
else:
sim_df = sim_df.apply(lambda sim: 1.0 * (sim - min_sim) / (max_sim - min_sim))
sim_df = sim_df.apply(lambda sim: sim + weight) # plus one
return sim_df
# 防止中间出错之后重新读取数据
trn_user_item_feats_df_rank_model = trn_user_item_feats_df.copy()
if offline:
val_user_item_feats_df_rank_model = val_user_item_feats_df.copy()
tst_user_item_feats_df_rank_model = tst_user_item_feats_df.copy()
# 定义特征列
lgb_cols = ['sim0', 'time_diff0', 'word_diff0','sim_max', 'sim_min', 'sim_sum',
'sim_mean', 'score','click_size', 'time_diff_mean', 'active_level',
'click_environment','click_deviceGroup', 'click_os', 'click_country',
'click_region','click_referrer_type', 'user_time_hob1', 'user_time_hob2',
'words_hbo', 'category_id', 'created_at_ts','words_count']
# 排序模型分组
trn_user_item_feats_df_rank_model.sort_values(by=['user_id'], inplace=True)
g_train = trn_user_item_feats_df_rank_model.groupby(['user_id'], as_index=False).count()["label"].values
if offline:
val_user_item_feats_df_rank_model.sort_values(by=['user_id'], inplace=True)
g_val = val_user_item_feats_df_rank_model.groupby(['user_id'], as_index=False).count()["label"].values
# 排序模型定义
lgb_ranker = lgb.LGBMRanker(boosting_type='gbdt', num_leaves=31, reg_alpha=0.0, reg_lambda=1,
max_depth=-1, n_estimators=100, subsample=0.7, colsample_bytree=0.7, subsample_freq=1,
learning_rate=0.01, min_child_weight=50, random_state=2018, n_jobs= 16)
# 排序模型训练
if offline:
lgb_ranker.fit(trn_user_item_feats_df_rank_model[lgb_cols], trn_user_item_feats_df_rank_model['label'], group=g_train,
eval_set=[(val_user_item_feats_df_rank_model[lgb_cols], val_user_item_feats_df_rank_model['label'])],
eval_group= [g_val], eval_at=[1, 2, 3, 4, 5], eval_metric=['ndcg', ], early_stopping_rounds=50, )
else:
lgb_ranker.fit(trn_user_item_feats_df[lgb_cols], trn_user_item_feats_df['label'], group=g_train)
# 模型预测
tst_user_item_feats_df['pred_score'] = lgb_ranker.predict(tst_user_item_feats_df[lgb_cols], num_iteration=lgb_ranker.best_iteration_)
# 将这里的排序结果保存一份,用户后面的模型融合
tst_user_item_feats_df[['user_id', 'click_article_id', 'pred_score']].to_csv(save_path + 'lgb_ranker_score.csv', index=False)
# 预测结果重新排序, 及生成提交结果
rank_results = tst_user_item_feats_df[['user_id', 'click_article_id', 'pred_score']]
rank_results['click_article_id'] = rank_results['click_article_id']