Fork me on GitHub

网易新闻推荐工程优化 - 特征算子篇

希孟 网易 稿

网易新闻推荐工程优化 - 特征平台篇

从2019年年中起,我们针对网易新闻个性化推荐的系统架构做了更新迭代,涉及的工作包括特征/样本的平台化、pCTR推理服务的性能优化、大规模分布式训练的探索,以及目前正在进行的推荐中台化改造等。

我们梳理了之前的部分工作,并将涉及特征、推理、训练的部分做了一个系列文章,希望和大家多多交流。本文主要描述网易新闻的特征算子侧。

1 项目背景

在推荐系统中,早期存在众多特征工程方法,其中最主要的是基于程序代码的开发方法。在这种方式下,特征变更时依赖程序的开发升级和部署,流程周期长,难以支持特征工程的频繁迭代,另外特征工程处理情况难以了解,可读性较差,通用性也较差,无法在不同业务场景下复用。同时基于程序开发的特征工程方法使用的数据类型有限,对非数据化数据类型支持差,难以在实际应用中支持复杂的数据类型。在实际应用中,特征工程处理特征多,运算效率要求更高,针对不同的算法模型,特征格式也需要更灵活。

针对这些问题点,我们设计一套基于特征配置的特征算子库,以解决现有特征工程方法中存在的特征工程策略可读性差、通用性差、特征调整迭代周期长、计算冗余度高等问题,能够在灵活性、高效性、复用性上有很好的表现能力。

2 特征算子设计

2.1 算子设计

在我们的业务场景下,我们对训练样本需要的特征进行汇总分析,并对依赖的原始数据进行梳理,整理出由原始数据到特征样本之间所有的变换、组合关系,我们称之为特征算子。例如原始数据用户年龄“age”是一个具体的值,但是在训练过程中我们使用的是全局用户年龄统计之后的分桶数据,将用户年龄0-100,按照不同比例划分区间,得到该用户的年龄分桶值“age_bucket”作为最终特征值,因此这里执行的age到age_bucket的分桶变换,抽象成bucket算子。类似的,我们还抽象出max,min,topK,cross,logN,split,norm等算子,一共有30多个,后续在业务适配过程中,也做了一些定制型的算子,并且也尽可能做到可复用。

2.2 特征配置设计

抽象算子之后,我们将进行特征描述和组合。我们选择json作为配置描述语言。例如{“feat”: “a = op(b)”, “param_A”: “val_A”, “paramB”: “val_B”},表示使用op对原始数据b变换处理得到特征a,变换过程中需要两个参数。同时,我们的描述语言支持嵌套关系,例如“a = opA(b, opB(c, d))”,表示一个特征变换的输出可以作为另一个特征变换的输入,并最终生产出特征a。另外一个层面,在网易新闻业务场景下,我们解决的个性化推荐均是两个item之间的关系,这里我们的item分别是user和doc,因此我们对user和doc侧的特征描述进行区分表示。下图展示了一个简单的配置样例。

图片

每项中"feat"关键字对应的内容表示一次特征工程运算,描述包括特征算子,特征输入,特征输出,算子参数。特别地,在{"feat": "i_cate"}项中没有直接写出特征运算单元的名称,这种情况会使用默认的特征算子keepOp,该算子将输入属性直接作为特征输出,由此这项和 {"feat": "i_cate=keepOp (i_cate)"}含义完全相同。另外,配置中"feature_format"关键字对应的 内容为待输出的特征格式,用来支持下游训练场景。目前我们已经支持浅层模型的哈希格式,深层模型的原始格式、哈希格式等,实现可以配置化,并可动态调整。我们真实业务场景下的配置如下图所示:

图片

在我们规定这样的书写语法之后,算法同事的工作就变得更加直接简单,他们描述自己的特征配置文件。特别地为了提高书写配置文件的便捷性,我们提供了若干辅助工具,包括原始数据列表和说明(每个原始数据的计算逻辑说明,样例数据,数据类型)、特征算子使用说明和样例(特征算子介绍,功能说明,使用方式,输入个数,输出个数,参数定义,支持的数据类型,每个数据类型下的样例)、特征配置调试工具(使用原始样本直接生成特征样本)、特征输出展示,以及基础数据的监控工具,包括原始数据分布、特征数据分布等,用来辅助算法同事书写、校验、评估自己的特征配置文件,以此提高用户特征配置项开发的开发效率和准确率。我们机器学习平台侧将更多聚焦特征配置的校验、解析、编译、执行、效率提升等工作上。

图片

2.3 特征配置校验

