提交 90b71336 编写于 作者: W wizardforcel

2020-11-21 10:56:46

上级 bcab51b8
......@@ -211,16 +211,16 @@ Note
《创世纪》有 44764 个词和标点符号或者叫“词符”。词符 表示一个我们想要整体对待的字符序列 —— 例如`hairy``his``:)`。当我们计数文本如 to be or not to be 这个短语中词符的个数时,我们计数这些序列出现的次数。因此,我们的例句中出现了 to 和 be 各两次,or 和 not 各一次。然而在例句中只有 4 个不同的词。《创世纪》中有多少不同的词?要用 Python 来回答这个问题,我们处理问题的方法将稍有改变。一个文本词汇表只是它用到的词符的 _ 集合 _,因为在集合中所有重复的元素都只算一个。Python 中我们可以使用命令:`set(text3)` 获得`text3` 的词汇表。当你这样做时,屏幕上的很多词会掠过。现在尝试以下操作:
```py
>>> sorted(set(text3)) ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> sorted(set(text3))
['!', "'", '(', ')', ',', ',)', '.', '.)', ':', ';', ';)', '?', '?)',
'A', 'Abel', 'Abelmizraim', 'Abidah', 'Abide', 'Abimael', 'Abimelech',
'Abr', 'Abrah', 'Abraham', 'Abram', 'Accad', 'Achbor', 'Adah', ...]
>>> len(set(text3)) ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> len(set(text3))
2789
>>>
```
`sorted()` 包裹起 Python 表达式`set(text3)` [![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#sorted-set),我们得到一个词汇项的排序表,这个表以各种标点符号开始,然后是以 A 开头的词汇。大写单词排在小写单词前面。我们通过求集合中元素的个数间接获得词汇表的大小,再次使用`len`来获得这个数值[![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#len-set)。尽管小说中有 44,764 个词符,但只有 2,789 个不同的单词或“词类型”。一个词类型是指一个词在一个文本中独一无二的出现形式或拼写 —— 也就是说,这个词在词汇表中是唯一的。我们计数的 2,789 个元素中包括标点符号,所以我们把这些叫做唯一元素类型而不是词类型。
`sorted()` 包裹起 Python 表达式`set(text3)` [](http://www.nltk.org/book/ch01.html#sorted-set),我们得到一个词汇项的排序表,这个表以各种标点符号开始,然后是以 A 开头的词汇。大写单词排在小写单词前面。我们通过求集合中元素的个数间接获得词汇表的大小,再次使用`len`来获得这个数值[](http://www.nltk.org/book/ch01.html#len-set)。尽管小说中有 44,764 个词符,但只有 2,789 个不同的单词或“词类型”。一个词类型是指一个词在一个文本中独一无二的出现形式或拼写 —— 也就是说,这个词在词汇表中是唯一的。我们计数的 2,789 个元素中包括标点符号,所以我们把这些叫做唯一元素类型而不是词类型。
现在,让我们对文本词汇丰富度进行测量。下一个例子向我们展示,不同的单词数目只是单词总数的 6%,或者每个单词平均被使用了 16 次(记住,如果你使用的是 Python 2,请在开始输入`from __future__ import division`)。
......@@ -247,10 +247,10 @@ Note
你也许想要对几个文本重复这些计算,但重新输入公式是乏味的。你可以自己命名一个任务,如“lexical_diversity”或“percentage”,然后用一个代码块关联它。现在,你只需输入一个很短的名字就可以代替一行或多行 Python 代码,而且你想用多少次就用多少次。执行一个任务的代码段叫做一个函数,我们使用关键字`def` 给函数定义一个简短的名字。下面的例子演示如何定义两个新的函数,`lexical_diversity()``percentage()`
```py
>>> def lexical_diversity(text): ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
... return len(set(text)) / len(text) ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> def lexical_diversity(text):
... return len(set(text)) / len(text)
...
>>> def percentage(count, total): ![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
>>> def percentage(count, total):
... return 100 * count / total
...
```
......@@ -259,7 +259,7 @@ Note
当遇到第一行末尾的冒号后,Python 解释器提示符由`>>>` 变为`...``...`提示符表示 Python 期望在后面是一个缩进代码块 。缩进是输入四个空格还是敲击 Tab 键,这由你决定。要结束一个缩进代码段,只需输入一个空行。
`lexical_diversity()` [![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#fun-parameter1)的定义中,我们指定了一个`text` 参数。这个参数是我们想要计算词汇多样性的实际文本的一个“占位符”,并在用到这个函数的时候出现在将要运行的代码块中 [![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#locvar)。类似地,`percentage()` 定义了两个参数,`count``total` [![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)](http://www.nltk.org/book/ch01.html#fun-parameter2)。
`lexical_diversity()` [](http://www.nltk.org/book/ch01.html#fun-parameter1)的定义中,我们指定了一个`text` 参数。这个参数是我们想要计算词汇多样性的实际文本的一个“占位符”,并在用到这个函数的时候出现在将要运行的代码块中 [](http://www.nltk.org/book/ch01.html#locvar)。类似地,`percentage()` 定义了两个参数,`count``total` [](http://www.nltk.org/book/ch01.html#fun-parameter2)
只要 Python 知道了`lexical_diversity()``percentage()` 是指定代码段的名字,我们就可以继续使用这些函数:
......@@ -290,14 +290,14 @@ _Brown 语料库 _ 中各种文体的词汇多样性
>>>
```
在提示符后面,我们输入自己命名的`sent1`,后跟一个等号,然后是一些引用的词汇,中间用逗号分割并用括号包围。这个方括号内的东西在 Python 中叫做列表:它就是我们存储文本的方式。我们可以通过输入它的名字来查阅它[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#inspect-var)。我们可以查询它的长度[![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#len-sent)。我们甚至可以对它调用我们自己的函数`lexical_diversity()`[![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)](http://www.nltk.org/book/ch01.html#apply-function)。
在提示符后面,我们输入自己命名的`sent1`,后跟一个等号,然后是一些引用的词汇,中间用逗号分割并用括号包围。这个方括号内的东西在 Python 中叫做列表:它就是我们存储文本的方式。我们可以通过输入它的名字来查阅它[](http://www.nltk.org/book/ch01.html#inspect-var)。我们可以查询它的长度[](http://www.nltk.org/book/ch01.html#len-sent)。我们甚至可以对它调用我们自己的函数`lexical_diversity()`[](http://www.nltk.org/book/ch01.html#apply-function)
```py
>>> sent1 ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> sent1
['Call', 'me', 'Ishmael', '.']
>>> len(sent1) ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> len(sent1)
4
>>> lexical_diversity(sent1) ![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
>>> lexical_diversity(sent1)
1.0
>>>
```
......@@ -318,10 +318,10 @@ _Brown 语料库 _ 中各种文体的词汇多样性
**轮到你来:** 通过输入名字、等号和一个单词列表, 组建几个你自己的句子,如`ex1 = ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail']`。重复一些我们先前在第[1](http://www.nltk.org/book/ch01.html#sec-computing-with-language-texts-and-words) 节看到的其他 Python 操作,如:`sorted(ex1)`, `len(set(ex1))`, `ex1.count('the')`
令人惊喜的是,我们可以对列表使用 Python 加法运算。两个列表相加[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#list-plus-list)创造出一个新的列表,包括第一个列表的全部,后面跟着第二个列表的全部。
令人惊喜的是,我们可以对列表使用 Python 加法运算。两个列表相加[](http://www.nltk.org/book/ch01.html#list-plus-list)创造出一个新的列表,包括第一个列表的全部,后面跟着第二个列表的全部。
```py
>>> ['Monty', 'Python'] + ['and', 'the', 'Holy', 'Grail'] ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> ['Monty', 'Python'] + ['and', 'the', 'Holy', 'Grail']
['Monty', 'Python', 'and', 'the', 'Holy', 'Grail']
>>>
```
......@@ -426,12 +426,12 @@ IndexError: list index out of range
>>>
```
按照惯例,`m:n` 表示元素 m…n-1。正如下一个例子显示的那样,如果切片从列表第一个元素开始,我们可以省略第一个数字[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#slice2), 如果切片到列表最后一个元素处结尾,我们可以省略第二个数字 [![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#slice3):
按照惯例,`m:n` 表示元素 m…n-1。正如下一个例子显示的那样,如果切片从列表第一个元素开始,我们可以省略第一个数字[](http://www.nltk.org/book/ch01.html#slice2), 如果切片到列表最后一个元素处结尾,我们可以省略第二个数字 [](http://www.nltk.org/book/ch01.html#slice3)
```py
>>> sent[:3] ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> sent[:3]
['word1', 'word2', 'word3']
>>> text2[141525:] ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> text2[141525:]
['among', 'the', 'merits', 'and', 'the', 'happiness', 'of', 'Elinor', 'and', 'Marianne',
',', 'let', 'it', 'not', 'be', 'ranked', 'as', 'the', 'least', 'considerable', ',',
'that', 'though', 'sisters', ',', 'and', 'living', 'almost', 'within', 'sight', 'of',
......@@ -441,17 +441,17 @@ IndexError: list index out of range
>>>
```
我们可以通过赋值给它的索引值来修改列表中的元素。在接下来的例子中,我们把`sent[0]` 放在等号左侧[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#list-assignment)。我们也可以用新内容替换掉一整个片段[![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#slice-assignment)。最后一个尝试报错的原因是这个链表只有四个元素而要获取其后面的元素就产生了错误[![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)](http://www.nltk.org/book/ch01.html#list-error)。
我们可以通过赋值给它的索引值来修改列表中的元素。在接下来的例子中,我们把`sent[0]` 放在等号左侧[](http://www.nltk.org/book/ch01.html#list-assignment)。我们也可以用新内容替换掉一整个片段[](http://www.nltk.org/book/ch01.html#slice-assignment)。最后一个尝试报错的原因是这个链表只有四个元素而要获取其后面的元素就产生了错误[](http://www.nltk.org/book/ch01.html#list-error)
```py
>>> sent[0] = 'First' ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> sent[0] = 'First'
>>> sent[9] = 'Last'
>>> len(sent)
10
>>> sent[1:9] = ['Second', 'Third'] ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> sent[1:9] = ['Second', 'Third']
>>> sent
['First', 'Second', 'Third', 'Last']
>>> sent[9] ![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
>>> sent[9]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: list index out of range
......@@ -518,13 +518,13 @@ SyntaxError: invalid syntax
## 2.4 字符串
我们用来访问列表元素的一些方法也可以用在单独的词或字符串上。例如可以把一个字符串指定给一个变量[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#assign-string),索引一个字符串[![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#index-string),切片一个字符串[![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)](http://www.nltk.org/book/ch01.html#slice-string):
我们用来访问列表元素的一些方法也可以用在单独的词或字符串上。例如可以把一个字符串指定给一个变量[](http://www.nltk.org/book/ch01.html#assign-string),索引一个字符串[](http://www.nltk.org/book/ch01.html#index-string),切片一个字符串[](http://www.nltk.org/book/ch01.html#slice-string)
```py
>>> name = 'Monty' ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> name[0] ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> name = 'Monty'
>>> name[0]
'M'
>>> name[:4] ![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
>>> name[:4]
'Mont'
>>>
```
......@@ -580,10 +580,10 @@ what output do you expect here?
[3.1](http://www.nltk.org/book/ch01.html#fig-tally) 中的表被称为频率分布,它告诉我们在文本中的每一个词项的频率。(一般情况下,它能计数任何观察得到的事件。)这是一个“分布”因为它告诉我们文本中单词词符的总数是如何分布在词项中的。因为我们经常需要在语言处理中使用频率分布,NLTK 中内置了它们。让我们使用`FreqDist` 寻找 _《白鲸记》_ 中最常见的 50 个词:
```py
>>> fdist1 = FreqDist(text1) ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> print(fdist1) ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> fdist1 = FreqDist(text1)
>>> print(fdist1)
<FreqDist with 19317 samples and 260819 outcomes>
>>> fdist1.most_common(50) ![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
>>> fdist1.most_common(50)
[(',', 18713), ('the', 13721), ('.', 6862), ('of', 6536), ('and', 6024),
('a', 4569), ('to', 4542), (';', 4072), ('in', 3916), ('that', 2982),
("'", 2684), ('-', 2552), ('his', 2459), ('it', 2209), ('I', 2124),
......@@ -599,7 +599,7 @@ what output do you expect here?
>>>
```
第一次调用`FreqDist`时,传递文本的名称作为参数[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#freq-dist-call)。我们可以看到已经被计算出来的 _《白鲸记》_ 中的总的词数(“outcomes”)—— 260,819[![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#freq-dist-inspect)。表达式`most_common(50)` 给出文本中 50 个出现频率最高的单词类型[![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)](http://www.nltk.org/book/ch01.html#freq-dist-most-common)。
第一次调用`FreqDist`时,传递文本的名称作为参数[](http://www.nltk.org/book/ch01.html#freq-dist-call)。我们可以看到已经被计算出来的 _《白鲸记》_ 中的总的词数(“outcomes”)—— 260,819[](http://www.nltk.org/book/ch01.html#freq-dist-inspect)。表达式`most_common(50)` 给出文本中 50 个出现频率最高的单词类型[](http://www.nltk.org/book/ch01.html#freq-dist-most-common)
注意
......@@ -691,10 +691,10 @@ build
计数词汇是有用的,我们也可以计数其他东西。例如,我们可以查看文本中词长的分布,通过创造一长串数字的列表的`FreqDist`,其中每个数字是文本中对应词的长度:
```py
>>> [len(w) for w in text1] ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
>>> [len(w) for w in text1]
[1, 4, 4, 2, 6, 8, 4, 1, 9, 1, 1, 8, 2, 1, 4, 11, 5, 2, 1, 7, 6, 1, 3, 4, 5, 2, ...]
>>> fdist = FreqDist(len(w) for w in text1) ![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)
>>> print(fdist) ![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
>>> fdist = FreqDist(len(w) for w in text1)
>>> print(fdist)
<FreqDist with 19 samples and 260819 outcomes>
>>> fdist
FreqDist({3: 50223, 1: 47933, 4: 42345, 2: 38513, 5: 26597, 6: 17111, 7: 14399,
......@@ -702,7 +702,7 @@ FreqDist({3: 50223, 1: 47933, 4: 42345, 2: 38513, 5: 26597, 6: 17111, 7: 14399,
>>>
```
我们以导出`text1` 中每个词的长度的列表开始[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#word-lengths),然后`FreqDist` 计数列表中每个数字出现的次数[![[2]](Images/3a93e0258a010fdda935b4ee067411a5.jpg)](http://www.nltk.org/book/ch01.html#freq-word-lengths)。结果[![[3]](Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)](http://www.nltk.org/book/ch01.html#freq-word-lengths-size) 是一个包含 25 万左右个元素的分布,每一个元素是一个数字,对应文本中一个词标识符。但是只有 20 个不同的元素,从 1 到 20,因为只有 20 个不同的词长。也就是说,有由 1 个字符,2 个字符,...,20 个字符组成的词,而没有由 21 个或更多字符组成的词。有人可能会问不同长度的词的频率是多少?(例如,文本中有多少长度为 4 的词?长度为 5 的词是否比长度为 4 的词多?等等)。下面我们回答这个问题:
我们以导出`text1` 中每个词的长度的列表开始[](http://www.nltk.org/book/ch01.html#word-lengths),然后`FreqDist` 计数列表中每个数字出现的次数[](http://www.nltk.org/book/ch01.html#freq-word-lengths)。结果[](http://www.nltk.org/book/ch01.html#freq-word-lengths-size) 是一个包含 25 万左右个元素的分布,每一个元素是一个数字,对应文本中一个词标识符。但是只有 20 个不同的元素,从 1 到 20,因为只有 20 个不同的词长。也就是说,有由 1 个字符,2 个字符,...,20 个字符组成的词,而没有由 21 个或更多字符组成的词。有人可能会问不同长度的词的频率是多少?(例如,文本中有多少长度为 4 的词?长度为 5 的词是否比长度为 4 的词多?等等)。下面我们回答这个问题:
```py
>>> fdist.most_common()
......@@ -823,12 +823,12 @@ NLTK 频率分布类中定义的函数
>>> word = 'cat'
>>> if len(word) < 5:
... print('word length is less than 5')
... ![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
...
word length is less than 5
>>>
```
使用 Python 解释器时,我们必须添加一个额外的空白行[![[1]](Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)](http://www.nltk.org/book/ch01.html#blank-line),这样它才能检测到嵌套块结束。
使用 Python 解释器时,我们必须添加一个额外的空白行[](http://www.nltk.org/book/ch01.html#blank-line),这样它才能检测到嵌套块结束。
注意
......
......@@ -55,11 +55,11 @@ SELECT City FROM city_table WHERE Country="china"
```py
>>> from nltk.sem import chat80
>>> rows = chat80.sql_query('corpora/city_database/city.db', q)
>>> for r in rows: print(r[0], end=" ") ![[1]](Images/ab3d4c917ad3461f18759719a288afa5.jpg)
>>> for r in rows: print(r[0], end=" ")
canton chungking dairen harbin kowloon mukden peking shanghai sian tientsin
```
由于每行`r`是一个单元素的元组,我们输出元组的成员,而不是元组本身[![[1]](Images/ab3d4c917ad3461f18759719a288afa5.jpg)](./ch10.html#tuple-val)。
由于每行`r`是一个单元素的元组,我们输出元组的成员,而不是元组本身[](./ch10.html#tuple-val)
总结一下,我们已经定义了一个任务:计算机对自然语言查询做出反应,返回有用的数据。我们通过将英语的一个小的子集翻译成 SQL 来实现这个任务们可以说,我们的 NLTK 代码已经“理解”SQL,只要 Python 能够对数据库执行 SQL 查询,通过扩展,它也“理解”如 What cities are located in China 这样的查询。这相当于自然语言理解的例子能够从荷兰语翻译成英语。假设你是一个英语为母语的人,已经开始学习荷兰语。你的老师问你是否理解[(3)](./ch10.html#ex-sem1)的意思:
......@@ -534,7 +534,7 @@ set()
```py
>>> print(read_expr(r'\x.\y.(dog(x) & own(y, x))(cyril)').simplify())
\y.(dog(cyril) & own(y,cyril))
>>> print(read_expr(r'\x y.(dog(x) & own(y, x))(cyril, angus)').simplify()) ![[1]](Images/ab3d4c917ad3461f18759719a288afa5.jpg)
>>> print(read_expr(r'\x y.(dog(x) & own(y, x))(cyril, angus)').simplify())
(dog(cyril) & own(angus,cyril))
```
......@@ -679,10 +679,10 @@ all x.(girl(x) -> exists z4.(dog(z4) & chase(x,z4)))
```
我们可以使用`draw()`方法[![[1]](Images/ab3d4c917ad3461f18759719a288afa5.jpg)](./ch10.html#draw-drs)可视化结果,如[5.2](./ch10.html#fig-drs-screenshot)所示。
我们可以使用`draw()`方法[](./ch10.html#draw-drs)可视化结果,如[5.2](./ch10.html#fig-drs-screenshot)所示。
```py
>>> drs1.draw() ![[1]](Images/ab3d4c917ad3461f18759719a288afa5.jpg)
>>> drs1.draw()
```
![Images/drs_screenshot0.png](Images/66d94cb86ab90a95cfe745d9613c37b1.jpg)
......
......@@ -223,17 +223,17 @@ f_out.write(bytes(s, 'UTF-8'))
最简单的情况,输入和输出格式是同构的。例如,我们可能要将词汇数据从 Toolbox 格式转换为 XML,可以直接一次一个的转换词条([4](./ch11.html#sec-working-with-xml))。数据结构反映在所需的程序的结构中:一个`for`循环,每次循环处理一个词条。
另一种常见的情况,输出是输入的摘要形式,如一个倒置的文件索引。有必要在内存中建立索引结构(见[4.8](./ch04.html#code-search-documents)),然后把它以所需的格式写入一个文件。下面的例子构造一个索引,映射字典定义的词汇到相应的每个词条[![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)](./ch11.html#map-word-lexeme)的语意[![[2]](Images/f9e1ba3246770e3ecb24f813f33f2075.jpg)](./ch11.html#lexical-entry),已经对定义文本分词[![[3]](Images/13f25b9eba42f74ad969a74cee78551e.jpg)](./ch11.html#definition-text),并丢弃短词[![[4]](Images/92cc2e7821d464cfbaaf651a360cd413.jpg)](./ch11.html#short-words)。一旦该索引建成,我们打开一个文件,然后遍历索引项,以所需的格式输出行[![[5]](Images/63a8e4c47e813ba9630363f9b203a19a.jpg)](./ch11.html#required-format)。
另一种常见的情况,输出是输入的摘要形式,如一个倒置的文件索引。有必要在内存中建立索引结构(见[4.8](./ch04.html#code-search-documents)),然后把它以所需的格式写入一个文件。下面的例子构造一个索引,映射字典定义的词汇到相应的每个词条[](./ch11.html#map-word-lexeme)的语意[](./ch11.html#lexical-entry),已经对定义文本分词[](./ch11.html#definition-text),并丢弃短词[](./ch11.html#short-words)。一旦该索引建成,我们打开一个文件,然后遍历索引项,以所需的格式输出行[](./ch11.html#required-format)
```py
>>> idx = nltk.Index((defn_word, lexeme) ![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)
... for (lexeme, defn) in pairs ![[2]](Images/f9e1ba3246770e3ecb24f813f33f2075.jpg)
... for defn_word in nltk.word_tokenize(defn) ![[3]](Images/13f25b9eba42f74ad969a74cee78551e.jpg)
... if len(defn_word) > 3) ![[4]](Images/92cc2e7821d464cfbaaf651a360cd413.jpg)
>>> idx = nltk.Index((defn_word, lexeme)
... for (lexeme, defn) in pairs
... for defn_word in nltk.word_tokenize(defn)
... if len(defn_word) > 3)
>>> with open("dict.idx", "w") as idx_file:
... for word in sorted(idx):
... idx_words = ', '.join(idx[word])
... idx_line = "{}: {}".format(word, idx_words) ![[5]](Images/63a8e4c47e813ba9630363f9b203a19a.jpg)
... idx_line = "{}: {}".format(word, idx_words)
... print(idx_line, file=idx_file)
```
......@@ -372,18 +372,18 @@ sleep: wake
Python 的 ElementTree 模块提供了一种方便的方式访问存储在 XML 文件中的数据。ElementTree 是 Python 标准库(自从 Python 2.5)的一部分,也作为 NLTK 的一部分提供,以防你在使用 Python 2.4。
我们将使用 XML 格式的莎士比亚戏剧集来说明 ElementTree 的使用方法。让我们加载 XML 文件并检查原始数据,首先在文件的顶部[![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)](./ch11.html#top-of-file),在那里我们看到一些 XML 头和一个名为`play.dtd`的模式,接着是根元素 `PLAY`。我们从 Act 1[![[2]](Images/f9e1ba3246770e3ecb24f813f33f2075.jpg)](./ch11.html#start-act-one)再次获得数据。(输出中省略了一些空白行。)
我们将使用 XML 格式的莎士比亚戏剧集来说明 ElementTree 的使用方法。让我们加载 XML 文件并检查原始数据,首先在文件的顶部[](./ch11.html#top-of-file),在那里我们看到一些 XML 头和一个名为`play.dtd`的模式,接着是根元素 `PLAY`。我们从 Act 1[](./ch11.html#start-act-one)再次获得数据。(输出中省略了一些空白行。)
```py
>>> merchant_file = nltk.data.find('corpora/shakespeare/merchant.xml')
>>> raw = open(merchant_file).read()
>>> print(raw[:163]) ![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)
>>> print(raw[:163])
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="shakes.css"?>
<!-- <!DOCTYPE PLAY SYSTEM "play.dtd"> -->
<PLAY>
<TITLE>The Merchant of Venice</TITLE>
>>> print(raw[1789:2006]) ![[2]](Images/f9e1ba3246770e3ecb24f813f33f2075.jpg)
>>> print(raw[1789:2006])
<TITLE>ACT I</TITLE>
<SCENE><TITLE>SCENE I. Venice. A street.</TITLE>
<STAGEDIR>Enter ANTONIO, SALARINO, and SALANIO</STAGEDIR>
......@@ -394,18 +394,18 @@ Python 的 ElementTree 模块提供了一种方便的方式访问存储在 XML
我们刚刚访问了作为一个字符串的 XML 数据。正如我们看到的,在 Act 1 开始处的字符串包含 XML 标记 title、scene、stage directions 等。
下一步是作为结构化的 XML 数据使用`ElementTree`处理文件的内容。我们正在处理一个文件(一个多行字符串),并建立一棵树,所以方法的名称是`parse` [![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)](./ch11.html#xml-parse)并不奇怪。变量`merchant`包含一个 XML 元素`PLAY` [![[2]](Images/f9e1ba3246770e3ecb24f813f33f2075.jpg)](./ch11.html#element-play)。此元素有内部结构;我们可以使用一个索引来得到它的第一个孩子,一个`TITLE`元素[![[3]](Images/13f25b9eba42f74ad969a74cee78551e.jpg)](./ch11.html#element-title)。我们还可以看到该元素的文本内容:戏剧的标题[![[4]](Images/92cc2e7821d464cfbaaf651a360cd413.jpg)](./ch11.html#element-text)。要得到所有的子元素的列表,我们使用`getchildren()`方法[![[5]](Images/63a8e4c47e813ba9630363f9b203a19a.jpg)](./ch11.html#getchildren-method)。
下一步是作为结构化的 XML 数据使用`ElementTree`处理文件的内容。我们正在处理一个文件(一个多行字符串),并建立一棵树,所以方法的名称是`parse` [](./ch11.html#xml-parse)并不奇怪。变量`merchant`包含一个 XML 元素`PLAY` [](./ch11.html#element-play)。此元素有内部结构;我们可以使用一个索引来得到它的第一个孩子,一个`TITLE`元素[](./ch11.html#element-title)。我们还可以看到该元素的文本内容:戏剧的标题[](./ch11.html#element-text)。要得到所有的子元素的列表,我们使用`getchildren()`方法[](./ch11.html#getchildren-method)
```py
>>> from xml.etree.ElementTree import ElementTree
>>> merchant = ElementTree().parse(merchant_file) ![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)
>>> merchant = ElementTree().parse(merchant_file)
>>> merchant
<Element 'PLAY' at 0x10ac43d18> # [_element-play]
>>> merchant[0]
<Element 'TITLE' at 0x10ac43c28> # [_element-title]
>>> merchant[0].text
'The Merchant of Venice' # [_element-text]
>>> merchant.getchildren() ![[5]](Images/63a8e4c47e813ba9630363f9b203a19a.jpg)
>>> merchant.getchildren()
[<Element 'TITLE' at 0x10ac43c28>, <Element 'PERSONAE' at 0x10ac43bd8>,
<Element 'SCNDESCR' at 0x10b067f98>, <Element 'PLAYSUBT' at 0x10af37048>,
<Element 'ACT' at 0x10af37098>, <Element 'ACT' at 0x10b936368>,
......@@ -527,7 +527,7 @@ SHYL 15 15 2 26 21 0
'kaakasi', 'kaakau', 'kaakauko', 'kaakito', 'kaakuupato', ..., 'kuvuto']
```
让我们查看 XML 格式的 Toolbox 数据。`ElementTree``write()`方法需要一个文件对象。我们通常使用 Python 内置的`open()`函数创建。为了屏幕上显示输出,我们可以使用一个特殊的预定义的文件对象称为`stdout` [![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)](./ch11.html#sys-stdout) (标准输出),在 Python 的`sys`模块中定义的。
让我们查看 XML 格式的 Toolbox 数据。`ElementTree``write()`方法需要一个文件对象。我们通常使用 Python 内置的`open()`函数创建。为了屏幕上显示输出,我们可以使用一个特殊的预定义的文件对象称为`stdout` [](./ch11.html#sys-stdout) (标准输出),在 Python 的`sys`模块中定义的。
```py
>>> import sys
......@@ -535,7 +535,7 @@ SHYL 15 15 2 26 21 0
>>> from xml.etree.ElementTree import ElementTree
>>> elementtree_indent(lexicon)
>>> tree = ElementTree(lexicon[3])
>>> tree.write(sys.stdout, encoding='unicode') ![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)
>>> tree.write(sys.stdout, encoding='unicode')
<record>
<lx>kaa</lx>
<ps>N</ps>
......@@ -629,7 +629,7 @@ Toolbox 格式的许多词汇不符合任何特定的模式。有些条目可能
('lx:rt:ps:pt:ge:tkp:dt:ex:xp:xe:ex:xp:xe', 27), ('lx:ps:pt:ge:tkp:nt:dt:ex:xp:xe', 20), ...]
```
检查完高频字段序列后,我们可以设计一个词汇条目的上下文无关语法。在[5.2](./ch11.html#code-toolbox-validation)中的语法使用我们在[8.](./ch08.html#chap-parse)看到的 CFG 格式。这样的语法模型隐含 Toolbox 条目的嵌套结构,建立一个树状结构,树的叶子是单独的字段名。最后,我们遍历条目并报告它们与语法的一致性,如[5.2](./ch11.html#code-toolbox-validation)所示。那些被语法接受的在前面加一个`'+'` [![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)](./ch11.html#accepted-entries),那些被语法拒绝的在前面加一个`'-'` [![[2]](Images/f9e1ba3246770e3ecb24f813f33f2075.jpg)](./ch11.html#rejected-entries)。在开发这样一个文法的过程中,它可以帮助过滤掉一些标签[![[3]](Images/13f25b9eba42f74ad969a74cee78551e.jpg)](./ch11.html#ignored-tags)。
检查完高频字段序列后,我们可以设计一个词汇条目的上下文无关语法。在[5.2](./ch11.html#code-toolbox-validation)中的语法使用我们在[8.](./ch08.html#chap-parse)看到的 CFG 格式。这样的语法模型隐含 Toolbox 条目的嵌套结构,建立一个树状结构,树的叶子是单独的字段名。最后,我们遍历条目并报告它们与语法的一致性,如[5.2](./ch11.html#code-toolbox-validation)所示。那些被语法接受的在前面加一个`'+'` [](./ch11.html#accepted-entries),那些被语法拒绝的在前面加一个`'-'` [](./ch11.html#rejected-entries)。在开发这样一个文法的过程中,它可以帮助过滤掉一些标签[](./ch11.html#ignored-tags)
```py
grammar = nltk.CFG.fromstring('''
......@@ -654,9 +654,9 @@ def validate_lexicon(grammar, lexicon, ignored_tags):
for entry in lexicon:
marker_list = [field.tag for field in entry if field.tag not in ignored_tags]
if list(rd_parser.parse(marker_list)):
print("+", ':'.join(marker_list)) ![[1]](Images/346344c2e5a627acfdddf948fb69cb1d.jpg)
print("+", ':'.join(marker_list))
else:
print("-", ':'.join(marker_list)) ![[2]](Images/f9e1ba3246770e3ecb24f813f33f2075.jpg)
print("-", ':'.join(marker_list))
```
另一种方法是用一个词块分析器([7.](./ch07.html#chap-chunk)),因为它能识别局部结构并报告已确定的局部结构,会更加有效。在[5.3](./ch11.html#code-chunk-toolbox)中我们为词汇条目建立一个词块语法,然后解析每个条目。这个程序的输出的一个示例如[5.4](./ch11.html#fig-iu-mien)所示。
......
此差异已折叠。
......@@ -88,12 +88,12 @@ Project Gutenberg; Dmitri Prokofitch; Andrey Semyonovitch; Hay Market
5338
>>> raw.rfind("End of Project Gutenberg's Crime")
1157743
>>> raw = raw[5338:1157743] ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> raw = raw[5338:1157743]
>>> raw.find("PART I")
0
```
方法`find()``rfind()`(反向的 find)帮助我们得到字符串切片需要用到的正确的索引值[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#raw-slice)。我们用这个切片重新给`raw`赋值,所以现在它以“PART I”开始一直到(但不包括)标记内容结尾的句子。
方法`find()``rfind()`(反向的 find)帮助我们得到字符串切片需要用到的正确的索引值[](./ch03.html#raw-slice)。我们用这个切片重新给`raw`赋值,所以现在它以“PART I”开始一直到(但不包括)标记内容结尾的句子。
这是我们第一次接触到网络的实际内容:在网络上找到的文本可能含有不必要的内容,并没有一个自动的方法来去除它。但只需要少量的额外工作,我们就可以提取出我们需要的材料。
......@@ -299,34 +299,34 @@ TypeError: cannot concatenate 'str' and 'list' objects
### 字符串的基本操作
可以使用单引号[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#single-quotes)或双引号[![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)](./ch03.html#double-quotes)来指定字符串,如下面的例子代码所示。如果一个字符串中包含一个单引号,我们必须在单引号前加反斜杠[![[3]](Images/7c20d0adbadb35031a28bfcd6dff9900.jpg)](./ch03.html#backslash-escape)让 Python 知道这是字符串中的单引号,或者也可以将这个字符串放入双引号中[![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)](./ch03.html#double-quotes)。否则,字符串内的单引号[![[4]](Images/0f4441cdaf35bfa4d58fc64142cf4736.jpg)](./ch03.html#unescaped-quote)将被解释为字符串结束标志,Python 解释器会报告一个语法错误:
可以使用单引号[](./ch03.html#single-quotes)或双引号[](./ch03.html#double-quotes)来指定字符串,如下面的例子代码所示。如果一个字符串中包含一个单引号,我们必须在单引号前加反斜杠[](./ch03.html#backslash-escape)让 Python 知道这是字符串中的单引号,或者也可以将这个字符串放入双引号中[](./ch03.html#double-quotes)。否则,字符串内的单引号[](./ch03.html#unescaped-quote)将被解释为字符串结束标志,Python 解释器会报告一个语法错误:
```py
>>> monty = 'Monty Python' ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> monty = 'Monty Python'
>>> monty
'Monty Python'
>>> circus = "Monty Python's Flying Circus" ![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)
>>> circus = "Monty Python's Flying Circus"
>>> circus
"Monty Python's Flying Circus"
>>> circus = 'Monty Python\'s Flying Circus' ![[3]](Images/7c20d0adbadb35031a28bfcd6dff9900.jpg)
>>> circus = 'Monty Python\'s Flying Circus'
>>> circus
"Monty Python's Flying Circus"
>>> circus = 'Monty Python's Flying Circus' ![[4]](Images/0f4441cdaf35bfa4d58fc64142cf4736.jpg)
>>> circus = 'Monty Python's Flying Circus'
File "<stdin>", line 1
circus = 'Monty Python's Flying Circus'
^
SyntaxError: invalid syntax
```
有时字符串跨好几行。Python 提供了多种方式表示它们。在下面的例子中,一个包含两个字符串的序列被连接为一个字符串。我们需要使用反斜杠[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#string-backslash)或者括号[![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)](./ch03.html#string-parentheses),这样解释器就知道第一行的表达式不完整。
有时字符串跨好几行。Python 提供了多种方式表示它们。在下面的例子中,一个包含两个字符串的序列被连接为一个字符串。我们需要使用反斜杠[](./ch03.html#string-backslash)或者括号[](./ch03.html#string-parentheses),这样解释器就知道第一行的表达式不完整。
```py
>>> couplet = "Shall I compare thee to a Summer's day?"\
... "Thou are more lovely and more temperate:" ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
... "Thou are more lovely and more temperate:"
>>> print(couplet)
Shall I compare thee to a Summer's day?Thou are more lovely and more temperate:
>>> couplet = ("Rough winds do shake the darling buds of May,"
... "And Summer's lease hath all too short a date:") ![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)
... "And Summer's lease hath all too short a date:")
>>> print(couplet)
Rough winds do shake the darling buds of May,And Summer's lease hath all too short a date:
```
......@@ -346,12 +346,12 @@ Rough winds do shake the darling buds of May,
And Summer's lease hath all too short a date:
```
现在我们可以定义字符串,也可以在上面尝试一些简单的操作。首先,让我们来看看`+`操作,被称为连接 [![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#string-concatenation)。此操作产生一个新字符串,它是两个原始字符串首尾相连粘贴在一起而成。请注意,连接不会做一些比较聪明的事,例如在词汇之间插入空格。我们甚至可以对字符串用乘法[![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)](./ch03.html#string-multiplication):
现在我们可以定义字符串,也可以在上面尝试一些简单的操作。首先,让我们来看看`+`操作,被称为连接 [](./ch03.html#string-concatenation)。此操作产生一个新字符串,它是两个原始字符串首尾相连粘贴在一起而成。请注意,连接不会做一些比较聪明的事,例如在词汇之间插入空格。我们甚至可以对字符串用乘法[](./ch03.html#string-multiplication)
```py
>>> 'very' + 'very' + 'very' ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> 'very' + 'very' + 'very'
'veryveryvery'
>>> 'very' * 3 ![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)
>>> 'very' * 3
'veryveryvery'
```
......@@ -426,10 +426,10 @@ Traceback (most recent call last):
IndexError: string index out of range
```
也与列表一样,我们可以使用字符串的负数索引,其中`-1`是最后一个字符的索引[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#last-character)。正数和负数的索引给我们两种方式指示一个字符串中的任何位置。在这种情况下,当一个字符串长度为 12 时,索引`5``-7`都指示相同的字符(一个空格)。(请注意,`5 = len(monty) - 7`。)
也与列表一样,我们可以使用字符串的负数索引,其中`-1`是最后一个字符的索引[](./ch03.html#last-character)。正数和负数的索引给我们两种方式指示一个字符串中的任何位置。在这种情况下,当一个字符串长度为 12 时,索引`5``-7`都指示相同的字符(一个空格)。(请注意,`5 = len(monty) - 7`。)
```py
>>> monty[-1] ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> monty[-1]
'n'
>>> monty[5]
' '
......@@ -650,7 +650,7 @@ b'\xc5\x84'
>>> line = lines[2]
>>> print(line.encode('unicode_escape'))
b'Niemc\\xf3w pod koniec II wojny \\u015bwiatowej na Dolny \\u015al\\u0105sk, zosta\\u0142y\\n'
>>> for c in line: ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> for c in line:
... if ord(c) > 127:
... print('{} U+{:04x} {}'.format(c.encode('utf8'), ord(c), unicodedata.name(c)))
b'\xc3\xb3' U+00f3 LATIN SMALL LETTER O WITH ACUTE
......@@ -660,7 +660,7 @@ b'\xc4\x85' U+0105 LATIN SMALL LETTER A WITH OGONEK
b'\xc5\x82' U+0142 LATIN SMALL LETTER L WITH STROKE
```
如果你使用`c`替换掉[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#unicode-info)中的`c.encode('utf8')`,如果你的系统支持 UTF-8,你应该看到类似下面的输出:
如果你使用`c`替换掉[](./ch03.html#unicode-info)中的`c.encode('utf8')`,如果你的系统支持 UTF-8,你应该看到类似下面的输出:
ó U+00f3 LATIN SMALL LETTER O WITH ACUTEś U+015b LATIN SMALL LETTER S WITH ACUTEŚ U+015a LATIN CAPITAL LETTER S WITH ACUTEą U+0105 LATIN SMALL LETTER A WITH OGONEKł U+0142 LATIN SMALL LETTER L WITH STROKE
......@@ -963,20 +963,20 @@ v 93 27 105 48 49
### 搜索已分词文本
你可以使用一种特殊的正则表达式搜索一个文本中多个词(这里的文本是一个词符列表)。例如,`"&lt;a&gt; &lt;man&gt;"`找出文本中所有 a man 的实例。尖括号用于标记词符的边界,尖括号之间的所有空白都被忽略(这只对 NLTK 中的`findall()`方法处理文本有效)。在下面的例子中,我们使用`&lt;.*&gt;`[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#single-token-wildcard),它将匹配所有单个词符,将它括在括号里,于是只匹配词(例如 monied)而不匹配短语(例如,a monied man)会生成。第二个例子找出以词 bro 结尾的三个词组成的短语[![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)](./ch03.html#three-word-phrases)。最后一个例子找出以字母 l 开始的三个或更多词组成的序列[![[3]](Images/7c20d0adbadb35031a28bfcd6dff9900.jpg)](./ch03.html#letter-l)。
你可以使用一种特殊的正则表达式搜索一个文本中多个词(这里的文本是一个词符列表)。例如,`"&lt;a&gt; &lt;man&gt;"`找出文本中所有 a man 的实例。尖括号用于标记词符的边界,尖括号之间的所有空白都被忽略(这只对 NLTK 中的`findall()`方法处理文本有效)。在下面的例子中,我们使用`&lt;.*&gt;`[](./ch03.html#single-token-wildcard),它将匹配所有单个词符,将它括在括号里,于是只匹配词(例如 monied)而不匹配短语(例如,a monied man)会生成。第二个例子找出以词 bro 结尾的三个词组成的短语[](./ch03.html#three-word-phrases)。最后一个例子找出以字母 l 开始的三个或更多词组成的序列[](./ch03.html#letter-l)
```py
>>> from nltk.corpus import gutenberg, nps_chat
>>> moby = nltk.Text(gutenberg.words('melville-moby_dick.txt'))
>>> moby.findall(r"<a> (<.*>) <man>") ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> moby.findall(r"<a> (<.*>) <man>")
monied; nervous; dangerous; white; white; white; pious; queer; good;
mature; white; Cape; great; wise; wise; butterless; white; fiendish;
pale; furious; better; certain; complete; dismasted; younger; brave;
brave; brave; brave
>>> chat = nltk.Text(nps_chat.words())
>>> chat.findall(r"<.*> <.*> <bro>") ![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)
>>> chat.findall(r"<.*> <.*> <bro>")
you rule bro; telling you bro; u twizted bro
>>> chat.findall(r"<l.*>{3,}") ![[3]](Images/7c20d0adbadb35031a28bfcd6dff9900.jpg)
>>> chat.findall(r"<l.*>{3,}")
lol lol lol; lmao lol lol; lol lol lol; la la la la la; la la la; la
la la; lovely lol lol love; lol lol lol.; la la la; la la la
```
......@@ -1099,15 +1099,15 @@ WordNet 词形归并器只在产生的词在它的词典中时才删除词缀。
... well without--Maybe it's always pepper that makes people hot-tempered,'..."""
```
我们可以使用`raw.split()`在空格符处分割原始文本。使用正则表达式能做同样的事情,匹配字符串中的所有空格符[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#split-space)是不够的,因为这将导致分词结果包含`\n`换行符;我们需要匹配任何数量的空格符、制表符或换行符[![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)](./ch03.html#split-whitespace):
我们可以使用`raw.split()`在空格符处分割原始文本。使用正则表达式能做同样的事情,匹配字符串中的所有空格符[](./ch03.html#split-space)是不够的,因为这将导致分词结果包含`\n`换行符;我们需要匹配任何数量的空格符、制表符或换行符[](./ch03.html#split-whitespace)
```py
>>> re.split(r' ', raw) ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> re.split(r' ', raw)
["'When", "I'M", 'a', "Duchess,'", 'she', 'said', 'to', 'herself,', '(not', 'in',
'a', 'very', 'hopeful', 'tone\nthough),', "'I", "won't", 'have', 'any', 'pepper',
'in', 'my', 'kitchen', 'AT', 'ALL.', 'Soup', 'does', 'very\nwell', 'without--Maybe',
"it's", 'always', 'pepper', 'that', 'makes', 'people', "hot-tempered,'..."]
>>> re.split(r'[ \t\n]+', raw) ![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)
>>> re.split(r'[ \t\n]+', raw)
["'When", "I'M", 'a', "Duchess,'", 'she', 'said', 'to', 'herself,', '(not', 'in',
'a', 'very', 'hopeful', 'tone', 'though),', "'I", "won't", 'have', 'any', 'pepper',
'in', 'my', 'kitchen', 'AT', 'ALL.', 'Soup', 'does', 'very', 'well', 'without--Maybe',
......@@ -1432,12 +1432,12 @@ Lee wants a pancake right now
### 对齐
到目前为止,我们的格式化字符串可以在页面(或屏幕)上输出任意的宽度。我们可以通过插入一个冒号`':'`跟随一个整数来添加空白以获得指定宽带的输出。所以`{:6}`表示我们想让字符串对齐到宽度 6。数字默认表示右对齐[![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)](./ch03.html#right-justified),单我们可以在宽度指示符前面加上`'&lt;'`对齐选项来让数字左对齐[![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)](./ch03.html#left-justified)。
到目前为止,我们的格式化字符串可以在页面(或屏幕)上输出任意的宽度。我们可以通过插入一个冒号`':'`跟随一个整数来添加空白以获得指定宽带的输出。所以`{:6}`表示我们想让字符串对齐到宽度 6。数字默认表示右对齐[](./ch03.html#right-justified),单我们可以在宽度指示符前面加上`'&lt;'`对齐选项来让数字左对齐[](./ch03.html#left-justified)
```py
>>> '{:6}'.format(41) ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> '{:6}'.format(41)
' 41'
>>> '{:<6}' .format(41) ![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)
>>> '{:<6}' .format(41)
'41 '
```
......@@ -1448,9 +1448,9 @@ System Message: ERROR/3 (`ch03.rst2`, line 2313)
Unexpected indentation.
```py
>>> '{:6}'.format('dog') ![[1]](Images/7e6ea96aad77f3e523494b3972b5a989.jpg)
>>> '{:6}'.format('dog')
'dog '
>>> '{:>6}'.format('dog') ![[2]](Images/be33958d0b44c88caac0dcf4d4ec84c6.jpg)
>>> '{:>6}'.format('dog')
' dog'
```
......
此差异已折叠。
......@@ -324,14 +324,14 @@ NNS-TL-HL [('Nations', 1)]
请注意 often 后面最高频率的词性是动词。名词从来没有在这个位置出现(在这个特别的语料中)。
接下来,让我们看一些较大范围的上下文,找出涉及特定标记和词序列的词(在这种情况下,`"&lt;Verb&gt; to &lt;Verb&gt;"`)。在 code-three-word-phrase 中,我们考虑句子中的每个三词窗口[![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)](./ch05.html#three-word),检查它们是否符合我们的标准[![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)](./ch05.html#verb-to-verb)。如果标记匹配,我们输出对应的词[![[3]](Images/934b688727805b37f2404f7497c52027.jpg)](./ch05.html#print-words)。
接下来,让我们看一些较大范围的上下文,找出涉及特定标记和词序列的词(在这种情况下,`"&lt;Verb&gt; to &lt;Verb&gt;"`)。在 code-three-word-phrase 中,我们考虑句子中的每个三词窗口[❶](./ch05.html#three-word),检查它们是否符合我们的标准[❷](./ch05.html#verb-to-verb)。如果标记匹配,我们输出对应的词[❸](./ch05.html#print-words)。
```py
from nltk.corpus import brown
def process(sentence):
for (w1,t1), (w2,t2), (w3,t3) in nltk.trigrams(sentence): ![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)
if (t1.startswith('V') and t2 == 'TO' and t3.startswith('V')): ![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)
print(w1, w2, w3) ![[3]](Images/934b688727805b37f2404f7497c52027.jpg)
for (w1,t1), (w2,t2), (w3,t3) in nltk.trigrams(sentence):
if (t1.startswith('V') and t2 == 'TO' and t3.startswith('V')):
print(w1, w2, w3)
>>> for tagged_sent in brown.tagged_sents():
... process(tagged_sent)
......@@ -409,17 +409,17 @@ that CNJ V WH DET
>>> pos = {}
>>> pos
{}
>>> pos['colorless'] = 'ADJ' ![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)
>>> pos['colorless'] = 'ADJ'
>>> pos
{'colorless': 'ADJ'}
>>> pos['ideas'] = 'N'
>>> pos['sleep'] = 'V'
>>> pos['furiously'] = 'ADV'
>>> pos ![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)
>>> pos
{'furiously': 'ADV', 'ideas': 'N', 'colorless': 'ADJ', 'sleep': 'V'}
```
所以,例如,[![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)](./ch05.html#pos-colorless)说的是 colorless 的词性是形容词,或者更具体地说:在字典`pos`中,键`'colorless'`被分配了值`'ADJ'`。当我们检查`pos`的值时[![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)](./ch05.html#pos-inspect),我们看到一个键-值对的集合。一旦我们以这样的方式填充了字典,就可以使用键来检索值:
所以,例如,[](./ch05.html#pos-colorless)说的是 colorless 的词性是形容词,或者更具体地说:在字典`pos`中,键`'colorless'`被分配了值`'ADJ'`。当我们检查`pos`的值时[](./ch05.html#pos-inspect),我们看到一个键-值对的集合。一旦我们以这样的方式填充了字典,就可以使用键来检索值:
```py
>>> pos['ideas']
......@@ -437,16 +437,16 @@ Traceback (most recent call last):
KeyError: 'green'
```
这就提出了一个重要的问题。与列表和字符串不同,我们可以用`len()`算出哪些整数是合法索引,我们如何算出一个字典的合法键?如果字典不是太大,我们可以简单地通过查看变量`pos`检查它的内容。正如在前面([![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)](./ch05.html#pos-inspect)行)所看到,这为我们提供了键-值对。请注意它们的顺序与最初放入它们的顺序不同;这是因为字典不是序列而是映射(参见[3.2](./ch05.html#fig-maps02)),键没有固定地排序。
这就提出了一个重要的问题。与列表和字符串不同,我们可以用`len()`算出哪些整数是合法索引,我们如何算出一个字典的合法键?如果字典不是太大,我们可以简单地通过查看变量`pos`检查它的内容。正如在前面([](./ch05.html#pos-inspect)行)所看到,这为我们提供了键-值对。请注意它们的顺序与最初放入它们的顺序不同;这是因为字典不是序列而是映射(参见[3.2](./ch05.html#fig-maps02)),键没有固定地排序。
换种方式,要找到键,我们可以将字典转换成一个列表[![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)](./ch05.html#dict-to-list)——要么在期望列表的上下文中使用字典,如作为`sorted()`的参数[![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)](./ch05.html#dict-sorted),要么在`for` 循环中[![[3]](Images/934b688727805b37f2404f7497c52027.jpg)](./ch05.html#dict-for-loop)。
换种方式,要找到键,我们可以将字典转换成一个列表[](./ch05.html#dict-to-list)——要么在期望列表的上下文中使用字典,如作为`sorted()`的参数[](./ch05.html#dict-sorted),要么在`for` 循环中[](./ch05.html#dict-for-loop)
```py
>>> list(pos) ![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)
>>> list(pos)
['ideas', 'furiously', 'colorless', 'sleep']
>>> sorted(pos) ![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)
>>> sorted(pos)
['colorless', 'furiously', 'ideas', 'sleep']
>>> [w for w in pos if w.endswith('s')] ![[3]](Images/934b688727805b37f2404f7497c52027.jpg)
>>> [w for w in pos if w.endswith('s')]
['colorless', 'ideas']
```
......@@ -466,7 +466,7 @@ sleep: V
ideas: N
```
最后,字典的方法`keys()``values()``items()`允许我们以单独的列表访问键、值以及键-值对。我们甚至可以排序元组[![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)](./ch05.html#sort-tuples),按它们的第一个元素排序(如果第一个元素相同,就使用它们的第二个元素)。
最后,字典的方法`keys()``values()``items()`允许我们以单独的列表访问键、值以及键-值对。我们甚至可以排序元组[](./ch05.html#sort-tuples),按它们的第一个元素排序(如果第一个元素相同,就使用它们的第二个元素)。
```py
>>> list(pos.keys())
......@@ -475,7 +475,7 @@ ideas: N
['ADJ', 'ADV', 'V', 'N']
>>> list(pos.items())
[('colorless', 'ADJ'), ('furiously', 'ADV'), ('sleep', 'V'), ('ideas', 'N')]
>>> for key, val in sorted(pos.items()): ![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)
>>> for key, val in sorted(pos.items()):
... print(key + ":", val)
...
colorless: ADJ
......@@ -535,12 +535,12 @@ TypeError: list objects are unhashable
这些默认值实际上是将其他对象转换为指定类型的函数(例如`int("2")`, `list("2")`)。当它们不带参数被调用时——`int()`, `list()`——它们分别返回`0``[]`
前面的例子中指定字典项的默认值为一个特定的数据类型的默认值。然而,也可以指定任何我们喜欢的默认值,只要提供可以无参数的被调用产生所需值的函数的名子。让我们回到我们的词性的例子,创建一个任一条目的默认值是`'N'`的字典[![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)](./ch05.html#default-noun)。当我们访问一个不存在的条目时[![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)](./ch05.html#non-existent),它会自动添加到字典[![[3]](Images/934b688727805b37f2404f7497c52027.jpg)](./ch05.html#automatically-added)。
前面的例子中指定字典项的默认值为一个特定的数据类型的默认值。然而,也可以指定任何我们喜欢的默认值,只要提供可以无参数的被调用产生所需值的函数的名子。让我们回到我们的词性的例子,创建一个任一条目的默认值是`'N'`的字典[](./ch05.html#default-noun)。当我们访问一个不存在的条目时[](./ch05.html#non-existent),它会自动添加到字典[](./ch05.html#automatically-added)
```py
>>> pos = defaultdict(lambda: 'NOUN') ![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)
>>> pos = defaultdict(lambda: 'NOUN')
>>> pos['colorless'] = 'ADJ'
>>> pos['blog'] ![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)
>>> pos['blog']
'NOUN'
>>> list(pos.items())
[('blog', 'NOUN'), ('colorless', 'ADJ')] # [_automatically-added]
......@@ -673,14 +673,14 @@ TypeError: list objects are unhashable
```py
>>> pos = defaultdict(lambda: defaultdict(int))
>>> brown_news_tagged = brown.tagged_words(categories='news', tagset='universal')
>>> for ((w1, t1), (w2, t2)) in nltk.bigrams(brown_news_tagged): ![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)
... pos[(t1, w2)][t2] += 1 ![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)
>>> for ((w1, t1), (w2, t2)) in nltk.bigrams(brown_news_tagged):
... pos[(t1, w2)][t2] += 1
...
>>> pos[('DET', 'right')] ![[3]](Images/934b688727805b37f2404f7497c52027.jpg)
>>> pos[('DET', 'right')]
defaultdict(<class 'int'>, {'ADJ': 11, 'NOUN': 5})
```
这个例子使用一个字典,它的条目的默认值也是一个字典(其默认值是`int()`,即 0)。请注意我们如何遍历已标注语料库的双连词,每次遍历处理一个词-标记对[![[1]](Images/7a979f968bd33428b02cde62eaf2b615.jpg)](./ch05.html#processing-pairs)。每次通过循环时,我们更新字典`pos`中的条目`(t1, w2)`,一个标记和它 _ 后面 _ 的词[![[2]](Images/6ac827d2d00b6ebf8bbc704f430af896.jpg)](./ch05.html#tag-word-update)。当我们在`pos`中查找一个项目时,我们必须指定一个复合键[![[3]](Images/934b688727805b37f2404f7497c52027.jpg)](./ch05.html#compound-key),然后得到一个字典对象。一个词性标注器可以使用这些信息来决定词 right,前面是一个限定词时,应标注为`ADJ`
这个例子使用一个字典,它的条目的默认值也是一个字典(其默认值是`int()`,即 0)。请注意我们如何遍历已标注语料库的双连词,每次遍历处理一个词-标记对[](./ch05.html#processing-pairs)。每次通过循环时,我们更新字典`pos`中的条目`(t1, w2)`,一个标记和它 _ 后面 _ 的词[](./ch05.html#tag-word-update)。当我们在`pos`中查找一个项目时,我们必须指定一个复合键[](./ch05.html#compound-key),然后得到一个字典对象。一个词性标注器可以使用这些信息来决定词 right,前面是一个限定词时,应标注为`ADJ`
## 3.7 反转字典
......
......@@ -148,14 +148,14 @@ def gender_features2(name):
图 1.3:用于训练有监督分类器的语料数据组织图。语料数据分为两类:开发集和测试集。开发集通常被进一步分为训练集和开发测试集。
已经将语料分为适当的数据集,我们使用训练集训练一个模型[![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)](./ch06.html#err-analysis-train),然后在开发测试集上运行[![[2]](Images/854532b0c5c8869f9012833955e75b20.jpg)](./ch06.html#err-analysis-run)。
已经将语料分为适当的数据集,我们使用训练集训练一个模型[](./ch06.html#err-analysis-train),然后在开发测试集上运行[](./ch06.html#err-analysis-run)
```py
>>> train_set = [(gender_features(n), gender) for (n, gender) in train_names]
>>> devtest_set = [(gender_features(n), gender) for (n, gender) in devtest_names]
>>> test_set = [(gender_features(n), gender) for (n, gender) in test_names]
>>> classifier = nltk.NaiveBayesClassifier.train(train_set) ![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)
>>> print(nltk.classify.accuracy(classifier, devtest_set)) ![[2]](Images/854532b0c5c8869f9012833955e75b20.jpg)
>>> classifier = nltk.NaiveBayesClassifier.train(train_set)
>>> print(nltk.classify.accuracy(classifier, devtest_set))
0.75
```
......@@ -223,14 +223,14 @@ correct=male guess=female name=Rich
>>> random.shuffle(documents)
```
接下来,我们为文档定义一个特征提取器,这样分类器就会知道哪些方面的数据应注意([1.4](./ch06.html#code-document-classify-fd))。对于文档主题识别,我们可以为每个词定义一个特性表示该文档是否包含这个词。为了限制分类器需要处理的特征的数目,我们一开始构建一个整个语料库中前 2000 个最频繁词的列表[![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)](./ch06.html#document-classify-all-words)。然后,定义一个特征提取器[![[2]](Images/854532b0c5c8869f9012833955e75b20.jpg)](./ch06.html#document-classify-extractor),简单地检查这些词是否在一个给定的文档中。
接下来,我们为文档定义一个特征提取器,这样分类器就会知道哪些方面的数据应注意([1.4](./ch06.html#code-document-classify-fd))。对于文档主题识别,我们可以为每个词定义一个特性表示该文档是否包含这个词。为了限制分类器需要处理的特征的数目,我们一开始构建一个整个语料库中前 2000 个最频繁词的列表[](./ch06.html#document-classify-all-words)。然后,定义一个特征提取器[](./ch06.html#document-classify-extractor),简单地检查这些词是否在一个给定的文档中。
```py
all_words = nltk.FreqDist(w.lower() for w in movie_reviews.words())
word_features = list(all_words)[:2000] ![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)
word_features = list(all_words)[:2000]
def document_features(document): ![[2]](Images/854532b0c5c8869f9012833955e75b20.jpg)
document_words = set(document) ![[3]](Images/f202bb6a4c773430e3d1340de573d0e5.jpg)
def document_features(document):
document_words = set(document)
features = {}
for word in word_features:
features['contains({})'.format(word)] = (word in document_words)
......@@ -239,9 +239,9 @@ def document_features(document): ![[2]](Images/854532b0c5c8869f9012833955e75b20.
注意
[![[3]](Images/f202bb6a4c773430e3d1340de573d0e5.jpg)](./ch06.html#document-classify-set)中我们计算文档的所有词的集合,而不仅仅检查是否`word in document`,因为检查一个词是否在一个集合中出现比检查它是否在一个列表中出现要快的多([4.7](./ch04.html#sec-algorithm-design))。
[](./ch06.html#document-classify-set)中我们计算文档的所有词的集合,而不仅仅检查是否`word in document`,因为检查一个词是否在一个集合中出现比检查它是否在一个列表中出现要快的多([4.7](./ch04.html#sec-algorithm-design))。
现在,我们已经定义了我们的特征提取器,可以用它来训练一个分类器,为新的电影评论加标签([1.5](./ch06.html#code-document-classify-use))。为了检查产生的分类器可靠性如何,我们在测试集上计算其准确性[![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)](./ch06.html#document-classify-test)。再一次的,我们可以使用`show_most_informative_features()`来找出哪些特征是分类器发现最有信息量的[![[2]](Images/854532b0c5c8869f9012833955e75b20.jpg)](./ch06.html#document-classify-smif)。
现在,我们已经定义了我们的特征提取器,可以用它来训练一个分类器,为新的电影评论加标签([1.5](./ch06.html#code-document-classify-use))。为了检查产生的分类器可靠性如何,我们在测试集上计算其准确性[](./ch06.html#document-classify-test)。再一次的,我们可以使用`show_most_informative_features()`来找出哪些特征是分类器发现最有信息量的[](./ch06.html#document-classify-smif)
```py
featuresets = [(document_features(d), c) for (d,c) in documents]
......@@ -334,7 +334,7 @@ if endswith(,) == False:
为了采取基于词的上下文的特征,我们必须修改以前为我们的特征提取器定义的模式。不是只传递已标注的词,我们将传递整个(未标注的)句子,以及目标词的索引。[1.6](./ch06.html#code-suffix-pos-tag)演示了这种方法,使用依赖上下文的特征提取器定义一个词性标记分类器。
```py
def pos_features(sentence, i): ![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)
def pos_features(sentence, i):
features = {"suffix(1)": sentence[i][-1:],
"suffix(2)": sentence[i][-2:],
"suffix(3)": sentence[i][-3:]}
......@@ -353,12 +353,12 @@ def pos_features(sentence, i): ![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jp
一种序列分类器策略,称为连续分类或贪婪序列分类,是为第一个输入找到最有可能的类标签,然后使用这个问题的答案帮助找到下一个输入的最佳的标签。这个过程可以不断重复直到所有的输入都被贴上标签。这是[5](./ch05.html#sec-n-gram-tagging)中的二元标注器采用的方法,它一开始为句子的第一个词选择词性标记,然后为每个随后的词选择标记,基于词本身和前面词的预测的标记。
[1.7](./ch06.html#code-consecutive-pos-tagger)中演示了这一策略。首先,我们必须扩展我们的特征提取函数使其具有参数`history`,它提供一个我们到目前为止已经为句子预测的标记的列表[![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)](./ch06.html#consec-pos-tag-features)。`history`中的每个标记对应`sentence`中的一个词。但是请注意,`history`将只包含我们已经归类的词的标记,也就是目标词左侧的词。因此,虽然是有可能查看目标词右边的词的某些特征,但查看那些词的标记是不可能的(因为我们还未产生它们)。
[1.7](./ch06.html#code-consecutive-pos-tagger)中演示了这一策略。首先,我们必须扩展我们的特征提取函数使其具有参数`history`,它提供一个我们到目前为止已经为句子预测的标记的列表[](./ch06.html#consec-pos-tag-features)`history`中的每个标记对应`sentence`中的一个词。但是请注意,`history`将只包含我们已经归类的词的标记,也就是目标词左侧的词。因此,虽然是有可能查看目标词右边的词的某些特征,但查看那些词的标记是不可能的(因为我们还未产生它们)。
已经定义了特征提取器,我们可以继续建立我们的序列分类器[![[2]](Images/854532b0c5c8869f9012833955e75b20.jpg)](./ch06.html#consec-pos-tagger)。在训练中,我们使用已标注的标记为征提取器提供适当的历史信息,但标注新的句子时,我们基于标注器本身的输出产生历史信息。
已经定义了特征提取器,我们可以继续建立我们的序列分类器[](./ch06.html#consec-pos-tagger)。在训练中,我们使用已标注的标记为征提取器提供适当的历史信息,但标注新的句子时,我们基于标注器本身的输出产生历史信息。
```py
def pos_features(sentence, i, history): ![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jpg)
def pos_features(sentence, i, history):
features = {"suffix(1)": sentence[i][-1:],
"suffix(2)": sentence[i][-2:],
"suffix(3)": sentence[i][-3:]}
......@@ -370,7 +370,7 @@ def pos_features(sentence, i): ![[1]](Images/78bc6ca8410394dcf6910484bc97e2b6.jp
features["prev-tag"] = history[i-1]
return features
class ConsecutivePosTagger(nltk.TaggerI): ![[2]](Images/854532b0c5c8869f9012833955e75b20.jpg)
class ConsecutivePosTagger(nltk.TaggerI):
def __init__(self, train_sents):
train_set = []
......
......@@ -33,9 +33,9 @@
```py
>>> def ie_preprocess(document):
... sentences = nltk.sent_tokenize(document) ![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)
... sentences = [nltk.word_tokenize(sent) for sent in sentences] ![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)
... sentences = [nltk.pos_tag(sent) for sent in sentences] ![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)
... sentences = nltk.sent_tokenize(document)
... sentences = [nltk.word_tokenize(sent) for sent in sentences]
... sentences = [nltk.pos_tag(sent) for sent in sentences]
```
注意
......@@ -61,20 +61,20 @@
我们将首先思考名词短语词块划分或 NP 词块划分任务,在那里我们寻找单独名词短语对应的词块。例如,这里是一些《华尔街日报》文本,其中的`NP`词块用方括号标记:
```py
>>> sentence = [("the", "DT"), ("little", "JJ"), ("yellow", "JJ"), ![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)
>>> sentence = [("the", "DT"), ("little", "JJ"), ("yellow", "JJ"),
... ("dog", "NN"), ("barked", "VBD"), ("at", "IN"), ("the", "DT"), ("cat", "NN")]
>>> grammar = "NP: {<DT>?<JJ>*<NN>}" ![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)
>>> grammar = "NP: {<DT>?<JJ>*<NN>}"
>>> cp = nltk.RegexpParser(grammar) ![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)
>>> result = cp.parse(sentence) ![[4]](Images/8b4bb6b0ec5bb337fdb00c31efcc1645.jpg)
>>> print(result) ![[5]](Images/bcf758e8278f3295df58c6eace05152c.jpg)
>>> cp = nltk.RegexpParser(grammar)
>>> result = cp.parse(sentence)
>>> print(result)
(S
(NP the/DT little/JJ yellow/JJ dog/NN)
barked/VBD
at/IN
(NP the/DT cat/NN))
>>> result.draw() ![[6]](Images/7bbd845f6f0cf6246561d2859cbcecbf.jpg)
>>> result.draw()
```
![tree_images/ch07-tree-1.png](Images/da516572f97daebe1be746abd7bd2268.jpg)
......@@ -96,7 +96,7 @@ Panamanian/JJ dictator/NN Manuel/NNP Noriega/NNP
要找到一个给定的句子的词块结构,`RegexpParser`词块划分器以一个没有词符被划分的平面结构开始。词块划分规则轮流应用,依次更新词块结构。一旦所有的规则都被调用,返回生成的词块结构。
[2.3](./ch07.html#code-chunker1)显示了一个由 2 个规则组成的简单的词块语法。第一条规则匹配一个可选的限定词或所有格代名词,零个或多个形容词,然后跟一个名词。第二条规则匹配一个或多个专有名词。我们还定义了一个进行词块划分的例句[![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)](./ch07.html#code-chunker1-ex),并在此输入上运行这个词块划分器[![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)](./ch07.html#code-chunker1-run)。
[2.3](./ch07.html#code-chunker1)显示了一个由 2 个规则组成的简单的词块语法。第一条规则匹配一个可选的限定词或所有格代名词,零个或多个形容词,然后跟一个名词。第二条规则匹配一个或多个专有名词。我们还定义了一个进行词块划分的例句[](./ch07.html#code-chunker1-ex),并在此输入上运行这个词块划分器[](./ch07.html#code-chunker1-run)
```py
grammar = r"""
......@@ -104,7 +104,7 @@ grammar = r"""
{<NNP>+} # chunk sequences of proper nouns
"""
cp = nltk.RegexpParser(grammar)
sentence = [("Rapunzel", "NNP"), ("let", "VBD"), ("down", "RP"), ![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)
sentence = [("Rapunzel", "NNP"), ("let", "VBD"), ("down", "RP"),
("her", "PP$"), ("long", "JJ"), ("golden", "JJ"), ("hair", "NN")]
```
......@@ -270,16 +270,16 @@ ChunkParse score:
正如你看到的,这种方法达到相当好的结果。但是,我们可以采用更多数据驱动的方法改善它,在这里我们使用训练语料找到对每个词性标记最有可能的块标记(`I`, `O`或`B`)。换句话说,我们可以使用 _ 一元标注器 _([4](./ch05.html#sec-automatic-tagging))建立一个词块划分器。但不是尝试确定每个词的正确的词性标记,而是根据每个词的词性标记,尝试确定正确的词块标记。
在[3.1](./ch07.html#code-unigram-chunker)中,我们定义了`UnigramChunker`类,使用一元标注器给句子加词块标记。这个类的大部分代码只是用来在 NLTK 的`ChunkParserI`接口使用的词块树表示和嵌入式标注器使用的 IOB 表示之间镜像转换。类定义了两个方法:一个构造函数[![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)](./ch07.html#code-unigram-chunker-constructor),当我们建立一个新的 UnigramChunker 时调用;以及`parse`方法[![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)](./ch07.html#code-unigram-chunker-parse),用来给新句子划分词块。
在[3.1](./ch07.html#code-unigram-chunker)中,我们定义了`UnigramChunker`类,使用一元标注器给句子加词块标记。这个类的大部分代码只是用来在 NLTK 的`ChunkParserI`接口使用的词块树表示和嵌入式标注器使用的 IOB 表示之间镜像转换。类定义了两个方法:一个构造函数[❶](./ch07.html#code-unigram-chunker-constructor),当我们建立一个新的 UnigramChunker 时调用;以及`parse`方法[❸](./ch07.html#code-unigram-chunker-parse),用来给新句子划分词块。
```py
class UnigramChunker(nltk.ChunkParserI):
def __init__(self, train_sents): ![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)
def __init__(self, train_sents):
train_data = [[(t,c) for w,t,c in nltk.chunk.tree2conlltags(sent)]
for sent in train_sents]
self.tagger = nltk.UnigramTagger(train_data) ![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)
self.tagger = nltk.UnigramTagger(train_data)
def parse(self, sentence): ![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)
def parse(self, sentence):
pos_tags = [pos for (word,pos) in sentence]
tagged_pos_tags = self.tagger.tag(pos_tags)
chunktags = [chunktag for (pos, chunktag) in tagged_pos_tags]
......@@ -288,9 +288,9 @@ class UnigramChunker(nltk.ChunkParserI):
return nltk.chunk.conlltags2tree(conlltags)
```
构造函数[![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)](./ch07.html#code-unigram-chunker-constructor)需要训练句子的一个列表,这将是词块树的形式。它首先将训练数据转换成适合训练标注器的形式,使用`tree2conlltags`映射每个词块树到一个`word,tag,chunk`三元组的列表。然后使用转换好的训练数据训练一个一元标注器,并存储在`self.tagger`供以后使用。
构造函数[](./ch07.html#code-unigram-chunker-constructor)需要训练句子的一个列表,这将是词块树的形式。它首先将训练数据转换成适合训练标注器的形式,使用`tree2conlltags`映射每个词块树到一个`word,tag,chunk`三元组的列表。然后使用转换好的训练数据训练一个一元标注器,并存储在`self.tagger`供以后使用。
`parse`方法[![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)](./ch07.html#code-unigram-chunker-parse)接收一个已标注的句子作为其输入,以从那句话提取词性标记开始。它然后使用在构造函数中训练过的标注器`self.tagger`,为词性标记标注 IOB 词块标记。接下来,它提取词块标记,与原句组合,产生`conlltags`。最后,它使用`conlltags2tree`将结果转换成一个词块树。
`parse`方法[](./ch07.html#code-unigram-chunker-parse)接收一个已标注的句子作为其输入,以从那句话提取词性标记开始。它然后使用在构造函数中训练过的标注器`self.tagger`,为词性标记标注 IOB 词块标记。接下来,它提取词块标记,与原句组合,产生`conlltags`。最后,它使用`conlltags2tree`将结果转换成一个词块树。
现在我们有了`UnigramChunker`,可以使用 CoNLL2000 语料库训练它,并测试其表现:
......@@ -326,7 +326,7 @@ ChunkParse score:
它已经发现大多数标点符号出现在 NP 词块外,除了两种货币符号`#`和`它也发现限定词(`DT`)和所有格(`PRP
建立了一个一元分块器,很容易建立一个二元分块器:我们只需要改变类的名称为`BigramChunker`,修改[3.1](./ch07.html#code-unigram-chunker)行[![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)](./ch07.html#code-unigram-chunker-buildit)构造一个`BigramTagger`而不是`UnigramTagger`。由此产生的词块划分器的性能略高于一元词块划分器:
建立了一个一元分块器,很容易建立一个二元分块器:我们只需要改变类的名称为`BigramChunker`,修改[3.1](./ch07.html#code-unigram-chunker)行[](./ch07.html#code-unigram-chunker-buildit)构造一个`BigramTagger`而不是`UnigramTagger`。由此产生的词块划分器的性能略高于一元词块划分器:
```py
>>> bigram_chunker = BigramChunker(train_sents)
......@@ -343,7 +343,7 @@ ChunkParse score:
无论是基于正则表达式的词块划分器还是 n-gram 词块划分器,决定创建什么词块完全基于词性标记。然而,有时词性标记不足以确定一个句子应如何划分词块。例如,考虑下面的两个语句:
```py
class ConsecutiveNPChunkTagger(nltk.TaggerI): ![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)
class ConsecutiveNPChunkTagger(nltk.TaggerI):
def __init__(self, train_sents):
train_set = []
......@@ -351,10 +351,10 @@ class ConsecutiveNPChunkTagger(nltk.TaggerI): ![[1]](Images/f4891d12ae20c39b6859
untagged_sent = nltk.tag.untag(tagged_sent)
history = []
for i, (word, tag) in enumerate(tagged_sent):
featureset = npchunk_features(untagged_sent, i, history) ![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)
featureset = npchunk_features(untagged_sent, i, history)
train_set.append( (featureset, tag) )
history.append(tag)
self.classifier = nltk.MaxentClassifier.train( ![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)
self.classifier = nltk.MaxentClassifier.train(
train_set, algorithm='megam', trace=0)
def tag(self, sentence):
......@@ -365,7 +365,7 @@ class ConsecutiveNPChunkTagger(nltk.TaggerI): ![[1]](Images/f4891d12ae20c39b6859
history.append(tag)
return zip(sentence, history)
class ConsecutiveNPChunker(nltk.ChunkParserI): ![[4]](Images/8b4bb6b0ec5bb337fdb00c31efcc1645.jpg)
class ConsecutiveNPChunker(nltk.ChunkParserI):
def __init__(self, train_sents):
tagged_sents = [[((w,t),c) for (w,t,c) in
nltk.chunk.tree2conlltags(sent)]
......@@ -431,7 +431,7 @@ ChunkParse score:
F-Measure: 86.7%
```
最后,我们尝试用多种附加特征扩展特征提取器,例如预取特征[![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)](./ch07.html#chunk-fe-lookahead)、配对特征[![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)](./ch07.html#chunk-fe-paired)和复杂的语境特征[![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)](./ch07.html#chunk-fe-complex)。这最后一个特征,称为`tags-since-dt`,创建一个字符串,描述自最近的限定词以来遇到的所有词性标记,或如果没有限定词则在索引`i`之前自语句开始以来遇到的所有词性标记。
最后,我们尝试用多种附加特征扩展特征提取器,例如预取特征[❶](./ch07.html#chunk-fe-lookahead)、配对特征[❷](./ch07.html#chunk-fe-paired)和复杂的语境特征[❸](./ch07.html#chunk-fe-complex)。这最后一个特征,称为`tags-since-dt`,创建一个字符串,描述自最近的限定词以来遇到的所有词性标记,或如果没有限定词则在索引`i`之前自语句开始以来遇到的所有词性标记。
```py
>>> def npchunk_features(sentence, i, history):
......@@ -447,10 +447,10 @@ ChunkParse score:
... return {"pos": pos,
... "word": word,
... "prevpos": prevpos,
... "nextpos": nextpos, ![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)
... "prevpos+pos": "%s+%s" % (prevpos, pos), ![[2]](Images/e5fb07e997b9718f18dbf677e3d6634d.jpg)
... "nextpos": nextpos,
... "prevpos+pos": "%s+%s" % (prevpos, pos),
... "pos+nextpos": "%s+%s" % (pos, nextpos),
... "tags-since-dt": tags_since_dt(sentence, i)} ![[3]](Images/6372ba4f28e69f0b220c75a9b2f4decf.jpg)
... "tags-since-dt": tags_since_dt(sentence, i)}
```
```py
......@@ -496,7 +496,7 @@ sentence = [("Mary", "NN"), ("saw", "VBD"), ("the", "DT"), ("cat", "NN"),
("sit", "VB"), ("on", "IN"), ("the", "DT"), ("mat", "NN")]
```
不幸的是,这一结果丢掉了 saw 为首的`VP`。它还有其他缺陷。当我们将此词块划分器应用到一个有更深嵌套的句子时,让我们看看会发生什么。请注意,它无法识别[![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)](./ch07.html#saw-vbd)开始的`VP`词块。
不幸的是,这一结果丢掉了 saw 为首的`VP`。它还有其他缺陷。当我们将此词块划分器应用到一个有更深嵌套的句子时,让我们看看会发生什么。请注意,它无法识别[](./ch07.html#saw-vbd)开始的`VP`词块。
```py
>>> sentence = [("John", "NNP"), ("thinks", "VBZ"), ("Mary", "NN"),
......@@ -678,7 +678,7 @@ Hogeschool N B-ORG
搜索关键字 in 执行的相当不错,虽然它的检索结果也会误报,例如`[ORG: House Transportation Committee] , secured the most money in the [LOC: New York]`;一种简单的基于字符串的方法排除这样的填充字符串似乎不太可能。
如前文所示,`conll2002`命名实体语料库的荷兰语部分不只包含命名实体标注,也包含词性标注。这允许我们设计对这些标记敏感的模式,如下面的例子所示。`clause()`方法以分条形式输出关系,其中二元关系符号作为参数`relsym`的值被指定[![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)](./ch07.html#relsym)。
如前文所示,`conll2002`命名实体语料库的荷兰语部分不只包含命名实体标注,也包含词性标注。这允许我们设计对这些标记敏感的模式,如下面的例子所示。`clause()`方法以分条形式输出关系,其中二元关系符号作为参数`relsym`的值被指定[](./ch07.html#relsym)。
```py
>>> from nltk.corpus import conll2002
......@@ -696,7 +696,7 @@ Hogeschool N B-ORG
>>> for doc in conll2002.chunked_sents('ned.train'):
... for r in nltk.sem.extract_rels('PER', 'ORG', doc,
... corpus='conll2002', pattern=VAN):
... print(nltk.sem.clause(r, relsym="VAN")) ![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)
... print(nltk.sem.clause(r, relsym="VAN"))
VAN("cornet_d'elzius", 'buitenlandse_handel')
VAN('johan_rottiers', 'kardinaal_van_roey_instituut')
VAN('annie_lennox', 'eurythmics')
......@@ -704,7 +704,7 @@ VAN('annie_lennox', 'eurythmics')
注意
**轮到你来:**替换最后一行[![[1]](Images/f4891d12ae20c39b685951ad3cddf1aa.jpg)](./ch07.html#relsym)为`print(rtuple(rel, lcon=True, rcon=True))`。这将显示实际的词表示两个 NE 之间关系以及它们左右的默认 10 个词的窗口的上下文。在一本荷兰语词典的帮助下,你也许能够找出为什么结果`VAN('annie_lennox', 'eurythmics')`是个误报。
**轮到你来:**替换最后一行[](./ch07.html#relsym)为`print(rtuple(rel, lcon=True, rcon=True))`。这将显示实际的词表示两个 NE 之间关系以及它们左右的默认 10 个词的窗口的上下文。在一本荷兰语词典的帮助下,你也许能够找出为什么结果`VAN('annie_lennox', 'eurythmics')`是个误报。
## 7 小结
......
......@@ -90,12 +90,12 @@ N[NUM=pl]
一般情况下,即使我们正在尝试开发很小的语法,把产生式放在一个文件中我们可以编辑、测试和修改是很方便的。我们将[1.1](./ch09.html#code-feat0cfg)以 NLTK 的数据格式保存为文件`'feat0.fcfg'`。你可以使用`nltk.data.load()`制作你自己的副本进行进一步的实验。
[1.2](./ch09.html#code-featurecharttrace) 说明了基于特征的语法图表解析的操作。为输入分词之后,我们导入`load_parser`函数[![[1]](Images/77460905bcad52d84e324fc4821ed903.jpg)](./ch09.html#load_parser1),以语法文件名为输入,返回一个图表分析器`cp` [![[2]](Images/07e7d99633e4a107388f7202380cce55.jpg)](./ch09.html#load_parser2)。调用分析器的`parse()`方法将迭代生成的分析树;如果文法无法分析输入,`trees`将为空,并将会包含一个或多个分析树,取决于输入是否有句法歧义。
[1.2](./ch09.html#code-featurecharttrace) 说明了基于特征的语法图表解析的操作。为输入分词之后,我们导入`load_parser`函数[](./ch09.html#load_parser1),以语法文件名为输入,返回一个图表分析器`cp` [](./ch09.html#load_parser2)。调用分析器的`parse()`方法将迭代生成的分析树;如果文法无法分析输入,`trees`将为空,并将会包含一个或多个分析树,取决于输入是否有句法歧义。
```py
>>> tokens = 'Kim likes children'.split()
>>> from nltk import load_parser ![[1]](Images/77460905bcad52d84e324fc4821ed903.jpg)
>>> cp = load_parser('grammars/book_grammars/feat0.fcfg', trace=2) ![[2]](Images/07e7d99633e4a107388f7202380cce55.jpg)
>>> from nltk import load_parser
>>> cp = load_parser('grammars/book_grammars/feat0.fcfg', trace=2)
>>> for tree in cp.parse(tokens):
... print(tree)
...
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册