数据分析学习总结笔记16:NLP自然语言处理与文本探索性分析
文章目录
1 引言
探索性数据分析是所有机器学习工作流程中最重要的部分之一,自然语言处理也不例外。但是应该选择哪些工具来进行有效地探索,及对文本数据进行可视化呢?
本文将讨论并实现几乎所有可用于理解文本数据的主要技术,并对完成这项工作的 Python 代码进行全面介绍。
2 数据集
本文将使用来自kaggle的包含100万个新闻标题的数据集。如果读者想一步一步地进行分析,可能需要安装以下python库:
pip install \ pandas matplotlib numpy \ nltk seaborn sklearn gensim pyldavis \ wordcloud textblob spacy textstat
现在,来看一下数据。
news= pd.read_csv('data/abcnews-date-text.csv',nrows=10000)
news.head(3)
数据集只包含两列,即发布日期和新闻标题。为了简单起见,本文将只对数据集中的前10000行进行探索分析,由于标题是按发布日期排序的,实际上是从2003年2月19日到2003年4月7日的两个月时间的新闻。

3 文本统计信息分析
文本统计可视化是一种简单但非常有见地的技术。包括:
- 词频分析
- 句子长度分析
- 平均字长分析
- 其他
这些有助于探索文本数据的基本特征。为此,我们将主要使用直方图(连续数据)和条形图(分类数据)。
首先,对每个句子中出现的字符数可视化。这可以让我们大致了解新闻标题的长度。
news['headline_text'].str.len().hist()

直方图显示新闻标题从10到70个字符不等,一般在25到55个字符之间。
现在,进行词级的数据探索,计算出每个新闻标题中出现的词数。
news['headline_text'].str.split().map(lambda x: len(x)).hist()

之后,进行每个句子中的平均词长度分析。
news['headline_text'].str.split().apply(lambda x : [len(i) for i in x]). map(lambda x: np.mean(x)).hist()

词的平均长度范围在3到9之间,5最常见。这是否意味着人们真正在新闻标题中使用了简短的词汇?
这种想法可能不正确的一个原因是“停用词”。在任何语言中,诸如英文中“ the”、“ a”、“ an”等,中文里的“的”、“在”等,最常用的词就是“停用词”。由于这些词的长度可能很小,也就可能导致了上面的图向左倾斜。
要获得包含停用词的语料库,可以使用nltk库。 nltk库包含多种语言的停用词。由于本文处理英语新闻,我们将从语料库中过滤英语中的停用词。
import nltk
nltk.download('stopwords')
stop = nltk.corpus.stopwords.words("english")
创建语料库。
corpus=[]
new= news['headline_text'].str.split()
new=new.values.tolist()
corpus=[word for i in new for word in i]
from collections import defaultdict
dic=defaultdict(int)
for word in corpus:
if word in stop:
dic[word]+=1
top=sorted(dic.items(), key=lambda x:x[1],reverse=True)[:10]
x,y=zip(*top)
plt.bar(x,y)
然后写出最后的停用词。

可以清楚地看到,诸如“ to”、“ in”和“ for”之类的停用词在新闻标题中占据主导地位。现在知道在文本中哪些停用词经常出现,那么除了这些停用词以外的哪些词经常出现。
我们将使用collections库中的 counter 函数来计数并在元组列表中存储每个词的出现次数。在处理自然语言处理中的词级分析时,这是一个非常有用的函数。
counter=Counter(corpus)
most=counter.most_common()
x, y= [], []
for word,count in most[:40]:
if (word not in stop):
x.append(word)
y.append(count)
sns.barplot(x=y,y=x)

“us”、“Iraq”和“war”占据了过去15年的新闻头条。这里的“ us”可能指美国或者我们(你和我)。us不是一个停顿词,但当观察图表中的其他词时,它们都与美伊战争有关,那么这里的“us”可能指的是美国。
4 Ngram模型探索
Ngrams是n个词的简单连续序列。例如“riverbank”、“The three musketeers”等等。如果词的数量是2,那么它就被称为二元组;有3个词叫做三元组。
关注最常见的n-grams可以更好地理解这个词的上下文。
为实现n-grams,本文将使用nltk.util中的ngrams函数:
from nltk.util import ngrams
list(ngrams(['I' ,'went','to','the','river','bank'],2))

