電影《芳華》在春節重映了一波,加上以前的熱映,最終取得了14億票房的好成績。嚴歌苓的原著也所以被更多的人細細品讀。用文本分析的一些技術肢解小說向來是天然語言處理領域的一大噱頭,此次固然也不能放過,本篇達成的成就有: 一、提取兩大主角劉峯和何小嫚(萍)的關鍵詞並繪製好看的人物詞雲; 二、以章節爲單位探索小說的主題分佈並畫圖展現。python
jieba
lda
wordcloud
seaborn
安裝命令: pip install ***
複製代碼
一、小說全文, 芳華-嚴歌苓.txt
二、中文停用詞,stopwords.txt
三、小說人物名稱,person.txt,做爲jieba的用戶自定義詞典
四、兩我的物的png圖片
五、你喜歡的中文字體的ttf文件,我用的楷體
複製代碼
文本挖掘的必備步驟,畢竟理解中文的最小單位是詞彙。這裏沒有使用簡單的jieba.cut進行分詞,由於咱們須要知道單詞的詞性,便於稍後根據詞性過濾不重要的詞。git
採用jieba.posseg.cut分詞能夠輸出詞性。咱們並不能拍腦門決定是要動詞仍是名詞等等,詞性有很是多個,我把所有分詞結果按照詞性分好類,看了一下每一個詞性對應哪些詞,最後決定保留詞性爲["a", "v", "x", "n", "an", "vn", "nz", "nt", "nr"]的詞,例如圖中,m表明量詞,這是對語義沒有幫助的詞,應該捨棄。 github
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
複製代碼
咱們按照「第*章」這樣的字眼將小說的不一樣章節分割開來,做爲獨立的文檔,用於以後的主題分析。定義了一個名爲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包能夠用於詞雲繪製,在使用過程當中須要注意:bash
一、用於定義形狀的外部圖片必須是png格式,默認純白色部分爲非圖像區域; 二、中文詞雲必須載入一個字體文件; 三、字的顏色能夠本身定義,也可使用圖片自己的底色。本例中何小嫚的圖片 底色很鮮豔明晰,能夠用自己的底色(ImageColorGenerator);而劉峯的圖片是單色,且色淺,我使用了自定義顏色(my_color_func); 四、繪製詞雲鬚要用到的數據格式爲dict,key爲詞,value爲詞頻,詞頻越大,在圖片中的字體越大。app
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格式。 爲了使形狀更鮮明,對小嫚的辮子還有腰的部分作了加白處理,能夠對比文章開頭原圖感覺一下。 dom
lda 方法的原理不作介紹了,假設你設置了這15章講了k個主題,那麼它的輸出是:一、每一個主題都由哪些詞構成,機率幾何; 二、每章內容中,k個主題各佔多大比例,佔比越大,該章內容與該主題越貼切。學習
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
複製代碼
咱們設置主題個數爲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])
複製代碼
對於各章節的不一樣主題的分佈,咱們能夠畫個圖來展現一下。 利用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