Kaggle spooky NLP

只愿长相守 提交于 2019-12-17 18:54:41

https://www.kaggle.com/arthurtok/spooky-nlp-and-topic-modelling-tutorial

介绍


在本笔记本中,我将对这个Spooky Author数据集的主题建模进行非常基本的尝试。主题建模是我们尝试根据基础文档和文本语料库中的单词来发现抽象主题或“主题”的过程。我将在这里介绍两种标准的主题建模技术,第一种是称为潜在Dirichlet分配(LDA)的技术,第二种是非负矩阵分解(NMF)。我还将借此机会介绍一些自然语言处理基础知识,例如原始文本的标记化,词干化和向量化,这些也有望在用学习模型进行预测时派上用场。

该笔记本的概述如下:

探索性数据分析(EDA)和Wordclouds-通过生成简单的统计数据(例如,不同作者的词频)以及绘制一些词云(带有图像蒙版)来分析数据。
带有NLTK(自然语言工具包)的自然语言处理(NLP)-引入了基本的文本处理方法,例如标记化,停止单词删除,通过术语频率(TF)和反向文档频率(TF-IDF)提取文本和对向量进行矢量化

使用LDA和NNMF进行主题建模-实现潜在狄利克雷分配(LDA)和非负矩阵分解(NMF)的两种主题建模技术。

根据比赛页面,我们已经提供了三种不同的作者姓名缩写,这些姓名缩写与实际作者的映射如下:

(如果单击其名称,则指向其Wikipedia页面配置文件的链接)

EAP-埃德加·艾伦·坡(Edgar Allen Poe):美国作家,其诗歌和短篇小说围绕神秘,悲惨和严峻的故事展开。可以说他最著名的作品是《乌鸦》这首诗,他也被广泛认为是侦探小说类型的开创者。

HPL-HP Lovecraft:以恐怖小说的创作而闻名,他最著名的故事是围绕着臭名昭著的生物“ Cthulhu”的虚构神话而生的-八爪鱼头部和类人动物的混合嵌合体,背面有翅膀。

MWS-玛丽·雪莱(Mary Shelley):似乎参与了一系列文学追求,包括小说家,戏剧家,旅行作家,传记作者。她以《科学怪人》的经典故事而闻名,科学家弗兰肯斯坦(又名“现代普罗米修斯”)创作出与他的名字有关的怪兽。

但是生成普通的wordcloud相当无聊,因此我想向您介绍一种导入图片(一些相关内容)并将该图片的轮廓用作我们wordcloud的蒙版的技术。 因此,我选择的照片是我认为对他们的作者最具代表性的照片:

)埃德加·艾伦·坡(Edgar Allen Poe)的乌鸦(Raven)2。
我在Kaggle上加载图片的方式是一种功能破解,尽管熟悉我的工作的读者都知道这一技巧。 我首先导出要使用的任何图像的Base64编码,然后使用该特定编码,然后将图像重新转换回笔记本计算机。 下面的单元格包含我将要使用的三个图像的Base64编码,但是我将它们隐藏了,这样我就不会仅用很长的文字污染笔记本电脑-如果要查看编码,请取消隐藏它们。

import codecs
# Generate the Mask for EAP
f1 = open("eap.png", "wb")
f1.write(codecs.decode(eap_64,'base64'))
f1.close()
img1 = imread("eap.png")
# img = img.resize((980,1080))
hcmask = img1

f2 = open("mws.png", "wb")
f2.write(codecs.decode(mws_64,'base64'))
f2.close()
img2 = imread("mws.png")
hcmask2 = img2

f3 = open("hpl.png", "wb")
f3.write(codecs.decode(hpl_64,'base64'))
f3.close()
img3 = imread("hpl.png")
hcmask3 = img3;

plt.figure(figsize=(16,13))
wc = WordCloud(background_color="black", max_words=10000, 
               mask=hcmask3, stopwords=STOPWORDS, max_font_size= 40)
