Spark ALS原始碼總結
Spark ALS是ALS的分散式實現,非常高效,程式碼進行了大量的優化,有許多可以借鑑和思考的地方,它實現了Explicit ALS和Implicit ALS分散式演算法。本文是閱讀Spark ALS原始碼後的一些總結和思考。
Implicit ALS原理
先說一下隱式資料的特點:
- 沒有負反饋
- 充滿噪聲
- 顯式資料的數值代表偏好,隱式資料的數值代表了置信等級(confidence level)。例如,我們觀測到該使用者看了某部電影,推測他可能喜歡該電影,如果發現他看了這部電影好幾遍,我們就很有信心認為他喜歡這部電影了
基於隱式資料的特點,Implicit ALS做如下假設:
引入二分值
p :
pu,i={10if ru,i>0otherwise 引入置信等級
cui
cui=1+αrui 損失函式
minx,y∑(u,i)∈Kcu,i(pu,i−x⊤uyi)2+λ(∑u||xu||2+∑i||yi||2) 迭代公式
xu=(Y⊤CuY+λI)−1Y⊤Cupuyi=(X⊤CiX+λI)−1X⊤Cipi
其中,
Cu=⎛⎝⎜⎜⎜cu1⋱cun⎞⎠⎟⎟⎟ 變換
以xu 為例,直接計算的話,計算量太大,注意到Y⊤CuY=Y⊤Y+Y⊤(Cu−I)Y ,而Cu−I 使得只需要計算使用者u 有過行為的物品集合,Y⊤Y 可以在一輪迭代裡只需要計算一次。這裡已經有點分散式的味道了。
Spark ALS並行化分析
以
- 使用者
u 的評分詳情(對哪個物品評了多少分),用於計算Cu−I ,pu - 使用者
u 關聯的所有物品集的隱式因子,用於計算Y⊤(Cu−I)Y 和Y⊤Cupu
Spark ALS 主要有三個步驟
- partitionRatings:將原始評分資料分片為塊
- makeBlocks:產生InBlock和OutBlock
- computeFactors:Normal Equation求解
分散式計算關注的重點是控制計算複雜度和通訊複雜度。Spark ALS首先是以Block為單位進行正態方程求解的。例如在迭代Block 1中的所有使用者
這裡Spark ALS設計了兩個結構InBlock和OutBlock。InBlock儲存評分詳情,OutBlock儲存“因子關聯索引”,用於索引相關的Item Facors,這部分的原始碼是個難點。
Spark ALS 資料格式衍變
通過閱讀原始碼,總結其從最初的評分資料格式衍變格式如下
編號 | 函式 | 形式 K |
---|---|---|
1 | partitionRatings 將原始ratings分片為blocks |
(srcBlockId, dstBlockId) |
2.1 | makeBlocks 建立InBlock和OutBlock |
srcBlockId dstLocalIndices表示Block本地索引 |
2.2 | srcBlockId dstEncodedIndices是dstBlockId和dstLocalIndices的組合 |
|
3 | InBlock形態 | srcBlockId 這裡進行了排序和矩陣壓縮 |
4 | OutBlock形態 | srcBlockId |
5 | Factor形態 | srcBlockId |
Block使用者集獲取所需Item集Factors的過程
ItemOutBlock.join(ItemFactors).flatMap {
case(ItemBlockId, (ItemOutBlock, ItemFactors)) =>
ItemOutBlock.view.zipWithIndex.map { case ([uniqItemIdLocalIndex], UserBlockId) =>
(UserBlockId, (ItemBlockId, AssocItemFactors)))
}
} =>(UserBlockId, Array[(ItemBlockId, ItemFactors)])
通過以上方式,所有需要的元素都傳輸到了一起。
重點原始碼分析
核心原始碼在org.apache.spark.ml.recommendation.ALS中,相比於Spark SVD++的200多行的程式碼,ALS的程式碼量真是巨無霸,洋洋灑灑1~2千行,不過核心模組的程式碼也是幾百行左右,這裡主要分析makeBlocks原始碼片段。
partitionRatings的作用
原始評分資料是