关于推荐系统:云音乐预估系统建设与实践

3次阅读

共计 6921 个字符,预计需要花费 18 分钟才能阅读完成。

作者:小人物

1. 什么是预估零碎?

    预估零碎的外围工作是实现模型计算,能够认为模型就是一个函数(举例:f(x1, x2)= ax1 + bx2 +c)。其中参数 a、b、c 是通过模型训练得出的权重值,自变量 x1 与 x2 就是特色,模型计算就是应用自变量 x1 与 x2 求解的过程。
    因而预估框架须要做的是:构造函数输出(特色),计算函数失去后果(模型计算)。即:特色抽取、模型计算

  • 特色抽取
    特色是对某个行为相干信息的形象表白。举荐过程中某个行为信息必须转换成某种数学模式能力被机器学习模型所学习。为了实现这种转换,就必须将行为过程中的信息以特色的模式抽取进去。
  • 模型计算
    集成机器学习库,加载机器学习平台训练出的模型,依照模型构造执行既定的矩阵、向量等计算。

2. 预估零碎的设计思考

2.1 通用计划的局限

    业内常见的计划是特色解决服务 + tfserving 模型计算服务,这种计划是将特色解决和模型计算分服务部署。在特色数量不大时能够提供较好的反对,但特色数量一旦变多,就会呈现显著的性能问题,次要是因为特色要跨网络(跨过程)传递给底层机器学习库,这样就会带来屡次编解码与内存拷贝,会造成微小的性能开销。

2.2、新零碎的思考

依据业务需要,并汲取通用计划存在的优缺点,咱们构建了新的高性能预估零碎,该零碎领导思路是:

  • 高性能:谋求特色抽取与模型计算高性能,提供大规模特色抽取和大规模模型计算的能力。
  • 形象与复用:零碎分层设计,分层复用;线上线下特色抽取逻辑复用;特色抽取提出算子概念,建设算子库,实现特色复用。
  • 实时化:实现样本采集实时化、模型训练实时化和模型更新实时化。
  • 可扩展性:特色抽取提供自定义算子接口,不便自定义算子的实现;模型计算提供集成多种机器学习库的能力。

下图展现了云音乐预估零碎架构:

3. 预估零碎的建设过程

一个优良的预估零碎须要解决如下三个问题:

  • 如何解决特色和模型的高效迭代?
  • 如何解决预估计算的性能问题?
  • 有没有机会通过工程伎俩晋升算法成果?

3.1 特色与模型的高效迭代

3.1.1 零碎分层设计

零碎分层设计,仅裸露下层接口层,不同业务间齐全复用中间层和底层实现,较大水平缩小代码开发。

  • 底层框架层:该层提供异步机制、工作队列、session 治理、多线程并发调度、通络通信相干的逻辑、内部文件加载与卸载等
  • 两头逻辑层:封装查问治理、缓存治理、模型更新治理、模型计算治理等性能
  • 下层接口层:依照执行流程提供 HighLevel 接口,算法在此层实现逻辑

下图展现了框架分层设计:

3.1.2 配置化实现模型计算全流程

依照执行流程,把处理过程分成三个阶段,别离是:数据查问、特色抽取、模型计算。在框架中对每个阶段进行抽取和封装,并提供配置化描述语言来实现各个阶段的逻辑表白。

(1) 数据查问

通过 XML 配置表名、查问 key、缓存工夫、查问依赖等就能实现特色数据的内部查问、解析、缓存全流程。如下所示:

<feature_tables>
    <table name="music-rec-fm_set_action" alias="trash_song" tag="user" key="user_id"/>
    <table name="music_fm_dsin_user_static_ftr_dpb" alias="u_static" tag="user" key="user_id"/>
    <table name="alg_song_ua_rt" alias="u_rt_red" tag="user" key="user_id" subkey="1"/>
    <table name="fm_dsin_song_promoted_info_feature_dpb_mdb" alias="item_promoted" tag="item" key="item_id" cache_time="7200" cache_size="800000" query_type="sync"/>
    <table name="fm_dsin_song_static_feature_dpb_mdb" alias="item_static" tag="item" key="item_id" cache_time="7200" cache_size="800000" query_type="asyc"/>
