算法模型离线评估方案

引言

该文档阐述了算法从开始迭代到上线前的一些工作流程。

首先第一需求背景。我们需要清楚本次迭代的需求背景。

第二特征评估。抽取样本,进行特征评估,主要用单特征AUC和缺失率着两个指标进行评估,确保数据没有异常。

第三模型评估,包括多次实验,对模型的AUC和接口的耗时这两个指标进行评估

第四AB实验,主要通过AB实验来对比评估,用CTR进行评估。

Demo代码地址:http://172.29.28.203:8888/lab/tree/wangyongpeng/global_search_rank_ctr/model_eval.ipynb

需求背景

描述本次上线的改动点、背景。例如:

为了捕捉用户对物料的兴趣偏好,加入了用户对物料的点击率特征(user_item_ctr)

特征评估

特征评估有很多种方式,主要包括两大类,特征和特征直接的相关性分析,特征和标签之间的相关性分析,特征和标签的分析方法包括:单特征AUC评估、Pearson系数、GBDT训练得到特征重要性等方法。

特征表维护

需要维护一张模型用到的特征列表,包含四个信息(特征名,特征含义,单特征AUC,特征缺失率)。

这张表可以帮助我们在每次迭代的时候,发现数据问题。比如缺失率突然升高,说明我们数据关联发生问题。不容易漏掉特征。

实例如下:

特征名

含义

Pearson相关性系数

缺失率

是否上线

备注

age用户的年龄

-0.04009939333393429

100%N缺失率100%,不作为特征使用
sex性别

-0.039342348680731327

17.47%
Y
role角色

-0.033291028680731327

0.0%
Y
total_show展示次数

-0.02418875134961658

0.0%
Y
total_click物料总点击

-0.07942286768376286

0.0%
Y
total_vote物料总点赞

-0.04246059351624654

0.0%
Y
total_collection物料总收藏

-0.043361133083929235

0.0%
Y
total_comment物料总评论

-0.023291028680731327

0.0%
Y
total_share物料总分享

-0.04204700458418045

0.0%
Y

样本频率分布直方图概览

如果想看细节的一些具体数值,就可以用以下代码仔细的看。

1

2

3

4

5

%matplotlib inline

import matplotlib.pyplot as plt

import pandas as pd

num_train_data['total_share'].hist(bins=100)

可视化完的结果如下,因为样本异常差异太大,可视化出来不美观。其表示的是我们应该在后续的特征工程阶段,将这些异常值进行范围限定

Pearson相关系数计算

Pearson相关系数的范围是在[-1,1]之间,下面给出Pearson相关系数的应用理解:
假设有X,Y两个变量,那么有:
(1) 当相关系数为0时,X变量和Y变量不相关;
(2) 当X的值和Y值同增或同减,则这两个变量正相关,相关系数在0到1之间;
(3) 当X的值增大,而Y值减小,或者X值减小而Y值增大时两个变量为负相关,相关系数在-1到0之间。
注:相关系数的绝对值越大,相关性越强,相关系数越接近于1或-1,相关度越强,相关系数越接近于0,相关度越弱。通常情况下通过以下取值范围判断变量的相关强度:
0.8-1.0 极强相关
0.6-0.8 强相关
0.4-0.6 中等程度相关
0.2-0.4 弱相关
0.0-0.2 极弱相关或无相关

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

%matplotlib inline

import seaborn as sns

import matplotlib.pyplot as plt

from pyhive import hive

import pandas as pd

import numpy as np

conn = hive.Connection(host='172.30.2.60', port=10000, database='recsys')

train_sql = """select * from recsys.search_full_link_30d  where resource_type='VIDEO' limit 10000"""

train_data = pd.read_sql(train_sql, conn)

conn.close()