知道如何创建 n-grams,就可以进行可视化。为词汇表构建代表,将使用 Countvectorizer。
Countvectorizer是一种简单的方法,用于标记、向量化和以适当的形式表示语料库。可以在sklearn.feature_extraction.text中使用。
因此,我们将分析新闻标题中的二元组。
def get_top_ngram(corpus, n=None):
vec = CountVectorizer(ngram_range=(n, n)).fit(corpus)
bag_of_words = vec.transform(corpus)
#前两行可直接写为
#bag_of_words = CountVectorizer(ngram_range=(n, n)).fit_transform(corpus)
sum_words = bag_of_words.sum(axis=0)
words_freq = [(word, sum_words[0, idx])
for word, idx in vec.vocabulary_.items()] #vec.vocabulary_矩阵化后,二元组词对应的列id
words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True)
return words_freq[:10]
top_n_bigrams=get_top_ngram(news['headline_text'],2)[:10]
x,y=map(list,zip(*top_n_bigrams))
sns.barplot(x=y,y=x)

可以看到,与战争有关的“反战”、“阵亡”等重大事件占据了新闻头条。三元组词是怎么样的?
top_tri_grams=get_top_ngram(news['headline_text'],n=3)
x,y=map(list,zip(*top_tri_grams))
sns.barplot(x=y,y=x)

可以看到,其中许多是“面对法庭”和“反战抗议”的一些组合。这意味着我们应该在数据清理方面投入一些精力,看看是否能够将这些同义词组合成一个干净的标记(token)。
5 基于pyLDAvis的主题模型探索
建立主题模型是一个过程,它使用非监督学习技术提取出文档集合中出现的主要主题。**隐含狄利克雷分布(LDA)**是一个简单易用的主题建模模型。每个文档由主题的分布来表示,每个主题由词语的分布来表示。
一旦将文档按主题进行分类,就可以对每个主题或主题组的进行深入的数据探索。
但在进入主题模型之前,必须对数据进行一些预处理:
- tokenize:将句子转换成标记或词列表的过程
- 删除停顿词
- lemmatize(词元法):把每个词的变化形式减少为一个共同的基础或词根
- 转换为单词包: 单词包是一个字典,其中键是单词(或ngram / token),值是每个单词在语料库中出现的次数
使用 NLTK,可以很容易地进行tokenize和lemmatize:
nltk.download('punkt')
nltk.download('wordnet')
def preprocess_news(df):
corpus=[]
stem=PorterStemmer()
lem=WordNetLemmatizer()
for news in df['headline_text']:
words=[w for w in nltk.word_tokenize(news) if (w not in stop)]
words=[lem.lemmatize(w) for w in words if len(w)>2]
corpus.append(words)
return corpus
corpus=preprocess_news(news)
我们使用gensim创建一个单词包模型。
dic=gensim.corpora.Dictionary(corpus)
bow_corpus = [dic.doc2bow(doc) for doc in corpus]
最终可以创建 LDA 模型:
lda_model = gensim.models.LdaMulticore(bow_corpus,
num_topics = 4,
id2word = dic,
passes = 10,
workers = 2)
lda_model.show_topics()

主题0表示与伊拉克战争和警察有关的东西。题目3显示与美伊战争声明和抗议相关。
可以展示所有的主题并尝试理解它们,但是有一些工具可以更有效地运行这种数据探索。其中一个工具是pyLDAvis,它可以交互式地可视化LDA的结果。
pyLDAvis.enable_notebook()
vis = pyLDAvis.genism.prepare(lda_model, bow_corpus, dic)
vis

- 在左边,每个圆圈的面积代表主题相对于语料库的重要性。由于设定为四个主题,则会显示出四个圈子。
- 圆心之间的距离表示主题之间的相似度。可以看到主题1和主题3重叠,这表明主题更加相似。
- 在右边,每个主题的直方图显示了前30个相关单词。例如,在主题1中最相关的词是“Iraq”,“say”,“death”等。
所以在案例中,可以在新闻标题中看到很多与战争相关的词语和话题。
6 绘制词云图
词云是表示文本数据的一种很好的方式。出现在词云中的每个单词的大小和颜色表示它的频率或重要性。
使用python创建词云很容易,但是需要以语料库的形式提供数据。幸运的是,前面的部分已经准备了它。
stopwords = set(STOPWORDS)
def show_wordcloud(data):
wordcloud = WordCloud(
background_color='white',
stopwords=stopwords,
max_words=100,
max_font_size=30,
scale=3,
random_state=1)
wordcloud=wordcloud.generate(str(data))
fig = plt.figure(1, figsize=(12, 12))
plt.axis('off')
plt.imshow(wordcloud)
plt.show()
show_wordcloud(corpus)

