文章推荐系统 | 十一、基于 LR 模型的离线排序

推荐阅读:
文章推荐系统 | 一、推荐流程设计
文章推荐系统 | 二、同步业务数据
文章推荐系统 | 三、收集用户行为数据
文章推荐系统 | 四、构建离线文章画像
文章推荐系统 | 五、计算文章相似度
文章推荐系统 | 六、构建离线用户画像
文章推荐系统 | 七、构建离线文章特征和用户特征
文章推荐系统 | 八、基于模型的离线召回
文章推荐系统 | 九、基于内容的离线及在线召回
文章推荐系统 | 十、基于热门文章和新文章的在线召回

前面,我们已经完成了召回阶段的全部工作,通过召回,我们可以从数百万甚至上亿的原始物品数据中,筛选出和用户相关的几百、几千个可能感兴趣的物品。接下来,我们将要进入到排序阶段,对召回的几百、几千个物品进行进一步的筛选和排序。

排序流程包括离线排序和在线排序:

  • 离线排序
    读取前天(第 T - 2 天)之前的用户行为数据作为训练集,对离线模型进行训练;训练完成后,读取昨天(第 T - 1 天)的用户行为数据作为验证集进行预测,根据预测结果对离线模型进行评估;若评估通过,当天(第 T 天)即可将离线模型更新到定时任务中,定时执行预测任务;明天(第 T + 1 天)就能根据今天的用户行为数据来观察更新后离线模型的预测效果。(注意:数据生产有一天时间差,第 T 天生成第 T - 1 天的数据)

  • 在线排序
    读取前天(第 T - 2 天)之前的用户行为数据作为训练集,对在线模型进行训练;训练完成后,读取昨天(第 T - 1 天)的用户行为数据作为验证集进行预测,根据预测结果对在线模型进行评估;若评估通过,当天(第 T 天)即可将在线模型更新到线上,实时执行排序任务;明天(第 T + 1 天)就能根据今天的用户行为数据来观察更新后在线模型的预测效果。

这里再补充一个数据集划分的小技巧:可以横向划分,随机或按用户或其他样本选择策略;也可以纵向划分,按照时间跨度,比如一周的数据中,周一到周四是训练集,周五周六是测试集,周日是验证集。

利用排序模型可以进行评分预测和用户行为预测,通常推荐系统利用排序模型进行用户行为预测,比如点击率(CTR)预估,进而根据点击率对物品进行排序,目前工业界常用的点击率预估模型有如下 3 种类型:

  • 宽模型 + 特征⼯程
    LR / MLR + 非 ID 类特征(⼈⼯离散 / GBDT / FM),可以使用 Spark 进行训练
  • 宽模型 + 深模型
    Wide&Deep,DeepFM,可以使用 TensorFlow 进行训练
  • 深模型:
    DNN + 特征 Embedding,可以使用 TensorFlow 进行训练

这里的宽模型即指线性模型,线性模型的优点包括:

  • 相对简单,训练和预测的计算复杂度都相对较低
  • 可以集中精力发掘新的有效特征,且可以并行化工作
  • 解释性较好,可以根据特征权重做解释

本文我们将采用逻辑回归作为离线模型,进行点击率预估。逻辑回归(Logistic Regression,LR)是基础的二分类模型,也是监督学习的一种,通过对有标签的训练集数据进行特征学习,进而可以对测试集(新数据)的标签进行预测。我们这里的标签就是指用户是否对文章发生了点击行为。

构造训练集

读取用户历史行为数据,将 clicked 作为训练集标签

spark.sql("use profile")
user_article_basic = spark.sql("select * from user_article_basic").select(['user_id', 'article_id', 'clicked'])

user_article_basic 结果如下所示

之前我们已经计算好了文章特征和用户特征,并存储到了 Hbase 中。这里我们遍历用户历史行为数据,根据其中文章 ID 和用户 ID 分别获取文章特征和用户特征,再将标签转为 int 类型,这样就将一条用户行为数据构造成为了一个样本,再将所有样本加入到训练集中

train = []
for user_id, article_id, clicked in user_article_basic:
    try:
        article_feature = eval(hbu.get_table_row('ctr_feature_article', '{}'.format(article_id).encode(), 'article:{}'.format(article_id).encode()))
    except Exception as e:
        article_feature = []
    try:
        user_feature = eval(hbu.get_table_row('ctr_feature_user', '{}'.format(temp.user_id).encode(), 'channel:{}'.format(temp.channel_id).encode()))
    except Exception as e:
        user_feature = []

    if not article_feature:
        article_feature = [0.0] * 111
    if not user_feature:
        user_feature = [0.0] * 10

    sample = []
    sample.append(user_feature)
    sample.append(article_feature)
    sample.append(int(clicked))

    train.append(sample)

接下来,还需要利用 Spark 的 Vectors 将 array<double> 类型的 article_feature 和 user_feature 转为 vector 类型

columns = ['article_feature', 'user_feature', 'clicked']

def list_to_vector(row):
    from pyspark.ml.linalg import Vectors
    
    return Vectors.dense(row[0]), Vectors.dense(row[1]), row[2]

train = train.rdd.map(list_to_vector).toDF(columns) 

