如何评价弹性模型训练的好坏?一文浅谈评测指标AUUC

2022-09-15 14:46:23 浏览数 (3)

每天给你送来NLP技术干货!


©作者 | 努力写文的乌龟

研究方向 | 因果推断

来自 | PaperWeekly

本篇文章的服务主题是——如何评价一个弹性模型训练的好坏?介绍当前广泛使用的评价指标 AUUC。本篇由浅入深,从基本定义到应用思考,希望对 AUUC 已经了解的各位也能有小小的帮助。

为什么会有 AUUC 这个指标?

在因果推断领域有一个最基本的问题——对于一个给定的个体,我们不可能知道对它施加各种动作的反应。举一个生活中的例子,商店给客户发优惠券,假如我们给一位用户发了优惠券,我们就只能知道这位用户收到优惠券的反应,我们无法知道他没有收到优惠券的反应。

弹性模型是因果推断的一个子领域,它的目标是学习个体的潜在弹性。假设一共有两个动作 , 代表“施加动作”; 和 代表个体的响应信号。因此弹性模型学习的是“施加动作相比于不施加动作的响应信号差值”,用公式表示为 。由刚才介绍的因果推断最基本问题可知,在实际数据中是没有弹性 的标签的——我们无法使用回归问题的指标,如 MSE 等指标用在评价弹性模型上

因为常见的评价指标无法衡量弹性模型训练的好坏,因此诞生了 AUUC 这个指标。AUUC 是一个序指标,它使用弹性模型预估出的弹性 值排序,评价这个序的优劣。从 AUUC 的值和相关的曲线,我们能知道当前弹性模型的很多信息,包括当前数据集的质量、相比随机排序弹性模型的提升等。

具体计算方法

AUUC 的全称是 Area Under the Uplift Curve, 计算的是 Uplift Curve 弹性曲线下的面积。因此首先解释下什么是 Uplift Curve,以及如何计算 Uplift Curve。

2.1 计算 AUUC 所需要的输入

假设我们有一个数据集 ,这是计算 AUUC 所需要的全部输入。其中 W 是用户所属组的标识,假如 ,则这个人在实验组;假如 ,则这个人在空白组。 是观测到的这个人的响应信号,比如在吃药和不吃药的问题中,响应信号可以是一周后病是否痊愈。scorei 是这个人的评分,一般使用的是弹性模型估计的弹性 。Uplift Curve 是一个序指标,我们根据 score 的排序计算这个排序结果。

2.2 计算AUUC的过程

首先计算 Uplift Curve:根据 score 值从大到小对数据集 D 排序,即 score 值高的排在前面。根据排序结果,我们可以计算排在前 k 个中属于实验组的人的响应之和,即如下公式所示:

其中 是指示函数,如果 ,则 ,否则为 0。同理,我们可以计算排在前 k 个中属于空白组的人的响应之和:

同理,我们可以计算排在前 k 中,实验组和空白组的人数,分别用 和 表示:

得到这四个值后,我们可以计算第 k 个的 uplift 值,它的计算公式如下:

第 k 个 Uplift 值的含义是:前 k 个人中实验组平均产生的价值-前 k 个人中空白组平均产生的价值。依次类推,我们可以得到第 1~n 个 Uplift 值,可以根据此画出曲线。

画出该曲线后,我们就可以计算曲线下的面积了,即如下所示:

我们可以对这个面积进行归一化,归一化除的分母是 ,归一化后的结果为

具体代码

causalml 包中有 AUUC 的代码,具体在 metrics/visualize.py 中,地址是:

https://github.com/uber/causalml

主要有三个关键函数,分别为 auuc_score(), get_cumgain(), get_cumlift()。

关于AUUC的思考

AUUC 的计算逻辑十分清晰,看过代码后很快就能掌握。但是分析 AUUC 数值和相关曲线的含义,却需要比较深入的思考。下面通过几个自我提问来加深对 AUUC 的了解。

4.1 AUUC代码中,需要关注的值和图像有哪些?

个人认为,需要关注两个值和两个图

代码语言:javascript复制
metric_df = pd.DataFrame([score_list,
                           y_list,
                           t_list]).T
metric_df.columns=['model','y','w'] 
lift = get_cumlift(metric_df)
lift.plot()
gain = lift.mul(lift.index.values, axis=0)
if normalize:
    gain = gain.div(np.abs(gain.iloc[-1, :]), axis=1)
