1. 程式人生 > >Spark ALS原始碼總結

Spark ALS原始碼總結

Spark ALS是ALS的分散式實現,非常高效,程式碼進行了大量的優化,有許多可以借鑑和思考的地方,它實現了Explicit ALS和Implicit ALS分散式演算法。本文是閱讀Spark ALS原始碼後的一些總結和思考。

Implicit ALS原理

先說一下隱式資料的特點:

  • 沒有負反饋
  • 充滿噪聲
  • 顯式資料的數值代表偏好,隱式資料的數值代表了置信等級(confidence level)。例如,我們觀測到該使用者看了某部電影,推測他可能喜歡該電影,如果發現他看了這部電影好幾遍,我們就很有信心認為他喜歡這部電影了

基於隱式資料的特點,Implicit ALS做如下假設:

  • 引入二分值p

    ui:

    pu,i={10if ru,i>0otherwise
  • 引入置信等級cui

    cui=1+αrui
  • 損失函式

    minx,y(u,i)Kcu,i(pu,ixuyi)2+λ(u||xu||2+i||yi||2)
  • 迭代公式

    xu=(YCuY+λI)1YCupuyi=(XCiX+λI)1XCipi
    其中,
    Cu=cu1cun
  • 變換
    xu為例,直接計算的話,計算量太大,注意到YCuY=YY+Y(CuI)Y,而CuI使得只需要計算使用者u有過行為的物品集合,YY可以在一輪迭代裡只需要計算一次。這裡已經有點分散式的味道了。

Spark ALS並行化分析

xu為例(之後也該角度分析),計算它需要兩個要素:

  • 使用者u的評分詳情(對哪個物品評了多少分),用於計算CuIpu
  • 使用者u關聯的所有物品集的隱式因子,用於計算Y(CuI)YYCupu

Spark ALS 主要有三個步驟

  • partitionRatings:將原始評分資料分片為塊
  • makeBlocks:產生InBlock和OutBlock
  • computeFactors:Normal Equation求解

分散式計算關注的重點是控制計算複雜度和通訊複雜度。Spark ALS首先是以Block為單位進行正態方程求解的。例如在迭代Block 1中的所有使用者U1

的factors的時候,需要將U1的所有評分詳情和所需的物品因子集全部彙總到同一個Partition中。

這裡Spark ALS設計了兩個結構InBlock和OutBlock。InBlock儲存評分詳情,OutBlock儲存“因子關聯索引”,用於索引相關的Item Facors,這部分的原始碼是個難點。

Spark ALS 資料格式衍變

通過閱讀原始碼,總結其從最初的評分資料格式衍變格式如下

編號 函式 形式 KV
1 partitionRatings
將原始ratings分片為blocks
(srcBlockId, dstBlockId) RatingBlock(srcIds, dstIds, ratings)
2.1 makeBlocks
建立InBlock和OutBlock
srcBlockId (dstBlockId, srcIds, dstLocalIndices, ratings)
dstLocalIndices表示Block本地索引
2.2 srcBlockId (srcIds, dstEncodedIndices, ratings)
dstEncodedIndices是dstBlockId和dstLocalIndices的組合
3 InBlock形態 srcBlockIdInBlock(uniqueSrcIds, dstPtrs, dstEncodedIndices, ratings)
這裡進行了排序和矩陣壓縮
4 OutBlock形態 srcBlockId[dstBlockId][uniqSrcIdLocalIndex]
5 Factor形態 srcBlockId[srcIdLocalIndex][Array[float]]

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的作用

原始評分資料是(user:ID,ite