特征配置校验,可以对特征配置进行语法校验和语义校验。当配置内容出现单词拼写错误、语句拼写错误、语句格式错误时或者书写的特征算子不存在时,确定配置项存在语法错误;当配置内容依赖原始数据在候选数据或当前配置行前面配置输出中找不到时,特征算子的输入个数、输入格式、输出个数和内部定义不匹配时,确定存在语义错误。当确定配置项存在语法错误或语义错误时,提示配置出错位置、错误原因和错误类型,方便算法同事针对性解决。

2.4 特征配置解析

特征配置解析,主要对特征配置每一个配置项解析,解析出该配置文件的各个字段和关键参数,例如用户侧特征描述、文章侧特征描述、特征格式、特征空间、标签等。将每个配置项解析成可执行的代码opNode,包含输入、输出、算子、参数,调用起compute方法即可生产特征。只需要按照顺序对配置项进行一一执行,就能完成整个过程。

3 执行优化

3.1 编译优化

在真正业务场景下,特征配置项之间通常存在依赖关系,并且存在指定配置项失效的配置,因此在执行过程中存在冗余计算,降低执行效率的问题。为了解决这个问题,提升执行效率,我们对解析出的特征配置进行编译优化。

主要工作是将配置项的依赖关系进行整理成图结构,并根据特征输出对图进行剪枝操作。具体来说,根据特征算子的输入、输出依赖关系将特征算子列表转成特征算子依赖关系图,该图为一个有向无环图。然后从依赖图中删除不被最终输出特征依赖的特征算子配置。再次,根据特征算子依赖的最初属性所 属对象的不同切分为不同的子图,例如用户user子图、文章doc子图。例如下图展示:

图片

3.2 图计算优化

在执行过程中,我们使用上述图结构进行更深层次优化抽象,图的每个结点表示一个特征配置,表示特征之间的依赖关系,并且我们对不需要输出的特征配置对应的图结点进行剪枝,减少多余计算。运行时,只需要根据图结点的依赖关系进行遍历计算,即可得到最终的特征数据。

另一方面,在我们业务场景下,用户侧user特征配置项只描述了用户侧特征的计算方法,其中依赖的原始数据均是用户属性,文章侧doc特征配置项描述了文章侧特征的计算方法,其中依赖数据包括文章属性和用户属性、用户特征。因此在执行时,用户user子图和文章doc子图将分开计算,完成用户子图计算之后,再并行计算doc子图,这样能够减少用户子图的重复计算。在实际业务场景下,冗余计算减少量相当可观,并且对性能有一定提升。

3.3 基于hash的图计算优化

目前的业务场景下,浅层模型和深层模型输入的特征数据已经全部使用hash值。因此在特征配置项中需要配置feature_format和hash_size参数。在实际场景下,我们对性能优化时,也做了针对优化。通过java method监控,我们发现字符串的getBytes操作调用量巨大,并且整体耗时较高。这是因为之前的图计算逻辑中,所有的输入数据都是string类型,并且我们根据feature_format在执行完成最后一步进行数据格式转化。由于特征之间的依赖关系,特别是cross算子的大量使用,会导致最后一步格式转化存在大量冗余hash计算,因此string的getBytes调用量巨大,整体耗时较高。

为了解决这一问题,我们根据依赖关系对hash计算进行前置。可以使用下面的样例进行说明。

图片

例如我们有上图类似的简单特征配置(visibility设置为hidden,即gender不作为最终输出),进行特征配置编译优化之后得到下图

图片

很多的特征结果为为string类型,cross_op, map_cosine等 需要多string进行操作,非常耗时,使用图模式后,直接在物料源头进行hash,减少后面op对string类型的操作。我们对算子进行分析梳理,梳理出输入输出必须是字符串的算子和支持hash输出输入的算子,同时对图进行自顶向上重新编译,根据isInputPreHashEnable标记找到最早支持hash的结点,并在此结点之后插入hash算子,将hash计算前置。这样在后续的图计算中,所有的结点输入输出均是数字操作,能够很大的提升性能。重新编译之后的图如下所示:

图片

基于预hash的特征配置图计算,在实际业务场景中性能能够提升45%以上。不仅仅得益于图剪枝操作,更多的是减少字符串之间的操作。对特征配置执行过程的性能优化,我们还做了其他大量工作,包括减少数据格式转换、缓存、预计算等众多举措,在整体优化过程中,性能提升了2倍多。

4 总结

目前基于特征配置的特征算子库已经全面应用在网易新闻个性化推荐排序中,线上正在使用的特征配置有10份以上,支持了个性化排序的绝大多数场景。同时,算法同事对特征的变更、新增、删减都能够基于配置快速生效。迭代效率由之前的天级别,变成现在的十分钟以内,极大的提升了同事的工作效率和算法迭代效率。


本文地址:https://www.6aiq.com/article/1648079760819
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出