《python数据分析(第2版)-阿曼多.凡丹戈》读书笔记第9章-分析文本数据和社交媒体
第9章分析文本数据和社交媒体
在前几章中,我们着重讨论了结构化数据的分析,主要涉及列表格式的数据。实际上,跟结构化数据一样,纯文本也是最常见的格式之一。文本分析需要用到词频分布分析、模式识别、标注、链接和关联分析(link and association analysis)、情感分析和可视化等。这里将借助Python自然语言工具包(The Python Natural Language Toolkit,NLTK)来分析文本。NLTK自身带有一个文本样本集,这个样本集名为corpora。同时,scikitlearn程序库也提供了许多文本分析工具,本章也会对这些工具进行简要的介绍。
此外,在本章中还会举例说明网络分析。本章涉及的主题如下。
- 安装NLTK
- NLTK简介
- 滤除停用字、姓名和数字
- 词袋模型
- 词频分析
- 朴素贝叶斯分类
- 情感分析
- 创建词云
- 社交网络分析
9.1NLTK
安装NLTK:
1 pip3 install nltk scikit-learn
9.2NLTK简介
NLTK是一个用来分析自然语言文本(如英文句子)的Python应用程序接口。NLTK起源于2001年,最初设计是用来进行教学的。
虽然我们在9.1节安装了NLTK,但还不够:还需要下载NLTK语料库。需要注意的是,这里的下载量相对较大(约1.8 GB),但是只需要下载一次。除非我们确切地知道自己需要哪种语料库,否则最好下载所有可用的语料库。从Python shell下载语料库的命令如下。
1 $ python3 2 >>> import nltk 3 >>> nltk.download()
这时,会出现一个GUI应用程序,你可以指定要下载的文件以及放到何处,如图9-1所示。
如果是NLTK新手,最方便的方法就是选择默认选项并下载所有内容。在本章中,我们将需要用到停用词、电影评论、名字和古腾堡语料库。所以,我们鼓励读者根据ch-09.ipynb文件中的代码来学习下列部分。
Tips:如果安装NLTK方面有疑惑,这篇文章或许对你有帮助。《数据分析实战-托马兹.卓巴斯》读书笔记第9章--自然语言处理NLTK(分析文本、词性标注、主题抽取、文本数据分类)
9.3滤除停用字、姓名和数字
进行文本分析时,我们经常需要对停用字(Stopwords)进行剔除。这里所谓的停用字,就是那些经常见、但是没有多大信息含量的词。NLTK为很多语种都提供了停用字语料库。下面加载英语停用字语料并输出部分单词。
1 sw = set(nltk.corpus.stopwords.words('english')) 2 print("Stop words:", list(sw)[:7])
下面是输出的部分常用字。
Stop words: ['yours', 'off', 'own', 'so', "won't", 'all', 'doing']
请注意,这个语料库中的所有单词都是小写的。
此外,NLTK还提供了一个Gutenberg语料库。Gutenberg项目是一个数字图书馆计划,供人们在互联网上阅读图书。
下面加载Gutenberg语料库并输出部分文件的名称。
1 gb = nltk.corpus.gutenberg 2 print("Gutenberg files:\n", gb.fileids()[-5:])
下面是输出的某些书籍的名称,其中有些你可能比较熟悉。
Gutenberg files: ['milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']
现在,从milton-paradise.txt文件中提取前两句内容,供下面过滤使用。下面给出具体代码。
1 text_sent = gb.sents("milton-paradise.txt")[:2] 2 print("Unfiltered:", text_sent)
下面是输出的句子。
Unfiltered: [['[', 'Paradise', 'Lost', 'by', 'John', 'Milton', '1667', ']'], ['Book', 'I']]
现在,过滤掉下面的停用字。
1 for sent in text_sent: 2 filtered = [w for w in sent if w.lower() not in sw] 3 print("Filtered:\n", filtered)
对于第一句,过滤后变成以下格式。
Filtered: ['[', 'Paradise', 'Lost', 'John', 'Milton', '1667', ']']
注意,与之前相比,单词by已经被过滤掉了,因为它出现在停用字语料库中了。有时,我们希望将文本中的数字和姓名也删掉。我们可以根据词性(Part of Speech,POS)标签来删除某些单词。在这个标注方案中,数字对应着基数(Cardinal Number,CD)标签。
姓名对应着单数形式的专有名词(the proper noun singular,NNP)标签。标注是基于启发式方法进行处理的,当然,这不可能非常精确。这个主题过于宏大,恐怕需要整本书来进行描述,详见前言部分。在过滤文本时,我们可以使用pos_tag()函数获取文本内所含的标签,具体如下。
1 tagged = nltk.pos_tag(filtered) 2 print("Tagged:\n", tagged)
对于我们的文本,将得到如下各种标签。
Tagged: [('[', 'JJ'), ('Paradise', 'NNP'), ('Lost', 'NNP'), ('John', 'NNP'), ('Milton', 'NNP'), ('1667', 'CD'), (']', 'NN')]
上面的pos_tag()函数将返回一个元组列表,其中各元组的第二个元素就是文本对应的标签。可见,一些单词虽然被标注为NNP,但是它们并不是。这里所谓的启发式方法,就是如果单词的第一个字母是大写的,那么就将其标注为NNP。如果将上面的文本全部转换为小写,那么就会得到不同的结果,这个留给读者自行练习。实际上,我们很容易就能利用NNP和CD标签来删除列表中的单词,代码如下。
1 words= [] 2 for word in tagged: 3 if word[1] != 'NNP' and word[1] != 'CD': 4 words.append(word[0]) 5 6 print("Words:\n",words)
下列代码摘自本书代码包中的ch-09.ipynb文件。
1 import nltk 2 from nltk import data 3 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径 4 for sent in text_sent: 5 filtered = [w for w in sent if w.lower() not in sw] 6 print("Filtered:\n", filtered) 7 tagged = nltk.pos_tag(filtered) 8 print("Tagged:\n", tagged) 9 10 words= [] 11 for word in tagged: 12 if word[1] != 'NNP' and word[1] != 'CD': 13 words.append(word[0]) 14 15 print("Words:\n",words)
9.4词袋模型
所谓词袋模型,即它认为一篇文档是由其中的词构成的一个集合(即袋子),词与词之间没有顺序以及先后的关系。对于文档中的每个单词,我们都要计算它出现的次数,即单词计数(word counts)。据此,我们可以进行类似垃圾邮件识别之类的统计分析。
如果有一批文档,那么每个唯一字(unique word)都可以视为语料库中的一个特征。这里所谓的“特征”,可以理解为参数或者变量。利用所有的单词计数,我们可以为每个文档建立一个特征向量这里的“向量”一词借用的是其数学含义。如果一个单词存在于语料库中,但是不存在于文档中,那么这个特征的值就为0。令人惊讶的是,NLTK至今尚未提供可以方便创建特征向量的实用程序。不过,我们可以借用Python机器学习库scikit-learn中的CountVectorizer类来轻松创建特征向量。第10章将对scikit-learn进行更详尽的介绍。
下面从NLTK的Gutenberg语料库加载两个文本文档,代码如下。
1 hamlet = gb.raw("shakespeare-hamlet.txt") 2 macbeth = gb.raw("shakespeare-macbeth.txt")
现在我们去掉英语停用词并创建特征向量,代码如下。
1 cv = sk.feature_extraction.text.CountVectorizer(stop_words='english') 2 print("Feature vector:\n", cv.fit_transform([hamlet, macbeth]).toarray())
下面是两个文档对应的特征向量。
Feature vector: [[ 1 0 1 ... 14 0 1] [ 0 1 0 ... 1 1 0]]
下面,我们输出部分特征(唯一字)值。
1 print("Features:\n", cv.get_feature_names()[:5])
这些特征是按字母顺序排序的。
Features: ['1599', '1603', 'abhominably', 'abhorred', 'abide']
下列代码摘自本书代码包中的ch-09.ipynb文件。
1 import nltk 2 import sklearn as sk 3 from nltk import data 4 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径 5 6 hamlet = gb.raw(“shakespeare-hamlet.txt”) 7 macbeth = gb.raw(“shakespeare-macbeth.txt”) 8 9 cv = sk.feature_extraction.text.CountVectorizer(stop_words=’english’) 10 11 print(“Feature vector:\n”, cv.fit_transform([hamlet, macbeth]).toarray()) 12 13 print(“Features:\n”, cv.get_feature_names()[:5])
9.5词频分析
NLTK提供的FreqDist类可以用来将单词封装成字典并计算给定单词列表中各个单词出现的次数。下面,我们来加载Gutenberg项目中莎士比亚的Julius Caesar中的文本。
首先,把停用词和标点符号剔除掉,代码如下。
1 punctuation = set(string.punctuation) 2 filtered = [w.lower() for w in words if w.lower() not in sw and w.lower() not in punctuation]
然后,创建一个FreqDist对象并输出频率最高的键和值,代码如下。
1 fd = nltk.FreqDist(filtered) 2 print("Words", list(fd.keys())[:5]) 3 print("Counts", list(fd.values())[:5])
输出的键和值如下。
Words ['tragedie', 'julius', 'caesar', 'william', 'shakespeare'] Counts [2, 1, 190, 1, 1]
很明显,这个列表中的第一个字并非英语单词,因此需要添加一种启发式搜索(Heuristic)方法,只选择最少含有两个字符的单词。对于NLTK提供的FreqDist类,我们既可以使用类似于字典的访问方法,也可以使用更便利的其他方法。下面,我们要提取最常出现的单词以及对应的出现次数。
1 print("Max", fd.max()) 2 print("Count", fd['d'])
对应下面的结果,你肯定不会感到吃惊。
Max caesar
Count 0
目前,我们只是针对单个单词构成的词汇进行了分析,不过可以推而广之,将分析扩展到含有两个单词和3个单词的词汇上,对于后面这两种,我们分别称之为双字词和三字词。对于这两种分析,分别有对应的函数,即bigrams()函数和trigrams()函数。现在,我们再次进行文本分析,不过,这次针对的是双字词。
1 fd = nltk.FreqDist(nltk.bigrams(filtered)) 2 print("Bigrams", list(fd.keys())[:5]) 3 print("Counts", list(fd.values())[:5]) 4 print("Bigram Max", fd.max()) 5 print("Bigram count", fd[('let', 'vs')])
结果如下。
Bigrams [('tragedie', 'julius'), ('julius', 'caesar'), ('caesar', 'william'), ('william', 'shakespeare'), ('shakespeare', '1599')] Counts [1, 1, 1, 1, 1] Bigram Max ('let', 'vs') Bigram count 16
下列代码摘自本书代码包中的ch-09.ipynb文件。
1 import nltk 2 import string 3 from nltk import data 4 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径 5 6 7 gb = nltk.corpus.gutenberg 8 words = gb.words("shakespeare-caesar.txt") 9 10 sw = set(nltk.corpus.stopwords.words('english')) 11 punctuation = set(string.punctuation) 12 filtered = [w.lower() for w in words if w.lower() not in sw and w.lower() not in punctuation] 13 fd = nltk.FreqDist(filtered) 14 print("Words", list(fd.keys())[:5]) 15 print("Counts", list(fd.values())[:5]) 16 print("Max", fd.max()) 17 print("Count", fd['d']) 18 19 fd = nltk.FreqDist(nltk.bigrams(filtered)) 20 print("Bigrams", list(fd.keys())[:5]) 21 print("Counts", list(fd.values())[:5]) 22 print("Bigram Max", fd.max()) 23 print("Bigram count", fd[('let', 'vs')])
9.6朴素贝叶斯分类
分类算法是机器学习算法中的一种,用来判断给定数据项所属的类别,即种类或类型。比如,我们可以根据某些特征来分辨一部电影属于哪个流派等。这样,流派就是我们要预测的类别。第10章还会对机器学习做进一步介绍。此刻,我们要讨论的是一个名为朴素贝叶斯分类的流行算法,它常常用于进行文本文档的分析。
朴素贝叶斯分类是一个概率算法,它基于概率与数理统计中的贝叶斯定理。贝叶斯定理给出了利用新证据修正某事件发生的概率的方法。例如,假设一个袋子里装有一些巧克力和其他物品,但是这些我们没法看到。这时,我们可以用P(D)表示从袋子中掏出一块深色巧克力的概率。同时,我们用P©代表掏出一块巧克力的概率。当然,因为全概率是1,所以P(D)和P©的最大取值也只能是1。贝叶斯定理指出,后验概率与先验概率和相似度的乘积成正比,具体公式如下。
P(D|C)
在上面的公式中,P(D|C)是在事件C发生的情况下事件D发生的可能性。在我们还没有掏出任何物品之前,P(D) = 0.5,因为我们尚未获得任何信息。实际应用这个公式时,我们必须知道P(C|D)和P(C),或者能够间接求出这两个概率。
朴素贝叶斯分类之所以称为朴素,是因为它简单假设特征之间是相互独立的。实践中,朴素贝叶斯分类的效果通常都会很好,说明这个假设得到了一定程度的保证。近来,人们发现这个假设之所以有意义,理论上是有依据的。不过,由于机器学习领域发展迅猛,现在已经发明了多种效果更佳的算法。
下面,我们将利用停用词或标点符号对单词进行分类。这里,我们将字长作为一个特征,因为停用词和标点符号往往都比较短。
为此,我们需要定义如下所示的函数。
1 def word_features(word): 2 return {'len': len(word)} 3 4 def isStopword(word): 5 return word in sw or word in punctuation
下面,我们对取自古登堡项目的shakespeare-caesar.txt中的单词进行标注,以区分是否为停用词,代码如下。
1 labeled_words = ([(word.lower(), isStopword(word.lower())) for 2 word in words]) 3 random.seed(42) 4 random.shuffle(labeled_words) 5 print(labeled_words[:5])
下面显示了5个标注后的单词。
[('i', True), ('is', True), ('in', True), ('he', True), ('ambitious', False)]
对于每个单词,我们可以求出其长度。
1 featuresets = [(word_features(n), word) for (n, word) in 2 labeled_words]
前几章我们介绍过拟合以及通过训练数据集和测试数据集的交叉验证来避免这种情况的方法。下面我们将要训练一个朴素贝叶斯分类器,其中90%的单词用于训练,剩下的10%用于测试。首先,创建训练数据集和测试数据集并针对数据展开训练,代码如下。
1 cutoff = int(.9 * len(featuresets)) 2 train_set, test_set = featuresets[:cutoff], featuresets[cutoff:] 3 classifier = nltk.NaiveBayesClassifier.train(train_set)
现在,我们拿出一些单词,检查该分类器的效果。
1 classifier = nltk.NaiveBayesClassifier.train(train_set) 2 print("'behold' class", classifier.classify(word_features('behold'))) 3 print("'the' class", classifier.classify(word_features('the')))
幸运的是,这些单词的分类完全正确:
'behold' class False 'the' class True
然后,根据测试数据集来计算分类器的准确性,代码如下。
1 print("Accuracy", nltk.classify.accuracy(classifier, test_set))
这个分类器的准确度非常高,几乎达到85%。下面来看哪些特征的贡献最大。
1 print(classifier.show_most_informative_features(5))
结果显示如图9-2所示,在分类过程中字长的作用最大。
下列代码摘自本书代码包中的ch-09.ipynb文件。
1 mport nltk 2 from nltk import data 3 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径 4 import string 5 import random 6 7 sw = set(nltk.corpus.stopwords.words('english')) 8 punctuation = set(string.punctuation) 9 10 def word_features(word): 11 return {'len': len(word)} 12 13 def isStopword(word): 14 return word in sw or word in punctuation 15 gb = nltk.corpus.gutenberg 16 words = gb.words("shakespeare-caesar.txt") 17 18 labeled_words = ([(word.lower(), isStopword(word.lower())) for 19 word in words]) 20 random.seed(42) 21 random.shuffle(labeled_words) 22 print(labeled_words[:5]) 23 24 featuresets = [(word_features(n), word) for (n, word) in 25 labeled_words] 26 cutoff = int(.9 * len(featuresets)) 27 train_set, test_set = featuresets[:cutoff], featuresets[cutoff:] 28 classifier = nltk.NaiveBayesClassifier.train(train_set) 29 print("'behold' class", classifier.classify(word_features('behold'))) 30 print("'the' class", classifier.classify(word_features('the'))) 31 32 print("Accuracy", nltk.classify.accuracy(classifier, test_set)) 33 print(classifier.show_most_informative_features(5))
9.7情感分析
随着社交媒体、产品评论网站及论坛的兴起,用来自动抽取意见的观点挖掘或情感分析也随之变成一个炙手可热的新研究领域。通常情况下,我们希望知道某个意见的性质是正面的、中立的还是负面的。当然,这种类型的分类我们前面就曾遇到过。也就是说,我们有大量的分类算法可用。还有一个方法就是,通过半自动的(经过某些人工编辑)方法来编制一个单词列表,每个单词赋予一个情感分,即一个数值(如单词“good”的情感分为5,而单词“bad”的情感分为−5)。如果有了这样一张表,就可以给文本文档中的所有单词打分,从而得出一个情感总分。当然,类别的数量可以大于3,如五星级评级方案。
我们会应用朴素贝叶斯分类方法对NLTK的影评语料库进行分析,从而将影评分为正面的或负面的评价。首先,加载影评语料库并过滤掉停用词和标点符号。这些步骤在此略过,因为之前就介绍过了。也可以考虑更精细的过滤方案,不过,需要注意的是,如果过滤得过火了,就会影响准确性。下面通过categories()方法对影评文档进行标注,代码如下。
1 labeled_docs = [(list(movie_reviews.words(fid)), cat) 2 for cat in movie_reviews.categories() 3 for fid in movie_reviews.fileids(cat)]
完整的语料库拥有数以万计的唯一字,这些唯一字都可以用作特征,不过,这样做会极大地影响效率。这里,我们选用词频最高的前5%的单词作为特征。
1 words = FreqDist(filtered) 2 N = int(.05 * len(words.keys())) 3 word_features = list(words.keys())[:N]
对于每个文档,可以通过一些方法来提取特征,其中包括以下方法:
- 检查给定文档是否含有某个单词;
- 求出某个单词在给定文档中出现的次数;
- 正则化单词计数,使得正则化后的最大单词计数小于或等于1;
- 将上面的数值加1,然后取对数。这里之所以加1,是为了防止对0取对数;
- 利用上面的数值组成一个度量指标。
俗话说“条条大路通罗马”,不过,这其中肯定不乏有些道路能够让我们更安全,也能更快地到达罗马。下面定义一个函数,该函数会使用原始单词计数来作为度量指标,代码为如下。
1 def doc_features(doc): 2 doc_words = FreqDist(w for w in doc if not isStopWord(w)) 3 features = {} 4 for word in word_features: 5 features['count (%s)' % word] = (doc_words.get(word, 0)) 6 return features
现在,可以训练我们的分类器了,具体方法与前面的例子相似。这里准确性达到78%,这个成绩已经相当不错了,非常接近情感分析成绩的上限。因为据研究发现,即便是人类,也无法对给定文档的表达出的感情的看法达成一致。因此,要想让情感分析软件获得满分是不可能的事情。
下面的图9-3给出包含信息量最大的特征。
如果仔细检查这个列表,我们很容易发现有一些褒义词,如“wonderful(妙不可言的)”和“outstanding(杰出的)”。而单词“bad(恶劣的)”“stupid(愚蠢的)”和“boring(无趣的)”则明显是一些贬义词。如果有兴趣,读者也可以分析其他的那些特征,这将作为一个作业留给读者自己完成。以下代码摘自本书代码包中的sentiment.py文件。
1 import random 2 from nltk import data 3 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径 4 from nltk.corpus import movie_reviews 5 from nltk.corpus import stopwords 6 from nltk import FreqDist 7 from nltk import NaiveBayesClassifier 8 from nltk.classify import accuracy 9 import string 10 11 labeled_docs = [(list(movie_reviews.words(fid)), cat) 12 for cat in movie_reviews.categories() 13 for fid in movie_reviews.fileids(cat)] 14 random.seed(42) 15 random.shuffle(labeled_docs) 16 17 review_words = movie_reviews.words() 18 print("# Review Words", len(review_words)) 19 20 sw = set(stopwords.words('english')) 21 punctuation = set(string.punctuation) 22 23 def isStopWord(word): 24 return word in sw or word in punctuation 25 26 filtered = [w.lower() for w in review_words if not isStopWord(w.lower())] 27 print("# After filter", len(filtered)) 28 words = FreqDist(filtered) 29 N = int(.05 * len(words.keys())) 30 word_features = list(words.keys())[:N] 31 32 def doc_features(doc): 33 doc_words = FreqDist(w for w in doc if not isStopWord(w)) 34 features = {} 35 for word in word_features: 36 features['count (%s)' % word] = (doc_words.get(word, 0)) 37 return features 38 39 featuresets = [(doc_features(d), c) for (d,c) in labeled_docs] 40 train_set, test_set = featuresets[200:], featuresets[:200] 41 classifier = NaiveBayesClassifier.train(train_set) 42 print("Accuracy", accuracy(classifier, test_set)) 43 44 print(classifier.show_most_informative_features())
9.8创建词云
阅读本书前,我们也许已在Wordle或其他地方见过词云了;如果没见过也不要紧,因为可以在本章看个够了。虽然Python有两个程序库都可以用来生成词云,但是它们生成词云的效果尚不能与Wordle网站的效果相媲美。所以,我们不妨利用Wordle网站的页面来创建词云。利用Wordle生成词云时,需要提供一个单词列表及其对应的权值,具体格式如下。
Word1 : weight
Word2 : weight
现在,对前面例子中的代码稍作修改,让它输出这个字表。这里,我们将使用词频作为度量指标,选取词频排名靠前的单词。实际上,这里不需要任何新的代码,具体内容可以参考本书代码包中的ch-09.ipynb文件。
1 from nltk import data 2 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径 3 from nltk.corpus import movie_reviews 4 from nltk.corpus import stopwords 5 from nltk import FreqDist 6 import string 7 8 sw = set(stopwords.words('english')) 9 punctuation = set(string.punctuation) 10 11 def isStopWord(word): 12 return word in sw or word in punctuation 13 review_words = movie_reviews.words() 14 filtered = [w.lower() for w in review_words if not isStopWord(w.lower())] 15 16 words = FreqDist(filtered) 17 N = int(.01 * len(words.keys())) 18 tags = list(words.keys())[:N] 19 20 for tag in tags: 21 print(tag, ':', words[tag])
将上面代码的输出结果复制粘贴到上面提到的Wordle页面,就能得到如图9-4所示的词云了。
plot : 1513 two : 1911 teen : 151 couples : 27 go : 1113 church : 69 party : 183 drink : 32 drive : 105 get : 1949 accident : 104 one : 5852 guys : 268 dies : 104 girlfriend : 218 continues : 88 see : 1749 life : 1586 nightmares : 26 deal : 219 watch : 603 movie : 5771 sorta : 10 find : 782 critique : 61 mind : 451 fuck : 17 generation : 96 touches : 55 cool : 208 idea : 386 presents : 78 bad : 1395 package : 30 makes : 992 review : 295 even : 2565 harder : 33 write : 119 since : 768 generally : 103 applaud : 10 films : 1536 attempt : 263 break : 175 mold : 14 mess : 159 head : 387 lost : 409 highway : 28 memento : 10 good : 2411 ways : 189 making : 602 types : 48 folks : 74 snag : 2 correctly : 17 seem : 574 taken : 225 pretty : 528 neat : 32 concept : 114 executed : 46 terribly : 58 problems : 293 well : 1906 main : 399 problem : 396 simply : 428 jumbled : 12 starts : 316 normal : 111 downshifts : 2 fantasy : 97 world : 1037 audience : 914 member : 126 going : 888 dreams : 131 characters : 1859 coming : 275 back : 1060 dead : 418 others : 288 look : 835 like : 3690 strange : 185 apparitions : 5 disappearances : 3 looooot : 1 chase : 159 scenes : 1274 tons : 21 weird : 100 things : 852 happen : 220 explained : 70 personally : 44 trying : 566 unravel : 9 film : 9517 every : 947 give : 561 clue : 45 kind : 559 fed : 29 biggest : 149 obviously : 228 got : 470 big : 1064 secret : 184 hide : 40 seems : 1033 want : 560 completely : 440 final : 380 five : 284 minutes : 644 make : 1642 entertaining : 314 thrilling : 46 engaging : 79 meantime : 19 really : 1558 sad : 108 part : 714 arrow : 29 dig : 19 flicks : 81 actually : 837 figured : 29 half : 535 way : 1693 point : 685 strangeness : 6 start : 312 little : 1501 bit : 568 sense : 555 still : 1047 guess : 226 bottom : 93 line : 435 movies : 1206 always : 586 sure : 523 given : 502 password : 4 enter : 68 understanding : 57 mean : 242 showing : 147 melissa : 12 sagemiller : 8 running : 323 away : 655 visions : 31 20 : 98 throughout : 302 plain : 82 lazy : 34 okay : 125 people : 1455 chasing : 43 know : 1217 need : 316 giving : 214 us : 1073 different : 430 offering : 38 insight : 54 apparently : 209 studio : 163 took : 164 director : 1237 chopped : 5 shows : 410 might : 635 decent : 164 somewhere : 127 suits : 45 decided : 104 turning : 80 music : 480 video : 322 edge : 104 would : 2109 actors : 706 although : 795 wes : 50 bentley : 9 seemed : 212 playing : 362 exact : 64 character : 2020 american : 559 beauty : 135 new : 1292 neighborhood : 21 kudos : 21 holds : 90 entire : 408 feeling : 225 unraveling : 2 overall : 160 stick : 69 entertain : 53 confusing : 94 rarely : 102 excites : 2 feels : 216 redundant : 14 runtime : 8 despite : 352 ending : 423 explanation : 69 craziness : 4 came : 185 oh : 216 horror : 473 slasher : 84 flick : 196 packaged : 3 someone : 401 assuming : 15 genre : 268 hot : 127 kids : 328 also : 1967 wrapped : 39 production : 300 years : 846 ago : 199 sitting : 93 shelves : 13 ever : 776 whatever : 136 skip : 45 joblo : 30 nightmare : 60 elm : 14 street : 140 3 : 222 7 : 115 10 : 449 blair : 98 witch : 108 2 : 439 crow : 55 9 : 75 salvation : 14 4 : 190 stir : 27 echoes : 31 8 : 140 happy : 215 bastard : 46 quick : 139 damn : 88 y2k : 4 bug : 81 starring : 184 jamie : 42 lee : 266 curtis : 37 another : 1121 baldwin : 89 brother : 268 william : 206 time : 2411 story : 2169 regarding : 31 crew : 214 tugboat : 2 comes : 733 across : 221 deserted : 21 russian : 68 tech : 29 ship : 264 kick : 52 power : 238 within : 227 gore : 110 bringing : 81 action : 1172 sequences : 293 virus : 77 empty : 67 flash : 62 substance : 73 middle : 222 nowhere : 98 origin : 8 pink : 24 flashy : 41 thing : 809 hit : 285 mir : 7 course : 648 donald : 38 sutherland : 39 stumbling : 15 around : 903 drunkenly : 2 hey : 70 let : 425 robots : 27 acting : 695 average : 119 likes : 99 likely : 164 work : 1020 halloween : 60 h20 : 7 wasted : 118 real : 915 star : 761 stan : 12 winston : 4 robot : 45 design : 87 schnazzy : 1 cgi : 50 occasional : 62 shot : 348 picking : 28 brain : 93 body : 269 parts : 207 turn : 363 otherwise : 156 much : 2049 sunken : 4 jaded : 12 viewer : 218 thankful : 7 invention : 11 timex : 1 indiglo : 1 based : 389 late : 238 1960 : 22 television : 220 show : 741 name : 392 mod : 17 squad : 40 tells : 255 tale : 216 three : 695 reformed : 7 criminals : 35 employ : 16 police : 241 undercover : 28 however : 989 wrong : 385 evidence : 66 gets : 865 stolen : 58 immediately : 163 suspicion : 19 ads : 33 cuts : 60 claire : 70 dane : 5 nice : 344 hair : 109 cute : 134 outfits : 21 car : 321 chases : 50 stuff : 208 blowing : 26 sounds : 133 first : 1836 fifteen : 64 quickly : 255 becomes : 526 apparent : 100 certainly : 361 slick : 39 looking : 501 complete : 197 costumes : 71 enough : 910 best : 1333 described : 57 cross : 111 hour : 355 long : 836 cop : 208 stretched : 19
仔细研究这个词云,会发现它远非完美,还有很大的改进空间。因此,我们不妨做进一步的尝试。
进一步过滤:我们应当剔除包含数字符号和姓名的那些单词。这时,可以借助NLTK的names语料库。此外,对于在所有语料库中仅出现一次的那些单词,我们也可以置之不理,因为它们不大可能提供足够有价值的信息。
使用更好的度量指标:词频和逆文档频率(The Term Frequency-Inverse Document Frequency,TF-IDF)看起来是一个不错的选择。
度量指标TF-IDF可以通过对语料库中的单词进行排名并据此赋予这些单词相应的权重。这个权重的值与单词在特定文档中出现的次数即词频成正比。同时,它还与语料库中含有该单词的文档数量成反比,即逆文档频率。TF-IDF的值为词频和逆文档频率之积。如果需要自己动手实现TF-IDF,那么还必须考虑对数标定处理。幸运的是,实际上我们根本无需考虑这些实现方面的细节,因为scikit-learn已经为我们准备好了一个TfidfVectorizer类,它有效地实现了TF-IDF。该类能够生成一个稀疏的SciPy矩阵,用术语来说就是一个词条-文档矩阵,这个矩阵存放的是单词和文档的每种可能组合的TF-IDF值。因此,对于一个含有2 000个文档和25 000个不重复的词的语料库,可以得到一个2 000×25 000的矩阵。由于大部分矩阵值都为0,我们将它作为一个稀疏矩阵来处理比较省劲。对每个单词来说,其最终的排名权重可以通过对其TF-IDF值求和来得到。
下面我们通过isalpha()方法和姓名语料库来改善过滤效果,代码如下。
1 all_names = set([name.lower() for name in names.words()]) 2 3 def isStopWord(word): 4 return (word in sw or word in punctuation) or not word.isalpha() or word in all_names
下面创建一个NLTK的FreqDist类,从而过滤掉那些只出现一次的单词。对于TfidfVectorizer类,需要为其提供一个字符串列表来指出语料库中的各个文档。
下面创建这个列表,代码如下。
1 for fid in movie_reviews.fileids(): 2 texts.append(" ".join([w.lower() for w in movie_reviews.words(fid) if not isStopWord(w.lower()) and words[w.lower()]
创建向量化程序,为了保险起见,令其忽略停用词。
1 vectorizer = TfidfVectorizer(stop_words='english')
创建稀疏的词条-文档矩阵。
1 matrix = vectorizer.fit_transform(texts)
为每个单词的TF-IDF值求和并将结果存放到NumPy数组中。
1 sums = np.array(matrix.sum(axis=0)).ravel()
下面通过单词的排名权值来创建一个Pandas DataFrame并进行相应的排序,代码如下。
1 ranks = [] 2 3 for word, val in zip(vectorizer.get_feature_names(), sums): 4 ranks.append((word, val)) 5 6 df = pd.DataFrame(ranks, columns=["term", "tfidf"]) 7 df = df.sort_values(['tfidf']) 8 print(df.head())
排名最低的值将被输出,同时供将来过滤用。
term tfidf
19963 superintendent 0.03035
8736 greys 0.03035
14010 ology 0.03035
2406 briefer 0.03035
2791 cannibalize 0.03035
matter : 10.160156320157853 review : 10.162109208095815 seeing : 10.193962242951153 jokes : 10.195055387739588 past : 10.229789978743266 romantic : 10.270767948140588 directed : 10.27679275085064 start : 10.302358509215921 finally : 10.315385095902199 video : 10.356897657857315 despite : 10.36356758711268 ship : 10.370281211670585 beautiful : 10.415601266078719 scream : 10.421970655899635 sequence : 10.461140540373234 supposed : 10.473608248283002 shot : 10.497822532176006 face : 10.520647846526677 turn : 10.535466043788666 lives : 10.536265259335323 later : 10.536596993112912 tell : 10.54178804022045 camera : 10.580870634146848 works : 10.585001927065935 children : 10.59229934724306 live : 10.658879764040353 daughter : 10.685408819519905 earth : 10.6855987888017 mr : 10.711280266859085 car : 10.715492238654587 believe : 10.724994487616465 maybe : 10.738295943695265 person : 10.766043701757003 book : 10.799070874951035 worst : 10.801808893863994 hand : 10.815936702218032 named : 10.818013961831461 game : 10.86383795127332 fight : 10.865544776692566 use : 10.88431817114859 used : 10.955534955009918 killer : 11.000641030461054 certainly : 11.001525429842374 begins : 11.070728771014815 perfect : 11.096242361915083 relationship : 11.102758849863765 said : 11.108251601369561 nice : 11.124424984182713 days : 11.138293311572346 kids : 11.181482360800292 called : 11.192445668887236 run : 11.198723318188565 playing : 11.216003589219902 final : 11.223074577571513 tries : 11.242871986273729 unfortunately : 11.295825206128804 group : 11.326924605147562 comic : 11.389992817954504 left : 11.438340356187926 entire : 11.444592282847251 idea : 11.461929302207174 based : 11.487214286939588 head : 11.516296149111216 wrong : 11.562368605644979 second : 11.585319233830582 summer : 11.586686728126798 shows : 11.63522507695588 main : 11.660671883754478 soon : 11.711290550319069 true : 11.754181091867876 turns : 11.821623440558202 getting : 11.874446133551853 human : 11.899923970603947 problem : 11.99620702280271 written : 12.006014424193982 hour : 12.018041625879004 different : 12.151447465665578 boy : 12.202291415418031 performances : 12.239403934569973 house : 12.251961733491308 simply : 12.29153134772739 war : 12.297910070957098 mind : 12.324864356668593 small : 12.327723933187466 especially : 12.352783302524191 rest : 12.359092724325766 tv : 12.368538157058103 lost : 12.399034233379663 completely : 12.435026821722477 looks : 12.4500832891327 humor : 12.480741394166033 line : 12.531463602753462 reason : 12.549615155979115 dead : 12.550701937661323 friend : 12.554066106444134 let : 12.557077785028916 thought : 12.650011584913317 stars : 12.688790737853829 couple : 12.731565830451245 alien : 12.811802155159485 moments : 12.890094530509302 evil : 12.90918008264871 wants : 12.91523366454186 friends : 12.971455692528544 night : 12.972118832796 mother : 13.069867009873525 given : 13.163657271730541 ending : 13.241085606272085 play : 13.241256041448459 feel : 13.260355813064143 gives : 13.541933025342574 got : 13.58120683575867 watching : 13.633462479391907 death : 13.638264772375894 looking : 13.717090584113414 girl : 13.728552228886974 instead : 13.774615981791037 probably : 13.808813918690175 city : 13.842578473593091 school : 13.897593833405203 father : 14.066422069009711 music : 14.075762648197905 help : 14.120498729117202 sure : 14.145480680268529 dialogue : 14.23176869122043 kind : 14.399967422476037 black : 14.447741822146453 actor : 14.522615872729146 sense : 14.629745462015816 want : 14.725766439030554 pretty : 14.809940495890388 making : 14.817117131361535 series : 14.821081643716946 set : 14.887819969400011 half : 14.89251185190553 money : 14.901355104392845 bit : 14.934268735728764 home : 14.976582330659667 place : 15.049919713647064 trying : 15.114785990368087 times : 15.118763478807493 sex : 15.166419440553032 american : 15.359810523477407 hard : 15.454208298004339 picture : 15.506551578323716 woman : 15.639843619779164 hollywood : 15.650704478670258 horror : 15.688047135511049 far : 15.758603032319007 watch : 15.781648059164944 fun : 15.799106034203923 special : 15.894929367222112 course : 15.931964229777236 away : 15.944636479597987 takes : 15.954305763102612 men : 16.036233030752417 wife : 16.103810528049053 interesting : 16.110566312034386 screen : 16.330798169836488 goes : 16.355214344649127 minutes : 16.578665989245824 point : 16.593231934007573 quite : 16.743903021148146 lot : 16.7556321332947 comes : 16.945112986149024 high : 16.96333443299414 day : 17.469931907378594 young : 17.61907491805685 come : 17.710951023055475 plays : 17.825218944178573 actors : 17.841139061718554 acting : 18.056556176317212 effects : 18.156452985282307 fact : 18.358966964131444 family : 18.436351324270554 cast : 18.462652990794695 right : 18.51721470872539 look : 18.709286795233268 original : 18.815142636940738 played : 18.96169328195863 years : 19.058321835992686 long : 19.063431217630267 actually : 19.081359841119173 thing : 19.347634515439985 script : 19.466362068961164 old : 19.68484647913068 things : 19.711840751606182 gets : 19.789969444538826 think : 19.79997034212867 role : 19.829843863051224 performance : 19.991860531957602 better : 20.069030711347395 audience : 20.229377054374694 going : 20.384999917030928 year : 20.404754669356507 seen : 20.741715274906582 real : 20.782565933126254 makes : 20.916315796004714 work : 21.48974389558135 funny : 22.184963867355552 world : 22.430195409736793 end : 22.501145434405117 comedy : 22.724158274326264 big : 23.394018499746736 director : 23.689769490697007 great : 24.489611034104875 scenes : 25.243560471583756 know : 25.25908281181752 new : 25.424409388545484 movies : 25.46489805929262 best : 25.793314078809164 scene : 26.65609949406872 man : 27.271016365028732 people : 27.772749971079577 action : 27.8317318022786 little : 27.912251658495467 make : 28.36244923373348 films : 29.081817882359086 bad : 29.1633844710237 plot : 29.81049966927315 really : 30.211353157494234 life : 30.844185813234848 characters : 33.204457851658944 character : 33.73419369933767 story : 36.69429364590541 time : 36.76869565644592 good : 38.945456240101606 like : 49.087665729852674 movie : 80.3332102380712 film : 109.34008874939663
接下来,我们输出排名靠前的单词,这样就可以利用Wordle网站生成如图9-5所示的词云了。
令人遗憾的是,我们必须亲自运行代码,才能看到上面词云中的不同色彩。相较于单调的词频而言,TF-IDF度量指标更富于变化,因此我们就能得到更加丰富的色彩。此外,这样还能使得云雾中的单词看起来联系更加紧密。下列代码摘自本书代码包中的ch-09.ipynb文件。
1 from nltk import data 2 data.path.append(r'D:\download\nltk_data') # 这里的路径需要换成自己数据文件下载的路径 3 from nltk.corpus import movie_reviews 4 from nltk.corpus import stopwords 5 from nltk.corpus import names 6 from nltk import FreqDist 7 from sklearn.feature_extraction.text import TfidfVectorizer 8 import itertools 9 import pandas as pd 10 import numpy as np 11 import string 12 13 sw = set(stopwords.words('english')) 14 punctuation = set(string.punctuation) 15 all_names = set([name.lower() for name in names.words()]) 16 17 def isStopWord(word): 18 return (word in sw or word in punctuation) or not word.isalpha() or word in all_names 19 20 review_words = movie_reviews.words() 21 filtered = [w.lower() for w in review_words if not isStopWord(w.lower())] 22 23 words = FreqDist(filtered) 24 25 texts = [] 26 27 for fid in movie_reviews.fileids(): 28 texts.append(" ".join([w.lower() for w in movie_reviews.words(fid) if not isStopWord(w.lower()) and words[w.lower()] > 1])) 29 30 vectorizer = TfidfVectorizer(stop_words='english') 31 matrix = vectorizer.fit_transform(texts) 32 sums = np.array(matrix.sum(axis=0)).ravel() 33 34 ranks = [] 35 36 for word, val in zip(vectorizer.get_feature_names(), sums): 37 ranks.append((word, val)) 38 39 df = pd.DataFrame(ranks, columns=["term", "tfidf"]) 40 df = df.sort_values(['tfidf']) 41 print(df.head()) 42 43 N = int(.01 * len(df)) 44 df = df.tail(N) 45 46 for term, tfidf in zip(df["term"].values, df["tfidf"].values): 47 print(term, ":", tfidf)
9.9社交网络分析
所谓社交网络分析,实际上就是利用网络理论来研究社会关系,其中,网络中的节点代表的是网络中的参与者。节点之间的连线代表的是参与者之间的相互关系。
严格来讲,这应该称为一个图。本书仅介绍如何利用流行的Python库NetworkX来分析简单的图。这里,通过Python库matplotlib来对这些网络图进行可视化。
为了安装NetworkX,可以使用如下命令。
1 $ pip3 install networkx
关于Networkx,也可以参照这篇文章《数据分析实战-托马兹.卓巴斯》读书笔记第8章--图(NetworkX、Gephi)修订版
下面导入NetworkX并指定一个简单的别名。
1 Import networkx as nx
NetworkX提供了许多示例图,下面将其列出,代码如下。
1 print([s for s in dir(nx) if s.endswith('graph')])
下面导入Davis Southern women图并绘制出各个节点的度的柱状图,代码如下。
1 G = nx.davis_southern_women_graph() 2 plt.hist(list(nx.degree(G).values())) #AttributeError: 'DegreeView' object has no attribute 'values',去掉values即可,邀月注。 3 plt.show()
最终得到如图9-6所示的柱状图。
下面来绘制带有节点标签的网络图,所需命令如下。
1 plt.figure(figsize=(8,8)) 2 pos = nx.spring_layout(G) 3 nx.draw(G, node_size=10) 4 nx.draw_networkx_labels(G, pos) 5 plt.show()
得到的图形如图9-7所示。
图像的可读性可以再优化:
1 plt.figure(figsize=(8,8)) 2 print(help(nx.spring_layout)) 3 pos = nx.spring_layout(G, scale=2) 4 5 nx.draw(G) 6 nx.draw_networkx_labels(G, pos) 7 plt.show()
效果如下图:
虽然这个例子很短,但是对于简单体验NetworkX的功能特性来说已经足够了。我们可以借助NetworkX来探索、可观化和分析社交媒体网络,如Twitter、Facebook以及LinkedIn等。当然,本节讲述的方法不仅适用于社交网络,实际上,所有类似的网络图都可以使用NetworkX来处理。
9.10小结
本章讲述了文本分析方面的知识。首先,我们给出了文本分析中剔除停用词的一个最佳实践。
介绍词袋模型时,我们还创建了一个词袋来存放文档内出现的单词。此外,我们还根据所有的单词计数,为每个文档生成了一个特征向量。
分类算法是机器学习算法中的一种,用来给特定事物进行分类。朴素贝叶斯分类是一种概率算法,它基于概率与数理统计中的贝叶斯定理。这里,贝叶斯定理指出,后验概率与先验概率和相似度之积成正比。
第10章将对机器学习进行更深入的介绍,因为这是一个充满了无限希望的研究领域。也许有一天,它将完全替代人类劳动力。届时,以气象数据为例,来说明Python机器学习库scikit-learn的具体使用方法。
第9章完。
随书源码官方下载:
https://www.ptpress.com.cn/shopping/buy?bookId=bae24ecb-a1a1-41c7-be7c-d913b163c111
需要登录后免费下载。