写点什么

Collective 的 Spark ML 经验分享:读者模型

  • 2015-11-19
  • 本文字数:3803 字

    阅读完需:约 12 分钟

【编者的话】 Collective 成立于 2005 年,其总部位于纽约,是一家从事数字广告业务的公司。 该公司的数字广告业务非常依赖于机器学习和预测模型,对于特定的用户在特定的时间应该投放什么样的广告完全是由实时或者离线的机器学习模型决定的。本文来自 Databricks 的技术博客,Eugene Zhulenev 分享了自己在 Collective 公司从事机器学习和读者模型工作的经验

Collective 公司有很多使用机器学习的项目,这些项目可以统称为读者模型,因为这些项目都是基于用户的浏览历史、行为数据等因素预测读者转化、点击率等信息的。在机器学习库的选择上,Collective 公司内部新开发的大部分项目都是基于 Spark 和 Spark MLLib 的,对于一些被大家广泛使用而 Spark 并不具备的工具和类库 Collective 还专门创建了一个扩展库 Spark Ext 。在本文中,Eugene Zhulenev 介绍了如何使用 Spark Ext 和 Spark ML 两个类库基于地理位置信息和浏览历史数据来预测用户转化。

预测数据

预测数据包含两种数据集,虽然这些数据都是使用虚拟的数据生成器生成的,但是它们与数字广告所使用的真实数据非常相似。这两类数据分别是:

重要通知:接下来 InfoQ 将会选择性地将部分优秀内容首发在微信公众号中,欢迎关注 InfoQ 微信公众号第一时间阅读精品内容。<>

用户的浏览历史日志

复制代码
Cookie | Site | Impressions
--------------- |-------------- | -------------
wKgQaV0lHZanDrp | live.com | 24
wKgQaV0lHZanDrp | pinterest.com | 21
rfTZLbQDwbu5mXV | wikipedia.org | 14
rfTZLbQDwbu5mXV | live.com | 1
rfTZLbQDwbu5mXV | amazon.com | 1
r1CSY234HTYdvE3 | youtube.com | 10

经纬度地理位置日志

复制代码
Cookie | Lat | Lng | Impressions
--------------- |---------| --------- | ------------
wKgQaV0lHZanDrp | 34.8454 | 77.009742 | 13
wKgQaV0lHZanDrp | 31.8657 | 114.66142 | 1
rfTZLbQDwbu5mXV | 41.1428 | 74.039600 | 20
rfTZLbQDwbu5mXV | 36.6151 | 119.22396 | 4
r1CSY234HTYdvE3 | 42.6732 | 73.454185 | 4
r1CSY234HTYdvE3 | 35.6317 | 120.55839 | 5
20ep6ddsVckCmFy | 42.3448 | 70.730607 | 21
20ep6ddsVckCmFy | 29.8979 | 117.51683 | 1

转换预测数据

正如上面所展示的,预测数据是长格式,对于每一个 cookie 与之相关的记录有多条,通常情况下,这种格式并不适合于机器学习算法,需要将其转换成“主键——特征向量”的形式。

Gather 转换程序
受到了 R 语音 tidyrreshape2包的启发,Collective 将每一个键对应的值的长数据框(long DataFrame)转换成一个宽数据框(wide DataFrame),如果某个键对应多个值就应用聚合函数。

复制代码
val gather = new Gather()
.setPrimaryKeyCols("cookie")
.setKeyCol("site")
.setValueCol("impressions")
.setValueAgg("sum") // 通过 key 对 impression 的值求和
.setOutputCol("sites")
val gatheredSites = gather.transform(siteLog)

转换后的结果

复制代码
Cookie | Sites
-----------------|----------------------------------------------
wKgQaV0lHZanDrp | [
| { site: live.com, impressions: 24.0 },
| { site: pinterest.com, impressions: 21.0 }
| ]
rfTZLbQDwbu5mXV | [
| { site: wikipedia.org, impressions: 14.0 },
| { site: live.com, impressions: 1.0 },
| { site: amazon.com, impressions: 1.0 }
| ]

