用word2vec解读延禧攻略人物关系

用word2vec解读延禧攻略人物关系

原文来自公众号 无界社区mixlab 链接如下:
https://mp.weixin.qq.com/s/zRqt9OL6G1s3UZY1AJR9ag
关系图谱地址 https://shadowcz007.github.io/text2kg/
本文是对原文进行的复现,现将具体的实现过程记录如下。

一. 语料准备
1.爬取延禧攻略剧本

要获取原始语料的通用办法就是利用爬虫技术对相应的内容进行爬取,由于本次爬取的内容比较简单,不需要考虑网址去重、增量爬取等比较难的问题,因此没有必要选取 scrapy 这种重量级的工具,直接用 request + BeautifulSoup 就足以应付了,代码如下:

# -*- coding: utf-8 -*-
# Author:gaozhengjie
# Blog:https://www.jianshu.com/u/02877dbc2662
# E-mail:gaozhengj@foxmail.com
# Python Version:3.6.1
# Time:2018/8/29
# Description:爬取延禧攻略剧本,并保存在本地

import urllib.request
from bs4 import BeautifulSoup

# 延禧攻略小说的URL
url = 'http://www.pingyaoji.com/yanqing/yxgljb/'
res = urllib.request.urlopen(url)
soup = BeautifulSoup(res, "html.parser")
character_list = soup.select('ul')[3]

# 开始捕捉具体页面的信息
content = ''
for each_li in character_list.select('.title'):
    res = urllib.request.urlopen('http://www.pingyaoji.com' + each_li.get('href'))
    soup = BeautifulSoup(res, "html.parser")
    content = content + soup.select('p')[0].getText()

with open(u'延禧攻略剧本.txt', 'w', encoding='utf-8') as fp:
    fp.write(content)
2. 爬取延禧攻略小说

小说和剧本均来源于同一个网站,其网页结构均一样,因此代码中只需要修改对应的网址和保存的文件名字即可。

# -*- coding: utf-8 -*-
# Author:gaozhengjie
# Blog:https://www.jianshu.com/u/02877dbc2662
# E-mail:gaozhengj@foxmail.com
# Python Version:3.6.1
# Time:2018/8/29
# Description:爬取延禧攻略小说,并保存在本地

import urllib.request
from bs4 import BeautifulSoup

# 延禧攻略小说的URL
url = 'http://www.pingyaoji.com/yanxigonglue/?tdsourcetag=s_pctim_aiomsg'
res = urllib.request.urlopen(url)
soup = BeautifulSoup(res, "html.parser")
character_list = soup.select('ul')[3]

# 开始捕捉具体页面的信息
content = ''
for each_li in character_list.select('.title'):
    res = urllib.request.urlopen('http://www.pingyaoji.com' + each_li.get('href'))
    soup = BeautifulSoup(res, "html.parser")
    content = content + soup.select('p')[0].getText()

with open(u'延禧攻略小说', 'w', encoding='utf-8') as fp:
    fp.write(content)
3. 剧中人物名称
延禧攻略演员表

上面这个截图就取自于前面我提供的网址里面显示的延禧攻略演员表,这个相比前两个来说,我采用的方法就很简单粗暴了。因为这本身就是一个表格,我直接将其复制到了 Excel 表格中进行处理,复制过去的内容会变成一列,剧中名称和真实名称会交替出现,如下图所示,在这种情况下,如何获取剧中角色的名称呢,可以参考此文 使用Excel隔行选取的3种小技巧

将网页中的内容复制粘贴到 Excel 中

最后别忘记两件事:

  • 将里面的 “饰演角色” 和 “演员名字” 删掉;
  • 将 “/” 中的内容进行分割,这是同一个人物的不同称呼,当然我们还需要对这些称呼进行完善,部分结果如下图所示。在此感谢 小昕同学 不辞劳苦帮忙完善的角色名称表!
延禧攻略名称表
二. 数据处理
1. 分词
  • jieba 分词
  • 停用词表
  • 由角色名称构成的用户自定义词典,以此确保分词的时候能将人物名称提取出来
# -*- coding: utf-8 -*-
# Author:gaozhengjie
# Blog:https://www.jianshu.com/u/02877dbc2662
# E-mail:gaozhengj@foxmail.com
# Python Version:3.6.1
# Time:2018/8/29
# Description:对延禧攻略的文本进行去停用词和分词处理

