美文网首页大数据,机器学习,人工智能大数据spark
使用Spark Graphx 探索你不知道的金庸武侠世界

使用Spark Graphx 探索你不知道的金庸武侠世界

作者: 猪联璧何 | 来源:发表于2019-03-16 19:58 被阅读5次

    GitHub地址:https://github.com/weijie-he/jinyong

    一、缘起

    2018年10月30日,金庸在香港逝世,享年94岁。

    知道这个消息之后,我的情绪很低落,讲台上老师在讲什么仿佛也听不见了,脑海中一直在回想着先生写过的关于离别的句子。

    程英道:“三妹,你瞧这些白云聚了又散,散了又聚,人生离合,亦复如斯。你又何必烦恼?” 她话虽如此说,却也忍不住流下泪来。

    却听得杨过朗声说道:“今番良晤,豪兴不浅,他日江湖相逢,再当杯酒言欢。咱们就此别过。”

    金庸先生告诉了我什么是“侠”。作为先生的忠实读者,我觉得自己该做点什么来缅怀先生,以我自己的方式。

    正好,我在学Spark,便想到了利用Spark Graphx 做金庸小说人物关系分析图。

    二、需求分析

    金庸先生给我们留下了什么呢?最著名的无非是“飞雪连天射白鹿,笑书神侠倚碧鸳”这14本小说了。最容易想到的便是对这14本书做一张人物关系分析图。但这一来人物太多,最后画出的图会很大;二来不同书之间的人物很多也没什么关联,硬把他们放在同一张图里并不妥当。最终我决定只选取人物联系最紧密的“射雕三部曲”(《射雕英雄传》、《神雕侠侣》、《倚天屠龙记》)来进行分析。

    但是只分析人物又感觉略显单薄。金庸小说中还有一些其他的元素,比如如雷贯耳的称号(东邪西毒)、高深莫测的武功(黯然销魂掌)、神兵利器(倚天剑、屠龙刀)。我想把这些元素也加入到分析之中。

    同时还要考虑怎么利用Spark Graphx 的图计算功能,做一些有意义的分析。

    最终确立了以下需求:

    分析人物之间的亲密度关系

    找出“专属昵称”

    (很多人物之间的交流并不会直呼其名,比如黄蓉会叫郭靖“靖哥哥”,我想找出类似的“专属昵称”)

    探索小说人物中“孤岛群体”(即“小圈子”)

    有没有谁经常被某种武功/兵器揍

    三、工作流程

    3.1 获取"射雕三部曲"小说原文、人物名册、称号\武功\武器大全等初始数据

      ​ 小说原文很容易获取,人物名册、称号\武功\武器大全 等也可以在网上搜到。

    3.2 数据预处理,将数据转换成Graphx需要的格式

      ​ Graphx需要的是顶点集和边集的信息。

      ​ 在人物亲密度图中,我将人名、昵称作为顶点;在人物—武器关系图中,我将人名、武器、武功作为顶点。

      至于边集信息,是这样确定的:以原文中每一句话为单位。如果在这句话中,出现了两个上述的“顶点”,则认为他们产生了一次联系。如果在这句话中,出现了三个“顶点”,则认为他们两两之间都有一次联系。以此类推。

    处理完的结果保存在resources文件夹中。结果如下所示

    3.3 使用Spark GraphX 生成图

    我想把联系的次数作为边的权重。首先就要统计同一个联系出现的次数。这一步有点像WordCount,由于不想让一些打酱油的人物出现,所以还用了个filter函数过滤。

    /**

    * 统计关系出现的次数

    * @param sc

    * @param path:边文件

    * @param num:关系数量阈值

    * @return

    */

    defedgeCount(sc:SparkContext,path:String,num:Int)={

    valtextFile=sc.textFile(path)

    valcounts=textFile.map(word=>(word,1))

    .reduceByKey(_+_).filter(_._2>num)

    //    counts.collect().foreach(println)

    counts

      }

    使用顶点集和边集构建图

    /**

    * 构建图

    * @param sc

    * @param path1:顶点文件

    * @param path2:边文件

    * @param num:关系数量阈值

    */

    defcreatGraph(sc:SparkContext,path1:String,path2:String,num:Int)={

    valhero=sc.textFile(path1)

    valcounts=edgeCount(sc,path2,num)

    valverticesAll=hero.map{line=>

    valfields=line.split('')

    (fields(0).toLong,fields(1))

       }

    valedges=counts.map{line=>

    valfields=line._1.split(" ")

    Edge(fields(0).toLong,fields(1).toLong,line._2)//起始点ID必须为Long,最后一个是属性,可以为任意类型

       }

    valgraph_tmp=Graph.fromEdges(edges,1L)

    //    经过过滤后有些顶点是没有边,所以采用leftOuterJoin将这部分顶点去除

    valvertices=graph_tmp.vertices.leftOuterJoin(verticesAll).map(x=>(x._1,x._2._2.getOrElse("")))

    valgraph=Graph(vertices,edges)

    graph

      }

    至此,需求中的第一点:人物亲密度关系图已经生成。

    类似的,我们更换一下顶点集和边集,就可以生成人物——武器\武功的关系图,从而找出有没有谁经常被某种武功/兵器揍。

    3.4 使用Spark GraphX 处理图

    可以通过找出度为1或2的点,来寻找“专属昵称”。

    /**

    * 找出度为1或2的点

    * @param g

    * @tparam VD

    * @tparam ED

    * @return

    */

    defminDegrees[VD,ED](g:GraphOps[VD,ED])={

    //    g.degrees.filter(_._2<3).map(_._1).collect().mkString("\n")

    g.degrees.filter(_._2<3).map(_._1).collect().map(a=>a.toInt)

      }

    通过使用内置函数connectedComponents()可以找到小说人物中“孤岛群体”(即“小圈子”)。

    /**

    * 使用连通组件找到孤岛人群

    * @param g

    * @tparam VD

    * @tparam ED

    * @return

    */

    defisolate[VD,ED](g:GraphOps[VD,ED])={

    g.connectedComponents.vertices.map(_.swap).groupByKey().map(_._2).collect().mkString("\n")

      }

    由于之前我们是每本书都生成一张图,最后我们还需要把这几张图合并为一张图。

    思路就是先取得所有顶点信息,去除,再对这些顶点重新编号。再对这些新生成的点重新构建边。

    /**

    * 合并2张图

    * @param g1

    * @param g2

    * @return

    */

    defmergeGraphs(g1:Graph[String,Int],g2:Graph[String,Int])={

    valv=g1.vertices.map(_._2).union(g2.vertices.map(_._2)).distinct().zipWithIndex()

    defedgeWithNewVid(g:Graph[String,Int])={

    g.triplets.map(et=>(et.srcAttr,(et.attr,et.dstAttr)))

    .join(v)

    .map(x=>(x._2._1._2,(x._2._2,x._2._1._1)))

    .join(v)

    .map(x=>newEdge(x._2._1._1,x._2._2,x._2._1._2))

       }

    defreduceEdge(g3:Graph[String,Int],g4:Graph[String,Int])={

    edgeWithNewVid(g3).union(edgeWithNewVid(g4)).

    map(e=>((e.dstId,e.srcId),e.attr)).

    reduceByKey(_+_).

    map(e=>Edge(e._1._1,e._1._2,e._2))

       }

    Graph(v.map(_.swap),reduceEdge(g1,g2))

      }

    3.5 导出到Gephi

    我们可以把图像按照gexf格式输出,然后在Gephi中打开,就可以进行图形化展示。

    /**

    * 输出为gexf格式

    * @param g:图

    * @tparam VD

    * @tparam ED

    * @return

    */

    deftoGexf[VD,ED](g:Graph[VD,ED])={

    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+

    "<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">\n"+

    " <graph mode=\"static\" defaultedgetype=\"directed\">\n  "+

    "<nodes>\n "+

    g.vertices.map(v=>"  <node id=\""+v._1+"\" label=\""+v._2+"\" />\n").collect().mkString+

    "</nodes>\n  "+

    "<edges>\n"+

    g.edges.map(e=>"  <edge source=\""+e.srcId+"\" target=\""+e.dstId+"\" weight=\""+e.attr+"\"/>\n").

    collect().mkString+

    "</edges>\n        </graph>\n      </gexf>"

      }

    四、结果展示与分析

    以下图片的高清完整版可在output/pics中找到

    4.1 人物亲密度关系分析

    可以看出郭靖和黄蓉的颜色是最深的(联系是最紧密的)。这是因为他们在《射雕》和《神雕》中都有很多戏份。《神雕》中的男女主小龙女和杨过联系也很紧密。相比之下《倚天》中的男女主张无忌和赵敏直接的线就淡的多了。一方面,这是因为赵敏的出场时间太晚(全书40章,赵敏在第23章才出场)。另一方面,张无忌优柔寡断,情感方面也一直在赵敏和周芷若之间犹豫不决,导致张无忌的情感线被周芷若分流了许多。

    4.2 “专属昵称”分析

    由于我只是筛选出了度为1和2的点,但有些点是人名,而不是昵称,不必看。

    我原来以为“专属昵称”只出现在情侣之间,但发现有两个例外。

    洪七公——靖儿

    这两人情同父子。郭靖自幼丧父,洪七公也没有子嗣。俗话说,“一日为师终身为父”,我觉得这两个人不是父子,甚是父子。所以有这样的“专属昵称”也不奇怪。

    也许江南七怪也和郭靖情同父子,但可能是因为出现的频率不够高,所以被过滤掉了,这张图上并没有出现。

    陆无双——傻蛋

    全书只有陆无双一人可以叫杨过”傻蛋“,因为当初杨过骗陆无双自称傻蛋。

    那道姑道:“我几时骗过你了?喂,小子,你叫甚么名字?”杨过道:“人人都叫我傻蛋,你不知道么?你叫甚么名字?”那道姑道:“傻蛋,你只叫我仙姑就得啦。”

    摘录了一下原文,发现短短几句话,这道姑(陆无双)就笑了2次,足见他们相处的多么愉快。过儿一生孤苦,和陆无双在一起的日子也算是为数不多的快乐时光。我觉得他们俩很有成为情侣的可能,只可惜过儿心里已经有了小龙女。最后他们俩结为了兄妹,也算是一段“有情人终成兄妹”的悲剧故事。

    4.3 “孤岛人群”分析

    发现只有3个“孤岛人群”(小团体)。

    简捷和薛公远是《倚天屠龙记》中被金花婆婆打伤,找胡青牛治病的人。和他们有交集的人确实很少。

    李萍被段天德绑架,很长一段时间内只有他们两个在一起,别人都不知道他们去了哪。

    术赤和察合台是成吉思汗的两个儿子。和他们有交集的人也很少。

    这三本书中涉及到的人物,即使过滤完,也有将近200号人。如果在现实生活中,200人中应该会有更多的小团体,而且也不会全是2人组,可能有3~5人小团体。

    以下是我认为可能的两点原因:

    小说中,配角是为主角服务的,一般不会独立于主线人物之外去写小团体

    即便需要,写2人也够了,没必要花笔墨写

    4.4 人物——武功\兵器分析

    主要想看谁经常被哪种武功\兵器揍。

    无忌——玄冥神掌

    无忌小时候就因为中了玄冥神掌差点死掉,长大后也经常和玄冥二老斗。

    郭靖——蛤蟆功

    蛤蟆功可以说是郭靖发明的,就是因为他篡改了《九阴真经》,写了本“九阴假经”,才让欧阳锋练成了蛤蟆功。后来也数次和欧阳锋的蛤蟆功交手。《神雕》中小杨过也学了点蛤蟆功,被郭靖发现了,这又产生了一次交集。

    杨过——金轮、浮尘

    这是书中两大反派金轮法王和李莫愁的武器。

    五、后记

      人人都知道金庸,可大多是通过影视作品,读过原著的人少的可怜。做这个项目,在缅怀先生的同时,也希望有更多的人能去读一读原著,体会一下先生笔下原汁原味的江湖。

    相关文章

      网友评论

        本文标题:使用Spark Graphx 探索你不知道的金庸武侠世界

        本文链接:https://www.haomeiwen.com/subject/qcstpftx.html