机器学习资料合计(一)
https://zhuanlan.zhihu.com/p/32057026
今日头条算法原理(全)
http://mp.weixin.qq.com/s/DXPMZN9SwKTxI4roaQmeMw
今日头条资深算法架构师曹欢欢:
本次分享将主要介绍今日头条推荐系统概览以及内容分析、用户标签、评估分析,内容安全等原理。
- 系统概览
推荐系统,如果用形式化的方式去描述实际上是拟合一个用户对内容满意度的函数,这个函数需要输入三个维度的变量。第一个维度是内容。头条现在已经是一个综合内容平台,图文、视频、UGC小视频、问答、微头条,每种内容有很多自己的特征,需要考虑怎样提取不同内容类型的特征做好推荐。第二个维度是用户特征。包括各种兴趣标签,职业、年龄、性别等,还有很多模型刻划出的隐式用户兴趣等。第三个维度是环境特征。这是移动互联网时代推荐的特点,用户随时随地移动,在工作场合、通勤、旅游等不同的场景,信息偏好有所偏移。结合三方面的维度,模型会给出一个预估,即推测推荐内容在这一场景下对这一用户是否合适。
这里还有一个问题,如何引入无法直接衡量的目标?
推荐模型中,点击率、阅读时间、点赞、评论、转发包括点赞都是可以量化的目标,能够用模型直接拟合做预估,看线上提升情况可以知道做的好不好。但一个大体量的推荐系统,服务用户众多,不能完全由指标评估,引入数据指标以外的要素也很重要。
比如广告和特型内容频控。像问答卡片就是比较特殊的内容形式,其推荐的目标不完全是让用户浏览,还要考虑吸引用户回答为社区贡献内容。这些内容和普通内容如何混排,怎样控制频控都需要考虑。
此外,平台出于内容生态和社会责任的考量,像低俗内容的打压,标题党、低质内容的打压,重要新闻的置顶、加权、强插,低级别账号内容降权都是算法本身无法完成,需要进一步对内容进行干预。
下面我将简单介绍在上述算法目标的基础上如何对其实现。
前面提到的公式y = F(Xi ,Xu ,Xc),是一个很经典的监督学习问题。可实现的方法有很多,比如传统的协同过滤模型,监督学习算法Logistic Regression模型,基于深度学习的模型,Factorization Machine和GBDT等。
一个优秀的工业级推荐系统需要非常灵活的算法实验平台,可以支持多种算法组合,包括模型结构调整。因为很难有一套通用的模型架构适用于所有的推荐场景。现在很流行将LR和DNN结合,前几年Facebook也将LR和GBDT算法做结合。今日头条旗下几款产品都在沿用同一套强大的算法推荐系统,但根据业务场景不同,模型架构会有所调整。
模型之后再看一下典型的推荐特征,主要有四类特征会对推荐起到比较重要的作用。
第一类是相关性特征,就是评估内容的属性和与用户是否匹配。显性的匹配包括关键词匹配、分类匹配、来源匹配、主题匹配等。像FM模型中也有一些隐性匹配,从用户向量与内容向量的距离可以得出。
第二类是环境特征,包括地理位置、时间。这些既是bias特征,也能以此构建一些匹配特征。
第三类是热度特征。包括全局热度、分类热度,主题热度,以及关键词热度等。内容热度信息在大的推荐系统特别在用户冷启动的时候非常有效。
第四类是协同特征,它可以在部分程度上帮助解决所谓算法越推越窄的问题。协同特征并非考虑用户已有历史。而是通过用户行为分析不同用户间相似性,比如点击相似、兴趣分类相似、主题相似、兴趣词相似,甚至向量相似,从而扩展模型的探索能力。
模型的训练上,头条系大部分推荐产品采用实时训练。实时训练省资源并且反馈快,这对信息流产品非常重要。用户需要行为信息可以被模型快速捕捉并反馈至下一刷的推荐效果。我们线上目前基于storm集群实时处理样本数据,包括点击、展现、收藏、分享等动作类型。模型参数服务器是内部开发的一套高性能的系统,因为头条数据规模增长太快,类似的开源系统稳定性和性能无法满足,而我们自研的系统底层做了很多针对性的优化,提供了完善运维工具,更适配现有的业务场景。
目前,头条的推荐算法模型在世界范围内也是比较大的,包含几百亿原始特征和数十亿向量特征。整体的训练过程是线上服务器记录实时特征,导入到Kafka文件队列中,然后进一步导入Storm集群消费Kafka数据,客户端回传推荐的label构造训练样本,随后根据最新样本进行在线训练更新模型参数,最终线上模型得到更新。这个过程中主要的延迟在用户的动作反馈延时,因为文章推荐后用户不一定马上看,不考虑这部分时间,整个系统是几乎实时的。
但因为头条目前的内容量非常大,加上小视频内容有千万级别,推荐系统不可能所有内容全部由模型预估。所以需要设计一些召回策略,每次推荐时从海量内容中筛选出千级别的内容库。召回策略最重要的要求是性能要极致,一般超时不能超过50毫秒。
召回策略种类有很多,我们主要用的是倒排的思路。离线维护一个倒排,这个倒排的key可以是分类,topic,实体,来源等,排序考虑热度、新鲜度、动作等。线上召回可以迅速从倒排中根据用户兴趣标签对内容做截断,高效的从很大的内容库中筛选比较靠谱的一小部分内容。
二、内容分析
内容分析包括文本分析,图片分析和视频分析。头条一开始主要做资讯,今天我们主要讲一下文本分析。文本分析在推荐系统中一个很重要的作用是用户兴趣建模。没有内容及文本标签,无法得到用户兴趣标签。举个例子,只有知道文章标签是互联网,用户看了互联网标签的文章,才能知道用户有互联网标签,其他关键词也一样。
另一方面,文本内容的标签可以直接帮助推荐特征,比如魅族的内容可以推荐给关注魅族的用户,这是用户标签的匹配。如果某段时间推荐主频道效果不理想,出现推荐窄化,用户会发现到具体的频道推荐(如科技、体育、娱乐、军事等)中阅读后,再回主feed,推荐效果会更好。因为整个模型是打通的,子频道探索空间较小,更容易满足用户需求。只通过单一信道反馈提高推荐准确率难度会比较大,子频道做的好很重要。而这也需要好的内容分析。
上图是今日头条的一个实际文本case。可以看到,这篇文章有分类、关键词、topic、实体词等文本特征。当然不是没有文本特征,推荐系统就不能工作,推荐系统最早期应用在Amazon,甚至沃尔玛时代就有,包括Netfilx做视频推荐也没有文本特征直接协同过滤推荐。但对资讯类产品而言,大部分是消费当天内容,没有文本特征新内容冷启动非常困难,协同类特征无法解决文章冷启动问题。
今日头条推荐系统主要抽取的文本特征包括以下几类。首先是语义标签类特征,显式为文章打上语义标签。这部分标签是由人定义的特征,每个标签有明确的意义,标签体系是预定义的。此外还有隐式语义特征,主要是topic特征和关键词特征,其中topic特征是对于词概率分布的描述,无明确意义;而关键词特征会基于一些统一特征描述,无明确集合。
另外文本相似度特征也非常重要。在头条,曾经用户反馈最大的问题之一就是为什么总推荐重复的内容。这个问题的难点在于,每个人对重复的定义不一样。举个例子,有人觉得这篇讲皇马和巴萨的文章,昨天已经看过类似内容,今天还说这两个队那就是重复。但对于一个重度球迷而言,尤其是巴萨的球迷,恨不得所有报道都看一遍。解决这一问题需要根据判断相似文章的主题、行文、主体等内容,根据这些特征做线上策略。
同样,还有时空特征,分析内容的发生地点以及时效性。比如武汉限行的事情推给北京用户可能就没有意义。最后还要考虑质量相关特征,判断内容是否低俗,色情,是否是软文,鸡汤?
分类的目标是覆盖全面,希望每篇内容每段视频都有分类;而实体体系要求精准,相同名字或内容要能明确区分究竟指代哪一个人或物,但不用覆盖很全。概念体系则负责解决比较精确又属于抽象概念的语义。这是我们最初的分类,实践中发现分类和概念在技术上能互用,后来统一用了一套技术架构。
目前,隐式语义特征已经可以很好的帮助推荐,而语义标签需要持续标注,新名词新概念不断出现,标注也要不断迭代。其做好的难度和资源投入要远大于隐式语义特征,那为什么还需要语义标签?有一些产品上的需要,比如频道需要有明确定义的分类内容和容易理解的文本标签体系。语义标签的效果是检查一个公司NLP技术水平的试金石。
今日头条推荐系统的线上分类采用典型的层次化文本分类算法。最上面Root,下面第一层的分类是像科技、体育、财经、娱乐,体育这样的大类,再下面细分足球、篮球、乒乓球、网球、田径、游泳等,足球再细分国际足球、中国足球,中国足球又细分中甲、中超、国家队等,相比单独的分类器,利用层次化文本分类算法能更好地解决数据倾斜的问题。有一些例外是,如果要提高召回,可以看到我们连接了一些飞线。这套架构通用,但根据不同的问题难度,每个元分类器可以异构,像有些分类SVM效果很好,有些要结合CNN,有些要结合RNN再处理一下。
上图是一个实体词识别算法的case。基于分词结果和词性标注选取候选,期间可能需要根据知识库做一些拼接,有些实体是几个词的组合,要确定哪几个词结合在一起能映射实体的描述。如果结果映射多个实体还要通过词向量、topic分布甚至词频本身等去歧,最后计算一个相关性模型。
三、用户标签
内容分析和用户标签是推荐系统的两大基石。内容分析涉及到机器学习的内容多一些,相比而言,用户标签工程挑战更大。
今日头条常用的用户标签包括用户感兴趣的类别和主题、关键词、来源、基于兴趣的用户聚类以及各种垂直兴趣特征(车型,体育球队,股票等)。还有性别、年龄、地点等信息。性别信息通过用户第三方社交账号登录得到。年龄信息通常由模型预测,通过机型、阅读时间分布等预估。常驻地点来自用户授权访问位置信息,在位置信息的基础上通过传统聚类的方法拿到常驻点。常驻点结合其他信息,可以推测用户的工作地点、出差地点、旅游地点。这些用户标签非常有助于推荐。
当然最简单的用户标签是浏览过的内容标签。但这里涉及到一些数据处理策略。主要包括:一、过滤噪声。通过停留时间短的点击,过滤标题党。二、热点惩罚。对用户在一些热门文章(如前段时间PG One的新闻)上的动作做降权处理。理论上,传播范围较大的内容,置信度会下降。三、时间衰减。用户兴趣会发生偏移,因此策略更偏向新的用户行为。因此,随着用户动作的增加,老的特征权重会随时间衰减,新动作贡献的特征权重会更大。四、惩罚展现。如果一篇推荐给用户的文章没有被点击,相关特征(类别,关键词,来源)权重会被惩罚。当然同时,也要考虑全局背景,是不是相关内容推送比较多,以及相关的关闭和dislike信号等。
用户标签挖掘总体比较简单,主要还是刚刚提到的工程挑战。头条用户标签第一版是批量计算框架,流程比较简单,每天抽取昨天的日活用户过去两个月的动作数据,在Hadoop集群上批量计算结果。
但问题在于,随着用户高速增长,兴趣模型种类和其他批量处理任务都在增加,涉及到的计算量太大。2014年,批量处理任务几百万用户标签更新的Hadoop任务,当天完成已经开始勉强。集群计算资源紧张很容易影响其它工作,集中写入分布式存储系统的压力也开始增大,并且用户兴趣标签更新延迟越来越高。
面对这些挑战。2014年底今日头条上线了用户标签Storm集群流式计算系统。改成流式之后,只要有用户动作更新就更新标签,CPU代价比较小,可以节省80%的CPU时间,大大降低了计算资源开销。同时,只需几十台机器就可以支撑每天数千万用户的兴趣模型更新,并且特征更新速度非常快,基本可以做到准实时。这套系统从上线一直使用至今。
四、评估分析
上面介绍了推荐系统的整体架构,那么如何评估推荐效果好不好?
有一句我认为非常有智慧的话,“一个事情没法评估就没法优化”。对推荐系统也是一样。
事实上,很多因素都会影响推荐效果。比如侯选集合变化,召回模块的改进或增加,推荐特征的增加,模型架构的改进在,算法参数的优化等等,不一一举例。评估的意义就在于,很多优化最终可能是负向效果,并不是优化上线后效果就会改进。
全面的评估推荐系统,需要完备的评估体系、强大的实验平台以及易用的经验分析工具。所谓完备的体系就是并非单一指标衡量,不能只看点击率或者停留时长等,需要综合评估。过去几年我们一直在尝试,能不能综合尽可能多的指标合成唯一的评估指标,但仍在探索中。目前,我们上线还是要由各业务比较资深的同学组成评审委员会深入讨论后决定。
很多公司算法做的不好,并非是工程师能力不够,而是需要一个强大的实验平台,还有便捷的实验分析工具,可以智能分析数据指标的置信度。
一个良好的评估体系建立需要遵循几个原则,首先是兼顾短期指标与长期指标。我在之前公司负责电商方向的时候观察到,很多策略调整短期内用户觉得新鲜,但是长期看其实没有任何助益。
其次,要兼顾用户指标和生态指标。今日头条作为内容分创作平台,既要为内容创作者提供价值,让他更有尊严的创作,也有义务满足用户,这两者要平衡。还有广告主利益也要考虑,这是多方博弈和平衡的过程。
另外,要注意协同效应的影响。实验中严格的流量隔离很难做到,要注意外部效应。
强大的实验平台非常直接的优点是,当同时在线的实验比较多时,可以由平台自动分配流量,无需人工沟通,并且实验结束流量立即回收,提高管理效率。这能帮助公司降低分析成本,加快算法迭代效应,使整个系统的算法优化工作能够快速往前推进。
实验过程中用户动作会被搜集,基本上是准实时,每小时都可以看到。但因为小时数据有波动,通常是以天为时间节点来看。动作搜集后会有日志处理、分布式统计、写入数据库,非常便捷。
在这个系统下工程师只需要设置流量需求、实验时间、定义特殊过滤条件,自定义实验组ID。系统可以自动生成:实验数据对比、实验数据置信度、实验结论总结以及实验优化建议。
五、内容安全
最后要介绍今日头条在内容安全上的一些举措。头条现在已经是国内最大的内容创作与分发凭条,必须越来越重视社会责任和行业领导者的责任。如果1%的推荐内容出现问题,就会产生较大的影响。
因此头条从创立伊始就把内容安全放在公司最高优先级队列。成立之初,已经专门设有审核团队负责内容安全。当时研发所有客户端、后端、算法的同学一共才不到40人,头条非常重视内容审核。
现在,今日头条的内容主要来源于两部分,一是具有成熟内容生产能力的PGC平台,一是UGC用户内容,如问答、用户评论、微头条。这两部分内容需要通过统一的审核机制。如果是数量相对少的PGC内容,会直接进行风险审核,没有问题会大范围推荐。UGC内容需要经过一个风险模型的过滤,有问题的会进入二次风险审核。审核通过后,内容会被真正进行推荐。这时如果收到一定量以上的评论或者举报负向反馈,还会再回到复审环节,有问题直接下架。整个机制相对而言比较健全,作为行业领先者,在内容安全上,今日头条一直用最高的标准要求自己。
分享内容识别技术主要鉴黄模型,谩骂模型以及低俗模型。今日头条的低俗模型通过深度学习算法训练,样本库非常大,图片、文本同时分析。这部分模型更注重召回率,准确率甚至可以牺牲一些。谩骂模型的样本库同样超过百万,召回率高达95%+,准确率80%+。如果用户经常出言不讳或者不当的评论,我们有一些惩罚机制。
泛低质识别涉及的情况非常多,像假新闻、黑稿、题文不符、标题党、内容质量低等等,这部分内容由机器理解是非常难的,需要大量反馈信息,包括其他样本信息比对。目前低质模型的准确率和召回率都不是特别高,还需要结合人工复审,将阈值提高。目前最终的召回已达到95%,这部分其实还有非常多的工作可以做。头条人工智能实验室李航老师目前也在和密歇根大学共建科研项目,设立谣言识别平台。
1.Spark SQL在100TB上的自适应执行实践
Spark SQL是Apache Spark最广泛使用的一个组件,它提供了非常友好的接口来分布式处理结构化数据,在很多应用领域都有成功的生产实践,但是在超大规模集群和数据集上,Spark SQL仍然遇到不少易用性和可扩展性的挑战。为了应对这些挑战,英特尔大数据技术团队和百度大数据基础架构部工程师在Spark 社区版本的基础上,改进并实现了自适应执行引擎。本文首先讨论Spark SQL在大规模数据集上遇到的挑战,然后介绍自适应执行的背景和基本架构,以及自适应执行如何应对Spark SQL这些问题,最后我们将比较自适应执行和现有的社区版本Spark SQL在100 TB 规模TPC-DS基准测试碰到的挑战和性能差异,以及自适应执行在Baidu Big SQL平台的使用情况。
挑战1:关于shuffle partition数
在Spark SQL中, shufflepartition数可以通过参数spark.sql.shuffle.partition来设置,默认值是200。这个参数决定了SQL作业每个reduce阶段任务数量,对整个查询性能有很大影响。假设一个查询运行前申请了E个Executor,每个Executor包含C个core(并发执行线程数),那么该作业在运行时可以并行执行的任务数就等于E x C个,或者说该作业的并发数是E x C。假设shuffle partition个数为P,除了map stage的任务数和原始数据的文件数量以及大小相关,后续的每个reduce stage的任务数都是P。由于Spark作业调度是抢占式的,E x C个并发任务执行单元会抢占执行P个任务,“能者多劳”,直至所有任务完成,则进入到下一个Stage。但这个过程中,如果有任务因为处理数据量过大(例如:数据倾斜导致大量数据被划分到同一个reducer partition)或者其它原因造成该任务执行时间过长,一方面会导致整个stage执行时间变长,另一方面E x C个并发执行单元大部分可能都处于空闲等待状态,集群资源整体利用率急剧下降。
那么spark.sql.shuffle.partition参数究竟是多少比较合适?如果设置过小,分配给每一个reduce任务处理的数据量就越多,在内存大小有限的情况下,不得不溢写(spill)到计算节点本地磁盘上。Spill会导致额外的磁盘读写,影响整个SQL查询的性能,更差的情况还可能导致严重的GC问题甚至是OOM。相反,如果shuffle partition设置过大。第一,每一个reduce任务处理的数据量很小并且很快结束,进而导致Spark任务调度负担变大。第二,每一个mapper任务必须把自己的shuffle输出数据分成P个hash bucket,即确定数据属于哪一个reduce partition,当shuffle partition数量太多时,hash bucket里数据量会很小,在作业并发数很大时,reduce任务shuffle拉取数据会造成一定程度的随机小数据读操作,当使用机械硬盘作为shuffle数据临时存取的时候性能下降会更加明显。最后,当最后一个stage保存数据时会写出P个文件,也可能会造成HDFS文件系统中大量的小文件。
从上,shuffle partition的设置既不能太小也不能太大。为了达到最佳的性能,往往需要经多次试验才能确定某个SQL查询最佳的shuffle partition值。然而在生产环境中,往往SQL以定时作业的方式处理不同时间段的数据,数据量大小可能变化很大,我们也无法为每一个SQL查询去做耗时的人工调优,这也意味这些SQL作业很难以最佳的性能方式运行。
Shuffle partition的另外一个问题是,同一个shuffle partition数设置将应用到所有的stage。Spark在执行一个SQL作业时,会划分成多个stage。通常情况下,每个stage的数据分布和大小可能都不太一样,全局的shuffle partition设置最多只能对某个或者某些stage最优,没有办法做到全局所有的stage设置最优。
这一系列关于shufflepartition的性能和易用性挑战,促使我们思考新的方法:我们能否根据运行时获取的shuffle数据量信息,例如数据块大小,记录行数等等,自动为每一个stage设置合适的shuffle partition值?
挑战2:Spark SQL最佳执行计划
Spark SQL在执行SQL之前,会将SQL或者Dataset程序解析成逻辑计划,然后经历一系列的优化,最后确定一个可执行的物理计划。最终选择的物理计划的不同对性能有很大的影响。如何选择最佳的执行计划,这便是Spark SQL的Catalyst优化器的核心工作。Catalyst早期主要是基于规则的优化器(RBO),在Spark 2.2中又加入了基于代价的优化(CBO)。目前执行计划的确定是在计划阶段,一旦确认以后便不再改变。然而在运行期间,当我们获取到更多运行时信息时,我们将有可能得到一个更佳的执行计划。
以join操作为例,在Spark中最常见的策略是BroadcastHashJoin和SortMergeJoin。BroadcastHashJoin属于map side join,其原理是当其中一张表存储空间大小小于broadcast阈值时,Spark选择将这张小表广播到每一个Executor上,然后在map阶段,每一个mapper读取大表的一个分片,并且和整张小表进行join,整个过程中避免了把大表的数据在集群中进行shuffle。而SortMergeJoin在map阶段2张数据表都按相同的分区方式进行shuffle写,reduce阶段每个reducer将两张表属于对应partition的数据拉取到同一个任务中做join。RBO根据数据的大小,尽可能把join操作优化成BroadcastHashJoin。Spark中使用参数spark.sql.autoBroadcastJoinThreshold来控制选择BroadcastHashJoin的阈值,默认是10MB。然而对于复杂的SQL查询,它可能使用中间结果来作为join的输入,在计划阶段,Spark并不能精确地知道join中两表的大小或者会错误地估计它们的大小,以致于错失了使用BroadcastHashJoin策略来优化join执行的机会。但是在运行时,通过从shuffle写得到的信息,我们可以动态地选用BroadcastHashJoin。以下是一个例子,join一边的输入大小只有600K,但Spark仍然规划成SortMergeJoin。
图1
这促使我们思考第二个问题:我们能否通过运行时收集到的信息,来动态地调整执行计划?
挑战3:数据倾斜
数据倾斜是常见的导致Spark SQL性能变差的问题。数据倾斜是指某一个partition的数据量远远大于其它partition的数据,导致个别任务的运行时间远远大于其它任务,因此拖累了整个SQL的运行时间。在实际SQL作业中,数据倾斜很常见,join key对应的hash bucket总是会出现记录数不太平均的情况,在极端情况下,相同join key对应的记录数特别多,大量的数据必然被分到同一个partition因而造成数据严重倾斜。如图2,可以看到大部分任务3秒左右就完成了,而最慢的任务却花了4分钟,它处理的数据量却是其它任务的若干倍。
图2
目前,处理join时数据倾斜的一些常见手段有: (1)增加shuffle partition数量,期望原本分在同一个partition中的数据可以被分散到多个partition中,但是对于同key的数据没有作用。(2)调大BroadcastHashJoin的阈值,在某些场景下可以把SortMergeJoin转化成BroadcastHashJoin而避免shuffle产生的数据倾斜。(3)手动过滤倾斜的key,并且对这些数据加入随机的前缀,在另一张表中这些key对应的数据也相应的膨胀处理,然后再做join。综上,这些手段都有各自的局限性并且涉及很多的人为处理。基于此,我们思考了第三个问题:Spark能否在运行时自动地处理join中的数据倾斜?
自适应执行背景和简介
早在2015年,Spark社区就提出了自适应执行的基本想法,在Spark的DAGScheduler中增加了提交单个map stage的接口,并且在实现运行时调整shuffle partition数量上做了尝试。但目前该实现有一定的局限性,在某些场景下会引入更多的shuffle,即更多的stage,对于三表在同一个stage中做join等情况也无法很好的处理。所以该功能一直处于实验阶段,配置参数也没有在官方文档中提及。
基于这些社区的工作,英特尔大数据技术团队对自适应执行做了重新的设计,实现了一个更为灵活的自适性执行框架。在这个框架下面,我们可以添加额外的规则,来实现更多的功能。目前,已实现的特性包括:自动设置shuffle partition数,动态调整执行计划,动态处理数据倾斜等等。
自适应执行架构
在Spark SQL中,当Spark确定最后的物理执行计划后,根据每一个operator对RDD的转换定义,它会生成一个RDD的DAG图。之后Spark基于DAG图静态划分stage并且提交执行,所以一旦执行计划确定后,在运行阶段无法再更新。自适应执行的基本思路是在执行计划中事先划分好stage,然后按stage提交执行,在运行时收集当前stage的shuffle统计信息,以此来优化下一个stage的执行计划,然后再提交执行后续的stage。
图3
从图3中我们可以看出自适应执行的工作方法,首先以Exchange节点作为分界将执行计划这棵树划分成多个QueryStage(Exchange节点在Spark SQL中代表shuffle)。每一个QueryStage都是一棵独立的子树,也是一个独立的执行单元。在加入QueryStage的同时,我们也加入一个QueryStageInput的叶子节点,作为父亲QueryStage的输入。例如对于图中两表join的执行计划来说我们会创建3个QueryStage。最后一个QueryStage中的执行计划是join本身,它有2个QueryStageInput代表它的输入,分别指向2个孩子的QueryStage。在执行QueryStage时,我们首先提交它的孩子stage,并且收集这些stage运行时的信息。当这些孩子stage运行完毕后,我们可以知道它们的大小等信息,以此来判断QueryStage中的计划是否可以优化更新。例如当我们获知某一张表的大小是5M,它小于broadcast的阈值时,我们可以将SortMergeJoin转化成BroadcastHashJoin来优化当前的执行计划。我们也可以根据孩子stage产生的shuffle数据量,来动态地调整该stage的reducer个数。在完成一系列的优化处理后,最终我们为该QueryStage生成RDD的DAG图,并且提交给DAG Scheduler来执行。
自动设置reducer个数
假设我们设置的shufflepartition个数为5,在map stage结束之后,我们知道每一个partition的大小分别是70MB,30MB,20MB,10MB和50MB。假设我们设置每一个reducer处理的目标数据量是64MB,那么在运行时,我们可以实际使用3个reducer。第一个reducer处理partition 0 (70MB),第二个reducer处理连续的partition 1 到3,共60MB,第三个reducer处理partition 4 (50MB),如图4所示。
图4
在自适应执行的框架中,因为每个QueryStage都知道自己所有的孩子stage,因此在调整reducer个数时,可以考虑到所有的stage输入。另外,我们也可以将记录条数作为一个reducer处理的目标值。因为shuffle的数据往往都是经过压缩的,有时partition的数据量并不大,但解压后记录条数确远远大于其它partition,造成数据不均。所以同时考虑数据大小和记录条数可以更好地决定reducer的个数。
动态调整执行计划
目前我们支持在运行时动态调整join的策略,在满足条件的情况下,即一张表小于Broadcast阈值,可以将SortMergeJoin转化成BroadcastHashJoin。由于SortMergeJoin和BroadcastHashJoin输出的partition情况并不相同,随意转换可能在下一个stage引入额外的shuffle操作。因此我们在动态调整join策略时,遵循一个规则,即在不引入额外shuffle的前提下才进行转换。
将SortMergeJoin转化成BroadcastHashJoin有哪些好处呢?因为数据已经shuffle写到磁盘上,我们仍然需要shuffle读取这些数据。我们可以看看图5的例子,假设A表和B表join,map阶段2张表各有2个map任务,并且shuffle partition个数为5。如果做SortMergeJoin,在reduce阶段需要启动5个reducer,每个reducer通过网络shuffle读取属于自己的数据。然而,当我们在运行时发现B表可以broadcast,并且将其转换成BroadcastHashJoin之后,我们只需要启动2个reducer,每一个reducer读取一个mapper的整个shuffle output文件。当我们调度这2个reducer任务时,可以优先将其调度在运行mapper的Executor上,因此整个shuffle读变成了本地读取,没有数据通过网络传输。并且读取一个文件这样的顺序读,相比原先shuffle时随机的小文件读,效率也更胜一筹。另外,SortMergeJoin过程中往往会出现不同程度的数据倾斜问题,拖慢整体的运行时间。而转换成BroadcastHashJoin后,数据量一般比较均匀,也就避免了倾斜,我们可以在下文实验结果中看到更具体的信息。
图5
动态处理数据倾斜
在自适应执行的框架下,我们可以在运行时很容易地检测出有数据倾斜的partition。当执行某个stage时,我们收集该stage每个mapper 的shuffle数据大小和记录条数。如果某一个partition的数据量或者记录条数超过中位数的N倍,并且大于某个预先配置的阈值,我们就认为这是一个数据倾斜的partition,需要进行特殊的处理。
图6
假设我们A表和B表做inner join,并且A表中第0个partition是一个倾斜的partition。一般情况下,A表和B表中partition 0的数据都会shuffle到同一个reducer中进行处理,由于这个reducer需要通过网络拉取大量的数据并且进行处理,它会成为一个最慢的任务拖慢整体的性能。在自适应执行框架下,一旦我们发现A表的partition 0发生倾斜,我们随后使用N个任务去处理该partition。每个任务只读取若干个mapper的shuffle 输出文件,然后读取B表partition 0的数据做join。最后,我们将N个任务join的结果通过Union操作合并起来。为了实现这样的处理,我们对shuffle read的接口也做了改变,允许它只读取部分mapper中某一个partition的数据。在这样的处理中,B表的partition 0会被读取N次,虽然这增加了一定的额外代价,但是通过N个任务处理倾斜数据带来的收益仍然大于这样的代价。如果B表中partition 0也发生倾斜,对于inner join来说我们也可以将B表的partition 0分成若干块,分别与A表的partition 0进行join,最终union起来。但对于其它的join类型例如Left Semi Join我们暂时不支持将B表的partition 0拆分。
自适应执行和Spark SQL在100TB上的性能比较
我们使用99台机器搭建了一个集群,使用Spark2.2在TPC-DS 100TB的数据集进行了实验,比较原版Spark和自适应执行的性能。以下是集群的详细信息:
图7
实验结果显示,在自适应执行模式下,103条SQL中有92条都得到了明显的性能提升,其中47条SQL的性能提升超过10%,最大的性能提升达到了3.8倍,并且没有出现性能下降的情况。另外在原版Spark中,有5条SQL因为OOM等原因无法顺利运行,在自适应模式下我们也对这些问题做了优化,使得103条SQL在TPC-DS 100TB数据集上全部成功运行。以下是具体的性能提升比例和性能提升最明显的几条SQL。
图8
图9
通过仔细分析了这些性能提升的SQL,我们可以看到自适应执行带来的好处。首先是自动设置reducer个数,原版Spark使用10976作为shuffle partition数,在自适应执行时,以下SQL的reducer个数自动调整为1064和1079,可以明显看到执行时间上也提升了很多。这正是因为减少了调度的负担和任务启动的时间,以及减少了磁盘IO请求。
原版Spark:
图10
自适应执行:
图11
在运行时动态调整执行计划,将SortMergeJoin转化成BroadcastHashJoin在某些SQL中也带来了很大的提升。例如在以下的例子中,原本使用SortMergeJoin因为数据倾斜等问题花费了2.5分钟。在自适应执行时,因为其中一张表的大小只有2.5k所以在运行时转化成了BroadcastHashJoin,执行时间缩短为10秒。
原版Spark:
图12
自适应执行:
图13
100 TB的挑战及优化
成功运行TPC-DS 100 TB数据集中的所有SQL,对于Apache Spark来说也是一大挑战。虽然SparkSQL官方表示支持TPC-DS所有的SQL,但这是基于小数据集。在100TB这个量级上,Spark暴露出了一些问题导致有些SQL执行效率不高,甚至无法顺利执行。在做实验的过程中,我们在自适应执行框架的基础上,对Spark也做了其它的优化改进,来确保所有SQL在100TB数据集上可以成功运行。以下是一些典型的问题。
统计map端输出数据时driver单点瓶颈的优化(SPARK-22537)
在每个map任务结束后,会有一个表示每个partition大小的数据结构(即下面提到的CompressedMapStatus或HighlyCompressedMapStatus)返回给driver。而在自适应执行中,当一次shuffle的map stage结束后,driver会聚合每个mapper给出的partition大小信息,得到在各个partition上所有mapper输出的数据总大小。该统计由单线程完成,如果mapper的数量是M,shuffle partition的数量为S,那么统计的时间复杂度在O(M x S) ~ O (M x S x log(M x S)) 之间,当CompressedMapStatus被使用时,复杂度为这个区间的下限,当HighlyCompressedMapStatus被使用时,空间有所节省,时间会更长,在几乎所有的partition数据都为空时,复杂度会接近该区间的上限。
在M x S增大时,我们会遇到driver上的单点瓶颈,一个明显的表现是UI上map stage和reduce stage之间的停顿。为了解决这个单点瓶颈,我们将任务尽量均匀地划分给多个线程,线程之间不相交地为scala Array中的不同元素赋聚合值。
在这项优化中,新的spark.shuffle.mapOutput.parallelAggregationThreshold(简称threshold)被引入,用于配置使用多线程聚合的阈值,聚合的并行度由JVM中可用core数和M * S / threshold + 1中的小值决定。
Shuffle读取连续partition时的优化 (SPARK-9853)
在自适应执行的模式下,一个reducer可能会从一个mapoutput文件中读取诺干个连续的数据块。目前的实现中,它需要拆分成许多独立的getBlockData调用,每次调用分别从硬盘读取一小块数据,这样就需要很多的磁盘IO。我们对这样的场景做了优化,使得Spark可以一次性地把这些连续数据块都读上来,这样就大大减少了磁盘的IO。在小的基准测试程序中,我们发现shuffle read的性能可以提升3倍。
BroadcastHashJoin中避免不必要的partition读的优化
自适应执行可以为现有的operator提供更多优化的可能。在SortMergeJoin中有一个基本的设计:每个reducetask会先读取左表中的记录,如果左表的 partition为空,则右表中的数据我们无需关注(对于非anti join的情况),这样的设计在左表有一些partition为空时可以节省不必要的右表读取,在SortMergeJoin中这样的实现很自然。
BroadcastHashJoin中不存在按照join key分区的过程,所以缺失了这项优化。然而在自适应执行的一些情况中,利用stage间的精确统计信息,我们可以找回这项优化:如果SortMergeJoin在运行时被转换成了BroadcastHashJoin,且我们能得到各个partition key对应partition的精确大小,则新转换成的BroadcastHashJoin将被告知:无需去读那些小表中为空的partition,因为不会join出任何结果。
Baidu真实产品线试用情况
我们将自适应执行优化应用在Baidu内部基于Spark SQL的即席查询服务BaiduBig SQL之上,做了进一步的落地验证,通过选取单日全天真实用户查询,按照原有执行顺序回放重跑和分析,得到如下几点结论:
- 对于秒级的简单查询,自适应版本的性能提升并不明显,这主要是因为它们的瓶颈和主要耗时集中在了IO上面,而这不是自适应执行的优化点。
- 按照查询复杂度维度考量测试结果发现:查询中迭代次数越多,多表join场景越复杂的情况下自适应执行效果越好。我们简单按照group by, sort, join, 子查询等操作个数来将查询分类,如上关键词大于3的查询有明显的性能提升,优化比从50%~200%不等,主要优化点来源于shuffle的动态并发数调整及join优化。
- 从业务使用角度来分析,前文所述SortMergeJoin转BroadcastHashJoin的优化在Big SQL场景中命中了多种典型的业务SQL模板,试考虑如下计算需求:用户期望从两张不同维度的计费信息中捞取感兴趣的user列表在两个维度的整体计费。收入信息原表大小在百T级别,用户列表只包含对应用户的元信息,大小在10M以内。两张计费信息表字段基本一致,所以我们将两张表与用户列表做inner join后union做进一步分析,SQL表达如下:
select t.c1, t.id, t.c2, t.c3, t.c4, sum(t.num1), sum(t.num2), sum(t.num3) from ( select c1, t1.id as id, c2, c3, c4, sum(num1s) as num1, sum(num2) as num2, sum(num3) as num3 from basedata.shitu_a t1 INNER JOIN basedata.user_82_1512023432000 t2 ON (t1.id = t2.id) where (event_day=20171107) and flag != 'true' group by c1, t1.id, c2, c3, c4 union all select c1, t1.id as id, c2, c3, c4, sum(num1s) as num1, sum(num2) as num2, sum(num3) as num3 from basedata.shitu_b t1 INNER JOIN basedata.user_82_1512023432000 t2 ON (t1.id = t2.id) where (event_day=20171107) and flag != 'true' group by c1, t1.id, c2, c3, c4 ) t group by t.c1, t.id, t.c2, t.c3, c4 |
对应的原版Spark执行计划如下:
图14
针对于此类用户场景,可以全部命中自适应执行的join优化逻辑,执行过程中多次SortMergeJoin转为BroadcastHashJoin,减少了中间内存消耗及多轮sort,得到了近200%的性能提升。
结合上述3点,下一步自适应执行在Baidu内部的优化落地工作将进一步集中在大数据量、复杂查询的例行批量作业之上,并考虑与用户查询复杂度关联进行动态的开关控制。对于数千台的大规模集群上运行的复杂查询,自适应执行可以动态调整计算过程中的并行度,可以帮助大幅提升集群的资源利用率。另外,自适应执行可以获取到多轮stage之间更完整的统计信息,下一步我们也考虑将对应数据及Strategy接口开放给Baidu Spark平台上层用户,针对特殊作业进行进一步的定制化Strategy策略编写。
总结
随着Spark SQL广泛的使用以及业务规模的不断增长,在大规模数据集上遇到的易用性和性能方面的挑战将日益明显。本文讨论了三个典型的问题,包括调整shuffle partition数量,选择最佳执行计划和数据倾斜。这些问题在现有的框架下并不容易解决,而自适应执行可以很好地应对这些问题。我们介绍了自适应执行的基本架构以及解决这些问题的具体方法。最后我们在TPC-DS 100TB数据集上验证了自适应执行的优势,相比较原版Spark SQL,103个SQL查询中,90%的查询都得到了明显的性能提升,最大的提升达到3.8倍,并且原先失败的5个查询在自适应执行下也顺利完成。我们在百度的Big SQL平台也做了进一步的验证,对于复杂的真实查询可以达到2倍的性能提升。总之,自适应执行解决了Spark SQL在大数据规模上遇到的很多挑战,并且很大程度上改善了Spark SQL的易用性和性能,提高了超大集群中多租户多并发作业情况下集群的资源利用率。将来,我们考虑在自适应执行的框架之下,提供更多运行时可以优化的策略,并且将我们的工作贡献回馈给社区,也希望有更多的朋友可以参与进来,将其进一步完善。
1月13日,SDCC 2017之数据库线上峰会即将强势来袭,秉承干货实料(案例)的内容原则,邀请了来自阿里巴巴、腾讯、微博、网易等多家企业的数据库专家及高校研究学者,围绕Oracle、MySQL、PostgreSQL、Redis等热点数据库技术展开,从核心技术的深挖到高可用实践的剖析,打造精华压缩式分享,举一反三,思辨互搏,报名及更多详情可扫描下方二维码或点击「阅读原文」查看。
一. 深度学习 Deep Learning 基础和基本思想
1,人工智能概述、计算智能、类脑智能
2,机器学习概述、记忆学习、归纳学习、统计学习
3,深度学习的前生今世、发展趋势
3,人工神经网络、前馈神经网络、BP 算法 、Hessian 矩阵、
结构性特征表示
二、深度学习 Deep Learning 基本框架结构
1,Caffe 2,Tensorflow
3,Torch 4,MXNet
三、深度学习 Deep Learning - 卷积神经网络
1,CNN 卷积神经网络
卷积层(一维卷积、二维卷积)、池化层(均值池化、最大池化)
全连接层 激活函数层 Softmax 层
2,CNN 卷积神经网络改进
R-CNN (SPPNET) Fast-R-CNN Faster-R-CNN (YOLO、SSD)
3,深度学习的模型训练技巧
4,梯度下降的优化方法详解
四,深度学习 Deep Learning- - 循环神经网络
1, RNN 循环神经网络
梯度计算 BPTT
2,RNN 循环神经网络改进
LSTM GRU Bi-RNN Attention based RNN
3,RNN 实际应用 Seq2Seq 的原理与实现
五、 强化学习
1,强化学习的理论知识
2,经典模型 DQN 讲解
3,AlphaGo 原理讲解
4,RL 实际应用;实现一个 AlphaGo
六, 对抗性生成网络
1, GAN 的理论知识
2, GAN 经典模型 CGAN,LAPGAN,DCGAN
3,GAN 经典模型 INFOGAN,WGAN,S2-GAN
4,GAN 实际应用 DCGAN 提高模糊图片分辨率
5,GAN 实际应用 InfoGAN 做特定的样本生成
七、迁移学习
1,迁移学习的理论概述
2,迁移学习的常见方法
特征、实例、数据、深度迁移、强化迁移、研究案例
八、CNN 应用案例
1,CNN 与手写数字集分类
2,YOLO 实现目标检测
3,PixelNet 原理与实现
4,利用卷积神经网络做图像风格结合
九、深度学习 Deep Learning 的常用模型或者方法
1,AutoEncoder 自动编码器
2,Sparse Coding 稀疏编码
3,Restricted Boltzmann Machine(RBM)限制波尔兹曼机
4,Deep BeliefNetworks 深信度网络
5,Convolutional Neural Networks 卷积神经网络
十、辅助课程
(1)疑难解答、分组讨论;
(2)关键问题解析;
(3)学后交流、微信群、QQ 群建立;
五 、参会对象:
各省市、自治区从事人工智能、深度学习、计算机视觉、人脸识别、 图像处理、行人检测、自然
语言处理等领域相关的企事业单位技术骨干、科研院所研究人员和大专院校相关专业教学人员及在校研
究生等相关人员,以及深度学习、计算机视觉广大爱好者;