赞
踩
本文从概念和实际操作量方面,从零开始,介绍在Python中进行自然语言处理。文章较长,且是PDF格式。
(作者案:本文是我最初发表在《ACM Crossroads》Volume 13,Issue 4 上的完整修订版。之所以修订是因为 Natural Language Toolkit(NLTK)改动较大。修订版代码兼容至最新版 NLTK(2013 年 9 月更新至 2.0.4 版)。尽管本文的代码一律经过测试,仍有可能出现一两个问题。如果你发现了问题,请向作者反映。如果你非用 0.7 版不可的话,请参考 这里。
本文试着向读者们介绍自然语言处理(Natural Language Processing)这一领域,通常简称为 NLP。然而,不同于一般只是描述 NLP 重要概念的文章,本文还借助 Python 来形象地说明。对于不熟悉 Python 的读者们,本文也提供了部分参考资料教你如何进行 Python 编程。
自然语言处理广纳了众多技术,对自然或人类语言进行自动生成,处理与分析。虽然大部分 NLP 技术继承自语言学和人工智能,但同样受到诸如机器学习,计算统计学和认知科学这些相对新兴的学科影响。
在展示 NLP 技术的例子前,有必要介绍些非常基础的术语。请注意:为了让文章通俗易懂,这些定义在语言上就不一定考究。
认识了基本的术语,下面让我们了解 NLP 常见的任务:
Python 是一种动态类型(dynamically-typed),面向对象的解释式(interpreted)编程语言。虽然它的主要优势在于允许编程人员快速开发项目,但是大量的标准库使它依然能适应大规模产品级工程项目。Python 的学习曲线非常陡峭并且有许多优秀的在线学习资源[11]。
尽管 Python 绝大部分的功能能够解决简单的 NLP 任务,但不足以处理标准的自然语言处理任务。这就是 NLTK (自然语言处理工具集)诞生的原因。NLTK 集成了模块和语料,以开源许可发布,允许学生对自然语言处理研究学习和生产研究。使用 NLTK 最大的优势是集成化(entirely self-contained),不仅提供了方便的函数和封装用于建立常见自然语言处理任务块,而且提供原始和预处理的标准语料库版本应用在自然语言处理的文献和课程中。
NLTK 官网提供了很棒的说明文件和教程进行学习指导[13]。单纯复述那些作者们的文字对于他们和本文都不公平。因此我会通过处理四个难度系数依次上升的 NLP 任务来介绍 NLTK。这些任务都来自于 NLTK 教程中没有给出答案的练习或者变化过。所以每个任务的解决办法和分析都是本文原创的。
正如前文所说,NLTK 囊括数个在 NLP 研究圈里广泛使用的实用语料库。在本节中,我们来看看三个下文会用到的语料库:
在开始利用 NLTK 处理我们的任务以前,我们先来熟悉一下它的命名约定(naming conventions)。最顶层的包(package)是 nltk,我们通过使用完全限定(fully qualified)的加点名称例如:nltk.corpus and nltk.utilities 来引用它的内置模块。任何模块都能利用 Python 的标准结构 from . . . import . . .
来导入顶层的命名空间。
上文提到,NLTK 含有多个 NLP 语料库。我们把这个任务制定为探索其中某个语料库。
任务:用 NLTK 的 corpus 模块读取包含在古登堡语料库的 austen-persuasion.txt,回答以下问题:
利用 corpus 模块可以探索内置的语料库,而且 NLTK 还提供了包含多个好用的类和函数在概率模块中,可以用来计算任务中的概率分布。其中一个是 FreqDist,它可以跟踪分布中的采样频率(sample frequencies)。清单1 演示了如何使用这两个模块来处理第一个任务。
清单 1: NLTK 内置语料库的探索.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
# 导入 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 samples
98171
>>>
print
fd
.
B
(
)
# number of bins or unique samples
6132
# 得到前 10 个按频率排序后的词
>>>
for
word
in
fd
.
keys
(
)
[
:
10
]
:
.
.
.
print
word
,
fd
[
word
]
,
6750
the
3120
to
2775
.
2741
and
2739
of
2564
a
1529
in
1346
was
1330
;
1290
|
解答:简奥斯丁的小说 Persuasion 总共包含 98171 字和 6141 个唯一单词。此外,最常见的词例是逗号,接着是单词the。事实上,这个任务最后一部分是最有趣的经验观察之一,完美说明了单词的出现现象。如果你对海量的语料库进行统计,将每个单词的出现次数和单词出现的频率由高到低记录在表中,我们可以直观地发现列表中词频和词序的关系。事实上,齐普夫(Zipf)证明了这个关系可以表达为数学表达式,例如:对于任意给定单词,$fr$ = $k$, $f$ 是词频,$r$ 是词的排列,或者是在排序后列表中的词序,而 $k$ 则是一个常数。所以,举个例子,第五高频的词应该比第十高频的词的出现次数要多两倍。在 NLP 文献中,以上的关系通常被称为“齐普夫定律(Zipf’s Law)”。
即使由齐普夫定律描述的数学关系不一定完全准确,但它依然对于人类语言中单词分布的刻画很有用——词序小的词很常出现,而稍微词序大一点的则较为少出现,词序非常大的词则几乎没有怎么出现。任务 1 最后一部分使用 NLTK 非常容易通过图形进行可视化,如 清单 1a 所示。相关的 log-log 关系,如图 1,可以很清晰地发现我们语料库中对应的扩展关系。
清单 1a: 使用 NLTK 对齐普夫定律进行作图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
>>>
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
.
append
(
fd
[
word
]
)
.
.
.
# 在 log-log 图中展示词序和词频的关系
>>>
plt
.
loglog
(
ranks
,
freqs
)
>>>
plt
.
xlabel
(’
frequency
(
f
)’
,
fontsize
=
14
,
fontweight
=’
bold’
)
>>>
plt
.
ylabel
(’
rank
(
r
)’
,
fontsize
=
14
,
fontweight
=’
bold’
)
>>>
plt
.
grid
(
True
)
>>>
plt
.
show
(
)
|
图 1: 齐普夫定律在古登堡语料库中适用吗?
现在我们已经探索过语料库了,让我们定义一个任务,能够用上之前探索的结果。
任务:训练和创建一个单词预测器,例如:给定一个训练过语料库,写一个能够预测给定单词的一下个单词的程序。使用这个预测器随机生成一个 20 个词的句子。
要创建单词预测器,我们首先要在训练过的语料库中计算两个词的顺序分布,例如,我们需要累加给定单词接下来这个单词的出现次数。一旦我们计算出了分布,我们就可以通过输入一个单词,得到它在语料库中所有可能出现的下一个单词列表,并且可以从列表中随机输出一个单词。为了随机生成一个 20 个单词的句子,我只需要给定一个初始单词,利用预测器来预测下一个单词,然后重复操作指导直到句子满 20 个词。清单 2 描述了怎么利用 NLTK 提供的模块来简单实现。我们利用简奥斯丁的 Persuasion 作为训练语料库。
清单 2:利用 NLTK 预测单词
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
>>>
from
nltk
.
corpus
import
gutenberg
>>>
from
nltk
import
ConditionalFreqDist
>>>
from
random
import
choice
# 分布实例化
>>>
cfd
=
ConditionalFreqDist
(
)
# 对于每个实例,统计给定词的下一个词数量
>>>
prev_word
=
None
>>>
for
word
in
gutenberg
.
words
(’
austen
-
persuasion
.
txt’
)
:
.
.
.
cfd
[
prev_word
]
.
inc
(
word
)
.
.
.
prev_word
=
word
# 给定“therefore”作为给定词作为预测器的初始词
>>>
word
=
’
therefore’
>>>
i
=
1
# 找到给定词的所有下一个可能的词,并随机选择一个
>>>
while
i
<
20
:
.
.
.
print
word
,
.
.
.
lwords
=
cfd
[
word
]
.
samples
(
)
.
.
.
follower
=
choice
(
lwords
)
.
.
.
word
=
follower
.
.
.
i
+=
1
.
.
.
therefore
it
known
of
women
ought
.
Leave
me
so
well
placed
in
five
altogether
well
placed
themselves
delighted
|
解答:输出的 20 个单词的句子当然不合语法。但就词的角度两两来看,是合语法的,因为用以估计条件分布概率(conditional frequency distribution)的训练语料库是合乎语法的,而我们正是使用了这个条件分布概率。注意在本任务中,我们使用前一个词作为预测器的上下文提示。显然也可以使用前两个,甚至前三个词。
NLTK 结合了一系列优秀的模块允许我们训练和构建相对复杂的词性标注器。然而,对于这次的任务,我们只限于对 NTLK 内置的已经标注过的语料库进行简单分析。
任务:对内置的布朗语料库分词(Tokenize)并创建一个或多个适合的数据结构能让我们回答以下问题:
对于这个任务,一定要注意 NTLK 内置了两个版本的布朗语料库:第一个我们已经在前两个任务中使用了,是原始的版本,第二个是被标注过的版本,亦即是每个句子的每个词例都被正确地词性标注过了。这一版的每个句子保存在元素为二元元组的列表中,形如: (token,tag)。例如标注过的语料库中的一个句子 the ball is green,在 NLTK 会被表示为 [(’the’,’at’), (’ball’,’nn’), (’is’,’vbz’), (’green’,’jj’)]。
前面已经解释过了,布朗语料库包含 15 个不同的部分,用单词 “a” 到 “r” 来表示。每个部分代表不同的文本类型,这样分是很有必要的,但这不在本文讨论范围内。有了这个信息,我们必须要构建数据结构来分析这个标注过的语料库。思考我们需要解决的问题,我们要运用文本中的词例来发现词性标准的频率分布和词性标签的条件频率分布。注意 NLTK 同时也允许我们直接从顶层命名空间导入 FreqDist 和 ConditionalFreqDist 类。清单 3 演示了怎样在 NLTK 使用。
清单 3: 借助 NLTK 分析标注过的语料库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
>>>
from
nltk
.
corpus
import
brown
>>>
from
nltk
import
FreqDist
,
ConditionalFreqDist
>>>
fd
=
FreqDist
(
)
>>>
cfd
=
ConditionalFreqDist
(
)
# 对于语料库中每个标注过的句子,以(词例,词性标签)对形式表示并统计词性标签和词例的词性标签
>>>
for
sentence
in
brown
.
tagged_sents
(
)
:
.
.
.
for
(
token
,
tag
)
in
sentence
:
.
.
.
fd
.
inc
(
tag
)
.
.
.
cfd
[
token
]
.
inc
(
tag
)
>>>
fd
.
max
(
)
# 频率最高的词性标签是 ...
’
NN’
>>>
wordbins
=
[
]
# Initialize a list to hold (numtags,word) tuple
# 添加每个(词例的唯一词性标签,词例)元组到列表中
>>>
for
token
in
cfd
.
conditions
(
)
:
.
.
.
wordbins
.
append
(
(
cfd
[
token
]
.
B
(
)
,
token
)
)
.
.
.
# 按唯一词性标签数从高到低对元组进行排序
>>>
wordbins
.
sort
(
reverse
=
True
)
>>>
print
wordbins
[
0
]
# 标签最多的词例是 ...
(
12
,
’
that’
)
>>>
male
=
[’
he’
,’
his’
,’
him’
,’
himself’
]
# 男性代词
>>>
female
=
[’
she’
,’
hers’
,’
her’
,’
herself’
]
# 女性代词
>>>
n_male
,
n_female
=
0
,
0
# 初始化计数器
# 男性代词总计:
>>>
for
m
in
male
:
.
.
.
n_male
+=
cfd
[
m
]
.
N
(
)
.
.
.
# 女性代词总计:
>>>
for
f
in
female
:
.
.
.
n_female
+=
cfd
[
f
]
.
N
(
)
.
.
.
>>>
print
float
(
n_male
)
/
n_female
# 计算比率
3.257
>>>
n_ambiguous
=
0
>>>
for
(
ntags
,
token
)
in
wordbins
:
.
.
.
if
ntags
>
1
:
.
.
.
n_ambiguous
+=
1
.
.
.
>>>
n_ambiguous
# 词性标签归属多于一个的词例数
8729
|
解答:在布朗语料库中最高频的词性标签理所当然是名词(NN)。含有最多词性标签的是词是 that。语料库中的男性代词使用率差不多是女性代词的三倍。最后,语料库中有 8700 多的词仍有歧义——这个数字可以看出词性标注任务的困难程度。
自由单词联想是神经语言学(Psycholinguistics)常见的任务,尤其是在词汇检索(lexical retrieval)的语境下——对于人类受试者(human subjects)而言,在单词联想上,更倾向于选择有高度联想性词,而非完全无关的词。这说明单词联想的处理是相当直接的——受试者在听到一个特殊的词时要马上从心里泛起另一个词。
任务:利用大规模词性标注过的语料库来实现自由单词联想。忽略功能词(function words),假设联想词都是名词。
对于这个任务而言,需要用到“词共现”(word co-occurrences)这一概念,例如:统计彼此间最接近的单词出现次数,然后藉此估算出联想度。对于句子中的每个词例,我们将其观察规定范围内接下来所有的词并且利用条件频率分布统计它们在该语境的出现率。清单 4演示了我们怎么用 Python 和 NLTK 对规定在 5 个单词的范围内的词性标注过的布朗语料库进行处理。
Listing 4: 利用 NLTK 实现单词联想
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
>>>
from
nltk
.
corpus
import
brown
,
stopwords
>>>
from
nltk
import
ConditionalFreqDist
>>>
cfd
=
ConditionalFreqDist
(
)
# 得到英文停用词表
>>>
stopwords_list
=
stopwords
.
words
(’
english’
)
# 定义一个函数,如果属于名词类则返回true
>>>
def
is_noun
(
tag
)
:
.
.
.
return
tag
.
lower
(
)
in
[’
nn’
,’
nns’
,’
nn
$’
,’
nn
-
tl’
,’
nn
+
bez’
,
’
nn
+
hvz’
,
’
nns
$’
,’
np’
,’
np
$’
,’
np
+
bez’
,’
nps’
,
’
nps
$’
,’
nr’
,’
np
-
tl’
,’
nrs’
,’
nr
$’
]
.
.
.
# 统计前 5 个单词的出现次数
>>>
for
sentence
in
brown
.
tagged_sents
(
)
:
.
.
.
for
(
index
,
tagtuple
)
in
enumerate
(
sentence
)
:
.
.
.
(
token
,
tag
)
=
tagtuple
.
.
.
token
=
token
.
lower
(
)
.
.
.
if
token
not
in
stopwords_list
and
is_noun
(
tag
)
:
.
.
.
window
=
sentence
[
index
+
1
:
index
+
5
]
.
.
.
for
(
window_token
,
window_tag
)
in
window
:
.
.
.
window_token
=
window_token
.
lower
(
)
.
.
.
if
window_token
not
in
stopwords_list
and
is_noun
(
window_tag
)
:
.
.
.
cfd
[
token
]
.
inc
(
window_token
)
# 好了。我们完成了!让我们开始进行联想!
>>>
print
cfd
[’
left’
]
.
max
(
)
right
>>>
print
cfd
[’
life’
]
.
max
(
)
death
>>>
print
cfd
[’
man’
]
.
max
(
)
woman
>>>
print
cfd
[’
woman’
]
.
max
(
)
world
>>>
print
cfd
[’
boy’
]
.
max
(
)
girl
>>>
print
cfd
[’
girl’
]
.
max
(
)
trouble
>>>
print
cfd
[’
male’
]
.
max
(
)
female
>>>
print
cfd
[’
ball’
]
.
max
(
)
player
>>>
print
cfd
[’
doctor’
]
.
max
(
)
bills
>>>
print
cfd
[’
road’
]
.
max
(
)
block
|
解答:我们构建的“单词联想器(word associator)”效果似乎出乎意料得好,尤其是在这么小的工作量下(事实上,在大众心理学的的语境下,我们的联想器似乎具备人类的特性,尽管是消极和悲观的)。我们的任务结果明确指出了通用语料库语言学的有效性。作为进一步的练习,联想器借助句法分析过的语料库和基于信息理论的联想测试能够很容易进行成熟的拓展[3]。
4 讨论
虽然本文使用了 Python 和 NLTK 作为基础 NLP 的介绍,但请注意,在 NLTK 之外还有另外的 NLP 框架活跃于学术界和工业界。其中比较流行的是 GATE(General Architecture for Text Engineering),由 University of Sheffield[4] 的 NLP 研究小组开发。使用 Java 编写的视窗环境,基础架构描述了语言处理部件彼此间的联系。GATE 提供免费下载,主要应用于文本挖掘(text mining)和信息抽取(information extraction)。
每种编程语言和框架都各有优劣。就本文而言,我们选择 Python 是因为与其他语言相比,它的优势在于:(a)可读性高(b)面相对象的范例(object-oriented paradigm)易于入门(c)易于扩展(d)强大的解码支持(e)强大的标准库。而且特别稳健,高效,这些都已经在复杂和大规模的 NLP 项目中得到了验证,例如最新的机器翻译解码器[2]。
自然语言处理是非常热门的研究领域因此每年吸引了非常多研究生。它集合了多个学科诸如语言学,心理学,计算科学和数学的优势来研究人类语言。另外选择 NLP 作为研究生生涯更重要的原因是大量有意思的难题都没有固定的解决办法。举个例子,机器翻译初始问题(original problem)的存在推动了该领域的发展,即使经过二十年诱人而又活跃的研究以后,这个难题依旧尚待解决。还有另外几个前沿的 NLP 问题目前已经有大量的研究工作,其中一些列举如下:
Python 和 NLTK 使每个编程人员不需要花费大量时间在获取资源上,直接可以接触 NLP 任务。文本意在给任何对学习 NLP 感兴趣的人提供解决这些简单的任务例子和参考。
Nitin Madnani 是 Educational Testing Service(ETS)研究科学家。他此前是 University of Maryland, College Park 计算机科学系的博士生和 Institute for Advanced Computer Studies 助理研究员。他的研究方向是统计自然语言处理,尤其是机器翻译和文本摘要。不论任务大小,他始终坚信:Python大法好。
[1]: Dan Bikel. 2004. On the Parameter Space of Generative Lexicalized Statistical Parsing Models. Ph.D. Thesis. http://www.cis.upenn.edu/~dbikel/papers/thesis.pdf
[2]: David Chiang. 2005. A hierarchical phrase-based model for statistical machine translation. Proceedings of ACL.
[3]: Kenneth W. Church and Patrick Hanks. 1990. Word association norms, mutual information, and lexicography. Computational Linguistics. 16(1).
[4:] H. Cunningham, D. Maynard, K. Bontcheva. and V. Tablan. 2002. GATE: A Framework and Graphical Development Environment for Robust NLP Tools and Applications. Proceedings of the 40th Anniversary Meeting of the Association for Computational Linguistics. 15
[5]: Michael Hart and Gregory Newby. Project Gutenberg. Proceedings of the 40th Anniversary Meeting of the Association for Computational Linguistics. http://www.gutenberg.org/wiki/Main_Page
[6]: H. Kucera and W. N. Francis. 1967. Computational Analysis of PresentDay American English. Brown University Press, Providence, RI.
[7]: Roger Levy and Christoper D. Manning. 2003. Is it harder to parse Chinese, or the Chinese Treebank ? Proceedings of ACL.
[8]: Dragomir R. Radev and Kathy McKeown. 1999. Generating natural language summaries from multiple on-line sources. Computational Linguistics. 24:469-500.
[9]: Adwait Ratnaparkhi 1996. A Maximum Entropy Part-Of-Speech Tagger. Proceedings of Empirical Methods on Natural Language Processing.
[10]: Dekai Wu and David Chiang. 2007. Syntax and Structure in Statistical Translation. Workshop at HLT-NAACL.
[11]: The Official Python Tutorial. http://docs.python.org/tut/tut.html
[12]: Natural Language Toolkit. http://nltk.sourceforge.net
[13]: NLTK Tutorial. http://nltk.sourceforge.net/index.php/Book
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。