用WordCloud词云+LDA主题模型,带你读一读《芳华》(python实现)

电影《芳华》在春节重映了一波,加上之前的热映,最终取得了14亿票房的好成绩。严歌苓的原著也因此被更多的人细细品读。用文本分析的一些技术肢解小说向来是自然语言处理领域的一大噱头,这次当然也不能放过,本篇达成的成就有: 1、提取两大主角刘峰和何小嫚(萍)的关键词并绘制好看的人物词云; 2、以章节为单位探索小说的主题分布并画图展示。

主要功能包:

jieba
lda
wordcloud
seaborn
安装命令: pip install ***
复制代码

需要的外部文件:

1、小说全文, 芳华-严歌苓.txt
2、中文停用词,stopwords.txt
3、小说人物名称,person.txt,作为jieba的用户自定义词典
4、两个人物的png图片
5、你喜欢的中文字体的ttf文件,我用的楷体
复制代码

一、文本预处理

1、分词,并过滤无意义词

文本挖掘的必备步骤,毕竟理解中文的最小单位是词汇。这里没有使用简单的jieba.cut进行分词,因为我们需要知道单词的词性,便于稍后根据词性过滤不重要的词。

采用jieba.posseg.cut分词可以输出词性。我们并不能拍脑门决定是要动词还是名词等等,词性有非常多个,我把全部分词结果按照词性分好类,看了一下每个词性对应哪些词,最后决定保留词性为["a", "v", "x", "n", "an", "vn", "nz", "nt", "nr"]的词,例如图中,m代表量词,这是对语义没有帮助的词,应该舍弃。

import jieba.posseg
jieba.load_userdict("data/person.txt")
STOP_WORDS = set([w.strip() for w in open("data/stopwords.txt").readlines()])

def cut_words_with_pos(text):
    seg = jieba.posseg.cut(text)
    res = []
    for i in seg:
        if i.flag in ["a", "v", "x", "n", "an", "vn", "nz", "nt", "nr"] and is_fine_word(i.word):
            res.append(i.word)
    return list(res)

# 过滤词长,过滤停用词,只保留中文
def is_fine_word(word, min_length=2):
    rule = re.compile(r"^[\u4e00-\u9fa5]+$")
    if len(word) >= min_length and word not in STOP_WORDS and re.search(rule, word):
        return True
    else:
        return False

复制代码

2、划分章节

我们按照“第*章”这样的字眼将小说的不同章节分割开来,作为独立的文档,用于之后的主题分析。定义了一个名为MyChapters的生成器,存储每章分好的词汇,是为了避免章节过多带来的一些程序运行问题。其实《芳华》仅有15章,用一个简单的列表也是可以的。

class MyChapters(object):
    def __init__(self, chapter_list):
        self.chapter_list = chapter_list

    def __iter__(self):
        for chapter in self.chapter_list:
            yield cut_words_with_pos(chapter)


def split_by_chapter(filepath):
    text = open(filepath).read()
    chapter_list = re.split(r'第.{1,3}章\n', text)[1:]
    return chapter_list
复制代码

二、人物关键词提取

要提取人物关键词,首先要解决的问题是,在不借助外部的人物描述(比如百度百科和豆瓣电影上的角色介绍)的情况下,如何确定跟这个人物相关的内容。这里采用的比较简单的策略是,对小说文件中的每一行,如果该人物的名称存在,则将该行加入到此人的相关语料中去。再以此为基础统计词频,结果大致ok,为了人物词云更精确的展示,我将词频输出到了文件,手动删除了一些词,并简单调整了一些词的词频,下图是调整过后的词和词频,左为何小嫚,右为刘峰。

import pandas as pd

def person_word(name):
    lines = open("data/芳华-严歌苓.txt", "r").readlines()
    word_list = []
    for line in lines:
        if name in line:
            words = cut_words_with_pos(line)
            word_list += words

    # 统计词频并按照词频由大到小排序,取top500
    cnt = pd.Series(word_list).value_counts().head(500)

    # 可以把结果输出到文件,进行一些手动调整
    # cnt.to_csv("data/cntliu.csv")

    # 返回字典格式
    return cnt.to_dict()
