1. 程式人生 > >基於Spark Grahpx+Neo4j 實現使用者社群發現

基於Spark Grahpx+Neo4j 實現使用者社群發現

上一篇文章知識圖譜在大資料中的應用我們介紹了知識圖譜的一些概念和應用場景,今天我們就來看一個具體的應用案例瞭解下知識圖譜的應用。使用者增長對於一個APP的生存起到了至關重要的作用,沒有持續的使用者增長,再好的APP也不會走的長遠,為了獲得更多的使用者,APP運營商往往會鼓勵老使用者拉新並給與獎勵,比如趣頭條的收徒模式,使用者每收一個徒弟就會得到幾塊到十幾塊的現金返現,但是這種模式同時也會引起廣大黑產團伙的注意,黑產會利用各種手段來薅這些APP運營商的羊毛。

中國有句老話,叫物以類聚,人以群分,在反作弊和市場營銷等應用中,如果我們能根據使用者間的某些聯絡發現社群,然後對這些社群進行反作弊分析或商品推薦,往往會起到意想不到的效果。

本文就來介紹一個簡單的社群發現的實踐。構建社群我們首先需要找到社群使用者的某種聯絡,上文提到的收徒模式本身就是使用者間的一個天然聯絡,我們可以根據使用者的師徒關係來構建社群。如下圖所示,根據師徒關係我們構建了一個社群,點表示使用者,邊表示師徒關係。

有了這樣的社群之後,我們就可以基於社群維度分析裝置及使用者行為的異常,比如單個裝置登陸過多的使用者,裝置一直處於充電狀態,所有使用者行為高度一致等,同時可以計算社群使用者作弊率來通過已知作弊使用者來發現新的作弊使用者。

理清了需求之後我們開始著手根據使用者師徒關係構建社群。對"緊密聯絡"的不同理解產生了很多社群發現演算法。下圖是幾種經典的社群發現演算法。

社群演算法

  1. Triangle Counting:三角關係,圖論基礎知識。
  2. Connected Components:連通圖,圖論基礎知識。
  3. Strongly Connected Components:強連通圖,圖論基礎知識。
  4. Label Propagation:標籤傳播演算法。
  5. Louvain:一種基於"模組度"的經典演算法。

因為本文重點不是講述社群發現演算法,所以這個演算法具體的含義此處略過,有感興趣的讀者可自行研究。本文選用了最簡單的連通圖演算法來實現社群發現,即只要兩個節點之間有邊我們就把它們歸屬為一個社群。下面我們進入根據使用者師徒關係生成社群階段。

Spark Graphx構建社群

Spark Graphx本身就提供了構建圖並生成連通圖的介面,我們只需要按要求輸入資料就好了。如下圖所示:

我們構建點和邊,然後呼叫Graphx介面生成圖,最後呼叫圖的介面直接獲取連通圖。需要注意的是,Spark Graphx構建點和邊時,id需要用Long型別的數字表示,所以我們需要維護一張使用者id到數字id的維表。

//構建使用者節點
val users: RDD[(VertexId, String)] =
      spark.sparkContext.parallelize(Array((3L, "u3"), (7L, "u7"),(5L, "u5"), (2L, "u2"), (4L, "u4"),(6L, "u6"),(8L, "u8")))

//構建使用者邊
val relationships: RDD[Edge[String]] =
      spark.sparkContext.parallelize(Array(Edge(7L, 3L,""), Edge(5L, 3L,""),Edge(5L, 2L,""), Edge(6L, 4L,""),Edge(8L, 6L,"")))

//組合節點和邊構建圖
val graph = Graph(users, relationships)

//從圖中抽取出連通圖
val components = graph.connectedComponents()

//獲取連通圖中的點,vertices是一個tuple型別,key分別為所有的頂點id,value為key所在的連通圖id(連通圖中頂點id最小值)
val vertices = components.vertices

得到的vertices是如下的k-v資料:

   /**
      * vertices:
      * (6,4)
      * (8,4)
      * (3,2)
      * (7,2)
      * (5,2)
      *
      * 是一個tuple型別,key分別為所有的頂點id,value為key所在的連通圖id(連通圖中頂點id最小值)
      */

然後我們將邊relationships與vertices求出每條邊所在連通圖裡頂點id最小值。

val result = relationships.map(x =>{
      (x.srcId,x.dstId.toString)
    }).join(vertices)
      .map(y =>{
        // (7,(3,2)) => (2,(7,3))
        (y._2._2,(y._1,y._2._1))
 }) 

我們將結果存入圖資料Neo4j,視覺化後如下所示,可以看到我們得到了兩個社群。

至此,我們利用Spark Graphx構建出了社群,每個社群都有自己的一個社群id,然後我們就可以基於社群做一些具體分析了,比如,我可以計算社群作弊率,並取出TOP N的社群,如下所示。

