Python自然语言处理入门

本文从概念和实际操作量方面,从零开始,介绍在Python中进行自然语言处理。文章较长,且是PDF格式。

作者案:本文是我最初发表在《ACM Crossroads》Volume 13,Issue 4 上的完整修订版。之所以修订是因为 Natural Language Toolkit(NLTK)改动较大。修订版代码兼容至最新版 NLTK(2013 年 9 月更新至 2.0.4 版)。尽管本文的代码一律经过测试,仍有可能出现一两个问题。如果你发现了问题,请向作者反映。如果你非用 0.7 版不可的话,请参考 这里。

1 缘起

本文试着向读者们介绍自然语言处理(Natural Language Processing)这一领域,通常简称为 NLP。然而,不同于一般只是描述 NLP 重要概念的文章,本文还借助 Python 来形象地说明。对于不熟悉 Python 的读者们,本文也提供了部分参考资料教你如何进行 Python 编程。

2 相关介绍

2.1 自然语言处理

自然语言处理广纳了众多技术,对自然或人类语言进行自动生成,处理与分析。虽然大部分 NLP 技术继承自语言学和人工智能,但同样受到诸如机器学习,计算统计学和认知科学这些相对新兴的学科影响。

在展示 NLP 技术的例子前,有必要介绍些非常基础的术语。请注意:为了让文章通俗易懂,这些定义在语言上就不一定考究。

  • 词例(Token):对输入文本做任何实际处理前,都需要将其分割成诸如词、标点符号、数字或纯字母数字(alphanumerics)等语言单元(linguistic units)。这些单元被称为词例。
  • 句子:由有序的词例序列组成。
  • 词例还原(Tokenization):将句子还原成所组成的词例。以分割型语言(segmented languages)英语为例,空格的存在使词例还原变得相对容易同时也索然无味。然而,对于汉语和阿拉伯语,因为没有清晰的边界,这项工作就稍显困难。另外,在某些非分割型语言(non-segmented languages)中,几乎所有的字符(characters)都能以单字(one-character)存在,但同样也可以组合在一起形成多字(multi-characterwords)形式。
  • 语料库:通常是由丰富句子组成的海量文本。
  • 词性标签(Part-of-speech (POS) Tag):任一单词都能被归入到至少一类词汇集(set of lexical)或词性条目(part-of-speech categories)中,例如:名词、动词、形容词和冠词等。词性标签用符号来代表一种词汇条目——NN(名词)、VB(动词)、JJ(形容词)和 AT(冠词)。Brown Corpus 是最悠久,也是最常用的标注集之一。详情且听下回分解。
  • 剖析树(Parse Tree):利用形式语法(formal grammar)的定义,可以用树状图来表示给定句子的句法(syntactic)结构。

认识了基本的术语,下面让我们了解 NLP 常见的任务:

  • 词性标注(POS Tagging):给定一个句子和组词性标签,常见的语言处理就是对句子中的每个词进行标注。举个例子,The ball is red,词性标注后将变成 The/AT ball/NN is/VB red/JJ。最先进的词性标注器[9]准确率高达 96%。文本的词性标注对于更复杂的 NLP 问题,例如我们后面会讨论到的句法分析(parsing)机器翻译(machine translation)非常必要。
  • 计算形态学(Computational Morphology):大量建立在“语素”(morphemes/stems)基础上的词组成了自然语言,语素虽然是最小的语言单元,却富含意义。计算形态学所关心的是用计算机发掘和分析词的内部结构。
  • 句法分析(Parsing):在语法分析的问题中,句法分析器(parser)将给定句子构造成剖析树。为了分析语法,某些分析器假定一系列语法规则存在,但目前的解析器已经足够机智地借助复杂的统计模型[1]直接推断分析树。多数分析器能够在监督式设置(supervised setting)下操作并且句子已经被词性标注过了。统计句法分析是自然语言处理中非常活跃的研究领域。
  • 机器翻译(Machine Translation(MT)):机器翻译的目的是让计算机在没有人工干预的情况下,将给定某种语言的文本流畅地翻译成另一种语言文本。这是自然语言处理中最艰巨的任务之一,这些年来已经用许多不同的方式解决。几乎所有的机器翻译方法都依赖了词性标注和句法分析作为预处理。

2.2 Python

Python 是一种动态类型(dynamically-typed),面向对象的解释式(interpreted)编程语言。虽然它的主要优势在于允许编程人员快速开发项目,但是大量的标准库使它依然能适应大规模产品级工程项目。Python 的学习曲线非常陡峭并且有许多优秀的在线学习资源[11]。