Google S2 几何单元 Id 转换程序

Google S2 几何类库是一个球面几何类库,该库非常适合于操作球面(通常是地球)上的区域和索引地理数据,它会为地球上的每一个区域分配一个唯一的单元 Id。

为了将经纬度信息转换成键值对的形式,Eugene Zhulenev 结合使用了 S2 类库和 Gather,转换后数据的键值是 S2 的单元 Id。

复制代码
// Transform lat/lon into S2 Cell Id
val s2Transformer = new S2CellTransformer()
.setLevel(5)
.setCellCol("s2_cell")
// Gather S2 CellId log
val gatherS2Cells = new Gather()
.setPrimaryKeyCols("cookie")
.setKeyCol("s2_cell")
.setValueCol("impressions")
.setOutputCol("s2_cells")
val gatheredCells = gatherS2Cells.transform(s2Transformer.transform(geoDf))

转换后的结果

复制代码
Cookie | S2 Cells
-----------------|----------------------------------------------
wKgQaV0lHZanDrp | [
| { s2_cell: d5dgds, impressions: 5.0 },
| { s2_cell: b8dsgd, impressions: 1.0 }
| ]
rfTZLbQDwbu5mXV | [
| { s2_cell: d5dgds, impressions: 12.0 },
| { s2_cell: b8dsgd, impressions: 3.0 },
| { s2_cell: g7aeg3, impressions: 5.0 }
| ]

生成特征向量

虽然 Gather 程序将与某个 cookie 相关的所有信息都组织到了一行中,变成了键值对的形式,但是这种形式依然不能作为机器学习算法的输入。为了能够训练一个模型,预测数据需要表示成 double 类型的向量。

Gather 编码程序
使用虚拟变量对明确的键值对进行编码。

复制代码
// Encode S2 Cell data
val encodeS2Cells = new GatherEncoder()
.setInputCol("s2_cells")
.setOutputCol("s2_cells_f")
.setKeyCol("s2_cell")
.setValueCol("impressions")
.setCover(0.95) // dimensionality reduction

原始数据

复制代码
Cookie | S2 Cells
-----------------|----------------------------------------------
wKgQaV0lHZanDrp | [
| { s2_cell: d5dgds, impressions: 5.0 },
| { s2_cell: b8dsgd, impressions: 1.0 }
| ]
rfTZLbQDwbu5mXV | [
| { s2_cell: d5dgds, impressions: 12.0 },
| { s2_cell: g7aeg3, impressions: 5.0 }
| ]

转换后的结果

复制代码
Cookie | S2 Cells Features
-----------------|------------------------
wKgQaV0lHZanDrp | [ 5.0 , 1.0 , 0 ]
rfTZLbQDwbu5mXV | [ 12.0 , 0 , 5.0 ]

对于转换后的结果,用户还可以根据场景选择性地使用顶部转换进行降维。首先计算不同用户每个特征的值,然后根据特征值进行降序排序,最后从结果列表中选择最上面那些数值总和占所有用户总和的百分比超过某个阈值(例如,选择最上面覆盖 99% 用户的那些网站)的数据作为最终的分类值。

Spark ML 管道

Spark ML 管道是 Spark MLLib 的一个新的高层 API。一个真正的 ML 管道通常会包含数据预处理、特征提取、模型拟合和验证几个阶段。例如,文本文档的分类可能会涉及到文本分割与清理、特征提取、使用交叉验证训练分类模型这几步。在使用 Spark ML 时,用户能够将一个 ML 管道拆分成多个独立的阶段,然后可以在一个单独的管道中将他们组合到一起,最后使用交叉验证和参数网格运行该管道从而找到最佳参数集合。

使用 Spark ML 管道将它们组合到一起

