【实践】spark 实现simrank计算图结构的相似

SimRank原理

【实践】spark 实现simrank计算图结构的相似_第1张图片

图1.二部图

所谓二部图(bipartite graphs),是指图中的节点可以分这两个子集,任意一条边关联的两个节点分别来自于这两个子集。用I(v)和O(v)分别表示节点v的in-neighbors和out-neighbors。看上面的二部图,我们把A、B当成两个人,把a、b、c当成三件商品,有向边代表人购买的商品。simrank的基本思想是:如果两个实体相似,那么跟它们相关的实体应该也相似。比如在上图中如果a和c相似,那么A和B应该也相似,因为A和a相关,而B和c相关。

SimRank的基本公式:

s(a,b)=C|I(a)||I(b)|i=1|I(a)|j=1|I(b)|s(Ii(a),Ij(b))(1)

s(a,b)是节点a和b的相似度,当a=b时,s(a,b)=1。Ii(a)

表示a的第i个in-neighbor。当 I(a)= 或  I(b)=  时式 (1)为0。 (1)式用一句话描述就是:a和b的相似度等于a的in-neighbors和b的in-neighbors相似度的平均值。参数C是个阻尼系数,它的含义可以这么理解:假如I(a)=I(b)={A},按照 (1)式计算出sim(a,b)=C*sim(A,A)=C,所以 C(0,1)

。把式(1)应用于图1所示的二部图就是:

s(A,B)=C1|O(A)||O(B)|i=1|O(A)|j=1|O(B)|s(Oi(A),Oj(B))    for AB(2)
s(a,b)=C2|I(a)||I(b)|i=1|I(a)|j=1|I(b)|s(Ii(a),Ij(b))    for ab(3)

忽略C1C2(2)式是说买家A和B的相似度等于他们购买的物品之间相似度的平均值,(3)式是说物品a和b的相似度是购买它们的买家之间相似度的平均值。

对于非二部图的情况,一个节点既可能有in-neighbors也可能有out-neighbors,比如在论文引用的场景下,一篇论文既可能引用其他论文,也可能被其他论文引用。站在引用的角度,两篇论文的相似度为

s1(a,b)=C1|O(a)||O(b)|i=1|O(a)|j=1|O(b)|s2(Oi(a),Oj(b))(4)

站在被引用的角度,两篇论文的相似度为

s2(a,b)=C2|I(a)||I(b)|i=1|I(a)|j=1|I(b)|s1(Ii(a),Ij(b))(5)

根据实际的应用场景,最终的s(a,b)可以采用(4)(5)其中的任何一个,或者是两者的综合。

矩阵形式

假设:
为SimRank相似度矩阵,其元素
表示相似度值
是一个按列归一化的图邻接矩阵,其元素
,若存在一条有向边
;否则为0。
于是,SimRank方程式可以用矩阵的形式表示如下:
其中,
是一个单位矩阵。

代码实现

输入数据(案例图):

graph_bipartite

A    a
A    b
B    b
B    c


spark2.1代码:

package model

import org.apache.spark.graphx._
import org.apache.log4j.{Level, Logger}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.mllib.linalg.distributed.{CoordinateMatrix, MatrixEntry}
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SparkSession}


class SimRankGraph() {
  /**
    * 创建图的结构
    *
    * @param indexedNode
    * @param nodes
    * @return
    */
  def graphStruct(indexedNode: RDD[(String, Long)], nodes: RDD[(String, String)]): Graph[String, Int] = {

    val indexedNodes = nodes.join(indexedNode).map(r => (r._2._1, r._2._2)).join(indexedNode).map(r => (r._2._1, r._2._2))

    val relationShips: RDD[Edge[Int]] = indexedNodes.map { x =>
      val x1 = x._1
      val x2 = x._2
      Edge(x1, x2, 1)
    }

    val users: RDD[(VertexId, String)] = indexedNode.map { x =>
      (x._2, x._1)
    }

    val graph = Graph(users, relationShips)
    graph
  }
}

