前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >特征漂移指标 PSI

特征漂移指标 PSI

原创
作者头像
骑着蜗牛向前跑
修改2023-11-06 10:33:04
2440
修改2023-11-06 10:33:04
举报
文章被收录于专栏:spark 与大数据

背景描述

稳定性指的是参与对比两者相同指标差异性很小。机器学习使用训练数据(训练集和验证集)建模,使用测试数据模拟生产环境数据测试模型结果,其建模的假设是:训练数据涵盖了该问题所有的案例数据,即训练数据和测试(生产)数据之间的差异是很小的。

但实际上这个假设是很难成立的,原因:

  1. 受限于数据收集方法,不可能收集到该问题的所有案例数据。
  2. 模型投产后,生产环境的数据随时间会受到自然环境、政策环境、市场环境等影响而发生变化并且不可预知。

模型的输出很可能是决策的依据,如果模型不能适应新数据,这是很大的风险点。因此,监控数据的稳定性并且及时更新模型是一件很重要的事情。

群体稳定性指标

群体稳定指标(Population Stability Index,PSI)反映了验证样本在各分箱段的分布与建模样本分布的稳定性,通常被用来筛选特征变量、评估模型稳定性。计算方法如下所示:

这里就先遵循拿来主义,“这个公式为什么就能表示稳定性”在此不做深究。下面对公式说一些说明:

A (actual)表示实际分布,即生产数据,E(expected)表示期望分布,即训练数据。期望生产数据尽可能和训练数据的分布一样。i 表示每个分箱。这个公式的意思是:将每个分箱下的生产数据和期望数据做数学计算后再将结果求和

PSI

稳定性

0~0.1

稳定性很好

0.1~0.2

稍微有点不稳定

大于 0.2

不稳定,分析生产数据并判断是否要重新训练模型

PSI 代码实现

1.创建模拟数据

代码语言:python
代码运行次数:0
复制
size = 5000
# 期望数据
p2 = np.random.normal(loc = 3, scale = 1, size = size)
# 实际数据
a2 = np.random.normal(loc = 3.5, scale = 0.75, size = size)
  1. 计算每个分箱的边界,推荐分箱数位 10-20,此处设置 10 个分箱
代码语言:python
代码运行次数:0
复制
num_bins = 10
eps = 1e-4

min_val = min(min(p2), min(a2))
max_val = max(max(a2), max(p2))
bins = [min_val + (max_val - min_val)*(i)/num_bins for i in range(num_bins+1)]
bins[0] = min_val - eps # 修正下界
bins[-1] = max_val + eps # 修正上界

print(bins)
# [-0.4810252475657688, 0.229173950184835, 0.9392731479354388, 1.649372345686043, 2.3594715434366464, 3.0695707411872504, 3.7796699389378547, 4.489769136688458, 5.199868334439062, 5.909967532189666, 6.620166729940269]
  1. 将实际数据和期望数据分箱
代码语言:python
代码运行次数:0
复制
# 计算数组总的元素属于哪个分箱
bins_p2 = pd.cut(p2, bins = bins, labels = range(1,num_bins+1))
# 将元素和封箱号对齐
df_p2 = pd.DataFrame({'p2': p2, 'bin': bins_p2})
# 统计每个封箱中的元素数量
grp_p2 = df_p2.groupby('bin').count()
# 计算每个分箱中元素数量占总数的百分比
grp_p2['percent_p2'] = grp_p2['p2'] / sum(grp_p2['p2'])

# 对实际数据也进行分享操作
bins_a2 = pd.cut(a2, bins = bins, labels = range(1,num_bins+1))
df_a2 = pd.DataFrame({'a2': a2, 'bin': bins_a2})
grp_a2 = df_a2.groupby('bin').count()
grp_a2['percent_a2'] = grp_a2['a2'] / sum(grp_a2['a2'])

# 比较 p2 和 a2 的分箱数据
psi_df = grp_p2.join(grp_a2, on = "bin", how = "inner")
print(psi_df)

bin

p2

percent_p2

a2

percent_a2

1

12

0.002400

0

0.0000

2

75

0.015003

3

0.0006

3

338

0.067614

36

0.0072

4

919

0.183837

309

0.0618

5

1297

0.259452

1061

0.2122

6

1293

0.258652

1883

0.3766

7

704

0.140828

1223

0.2446

8

278

0.055611

430

0.0860

9

70

0.014003

54

0.0108

10

13

0.002601

1

0.0002

  1. 计算各分箱的 psi
代码语言:python
代码运行次数:0
复制
# 当 percent_p* 是 0 时,给其加上 eps。防止 0 参与运算抛异常。
psi_df['percent_p2'] = psi_df['percent_p2'].apply(lambda x: eps if x == 0 else x)
psi_df['percent_a2'] = psi_df['percent_a2'].apply(lambda x: eps if x == 0 else x)
# 计算每行的 psi
psi_df['psi'] = (psi_df['percent_p2'] - psi_df['percent_a2']) * np.log(psi_df['percent_p2'] / psi_df['percent_a2'])

