基于Spark的大規(guī)模機(jī)器學(xué)習(xí)在微博的應(yīng)用
眾所周知,自2015年以來(lái)微博的業(yè)務(wù)發(fā)展迅猛。如果根據(jù)內(nèi)容來(lái)劃分,微博的業(yè)務(wù)有主信息(Feed)流、熱門(mén)微博、微博推送(Push)、反垃圾、微博分發(fā)控制等。每個(gè)業(yè)務(wù)都有自己不同的用戶構(gòu)成、業(yè)務(wù)關(guān)注點(diǎn)和數(shù)據(jù)特征。龐大的用戶基數(shù)下,由用戶相互關(guān)注衍生的用戶間關(guān)系,以及用戶千人千面的個(gè)性化需求,要求我們用更高、更大規(guī)模的維度去刻畫(huà)和描繪用戶。大體量的微博內(nèi)容,也呈現(xiàn)出多樣化、多媒體化的發(fā)展趨勢(shì)。
一直以來(lái),微博都嘗試通過(guò)機(jī)器學(xué)習(xí)來(lái)解決業(yè)務(wù)場(chǎng)景中遇到的各種挑戰(zhàn)。本文為新浪微博吳磊在CCTC 2017云計(jì)算大會(huì)Spark峰會(huì)所做分享《基于Spark的大規(guī)模機(jī)器學(xué)習(xí)在微博的應(yīng)用》主題的一部分,介紹微博在面對(duì)大規(guī)模機(jī)器學(xué)習(xí)的挑戰(zhàn)時(shí),采取的最佳實(shí)踐和解決方案。
Spark Mllib
針對(duì)微博近百億特征維度、近萬(wàn)億樣本量的模型訓(xùn)練需求,我們首先嘗試了Apache Spark原生實(shí)現(xiàn)的邏輯回歸算法。采用該方式的優(yōu)點(diǎn)顯而易見(jiàn),即開(kāi)發(fā)周期短、試錯(cuò)成本低。我們將不同來(lái)源的特征(用戶、微博內(nèi)容、用戶間關(guān)系、使用環(huán)境等)根據(jù)業(yè)務(wù)需要進(jìn)行數(shù)據(jù)清洗、提取、離散化,生成Libsvm格式的可訓(xùn)練樣本集,再將樣本喂給LR算法進(jìn)行訓(xùn)練。在維度升高的過(guò)程中,我們遇到了不同方面的問(wèn)題,并通過(guò)實(shí)踐提供了解決辦法。
【Stack overflow】
棧溢出的問(wèn)題在函數(shù)嵌套調(diào)用中非常普遍,但在我們的實(shí)踐中發(fā)現(xiàn),過(guò)多Spark RDD的union操作,同樣會(huì)導(dǎo)致棧溢出的問(wèn)題。解決辦法自然是避免大量的RDD union,轉(zhuǎn)而采用其他的實(shí)現(xiàn)方式。
【AUC=0.5】
在進(jìn)行模型訓(xùn)練的過(guò)程中,曾出現(xiàn)測(cè)試集AUC一直停留在0.5的尷尬局面。通過(guò)仔細(xì)查看訓(xùn)練參數(shù),發(fā)現(xiàn)當(dāng)LR的學(xué)習(xí)率設(shè)置較大時(shí),梯度下降會(huì)在局部最優(yōu)左右搖擺,造成訓(xùn)練出來(lái)的模型成本偏高,擬合性差。通過(guò)適當(dāng)調(diào)整學(xué)習(xí)率可以避免該問(wèn)題的出現(xiàn)。
【整型越界】
整型越界通常是指給定的數(shù)據(jù)值過(guò)大,超出了整形(32bit Int)的上限。但在我們的場(chǎng)景中,導(dǎo)致整型越界的并不是某個(gè)具體數(shù)據(jù)值的大小,而是因?yàn)橛?xùn)練樣本數(shù)據(jù)量過(guò)大、HDFS的分片過(guò)大,導(dǎo)致Spark RDD的單個(gè)分片內(nèi)的數(shù)據(jù)記錄條數(shù)超出了整型上限,進(jìn)而導(dǎo)致越界。Spark RDD中的迭代器以整數(shù)(Int)來(lái)記錄Iterator的位置,當(dāng)記錄數(shù)超過(guò)32位整型所包含的范圍(2147483647),就會(huì)報(bào)出該錯(cuò)誤。
解決辦法是在Spark加載HDFS中的HadoopRDD時(shí),設(shè)置分區(qū)數(shù),將分區(qū)數(shù)設(shè)置足夠大,從而保證每個(gè)分片的數(shù)據(jù)量足夠小,以避免該問(wèn)題??梢酝ㄟ^(guò)公式(總記錄數(shù)/單個(gè)分片記錄數(shù))來(lái)計(jì)算合理的分區(qū)數(shù)。
【Shuffle fetch failed】
在分布式計(jì)算中,Shuffle階段不可避免,在Shuffle的Map階段,Spark會(huì)將Map輸出緩存到本機(jī)的本地文件系統(tǒng)。當(dāng)Map輸出的數(shù)據(jù)較大,且本地文件系統(tǒng)存儲(chǔ)空間不足時(shí),會(huì)導(dǎo)致Shuffle中間文件的丟失,這是Shuffle fetch failed錯(cuò)誤的常見(jiàn)原因。但在我們的場(chǎng)景中,我們手工設(shè)置了spark.local.dir配置項(xiàng),將其指向存儲(chǔ)空間足夠、I/O效率較高的文件系統(tǒng)中,但還是碰到了該問(wèn)題。
通過(guò)仔細(xì)查對(duì)日志和Spark UI的記錄,發(fā)現(xiàn)有個(gè)別Executor因任務(wù)過(guò)重、GC時(shí)間過(guò)長(zhǎng),丟失了與Driver的心跳。Driver感知不到這些Executor的心跳,便主動(dòng)要求Yarn的Application master將包含這些Executor的Container殺掉。
皮之不存、毛之焉附,Executor被殺掉了,存儲(chǔ)在其中的Map輸出信息自然也就丟了,造成在Reduce階段,Reducer無(wú)法獲得屬于自己的那份Map輸出。解決辦法是合理地設(shè)置JVM的GC設(shè)置,或者通過(guò)將spark.network.timeout的時(shí)間(默認(rèn)60s)設(shè)置為120s,該時(shí)間為Driver與Executor心跳通信的超時(shí)時(shí)間,給Executor足夠的響應(yīng)時(shí)間,讓其不必因處理任務(wù)過(guò)重而無(wú)暇與Driver端通信。
通過(guò)各種優(yōu)化,我們將模型的維度提升至千萬(wàn)維。當(dāng)模型維度沖擊到億維時(shí),因Spark Mllib LR的實(shí)現(xiàn)為非模型并行,過(guò)高的模型維度會(huì)導(dǎo)致海森矩陣呈指數(shù)級(jí)上漲,導(dǎo)致內(nèi)存和網(wǎng)絡(luò)I/O的極大開(kāi)銷(xiāo)。因此我們不得不嘗試其他的解決方案。
基于Spark的參數(shù)服務(wù)器
在經(jīng)過(guò)大量調(diào)研和初步的嘗試,我們最終選擇參數(shù)服務(wù)器方案來(lái)解決模型并行問(wèn)題。參數(shù)服務(wù)器通過(guò)將參數(shù)分片以分布式形式存儲(chǔ)和訪問(wèn),將高維模型平均分配到參數(shù)服務(wù)器集群中的每一臺(tái)機(jī)器,將CPU計(jì)算、內(nèi)存消耗、存儲(chǔ)、磁盤(pán)I/O、網(wǎng)絡(luò)I/O等負(fù)載和開(kāi)銷(xiāo)均攤。典型的參數(shù)服務(wù)器采用主從架構(gòu),Master負(fù)責(zé)記錄和維護(hù)每個(gè)參數(shù)服務(wù)器的心跳和狀態(tài);參數(shù)服務(wù)器則負(fù)責(zé)參數(shù)分片的存儲(chǔ)、梯度計(jì)算、梯度更新、副本存儲(chǔ)等具體工作。圖1是我們采用的參數(shù)服務(wù)器方案。
圖1 微博參數(shù)服務(wù)器架構(gòu)圖
藍(lán)色文本框架即是采用主從架構(gòu)的參數(shù)服務(wù)器集群,以Yarn應(yīng)用的方式部署在Yarn集群中,為所有應(yīng)用提供服務(wù)。在參數(shù)服務(wù)器的客戶端,也是通過(guò)Yarn應(yīng)用的方式,啟動(dòng)Spark任務(wù)執(zhí)行LR分布式算法。在圖中綠色文本框中,Spark模型訓(xùn)練以獨(dú)立的應(yīng)用存在于Yarn集群中。在模型訓(xùn)練過(guò)程中,每個(gè)Spark Executor以數(shù)據(jù)分片為單位,進(jìn)行參數(shù)的拉取、計(jì)算、更新和推送。
在參數(shù)服務(wù)器實(shí)現(xiàn)方面,業(yè)界至少有兩種實(shí)現(xiàn)方式,即全同步與全異步。全同步的方式能夠在理論層面保證模型收斂,但在分布式環(huán)境中,鑒于各計(jì)算節(jié)點(diǎn)的執(zhí)行性能各異,加上迭代中需要彼此間相互同步,容易導(dǎo)致過(guò)早執(zhí)行完任務(wù)的節(jié)點(diǎn)等待計(jì)算任務(wù)繁重的節(jié)點(diǎn),引入通信邊界,從而造成計(jì)算資料的浪費(fèi)和開(kāi)銷(xiāo)。全異步方式能夠很好地避免這些問(wèn)題,因節(jié)點(diǎn)間無(wú)需等待和同步,可以充分利用各個(gè)節(jié)點(diǎn)的計(jì)算資源。雖然從理論上無(wú)法驗(yàn)證模型一定收斂,但是通過(guò)實(shí)踐發(fā)現(xiàn),模型每次的迭代速度會(huì)更快,AUC的加速度會(huì)更高,實(shí)際訓(xùn)練出的模型效果可以滿足業(yè)務(wù)和線上的要求。
在通過(guò)參數(shù)服務(wù)器進(jìn)行LR模型訓(xùn)練時(shí),我們總結(jié)了影響執(zhí)行性能的關(guān)鍵因素,羅列如下:
【Batch size】
即Spark數(shù)據(jù)分片大小。前文提到,每個(gè)Spark Executor以數(shù)據(jù)分片為單位,進(jìn)行參數(shù)的拉取和推送。分片的大小直接決定本次迭代需要拉取和通信的參數(shù)數(shù)量,而參數(shù)數(shù)量直接決定了本地迭代的計(jì)算量、通信量。因此分片大小是影響模型訓(xùn)練執(zhí)行性能的首要因素。過(guò)大的數(shù)據(jù)分片會(huì)造成單次迭代任務(wù)過(guò)重,Executor不堪重負(fù);過(guò)小的分片雖然能夠充分利用網(wǎng)絡(luò)吞吐,但是會(huì)造成很多額外的開(kāi)銷(xiāo)。因此,選擇合理的Batch size,將會(huì)令執(zhí)行性能的提升事半功倍。下文將以Batch size為例,對(duì)比不同設(shè)置下模型訓(xùn)練執(zhí)行性能的差異。
【PS server數(shù)量】
參數(shù)服務(wù)器的數(shù)量,決定了模型參數(shù)的存儲(chǔ)容量。通過(guò)擴(kuò)展參數(shù)服務(wù)器集群,理論上可以無(wú)限擴(kuò)展存儲(chǔ)容量。但是當(dāng)集群大小達(dá)到瓶頸值時(shí),過(guò)多的參數(shù)服務(wù)器帶來(lái)的網(wǎng)絡(luò)開(kāi)銷(xiāo)反而會(huì)令整體執(zhí)行性能趨于平緩甚至下降。
【特征稀疏度】
根據(jù)需要可以將原始業(yè)務(wù)特征(用戶、微博內(nèi)容、用戶間關(guān)系、使用環(huán)境等)通過(guò)映射函數(shù)映射到高維模型,以這種方式提煉出區(qū)分度更佳的特征。特征稀疏度結(jié)合每次迭代數(shù)據(jù)分片的數(shù)據(jù)分布,決定了該分片本次迭代需要拉取和推送的參數(shù)數(shù)量,進(jìn)而決定了本次迭代所需的計(jì)算資源和網(wǎng)絡(luò)開(kāi)銷(xiāo)。
【PS分區(qū)策略】
分區(qū)策略決定了模型參數(shù)在參數(shù)服務(wù)器的分布,好的分區(qū)策略能夠使模型參數(shù)的分布更均勻,從而均攤每個(gè)節(jié)點(diǎn)的計(jì)算和通信負(fù)載。
【Spark內(nèi)存規(guī)劃】
在PS的客戶端,Spark Executor需要保證有足夠的內(nèi)存容納本次迭代分片所需的參數(shù)向量,才能完成后續(xù)的參數(shù)計(jì)算、更新任務(wù)。
下表為不同的Batch size下,各執(zhí)行性能指標(biāo)對(duì)比。Parameter(MB)表示一次迭代所需參數(shù)個(gè)數(shù);Tx(MB)表示一次迭代的網(wǎng)絡(luò)吞吐;Pull(ms)和Push(ms)分別表示一次迭代的拉取和推送時(shí)間消耗;Time(s)為一次迭代的整體執(zhí)行時(shí)間。從表1中可見(jiàn),參數(shù)個(gè)數(shù)與分片大小成正比、網(wǎng)絡(luò)吞吐與分片大小成反比。分片越小,需要通信、處理的參數(shù)越少,但PS客戶端與PS服務(wù)器通信更加頻繁,因而網(wǎng)絡(luò)吞吐更高。但是當(dāng)分片過(guò)小時(shí),會(huì)產(chǎn)生額外的開(kāi)銷(xiāo),造成參數(shù)拉取、推送的平均耗時(shí)和任務(wù)的整體耗時(shí)上升。
通過(guò)參數(shù)服務(wù)器的解決方案,我們解決了微博機(jī)器學(xué)習(xí)平臺(tái)化進(jìn)程中的大規(guī)模模型訓(xùn)練問(wèn)題。眾所周知,在機(jī)器學(xué)習(xí)流中,模型訓(xùn)練只是其中耗時(shí)最短的一環(huán)。如果把機(jī)器學(xué)習(xí)流比作烹飪,那么模型訓(xùn)練就是最后翻炒的過(guò)程,烹飪的大部分時(shí)間實(shí)際上都花在了食材、佐料的挑選,洗菜、擇菜,食材再加工(切丁、切塊、過(guò)油、預(yù)熱)等步驟。
在微博的機(jī)器學(xué)習(xí)流中,原始樣本生成、數(shù)據(jù)處理、特征工程、訓(xùn)練樣本生成、模型后期的測(cè)試、評(píng)估等步驟所需要投入的時(shí)間和精力,占據(jù)了整個(gè)流程的80%之多。如何能夠高效地端到端進(jìn)行機(jī)器學(xué)習(xí)流的開(kāi)發(fā),如何能夠根據(jù)線上的反饋及時(shí)地選取高區(qū)分度特征,對(duì)模型進(jìn)行優(yōu)化,驗(yàn)證模型的有效性,加速模型迭代效率,滿足線上的要求,都是我們需要解決的問(wèn)題。在新一期《程序員》“weiflow——微博機(jī)器學(xué)習(xí)流統(tǒng)一計(jì)算框架”一文中,我們將為你一一解答。
吳磊,微博算法平臺(tái)高級(jí)工程師,主要負(fù)責(zé)以Spark為核心的大數(shù)據(jù)計(jì)算框架、機(jī)器學(xué)習(xí)平臺(tái)的設(shè)計(jì)和實(shí)現(xiàn)。曾任職于IBM、聯(lián)想研究院,從事數(shù)據(jù)庫(kù)、數(shù)據(jù)倉(cāng)庫(kù)、大數(shù)據(jù)分析相關(guān)工作。
張拓宇,微博系統(tǒng)開(kāi)發(fā)工程師,作為主要開(kāi)發(fā)設(shè)計(jì)人員參與微博大規(guī)模機(jī)器學(xué)習(xí)、特征工程等項(xiàng)目,負(fù)責(zé)計(jì)算平臺(tái)參數(shù)服務(wù)器和大規(guī)模學(xué)習(xí)算法的研究和工程實(shí)現(xiàn)工作。