复制代码

三、词云绘制

python有wordcloud包可以用于词云绘制,在使用过程中需要注意:

1、用于定义形状的外部图片必须是png格式,默认纯白色部分为非图像区域; 2、中文词云必须载入一个字体文件; 3、字的颜色可以自己定义,也可以使用图片本身的底色。本例中何小嫚的图片 底色很鲜艳明晰,可以用本身的底色(ImageColorGenerator);而刘峰的图片是单色,且色浅,我使用了自定义颜色(my_color_func); 4、绘制词云需要用到的数据格式为dict,key为词,value为词频,词频越大,在图片中的字体越大。

import matplotlib.pyplot as plt
from wordcloud import WordCloud, ImageColorGenerator
from scipy.misc import imread
from random import choice

# 定义颜色,方法很多,这里用到的方法是在四个颜色中随机抽取
def my_color_func(word, font_size, position, orientation, random_state=None, **kwargs):
    return choice(["rgb(94,38,18)", "rgb(41,36,33)", "rgb(128,128,105)", "rgb(112,128,105)"])

def draw_cloud(mask_path, word_freq, save_path):
    mask = imread(mask_path)  #读取图片
    wc = WordCloud(font_path='data/kaiti.TTF',  # 设置字体
                   background_color="white",  # 背景颜色
                   max_words=500,  # 词云显示的最大词数
                   mask=mask,  # 设置背景图片
                   max_font_size=80,  # 字体最大值
                   random_state=42,
                   )
    # generate_from_frequencies方法,从词频产生词云输入
    wc.generate_from_frequencies(word_freq)

    plt.figure()

    # 刘峰, 采用自定义颜色
    plt.imshow(wc.recolor(color_func=my_color_func), interpolation='bilinear')

    # 何小嫚, 采用图片底色
    # image_colors = ImageColorGenerator(mask)
    # plt.imshow(wc.recolor(color_func=image_colors), interpolation='bilinear')

    plt.axis("off")
    wc.to_file(save_path)
    plt.show()
复制代码
# 获取关键词及词频
input_freq = person_word("刘峰")
# 经过手动调整过的词频文件,供参考
# freq = pd.read_csv("data/cntliu.csv", header=None, index_col=0)
# input_freq = freq[1].to_dict()
draw_cloud("data/liu.png", input_freq, "output/liufeng.png")
复制代码

对人物进行抠图,背景设置为纯白,存储为png格式。 为了使形状更鲜明,对小嫚的辫子还有腰的部分做了加白处理,可以对比文章开头原图感受一下。

如果你看过这部作品,不知道印象最深的是不是像词云显示的那样?小嫚在精神病院的月下独舞,刘峰对丁丁的深深眷恋,在战争中失去手臂,与不爱的人结婚又离婚,和小嫚以朋友的姿态相伴终老... ...

四、主题分析

lda 方法的原理不做介绍了,假设你设置了这15章讲了k个主题,那么它的输出是:1、每个主题都由哪些词构成,概率几何; 2、每章内容中,k个主题各占多大比例,占比越大,该章内容与该主题越贴切。

1、首先,整理模型输入

lda要求的输入格式为文档-词汇频次矩阵,也就是各词语在个章节中出现了多少次,我们用CountVectorizer可以一步实现。 CountVectorizer要求的输入格式为:["word1 word2", "word3 word4", ...] 即一个章节作为一个完整的字符串,其中的词用空格隔开

from sklearn.feature_extraction.text import CountVectorizer

def get_lda_input(chapters):
    corpus = [" ".join(word_list) for word_list in chapters]
    vectorizer = CountVectorizer()
    X = vectorizer.fit_transform(corpus)
    return X.toarray(), vectorizer
复制代码

2、训练模型