print(psi_df)

bin

p2

percent_p2

a2

percent_a2

psi

1

12

0.002400

0

0.0000

0.007312

2

75

0.015003

3

0.0006

0.046364

3

338

0.067614

36

0.0072

0.135310

4

919

0.183837

309

0.0618

0.133038

5

1297

0.259452

1061

0.2122

0.009500

6

1293

0.258652

1883

0.3766

0.044313

7

704

0.140828

1223

0.2446

0.057291

8

278

0.055611

430

0.0860

0.013248

9

70

0.014003

54

0.0108

0.000832

10

13

0.002601

1

0.0002

0.006158

  1. 各分箱的 psi 求和
代码语言:python
代码运行次数:0
复制
psi = psi_df['psi'].sum()

print(psi)
# 0.4533650280982507

通过上述代码实现可以看出:特征漂移实际是在计算预期数据和实际数据的分布差异情况。。

工程中的实际应用:PSI 和 CSI

PSI

以回归算法 ElasticNet() 拟合 y = a1*x1 + a2*x2 + a3*x3 + b 函数为例演示 psi 在工程中的使用。

代码语言:python
代码运行次数:0
复制
size = 5000
# 合成训练需要的数据
x1 = np.random.normal(loc = 0, scale = 2, size = size)
x2 = np.random.normal(loc = 3, scale = 1, size = size)
x3 = np.random.normal(loc = 5, scale = 2, size = size)
y = [-2*x1 + 3.1415*x2 + 2.7183*x3 + 1.6180 for (x1, x2, x3) in zip(x1,x2,x3)]

# 划分训练集和验证集
X = pd.DataFrame({'x1': x1, 'x2': x2, 'x3': x3})
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)

# 训练模型(该模型尽可能拟合出 y = a1*x1 + a2*x2 + a3*x3 + b 表达式)
model = ElasticNet()
model.fit(X_train, y_train)

# 验证结果(模型线下的评测结果)
y_pred = model.predict(X_test)

# 再合成生产环境(线上)数据
z1 = np.random.normal(loc = 0, scale = 2, size = size)  
z2 = np.random.normal(loc = 3.5, scale = 0.75, size = size)  
z3 = np.random.normal(loc = 8, scale = 3, size = size)
# 计算模型的线上推理结果
Z = pd.DataFrame({'x1': z1, 'x2': z2, 'x3': z3})
z_pred = model.predict(Z)

"""
此时线下模型效果 y_pred 和线上模型效果 z_pred 都是一维的,令 p2 = y_pred, a2 = z_pred。
套用“PSI 代码实现部分”就可以计算出群体性稳定指标 PSI,最终判定出模型稳定性处于什么程度。
"""

通过计算 训练模型时的推理结果 和 线上模型的推理结果 的 psi 就能初步得出模型的稳定程度。

CSI

psi 只能宏观判断出模型的稳定程度,如果不稳定,到底是哪些特征引起的不稳定?此时就需要使用特征稳定性指标(CSI)进行判断。计算方式没有什么变化,只是用的数据不一样而已。

上述代码中训练数据特征 x1、x2、x3,线上数据特征 z1、z2、z3。

代码语言:python
代码运行次数:0
复制
令 p2 = x1, a2 = z1, 计算出 psi1;
令 p2 = x2, a2 = z2, 计算出 psi2;
令 p2 = x3, a2 = z3, 计算出 psi3;
通过 psi* 就能看出是哪个特征引起的不稳定。

两个小问题

  1. 在机器学习中,回归和分类分别适用什么场景?

归回使用预测值连续的场景。回归问题的目标是找到输入特征与输出值之间的关系,以便能够对未知数据进行预测。房价预测就是个典型的回归问题。分类问题适用于样本划分不同类型的场景。当目标变量是离散的、具有预定义类别的数据时,分类算法被用来预测样本的类别。分类任务的目标是构建一个模型,该模型根据输入特征将样本划分到正确的类型中。垃圾邮件分类就是分类问题。

  1. 模型评价指标已经能判断模型效果了,为什么还要用 psi 判断要不要重新训练模型? 两种评价的目的不一样,模型指标是判断模型训练的好不好,准确率有多高,能不能投产使用。psi 是模型已经上线了,受环境影响生产中的数据时刻在变化,psi 判断当前模型能不能适应这些变化的数据。psi 还有个作用是用来筛选特征。

参考资料

https://towardsdatascience.com/checking-model-stability-and-population-shift-with-psi-and-csi-6d12af008783

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景描述
  • 群体稳定性指标
  • PSI 代码实现
  • 工程中的实际应用:PSI 和 CSI
    • PSI
      • CSI
      • 两个小问题
      • 参考资料
      相关产品与服务
      大数据
      全栈大数据产品,面向海量数据场景,帮助您 “智理无数,心中有数”!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档