再将 article_feature, user_feature 合并为统一输入到 LR 模型的特征列 features,这样就完成训练集的构建

train = VectorAssembler().setInputCols(columns[0:1]).setOutputCol('features').transform(train)

模型训练

Spark 已经实现好了 LR 模型,通过指定训练集 train 的特征列 features 和标签列 clicked,即可对 LR 模型进行训练,再将训练好的模型保存到 HDFS

from pyspark.ml.feature import VectorAssembler
from pyspark.ml.classification import LogisticRegression

lr = LogisticRegression()
model = lr.setLabelCol("clicked").setFeaturesCol("features").fit(train)
model.save("hdfs://hadoop-master:9000/headlines/models/lr.obj")

加载训练好的 LR 模型,调用 transform() 对训练集做出预测(实际场景应该对验证集和训练集进行预测)

from pyspark.ml.classification import LogisticRegressionModel

online_model = LogisticRegressionModel.load("hdfs://hadoop-master:9000/headlines/models/lr.obj")
sort_res = online_model.transform(train)

预测结果 sort_res 中包括 clicked 和 probability 列,其中 clicked 为样本标签的真实值,probability 是包含两个元素的列表,第一个元素是预测的不点击概率,第二个元素则是预测的点击概率,可以提取点击率(CTR)

def get_ctr(row):
    return float(row.clicked), float(row.probability[1]) 

score_label = sort_res.select(["clicked", "probability"]).rdd.map(get_ctr)

模型评估

离线模型评估指标包括:

  • 评分准确度
    通常是均方根误差(RMSE),用来评估预测评分的效果
  • 排序能力
    通常采用 AUC(Area Under the Curve),即 ROC 曲线下方的面积
  • 分类准确率(Precision)
    表示在 Top K 推荐列表中,用户真实点击的物品所占的比例
  • 分类召回率(Recall)
    表示在用户真实点击的物品中,出现在 Top K 推荐列表中所占的比例

当模型更新后,还可以根据商业指标进行评估,比例类的包括: 点击率(CTR)、转化率(CVR),绝对类的包括:社交关系数量、用户停留时长、成交总额(GMV)等。

推荐系统的广度评估指标包括:

  • 覆盖率
    表示被有效推荐(推荐列表长度大于 c)的用户占全站用户的比例,公式如下:
    Con_{UV}=\frac{N_{l >c}}{N_{UV}}
  • 失效率
    表示被无效推荐(推荐列表长度为 0)的用户占全站用户的比例,公式如下:
    Lost_{UV}=\frac{N_{l =0}}{N_{UV}}
  • 新颖性
  • 更新率
    表示推荐列表的变化程度,当前周期与上个周期相比,推荐列表中不同物品的比例
    Update=\frac{N_{diff}}{N_{last}}

推荐系统的健康评估指标包括:

  • 个性化
    用于衡量推荐的个性化程度,是否大部分用户只消费小部分物品,可以计算所有用户推荐列表的平均相似度
  • 基尼系数
    用于衡量推荐系统的马太效应,反向衡量推荐的个性化程度。将物品按照累计推荐次数排序,排序位置为 i,推荐次数占总推荐次数的比例为 P_i,推荐次数越不平均,基尼系数越接近 1,公式为:
    Gini=\frac{1}{n}\sum_{i=1}^n P_i(2i-n-i)
  • 多样性
    通常是在类别维度上衡量推荐结果的多样性,可以衡量各个类别在推荐时的熵
    Div=\frac{\sum_{i=1}^n-P_i\log(P_i)}{n\log(n)}
    其中,物品共包括 n 个类别,类别 i 被推荐次数占总推荐次数的比例为 P_i,分母是各个类别最均匀时对应的熵,分子是实际推荐结果的类别分布熵。这是整体推荐的多样性,还可以计算每次推荐和每个用户推荐的多样性。

我们这里主要根据 AUC 进行评估,首先利用 model.summary.roc 绘制 ROC 曲线

import matplotlib.pyplot as plt

plt.figure(figsize=(5,5))
plt.plot([0, 1], [0, 1], 'r--')
plt.plot(model.summary.roc.select('FPR').collect(),
         model.summary.roc.select('TPR').collect())
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.show()

ROC 曲线如下所示,曲线下面的面积即为 AUC(Area Under the Curve),AUC 值越大,排序效果越好

利用 Spark 的 BinaryClassificationMetrics() 计算 AUC

from pyspark.mllib.evaluation import BinaryClassificationMetrics

metrics = BinaryClassificationMetrics(score_label)
metrics.areaUnderROC

也可以利用 sklearn 的 roc_auc_score() 计算 AUC,accuracy_score() 计算准确率

from sklearn.metrics import accuracy_score, roc_auc_score,
import numpy as np

arr = np.array(score_label.collect())
# AUC
roc_auc_score(arr[:, 0], arr[:, 1]) # 0.719274521004087

# 准确率
accuracy_score(arr[:, 0], arr[:, 1].round()) # 0.9051438053097345

参考

https://www.bilibili.com/video/av68356229
https://book.douban.com/subject/34872145/
https://pan.baidu.com/s/1-uvGJ-mEskjhtaial0Xmgw(学习资源已保存至网盘, 提取码:eakp)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容