同样可以看到与战争相关的术语被突出显示,这表明这些词经常出现在新闻标题中。
程序中有许多参数可以调整,其中最突出的有:
- stopwords: 被阻止出现在图像中的一组词
- max_words: 指示要显示的最大词数
- max_font_size: 最大字体大小
7 情感分析
情感分析是一种非常常见的自然语言处理任务,它决定文本所表达的情感是正面的、负面的还是中性的。这对于发现与评论相关的情绪非常有用,评论可以帮助我们从文本数据中获得一些有价值的见解。
关于情感分析若需详细了解可查看之前的笔记:数据分析学习总结笔记01:情感分析
有很多python库可以用来进行情绪分析,本文将介绍 TextBlob 和 Vader Sentiment。
7.1 TextBlob
Textblob是一个构建在nltk之上的python库。它已经出现了一段时间,非常容易和方便使用。
Textblob的情感分析函数会返回两个属性:
- polarity极性: [-1,1]范围内的浮点数,其中1表示肯定,-1表示否定。
- subjectivity主观性: 指一个人的判断是如何被个人的观点和感觉所塑造的。主观性被表示为位于[0,1]范围内的浮点值
在新闻标题数据中运行这个函数。
TextBlob('100 people killed in Iraq').sentiment

Textblob得到结果,“100人在伊拉克被杀害”文本是负面的,且不是一种意见或感觉,而是一种事实陈述。我们知道如何计算这些情绪得分后,可以使用直方图将它们可视化,并进一步探索数据。
def polarity(text):
return TextBlob(text).sentiment.polarity
news['polarity_score']=news['headline_text'].apply(lambda x : polarity(x))
news['polarity_score'].hist()

可以看到极性主要在0.00到0.20之间。这表明大多数新闻标题是偏中性的。再深入一点,根据极性得分将新闻分为消极、积极和中性。
def sentiment(x):
if x<0:
return 'neg'
elif x==0:
return 'neu'
else:
return 'pos'
news['polarity']=news['polarity_score'].map(lambda x: sentiment(x))
plt.bar(news.polarity.value_counts().index,
news.polarity.value_counts())

可以确定,70%的新闻是中性的,只有18%的正面新闻和11%的负面新闻。看看一些正面和负面的标题。
news[news['polarity']=='pos']['headline_text'].head()

可以看出,正面新闻标题大多是关于体育方面的胜利。再看看负面新闻的标题:
news[news['polarity']=='neg']['headline_text'].head()

7.2 Vader Sentiment Analysis
相比Textblob,Vader在侦测负面情绪方面做得更好。这在社交媒体文本情感分析中是非常有用的。
Vader或Valence Aware Dictionary and Sentiment Reasoner 是一个基于规则 / 词典的开源情绪分析器预先构建的库,受MIT许可证保护。
Vader情感分析类会返回一个字典,其中包含文本为正、负和中性的概率。之后就可以以最大的概率对情绪进行过滤和选择。
我们将使用VADER进行同样的分析,并检查是否有很大差别。
nltk.download('vader_lexicon')
sid = SentimentIntensityAnalyzer()
def get_vader_score(sent):
# Polarity score returns dictionary
ss = sid.polarity_scores(sent)
#return ss
return np.argmax(list(ss.values())[:-1])
news['polarity']=news['headline_text'].\
map(lambda x: get_vader_score(x))
polarity=news['polarity'].replace({0:'neg',1:'neu',2:'pos'})
plt.bar(polarity.value_counts().index,
polarity.value_counts())

可以发现,在分布上有细微的差别。更多的新闻标题被归类为中性,大约85%,而负面新闻标题的数量增加到了13%。
8 命名实体识别NER
命名实体识别是一种信息抽取识别方法,它将文本中出现的实体分为预定义的实体类型,如“人”、“地点”、“组织”等。
通过使用 NER,我们可以获得给定文本数据集中存在的实体类型的深刻见解。
让我们来看一篇新闻报道为例子。

在上面的新闻中,命名实体识别模型应该能够识别实体,例如RBI作为一个组织,孟买和印度作为地点,等等。
有三个标准库可以进行命名实体识别:
- Standford NER
- spaCy
- NLTK
在本文中,将使用spaCy,这是一个用于高级自然语言处理任务的开源库。除了NER之外,spaCy还提供了许多其他功能,如词性标注、词向量转换等。
Spacy的命名实体识别已经在OntoNotes 5语料库上进行了训练,它支持以下实体类型:

