上一篇文章知識圖譜在大數據中的應用我們介紹了知識圖譜的一些概念和應用場景,今天我們就來看一個具體的應用案例了解下知識圖譜的應用。用戶增長對於一個APP的生存起到了至關重要的作用,沒有持續的用戶增長,再好的APP也不會走的長遠,為了獲得更多的用戶,APP運營商往往會鼓勵老用戶拉新並給與獎勵,比如趣頭條的收徒模式,用戶每收一個徒弟就會得到幾塊到十幾塊的現金返現,但是這種模式同時也會引起廣大黑產團伙的注意,黑產會利用各種手段來薅這些APP運營商的羊毛。
中國有句老話,叫物以類聚,人以群分,在反作弊和市場營銷等應用中,如果我們能根據用戶間的某些聯系發現社群,然后對這些社群進行反作弊分析或商品推薦,往往會起到意想不到的效果。
本文就來介紹一個簡單的社群發現的實踐。構建社群我們首先需要找到社群用戶的某種聯系,上文提到的收徒模式本身就是用戶間的一個天然聯系,我們可以根據用戶的師徒關系來構建社群。如下圖所示,根據師徒關系我們構建了一個社群,點表示用戶,邊表示師徒關系。
有了這樣的社群之后,我們就可以基於社群維度分析設備及用戶行為的異常,比如單個設備登陸過多的用戶,設備一直處於充電狀態,所有用戶行為高度一致等,同時可以計算社群用戶作弊率來通過已知作弊用戶來發現新的作弊用戶。
理清了需求之后我們開始着手根據用戶師徒關系構建社群。對"緊密聯系"的不同理解產生了很多社區發現算法。下圖是幾種經典的社群發現算法。
社群算法

- Triangle Counting:三角關系,圖論基礎知識。
- Connected Components:連通圖,圖論基礎知識。
- Strongly Connected Components:強連通圖,圖論基礎知識。
- Label Propagation:標簽傳播算法。
- 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很像,學起來相對簡單。
-
基本格式
MATCHWHERE 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演示了一個社交網絡的圖檢索示例。
想及時了解更多大數據實踐,請關注我的公眾號《大數據技術進階》