2.3 自然语言工具集(Natural Language Toolkit)

尽管 Python 绝大部分的功能能够解决简单的 NLP 任务,但不足以处理标准的自然语言处理任务。这就是 NLTK (自然语言处理工具集)诞生的原因。NLTK 集成了模块和语料,以开源许可发布,允许学生对自然语言处理研究学习和生产研究。使用 NLTK 最大的优势是集成化(entirely self-contained),不仅提供了方便的函数和封装用于建立常见自然语言处理任务块,而且提供原始和预处理的标准语料库版本应用在自然语言处理的文献和课程中。

3 使用 NLTK

NLTK 官网提供了很棒的说明文件和教程进行学习指导[13]。单纯复述那些作者们的文字对于他们和本文都不公平。因此我会通过处理四个难度系数依次上升的 NLP 任务来介绍 NLTK。这些任务都来自于 NLTK 教程中没有给出答案的练习或者变化过。所以每个任务的解决办法和分析都是本文原创的。

3.1 NLTK 语料库

正如前文所说,NLTK 囊括数个在 NLP 研究圈里广泛使用的实用语料库。在本节中,我们来看看三个下文会用到的语料库:

  • 布朗语料库(Brown Corpus):Brown Corpus of Standard American English 被认为是第一个可以在计算语言学处理[6]中使用的通用英语语料库。它包含了一百万字 1961 年出版的美语文本。它代表了通用英语的样本,采样自小说,新闻和宗教文本。随后,在大量的人工标注后,诞生了词性标注过的版本。
  • 古登堡语料库(Gutenberg Corpus):古登堡语料库从最大的在线免费电子书[5]平台 古登堡计划(Gutenberg Project) 中选择了 14 个文本,整个语料库包含了一百七十万字。
  • Stopwords Corpus:除了常规的文本文字,另一类诸如介词,补语,限定词等含有重要的语法功能,自身却没有什么含义的词被称为停用词(stop words)。NLTK 所收集的停用词语料库(Stopwords Corpus)包含了 来自 11 种不同语言(包括英语)的 2400 个停用词。

3.2 NLTK 命名约定

在开始利用 NLTK 处理我们的任务以前,我们先来熟悉一下它的命名约定(naming conventions)。最顶层的包(package)是 nltk,我们通过使用完全限定(fully qualified)的加点名称例如:nltk.corpus and nltk.utilities 来引用它的内置模块。任何模块都能利用 Python 的标准结构 from . . . import . . . 来导入顶层的命名空间。

3.3 任务 1 : 探索语料库

上文提到,NLTK 含有多个 NLP 语料库。我们把这个任务制定为探索其中某个语料库。

任务:用 NLTK 的 corpus 模块读取包含在古登堡语料库的 austen-persuasion.txt,回答以下问题:

  • 这个语料库一共有多少字?
  • 这个语料库有多少个唯一单词(unique words)?
  • 前 10 个频率最高的词出现了几次?

利用 corpus 模块可以探索内置的语料库,而且 NLTK 还提供了包含多个好用的类和函数在概率模块中,可以用来计算任务中的概率分布。其中一个是 FreqDist,它可以跟踪分布中的采样频率(sample frequencies)。清单1 演示了如何使用这两个模块来处理第一个任务。

清单 1: NLTK 内置语料库的探索.

12345678910111213141516171819202122232425262728293031 # 导入 gutenberg 集>>> from nltk.corpus import gutenberg# 都有些什么语料在这个集合里?>>> print gutenberg.fileids()[\’austen-emma.txt\’, \’austen-persuasion.txt\’, \’austen-sense.txt\’, \’bible-kjv.txt\’, \’blake-poems.txt\’, \’bryant-stories.txt\’, \’burgess-busterbrown.txt\’, \’carroll-alice.txt\’, \’chesterton-ball.txt\’, \’chesterton-brown.txt\’, \’chesterton-thursday.txt\’, \’edgeworth-parents.txt\’, \’melville-moby_dick.txt\’, \’milton-paradise.txt\’, \’shakespeare-caesar.txt\’, \’shakespeare-hamlet.txt\’, \’shakespeare-macbeth.txt\’, \’whitman-leaves.txt\’] # 导入 FreqDist 类>>> from nltk import FreqDist# 频率分布实例化>>> fd = FreqDist()# 统计文本中的词例>>> for word in gutenberg.words(\’austen-persuasion.txt\’):... fd.inc(word)...>>> print fd.N() # total number of samples98171>>> print fd.B() # number of bins or unique samples6132# 得到前 10 个按频率排序后的词>>> for word in fd.keys()[:10]:... print word, fd[word], 6750the 3120to 2775. 2741and 2739of 2564a 1529in 1346was 1330; 1290

