Facebook 在数十亿用户规模场景下 GBDT 算法性能优化 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
feiyang21687
V2EX    机器学习

Facebook 在数十亿用户规模场景下 GBDT 算法性能优化

  •  
  •   feiyang21687 2017-03-29 20:46:46 +08:00 2524 次点击
    这是一个创建于 3117 天前的主题,其中的信息可能已经有所发展或是发生改。

    原文: https://code.facebook.com/posts/975025089299409/evaluating-boosted-decision-trees-for-billions-of-users/

    下面由 Google 翻译,看不懂别问我。

    Facebook 应用中的诸多功能都是通过机器学习和排序模型来优化用户体验,例如预测哪些通知应该发给用户,用户更喜欢哪些 story ,哪些兴趣 page 是你可能会关注。为了能够挖掘出用户感兴趣的内容,高质量的机器学习模型是至关重要的。 Facebook 的机器学习排序模型会根据非常多的实时反馈信号,动态决定最佳的排序结果。例如在通知预测这个场景中, Facebook 的模型会感知用户是否点击过相似的通知内容,以及通知中的 story 已经获得了多少 like 。由于每一条新产生的通知都需要根据这些实时数据进行预测预测,所以对于预测算法性能要求非常高。

    越复杂的模型,预测的精准度会越高,但同时越消耗 CPU 的计算能力,预测所需要的时间也会越长。这些条件会限制系统的吞吐能力,也就无法对所有的候选集进行预测。如果能够优化算法模型的性能,使用同样的计算资源就可以预测更多的候选集,预测的精准度也就相应提高。

    下面内容会对比多种 GBDT 算法实现的性能差异,已经在 C++算法实现上的一些优化经验。

    决策树模型

    决策树通常被用作预测模型,原理上是建立对象的观测结果与属性的映射关系。因为决策树具有非线性,速度快等特点,所以在机器学习、数据分析、数据统计等领域决策树是最常见的预测实现算法。在决策树的结构中,叶子节点表示分类标签,中间节点表示这些特征之间的关联性。

    虽然决策树模型非常有效,但是训练数据轻微的一个变动就会生成一颗完全不同的决策树,结构差别非常大。这个问题可以通过梯度提升技术来解决。也就是说每次分类都将上一次分错的数据权重提高一点再进行分类,生成一个新的树,如此反复,最终得分就是所有叶子节点得分权重的加权。

    一般来说训练复杂的模型需要的数个小时,所以模型不会频繁更新。但在 Facebook 的规模下,模型需要经常更新,并且预测耗时要求是毫秒级。 Facebook 的很多后端服务是 C++实现的,因此我们利用 C++的一些特性,并作出了改进,实现更有效的模型,使用较少的 CPU 计算资源

    下图中是一个简单的决策树模型,其中特征包括:

    • A 用户一天点击通知的次数
    • 通知中 story 的 like 数
    • A 点击通知的总数

    通过遍历树中不同节点的特征值,得到用户点击这个通知的概率。

    扁平化实现

    决策树的初始实现方式是通过简单的二叉树指针实现。这种实现方案不是很高效,主要原因是节点的数据在内存中不是连续存储的,所以执行时 CPU Cache Miss 会比较严重。另一方面,决策树的结构通常是完全二叉树(每个节点要么有两个孩子要么没有),这种结构可以通过数组形式实现,以便让节点数据分布在一片连续的内存空间里,同时省去了指针的开销。 Facebook 对比了两种实现方式的性能差异。

    编译实现

    每个二叉树可以表示为一个复杂的三元表达式,它可以被编译并链接到可以直接用于服务的动态库( DLL )。 请注意,我们可以实时添加或更新决策树模型,而无需重新启动服务。

    Facebook 还利用了 C++中的LIKELY / UNLIKELY注释。 这些注释是编译器发出指令的方向,这些指令将导致分支预测有利于跳转指令的“可能”侧。如果预测是正确的,跳转指令只占用零个 CPU 周期。 因为训练数据和实际评估数据的分布不会有太大变化,所以可以根据实际样本或离线分析计算每个分支的概率。下面是上图中决策树的实现样例代码:

    int tree(double* F) { return LIKELY(F[0] < 1) : (UNLIKELY(F[1] > 10) ? (F[2] > 50 ? 0.1 : 0.05) : 0.01) ? (F[2] > 100 ? 0.5 : 0.2); 

    分片预测

    在典型的排名预测中,需要逐一对每个实例的特征向量进行预测。 例如,为一个用户预测 1000 个不同的潜在候选人的排名,并选择最相关的候选人。

    简单的实现可以迭代所有候选,每次对一个候选计算一次完整的预测。 但是通常情况下,模型和所有候选特征数据不能完整的加载到 CPU 高速缓存中。参考 GBDT 的思路,实际上可以将模型分解成树的范围(前 N 个树,然后是 N 个树等),使得每个范围都足够小以适应 CPU 缓存。预测的实现可以按树的范围进行拆分:所有样本对一个小范围内的数据进行预测,而不是每个样本预测所有的树。 这样就能够将小范围的树和所有候选的特征向量全部加载到 CPU 缓存中,并在下一次迭代中只是替换树集。 这减少了高速缓存未命中的数量,并且使用块读 /写而不是 RAM 。

    此外,有多个模型时,例如,用户点击,喜欢,评论的模型,这种方式有助于将所有特征向量保存在 CPU 缓存中并逐个评估模型。

    另一个折衷方案通过前 N 个树的排名后,基于增强算法的性质,直接丢弃排名最低的候选人。这样进入下 N 个树预测的候选集就会变小,这可以提高延迟,但精度略有下降。

    公共特征

    有时有些特征维度是完全相同的。例如像上面例子中,对于特定的一个用户,所有候选数据的特征数据[F[0], F[1], F[2]]如下:

    [0, 2, 100] [0, 0, 100] [0, 1, 100] [0, 1, 100] 

    其中 F[0]和 F[2]对于所有候选是完全相同的,这种情况只对 F[1]特征进行预测即可,以此缩短预测时间

    分类特征

    大多数 ML 算法都使用二叉树,这可以扩展到k-ary 树。另一方面,有些特征是不可比的分类数据。例如,国家作为一维特征,并不能说某一个值小于等于"US"(或者小于等于 US 对应的枚举值)。如果树的深度足够深,这种比较可以通过逐层二值比较来实现。 Facebook 的做法是通过检查当前要素是否属于一组值的可能性,来实现分类特征的比较。

    学习方法并没有太大变化,仍然尝试找到最好的子集来分解

    算法上并没有太大变化 - 尝试找到最好的子集拆分边界,而且计算的速度非常快。 基于集合的大小,我们使用 C ++集合的in_set操作或者直接使用if判断逐一比较。这种实现方式可以减少模型尺寸,并有助于收敛。

    对比结果

    Facebook 训练了一个含有 256 颗树的预测通知点击率的 GBDT 模型,每棵树含有 32 个叶子节点。对平均 1000 候选特征预测对比 CPU 使用率。批量计算因子 N ,根据 CPU 的 L1/L2 缓存大小做了特殊的调优。可以看到性能提升的效果:

    • 简单编译实现模型: 2x
    • Likely/Unlikely 优化编译实现模型: 2.5x
    • Likely/Unlikely 优化编译,分片,分类特征优化: 5x

    在不同的模型参数下,性能提升数据基本一致( 128 或者 512 颗树, 16 或者 64 叶子节点)

    总结

    CPU 使用率的改善和性能的优化,使 Facebook 能够使用相同的资源量的情况下,可以同时预测更多的候选以及增加模型复杂度。 这种方法已经应用于 Facebook 上的几个排名模型,包括通知过滤, Feed 排名以及关系推荐和 Page 推荐。 Facebook 机器学习平台不断发展,通过更精确的算法结合更高效的工程实现,不断改进排名系统,为数十亿人提供最佳的个性化体验。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2924 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 12:52 PVG 20:52 LAX 05:52 JFK 08:52
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86