想及時瞭解更多大資料實踐,請關注我的公眾號《大資料技術進階》
上面只是一個簡單的示例,其實我們可以給點和邊加上更多的屬性,利用圖的特性進行檢索,可以更高效的檢索出更多的資訊。為了更方便的儲存和查詢社群內的資料,我們可以將社群儲存到圖資料庫Neo4j。上面的社群圖就是用Neo4j展示的,那麼什麼是Neo4j呢?下面我們簡單的介紹下。

Neo4j簡介

Neo4j是一個嵌入式的、基於磁碟的、具備完全的事務特性的圖資料儲存引擎。作為圖資料庫,Neo4j最大的特點是關係資料的儲存。圖資料庫除了能夠像普通的資料庫一樣儲存一行一行的資料之外,還可以很方便的儲存資料之間的關係資訊。

例如,對於一個社交網路的使用者資料庫,你除了要儲存每個使用者的姓名、性別、喜好這些基本資訊外,你還需要儲存一個使用者和哪些使用者是朋友,和哪個使用者是情侶這些關係資料,這個時候Neo4j這樣的圖資料庫就可以派上用場啦。

通過下圖,大家可以瞭解下什麼是圖資料庫以及什麼是關係資料。

在上圖中,包含兩個標籤為"人"的資料節點,分別代表Ann和Dan兩個使用者。這兩個資料節點還包含姓名、出生地等屬性資訊,用於表示兩個使用者的基本資訊,就如同常規資料庫中的兩行資料。

除此之外,兩個資料節點之間還包含兩條關係資料,即Ann嫁給了Dan,Ann和Dan同居。利用這些關係資料,你就可以方便的作出基於關係的查詢,例如你可以查詢Ann跟誰結婚了,這就是圖資料庫的優勢。

可能有人會說,上邊寫的這種關係資料結構,SQL也可以通過多表join等方法實現,那要Neo4j還有什麼用?但畢竟術業有專攻,對於大量、複雜的關係資料處理,Neo4j在效能和使用方便程度上都是要遠勝於SQL的。下邊給大家簡單總結下Neo4j的特點。

Neo4j的特點

  • 像SQL一樣的查詢語言cypher
  • 它遵循屬性圖資料模型
  • 它通過使用Apache Lucence支援索引
  • 它支援UNIQUE約束
  • 它包含一個用於執行cypher命令的UI:Neo4j資料瀏覽器
  • 它支援完整的ACID(原子性,一致性,隔離性和永續性)規則
  • 它支援查詢的資料匯出到JSON和XLS格式
  • 它提供了REST API,可以被任何程式語言(如Java,Spring,Scala等)訪問
  • 它提供了可以通過任何UI MVC框架(如Node JS)訪問的Java指令碼
  • 它支援兩種Java API:Cypher API和Native Java API來開發Java應用程式
  • 支援高可用性主從叢集部署。

Cypher語言

Cypher是Neo4j的圖形查詢語言,關鍵字大小寫不敏感。語法和SQL很像,學起來相對簡單。

  • 基本格式
    MATCH WHERE RETURN
  • 模式
    () 表示節點
    [] 表示關係,關係是有向的,連線的點分為源點和目標點
    {} 表示屬性,每個屬性通過key:value的形式表示,多個屬性之間用逗號隔開,關係也可以有屬性
  • 標籤
    用來標識一個節點屬於哪一類。一個節點可以有多個或0個標籤。標籤沒有屬性。
    node:label1:label2 通過冒號給節點新增標籤,通過冒號分隔多個標籤

  • 基本的增刪改查
插入一個節點
CREATE (n:Person {name : 'Andres'});

插入一條邊
MATCH (a:Person),(b:Person) WHERE a.name = 'Node A' AND b.name = 'Node B‘ CREATE (a)-[r:Follow]->(b);

更新節點
MATCH (n:Person { name: 'Andres' })  SET n.name = 'Taylor';

刪除節點
MATCH (n:Person { name:'Taylor' }) DETACH DELETE n;

刪除邊
MATCH (a:Person)-[r:Follow]->(b:Person) WHERE a.name = 'Node A' AND b.name = 'Node B‘ DELETE r;

查詢一個節點的所有Follow
MATCH (:Person { name:'Taylor' })-[r:Follow]->(Person) RETURN Person.name;

查詢一個節點最短路徑
MATCH (ms:Person { name:'Node A' }),(cs:Person { name:'Node B' }), p = shortestPath((ms)-[r:Follow]-(cs))     RETURN p;

清空資料庫
MATCH (n) DETACH DELETE n

Neo4j資料瀏覽器

通過Neo4j瀏覽器就可以直接進行圖的查詢。

Cypher演示示例

我們使用Cypher查詢語言對Neo4j中的一個家庭進行建模,包括年齡,性別和家庭成員之間的關係等個人屬性。我們建立了一些朋友來擴大我們的社交圖,然後新增鍵/值對來生成每個使用者看過的電影列表。最後,我們查詢了我們的資料,使用圖形分析來搜尋一個使用者沒有看到但可能喜歡的電影。