我们设置主题个数为20个,并打印如下内容: 每个主题打印最能描述该主题的前20个词 每章打印占比最高的前3个主题

def lda_train(weight, vectorizer):
    model = lda.LDA(n_topics=20, n_iter=500, random_state=1)
    model.fit(weight)

    doc_num = len(weight)
    topic_word = model.topic_word_
    vocab = vectorizer.get_feature_names()
    titles = ["第{}章".format(i) for i in range(1, doc_num + 1)]

    n_top_words = 20
    for i, topic_dist in enumerate(topic_word):
        topic_words = np.array(vocab)[np.argsort(topic_dist)][:-(n_top_words + 1):-1]
        print('Topic {}: {}'.format(i, ' '.join(topic_words)))

    doc_topic = model.doc_topic_
    print(doc_topic, type(doc_topic))
    plot_topic(doc_topic)
    for i in range(doc_num):
        print("{} (top topic: {})".format(titles[i], np.argsort(doc_topic[i])[:-4:-1]))
复制代码
def main():
    chapter_list = split_by_chapter("data/芳华-严歌苓.txt")
    chapters = MyChapters(chapter_list)
    weight, vectorizer = get_lda_input(chapters)
    lda_train(weight, vectorizer)
复制代码

输出结果:

Topic 0: 小惠 郝淑雯 少俊 好人 啤酒 看着 生意 老板 刘大哥 老战友 发廊 老公 邻居 汽车 背叛 城管 出卖 眼线 惠雅玲 路灯
Topic 1: 年轻 女人 照片 眼睛 想到 生命 跟着 来到 笑笑 院子 回去 房间 好看 军区 结婚 接受 打开 听说 坐在 关系
Topic 2: 刘峰 红苕 老百姓 老太太 红楼 括弧 落后 大娘 打靶 子弹 练功 板凳 榔头 文工团 男孩儿 地板 大胜 打着 剩下 姨太太
Topic 3: 母亲 父亲 女儿 牺牲 善良 名字 前线 丈夫 看着 坏话 干事 生活 触摸 碰到 妻子 家庭 手指尖 继父 脊梁 手指
Topic 4: 丁丁 林丁丁 干事 人格 小林 出卖 恶心 报告 声乐 回答 老师 摄影 库房 对象 演员 男女 喜欢 王老师 组织 权利
Topic 5: 女人 侄子 事儿 女朋友 电话 想象 红楼 手机 回来 地方 公司 酒店 日子 轿车 叔叔 皮包 战友 电梯 化疗 客厅
Topic 6: 丁丁 丈夫 食堂 妹妹 王家 肯定 故事 文工团 条件 函授 机器 笑笑 老板 夫人 工作 老三 姐妹 考试 姨妈 虚荣
Topic 7: 父亲 标兵 女兵 爸爸 父母 茶缸 政治 包裹 招待所 看成 学雷锋 萧穗子 送来 编造 捎来 文件 放进 行李袋 友谊商店 真话
Topic 8: 看着 黑色 眼睛 红色 郝淑雯 故事 学校 怀疑 毛衣 走出 闹钟 帽子 玩儿 老兵 柔软 军帽 起床 热爱 冷水 新兵
Topic 9: 何小嫚 头发 衬衫 演出 感觉 同志 所有人 轻伤 掌上明珠 伤员 分队 小何 潜意识 表演 体温 下放 连队 温度 退回 对话
Topic 10: 小时 跟头 毯子 看着 同屋 同情 小郝 领导 危险 甜饼 自由 炊事班 中提琴手 伙食 邀请 办法 目光 女孩儿 巷子 梆子
Topic 11: 团长 驾驶员 卫生员 骑兵 战士 护士 包扎 士兵 奖品 装病 流传 红蚁 卡车 体温计 服装 舞蹈 温度计 弹药 离开 军马
Topic 12: 护士 服务员 报道 医院 首长 战友 政治部 报纸 天使 住院 只能 剪断 包扎 标语 伏击 妈妈 学习 歌声 迟到 护理员
Topic 13: 母亲 弟弟 继父 女儿 拖油瓶 厅长 毛衣 弄堂 妹妹 绒线衫 高烧 亭子间 跟着 饺子 讨厌 小时 姆妈 虫蛀 卫生 姐姐
Topic 14: 刘峰 郝淑雯 点儿 林丁丁 告诉 女儿 时间 发生 男人 老家 好像 医院 拿出 无耻 等待 所有人 世界 不错 帮忙 不知
Topic 15: 丁丁 沙发 表弟 林丁丁 秘密 萧穗子 排长 胆石 眼睛 参观 吉普 排球场 专注 肌肤 卫生带 脱下 成功 距离 合算 衬衫
Topic 16: 身体 发现 孩子 回到 找到 不知 记得 说话 意识 见到 摇摇头 所有人 漂亮 样子 机会 显得 毛巾 我会 发言 不见
Topic 17: 老师 朱克 头发 郝淑雯 身体 走廊 乳罩 承认 藤椅 卫生员 军帽 撒谎 哨兵 地板 活儿 范儿 男舞者 眼泪 衬衣 海绵
Topic 18: 女兵 男兵 首长 明白 发现 地方 排练 回来 部队 舞蹈 动作 触摸 演出 事件 秘密 军装 生病 舞台 结束 接下去
Topic 19: 刘倩 平凡 追悼会 新兵 堂叔 灵台 操场 小林 灵堂 钥匙 冬青 通知 萨其马 老头儿 烈士陵园 小徐 看望 皮肤 土黄色 成就
复制代码