</feature_tables>

特色数据查问配置化带来了开发效率的大幅晋升,用几行配置就实现了以往须要大量编码能力实现的特色查问性能。

(2) 特色抽取

开发特色抽取库,封装特色抽取算子,开发特色计算 DSL 语言,通过配置化实现整个特色抽取过程。如下所示:

<feature_extract_config cache="true" log_level="3" log_echo="false" version="2">
    <fea name="isfollowedaid" dataType="int64" default="0L" extractor="StringHit($item_id, $uLikeA.followed_anchors)"/>
    <fea name="rt_all_all_pv" dataType="int64" default="LongArray(0, 5)" extractor="RtFeature($all_all_pv.f, 2)"/>
    <fea name="anchor_all_impress_pv" dataType="int64" default="0" extractor="ReadIntVec($rt_all_all_pv, 0)"/>
    <fea name="anchor_all_click_pv" dataType="int64" default="0" extractor="ReadIntVec($rt_all_all_pv, 1)"/>
    <fea name="anchor_all_impress_pv_id" dataType="int64" default="0" extractor="Bucket($anchor_all_impress_pv, $bucket.all_impress_pv)"/>
    <fea name="anchor_all_ctr_pv" dataType="float" default="0.0" extractor="Smooth($anchor_all_click_pv, $anchor_all_impress_pv, 1.0, 1000.0, 100.0)"/>
    <fea name="user_hour" dataType="int64" extractor="Hour()" default="0L"/>
    <fea name="anchor_start_tags" dataType="int64" extractor="Long2ID($live_anchor_index.start_tags,0L,$vocab.start_tags)" default="0L"/>
</feature_extract_config>

特色抽取库的会在上面具体介绍。

(3) 模型计算

对模型加载、参数输出、模型计算等进行封装,通过配置化实现模型加载与计算全流程。具体特点如下:

  • 预估框架集成 tensorflow core,反对多种模型状态。
  • 反对多模型加载,反对多模型交融打分。
  • 反对多 buf 模型更新,主动实现模型预热加载。
  • 反对多种格局的参数输出(Example 和 Tensor),内置 Example 结构器和 Tensor 结构器,对外屏蔽简单的参数结构细节,简略易用。
  • 扩大多种机器库,例如 paddle、gbm 等。
<model_list>
    <!-- pb 模型,tensor 输出,指定 out_names -->
    <id model_name="model1" model_type="pb" input_type="tensor" out_names="name1;name2" separator=";" />

    <!-- savedmodel 模型,tensor 输出,指定 out_names -->
    <id model_name="model2" model_type="mdl" input_type="tensor" out_names="name1;name2" separator=";" />
    
    <!-- savedmodel 模型,example 输出,指定 out_aliases -->
    <id model_name="model3" model_type="mdl" input_type="example" out_aliases="aliase1;aliase2" separator=";" signature="serving_default" />
</model_list>

通过上述配置,能够实现模型加载,模型输出结构,模型计算全流程。用几行配置实现了之前须要大量编码能力实现的模型计算性能。

3.1.3 封装特色抽取框架

特色抽取目标是将非标准的数据转换成规范的数据,而后提给机器学习训练平台和在线计算平台应用。特色抽取分为离线过程和在线过程。

下图展现了什么是特色抽取:

3.1.3.1 特色抽取存在哪些个问题?
  • 一致性难以保障

线上抽取与线下抽取因平台不同(语言不同)须要开发多套代码,从而会引发逻辑不统一的问题。一致性问题一方面会影响算法成果,另一方面一致性校验会带来昂扬工程落地老本。

  • 开发效率低

特色生产和特色利用要对应多套零碎,新增一个特色要改多处代码,开发效率低下。

  • 复用难

框架不足对复用能力的撑持,特色计算逻辑差别大,导致团队间数据复用难,资源节约、特色价值无奈充分发挥。