wc.generate(" ".join(hpl))
plt.title("HP Lovecraft (Cthulhu-Squidy)", fontsize=20)
# plt.imshow(wc.recolor( colormap= 'Pastel1_r' , random_state=17), alpha=0.98)
plt.imshow(wc.recolor( colormap= 'Pastel2' , random_state=17), alpha=0.98)
plt.axis('off')

标记化-将文本分离成各个组成词。
停用词-丢弃出现频​​率过高的任何词,因为其出现频率对帮助检测相关文本没有帮助。 (顺便说一句,也请考虑丢弃很少出现的单词)。
词干-将单词的变体组合成单个父词,该词仍传达相同的含义
向量化-将文本转换为向量格式。最简单的方法之一就是著名的词袋方法,您可以在其中创建一个矩阵(针对语料库中的每个文档或文本)。以最简单的形式,该矩阵存储单词频率(单词计数),通常被称为原始文本的矢量化。
自然语言工具包(NLTK):为了使我们的自然语言处理工作更加方便,让我向您介绍NLP上最方便的工具包之一-自然语言工具包,也通常称为NLTK模块。要导入该工具包,它就像以下操作一样简单:

标记化的概念是在给定文档中采取一系列字符(例如Python字符串)并将其切成单独的组成部分的动作,这些组成部分是此方法的同义“标记”。 人们可能会松散地将它们视为句子中的单数单词。 可以天真地在字符串上实现“ split()”方法,该字符串根据参数中的标识符将其分为python列表。 实际上,这并不是一件容易的事

在这里,我们将训练数据中的文本的第一句话划分为一个空格,如下所示:

# Storing the first text element as a string
first_text = train.text.values[0]
print(first_text)
print("="*90)
print(first_text.split(" "))
This process, however, afforded me no means of ascertaining the dimensions of my dungeon; as I might make its circuit, and return to the point whence I set out, without being aware of the fact; so perfectly uniform seemed the wall.
==========================================================================================
['This', 'process,', 'however,', 'afforded', 'me', 'no', 'means', 'of', 'ascertaining', 'the', 'dimensions', 'of', 'my', 'dungeon;', 'as', 'I', 'might', 'make', 'its', 'circuit,', 'and', 'return', 'to', 'the', 'point', 'whence', 'I', 'set', 'out,', 'without', 'being', 'aware', 'of', 'the', 'fact;', 'so', 'perfectly', 'uniform', 'seemed', 'the', 'wall.']

但是,如您从第一次进行标记化的尝试中所看到的,将句子分隔成各个元素(或术语)并不完全准确。 例如,请查看包含术语“过程”的列表的第二个元素。 标点符号(逗号)也已包括在内,并与“过程”一词一起被视为术语本身。 理想情况下,我们希望逗号和单词位于列表的两个不同且独立的元素中。 尝试使用纯python列表操作执行此操作将非常复杂,因此这就是NLTK库发挥作用的地方。 有一个方便的方法“ word_tokenize()”(TreebankWord标记生成器),该方法自动将单数单词和标点符号剥离为单独的元素,如下所示:

first_text_list = nltk.word_tokenize(first_text)
print(first_text_list)
['This', 'process', ',', 'however', ',', 'afforded', 'me', 'no', 'means', 'of', 'ascertaining', 'the', 'dimensions', 'of', 'my', 'dungeon', ';', 'as', 'I', 'might', 'make', 'its', 'circuit', ',', 'and', 'return', 'to', 'the', 'point', 'whence', 'I', 'set', 'out', ',', 'without', 'being', 'aware', 'of', 'the', 'fact', ';', 'so', 'perfectly', 'uniform', 'seemed', 'the', 'wall', '.']

移除停用词

first_text_list_cleaned = [word for word in first_text_list if word.lower() not in stopwords]
print(first_text_list_cleaned)
print("="*90)
print("Length of original list: {0} words\n"
      "Length of list after stopwords removal: {1} words"
      .format(len(first_text_list), len(first_text_list_cleaned)))