Spacy有三种预训练过的英语模型。本文将使用en_core_web_sm来完成分析,但是也可以尝试其他的模型。
要使用它,必须先进行下载:
python -m spacy download en_core_web_sm
初始化语言模型:
nlp = spacy.load("en_core_web_sm")
Spacy 的一个优点是我们只需要应用一次nlp函数,整个后台就会返回我们需要的对象。
doc=nlp('India and Iran have agreed to boost the economic viability \
of the strategic Chabahar port through various measures, \
including larger subsidies to merchant shipping firms using the facility, \
people familiar with the development said on Thursday.')
[(x.text,x.label_) for x in doc.ents]

可以看到,印度和伊朗被认为是地理位置(GPE) ,恰巴哈尔被认为是人,星期四被认为是日期。还可以使用 spaCy 中的display模块来实现输出的可视化。
spacy.displacy.render(doc, style='ent')

这样就创建了一个非常简洁的可视化的句子,其中每个实体类型都用不同的颜色标记。
知道了如何执行NER,就可以通过对从数据集中提取的命名实体进行各种可视化来进一步探索数据。
首先,在新闻标题上进行命名实体识别,并存储实体类型。
def ner(text):
doc=nlp(text)
return [X.label_ for X in doc.ents]
ent=news['headline_text'].apply(lambda x : ner(x))
ent=[x for sub in ent for x in sub]
counter=Counter(ent)
count=counter.most_common()
将实体频率可视化:
x,y=map(list,zip(*count))
sns.barplot(x=y,y=x)

现在可以看到地理位置GPE 和机构ORG 占据了新闻头条,其次是 PERSON实体。
还可以将每个实体中最常见的标记可视化。新闻标题中最常见的地理位置:
def ner(text,ent="GPE"):
doc=nlp(text)
return [X.text for X in doc.ents if X.label_ == ent]
gpe=news['headline_text'].apply(lambda x: ner(x))
gpe=[i for x in gpe for i in x]
counter=Counter(gpe)
x,y=map(list,zip(*counter.most_common(10)))
sns.barplot(y,x)

从上图可以证实这样一个事实: “us”在新闻标题中表示是美国。
以下可视化新闻标题中最常见的人名:
per=news['headline_text'].apply(lambda x: ner(x,"PERSON"))
per=[i for x in per for i in x]
counter=Counter(per)
x,y=map(list,zip(*counter.most_common(10)))
sns.barplot(y,x)

萨达姆·侯赛因和乔治·布什分别是战时伊拉克和美国的总统。此外,可以看到,该模型的分类并不完美,“ vic govt”或“ nsw govt”作分类为人名,而不是一个政府机构。
9 词性标签探索分析
词性标注是将词性标注分配给句子中的词的一种方法。
词性可分为八个主要部分:
- 名词(NN) - 约瑟夫,伦敦,桌子,猫,老师,笔,城市
- 动词(VB) - 阅读,说话,跑步,吃饭,玩耍,生活,走路,拥有,喜欢
- 形容词(JJ) -美丽,快乐,悲伤,年轻,有趣,三个
- 副词(RB) - 慢慢地,安静地,非常,总是,从来没有,也,好吧,明天
- 介词(IN) - 在,关于,来自
- 连词(CC) - 和,或,与,因为,如果
- 代词(PRP) - 我,你,我们,他们,他,她,它,我,他,她,这个
- 感叹词(INT) - 哎哟! 哇! 太棒了! 救命! 哦! 嘿! 嗨!
这不是一个简单的任务,因为同一个词可能在不同的句子和不同的上下文中使用。但是,一旦这样做了,就可以创建许多有用的可视化,可以得到额外的洞察。
本文将使用nltk来进行词性标注,但是还有其他库(spacy、textblob)可以做得很好。
让我们来看一个例子。
nltk.download('averaged_perceptron_tagger')
sentence="The greatest comeback stories in 2019"
tokens=nltk.word_tokenize(sentence)
nltk.pos_tag(tokens)

同样,还可以使用 spacy.displacy 模块可视化句子中的词类及其依存图。
doc = nlp('The greatest comeback stories in 2019')
displacy.render(doc, style='dep', jupyter=True, options={'distance': 150})