3.1.3.2 如何解决上述问题?
(1) 形象算子

提出算子概念,将特色计算逻辑封装成算子。为了实现算子形象,首先必须定义对立的数据协定(标准化算子的输出与输入),这里咱们采纳动静 PB 技术,依据特色的元数据信息,采纳对立的形式解决任意格局的特色。
并且建设平台通用算子库和业务算子库,实现了特色数据和特色计算的复用能力。

(2) 定义特色计算 DSL 语言

基于算子,咱们设计了特色计算示意语言 DSL,通过该语言能够反对多阶抽取和抽取依赖,通过根底算子的多种组合能够实现简单的特色计算逻辑,提供丰盛的表达能力。DSL 表达式如下:


(3) 解决逻辑不统一问题

    呈现特色计算逻辑不统一根本原因是因为特色计算分为离线过程和在线过程,离线特色计算个别是在 Spark 平台和 Flink 平台(应用 scala 或 java 语言),而在线特色计算个别 c ++ 环境。平台和语言的不一样,就导致雷同的特色计算逻辑要开发多套代码,多套代码间就可能呈现逻辑不统一的问题。
    为了解决上述问题,就必须实现特色解决逻辑多平台兼容。思考到在线程计算平台应用的是 c ++ 语言,以及 c ++ 的高性能和多平台(多语言)运行的兼容性,于是特色解决外围库采纳 c ++ 语言实现,对外提供 c ++ 接口(反对在线计算平台)、java 接口(反对 spark 和 flink 平台)。并提供一键编译的形式,编译后生成 so 库和 jar 包,不便集成到各个平台中运行。

下图展现了特色抽取跨平台解决方案:

    通过封装特色抽取库,提供特色抽取跨平台运行能力,实现一次代码编写,多平台(多语言)运行,从而解决多平台特色抽取逻辑不统一的问题;通过算子封装、算子库建设,实现了特色与特色计算的高度复用;通过开发特色计算 DSL 语言,实现配置化开发特色抽取的能力。基于上述建设,极大的晋升了咱们在特色抽取上的开发效率。

3.2 高性能预估计算

    预估服务是计算密集型服务,对性能要求极高,尤其在解决简单特色和简单模型时,性能问题体现的尤为突出,咱们在开发预估零碎时有很多性能上的思考和尝试,上面将具体介绍。

3.2.1 无缝集成高性能机器学习库

    后面介绍过传统预估计划是将特色解决服务和模型计算服务分过程部署,这样就会波及到特色的跨网络传输、序列化、反序列化和频繁且大量的内存申请和开释,尤其是举荐场景特色量大,会带来较大的性能开销。下图展现了服务部署和特色传输的过程:

下图展现了传统计划服务部署和特色传输:

    为了解决上述问题,在新的预估零碎中,将高性能机器学习库 TF-core 无缝集成到预估零碎内,即特色解决和模型计算同过程部署。这样能够实现特色指针化操作,防止序列化、反序列化、网络传输等开销,较大水平晋升性能。

下图展现了新预估零碎特色传输:

3.2.2 全异步架构

    为了晋升计算能力,咱们采纳了全异步架构设计。计算框架异步解决、内部调用无阻塞期待,最大水平将线程资源留给理论计算。机器过载时,会主动抛弃工作队列中超时的工作,防止机器 / 过程被拖垮。

下图展现了异步架构设计:

3.2.3 多级缓存

    举荐场景的申请由一个 User 和一批候选 Item 组成(50-1000 个不等),而热门 Item 的数量仅在十万或者百万级别,Item 的特色分为离线特色(小时级或者天级更新)和实时特色(秒级或者分钟级更新)。基于举荐场景的申请特点,Item 特色齐全能够通过申请触发的形式缓存在过程内,在超时工夫内能够间接应用缓存中的内容,防止有效的内部查问、特色解析和特色抽取。不仅能大大降低内部存储的查问量,也能大大降低预估服务的资源占用。
    目前预估服务在特色数据查问和特色抽取计算缓环节应用了缓存机制。在缓存设计上采纳了线程池异步解决、分多个桶保留缓存后果缩小写锁碰撞等措施晋升缓存查问性能。并且提供多种特色查问及缓存策略:

  • 同步查问 &LRU 缓存
    实用特色重要且特色规模宏大的特色(性能低)

    • 异步查问 &LRU 缓存

    实用于特色不太重要且规模宏大的特色(性能中)

    • 特色批量导入

    实用与特色规模在千万以下的特色(性能高)

