SparkでのPageRankアルゴリズムの簡単な実装

「SparkFastBig Data Analysis」には、GoogleのPageRankアルゴリズムを数行で実装する不明瞭なScalaコードが含まれているため、簡単な実験を行って検証しました。

1.実験環境
スパーク1.5.0

2. PageRankアルゴリズムの概要(「SparkFast Big
        DataAnalysis 」から抜粋PageRankは、複数の接続を実行する反復アルゴリズムであるため、RDDパーティション操作の優れたユースケースです。アルゴリズムは2つのデータセットを維持します。1つは(pageID、linkList)要素で構成され、各ページの隣接ページのリストを含みます。もう1つは(pageID、rank)要素で構成され、各ページの現在のランキング値を含みます。以下のように計算されます。
各ページのソート値を1.0に初期化します。
各反復で、ページpについて、rank(p)/ numNeighbors(p)の寄与値が隣接する各ページ(直接リンクのあるページ)に送信されます。
各ページのランキング値を0.15+ 0.85 * ContributionsReceivedに設定します。
        最後の2つのステップは数サイクル繰り返されます。このプロセスの間、アルゴリズムは各ページの実際のPageRank値に徐々に収束します。実際には、収束には通常、約10回の反復が必要です。

3.シミュレーションデータ
A、B、C、およびDの4ページで構成される小グループを想定します。隣接するページは次のとおりです
。A:BC
B:AC
C:ABD
D:C

4、テストコード

import org.apache.spark.HashPartitioner
 
val links = sc.parallelize(List(("A",List("B","C")),("B",List("A","C")),("C",List("A","B","D")),("D",List("C")))).partitionBy(new HashPartitioner(100)).persist()
 
var ranks=links.mapValues(v=>1.0)
 
for (i <- 0 until 10) {
val contributions=links.join(ranks).flatMap {
case (pageId,(links,rank)) => links.map(dest=>(dest,rank/links.size))
}
ranks=contributions.reduceByKey((x,y)=>x+y).mapValues(v=>0.15+0.85*v)
}
 
ranks.sortByKey().collect()

         実行結果を下図に示します。


初期のlinksRDDとranksRDDは次のとおりです
。linksRDD:
Array [(String、List [String])] = Array((A、List(B、C))、(B、List(A、C))、(C、 List(A、B、D))、(D、List(C)))
ranksRDD:
Array [(String、Double)] = Array((A、1.0)、(B、1.0)、(C、1.0)、 (D、1.0))
最初の反復後のcontributionsRDDとranksRDDは次のとおりです。contributionsRDD

Array [(String、Double)] = Array((A、0.5)、(A、0.3333333333333333)、(B、0.5)、( B、0.3333333333333333)、(C、0.5)、(C、0.5)、(C、1.0)、(D、0.3333333333333333))
ranksRDD:
Array [(String、Double)] = Array((A、0.8583333333333333)、(B 、0.8583333333333333)、(C、1.8499999999999999)、(D、0.43333333333333335))
検証データ:
最初の反復:
PR(A)= 0.15 + 0.85 *(1/2 + 1/3)= 0.858333
PR(B)= 0.15 + 0.85 *(1/2 + 1/3)= 0.858333
PR(C)= 0.15 + 0.85 *(1/2 + 1/2 + 1/1)= 1.85
PR(D)= 0.15 + 0.85 *(1/3)= 0.433333
第2反復迭代:
PR(A)= 0.15 + 0.85 *(0.858333 / 2 + 1.85 / 3)= 1.038958191100
PR(B)= 0.15 + 0.85 *(0.858333 / 2 + 1.85 / 3)= 1.038958191100
PR(C)= 0.15 + 0.85 *(0.858333 / 2 + 0.858333 / 2 + 0.433333 / 1)= 1.247916100000
PR(D)= 0.15 + 0.85 *(1.85 / 3)= 0.67416667
第3回迭代:
PR(A)= 0.15 + 0.85 *(1.038958191100 / 2 + 1.247916100000 / 3)= 0.945133459550833333
PR(B)= 0.15 + 0.85 *(1.038958191100 / 2 + 1.247916100000 / 3)= 0.945133459550833333
PR(C)= 0.15 + 0.85 *(1.038958191100 / 2 + 1.038958191100 / 2 + 0.67416667 / 1)= 1.606156131935000000
D(0。)+ 0.85 *(1.247916100000 / 3)= 0.503576228333333333

5.コードの説明(「SparkFast Big DataAnalysis」から取得)以上
        です!アルゴリズムは、ranksRDDの各要素の値を1.0に初期化することから始まり、その後、各反復でランク変数を継続的に更新します。SparkでPageRankの本体を作成するのは非常に簡単です。まず、現在のranksRDDと静的linkRDDでjoin()操作を実行して、各ページIDと現在のランキング値に対応する隣接ページリストを取得し、次にflatMapを使用して隣接する各ページへの各ページの貢献度を記録する「貢献度」。次に、ページID(共有ページによる)に従ってこれらの貢献度の値を合計し、ページのランキング値を0.15 + 0.85 * ContributionsReceivedに設定します。
        コード自体は非常に単純ですが、このサンプルプログラムは、RDDがより効率的な方法でパーティション化され、通信オーバーヘッドを最小限に抑えるために多くのことを行います。
(1)linksRDDは各反復でランクに接続されることに注意してください。リンクは静的データセットであるため、プログラムの最初にパーティション化したため、ネットワークを介してシャッフルする必要はありません。実際、linksRDDのバイト数は一般にランクよりもはるかに大きく、結局のところ、Double値だけでなく、各ページの隣接ページのリスト(ページIDで構成される)が含まれているため、この最適化はPageRankの元の実装(通常のMapReduceなど)により、ネットワーク通信のオーバーヘッドが大幅に節約されます。
(2)同じ理由で、リンクのpersist()メソッドを呼び出し、反復ごとにメモリに保持します。
(3)初めてランクを作成するときは、map()の代わりにmapValues()を使用して、親RDD(リンク)のパーティショニングメソッドを保持します。これにより、最初の接続操作のオーバーヘッドが小さくなります。
(4)ループ本体では、reduceByKey()の後にmapValues()を使用します。reduceByKey()の結果はすでにハッシュ分割されているため、このように、マッピング操作の結果は次のサイクルでリンクを使用して再度実行されます。接続するとより効率的になります。

Scalaの言語は本当に簡潔です。次の図に示すように、ビッグデータの一般的なサンプルプログラムのワードカウントは、scalaで1行を記述することで実行できます。

var input = sc.textFile("/NOTICE.txt")
input.flatMap(x=>x.split(" ")).countByValue()

おすすめ

転載: blog.csdn.net/qq_32445015/article/details/103548598