复制代码
// Encode site data
val encodeSites = new GatherEncoder()
.setInputCol("sites")
.setOutputCol("sites_f")
.setKeyCol("site")
.setValueCol("impressions")
// Encode S2 Cell data
val encodeS2Cells = new GatherEncoder()
.setInputCol("s2_cells")
.setOutputCol("s2_cells_f")
.setKeyCol("s2_cell")
.setValueCol("impressions")
.setCover(0.95)
// Assemble feature vectors together
val assemble = new VectorAssembler()
.setInputCols(Array("sites_f", "s2_cells_f"))
.setOutputCol("features")
// Build logistic regression
val lr = new LogisticRegression()
.setFeaturesCol("features")
.setLabelCol("response")
.setProbabilityCol("probability")
// Define pipeline with 4 stages
val pipeline = new Pipeline()
.setStages(Array(encodeSites, encodeS2Cells, assemble, lr))
val evaluator = new BinaryClassificationEvaluator()
.setLabelCol(Response.response)
val crossValidator = new CrossValidator()
.setEstimator(pipeline)
.setEvaluator(evaluator)
val paramGrid = new ParamGridBuilder()
.addGrid(lr.elasticNetParam, Array(0.1, 0.5))
.build()
crossValidator.setEstimatorParamMaps(paramGrid)
crossValidator.setNumFolds(2)
println(s"Train model on train set")
val cvModel = crossValidator.fit(trainSet)

结论

Spark ML API 让机器学习变得更加容易。同时,用户还可以通过 Spark Ext 创建自定义的转换 / 估计,并对这些自定义的内容进行组装使其成为更大管道中的一部分,此外这些程序还能够很容易地在多个项目中共享和重用。如果想要查看本示例的代码,可以点击这里

编后语

《他山之石》是InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到editors@cn.infoq.com。


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。

2015-11-19 18:002327
用户头像

发布了 321 篇内容, 共 126.7 次阅读, 收获喜欢 19 次。

关注

评论

发布
暂无评论
发现更多内容

模块二作业

晨晨

Linux之df命令

入门小站

Linux

分析微信朋友圈的高性能复杂度

feitian

架构实战营 -- 模块二

小牧ah

架构实战营

ACM金牌选手算法讲解《线性表》

编程熊

算法 LeetCode 线性表 数据结构与算法

架构实战营 模块二 作业

三叔叔_拖延症晚期

架构实战营

模块2作业G20210698020270

哆啦A萌

Rust从0到1-并发-状态共享

rust 并发 Concurrency 状态共享 Shared-State

Vue进阶(四十三):Vuex之Mutations详解

No Silver Bullet

Vue 7月日更 mutations

架构实战营 - 模块 2 - 作业

Vincent

架构实战营

架构训练营模块二作业

以吻封笺

手写插入排序算法

实力程序员

程序员 算法 排序 实力

微信朋友圈复杂度分析

buoge

领域驱动设计中的分层模型

escray

学习 极客时间 7月日更 如何落地业务建模

大数据训练营一期0711作业

朱磊

微信朋友圈的高性能复杂度分析

tjudream

架构 高性能 朋友圈

架构实战营模块二作业

A-领悟 Lifetruth‖

#架构实战营

【架构实战营】模块二作业

Abner S.

架构实战营 #架构实战营

模块二作业 微信朋友圈高性能复杂度分析

君子意如何

「架构师训练营第 1 期」

WICC 2021即将召开 荔枝将揭秘高音质体验之关键技术

融云 RongCloud

模块一作业-架构训练营

零度

「架构师训练营第 1 期」

Python OpenCV 图像开闭操作,图像处理取经之旅第 39 篇

梦想橡皮擦

7月日更

Vue进阶(九十七):对象动态添加属性和值

No Silver Bullet

Vue set 7月日更

Pandas高级教程之:window操作

程序那些事

Python 数据分析 pandas 程序那些事

浪潮云洲走进包头 展示特色产业“触网”路径

工业互联网

模块二作业

king

架构模块2 作业

柱林

模块二作业

秀聪

架构实战营

【Flutter 专题】82 初识 Flutter Stream (二)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 7月日更

在线XML转CSV工具

入门小站

工具

架构实战营模块 2 作业

zlz

Collective的Spark ML经验分享:读者模型_语言 & 开发_孙镜涛_InfoQ精选文章