ML/NLP入门教程Python版(第一部分:文本处理)

第一部分:文本处理

欢迎来到机器学习和自然语言处理原型编码教程系列的第一部分。 Thoughtly正在制作一个着重于理解机器学习基础的系列教程,着重关注于在自然语言处理中的应用。

这一系列教程的目标是提供有据可查的可用代码,附加留言部分的深入探讨。代码将被放到GitHub上,在一个开放的许可证下,允许你任意修改或使用——不必署名(注明来源)。这里的代码为了明白起见以牺牲性能为代价写的比较冗长。如果你有大量的数据要处理,这些工具的可扩展性很可能无法达到完成你目的的要求。幸运的是,我们正在计划通过研究此处讨论的算法在当下最新的实现,来更好地对这个系列进行深入探索。这些内容都是黑盒子,是我们在初始系列中有意避免(到实用的程度)提到的内容。我们相信,在能使用这些黑盒子之前,在机器学习方面打下一个坚实的基础是至关重要的。

第一部分的重点是如何从文本语料库提取出信息来。我们有意用介绍性的水平来开始教程,但是它涉及到很多不同的技巧和测量标准,这些方法都会在之后应用到更深入的机器学习任务上。

文本提取

下文介绍的以及此处代码中用到的工具,都假设我们将所选的语料当作一袋单词。这是你在处理文本文档的时候常常会看到的一个基本概念。将语料当作一袋单词是将文档向量化中的一个典型步骤,以供机器学习算法进一步处理。把文档转换成可处理向量通常还需要采取一些额外步骤,我们将在后面的课程中对此进行讨论。本课程中介绍的概念和工具将作为后面工具的构建模块。也许更重要的是,这些工具可以帮助你通过快速检查一个文本语料库,从而对它所包含的内容有一个基本的了解。

本课程中我们所研究的代码及示例都是使用Python实现的。这些代码能够从NLTK(Python的自然语言工具包)所提供的不同的文本语料库中提取数据。这是个包括了ABC新闻的文字、圣经的创世纪、从古滕堡计划中选取的部分文本、总统就职演说、国情咨文和从网络上截取的部分文本所组成的语料库。另外,用户还能从他们自己提供的语料库来提取文本。从NLTK导入的代码并不是特别有趣,但我们想指出的是,要从NLTK文本语料库中提取数据是非常简单方便的。

上面的大部分代码只是日志。有意思的部分在357行、362行、367行等。基于用户选择,每部分加载不同的语料库。 NLTK对从现有语料库中提取文本提供了一些非常便利的方法。这包括一些简单的、纯文本的语料库,也包括一些已经用各种方式被标记过的语料库 —— 语料库中的每个文档可能被标记过类别或是语料库中有的语音已被加过标签,如此等等。在本课程中,我们对NLTK的使用仅限于语料库的导入、词汇的切分,以及我们下面将讨论两个操作,词根和词形还原。虽然不会总是如此,但现在为止足够我们需要的所有功能。值得注意的是,您还可以在脚本中使用-custom参数导入自定义语料库。这应该是含有.txt文件的文件夹。该文件夹是递归读入的,所以含有.txt文件的子文件夹也能被处理。

词汇切分

词汇切分是切分语料库,使之变成各个独立部分——通常指单词,的行为。我们这样做是因为大多数ML算法无法处理任意长的文本字符串。相反,他们会假设你已经分割你的语料库为单独的,算法可处理的词块(token)。虽然我们将在后面的课程详细讨论这个话题,算法不一定限于一次只处理一个词块(token)。事实上,许多算法只在处理短序列(n-grams)时有用。本课程中我们将情况限定于一序列(1-grams),或者叫,单序列(unigram)。