import jieba
import jieba.analyse
import openpyxl

# jieba.analyse.set_stop_words('.\\stopwords.txt')
stopwords_file = 'stopwords.txt'  # 停用词表
stopwords_set = open(stopwords_file, 'r', encoding='utf-8').read().split('\n')  # 导入停用词表
stopwords = [word.strip() for word in stopwords_set]
name_list =  open('name_dict.txt', 'r', encoding='GBK').read().split('\n')
jieba.load_userdict(name_list)

text = open(u'延禧攻略小说.txt', 'r', encoding='utf-8').read()
word_cut = jieba.cut(text, cut_all=False)  # 精确模式,返回的结构是一个可迭代的genertor
word_list = list(word_cut)  # genertor转化为list,每个词unicode格式
word_set = []
for word in word_list:
    word = word.strip()
    if word not in stopwords and word != '' and not word.isdigit():  # 去停用词
        # if word >= u'\u4e00' and word <= u'\u9fa5':  # 判断是否是汉字
        word_set.append(word)
word_str = ' '.join(word_set)
# 对别称进行替换并保存分词后的结果
wb = openpyxl.load_workbook('延禧攻略人物名称.xlsx')
sheet = wb[wb.sheetnames[0]]
for i in range(1, sheet.max_row+1):
    for j in range(2, sheet.max_column+1):
        if sheet.cell(row=i,column=j).value != None:
            word_str = word_str.replace(sheet.cell(row=i,column=j).value, sheet.cell(row=i,column=1).value)
open(u'延禧攻略小说分词结果.txt', 'w', encoding='utf-8').write(' '.join(word_set)) 
2. 训练 Word2Vec 模型

关于 Word2Vec 的介绍,可以查看公众号中的原文,关于 gensim 中的 word2vec 模型的简单使用,可以参考博文 word2vec的应用----使用gensim来训练模型

from gensim.models import word2vec

#加载分此后的文本,使用的是Ttext2Corpus类
sentences = word2vec.Text8Corpus(u'延禧攻略剧本和小说分词结果.txt')

#训练模型,部分参数如下
model  = word2vec.Word2Vec(sentences, size = 100, hs = 1, min_count = 1, window = 3)

# 模型的保存与载入
model.save(u'延禧攻略小说.model')
3. 处理数据

本模块主要涵盖以下任务:

  • 关系图谱中人物的图标有大小之分,越核心的人物的图标越大,关于如何界定人物的重要性呢,本文采用 TF-IDF 方法来计算人物的重要性,将其 TF-IDF 值作为图标的 size 的基准值。对于频率实在太低的边缘性人物,本文直接为其设定了 size 的下界为 1,以此避免图标太小根本看不见的情况。
    result.append({"name": i[0], "value": i[1], "symbolSize": max(i[1]*30, 1)})
  • 通过设定阈值,计算出每个人物的近邻集合,近邻即是与谁的关系比较接近
  • 先将所有人物按照频率降序排列,然后将所有人物依次遍历计算一遍相似度,找到相似人集合,以此建立 source → target 有向关系图
  • 将数据构造成 json 格式,然后存储于本地文件夹中,方便前端进行调用

需要注意的是
json 格式中的引号是双引号,而非单引号,同时,一定要用 json.dumps() 方法进行将字符串转换成 json 格式,否则前端的关系图谱无法正常显示。关于python如何解析和转换json数据的可以参看我之前的博文 用Python解析json数据

from sklearn import feature_extraction
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
import gensim
import json

fp = open(u'延禧攻略剧本和小说分词结果.txt', 'r', encoding='utf-8').read()
corpus = [fp]

vectorizer=CountVectorizer()#该类会将文本中的词语转换为词频矩阵,矩阵元素a[i][j] 表示j词在i类文本下的词频
transformer=TfidfTransformer()#该类会统计每个词语的tf-idf权值
tfidf=transformer.fit_transform(vectorizer.fit_transform(corpus))#第一个fit_transform是计算tf-idf,第二个fit_transform是将文本转为词频矩阵
word=vectorizer.get_feature_names()#获取词袋模型中的所有词语
weight=tfidf.toarray()#将tf-idf矩阵抽取出来,元素a[i][j]表示j词在i类文本中的tf-idf权重

