序言
达观数据是一家基于文本语义理解为企业提供自动抽取、审核、纠错、推荐、搜索、写作等系统服务的人工智能企业,其中在推荐场景上我们也服务了很多客户企业,客户在要求推荐服务稳定、需求响应及时的基础上,对系统的效果也提出了越来越高的期望,这对算法团队也是一个挑战。本文将从资讯信息流这个场景入手,先简单介绍达观推荐引擎的架构演化,同时尽可能详细的介绍学习排序这个核心技术的实践和落地经验。
达观推荐引擎架构
达观推荐引擎采用在线 - 近线 - 离线三层系统架构,可以从性能和算法复杂度两个维度来进行区分。在线:实时响应客户 http api 推荐请求,一般需要严格控制在 100ms 以内,最好在 50ms。该模块需要严格保证稳定性,综合考虑各个依赖模块的异常兼容、流量的超时控制等。
近线:准实时捕捉用户实时行为并做出反馈,即近线模块的输出需要考虑用户的实时行为反馈。该模块一般处理延迟为秒级。
离线:基于分布式平台离线挖掘,输出包括 item-base 协同过滤结果、基于标签的召回结果、各维度热门结果、用户画像等等。该模块的处理延迟一般为小时级或者天级。
一个通用的资讯流推荐架构如下:图 1:online-nearline-offline 三层架构
hot rec 模块负责生成各个维度的热门结果,如分类别热门、分地域热门;tagrec 生成各个标签的召回结果,如 英超 -> (item1,item2,….);item rec 生成每个资讯 item 的相关结果;user rec nearline 根据用户实时行为和离线画像负责生成用户的推荐结果;reconline 响应推荐请求;item cache 返回资讯的信息;uhvreceiver 负责接收用户对 item 的行为反馈。关于架构可参考更过之前达观数据发布的推荐技术文章。
为什么需要学习排序
学习排序(LTR:learning to rank)是信息检索领域的经典问题,也是互联网场景中 ranking 这个核心算法问题。推荐整个流程可以分为召回、排序、重排序这三个阶段,通俗来说,召回就是找到用户可能喜欢的几百条资讯,排序就是对这几百条资讯利用机器学习的方法预估用户对每条资讯的偏好程度,一般以点击率衡量,所以学习排序在很多情况下等同于点击率预估,都是将用户最可能点击的资讯优先推给用户;重排序更多考虑业务逻辑,如在推荐结果的多样性、时效性、新颖性等方面进行控制。
在没有学习排序之前,也可以单纯使用协同过滤算法来进行推荐。列如使用用户最近点击的资讯信息召回这些 item 的相关结果和偏好类别热门结果组合后进行返回。但是这对于资讯类推荐需要考虑一下问题:资讯类信息流属于用户消费型场景,item 时效性要求高,item base cf 容易召回较旧的内容,而且容易导致推荐结果收敛。因此可以将 item 的相关结果保证时效性的基础上,融合类别、标签热门结果,对各个策略的召回结果按照线上总体反馈进行排序,就可以作为用户的推荐结果。但是这一融合过程比较复杂,一种简单的方式就是看哪种召回策略总体收益越高就扩大这个策略的曝光占比,对于个体而言却显得不是特别个性化,而且在规则调参上也比较困难。
LTR 架构
我们迅速在资讯信息流推荐场景上实践 ltr 算法。Ltr 一般分为 point wise、pairwise、list wise,一般工程上使用 pointwise 较多,简单,成本低,收益也可靠。简单来说,Ltr 即预测 user 对一个未消费 item 的预估点击率,即:
即这个预估的点击率是和 user、item、context 相关的。我们使用逻辑回归(logistic regression,lr)模型来搭建我们第一版的学习排序架构,lr 模型简单可解释,缺点在于需要对业务特征有较深理解,特征工程比较费力,但从应用角度出发,无论是 lr、ffm 亦或是较新的 wide& deep 等模型,特征挖掘都是极其重要的一环。因此在首先基于 lr 模型的基础上,核心工作就是基于业务理解并发掘特征。以下是排序模型的整体推荐架构。图 2:ltr 整体架构
1 日志过滤
推荐日志详细打印了每次推荐请求的参数信息和返回信息,如屏数、请求个数、设备信息、位置信息、返回的推荐结果。推荐日志需要尽可能的考虑后期可能使用到的特征,并做好充分的记录。将推荐日志与曝光日志进行第一次 join,过滤掉未曝光即用户没有看到的推荐 item,这部分样本没有参考意义,可以省略;第一个 join 后的结果与点击日志 join,即可以得到每条样本的 label(0/1: 未点击 / 点击)。两次 join 需要根据请求时间、userid、itemid 三者进行 inner join,确保数据准确。日志过滤后生成的每条样本信息如下:
[请求时间、曝光时间、点击时间(如果有)、userid、最近的点击 item 列表、最近曝光的 item 列表、itemid、召回策略、屏数、曝光顺序位置、地理位置、设备信息]–> 点击 label。
2 特征工程
经过 1)的样本缺少足够的特征,我们需要补充 user 和 item 端的特征。该部分特征需要离线挖掘并提前入库。总结后的可使用特征种类大致如下:
特征种类 User 特征:手机型号、地域、图文曝光 / 点击总数、视频曝光 / 点击总数、图文点击率、视频点击率,最近 1、2、3 天图文视频点击数、最近点击时间、最近一次点击是图文还是视频、一二级类别点击率、标签偏好,类别偏好、最近 16 次点击的素材分布、最近 16 次点击 item 的平均标题向量、曝光时间、点击时间等;
item 特征:itemid、类别、总体点击率、最近一周点击率、图片个数、来源、类型(图文还是视频)、发布时间、标题向量、召回策略、点击反馈 ctr 等;
context 特征:屏数、曝光顺序位置、请求时间段等;
交叉特征:用户对 item 类别的一二级类别点击率、用户对 item 标签的偏好、用户对 item 素材类型的曝光、点击次数和点击率、最近 16 个点击 item 与预测 item 标题向量的余弦相似度、相似度最大值等。
交叉特征对于 ranking 特别重要,核心在于逻辑回归函数中,如果与预测 item 无关的特征不会对 item 的排序产生影响,只有 item 特征或者与 item 交叉的特征才会对排序有实质影响, 因为其他特征对任何待预测 item 的打分贡献是一样的。
我们没有使用 bagof word 模型来表示标题,因为这非常稀疏,而是采用标题中关键词的 word2vec 向量组合生成标题表示,使用词向量来表示标题极大减少了特征规模,实现上比较方便。标题向量同时需要归一化成单位向量,单位向量的余弦相似度即两个向量的内积,这个优化显著提高了 ltr 在线模块的性能。
我们将所有特征按类型划分为离散型、连续型、向量型三种类型。如 item 类别就是一个离散型特征、item ctr 就是一个连续性特征、标题向量就是一个向量型特征。对于每种特征,其处理方式都会不太一样,对于离散型一般直接根据离散值做 feature name,对于连续值我们部分参考 youtube wide & deep 论文中的等频归一化方法,简单来说加入 ctr 特征需要等屏成 10 个特征,即将 ctr 值按照分布取其 10 等分点,这 10 等分点就定义了 10 个区间,每个区间的样本数都占 10%。需要注意的是,ltr 在线部分需要 hardcode 写死这 10 个区间来提高特征离散化的效率。
由于离线和在线都会需要 User 和 item 端特征,我们在 hive 数仓和 ssdb 集群总中都存储一份,离线负责 join hive 表,在线负责读取 ssdb。
3 模型训练与评估
经过特征工程后,训练数据按照 libsvm 格式进行打印。使用一天的训练数据的情况下,整个特征空间规模约为 30 万维左右。模型训练采用 sklearn 的 logistic regression 模型进行训练,方便 dump 和 load 模型,我们采用了 lbfgs 算法来进行训练,lbfgs 是一种拟牛顿法,不同于随机梯度下降,lbfgs 总是朝着最优化梯度方向进行迭代。
简单起见,我们使用 N - 2 天前的日志做训练,N- 1 天前的日志做评估,需保证两部分日志的用户群体是一致的,我们再做 ab 测试的过程中,不能训练数据用的是 1 号桶,评估数据用的是 2 号桶。
实际过程中,我们采用 1500 万条样本做训练,300 万条样本做评估,训练完成后离线 auc 为 0.79-0.8 区间内,在线 auc 为 0.75-0.76 区间内,存在一定差距。关于 auc 可以自行参考技术文章,简单来说 auc 就是衡量模型将正样本排在负样本前面的概率,即排序能力。
4 在线服务于评估
我们的最终目的是要在线上流程产生收益,我们采用 rpc 搭建了一个 ltr 在线服务,负责接收 online 的 ltr 请求。推荐 online 在召回各个策略的结果后,会将 userid、预测的 itemid 列表、context 等信息传给 ltr online,ltr online 打分后返回。我们对 ltr online 做了充足的优化,包括标题向量的单位化、ssdb 性能优化、特征离散化的优化,显著提高了性能,对 200-300 个 item 打分的平均响应时间控制在 100ms 以内。
模型不仅需要离线评估,还需要在线评估,在线评估即评估在线样本的 auc,recommend log 中记录了 ltr score,因此可以方便的计算在线 auc。计算在线 auc 的目的是为了验证离线效果提升和在线效果提升的同步性。
5 业务效果的提升
我们在测试组上线 ltr 逻辑后,在点击率指标上相比原算法取得了明显的提升。如下图所示:
可以明显看出上线后,基于点击率目标的 ltr 对于天级点击率的提升是非常明显的。
问题探讨
1 单机训练大规模样本
由于选取的样本数较大,1000-2000 万的规模,简单增大样本数可以显著提高 auc,在我们的场景上在往上增加 auc 就似乎增加不明显了。这么大的训练样本单机训练的话显然只能用稀疏矩阵的方式来存储样本。Scipy 的 cs_matrix 就是非常好的选择,由于 sklearn 的转载 cs_matrix 时数组下表采用 int,故最大空间只能到 20 亿,显然 2000 万样本 * 每个样本的平均特征数远远大于 20 亿,因此我们探讨了 cs_matrix 如何加载大规模数据的方法,最终我们参考 liblinner 工具包中加载 libsvm 格式数据的代码,当然 libliner 加载方式也存在问题,经过修改调试后,成功的完成了训练数据的加载,具体问题和解决方式可以参考 https://blog.csdn.net/wh_spri…。
2 样本和特征的时间正交
样本和特征数据的时间正交即两者在时间上不应该有交叉。举个例子,前期我们在 join 用户端特征时,用的是 1 号的训练样本数据,用户离线特征用的也是 1 号的数据,这样两者就会存在交叉,即 user 点击了一篇英超新闻,同时 user 画像中也偏好英超标签(由 1 号的点击生成),这样就会导致 auc 偏高,当然这种偏高就是虚假偏高,模型的泛化能力是很差的。在实际过程中,遇到过几次 auc 突然偏高的情况,发现大部分都是由于没有保证数据正交性导致的。
在整个流程中,数据的时间正交总是被不断强调。无论是 user、item 特征还是样本数据,比如训练样本中一个特定 user 的样本按照时间排序是 (s1,s2,s3,s4,s5,s6,s7,s8,s9,s10),使用 s1-s8 训练,s9,s10 评估是合理的,而使用 s3-s10 训练,s1,s2 则显然是不合理的。
3 预估点击率和实际点击率的一致性
点击率预估基本要求就是预估的点击率要精准,如果只考虑位置的 ranking,可以不用过分关心预估的绝对值,但实际情况下还是需要尽量保证预估分数的合理性,往往预估精准的 ctr 具有很大的参考价值。
前期我们预估的点击率一直偏高,平均打分甚至达到了 0.5,经过排查在于训练模型的参数设置不合理,错误的将 LogisticRegression 的 class_weight 参数设置成 balanced,导致损失函数中正样本预测错误的代价增大,导致模型偏向正样本,从而导致预估的点击率极度偏高,修复成默认值预估点击率下降明显,接近实际值。具体参考:https://scikitlearn.org/stabl…。
同时为了保证训练数据和在线服务完全一致性,我们调整了推荐的整体架构,更多的直接在推荐 online 模块负责召回和排序,这样又可以进一步保证预估点击率和实际点击率的一致。
4 重要特征和 case 排查
lr 模型可以方便 debug 每个样本的各个特征和权重,权重高的特征显然更加重要。如果你觉得重要的特征权重过低了或者不重要的特征权重过高了,也许就要思考为什么了。以下是一个样本的 debug 信息。例如我们发现 ctr 特征权重特别高,假设一个新 item 曝光了一次点击了一次,点击率是 1.0,乘上 ctr 的权重上这个 item 极易被排到最前面,因此我们考虑 ctr 的置信度,考虑对 ctr 类特征做了平滑。
值根据实际情况设定。
总结
本文详细介绍了达观数据的推荐引擎架构和在资讯信息流推荐场景中利用 ltr 排序显著提高业务指标的实践和经验。由于篇幅有限,关于非线性的 ffm、wide & deep 没有做详细介绍,而这也是算法团队一直继续投入研究的重点。
关于作者文辉:达观数据联合创始人,主要负责达观数据推荐系统、爬虫系统等主要系统的研究和开发。同济大学计算机应用技术专业硕士,曾就职于盛大文学数据中心部门,负责爬虫系统、推荐系统、数据挖掘和分析等大数据系统的研发工作,在爬虫系统、Hadoop/Hive、数据挖掘等方面具备充足的研发和实践经验。