本
文
摘
要
受刘思喆大佬一篇文章《爱情永远寂寞》的启发,对其中的文本挖掘技术非常感兴趣,遂产生了复制这个分析的想法。最近我一直在阅读各种各样的有趣分析,或者是Kaggle Notebooks,其中若有我比较感兴趣的主题、数据、算法,我都会尝试复现一下。
比如《爱情永远寂寞》这篇文章中提到的:
拿到22996 首中文歌曲(844 个流行歌手)之后,总觉得在这些非结构化数据背后肯定隐藏了什么,但到底会有什么模式呢?当经历了分词、构建矩阵、去除停止词、矩阵约减,最终通过模式识别,统计数字之后爱情居然是这样的一个真相
看上去还挺复杂的有没有?我之前只接触过Text Mining with R 这本书提供的方法,也就是构建N-gram。也不知道上边说的“构建矩阵”、“矩阵约减”、“模式识别”之类的方法怎么实现。但目前确定的是构建N-gram能够给出类似的文本词语关联关系。
这篇文章并没有给出分析用的原始代码及数据。那么复现类似的分析需要自己找一份歌词数据。在github上搜了一下,找到的是这个repo中所用的数据:
https://github.com/liuhuanyong/MusicLyricChatbot
数据是json格式的。首先我使用了Python读入json数据,使用jieba工具进行汉语分词。Python程序如下:
import jieba import json import time import gc import pandas as pd import matplotlib.pyplot as plt stopword_list = [k.strip() for k in open(../input/english-and-chinese-stopwords/stopwords.txt, encoding=utf8).readlines() if k.strip() != ] music = [] for line in open(../input/chinese-pop-song-lyrics/music.json,r): music.append(json.loads(line)) print(json文件总长度 {}.format(len(music))) # json文件总长度 140068 lyrics = [] for i in music: lyrics.append(i[geci]) def generate_ngrams(text, n_gram=2): 得到N元组 token = [k for k in jieba.cut(text) if k.isspace() == False if k != and k not in stopword_list] ngrams = zip(*[token[i:] for i in range(n_gram)]) single = [_.join(j) for j in ngrams if j != and j not in stopword_list] return single generate_ngrams(难道我怕找不到更好, 3) # [难道_我怕_找, 我怕_找_不到, 找_不到_更好]上面展示了N元组(n-gram)函数,N元组反映了一句话中词语的顺序关系。对于“难道我怕找不到更好”这一句话来说,可以分成这样的二元组[难道_我怕, 我怕_找, 找_不到, 不到_更好],三元组:[难道_我怕_找, 我怕_找_不到, 找_不到_更好]。通过对数据集中的每一句歌词进行这样的处理,构建大量的二元组,并且根据出现的频率筛选掉长尾二元组,我们可以构建出一个比较有代表性的文本网络。
gramList = [] start = time.time() for song in lyrics: songlist = [] for line in song: ngram = generate_ngrams(line) songlist.extend(ngram) gramList.extend(songlist) print(this program costs {:.2f} seconds.format(time.time() - start)) # this program costs 1415.58 seconds len(gramList) gramDt = pd.DataFrame(gramList) del gramList gc.collect() gramDt.columns = [bigram] gramDt[word1] = gramDt[bigram].apply(lambda x: x.split(_)[0]) gramDt[word2] = gramDt[bigram].apply(lambda x: x.split(_)[1]) gramDt # bigram word1 word2 # 0 玫瑰_多情 玫瑰 多情 # 1 多情_刺 多情 刺 # 2 刺_竟 刺 竟 # 3 竟_奶 竟 奶 # 4 奶_似曾相识 奶 似曾相识 # ... ... ... ... # 14821649 话题_什么 话题 什么 # 14821650 什么_看法 什么 看法 # 14821651 看法_欢迎 看法 欢迎 # 14821652 欢迎_留言 欢迎 留言 # 14821653 留言_交流 留言 交流 gramDt.to_csv(gramDt.csv, index=False)最后我们得到了如上所示的数据框并且保存成csv格式。本来我计划将所有分析都放在Python中完成,但当我进行绘图的时候,我发现Python的图形工具并不好用。在对中文的支持方面,Python真的不够出色。这是我无法解决的问题。因此我只好将数据存下来使用R语言进一步分析。
library(data.table) library(igraph) gramDt = read.csv("./gramDt.csv", encoding="UTF-8") gramDt = as.data.table(gramDt) gramDt = gramDt[,.(n = .N), by=c(word1, word2)] # 根据二元组计算词频 head(gramDt, 150) setorder(gramDt, -n) gramDt = gramDt[ !grepl("^([a-zA-Z0-9])", x=word1), ] gramDt = gramDt[ !grepl("^([a-zA-Z0-9])", x=word2), ] # 筛选日文 gramDt = gramDt[ !grepl([\u3040-\u309Fー]+, x=word1), ] gramDt = gramDt[ !grepl([\u3040-\u309Fー]+, x=word2), ] gramDt = gramDt[ !grepl( [\u30A0-\u30FF]+, x=word1), ] gramDt = gramDt[ !grepl( [\u30A0-\u30FF]+, x=word2), ] # 筛选韩文 gramDt = gramDt[ !grepl( [\u3130-\u318F\uAC00-\uD7AF]+, x=word1), ] gramDt = gramDt[ !grepl( [\u3130-\u318F\uAC00-\uD7AF]+, x=word2), ] gramDt = gramDt[!(word1==word2),] symbol = c(?, 卐,(,),!, 々, ∶,:,★,☆,@,#,;,←,√,к,и,壳,日, 発, 売) gramDt = gramDt[ !word1 %in% symbol, ] gramDt = gramDt[ !word2 %in% symbol, ]以上是使用R语言进行进一步数据清理的代码。可以看到我使用了大量正则表达式来筛选掉日文韩文。这是因为数据集中混杂了大量其他语言的歌曲,需要将非汉语的内容全部去掉。这里实际上是前期探索性数据分析的缺失导致的,由于一开始不知道数据的全貌,导致分析后期才发现多语种的混杂问题。
接下来我们需要筛选掉过于长尾的词组,保留高频词组。igraph::graph_from_data_frame()函数能够从数据框中生成图结构。plot()泛型函数能够提供图的各种定制化修改。这里我们使用rescale方法按照词频显示汉字的大小。
gramDt = gramDt[n>700,] setcolorder(gramDt, c("word1", "word2", "n")) lyricgram = graph_from_data_frame(gramDt, directed=F) rescale = function(x,a,b,c,d){c + (x-a)/(b-a)*(d-c)} plot(lyricgram, vertex.size=2, vertex.label.dist=0.9, vertex.label.color=purple, vertex.color=blue, vertex.shape=circle, edge.color=grey, vertex.label.cex=rescale(degree(lyricgram), 1, 24, 0.5, 1), layout = layout.fruchterman.reingold )输出的图形如下:
总体而言,“爱”在中文流行歌曲中占据非常重要的地位。相当多的词语都围绕着“爱”这个主题。其次,一些介词(比如“里”、“中”、“不要”、“会”)连接了其他主题。这可能是一种伪关联。或许通过其他方法,能够揭示出词语间更为本质的联系。可能因为数据的不同,这个分析结果并未复现出刘思喆文中“爱情永远寂寞”的主题。总体而言,这是一次对文本网络进行分析的尝试。实际上影响结果的还有很多“超参数”,比如N元组的选取(本次分析仅选择了2元组)、分词方式的选取、对词的筛选等等,这些都需要仔细地调节。
ypseNow,分享好玩有趣的数据分析