['process', ',', 'however', ',', 'afforded', 'means', 'ascertaining', 'dimensions', 'dungeon', ';', 'might', 'make', 'circuit', ',', 'return', 'point', 'whence', 'set', ',', 'without', 'aware', 'fact', ';', 'perfectly', 'uniform', 'seemed', 'wall', '.']
==========================================================================================
Length of original list: 48 words
Length of list after stopwords removal: 28 words

词干和词法化
删除停用词后,我要介绍的NLP的下一个阶段是词干提取过程。 此阶段的工作试图将相似单词的尽可能多的不同变体简化为一个术语(不同的分支都简化为单个单词的词干)。 因此,如果我们具有“running”,“runs”和“run”,则您确实希望这三个不同的词变成“运行”一词。 (但是,您当然会失去过去,现在或将来时的粒度)。

我们可以再次转到NLTK,它提供了各种词干分析器,包括Porter词干分析算法,lancaster词干分析器和Snowball词干分析器等变体。 在以下示例中,我将创建一个porter stemmer实例,如下所示:

stemmer = nltk.stem.PorterStemmer()
print("The stemmed form of running is: {}".format(stemmer.stem("running")))
print("The stemmed form of runs is: {}".format(stemmer.stem("runs")))
print("The stemmed form of run is: {}".format(stemmer.stem("run")))
The stemmed form of running is: run
The stemmed form of runs is: run
The stemmed form of run is: run

如我们所见,词干提取器已成功地将上述给定的单词简化为基本形式,这将最大程度地帮助我们减少学习和分类任务时单词集的大小。

但是,词干存在一个缺陷,那就是以下事实:该过程在切掉单词的末尾以将特定单词简化为人类可识别的基本形式时涉及相当粗略的启发式方法。 因此,此过程在折叠单词时不考虑词汇或单词形式,如本示例所示:

print("The stemmed form of leaves is: {}".format(stemmer.stem("leaves")))
The stemmed form of leaves is: leav

合法化营救
因此,我们转向另一种可以代替词干的方法。 此方法称为lemmatization,旨在实现与前一种方法相同的效果。 但是,与词干分析器不同的是,对数据集进行词素化的目的在于根据实际的词典或词汇表(Lemma)来减少词,因此不会将词切成没有词义的词干形式。 在这里,我们可以再次利用NLTK来初始化lemmatizer(WordNet变体),并检查它如何折叠单词,如下所示:

from nltk.stem import WordNetLemmatizer
lemm = WordNetLemmatizer()
print("The lemmatized form of leaves is: {}".format(lemm.lemmatize("leaves")))
The lemmatized form of leaves is: leaf

矢量化原始文本

在大量的NLP文献中,分析原始文本有许多不同的目的,在某些情况下,您希望比较一个文本主体与另一个文本主体的相似性(聚类技术/距离测量),文本分类(比赛),并发现包含正文的主题(本笔记本的目的)。为了在我们脑海中发掘主题,我们现在必须考虑如何将原始文本输入到机器学习模型中。在讨论了标记化,停用词删除和词干提取(或词根化)之后,我们现在得出了一个比较干净的文本数据集,然后开始使用。但是,在此关头,不幸的是,我们的原始文本虽然仍然是人类可读的,但仍无法机器可读。机器可以读取位数和数字,因此我们首先需要将文本转换为数字,我们使用一种非常普遍的方法,即“词袋”

言语袋法

此方法将单词计数用作起始块,并在特定于该单词的向量中记录每个单词(从整个文本开始)的出现。例如,给定这两个句子“我爱吃汉堡”,“我爱吃薯条”,我们首先标记化以获取6个单词的词汇量,从中我们可以得到-[I,love,to,eat ,汉堡,薯条]。

通过单词袋方法对文本进行矢量化处理,我们为每个单词得到六个不同的矢量。所以您问,既然我们现在有由数字(而不是文本)组成的行,则这些列(或要素)是什么形成的?好了,现在每个单词都变成了这个新的转换数据集中的一个单独的特征/列。为了说明这一点,我将利用Scikit-learn库来实现一个向量生成器,该向量生成器通过CountVectorizer方法生成单词计数(词频)的向量,如下所示。