建立家庭成員節點及關係

CREATE (person:Person {name: "Steven", age: 45}) RETURN person
CREATE (person:Person {name: "Michael", age: 16}) RETURN person
CREATE (person:Person {name: "Rebecca", age: 7}) RETURN person
CREATE (person:Person {name: "Linda",age:40}) RETURN person
MATCH (steven:Person {name: "Steven"}), (linda:Person {name: "Linda"}) CREATE (steven)-[:IS_MARRIED_TO]->(linda) return steven, linda
MATCH (michael:Person {name: "Michael"}), (rebecca:Person {name: "Rebecca"}) CREATE (michael)-[:IS_SIBLILNG]->(rebecca) return michael, rebecca
MATCH (steven:Person {name: "Steven"}), (michael:Person {name: "Michael"}) CREATE (steven)-[:HAS_CHILD]->(michael) return steven, michael
MATCH (steven:Person {name: "Steven"}), (rebecca:Person {name: "Rebecca"}) CREATE (steven)-[:HAS_CHILD]->(rebecca) return steven, rebecca
MATCH (linda:Person {name: "Linda"}), (michael:Person {name: "Michael"}) CREATE (linda)-[:HAS_CHILD]->(michael) return linda, michael
MATCH (linda:Person {name: "Linda"}), (rebecca:Person {name: "Rebecca"}) CREATE (linda)-[:HAS_CHILD]->(rebecca) return linda, Rebecca

新增朋友節點及關係,組成社交網路

MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(charlie:Person {name: "Charlie", age: 16}) RETURN michael, charlie
MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(koby:Person {name: "Koby"}) RETURN michael, koby
MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(grant:Person {name: "Grant"}) RETURN michael, grant
MATCH (rebecca:Person {name: "Rebecca"}) CREATE (rebecca)-[:FRIEND]->(jordyn:Person {name: "Jordyn"}) RETURN rebecca, jordyn
MATCH (rebecca:Person {name: "Rebecca"}) CREATE (rebecca)-[:FRIEND]->(katie:Person {name: "Katie"}) RETURN rebecca, katie

新增電影節點及關係,並攜帶打分屬性

CREATE (movie:Movie {title:"Avengers"}) RETURN movie
MATCH (michael:Person {name:"Michael"}), (avengers:Movie {title:"Avengers"}) CREATE (michael)-[:HAS_SEEN {rating:5}]->(avengers) return michael, avengers
CREATE (movie:Movie {title:"Batman"}) RETURN movie
CREATE (movie:Movie {title:"Gone with the Wind"}) RETURN movie
CREATE (movie:Movie {title:"Spongebob Square Pants"}) RETURN movie
CREATE (movie:Movie {title:"Avengers 2"}) RETURN movie
MATCH (charlie:Person {name:"Charlie"}), (movie:Movie {title:"Batman"}) CREATE (charlie)-[:HAS_SEEN {rating:4}]->(movie) return charlie, movie
MATCH (charlie:Person {name:"Charlie"}), (movie:Movie {title:"Gone with the Wind"}) CREATE (charlie)-[:HAS_SEEN {rating:0}]->(movie) return charlie, movie
MATCH (koby:Person {name:"Koby"}), (movie:Movie {title:"Batman"}) CREATE (koby)-[:HAS_SEEN {rating:4}]->(movie) return koby, movie
MATCH (koby:Person {name:"Koby"}), (movie:Movie {title:"Avengers 2"}) CREATE (koby)-[:HAS_SEEN {rating:5}]->(movie) return koby, movie
MATCH (grant:Person {name:"Grant"}), (movie:Movie {title:"Spongebob Square Pants"}) CREATE (grant)-[:HAS_SEEN {rating:1}]->(movie) return grant, movie
MATCH (jordyn:Person {name:"Jordyn"}), (movie:Movie {title:"Spongebob Square Pants"}) CREATE (jordyn)-[:HAS_SEEN {rating:5}]->(movie) return jordyn, movie
MATCH (michael:Person {name: "Michael"}) SET michael.gender = "male" RETURN michael
MATCH (rebecca:Person {name: "Rebecca"}) SET rebecca.gender = "female" RETURN rebecca

最後我們通過下面語句查詢steven的孩子的男性朋友看過而且打分大於3分的電影

MATCH (steven:Person {name:"Steven"})-[:HAS_CHILD]-(child:Person)-[:FRIEND]-(friend:Person)-[hasSeen:HAS_SEEN]-(movie:Movie) WHERE child.gender = "male" AND hasSeen.rating > 3 RETURN DISTINCT movie.title

總結

本文主要介紹了利用Spark Graphx實現了一個簡單的連通圖社群發現示例,並將社群存入到圖資料庫Neo4j中,同時進一步介紹了Neo4j的一些概念和使用,最後用Neo4j演示了一個社交網路的圖檢索示例。

想及時瞭解更多大資料實踐,請關注我的公眾號《大資料技術進階