train_data.columns = [i.split('.')[1for in list(train_data.columns)]

# 输入特征列和标签列,输出列名对应的皮尔逊系数

# 分别计算每个特征与标签的相关系数

from scipy.stats import pearsonr

def calc_pearsonr(dataframe, col_name, label_name):

     

    = dataframe[col_name].values

    label = dataframe[label_name].values

    = pearsonr(x, label)[0]

    print("%s 和Label的系数 = %s"%(col_name, p))

train_data_fillna = train_data.fillna(0)

featureCols =['total_show''total_click''total_share''total_vote',

       'total_collection''total_comment']

for col in featureCols:

    calc_pearsonr(train_data_fillna, col, 'click_label')

# total_show 和Label的系数 = -0.02418875134961658

# total_click 和Label的系数 = -0.07942286768376286

# total_share 和Label的系数 = -0.04204700458418045

# total_vote 和Label的系数 = -0.04246059351624654

# total_collection 和Label的系数 = -0.043361133083929235

# total_comment 和Label的系数 = -0.023291028680731327

缺失率计算

计算样本中,某列值得缺少情况,可以判断样本中存在的问题

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

import pandas as pd

mHeader = ['age','city_id','hospital_id']

rowDF = pd.read_csv('./data/raw_data.csv', sep=',', skiprows=[0],names=mHeader)

  

# 传入列表头,输出特征的缺失率

def get_null_rate(featureCol, DF):

    # 统计每列的缺失率,获取总数

    totalRows = DF.shape[0]

    tempCount = 1-(DF[featureCol].where(DF[featureCol] != -1.0).count()/totalRows)

    return round(tempCount*1002)

  

featureCols = ['age','city_id','hospital_id']

  

for col in featureCols:

    print('{}={}%'.format(col, get_null_rate(col, rowDF)))

# 输出结果

# age=14.65%

# city_id=15.27%

# hospital_id=14.44%

模型评估

模型离线AUC评估

先简单介绍一些AUC的概念

什么是AUC

  • 其本身含义是正负样本之间预测的gap越大,auc越大.

  • 随机抽出一对样本(一个正样本,一个负样本),然后用训练得到的分类器来对这两个样本进行预测,预测得到正样本的概率大于负样本概率的概率。

AUC的优势:

AUC的计算方法同时考虑了分类器对于正例和负例的分类能力,在样本不平衡的情况下,依然能够对分类器作出合理的评价。且AUC对均匀正负样本采样不敏感。

以下代码是模型中设置评估指标的代码,metrics可以自己改动根据相应的场景进行设置。

1

2

3

4

5

6

model = DeepFM(linear_feature_columns,dnn_feature_columns,task='binary', dnn_hidden_units=(643216))

    model.compile("adam""binary_crossentropy",

                  metrics=['binary_crossentropy','AUC'], )

    history = model.fit(train_model_input, train['click_label'].values,

                        batch_size=32, epochs=10, verbose=2, validation_split=0.2, )

TensorBoard可视化

项目中集成tensorboard

下面代码是将模型训练的过程保存在"./tensorboard_logs"这个目录中。

1

2

3

4

5

6

7

model = DeepFM(linear_feature_columns,dnn_feature_columns,task='binary', dnn_hidden_units=(643216))

model.compile("adam""binary_crossentropy",

              metrics=['binary_crossentropy','AUC'], )

tf_callback = tf.keras.callbacks.TensorBoard(log_dir="./tensorboard_logs")

history = model.fit(train_model_input, train['click_label'].values,

                    batch_size=512, epochs=20, verbose=1, validation_split=0.1, callbacks=[tf_callback])

启动tensorboard命令

tensorboard --logdir tensorboard_logs/ --port 8081

可视化效果

下图显示的是在每次迭代中,AUC的变化曲线

离线实验记录表

这部分主要的目的就是新旧模型的PK,新增特征前后的PK,前提是在同一份测试集上。这部分可能会有很多次实验。在训练阶段,我们应该形成如下的记录,其中:

  • 训练集:54天的数据作为训练数据
  • 测试集:一周,7天的数据进行测试

训练测试集切分代码

如下代码是按照天数进行对数据进行切分,先获取数据集中的最大日期,然后7天前的作为训练集,7天后的作为测试集。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# 切分时间的函数

def split_data_by_dt(dataframe):

    # 先获取最大的时间

    max_dt = dataframe['log_date'].max()

    # 得到分割时间

    dt = datetime.datetime.strptime(max_dt, "%Y-%m-%d")

    split_date = (dt + datetime.timedelta(days=-7)).strftime("%Y-%m-%d")

    # where条件区分训练测试集, 去空

    test = dataframe.where(dataframe['log_date'] > split_date)

    test = test.dropna(how='all')

    train = dataframe.where(dataframe['log_date'] <= split_date)

    train = train.dropna(how='all')

    # 返回训练测试集

    print("训练数据量 = " + str(train.shape))

    print("测试数据量 = " + str(test.shape))

    return train, test

实验

改动点

参数

样本数据

离线AUC

备注

Base组线上版本

hidden_units=[64, 32, 16]

epochs=20

batch_size=512

训练:20220906~2022-10-30

测试:2022-10-30~20221106

0.7323线上基础版本的情况
实验组增加特征

hidden_units=[64, 32, 16]

epochs=20

batch_size=512

训练:20220906~2022-10-30,(54天),302431条数据

测试:2022-10-30~20221106,(7天),37403条数据

0.7486AUC提升+0.0163,提升巨大,可以上线

线上推理接口时间评估

模型性能主要包括接口线上预测的响应时长。响应时长在200ms以内可接受。

该时间是指接口整体的响应时间,在测试环境中,响应时长是指五次请求的平均响应时间(不包含第一次请求)

记录响应时间的代码如下,通过time.time()方法实现:

1

2

3

4

5

6

7

8

9

10

11

@app.route("/citizen_search_rank_ctr", methods=["GET"])

def search_rank_ctr():

    total_start_time = time.time()

    aa = rank(user_id, resource_ids, resource_type)

    total_end_time = time.time()

    print("====================== total time = %s======================"%int((total_end_time-total_start_time)*1000))

    return aa

     

if __name__ == '__main__':

    # 模型部署

    app.run(host='0.0.0.0', threaded=True, port=5012)

响应时长在现有场景主要包含三部分:从redis获取特征、模型预测、其他三个方面的耗时。响应时长如下可以从控制台日志得到。

1

2

3

4

5

curl -G -d 'user_id=61110e6ce4b0fe63c65be1c2' -d 'resource_id_list=31662,31767,31672,33381,33742,31292,31706,33559,31613,31516,32569,24908,33731,33734,33774,33128,32546,31731,25866,33808' -d 'resource_type=VIDEO' http://172.16.68.209:6058/global_search_ranking

# ====================== get feature from redis time = 19======================

# ====================== pridict time = 12======================

# ====================== total time = 34======================

我们将上面的数据进行整理可以得到如下表格,可以从数据我们得到,模型在测试环境相应时间为34ms, 符合200ms要求,可以上线。

总耗时

redis获取特征

推理预测

其他

耗时34ms19ms12ms3ms

HTTP接口并发测试

压测我们采用ApacheBench,简称ab,压测命令如下,从压测结果看,我们的接口可以支持5000日活的没有压力,但是想更多,就需要更多的机器进行负载。

1

ab -n 10000 -c 5000 -r http://172.16.68.209:6058/global_search_ranking?user_id=61110e6ce4b0fe63c65be1c2&resource_id_list=31662,31767,31672,33381,33742,31292,31706,33559,31613,31516,32569,24908,33731,33734,33774,33128,32546,31731,25866,33808&resource_type=VIDEO

其中:

  • -n 后面的1000,表示总共发出1000 0个请求;
  • -c 后面的5000 ,表示采用5000个并发(模拟 5000个人同时访问)

测试完后会有如下评估报告:

指标

备注

Complete requests10000总请求数
Failed requests2失败次数(Connect: 0, Receive: 0, Length: 2, Exceptions: 0)
Time per request0.591 [ms]请求平均时长

AB实验

线上AUC计算

线上AUC计算,是全量之前重要的一步,线上AUC可以直接单向的反映模型上线后收益是正向还是负向。
计算方式是拿到线上点击标签,模型线上打分分数,根据predict分数和label来计算线上的AUC

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

import pandas as pd

import numpy as np

from sklearn.metrics import *

pd.set_option('display.max_columns'None)

pd.set_option('display.max_rows'None)

from sklearn.metrics import roc_auc_score

mHeader = ['label''score']

expDF = pd.read_csv('./data/exp2022-11-24.csv' , sep='\t', skiprows=[0,1], names=mHeader)

baseDF = pd.read_csv('./data/base2022-11-24.csv', sep='\t', skiprows=[0,1], names=mHeader)

# 删除空值和设置label

expDF = expDF.dropna()

baseDF = baseDF.dropna()

= expDF['label'].values

pred = expDF['score'].values.reshape(-1,1)

exp_auc = roc_auc_score(y, pred)

y_base = baseDF['label'].values

pred_base = baseDF['score'].values.reshape(-1,1)

base_auc = roc_auc_score(y_base, pred_base)

print("计算实验组合Base组的AUC : base_auc = %s, exp_auc = %s" %(base_auc, exp_auc))

实验收益统计

在全量上线之前,应该先小流量进行对比,先上50%流量进行统计计算,连续观察4天(上线当天不算),如果点击率高,实验正向,就可以上线全量。

日期AB组showUVclickCTR(click/show)
20221101Base3839045000049500.011205926
20221102Base3940504983743020.007636087
20221103Base3398763992030090.009983053
20221104Base3398744009233930.014564221
20221101Exp4998775670049500.010198509
20221102Exp498385098750980.080440628
20221103Exp599875098840090.099821628
20221104Exp3998744005259880.014974717
平均Base36442644962.253913.50.010847322
平均Exp499502.2549681.755261.250.011301451
预计全量后

点击率提升+4.19%

工业Tricks分享

AUC提升和业务指标不一致问题

在实际的工作中,常常是模型迭代的auc比较,即新模型比老模型auc高,代表新模型对正负样本的排序能力比老模型好。理论上,这个时候上线abtest,应该能看到ctr之类的线上指标增长。

但是经常会出现线下AUC提升明显,线上AUC或者业务指标负向的情况,这个时候可以排查线上线下一致性。

经验汇总

  1. learning_rate:学习率(learning_rate)越小,模型AUC越高,但是训练速度会变慢,我们需要取一个平衡。我会通常设置learning_rate=0.0007
  2. epoch:epoch是指对训练集反复训练多少次,一般理想情况下AUC随着迭代的次数而升高,我的经验值是epoch = 20
  3. 神经网络层的大小只会影响训练的时长,并不会影响线上预测的时长
  4. AUC的大小和正负样本的采样无关
  5. 数值型特征进行分桶,转化为类别型特征会更好一下,有利于模型分类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值