解答:简奥斯丁的小说 Persuasion 总共包含 98171 字和 6141 个唯一单词。此外,最常见的词例是逗号,接着是单词the。事实上,这个任务最后一部分是最有趣的经验观察之一,完美说明了单词的出现现象。如果你对海量的语料库进行统计,将每个单词的出现次数和单词出现的频率由高到低记录在表中,我们可以直观地发现列表中词频和词序的关系。事实上,齐普夫(Zipf)证明了这个关系可以表达为数学表达式,例如:对于任意给定单词,$fr$ = $k$, $f$ 是词频,$r$ 是词的排列,或者是在排序后列表中的词序,而 $k$ 则是一个常数。所以,举个例子,第五高频的词应该比第十高频的词的出现次数要多两倍。在 NLP 文献中,以上的关系通常被称为“齐普夫定律(Zipf’s Law)”。

即使由齐普夫定律描述的数学关系不一定完全准确,但它依然对于人类语言中单词分布的刻画很有用——词序小的词很常出现,而稍微词序大一点的则较为少出现,词序非常大的词则几乎没有怎么出现。任务 1 最后一部分使用 NLTK 非常容易通过图形进行可视化,如 清单 1a 所示。相关的 log-log 关系,如图 1,可以很清晰地发现我们语料库中对应的扩展关系。

 

清单 1a: 使用 NLTK 对齐普夫定律进行作图

12345678910111213141516171819202122232425 >>> from nltk.corpus import gutenberg>>> from nltk import FreqDist# 作图需要 matplotlib(可以从 NLTK 下载页获得)>>> import matplotlib>>> import matplotlib.pyplot as plt# 统计 Gutenberg 中每个词例数量>>> fd = FreqDist()>>> for text in gutenberg.fileids():... for word in gutenberg.words(text):... fd.inc(word)# 初始化两个空列表来存放词序和词频>>> ranks = []>>> freqs = []# 生成每个词例的(词序,词频)点并且将其添加到相应列表中,# 注意循环中的 fd 会自动排序>>> for rank, word in enumerate(fd):... ranks.append(rank+1)... freqs.appendpan>+1)... freqs.append方面,从零开始,介绍在Python中进行自然语言处理。文章较长,且是PDF格式。

作者案:本文是我最初发表在《ACM Crossroads》Volume 13,Issue 4 上的完整修订版。之所以修订是因为 Natural Language Toolkit(NLTK)改动较大。修订版代码兼容至最新版 NLTK(2013 年 9 月更新至 2.0.4 版)。尽管本文的代码一律经过测试,仍有可能出现一两个问题。如果你发现了问题,请向作者反映。如果你非用 0.7 版不可的话,请参考 这里。

1 缘起

本文试着向读者们介绍自然语言处理(Natural Language Processing)这一领域,通常简称为 NLP。然而,不同于一般只是描述 NLP 重要概念的文章,本文还借助 Python 来形象地说明。对于不熟悉 Python 的读者们,本文也提供了部分参考资料教你如何进行 Python 编程。

2 相关介绍

2.1 自然语言处理

自然语言处理广纳了众多技术,对自然或人类语言进行自动生成,处理与分析。虽然大部分 NLP 技术继承自语言学和人工智能,但同样受到诸如机器学习,计算统计学和认知科学这些相对新兴的学科影响。

在展示 NLP 技术的例子前,有必要介绍些非常基础的术语。请注意:为了让文章通俗易懂,这些定义在语言上就不一定考究。

  • 词例(Token):对输入文本做任何实际处理前,都需要将其分割成诸如词、标点符号、数字或纯字母数字(alphanumerics)等语言单元(linguistic units)。这些单元被称为词例。
  • 句子:由有序的词例序列组成。
  • 词例还原(Tokenization):将句子还原成所组成的词例。以分割型语言(segmented languages)英语为例,空格的存在使词例还原变得相对容易同时也索然无味。然而,对于汉语和阿拉伯语,因为没有清晰的边界,这项工作就稍显困难。另外,在某些非分割型语言(non-segmented languages)中,几乎所有的字符(characters)都能以单字(one-character)存在,但同样也可以组合在一起形成多字(multi-characterwords)形式。
  • 语料库:通常是由丰富句子组成的海量文本。
  • 词性标签(Part-of-speech (POS) Tag):任一单词都能被归入到至少一类词汇集(set of lexical)或词性条目(part-of-speech categories)中,例如:名词、动词、形容词和冠词等。词性标签用符号来代表一种词汇条目——NN(名词)、VB(动词)、JJ(形容词)和 AT(冠词)。Brown Corpus 是最悠久,也是最常用的标注集之一。详情且听下回分解。
  • 剖析树(Parse Tree):利用形式语法(formal grammar)的定义,可以用树状图来表示给定句子的句法(syntactic)结构。

