何来因果 ?
因果
源于时间的不可逆。
在我们生活的世界, 很多事情一旦发生便不可撤销,例如亲人的去世、商业活动的失败 ...
在发生这些事情的同时, 往往有一些前兆, 例如地震前兆为 动物异常活动
, 井水陡涨陡落
等等,我们迫切希望预知到即将到来的危险,并且提前做好防范。
于是,我们创造出 因果
一词,代指人们对于渴望更早预知将要发生的 事实
。
但是, 请注意 果
的定义一般明确,但 因
可以有丰富的解释。
以地震为例, 动物异常活动
并不是地震的原因
, 事实上 动物异常活动
是地震的级连 结果
。但是观察到 动物异常活动
又确实可以提早防范地震,因为在时间上地震到来晚于 动物异常活动
。
现在我们知道 地壳岩层受力后快速破裂错动
会造成 地震
, 这是一个更合理的因
,因为 地壳岩层破裂错动
在时间上早于 动物异常活动
。
但是,又要发问: 地壳岩层破裂错动
的原因是什么呢 ? 其实这是一个无穷无尽的过程,并且多种原因又会错综复杂交织在一起,不出100次的连续发问就可以回溯到宇宙大爆炸。
但一个原则是: 因
应该与实际意义结合解释,过早的因
不具备实际可操作性, 过晚的因又来不及对果
进行干涉。
综上所属:
因
果
为时序发生的一系列事件,因
必须早于果
因
可以不止一种, 并且寻找原因的征途是没有止境的- 但只要可以发现早于
果
的因
,并且可以及时干涉果
,这样的因
便是有意义的 - 如果时间是可逆的,不存在因果关系
因果关系之梯
图灵奖得主 Judea Pearl
在 《The Book Of Why》 一书中为我们展示了 "因果关系之梯"。
其中分为了三个层级:
- 关联 (association): 也是当前机器学习主要面向的领域, 对于给定的一组变量, 探索目标变量与其余变量的相关性,以进一步进行预测。
- 干预 (intervention): 通过进一步干预, 确定原因, 为了克服
关联
的弊端, 高相关性不一定是因
, 有可能是果
, 而真正的因
不存在于自变量中 - 反事实推断 (counterfactuals): 因为实际情况, 无法执行干预(例如GDP根因分析不能故意去干预降低GDP,损失过大), 需要靠
已有相关知识
进行逻辑推断。
一个最主要的概念区别是: 相关性高
不代表是 原因
一个著名的例子是: 冰激淋销量与犯罪量呈现高度相关,推断: 应该禁止冰激淋销售降低犯罪率。
但是进一步思考: 气温升高时, 人心情浮躁可能才是更可靠的原因。
但这不是问题的终点。气温升高时, 为何人会心情浮躁, 是基因的作用嘛 ? 是否去除掉高温使人浮躁的基因是最终解决方案。
但进一步思考: 高温使人浮躁, 是否是对人的一种保护机制,避免人长期处于对身体有害的温度中。
为了解答上面所有的问题, 需要进行反复的干预
实验,这些实验不会天然存在于自然中。
自然永远是单向演化,而干预
实验实际上是人工引发与自然演化的不一致来观察结果, 进而验证假设。
TDEM 中的归因分析
对某项事实, 尝试寻找原因的过程便为归因。
归因有重要的商业意义,任何商业行为都有目标, 而目标最终成功/失败与多种因素相关,了解商业目标的成功/失败与何种因素相关有助于实现商业决策。
TDEM 承载了多种应用数据, 包括:
- 用户性能事件, 例如 卡顿、Crash、网络错误 ...
- 用户操作记录,例如 进入的页面,点击的按钮
- 衍生指标: DAU/WAU/流失率 ...
以上数据就是 TDEM 目前的认知边界,我们无法做到超越认知边界以外的事情,因此归因局限在上述数据范围。
我们尝试回答像这样的问题:
- 什么
性能类因素
造成了 用户流失 ? - 什么
性能类因素
造成了 订单转化率低 ?
当然, 如果有更多非性能类因素可以加入到推断中会有更全面的归因结果(例如运营活动以及设计改版), 但就目前来讲,我们可以提供的只有 性能类因素
的归因
归因分析业界算法
- Adtributor, 参考 Adtributor: Revenue Debugging in Advertising Systems
- JS散度, 参考 DAU归因分析之JS散度
- Squeeze, 参考 Generic and Robust Localization of Multi-Dimensional Root Causes
TDEM 归因分析实现
许多上述的业界算法都是尝试在多维空间中,寻找异常维度组合。
TDEM 数据特征
- 维度多样, 几十种归因维度并且会持续增加
- 数据量级大, 原始数据量级在在十亿以上
- 作为 Sass 平台, 接入多个产品, 这些产品是需要分开计算的
方案选型
考虑到数据量级以及低实时性需求,我们选择了大数据生态下的解决方案。
最终以 Spark MLib
提供的核心能力,选择了频繁度挖掘算法 FP-Growth
作为主力算法。
在 Spark MLib
提供的能力中,可作为归因还有 Gradient-boosted tree classifier , GBT 在 Spark MLib 提供的分类算法中,在实际数据测试中有最好的 areaUnderROC 指标。并且有良好的可解释性。
但是实测中, GBT 算法中, 性能指标的 Feature Importances
普遍偏低,并且预测的准确率低于 70%,这给结果的可解释性带来了困难。
我们思路转向了: 是否可以找出性能因素有更高支持度的场景,例如: 广东电信的用户中, 网络错误是用户流失的一大原因 (可能产品在广东电信场景下有运营问题)。
我们最终使用 FP-Growth
,尝试寻找在特定场景下的归因。
TDEM 归因流程
如上图所示, 归因分为了 6 个步骤:
- 计算 Features, 用户为维度, 每天计算一次, Features 包括用户属性(如运营商/机型/地域)及归因源(如卡顿次数/Crash次数/启动平均耗时)
- 将所有字段分为 4 个部分, 命名空间 / 用户属性 / 归因源 / 归因目标
- 归因目标, 此次归因分析的目标,例如用户流失,订单转化失败 ...
- 命名空间, 每个命名空间中的数据是独立计算的, 当前命名空间为 AppID, 即每个产品的归因在逻辑上是不相互影响
- 用户属性, 先验经验不能作为归因的字段, 例如: 机型/地域/运营商 这些,这些不是最终原因,但我们把这些字段作为用户分组字段
- 归因源, 先验经验中,可以作为归因的字段,例如: Crash 次数/卡顿次数/启动耗时 ...
- 将”归因源”字段中值类型的转为 Label: 高/中/低,并筛选
Label
- 因为
FP-Growth
算法只接受Category
,不接受Number
- 转为
Label
后,按照先验经验,只保留会造成用户体验损失的Label
, 例如卡顿次数:少
不会作为归因, 而只有卡顿次数:多
才能作为归因
- 因为
- 筛选命中归因目标的用户, 以
归因源
和用户属性
字段作为 Items, 进行 fpGrowth 频繁度挖掘- 目的是寻找归因目标下,有哪些模式频繁发生
- 对产生的频繁集, 筛选含有1个
归因源
的频繁集, 将频繁集中的用户属性
集合定义为用户分组
- 为何是 1 个
归因源
而不是 2 个或更多 ? 首先 1 个归因源
支持度大于 2 个及以上 1 个归因源
代表的是最主要归因
- 为何是 1 个
- 对每个命名空间下的所有
用户分组
, 分别计算每个分组的下属指标- 归因目标发生次数
- 归因命中次数: 归因目标发生时, 归因发生次数
- 支持度: 归因命中次数 / 归因目标发生次数
TDEM 归因架构
简化架构如上图所示:
- Spark SQL 计算 Features
- Spark MLib 计算归因并导出到 ES
- 数据存储在 HDFS/DeltaLake
- 每天离线计算一次
TDEM 归因实现细节
- 资源允许时, 将 user_features cache 到内存, 在需要每个产品单独计算时提高 Filter 性能
- 尽量减少需要分别每个产品单独计算的部分, 在进行 FP-Growth 分产品计算完成后立即 Union, 后续指标计算不再分产品
- 每个产品会产生成百上千个
用户分组
,计算每个用户分组的指标时, 可以先计算每个用户分别属于哪些分组 - 计算每个用户分别属于哪些分组时, 可以将所有分组使用
broadcast
变量广播后, 使用Spark UDF
进行计算 - 计算完 每个用户属于的分组后, 对分组列表进行
explode
进行后续指标计算 - 调节 Partition 个数, 避免 OOM
- 计算 Feature Label 时, 需要事先确定数十个维度的 25分位/75分位, 此过程可能发生 OOM, 可以抽样计算
优势与局限性
TDEM 实现的归因, 理论上的优势在于先验经验的加入。
在先验经验上, 一些不能作为 因
的部分提前被剔除, 让最终结果减少噪声。
此外, 依靠频繁模式而非多维空间下钻, 可以规避 维度灾难
局限性:
频繁
与原因
之间还是有差别,缺乏足够的可解释性- 依然处在
因果关系之梯
的最底层: 关联, 而干涉需要进行A/B Test,并且性能问题的干涉并不是非常简单可行
关于归因的想法,欢迎评论区探讨
欢迎接入 TDEM (原QAPM) 体验