对文本语料库做词汇切分的最简单的方法就是仅基于空白字符。这种方法确实非常简单,但它也有缺点。例如,它会导致位于句尾的文本包含有句尾标点符号,而一般不需要这样。在另一方面,类似can’t和e.g.这样带有词内标点的单词就没法被正确提取出来了。我们可以添加一步操作来删除所有非字母数字的字符。这将解决句尾标点符号的问题,同时也能将can’t和e.g.这样的单词提取出来,尽管是以丢掉了他们的标点符号的方式被提取出来的。然而,这也引入了一个新的问题。对于某些应用,我们还是希望保留标点符号。在创建语言模型的时候,句尾标点能区分一个单词是否是结尾单词,从这方面来说,额外的标点信息是有价值的。

对于这个任务,我们要将一些标点符号(句号)作为一个词,使用NLTK word_tokenizer(它是基于TreebankWordTokenizer来实现的)来做词汇切分。这个分词器有很多针对各式各样的词汇做切分的规则。举例来说,“can’t”这样的缩写实际上被分成了两个词(token) – ca和n’t。有趣的是,这意味着我们最后会得到ca这样的词,它理想地匹配了can(在某些任务中)。这样的错误匹配是这种符号化算法带来的不幸后果。NLTK支持多种分词器。这是一个及其冗长的文件,http://www.nltk.org/api/nltk.tokenize.html,但在里面可以找到它所支持的分词器的细节。

词干提取和词形还原

一旦取到了文字我们就可以开始处理它。脚本提供了许多简单的工具,它们会帮助我们查看我们所选择的内容。之后我们会深入谈到这些工具。首先,让我们思考一下该用什么方法来操作我们取到的文本。通常我们需要为ML算法提供从语料库提取的原始文本词汇(单词)。在其他情况下,将这些单词转成原始内容的各种变形也是有道理的。

具体来说,我们经常要将原始单词截断到它的词根。那么,什么是一个词根呢?英语单词有从原始单词延伸出的通用后缀。就拿单词”run”为例。有很多的扩展它的词 – “runner”,”runs”,”running”等,即对基本定义的进一步阐述。词干提取是从”runner”,”runs”以及“running”中去除所有和”run”不一致的部分的过程。请注意,在上述列表中不包含”ran” —— 后面我们再对此进行阐述。下面是一个被提取词干的句子的具体实例。

我们已经丢失了”吉姆在跑步”这个信息,尽管此处的上下文隐含的所有其他信息都说不通。我们不可能完全扭转这一点 —— 我们可以猜测那里曾经是什么词,但我们很可能会弄错。

此处提供的代码可以让你对你的语料库进行词干提取。实际的词干提取是微不足道的,因为我们会使用NLTK来进行这部分工作。我们只需通过输入数组迭代,并返回使用NLTK Porter Stemmer所得到的各种提取后的词干变体。有许多不同的词干分析器可供选择,还包括非英语语言的选项。Porter Stemmer常用于英语。

词形还原类似于词干提取,但又有着重要的区别。与使用一系列简单的规则将一个单词截断成它的词根不同,词形还原尝试对输入的单词确定一个恰当的词根。本质上,词形还原试图找到一个单词的字典项,也称为单词的基本形(base term)。为了使这种查找能正确的工作,词形还原器必须知道您寻找的这个词在句子中的词性。生成语料库的词条与词干提取的代码基本上是相同的(尽管这段代码有上文略为提及的缺点,我们将在下面进一步对此进行讨论)。这里我们用了WordNetLemmatizer,它使用WordNet的数据库作为其查询指定词条的字典。