认识了基本的术语,下面让我们了解 NLP 常见的任务:

  • 词性标注(POS Tagging):给定一个句子和组词性标签,常见的语言处理就是对句子中的每个词进行标注。举个例子,The ball is red,词性标注后将变成 The/AT ball/NN is/VB red/JJ。最先进的词性标注器[9]准确率高达 96%。文本的词性标注对于更复杂的 NLP 问题,例如我们后面会讨论到的句法分析(parsing)机器翻译(machine translation)非常必要。
  • 计算形态学(Computational Morphology):大量建立在“语素”(morphemes/stems)基础上的词组成了自然语言,语素虽然是最小的语言单元,却富含意义。计算形态学所关心的是用计算机发掘和分析词的内部结构。
  • 句法分析(Parsing):在语法分析的问题中,句法分析器(parser)将给定句子构造成剖析树。为了分析语法,某些分析器假定一系列语法规则存在,但目前的解析器已经足够机智地借助复杂的统计模型[1]直接推断分析树。多数分析器能够在监督式设置(supervised setting)下操作并且句子已经被词性标注过了。统计句法分析是自然语言处理中非常活跃的研究领域。
  • 机器翻译(Machine Translation(MT)):机器翻译的目的是让计算机在没有人工干预的情况下,将给定某种语言的文本流畅地翻译成另一种语言文本。这是自然语言处理中最艰巨的任务之一,这些年来已经用许多不同的方式解决。几乎所有的机器翻译方法都依赖了词性标注和句法分析作为预处理。

2.2 Python

Python 是一种动态类型(dynamically-typed),面向对象的解释式(interpreted)编程语言。虽然它的主要优势在于允许编程人员快速开发项目,但是大量的标准库使它依然能适应大规模产品级工程项目。Python 的学习曲线非常陡峭并且有许多优秀的在线学习资源[11]。

2.3 自然语言工具集(Natural Language Toolkit)

尽管 Python 绝大部分的功能能够解决简单的 NLP 任务,但不足以处理标准的自然语言处理任务。这就是 NLTK (自然语言处理工具集)诞生的原因。NLTK 集成了模块和语料,以开源许可发布,允许学生对自然语言处理研究学习和生产研究。使用 NLTK 最大的优势是集成化(entirely self-contained),不仅提供了方便的函数和封装用于建立常见自然语言处理任务块,而且提供原始和预处理的标准语料库版本应用在自然语言处理的文献和课程中。

3 使用 NLTK

NLTK 官网提供了很棒的说明文件和教程进行学习指导[13]。单纯复述那些作者们的文字对于他们和本文都不公平。因此我会通过处理四个难度系数依次上升的 NLP 任务来介绍 NLTK。这些任务都来自于 NLTK 教程中没有给出答案的练习或者变化过。所以每个任务的解决办法和分析都是本文原创的。

3.1 NLTK 语料库

正如前文所说,NLTK 囊括数个在 NLP 研究圈里广泛使用的实用语料库。在本节中,我们来看看三个下文会用到的语料库:

  • 布朗语料库(Brown Corpus):Brown Corpus of Standard American English 被认为是第一个可以在计算语言学处理[6]中使用的通用英语语料库。它包含了一百万字 1961 年出版的美语文本。它代表了通用英语的样本,采样自小说,新闻和宗教文本。随后,在大量的人工标注后,诞生了词性标注过的版本。
  • 古登堡语料库(Gutenberg Corpus):古登堡语料库从最大的在线免费电子书[5]平台 古登堡计划(Gutenberg Project) 中选择了 14 个文本,整个语料库包含了一百七十万字。
  • Stopwords Corpus:除了常规的文本文字,另一类诸如介词,补语,限定词等含有重要的语法功能,自身却没有什么含义的词被称为停用词(stop words)。NLTK 所收集的停用词语料库(Stopwords Corpus)包含了 来自 11 种不同语言(包括英语)的 2400 个停用词。

