算法模型离线评估方案

引言

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

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

第二特征评估。抽取样本,进行特征评估,主要用单特征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(``'.'``)[``1``] ``for i ``in list``(train_data.columns)] # 输入特征列和标签列,输出列名对应的皮尔逊系数 # 分别计算每个特征与标签的相关系数 from scipy.stats ``import pearsonr def calc_pearsonr(dataframe, col_name, label_name): ``x ``= dataframe[col_name].values ``label ``= dataframe[label_name].values ``p ``= 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``*``100``, ``2``) 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``=``(``64``, ``32``, ``16``)) ``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``=``(``64``, ``32``, ``16``)) 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.7486 AUC提升+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获取特征 | 推理预测 | 其他 |

耗时 34ms 19ms 12ms 3ms

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 requests 10000 总请求数
Failed requests 2 失败次数(Connect: 0, Receive: 0, Length: 2, Exceptions: 0)
Time per request 0.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() y ``= 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组 show UV click CTR(click/show)
20221101 Base 383904 50000 4950 0.011205926
20221102 Base 394050 49837 4302 0.007636087
20221103 Base 339876 39920 3009 0.009983053
20221104 Base 339874 40092 3393 0.014564221
20221101 Exp 499877 56700 4950 0.010198509
20221102 Exp 49838 50987 5098 0.080440628
20221103 Exp 59987 50988 4009 0.099821628
20221104 Exp 399874 40052 5988 0.014974717
平均 Base 364426 44962.25 3913.5 0.010847322
平均 Exp 499502.25 49681.75 5261.25 0.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. 数值型特征进行分桶,转化为类别型特征会更好一下,有利于模型分类
相关推荐
游是水里的游5 分钟前
【算法day19】回溯:分割与子集问题
算法
不想当程序猿_5 分钟前
【蓝桥杯每日一题】分糖果——DFS
c++·算法·蓝桥杯·深度优先
南城花随雪。24 分钟前
单片机:实现FFT快速傅里叶变换算法(附带源码)
单片机·嵌入式硬件·算法
dundunmm40 分钟前
机器学习之scikit-learn(简称 sklearn)
python·算法·机器学习·scikit-learn·sklearn·分类算法
古希腊掌管学习的神40 分钟前
[机器学习]sklearn入门指南(1)
人工智能·python·算法·机器学习·sklearn
波音彬要多做42 分钟前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
程序员老冯头3 小时前
第十五章 C++ 数组
开发语言·c++·算法
AC使者7 小时前
5820 丰富的周日生活
数据结构·算法
cwj&xyp8 小时前
Python(二)str、list、tuple、dict、set
前端·python·算法
xiaoshiguang312 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode