笔记

PAGE1

Python是一门十分流行的编程语言,它免费、易学,而且功能强大,在网络编程、图形用户界面编程、科学计算、数据挖掘、机器学习、人工智能等方面都有着广泛的应用。今天我们教大家用python找出《天龙八部》《倚天屠龙记》《射雕英雄传》里的主角。


正文


对于预处理得到的数据,我们会进行一些数据统计,分析数据的统计情况。对于文本数据,一个比较有用的统计数据是词频。所谓词频(Word Frequncy),指的是单词在文本中出现的次数。


词频的统计—谁是金庸笔下的主角?


具体到小说分析问题,我们可以统计小说中的各个角色的出场次数。显然,对于角色来说,主要人物的出场次数多,词频也较高,因此可以通过词频统计,大致看出小说中谁的主角光环比较强。

假定所有的小说文件的名称都是“小说名.txt”,编码都是utf8。

仍然以《天龙八部》为例,首先用codecs模块,读取小说的内容:

with codecs.open("天龙八部.txt", encoding="utf8") as f:

    content = f.read()

利用人物字典character_info得到《天龙八部》中所有人物的列表chars,并用字符串的.count()方法对chars中单个人物c在content出现的次数进行计数:

chars = character_info["天龙八部"]

counts = [content.count(c) for c in chars]

为了方便对词频进行排序,我们将这两个列表变成NumPy数组:

import numpy as np


chars = np.array(character_info["天龙八部"])

counts = np.array([content.count(c) for c in chars])

利用数组的.argsort()方法对单词出现的次数counts进行排序,得到相应的下标:

idx = counts.argsort()

默认情况下,数组的排序是按照从小到大进行的。因此,可以使用idx[-10:]得到《天龙八部》中出现次数最多的10个角色的索引并利用索引i得到人名及其出现的次数:

for i in idx[-10:]:

    print chars[i], counts[i]


木婉清 688

段正淳 706

王语嫣 814

慕容复 861

阿朱 961

乔峰 1086

阿紫 1108

虚竹 1584

萧峰 1724

段誉 3245


自学Python,用Python找出金庸先生笔下的主角_词频

从词频统计来看,段誉荣登主角宝座


词频的绘图—谁是金庸笔下的主角?


我们还可以对词频进行绘图,从而得到更直观的结果。

在绘制词频图像之前,需要解决在Matplotlib中显示汉字的问题。

默认情况下,Matplotlib是不能直接显示汉字的。例如,直接添加汉字标题会得到如图1所示的结果:

import matplotlib.pyplot as plt

plt.title("中文")

plt.show()

自学Python,用Python找出金庸先生笔下的主角_词频统计_02自学Python,用Python找出金庸先生笔下的主角_词频_03

图1  不能正常显示的中文标题


标题中应该显示“中文”两个字的地方被两个方块替代了,原因是Matplotlib找不到合适的中文字体去显示中文。

为此,我们需要找到一些支持中文的字体。

Windows 7 及以上的系统中,字体库的位置为 C:/Windows/Fonts,如:

l  宋体:C:/Windows/Fonts/simsun.ttc

Linux 系统可以通过 fc-list 命令查看已有的字体和相应的位置,如:

l  宋体:/usr/share/fonts/truetype/osx-font-family/Songti.ttc

也可以从网上下载字体:

l  Yahei Consolas 字体:YaHei.Consolas.1.11b.ttf;

字体可以使用matplotlib.font_manager 中的FontProperties类导入:

from matplotlib.font_manager import FontProperties

font_song = FontProperties(fname="C:/Windows/Fonts/simsun.ttc")

其中,fname参数表示字体文件的位置。

这样,我们就将宋体导入了对象font_song中。

导入后,为了显示汉字我们在需要写入中文文字的函数中,加入fontProperties参数,指定显示文字的字体为font_song。例如,对于第一小节中的例子,加入参数指定宋体后,标题可以正常显示中文,得到如图2所示的结果:

import matplotlib.pyplot as plt

plt.title("中文", fontproperties=font_song)

plt.show()


自学Python,用Python找出金庸先生笔下的主角_python_04

图2  正常显示的中文标题


解决图像中文显示的问题后,我们回到文本分析的案例中。

为了让词频的显示更直观,可以使用plt.barh()函数绘制一个纵向的条形图,来表示《天龙八部》中出现次数前10位的人物的词频统计结果,如图3所示:

plt.barh(range(10), counts[idx[-10:]])

plt.title("天龙八部", fontproperties=font_song)

plt.yticks(range(10), chars[idx[-10:]], fontproperties=font_song)

plt.show()

其中,横轴是词出现的次数,纵轴是对应的人物名。

从图3中可以看到,词频图像的显示要比文字更为直观,不难得出“段誉是贯穿全书的主角”的猜想。

为了统计其他小说的词频,我们定义一个函数find_main_chars(),实现词频统计和绘图的过程。该函数接受两个参数:novel和num,其中novel是小说的名称,num是显示的主角个数(默认为10):



自学Python,用Python找出金庸先生笔下的主角_词频统计_05

图10-3  《天龙八部》主角词频统计


def find_main_chars(novel, num=10):

    # 读取文件

    with codecs.open("{}.txt".format(novel), encoding="utf8") as f:

        content = f.read()

    # 词频统计

    chars = np.array(character_info[novel])

    counts = np.array([content.count(c) for c in chars])

    idx = counts.argsort()

    # 绘图

    plt.barh(range(num), counts[idx[-num:]])

    plt.title(novel, fontproperties=font_song)

    plt.yticks(range(num), chars[idx[-num:]], fontproperties=font_song)

    plt.show()

可以利用该函数查看其他小说中的词频统计。例如,《射雕英雄传》和《倚天屠龙记》的词频统计如图4和5所示:


自学Python,用Python找出金庸先生笔下的主角_词频_06

图4  《射雕英雄传》主角词频统计


自学Python,用Python找出金庸先生笔下的主角_词频统计_07

图5  《倚天屠龙记》主角词频统计


find_main_charecters("射雕英雄传")

find_main_charecters("倚天屠龙记")

从图4和图5中,不难得出《射雕英雄传》的主角是郭靖以及《倚天屠龙记》的主角是张无忌的结论。

以上结果说明,词频统计能够较好地找出小说中的主角人物。


关系分析


找出主角之后,我们再来运用关系分析的方法,找出下金庸先生笔下,各路英雄豪杰的微妙关系。

之前有人研究过,用Word2Vec生成的词向量存在这样的现象:

vec("北京")-vec("中国")=vec("巴黎")-vec("法国")

这种现象通常说明了一种对应关系:

“北京”之于“中国”,正如“巴黎”之如“法国”.

在gensim中,对应关系也可以使用模型的.most_similar()方法来找到。利用该方法,可以定义函数find_relationship()来寻找这样的对应关系,该函数接受a,b,c作为参数,以a和b的关系作为参考,返回与c对应具有类似关系的d。

在函数中,我们将.most_similar()方法中的postive参数设为b和c组成的列表,并加入negative参数,将其设为只有a一个元素组成的列表。.most_similar()方法返回的结果仍然是前10个相关词及其对应的相似度,这里取返回结果的第一个,作为最终找到的d。

因此,函数find_relationship()的定义为:

def find_relationship(a, b, c):

    d, _ = model.most_similar(positive=[c, b], negative=[a])[0]

    print "{}之于{},正如{}之于{}".format(a, b, c, d)

利用find_relationship()函数,我们可以找出人物之间存在的一些微妙关系:

find_relationship("郭靖", "黄蓉", "杨过")

find_relationship("郭靖", "华筝", "杨过")

find_relationship("段誉", "公子", "韦小宝")

find_relationship("郭靖", "降龙十八掌", "黄蓉")


郭靖之于黄蓉,正如杨过之于小龙女

郭靖之于华筝,正如杨过之于郭芙

段誉之于公子,正如韦小宝之于大人

郭靖之于降龙十八掌,正如黄蓉之于打狗棒法

最铁的关系大概是这对:

find_relationship("杨过", "小龙女", "韦小宝")

find_relationship("令狐冲", "盈盈", "韦小宝")

find_relationship("郭靖", "黄蓉", "韦小宝")


杨过之于小龙女,正如韦小宝之于康熙

令狐冲之于盈盈,正如韦小宝之于康熙

郭靖之于黄蓉,正如韦小宝之于康熙

这些关系也能佐证使用词向量模型的合理性。


自学Python,用Python找出金庸先生笔下的主角_词频_08