广告推荐算法(group auc)评价指标及Spark实现代码
我们曾经有这样的疑惑,那就是训练样本,AUC得到提升。当将新模型放到线上后,却发现实际效果却没有老模型好,这时候很多人就开始疑惑了。
在机器学习算法中,很多情况我们都是把auc当成最常用的一个评价指标,而auc反映整体样本间的排序能力,但是有时候auc这个指标可能并不能完全说明问题,有可能auc并不能真正反映模型的好坏,以CTR预估算法(推荐算法一般把这个作为一个很重要的指标)为例,把用户点击的样本当作正样本,没有点击的样本当作负样本,把这个任务当成一个二分类进行处理,最后模型输出的是样本是否被点击的概率。
举个很简单的例子,假如有两个用户,分别是甲和乙,一共有5个样本,其中+表示正样本,-表示负样本,我们把5个样本按照模型A预测的score从小到大排序,得到 甲-,甲+,乙-,甲+,乙+. 那么实际的auc应该是 (1+2+2)/(32)=0.833, 那假如有另一个模型B,把这5个样本根据score从小到大排序后,得到 甲-,甲+,甲+,乙-,乙+, 那么该模型预测的auc是(1+1+2)/(32)=0.667。
那么根据auc的表现来看,模型A的表现优于模型B,但是从实际情况来看,对于用户甲,模型B把其所有的负样本的打分都比正样本低,故,对于用户甲,模型B的auc是1, 同理对于用户乙,模型B的auc也应该是1,同样,对于用户甲和乙,模型A的auc也是1,所以从实际情况来看,模型B的效果和模型A应该是一样好的,这和实际的auc的结果矛盾。
可能auc这个指标失真了,因为用户广告之间的排序是个性化的,不同用户的排序结果不太好比较,这可能导致全局auc并不能反映真实情况。
因为auc反映的是整体样本间的一个排序能力,而在计算广告领域,我们实际要衡量的是不同用户对不同广告之间的排序能力, 实际更关注的是同一个用户对不同广告间的排序能力,为此,参考了阿里妈妈团队之前有使用的group auc的评价指标 group auc实际是计算每个用户的auc,然后加权平均,最后得到group auc,这样就能减少不同用户间的排序结果不太好比较这一影响。group auc具体公式如下:
实际处理时权重一般可以设为每个用户view的次数,或click的次数,而且一般计算时,会过滤掉单个用户全是正样本或负样本的情况。
但是实际上一般还是主要看auc这个指标,但是当发现auc不能很好的反映模型的好坏(比如auc增加了很多,实际效果却变差了),这时候可以看一下gauc这个指标。
由于Spark计算AUC需要将数据转为RDD,且非常慢。如果计算每个用户的AUC将会超级花费时间。下面是我实现的一种快速计算GAUC的方式,供大家参考。
object AUCUtil {
/**
* 获取ROC曲线
* scoreAndLabels : _._1 is positive probability,_._2 is true label
*/
def roc(scoreAndLabels: Array[(Double, Double)]): Array[(Double, Double)] = {
val results = scala.collection.mutable.ArrayBuffer[(Double, Double)]()
val scoreAndLabelsCount = scoreAndLabels.length
val scoreAndLabelsWithIndex = scoreAndLabels.seq.sortBy(_._1).zipWithIndex.map(row => {
(row._2 + 1, row._1._1, row._1._2)
})
val num = 20
//阀值
val thresholds = scala.collection.mutable.Set[Double]()
for (a = scoreAndLabelsCount) index = scoreAndLabelsCount
val threshold = scoreAndLabelsWithIndex.filter(_._1 == index)(0)._2
thresholds += threshold
}
//正样本的数量
val positiveCount = scoreAndLabels.filter(_._2 == 1.0).length
//负样本的数量
val negativeCount = scoreAndLabels.filter(_._2 == 0.0).length
results += ((0, 0))
//全是正样本和全是负样本不处理
if (positiveCount != 0 && negativeCount != 0) {
val thrsholdsSorted = thresholds.toSeq.sortWith(_ > _)
for (threshold row._2 == 1.0 && row._1 >= threshold).length
//预测为正样本的数量
val P = scoreAndLabels.filter(_._1 >= threshold).length
//负样本中预测错误的数量
val FP = scoreAndLabels.filter(row => row._2 == 0.0 && row._1 >= threshold).length
//FPR
val FPR = (FP.toDouble / negativeCount.toDouble).toDouble.formatted("%.5f").toDouble
//TPR,召回率
val TPR = (TP.toDouble / positiveCount.toDouble).toDouble.formatted("%.5f").toDouble
results += ((FPR, TPR))
}
}
results += ((1, 1))
results.distinct.toArray
}
/**
* 使用梯形法则计算连接两个输入点的线下面积。
* (上底+下底)*高/2
*/
def trapezoid(points: Seq[(Double, Double)]): Double = {
require(points.length == 2)
val x = points.head
val y = points.last
(y._1 - x._1) * (y._2 + x._2) / 2.0
}
/**
* 计算曲线下的面积
* curve : _1 is FPR,_2 is TPR
*/
def areaUnderROC(curve: Iterable[(Double, Double)]): Double = {
curve.toIterator.sliding(2).withPartial(false).aggregate(0.0)(
seqop = (auc: Double, points: Seq[(Double, Double)]) => auc + trapezoid(points),
combop = _ + _)
}
def group_auc(predictions: DataFrame, userId: String): Double = {
group_auc(predictions, userId, "probability")
}
/**
* 二分类模型GAUC评估,使用曝光数加权
* @param predictions 测试集返回的预测结果
* @param userId 用户ID列名
* @param probabilityCol 概率值的列名
*/
def group_auc(predictions: DataFrame, userId: String, probabilityCol: String): Double = {
import predictions.sparkSession.implicits._
val positiveUser = predictions.where("label = 1").select(userId).distinct()
val scoreAndLabels = predictions.join(positiveUser, Seq[String](userId), "leftsemi").select(col(userId).cast(StringType), col(probabilityCol), col("label").cast(DoubleType)).rdd.map(row => {
val label = row.getAs[Double]("label")
val id = row.getAs[String](userId)
var score = row.getAs[Vector](probabilityCol)(1)
(id, score, label)
}).toDF(userId, "score", "label")
val userCount = scoreAndLabels.select(userId).distinct().count().toInt
println(s"userCount = ${userCount}")
var result = scoreAndLabels.repartitionByRange(1000, col(userId)).rdd.mapPartitions({
val results = ArrayBuffer[(Long,Double)]()
iter =>
{
val scoreAndLabelMap = HashMap[String, Array[(Double, Double)]]()
for (row scoreAndLabelArray)
} else {
var scoreAndLabelArray = Array[(Double, Double)]()
scoreAndLabelArray :+= ((score, label))
scoreAndLabelMap += (id -> scoreAndLabelArray)
}
}
val users = scoreAndLabelMap.keys
for (user ((x._1 + y._1),(x._2 + y._2)))
val totalImpression = result._1
val totalAUC = result._2
println(s"totalImpression = ${totalImpression}")
println(s"totalAUC = ${totalAUC}")
val gauc = if (totalImpression != 0) (totalAUC / totalImpression.toDouble).formatted("%.8f").toDouble else 0.0d
println(s"GAUC = ${gauc}")
gauc
}
def main(args: Array[String]): Unit = {
val scoreAndLabels = scala.collection.mutable.ArrayBuffer[(Double, Double)]()
scoreAndLabels += ((0.4, 1))
scoreAndLabels += ((0.4, 0))
scoreAndLabels += ((0.3, 0))
scoreAndLabels += ((0.35, 0))
scoreAndLabels.toArray
val roc_line = roc(scoreAndLabels.toArray)
println(roc_line.mkString(","))
val auc_value = areaUnderROC(roc_line)
println(auc_value)
}
代码已经测试可用,希望对大家有用。
python版的代码可以参考:
https://github.com/qiaoguan/deep-ctr-prediction/blob/master/DeepCross/metric.py
广告推荐算法(group auc)评价指标及Spark实现代码相关推荐
- 数据与广告系列三十二:重排ReRank,广告推荐算法链路上的背叛者,生态系统格局的重塑者...
作者·黄崇远 『数据虫巢』 全文23138字 题图ssyer.com " 在推荐系统又或者计算广告中,重排ReRank明目张胆的把召回.粗排.精排几个链路逻辑辛苦生成的序给打乱,但却敢号称是 ...
- 推荐算法的准确度评价指标:
1.预测准确度:比如MAE,RMSE 2.分类准确度: 分类准确度定义为推荐算法对一个产品用户是否喜欢判定正确的比例.因此 ,当用户只有二元选择时 ,用分类准确度进行评价较为合适.因此,想要用准确率和 ...
- 字节跳动UG部门广告推荐算法实习面经
一面 算法题:355. 设计推特 AUC怎么计算 如果把预测结果随机分成两部分,这两个AUC有什么关系 熟悉的优化器有哪些 说一下adam adam的一阶矩和二阶矩对于梯度遇到陡峭和平坦的部分怎么处理 ...
- 【大会信息分享】新一代推荐算法核心技术与实践
导读:12月20日,09:00-12:40,受DataFunTalk组委会邀请,阿里妈妈资深算法专家朱小强老师将担任DataFunTalk年终大会推荐算法论坛出品人,并邀请来自阿里.快手.58同城.陌 ...
- 人工智能导论实验——基于MindSpore的广告推荐
实验目的 掌握Wide&Deep算法的基本原理以及点击率预测的实验流程. 实验任务 基于Criteo数据,使用推荐系统的深度学习算法wide&deep实现广告推荐. 实验步骤 在命令行 ...
- 基于python 的电影推荐算法_基于python语言编程的矩阵分解电影推荐算法
[实例简介]一种基于矩阵分解方法的电影推荐算法 [实例截图] [核心代码] import numpy as np from numba import cuda, float64, jit from s ...
- 推荐算法(四)——经典模型 DeepFM 模型详解及代码实践
目录 1 介绍 2 模型结构 3 实验结果 4 总结 5 代码实践 1 介绍 DeepFM 是华为诺亚方舟实验室在 2017 年提出的模型. 论文传送门: A Factorization-Machin ...
- 推荐系统[一]:超详细知识介绍,一份完整的入门指南,解答推荐系统相关算法流程、衡量指标和应用,以及如何使用jieba分词库进行相似推荐,业界广告推荐技术最新进展
搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排).系统架构.常见问题.算法项目实战总结.技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排 ...
- 推荐系统系列——推荐算法评价指标
文章目录 同步读书之<菜根谭> 9.静坐观心,真妄毕现 10.得意早回头,拂心莫停手 推荐算法评价指标 1 评分预测指标 1.1 符号定义 1.2 平均绝对误差 1.3 均方根误差 1.4 ...
最新文章
- 如何在GNOME中添加自己的菜单项
- python2.7读写xls
- 日元兑换——国内兑换需要护照和签证,国外的机场有兑换ATM
- BaiDu往年面试题目汇总☆WM☆
- 分享:MetaModel 3.2.5 发布,数据库元模型
- dns 主从 windows
- 绘图库:Matplotlib
- smbclient用法
- matlab ifft频率分辨率,matlab中关于FFT的使用(理解频率分辨率、补零问题)
- 下行文格式图片_谁能告诉我公文的下行文\上行文的模板
- co作为前缀的意思_com,con,col,cor等前缀为什么表示共同的意思?
- Scrapy爬虫代理IP的使用
- 跟着团子学SAP PS—项目结算规则的自动生成 CJB2/CJB1 (ETO模式下正确结算规则设定案例)
- 定时监控Ubuntu系统HDMI热插拔进行锁屏操作
- 有趣又实用的软件,给生活增添趣味
- 简单爬虫——京东网图书爬取
- 利用javascript获取图片的top N主色值
- 二分查找(Binary Search)需要注意的问题,以及在数据库内核中的实现
- 电快速脉冲群EFT(概念)
- 一文看懂嵌入式总线技术,ISA总线最流行?
热门文章
- 基于51单片机的数字频率计设计
- SwitchBox系列 - 介绍
- GitHub Action入门简介
- JavaWeb学习——用户登录案例(13)
- TypeScript 入门教程
- Dirty Pipe linux内核提权漏洞分析(CVE-2022-0847)
- 解题:POI 2011 Strongbox
- 邮政平邮/小包多个单号的物流信息是怎么同时查询的
- win7的远程桌面连接在哪
- ruoyi-cloud 服务器端idea启动报错Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.6.0:exec (defau