# Defining our sentence
sentence = ["I love to eat Burgers", 
            "I love to eat Fries"]
vectorizer = CountVectorizer(min_df=0)
sentence_transform = vectorizer.fit_transform(sentence)

print("The features are:\n {}".format(vectorizer.get_feature_names()))
print("\nThe vectorized array looks like:\n {}".format(sentence_transform.toarray()))
The features are:
 ['burgers', 'eat', 'fries', 'love', 'to']

The vectorized array looks like:
 [[1 1 0 1 1]
 [0 1 1 1 1]]

稀疏矩阵向量输出

从矢量化文本的输出中,我们可以看到,这些功能由输入到矢量化器中的文本语料库中的单词组成(此处,语料库是我们前面定义的两个句子)。 只需从矢量化器中调用get_feature_names属性即可对其进行检查。

关于转换后的文本,人们很想通过简单地调用它来检查值。 但是,当您尝试调用它时,您实际上仅会收到一条消息,指出“类型类'numpy.int64'的稀疏矩阵,其中包含8个以压缩稀疏行格式存储的元素”。 因此,这意味着矢量化程序将转换后的原始文本作为矩阵返回,其中其大多数值均为零或几乎可以忽略,因此称为稀疏。 考虑到这一点,由于以下原因,我们返回的矩阵包含高度稀疏性确实是有道理的

 

到达最终目的地(双关语意)时,我将实现两种不同的主题建模技术,如下所示:

潜在狄利克雷分配-概率生成模型,通过为语料库中的单词分配权重来发现潜在于数据集的主题,其中每个主题将为每个单词分配不同的概率权重。

非负矩阵因式分解-一种近似方法,采用输入矩阵并将该矩阵的因式分解近似为另外两个矩阵,但需要注意的是矩阵中的值是非负的。

# Define helper function to print top words
def print_top_words(model, feature_names, n_top_words):
    for index, topic in enumerate(model.components_):
        message = "\nTopic #{}:".format(index)
        message += " ".join([feature_names[i] for i in topic.argsort()[:-n_top_words - 1 :-1]])
        print(message)
        print("="*70)

将所有预处理步骤放在一起
现在,这是将上一节中提到的所有文本预处理步骤组合在一起的绝佳机会。因此,您在问自己,我们真的需要在定义标记化,停用词删除,词干/词根化等方面再次经历所有的工作和步骤吗?

值得庆幸的是,我们不需要再次经历所有这些。我方便地省略了有关Sklearn矢量化器的关键细节,但在此关头会提及。使用CountVectorizer对原始文本进行矢量化处理时,标记化和停用词过滤的两个阶段会自动作为高级组件包含在内。在这里,与您之前在第2a节中介绍的NLTK标记生成器不同,Sklearn的标记生成器会丢弃所有单字符术语,例如('a','w'等),并且默认情况下也会将所有术语都转换为小写。在Sklearn中过滤停用词很方便,就像将值“ english”传递到参数“ stop_words”一样,该参数会自动使用内置的英语停用词列表。

不幸的是,矢量化器中没有内置的lemmatizer,因此我们有两个选择。要么在每次馈入数据进行矢量化之前分别进行实现,要么以某种方式扩展sklearn实现以包括此功能。对我们来说幸运的是,我们有后一种选择,其中我们可以通过覆盖“ build_analyzer”方法来扩展CountVectorizer类,如下所示:

用lemmatizer扩展CountVectorizer类¶

lemm = WordNetLemmatizer()
class LemmaCountVectorizer(CountVectorizer):
    def build_analyzer(self):
        analyzer = super(LemmaCountVectorizer, self).build_analyzer()
        return lambda doc: (lemm.lemmatize(w) for w in analyzer(doc))

在这里,我们利用了面向对象编程(OOP)中的一些细微概念。 实际上,我们已经继承并继承了原始Sklearn的CountVectorizer类,并通过为原始文本矩阵中的每个列表实现lemmatizer来覆盖build_analyzer方法。

