一直以来对自然语言处理和社交网络分析都很感兴趣,前者能帮助我们从文本中获得很多发现,而后者能够让我们对人们和各个事物之间普遍存在的网络般的联系有更多认识。当二者结合,又会有怎样的魔力呢?
作为一个三国迷,我就有了这样的想法:能不能用文本处理的方法,得到《三国演义》中的人物社交网络,再进行分析呢?python中有很多好工具能够帮助我实践我好奇的想法,现在就开始动手吧。
准备工作及小范围尝试
获得《三国演义》的文本。
chapters = get_sanguo() # 文本列表,每个元素为一章的文本
print(chapters[0][:106])
第一回 宴桃园豪杰三结义 斩黄巾英雄首立功
滚滚长江东逝水,浪花淘尽英雄。是非成败转头空。
青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。一壶浊酒喜相逢。
古今多少事,都付笑谈中。
《三国演义》并不是很容易处理的文本,它接近古文,我们会面对古人的字号等一系列别名。比如电脑怎么知道“玄德”指的就是“刘备”呢?那就要我们给它一些知识。我们人通过学习知道“玄德”是刘备的字,电脑也可以用类似的方法完成这个概念的连接。我们需要告诉电脑,“刘备”是实体(类似于一个对象的标准名),而“玄德”则是“刘备”的一个指称,告诉的方式,就是提供电脑一个知识库。
entity_mention_dict, entity_type_dict = get_sanguo_entity_dict()
print("刘备的指称有:",entity_mention_dict["刘备"])
刘备的指称有: ['刘备', '刘玄德', '玄德', '使君']
除了人的实体和指称以外,我们也能够包括三国势力等别的类型的指称,比如“蜀”又可以叫“蜀汉”,所以知识库里还可以包括实体的类型信息来加以区分。
print("刘备的类型为",entity_type_dict["刘备"])
print("蜀的类型为",entity_type_dict["蜀"])
print("蜀的指称有",entity_mention_dict["蜀"])
刘备的类型为 人名
蜀的类型为 势力
蜀的指称有 ['蜀', '蜀汉']
有了这些知识,理论上我们就可以编程联系起实体的各个绰号啦。不过若是要从头做起的话,其中还会有不少的工作量。而HarvestText[1]是一个封装了这些步骤的文本处理库,可以帮助我们轻松完成这个任务。
ht = HarvestText()
ht.add_entities(entity_mention_dict, entity_type_dict) # 加载模型
print(ht.seg("誓毕,拜玄德为兄,关羽次之,张飞为弟。",standard_name=True))
['誓毕', ',', '拜', '刘备', '为兄', ',', '关羽', '次之', ',', '张飞', '为弟', '。']
成功地把指称统一到标准的实体名以后,我们就可以着手挖掘三国的社交网络了。具体的建立方式是利用邻近共现关系。每当一对实体在两句话内同时出现,就给它们加一条边。那么建立网络的整个流程就如同下图所示:
我们可以使用HarvestText提供的函数直接完成这个流程,让我们先在第一章的小文本上实践一下:
# 准备工作
doc = chapters[0].replace("操","曹操") # 由于有时使用缩写,这里做一个微调
ch1_sentences = ht.cut_sentences(doc) # 分句
doc_ch01 = [ch1_sentences[i]+ch1_sentences[i+1] for i in range(len(ch1_sentences)-1)] #获得所有的二连句
ht.set_linking_strategy("freq")
# 建立网络
G = ht.build_entity_graph(doc_ch01, used_types=["人名"]) # 对所有人物建立网络,即社交网络
# 挑选主要人物画图
important_nodes = [node for node in G.nodes if G.degree[node]>=5