下图展现了预估零碎缓存机制:

3.2.4 正当的模型输出倡议

    针对 Tensorflow 的模型输出,Tensor 输出的性能会优于 Example 输出的性能。下图一展现了 Example 输出的 timeline 图,下图二展现了 Tensor 输出的 timeline 图。能够显著的发现应用 Example 作为模型输出时,在模型内有较长的解析过程,并且在解析实现前,前面的计算 op 无奈并发执行。

下图展现了 Example 输出的 Timeline:

下图展现了 Tensor 输出的 Timeline:

(1) 为什么应用 Tensor 输出能够进步性能?

次要是能够缩小 Example 的序列化和反序列化,以及 ParseExample 的耗时。

(2) 相比 Example,用 Tensor 输出存在哪些问题?

Tensor 结构逻辑逻辑简单,开发效率低
须要关注 Tensor 维度细节,如果 Tensor 扭转(增加、删除、维度扭转等)都须要从新开发 Tensor 结构代码

为了兼顾 Tensor 输出的高性能和 Example 输出的开发便捷性,在预估零碎中开发了一套 Tensor 结构器,即保障了性能,也升高开发难度。

3.2.5 模型加载与更新优化

    在模型的加载与更新上尝试多种优化策略,其中包含:反对模型双 buf 热更新、反对模型主动预热加载、旧模型延时卸载等办法晋升模型加载性能。通过上述优化形式可实现大模型分钟级热更新,线上申请耗时无抖动,为模型实时化提供根底。

下图展现了模型主动预热计划:

3.3 通过工程伎俩晋升算法成果

预估零碎除了要保障高可用、低延时等外围因素外,是否通过一些工程伎俩带来算法成果的晋升呢?咱们从特色、样本、模型等维度做了一些思考和尝试。

3.3.1 传统预估计划中存在哪些能够优化的点?

(1) 特色穿梭的问题

    传统的样本生产方式,存在特色穿梭的问题。样本拼接是将用户行为和特色进行拼接,用户行为是用户在 t - 1 时刻对预估后果产生的行为,正确的做法是 t - 1 时刻的用户行为与 t - 1 时刻的特色进行拼接,然而传统做法是只能用 t 时刻的特色进行拼接,即导致 t 时刻的特色呈现在 t - 1 时刻的样本中,从而导致特色穿梭的产生。呈现特色穿梭会导致训练样本不准,从而导致算法成果降落。

(2) 模型实时性不够

    传统举荐零碎是天级别更新用户举荐后果,实时性差,无奈满足实时性要求很高的场景。例如直播中主播开播状态、直播内容变动、业务环境调整等都须要举荐零碎能实时感知。

3.3.2 如何解决上述问题?

    为了解决上述问题,咱们开发了模型实时化计划。该计划是基于预估服务,将预估时的状态(申请内容、过后应用的特色等)实时保留到 kafka,并通过 traceid 与 ua 回流日志做关联。这样就能确保用户标签与特色能一一对应,从而解决特色穿梭的问题。
    并且该计划也实现样本流秒级落盘,为模型增量训练提供了样本根底。在训练侧实现了模型增量训练,并将训练产出的模型实时推送到线网服务。具体实现如下如图:

下图展现了模型实时化计划:

通过模型实时化计划,实现样本实时保留,从而解决特色穿梭的问题;实现模型分钟级训练和更新,能让模型及时捕获新动静和新热点。从而晋升算法成果。

本文公布自网易云音乐技术团队,文章未经受权禁止任何模式的转载。咱们长年招收各类技术岗位,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 staff.musicrecruit@service.ne…。

正文完
 0