3.2 NLTK 命名约定

在开始利用 NLTK 处理我们的任务以前,我们先来熟悉一下它的命名约定(naming conventions)。最顶层的包(package)是 nltk,我们通过使用完全限定(fully qualified)的加点名称例如:nltk.corpus and nltk.utilities 来引用它的内置模块。任何模块都能利用 Python 的标准结构 from . . . import . . . 来导入顶层的命名空间。

3.3 任务 1 : 探索语料库

上文提到,NLTK 含有多个 NLP 语料库。我们把这个任务制定为探索其中某个语料库。

任务:用 NLTK 的 corpus 模块读取包含在古登堡语料库的 austen-persuasion.txt,回答以下问题:

  • 这个语料库一共有多少字?
  • 这个语料库有多少个唯一单词(unique words)?
  • 前 10 个频率最高的词出现了几次?

利用 corpus 模块可以探索内置的语料库,而且 NLTK 还提供了包含多个好用的类和函数在概率模块中,可以用来计算任务中的概率分布。其中一个是 FreqDist,它可以跟踪分布中的采样频率(sample frequencies)。清单1 演示了如何使用这两个模块来处理第一个任务。

清单 1: NLTK 内置语料库的探索.

12345678910111213141516171819202122232425262728293031 # 导入 gutenberg 集>>> from nltk.corpus import gutenberg# 都有些什么语料在这个集合里?>>> print gutenberg.fileids()[\’austen-emma.txt\’, \’austen-persuasion.txt\’, \’austen-sense.txt\’, \’bible-kjv.txt\’, \’blake-poems.txt\’, \’bryant-stories.txt\’, \’burgess-busterbrown.txt\’, \’carroll-alice.txt\’, \’chesterton-ball.txt\’, \’chesterton-brown.txt\’, \’chesterton-thursday.txt\’, \’edgeworth-parents.txt\’, \’melville-moby_dick.txt\’, \’milton-paradise.txt\’, \’shakespeare-caesar.txt\’, \’shakespeare-hamlet.txt\’, \’shakespeare-macbeth.txt\’, \’whitman-leaves.txt\’] # 导入 FreqDist 类>>> from nltk import FreqDist# 频率分布实例化>>> fd = FreqDist()# 统计文本中的词例>>> for word in gutenberg.words(\’austen-persuasion.txt\’):... fd.inc(word)...>>> print fd.N() # total number of samples98171>>> print fd.B() # number of bins or unique samples6132# 得到前 10 个按频率排序后的词>>> for word in fd.keys()[:10]:... print word, fd[word], 6750the 3120to 2775. 2741and 2739of 2564a 1529in 1346was 1330; 1290

解答:简奥斯丁的小说 Persuasion 总共包含 98171 字和 6141 个唯一单词。此外,最常见的词例是逗号,接着是单词the。事实上,这个任务最后一部分是最有趣的经验观察之一,完美说明了单词的出现现象。如果你对海量的语料库进行统计,将每个单词的出现次数和单词出现的频率由高到低记录在表中,我们可以直观地发现列表中词频和词序的关系。事实上,齐普夫(Zipf)证明了这个关系可以表达为数学表达式,例如:对于任意给定单词,$fr$ = $k$, $f$ 是词频,$r$ 是词的排列,或者是在排序后列表中的词序,而 $k$ 则是一个常数。所以,举个例子,第五高频的词应该比第十高频的词的出现次数要多两倍。在 NLP 文献中,以上的关系通常被称为“齐普夫定律(Zipf’s Law)”。

即使由齐普夫定律描述的数学关系不一定完全准确,但它依然对于人类语言中单词分布的刻画很有用——词序小的词很常出现,而稍微词序大一点的则较为少出现,词序非常大的词则几乎没有怎么出现。任务 1 最后一部分使用 NLTK 非常容易通过图形进行可视化,如 清单 1a 所示。相关的 log-log 关系,如图 1,可以很清晰地发现我们语料库中对应的扩展关系。

 

清单 1a: 使用 NLTK 对齐普夫定律进行作图

12345678910111213141516171819202122232425 >>> from nltk.corpus import gutenberg>>> from nltk import FreqDist# 作图需要 matplotlib(可以从 NLTK 下载页获得)>>> import matplotlib>>> import matplotlib.pyplot as plt# 统计 Gutenberg 中每个词例数量>>> fd = FreqDist()>>> for text in gutenberg.fileids():... for word in gutenberg.words(