gain.plot()
auuc_score = gain.sum()/gain.shape[0]
  • 两个值:auuc_score() 函数返回的两个值。
    • auuc_score['model'] 是根据 score_list 排序的 AUUC
    • auuc_score['random'] 是随机排序的 AUUC。
  • 两个图:分别是 get_cumlift(),get_cumgain() 的返回值画出的图。
    • lift.plot() 的曲线图代表着 uplift 曲线,曲线上 x 轴 y 轴对应系为
    • gain.plot() 的曲线图代表着累积 uplift 曲线,曲线上 x 轴 y 轴对应关系为
    • 两者的横坐标的含义都是人,假如计算的数据有 10000 个人,则 x 的范围为 [1, 10000]。

4.2 AUUC代码中,归一化步骤做了什么?

我们看到代码中,会有一个可选参数 “normalize”,normalize 默认为 True,即会对 AUUC 进行归一化。这是因为累积 uplift 值会比较高,对 y 轴归一化更方便分析。

归一化的除数是 gain.iloc[-1,:],它是“排序在最末尾的累积 uplift 值”,。根据上文讲到的 uplift 公式我们可以知道 ,我们会发现 其实就是全量人群的 Average Treatment Effect(ATE)。在归一化后,归一化后 gain.plot() 曲线在 处,。

4.3 AUUC代码中,计算的random值的含义是什么?

在 AUUC 代码中,我们能看到 auuc_score() 函数最终产出了一个随机得分,lift 和 gain 也分别有一条 random 曲线。那么 random 究竟代表着什么呢?它代表了无规律排序下,实验组相对于控制组的期望增值。在大多数情况下,使用归一化的 auuc_score(normalize=True) 函数计算得到的 random 值应该接近 0.5,gain.plot() 画出的 random 应该是一条直线。原因是在多次随机排序下,期望增值应该是稳步上升的。

假如 random 值不是 0.5,图像不是直线时,这意味着什么?

导致不是 0.5 的背后原因可能有很多,可以先分析下是不是以下三种情况。1.实验组和空白组不是平衡的,两者人群不是同质的,这时算 AUUC 没有很大的意义了,应该调平人群后再计算。2.样本的 y 值即响应信号的离群点比较多。3.样本量太小,无法支撑实验组和对照组的匹配。

除此之外,还有一种情况是 random 值为 -0.5,这时整体的 ATE 为负数,所有样本均为负弹,这时候 uplift 值不再是“收益”,更像是“花销”。

来画个图,观赏下AUUC曲线吧

讲完理论可能还是缺乏真实感,接下来我们造个虚拟数据集画一下 AUUC 曲线。

代码语言:javascript复制
import numpy as np
%matplotlib inline
import pandas as pd
from EvolveUplift import get_cumlift_change
import matplotlib.pyplot as plt
y_c = np.array(1000*[0])
y_t = y_c.copy()
u = np.array(200*[10] 200*[4] 200*[0] 200*[-2] 200*[-4])
for i in [0,200,400,600,800]:
    y_t[i:i 200] = y_c[i:i 200] u[i]
r = []
for i in range(0,1000):
    r.append(y_t[i])
    r.append(y_c[i])

y = np.array(r)
u = np.concatenate((np.random.normal(10,0.01,[400]),
                    np.random.normal(8,0.01,[400]),
                    np.random.normal(6,0.01,[400]),
                    np.random.normal(4,0.01,[400]),
                    np.random.normal(2,0.01,[400])),axis=0)
metric_dfa = pd.DataFrame([u,
                           y,
                           np.array(1000*[1,0])]).T
metric_dfa.columns=['ite','y','w'] 
lift = get_cumlift_change(metric_dfa)
lift.plot()
gain = lift.mul(lift.index.values, axis=0)
gain.plot()
print(gain.sum() / gain.shape[0])
print("------------------------")
gain = gain.div(np.abs(gain.iloc[-1, :]))
gain.plot()
print(gain.sum() / gain.shape[0])

▲ AUUC score 上面是归一化前,下面是归一化后

▲ Uplift曲线

▲ 左图是归一化之前的AUUC曲线,右图是归一化后的,可以看到归一化后的最右顶点的y值为1

我们能从这个示例中看出什么呢?

理想情况下(模型估计得很准或者虚拟数据集有虚拟真值),uplift 值从大到小排序,gain.plot() 曲线的斜率(即一阶导数)应该是从大到小变化的。要强调下,这是在理想条件下,真实数据集往往会出现轻微的不平衡和极值,画出的 AUUC 曲线没有这么优美。

代码中的虚拟数据集的弹性值可以修改,大家可以试一试弹性都为正数或负数时,曲线会是什么样的?是否会验证前文提到的结论。

参考文献

Learning to rank for uplift modeling AUUC 其实有很多种计算方式,我选择的是 causalml 包代码中的计算逻辑。这篇论文很详细的介绍了多种 AUUC 计算方式,值得去了解关注一下。

因果推断领域相关系列的文章:

https://zhuanlan.zhihu.com/p/463789355

https://zhuanlan.zhihu.com/p/464632616


0 人点赞