name_dict = {}
with open('name.txt', 'r', encoding='GBK') as fp:
    for each_name in fp.readlines():
        name = each_name.strip()
        try:
            index = word.index(name)
        except ValueError as error:
            pass
#             print("无对应的名称", each_name.strip())
        else:
            frequency = weight[0,word.index(name)]
#             print(name, frequency)
            name_dict[name] = frequency

sort_list = sorted(name_dict.items(),key = lambda x:x[1],reverse = True)

# 计算每个节点的信息
result = []
for i in name_dict.items():
    result.append({"name": i[0], "value": i[1], "symbolSize": max(i[1]*30, 1)})

# 接下来主要是处理 source → target 的关系
# 主要需要计算人物之间的相似度
model = gensim.models.Word2Vec.load(u'延禧攻略小说.model')
link = []
for i in range(0,len(sort_list)-1):
    for j in range(i+1, len(sort_list)):
        try:
            if model.similarity(sort_list[i][0], sort_list[j][0]) > 0.9:
                link.append({"source": sort_list[i][0], "target": sort_list[j][0]})
        except KeyError as error:
            pass

with open('node_info.txt', 'w', encoding='utf-8') as fp:
    fp.write(json.dumps(result))
with open('edge_info.txt', 'w', encoding='utf-8') as fp:
    fp.write(json.dumps(link))
前端显示

采用的前端数据可视化框架是 echarts
要显示人物关系图,需要注意以下几个问题:

  • json 数据通过 ajax 访问本地文件进行加载,此时需要配置服务器才能进行访问,所幸的是 python 自带的有 http.server ,使用起来非常方便,只需要一行命令即可启动服务器,然后在浏览器中访问即可。

python -m http.server

<!DOCTYPE html>
<html style="height: 100%">
<head>
    <meta charset="utf-8">
    <!-- 引入 ECharts 文件 -->
    <script src="echarts.min.js"></script>
    <script src="jquery-3.2.1.min.js"></script>
    <script src="http://echarts.baidu.com/gallery/vendors/echarts/extension/dataTool.js"></script>
</head>
<body style="height: 100%; margin: 0">
<div id="container" style="height: 100%; width: 100%"></div>

<script type="text/javascript">
    $.ajax({
        type: "GET",
        url: "/data/node_info.txt",
        dataType: "text",
        success: function (data) {
            data = eval(data)
            console.log(data);
            node_info = data;
        },
        error: function (err) {
            alert("节点信息读取错误");
        }
    });
    $.ajax({
        type: "GET",
        url: "/data/edge_info.txt",
        dataType: "text",
        success: function (data) {
            data = eval(data)
            console.log(data);
            edge_info = data;
        },
        error: function (err) {
            alert("边信息读取错误");
        }
    });

    var dom = document.getElementById("container");
    var myChart = echarts.init(dom);
    var app = {};
    option = null;
    app.title = '力引导布局';

    myChart.showLoading();
    $.get('/data/les-miserables.gexf', function (xml) {
        myChart.hideLoading();

        var graph = echarts.dataTool.gexf.parse(xml);

        option = {
            title: {},
            tooltip: {},
            legend: [{
                data: categories.map(function (a) {
                    return a.name;
                })
            }],
            animation: false,  // 是否开启动画
            series: [
                {
                    type: 'graph',
                    layout: 'force',
                    data: node_info,
                    links: edge_info,
                    roam: true,
                    label: {
                        normal: {
                            position: 'right',  // 标签位置
                            show: true,  // 显示标签
                            // showContent: true
                        }
                    },
                    force: {
                        initLayout: 'circular',
                        // layoutAnimation: false,
                        repulsion: 100  //排斥力
                    },
                    draggable: true,  // 允许拖拽
                    focusNodeAdjacency: true,  // 在鼠标移到节点上的时候突出显示节点以及节点的边和邻接节点
                    emphasis: {  // 设置高亮图形样式
                        lineStyle: {
                            width: 3
                        },
                        itemStyle: {
                            color: 'black',
                        }
                    },
                    roam: true,  // 开启鼠标缩放和平移漫游
                    edgeLength: 10,
                }
            ]
        };

        myChart.setOption(option);
    }, 'xml');
    ;
    if (option && typeof option === "object") {
        myChart.setOption(option, true);
    }
</script>
</body>
</html>
参考文章
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容