object SimRank {

 
   /**
    * 获取item相似图
    *
    * @param nodes
    * @param damp
    */
  def getSimilarity(nodes: RDD[(String, String)], damp: Double) = {
    val itemSet = nodes.map(x => (x._2, "-")).distinct()
    val index2Node = (nodes.map(_._1) union (nodes.map(_._2))).distinct.zipWithIndex().cache()
    val nodesNum = index2Node.count().toInt
    val graph = new SimRankGraph().graphStruct(index2Node, nodes)
    val outs = graph.outDegrees.map(x => (x._1, (1 / x._2.toDouble)))
    val ins = graph.inDegrees.map(x => (x._1, (1 / x._2.toDouble)))

    val rdd_out = graph.outerJoinVertices(outs)((id, _, degin) => (id.toString, degin.getOrElse(0)))
      .triplets.map { x =>
      (x.dstId, x.srcId, x.srcAttr._2.toString.toDouble * x.attr.toInt)
    }
    val rdd_int = graph.outerJoinVertices(ins)((id, _, degin) => (id.toString, degin.getOrElse(0)))
      .triplets.map { x =>
      (x.srcId, x.dstId, x.dstAttr._2.toString.toDouble * x.attr.toInt)
    }

    val rdd_all = rdd_out.union(rdd_int)
    //概率转移矩阵Q
    val transferMatrix = new CoordinateMatrix(rdd_all.map { x =>
      MatrixEntry(x._1, x._2, x._3)
    }).toBlockMatrix

    // 单位矩阵I
    val unitMatrix = new CoordinateMatrix(nodes.sparkContext.parallelize(0 until nodesNum).map { x =>
      MatrixEntry(x, x, 1.0)
    })

    // C
    val cMatrix = new CoordinateMatrix(unitMatrix.entries.map { x =>
      MatrixEntry(x.i, x.j, x.value * damp)
    }).toBlockMatrix

    // (1-c) * I = S0
    val simMatrix = new CoordinateMatrix(unitMatrix.entries.map { x =>
      MatrixEntry(x.i, x.j, x.value * (1 - damp))
    }).toBlockMatrix

    // 初始化相似度矩阵
    val S_0 = simMatrix
    // K次迭代相似度矩阵
    var S_k = S_0
    // K+1次迭代相似度矩阵
    var S_kp1 = S_k

    for (i <- 0 until 5) {
      //S_kp1 = c * Q(T) * S_k * Q + (1-c)* I
      S_kp1 = transferMatrix.transpose.multiply(S_k).multiply(transferMatrix).multiply(cMatrix).add(simMatrix)
      S_k = S_kp1
    }


    val node2Index = index2Node.map(x => (x._2, x._1))
    val result = S_kp1.toCoordinateMatrix.entries.map {
      case MatrixEntry(x, y, j) => (x, y, "%.6f" format j)
    }.map(x => (x._1, (x._2, x._3)))
      .join(node2Index, 500)
      .map(x => (x._2._1._1, (x._2._1._2, x._2._2))).join(node2Index, 500)
      .map(x => (x._2._1._2, (x._2._2, x._2._1._1)))
      .join(itemSet, 500)
      .map(x => (x._1, x._2._1))
    result.filter(x => !x._1.equals(x._2._1)).map(x => (x._1, (x._2._1, x._2._2.toDouble)))
  }


  def main(args: Array[String]): Unit = {

    //屏蔽日志
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)


    //阻尼系数
    val damp = 0.6
    val spark = SparkSession
      .builder()
      .master("local")
      .appName("SimRank")
      .enableHiveSupport()
      .getOrCreate()

    val data = spark.sparkContext.textFile("data/graph_bipartite").map(x => (x.split("\t")(0), x.split("\t")(1)))
    // 计算item的图的相似度
    val item_sim = getSimilarity(data , damp)

    item_sim.take(100).foreach(println)
    spark.close() 
}}


你可能感兴趣的:(广告/推荐)