下面展示的章节所包括的主题,对照上面相应主题序号的词语,是否能大致判断每章在讲些什么呢。

第1章 (top topic: [ 2 18 16])
第2章 (top topic: [ 7 14 18])
第3章 (top topic: [10 14  7])
第4章 (top topic: [ 4 18 14])
第5章 (top topic: [15 14  1])
第6章 (top topic: [ 4 14 15])
第7章 (top topic: [ 3 14 16])
第8章 (top topic: [13 16  1])
第9章 (top topic: [ 8 13 18])
第10章 (top topic: [17  9 18])
第11章 (top topic: [11  9 18])
第12章 (top topic: [12  1  3])
第13章 (top topic: [ 6 14 18])
第14章 (top topic: [ 0 14  5])
第15章 (top topic: [19 14  1])
复制代码

3、画图

对于各章节的不同主题的分布,我们可以画个图来展示一下。 利用lda输出的doc_topic画热力图,doc_topic是一个二维数组,值为某主题在某章节的占比,刚刚打印的内容只可以看到每章包括的前三个主题,从下图中则可以看到全部主题在各章的分布情况,参考图例,颜色越深代表占比越大。

def plot_topic(doc_topic):
    f, ax = plt.subplots(figsize=(10, 4))
    cmap = sns.cubehelix_palette(start=1, rot=3, gamma=0.8, as_cmap=True)
    sns.heatmap(doc_topic, cmap=cmap, linewidths=0.05, ax=ax)
    ax.set_title('proportion per topic in every chapter')
    ax.set_xlabel('topic')
    ax.set_ylabel('chapter')
    plt.show()
    f.savefig('output/topic_heatmap.jpg', bbox_inches='tight')
复制代码

五、结语

中文的自然语言处理技术是一项特别繁杂的工作,需要注意非常多的细节,在分析的过程中,我也花了足够的精力做数据可视化,好看的图不仅可以吸引人的眼球,更可以加深我们对数据的理解。此外,探索一本小说,除了关键词和主题,还有很多别的思路,比如利用pagerank算法自动提取文本摘要,以及利用深度学习的模型自动续写情节... ...期待看到更多相关的作品,enjoy。 完整代码和数据:https://github.com/scarlettgin/novel_analysis

你可能感兴趣的:(用WordCloud词云+LDA主题模型,带你读一读《芳华》(python实现))