正如上文所述,词形还原知道单词的词性。NLTK WordNetLemmatizer天真地假设,所有传入的单词都是名词。这种假设意味着你必须告诉词形还原器要传递的词不是一个名词,否则它会错误的地将其视为一个名词。这个行为,加上对未知的单词(特别是当它混在一段文本中的时候)不做任何处理直接输出的行为,使得词形还原器处理效果很差。举例来说,如果让词形还原器处理”ran”这个词,在不指出”ran”属于一段文本的情况下,它将直接输出”ran”。它不知道的作为名词的”ran”,因为很明显”ran”不是一个名词。但是,如果你正确地指出”ran”是动词,那么词形还原器就能输出”run”。与相对,此处词干分析器就会输出”ran”。因此,如果我们要有效地利用词形还原器,我们也必须付出在源代码中对词性进行标注的代价,我们将在后面的课程中对词性标注的部分进行讨论。标记单词词性的额外成本也是词形还原器不像词干分析器那样应用广泛的原因之一 —— 所添加的功能抵不上所花的成本。

词汇量

现在,使用词干提取或词形还原的方法,我们已经拉取了一个语料库并且(视情况)对它做了变形,终于可以开始查看它的内容了。下面不是一个详尽的清单,但作为审查文本的技术参考。有些是立刻会用到的,其他则会在以后讨论到。

第一项测量是最简单的——词汇计数。这个指标是语料库内所有唯一字的计数。正如你所期望的,代码很容易实现。唯一一个你之后还会再遇到的技巧,是我们决定使用Python里dictionary的唯一性。即任一字典的条目在字典中不能出现超过一次。

这种方法可以让我们对我们的数据有所认知。思考我们使用词干提取及词形还原来考察ABC语料库后的如下输出。

首先是原始语料文本:

其次是词形还原后的语料库:

最后,是词干提取后的语料库:

可以看到,从原始数据到词形还原到词干提取后,语料库中唯一字计数值总体在减少,从31K至28K到22K。这个模式重复于每个语料库。在每个实例中,原始语料库的字数统计大于词干提取后的,而词干提取后的字数统计则大于词形还原后的。

上面的图表是使用我们共享工程的Python代码生成。它对非定制语料库列表进行遍历,并分别计算原始、词干提取后、词形还原后的唯一字数量。你可以用命令行重现这个图表。你还可以得到一份同样内容的文本转储。

词项存在

加入一点复杂性,我们接下来看看如何输出上文所指出的词项。我们生成一个CSV文件,它包含了出现在语料中的每个唯一字。它使用了上文中collect_unique_terms这个方法,只是不同于仅仅简单地输出唯一字计数,它通过遍历返回的字典会打印出每个键值。

虽然在仅仅输出一个单词的情况下,它可能看起来意义非常有限,但也有比统计每个词的总计数更好的算法(我们接下来将要将讨论)。正如对微博(tweets)的情感分析一样——在处理较短的文字序列时,我们更倾向于选择它。

您可以在命令行中使用所提供的代码生成CSV。

词频

词频是词项存在的延伸。不是简单地指出一个词的存在,而是在确定词频的时候,我们更关心语料库中的每个词所出现的实例个数。计算这个的代码与确定词项存在的代码是非常相似的。

词频,或者我们将在下一节中看到的基于它的变形,是机器学习的矢量化过程中常见的主要组成部分。一般来说,ML算法需要一组能代表需要判定的单个样本的特征集。但是,文本并不能自动地适应这种模式。要迫使它去适应,我们不能考虑文本本身,而是要看文字实例的数量。词频是将文本域映射到ML友好的实数域一个简单的方法。

您可以在命令行中使用提供的源代码生成包含所有词条和其频率的有序列表的CSV文件以及上面的图表。

记录标准化词频

该图显示了语料库中三个最常见词条和最不常见词条的原始频率。这里提供的代码可以为用户选择的语料库生成上述图表。此外。运行该脚本还会生成一个名为term_frequencies.csv的文件,它能让用户看到一个包含文档中的所有唯一字及其相应词频的电子表格。使用美国总统就职演说来生成就是:

机器学习算法在特征值没有规范到相似的尺度内的时候常常就不工作了。在计算词频的情况下,相对于罕见的单词来说,常用字可能会出现得非常频繁。这将造成这些组完全不同的频率字之间的显著歪斜。即使算法能处理歪斜的特征量, 如果你认为出现率10倍以上的词条就重要10倍以上的话,那么特定的任务可能无法正常工作。收缩特征值大小,同时还允许收缩后的特征值随着原始数据的增长而增长,一种常见的方法是取该特征值的对数。在此实例中,我们使用下面的方程来对词频数据进行归一:

使用对数以10为底意味着对于每10倍增加的词频,我们将看到对数归一后的词频的一个数量点的增长。我们将对数归一后的词频初始化为1,这样一来,对于词频是0的词来说值刚好也是1。用来计算归一化后的词频的代码非常简单,并依赖于前一节中提到的频率采集器。请注意,此代码同时转储输出到一个CSV文件。上文其他的示例代码没有这一步,因为它们其实是那些最终去转储CSV方法的辅助方法。这段代码恰好是在转储到CSV前做了少量工作(计算对数归一化)。

下面的图表和上节”词频”中使用的是相同的数据。它显示了就职演说语料库中的三个最常用的词和三个不最常用的词。令人感兴趣的是词频值的压缩。在语料库中出现频率约2000倍以上的词,它的对数归一化版本比文档中只出现一次得分为1(固有地、不公地、疏远地)的词得分只略微高了一点。出现频率超过8000倍的时候,它的分数也只增长了5倍而已。这种压缩用于将文本特征尺寸保持在一个相对小的数值范围内。

类似前面的例子,此图也可以直接使用所提供的代码生成。

词频频率

不,这不是一个打字错误。词频频率与我们到目前为止讨论的指标稍有不同。这些信息不太可能被直接当作一个ML算法的特征值来用。然而,它可以为检查语料库结构的人提供很多信息。本质上,词频频率对给定的频率的词项进行计数。说再多也比不上直接举一个例子。如果你运行下面的命令行:

它会生成一个名为frequency_frequencies.csv的CSV文件,以及下面的图表。

可以看到,它计算了一个单词被使用特定(大约4200次)次数的计数(1次)。这是一种奇怪的指标,但它多少可以给你这个语料库是否都是由很少使用的单词所组成的一个大致印象。在这个例子中,整个语料库包含145735个字。例如,让我们认为出现四次或更少次数的单词就是罕见词,别的则是常用词。我们知道,这个语料库中有4122+1488+817+547=6,974个或约占总字数4.7%的罕见词。与此相比,国情咨文语料库中有9581,总字数为399822,或约占总字数2%的罕见词。这似乎暗示了就职演说与国情咨文相比有着更丰富的词汇量。这是有用的信息吗?可能有。取决于你想了解该文本的什么方面。

计算词频频率的代码是非常简单的。它和上文利用相同的词频数据。该方法通过遍历”词/词频”字典,并建立一个新的frequency_frequencies字典,累计对应频率的不同词个数。总而言之,我们对出现在每个频率的词条数进行计数。

总结

在这一课中,我们研究了一些基本指标和文本分析的一些基础模块。我们没有做任何的机器学习相关的事情。别担心,ML代码的干货即将到来。在我们进行到那里之前,我们要先了解基础的概率论和一些简单的语言建模技术。将本课程、概率论和一些语言建模技术的结合起来,能带领我们接触第一个真正的机器学习任务——朴素贝叶斯分类器。从那里开始,我们将接触一系列不同的ML相关的主题,所以暂时别离开,我们保证讨论ML代码前的主题也能引起你的兴趣。

最后,我们希望这个系列是可延展且有见地的。如果我们发现更多更加清晰的材料或新的有用的例子,我们将把它们添加进来。如果你觉得有什么是值得添加的,也请留言给我们。同样重要的是,如果读者发现任何我们弄错的地方,不管是代码或以其他方面,不要犹豫,马上告知我们。我们对此表示衷心的感谢。

1 1 收藏 评论

关于作者:霉霉

据说简介不能为空。 个人主页 · 我的文章 · 10

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部