# Storing the entire training text in a list
text = list(train.text.values)
# Calling our overwritten Count vectorizer
tf_vectorizer = LemmaCountVectorizer(max_df=0.95, 
                                     min_df=2,
                                     stop_words='english',
                                     decode_error='ignore')
tf = tf_vectorizer.fit_transform(text)

回顾术语频率

实施了词素化计数向量器后,让我们修改前50个词的词频图(按频率)。 从图中可以看到,我们所有的预处理工作都没有浪费。 随着停用词的删除,您可以看到其余词似乎更有意义,您可以在其中看到较早术语频率图中的所有停用词

潜在狄利克雷分配
最后,我们讨论了主题建模和一些无监督学习算法的实现。 我要谈的第一种方法是潜在Dirichlet分配。 现在,有两种不同的LDA算法实现方式,但是在本笔记本中,我将使用Sklearn的实现方式。 另一个非常著名的LDA实现是Radim Rehurek的gensim,因此也请检查一下。

语料库-文档-词:主题生成

在LDA中,建模过程围绕三件事:文本语料库,其文档集合,D和文档中的单词W。 因此,该算法尝试通过以下方式从该语料库中发现K个主题(如图所示)

通过βk给出的Dirichlet先验分布对κ进行建模:

通过参数化的另一个Dirichlet分布对每个文档d进行建模:

随后对于文档d,我们通过多项式分布生成一个主题,然后回溯并用于通过另一个多项式分布来生成与该主题相关的对应单词:

(图片来源:http://scikit-learn.org/stable/modules/decomposition.html#latentdirichletallocation)

LDA算法首先通过主题的混合模型对文档进行建模。 然后根据这些主题的概率分布从这些主题为单词分配权重。 正是这种对单词的概率分配使LDA的用户能够说出某个特定单词落入主题的可能性。 随后,通过从分配给特定主题的单词集合中,我们可以从词汇的角度了解该主题实际代表什么。

对于标准的LDA模型,在调用模型之前,确实需要牢记一些关键参数并考虑以编程方式进行调整:

n_components:您为模型指定的主题数
α参数:这是dirichlet参数,可以事先链接到文档主题
β参数:这是链接到主题词在前的dirichlet参数
要调用该算法,我们只需通过Sklearn的LatentDirichletAllocation函数创建一个LDA实例。 理想地,各种参数将通过某种验证方案获得。 在这种情况下,通过执行KMeans +潜在语义分析方案(如本文中所示)找到n_components(或主题号)的最佳值,从而迭代Kmeans簇的数量和LSA维数,并且最佳 轮廓平均得分。

lda = LatentDirichletAllocation(n_components=11, max_iter=5,
                                learning_method = 'online',
                                learning_offset = 50.,
                                random_state = 0)

lda.fit(tf)

LDA生成的主题
我们将利用我们先前定义的“ print_top_words”帮助函数返回归因于每个LDA生成主题的前10个单词。 要选择主题数,可通过函数中的参数n_components进行处理。

n_top_words = 40
print("\nTopics in LDA model: ")
tf_feature_names = tf_vectorizer.get_feature_names()
print_top_words(lda, tf_feature_names, n_top_words)
Topics in LDA model: 

Topic #0:mean night fact young return great human looking wonder countenance difficulty greater wife finally set possessed regard struck perceived act society law health key fearful mr exceedingly evidence carried home write lady various recall accident force poet neck conduct investigation
======================================================================

Topic #1:death love raymond hope heart word child went time good man ground evil long misery replied filled passion bed till happiness memory heavy region year escape spirit grief visit doe story beauty die plague making influence thou letter appeared power
======================================================================

Topic #2:left let hand said took say little length body air secret gave right having great arm thousand character minute foot true self gentleman pleasure box clock discovered point sought pain nearly case best mere course manner balloon fear head going
======================================================================

下面还有制作词云的

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!