[转] - Weiflow——微博机器学习框架
本文从开发效率(易用性)、可扩展性、执行效率三个方面,介绍了微博机器学习框架Weiflow在微博的应用和最佳实践。
在上期《基于Spark的大规模机器学习在微博的应用》一文中我们提到,在机器学习流中,模型训练只是其中耗时最短的一环。如果把机器学习流比作烹饪,那么模型训练就是最后翻炒的过程;烹饪的大部分时间实际上都花在了食材、佐料的挑选,洗菜、择菜,食材再加工(切丁、切块、过油、预热)等步骤。在微博的机器学习流中,原始样本生成、数据处理、特征工程、训练样本生成、模型后期的测试、评估等步骤所需要投入的时间和精力,占据了整个流程的80%之多。如何能够高效地端到端进行机器学习流的开发,如何能够根据线上的反馈及时地选取高区分度特征,对模型进行优化,验证模型的有效性,加速模型迭代效率,满足线上的要求,都是我们需要解决的问题。
Weiflow的诞生源自于微博机器学习流的业务需求,在微博的机器学习流图中(如图1所示),多种数据流(如发博流、曝光流、互动流)经过Spark Streaming、Storm的实时处理,存储至特征工程并生成离线的原始样本。在离线系统,根据业务人员的开发经验,对原始样本进行各式各样的数据处理(统计、清洗、过滤、采样等)、特征处理、特征映射,从而生成可训练的训练样本;业务人员根据实际业务场景(排序、推荐),选择不同的算法模型(LR、GBDT、频繁项集、SVM、DNN等),进行模型训练、预测、测试和评估;待模型迭代满足要求后,通过自动部署将模型文件和映射规则部署到线上。线上系统根据模型文件和映射规则,从特征工程中拉取相关的特征值,并根据映射规则进行预处理,生成可用于预测的样本格式,进行线上的实时预测,最终将预测的结果(用户对微博内容的兴趣程度)输出,供线上服务调用。
图1 微博机器学习流图
图1 微博机器学习流图
Weiflow的设计初衷就是将微博机器学习流的开发简单化、傻瓜化,让业务开发人员从纷繁复杂的数据处理、特征工程、模型工程中解脱出来,将宝贵的时间和精力投入到业务场景的开发和优化当中,彻底解放业务人员的生产力,大幅提升开发效率。
考虑到微博业务场景越来越复杂、多样的趋势,Weiflow在设计之初就充分考虑并权衡了框架的开发效率、可扩展性和执行效率。Weiflow通过统一格式的配置文件式开发(XML流程文件),允许业务人员像搭积木一样灵活地将需要用到的模块(数据处理、特征映射、生成训练样本、模型的训练、预测、测试、评估等)堆叠到一起,根据依赖关系形成计算流图(Directed Acyclic Graph有向无环图),Weiflow将自动解析不同模块之间的依赖关系,并调用每个模型的执行类进行流水线式的作业。对于每一个计算模块,用户无需关心其内部实现、执行效率,只需关心与业务开发相关的参数调优,如算法的超参数、数据采样率、采样方式、特征映射规则、数据统计方式、数据清洗规则等等,从而大幅提升开发效率、模型迭代速度。为了让更多的开发者(包括具有代码能力的业务人员)能够参与到Weiflow的开发中来,Weiflow设计并提供了丰富的多层次抽象,基于预定义的基类和接口,允许开发者根据新的业务需求实现自己的处理模块(如新的算法模型训练、预测、评估模块)、计算函数(如复杂的特征计算公式、特征组合函数等),从而不断丰富、扩展Weiflow的功能。在框架的执行效率方面,在第二层DAG中(后面将详细介绍Weiflow的双层DAG结构),充分利用各种计算引擎(Spark、Tensorflow、Hive、Storm、Flink等)的优化机制,同时结合巧妙的数据结构设计与开发语言(如Scala的Currying、Partial Functions等)本身的特性,保证框架在提供足够的灵活性和近乎无限的可扩展性的基础上,尽可能地提升执行性能。
为了应对微博多样的计算环境(Spark、Tensorflow、Hive、Storm、Flink等),Weiflow采用了双层的DAG任务流设计,如图2所示。
图2 Weiflow双层DAG任务流设计
图2 Weiflow双层DAG任务流设计
外层的DAG由不同的node构成,每一个node具备独立的执行环境,即上文提及的Spark、Tensorflow、Hive、Storm、Flink等计算引擎。外层DAG设计的初衷是让最合适的锤子去敲击最适合的钉子,大多数计算引擎因其设计阶段的历史局限性,都很难做到兼顾所有的工作负载类型,而是在不同程度上更好地支持某些负载(如批处理、流式实时处理、即时查询、分析型数据仓库、机器学习、图计算、交易型数据库等),因此我们的思路是让用户选择最适合自己业务负载的计算引擎。内层的DAG,根据计算引擎的不同,利用引擎的特性与优化机制,实现不同的抽象作为DAG中计算模块之间数据交互的载体。例如在Spark node中,我们会充分挖掘并利用Spark已有的优化策略和数据结构,如Datasets、Dataframe、Tungsten、Whole Stage Code Generation,并将Dataframe作为Spark node内DAG数据流的载体。在每一个node内部,根据其在DAG中上下游的位置,提供了三种操作类型的抽象,即Input、Process、Output。Input基类定义了Spark node中输入数据的格式、读取和解析规范,用户可以根据Spark支持的数据源,创建各种格式的Input,如图2中示例的Parquet、Orc、Json、Text、CSV。当然用户也可以定义自己的输入格式,如图2中示例的Libsvm。在微博的机器学习模型训练中,有一部分场景是需要Libsvm格式数据作为训练样本,用户可以通过实现Input中定义的规范和接口,实现Libsvm格式数据的读入模块。通过Input读入的数据会被封装为Dataframe,传递给下游的Process类处理模块。Process基类定义了用户计算逻辑的通用规范和接口,通过实现Process基类中的函数,开发者可以灵活地实现自己的计算逻辑,如图2中示例的数据统计、清洗、过滤、组合、采样、转换等,与机器学习相关的模型训练、预测、测试等步骤,都可以在Process环节实现。通过Process处理的数据,依然被封装为Dataframe,并传递给下游的Output类处理模块。Output类将Process类传递的数据进一步处理,如模型评估、输出数据存储、模型文件存储、输出AUC等,最终将结果以不同的方式(磁盘存储、屏幕打印等)输出。需要指出的是,凡是Input支持的数据读入格式,Output都有对应的存储格式支持,从而形成逻辑上的闭环。
在使用方面,业务人员根据事先约定好的规范和格式,将双层DAG的计算逻辑定义在XML配置文件中。依据用户在XML指定的依赖关系和处理模块类,Weiflow将自动生成DAG任务流图,并在运行时阶段调用处理模块的实现类来完成用户指定的任务流。代码1展示了微博应用广泛的GBDT+LR模型训练流程的开发示例(由于篇幅有限,示例中只保留了第一个node的细节),代码1示例的训练流程所构成的双层DAG依赖及任务流图如图3所示。通过在XML配置文件中将所需计算模块按照依赖关系(外层的node依赖关系与内层的计算逻辑依赖关系)堆叠,即可以搭积木的方式完成配置化、模块化的流水线作业开发。
GBDTtraining</
node>
GBDTplusLR
com.weibo.datasys.
dataflow.input.InputSparkText
hdfs://path/of/your/
data
/path/of/your/meta</
metaPath>
\t</
fieldDelimiter>
com.weibo.
datasys.dataflow.process.
ProcessSparkGBDTTraining
input1
gbdt.data.conf
com.weibo.datasys.
dataflow.output.OutputSparkGBDTModel</
className>
process1</
dependency>
hdfs://path/of/your/
data
代码1 用Weiflow完成微博GBDT+LR模型训练流程
图3 Weiflow中微博GBDT+LR模型训练流程的双层DAG依赖关系及任务流图
图3 Weiflow中微博GBDT+LR模型训练流程的双层DAG依赖关系及任务流图
通过灵活的模块化开发,业务人员大幅提升了机器学习、数据科学作业的效率。随着微博的业务场景越来越复杂,业务需求也呈多样化的发展趋势,为了让更多的开发者灵活地扩展Weiflow的功能,Weiflow在设计之初便充分考量了框架的可扩展性。Weiflow通过多层次、模块化的抽象,提供近乎无限的扩展能力。
多层次的抽象是为了满足DAG外层计算引擎(上文提及的Spark、Tensorflow、Hive、Storm、Flink等)的可扩展性,通过Top level abstraction提供的高度抽象定义,DAG外层的各个计算引擎只需继承Top level抽象中定义的属性和方法,即可实现对计算引擎层面抽象的实现。如图4所示,黑色文本框中的Top level abstraction提供了多个抽象Base,蓝色文本框中不同的执行引擎通过继承其属性和方法,提供更加具体的抽象实现。当有新的计算引擎(如Apache Flink)需要添加至Weiflow时,用户只需将新定义的计算引擎类继承Top level的抽象类,即可提供该引擎的抽象实现。
模块化的抽象是从业务处理的角度出发,从业务需求中抽象出基础、通用的模块概念,进而定义这些基本模块的基础属性和基础方法。如图4所示各文本框中分别定义、继承、实现了四大基础模块,即Node、Input、Process和Output。Node基础类定义了计算引擎相关的基础属性,如数据流通媒介、执行环境、运行时数据流方式、运行参数抽象等。Input基础类为计算引擎定义了该引擎内支持的所有输入类型,如Spark引擎中支持Parquet、Orc、Json、CSV、Text等,并将输入类型转换为数据流通媒介(如Spark执行引擎的Dataframe、RDD)。在Weiflow的实现过程中(后文将详细介绍Weiflow实现与优化的最佳实践),每个node内部的模块实现都充分利用了现有引擎的数据结构与优化机制,如在Spark node中,我们充分利用了Spark原生支持的功能(如对各种数据源的支持)和性能优化(如Tungsten优化机制、二进制数据结构、Whole Stage Code Generation等)。例如在Input基础类中,我们通过Spark原生数据源的支持,提供了多种压缩、纯文本格式的输入供用户选择。通过实现Input基础类中定义的对象和方法,开发者可以灵活地实现业务所需的数据格式,如前文提及的Libsvm格式。Process基础类囊括了所有业务处理逻辑,在实现方面,同样利用了所在引擎所提供的各种原生支持。如在Spark node中,通过Spark SQL或Dataframe DSL(Domain Specific Language)可以轻松地实现大部分处理逻辑,如数据统计、清洗、过滤、联接等操作。当开发者需要实现新的业务逻辑时,如对数据按比例进行向上、向下采样,只需继承Process基础类中定义的属性和方法,充分利用Spark Dataframe和RDD的开放API,将采样的具体实现封装到既定的接口内,即可完成开发,进而扩展Weiflow功能,供业务人员使用。与Input相对应,Output基础类定义了Weiflow在计算引擎内的各种数据格式的输出,提供了与Input相对应的接口,如Input提供了read接口,Output则提供了write接口,形成逻辑层面的闭环。
通过Weiflow多层次、模块化的抽象机制,开发者可以轻松地在执行引擎和业务功能方面进行扩展,从而满足不断变化的业务需求。前文提到,自2016年以来,微博业务步入了二次繁荣,微博的业务呈多样、复杂的发展趋势,用户、博文、互动相关的数据呈爆炸式增长,机器学习规模化的挑战迫在眉睫。为了满足微博机器学习规模化的需求,Weiflow在设计之初就充分考虑到实现中的执行效率问题。
图4 Weiflow开放API的抽象层次
图4 Weiflow开放API的抽象层次
Weiflow在实现方面分别从语言特性、数据结构、引擎优化等几个方面考虑,优化任务执行性能。考虑到Scala函数式编程语言的灵活性、丰富算子、超高的开发效率及其并发能力,Weiflow框架的主干代码和Spark node部分业务实现都采用Scala来实现。
对于业务人员来说,XML配置开发文件即是Weiflow的入口。Weiflow通过Scala的XML内置模块对用户提供的XML文件进行解析并生成相应的数据结构,如DAG node,模块间依赖关系等。在成功解析各模块的依赖关系后,Weiflow通过Scala语言的懒值特性和Call By Name机制,将依赖关系转化为DAG网络图,并通过调用Output实现类中提供的Action函数(Output.write),触发整个DAG网络的回溯执行。在回溯执行阶段,Weiflow调取用户XML文件中提供的实现类,通过Scala语言提供的反射机制,在运行时生成实现类对象,完成计算逻辑的执行。
在执行效率方面,Weiflow充分利用了Scala的语言特性来大幅提升整体执行性能。在微博的大部分机器学习应用场景中,需要利用各种处理函数(如log10、hash、特征组合、公式计算等)将原始特征映射到高维特征空间。其中一部分复杂函数(如pickcat,根据字符串列表反查字符串索引)需要多个输入参数。这类函数首先通过第一个参数,如pickcat函数所需的字符串列表(在规模化机器学习应用中会变得异常巨大),生成预定义的数据结构,然后通过第二个参数反查该数据结构,并返回其在数据结构中的索引。对于这样的需求,如果采用传统编程语言中的函数来实现,将带来巨大的计算开销。处理函数被定义后,通过闭包发送到各执行节点(如Spark中的Executor),在执行节点遍历数据时,该函数将每次执行读取第一个字符串列表参数、生成特定数据结构的任务;然后读取第二个字符串参数,反查数据结构并返回索引。但业务人员真正关心的是第二个参数所返回的索引值,无需每次遍历数据都运行生成数据结构的任务,因此该函数在执行节点的运行带来大量不必要的计算开销。然而通过Scala语言中的Currying特性,可以很容地解决上述问题。在Scala中,函数为一等公民,且所有函数均为对象。通过将pickcat函数柯里化,将pickcat处理第一个参数的过程封装为另一个函数(pickcat_),然后将该函数通过闭包发送到执行节点,执行引擎在遍历数据时,其所见的函数pickcat_将只接收一个参数,也即原函数pickcat的第二个参数,然后处理反查索引的计算即可。当然,柯里化只是Scala函数式编程语言丰富的特性之一,其他特性诸如Partial functions、Case class、Pattern matching、Function chain等都被应用到了Weiflow的实现之中。
在数据结构的设计和选择上,Weiflow的实现经历了从简单粗暴到精雕细琢的变迁。在Weiflow的初期版本中,因为当时还没有遇到规模化计算的挑战,出于开发效率的考虑,数据结构大量采用了不可变长数组,此时并未遇到任何性能瓶颈。但当Weiflow承载大规模计算时,执行性能几乎无法容忍。经过排查发现,原因在于特征映射过程中,存在大量根据数据字典,反查数据值索引的需求,如上文提及的pickcat函数。面对千万级、亿级待检索数据,当数据字典以不可变长数组存储时,通过数据值反查索引的时间复杂度显而易见。后来通过调整数据字典结构,对多种数据结构进行对比、测试,最终将不可变长数组替换为HashMap,解决了反查索引的性能问题。在特征映射之后的生成Libsvm格式样本阶段中,也大量使用了数组数据结构,以稠密数组的方式实现了Libsvm数据值的存储。当特征空间维度上升到十亿、百亿级时,几乎无法正常完成生成样本的任务。通过仔细的分析业务场景发现,几乎所有的特征空间都是极其稀疏的,以10亿维的特征空间为例,其特征稀疏度通常都在千、万级别,将特征空间以稠密矩阵的方式存储和计算,无疑是巨大的浪费。最后通过将稠密矩阵替换为稀疏矩阵,解决了这一性能问题。
表1 采用Weiflow前后开发效率、可扩展性和执行效率的量化对比
表1 采用Weiflow前后开发效率、可扩展性和执行效率的量化对比
前文提到过,在Weiflow的双层DAG设计中,内存的DAG实现会充分地利用执行引擎已有的特性来提升执行性能。以Spark为例,在Weiflow的业务模块实现部分,充分利用了Spark的各种性能优化技巧,如Map Partitions、Broadcast variables、Dataframe、Aggregate By Key、Filter and Coalesce、Data Salting等。
经过多个方面的性能优化,Weiflow在执行效率方面已经完全能够胜任微博机器学习规模化的需求,如表1中所示对比,Weiflow优化后执行性能提升6倍以上。表1中同时列举了Weiflow在开发效率、易用性、可扩展性方面的优势和提升。
本文从开发效率(易用性)、可扩展性、执行效率三个方面,介绍了微博机器学习框架Weiflow在微博的应用和最佳实践,希望能够对读者提供有益的参考。
[转] - Weiflow——微博机器学习框架的更多相关文章
- 一个开源的,跨平台的.NET机器学习框架ML.NET
微软在Build 2018大会上推出的一款面向.NET开发人员的开源,跨平台机器学习框架ML.NET. ML.NET将允许.NET开发人员开发他们自己的模型,并将自定义ML集成到他们的应用程序中,而无 ...
- 基于Docker的TensorFlow机器学习框架搭建和实例源码解读
概述:基于Docker的TensorFlow机器学习框架搭建和实例源码解读,TensorFlow作为最火热的机器学习框架之一,Docker是的容器,可以很好的结合起来,为机器学习或者科研人员提供便捷的 ...
- 开源的,跨平台的.NET机器学习框架ML.NET
微软在Build 2018大会上推出的一款面向.NET开发人员的开源,跨平台机器学习框架ML.NET. ML.NET将允许.NET开发人员开发他们自己的模型,并将自定义ML集成到他们的应用程序中,而无 ...
- Python开源机器学习框架:Scikit-learn六大功能,安装和运行Scikit-learn
Python开源机器学习框架:Scikit-learn入门指南. Scikit-learn的六大功能 Scikit-learn的基本功能主要被分为六大部分:分类,回归,聚类,数据降维,模型选择和数据预 ...
- TensorFlow机器学习框架-学习笔记-001
# TensorFlow机器学习框架-学习笔记-001 ### 测试TensorFlow环境是否安装完成-----------------------------```import tensorflo ...
- 微博开源框架Motan初体验
前两天,我在开源中国的微信公众号看到新浪微博的轻量Rpc框架--Motan开源了.上网查了下,才得知这个Motan来头不小,支撑着新浪微博的千亿调用,曾经在2014年的春晚中有着千亿次的调用,对抗了春 ...
- 使用Java语言开发机器学习框架和参数服务器
https://github.com/wudikua/ps 本项目是我自己动手实现的机器学习训练框架,代码简单,有很多不完善,但是也保留了最小可用功能 通过自己编写这个项目,可以帮助自己入门机器学习 ...
- 了解机器学习框架CoreML
代码地址如下:http://www.demodashi.com/demo/11972.html CoreML是iOS 11新推出的机器学习框架,是人工智能的核心内容,他可以在训练好的机器学习模型应用到 ...
- 机器学习框架ML.NET学习笔记【4】多元分类之手写数字识别
一.问题与解决方案 通过多元分类算法进行手写数字识别,手写数字的图片分辨率为8*8的灰度图片.已经预先进行过处理,读取了各像素点的灰度值,并进行了标记. 其中第0列是序号(不参与运算).1-64列是像 ...
随机推荐
- MDD Cup 2017 小记
http://blog.csdn.net/zhangzhengyuan123123/article/details/78971298
- 【JVM】垃圾收集器
程序计数器.Java虚拟机栈.本地方法栈分配的内存是确定的,生命周期与线程同样.所以不须要过多考虑回收问题. 而Java堆和方法区仅仅有运行时才知道有哪些对象被创建,须要多少内存,这部分的内存分配和回 ...
- 类中添加log4j日志
在编写代码的时候需要随时查看工作日志,查看工作日志的好处就是随时能检查出错误.所以我一般就需要在编写代码的前期添加工作日志,以便更好的查看相关错误输出. 以一个springmvc小demo为例子 主 ...
- Windows两个网卡配置路由规则 同时访问内网和外网
电脑上有两个网卡,一个有线一个无线,有线连局域网,无线连外网,虽然两个网都连着,但还是会出现访问不通的情况. 这就要求我们自己来配置路由规则,让内网的访问走内网的网卡,外网的访问走外网的网卡. 一.查 ...
- codevs 2033 邮票
洛谷 P2725 邮票 Stamps codevs 2033 邮票 题目链接 http://codevs.cn/problem/2033/ https://www.luogu.org/problemn ...
- 朗科32G TF卡的读写测试
卡是这样的, 下面是实际测试的结果. 容量测试 SKS的USB2外置读卡器, X240内置读卡器加上SD卡套 UNITEK的USB3.0读卡器, 经过UNITEK的USB3.0 HUB 看来读4 ...
- MySQL读取配置文件的顺序、启动方式、启动原理
一.MySQL读取配置文件的顺序 读取顺序:/etc/my.cnf > /etc/mysql/my.cnf > /usr/etc/my.cnf > ~/.my.cnf 命令验证:[r ...
- Nginx的location匹配规则
一 Nginx的location语法 location [=|~|~*|^~] /uri/ { … } = 严格匹配.如果请求匹配这个location,那么将停止搜索并立即处理此请求 ...
- zabbix之微信告警(python版):微信个人报警,微信企业号告警脚本
微信个人告警脚本 微信个人告警:使用个人微信,发送到微信群组,微信好友 两个脚本执行: 1)能连接网络2)先执行server.py,扫描登录微信,登录之后没有报错,打开新终端查看端口是否起来了3)在z ...
- 分布式Id教程
转自:https://baijiahao.baidu.com/s?id=1584913615817222458&wfr=spider&for=pc 一,题记 所有的业务系统,都有生成I ...