可以在这里观察到各种依赖标签。例如,DET标记表示限定词“ the”和名词“ stories”之间的关系。可以在这里查看依赖项标记列表及其含义。
知道了什么是词性标签,就可以用它来探索新闻标题数据集。
def pos(text):
pos=nltk.pos_tag(nltk.word_tokenize(text))
pos=list(map(list,zip(*pos)))[1]
return pos
tags=news['headline_text'].apply(lambda x : pos(x))
tags=[x for l in tags for x in l]
counter=Counter(tags)
x,y=list(map(list,zip(*counter.most_common(7))))
sns.barplot(x=y,y=x)

可以清楚地看到,名词(NN)在新闻标题中占主导地位,其次是形容词(JJ)。这是典型的新闻文章,而艺术形式较高的形容词(JJ)出现频率相当高。
通过调查新闻标题中最常出现的单数名词来深入研究这个问题。
def get_adjs(text):
adj=[]
pos=nltk.pos_tag(nltk.word_tokenize(text))
for word,tag in pos:
if tag=='NN':
adj.append(word)
return adj
words=news['headline_text'].apply(lambda x : get_adjs(x))
words=[x for l in words for x in l]
counter=Counter(words)
x,y=list(map(list,zip(*counter.most_common(7))))
sns.barplot(x=y,y=x)

可以通过调查新闻标题中最常出现的单数名词来深入研究这个问题。
诸如“战争”、“伊拉克”、“人”等名词在新闻标题中占主导地位。同样可以使用上面的函数来观察和检查其他词类。
10 文本可读性分析
了解文本易读性(难以阅读)和什么类型的读者可以充分理解它,是非常重要的。是否需要拥有大学学位才能理解信息,或者一个一年级学生就可以清楚地看到重点是什么?
实际上,可以在文档或文本上放置一个称为可读性索引的数字。可读性索引是一个数值,表示阅读和理解文本的难度(或容易程度)。
英语中有许多可读性评分公式,其中最突出的有:

Textstat是一个很酷的Python库,它提供了所有这些文本统计计算方法的实现函数。让我们使用Textstat来实现Flesch-Kincaid可读性测试索引。
现在,可以绘制分数的直方图并可视化输出。
news['headline_text'].apply(lambda x : flesch_reading_ease(x)).hist()

几乎所有的可读性分数都在60以上,这意味着一个普通的11岁学生可以阅读和理解的新闻标题。看看所有可读性得分低于5的新闻标题。
reading = list(news['headline_text'].apply(lambda x : flesch_reading_ease(x)))
x=[i for i in range(len(reading)) if reading[i]<5]
news.iloc[x]['headline_text'].head()

可以发现,在新闻标题中存在一些复杂的词语,如“投降”、“临时”、“诱捕”等。这些词语可能导致分数降到了5分以下。
11 结语
在本文中,我们讨论并实现了针对文本数据的各种探索性数据分析方法。有些是常见的,有些是不太为人所知的,但这些都可以成为数据探索工具箱的一个很好的补充。
希望读者能在当前和未来的项目中发现其中的一些有用之处。
本文主要参考于:
NLP自然语言处理与探索性分析(沈浩老师)
相关笔记:
- Python相关实用技巧01:安装Python库超实用方法,轻松告别失败!
- Python相关实用技巧02:Python2和Python3的区别
- Python相关实用技巧03:14个对数据科学最有用的Python库
- Python相关实用技巧04:网络爬虫之Scrapy框架及案例分析
- Python相关实用技巧05:yield关键字的使用
- Scrapy爬虫小技巧01:轻松获取cookies
- Scrapy爬虫小技巧02:HTTP status code is not handled or not allowed的解决方法
- 数据分析学习总结笔记01:情感分析
- 数据分析学习总结笔记02:聚类分析及其R语言实现
- 数据分析学习总结笔记03:数据降维经典方法
- 数据分析学习总结笔记04:异常值处理
- 数据分析学习总结笔记05:缺失值分析及处理
- 数据分析学习总结笔记06:T检验的原理和步骤
- 数据分析学习总结笔记07:方差分析
- 数据分析学习总结笔记07:回归分析概述
- 数据分析学习总结笔记08:数据分类典型方法及其R语言实现
- 数据分析学习总结笔记09:文本分析
- 数据分析学习总结笔记10:网络分析
- 数据分析学习总结笔记11:空间复杂度和时间复杂度
- 数据分析学习总结笔记12:空间自相关——空间位置与相近位置的指标测度
- 数据分析学习总结笔记13:生存分析及Python实现
- 数据分析学习总结笔记14:A/B Test及Python实现
- 数据分析学习总结笔记15:时间序列分析及Python实现
- 笔记专栏——数据研发笔试Leetcode刷题
更多推荐
所有评论(0)