提交 11a66740 编写于 作者: W wizardforcel

2020-12-29 14:21:35

上级 544f90aa
......@@ -208,7 +208,7 @@ Note
>>>
```
《创世纪》有 44764 个词和标点符号或者叫“词符”。词符 表示一个我们想要整体对待的字符序列 —— 例如`hairy``his``:)`。当我们计数文本如 to be or not to be 这个短语中词符的个数时,我们计数这些序列出现的次数。因此,我们的例句中出现了 to 和 be 各两次,or 和 not 各一次。然而在例句中只有 4 个不同的词。《创世纪》中有多少不同的词?要用 Python 来回答这个问题,我们处理问题的方法将稍有改变。一个文本词汇表只是它用到的词符的*集合*,因为在集合中所有重复的元素都只算一个。Python 中我们可以使用命令:`set(text3)`获得`text3`的词汇表。当你这样做时,屏幕上的很多词会掠过。现在尝试以下操作:
《创世纪》有 44764 个词和标点符号或者叫“词符”。词符 表示一个我们想要整体对待的字符序列 —— 例如`hairy``his``:)`。当我们计数文本如`to be or not to be`这个短语中词符的个数时,我们计数这些序列出现的次数。因此,我们的例句中出现了`to``be`各两次,`or``not`各一次。然而在例句中只有 4 个不同的词。《创世纪》中有多少不同的词?要用 Python 来回答这个问题,我们处理问题的方法将稍有改变。一个文本词汇表只是它用到的词符的*集合*,因为在集合中所有重复的元素都只算一个。Python 中我们可以使用命令:`set(text3)`获得`text3`的词汇表。当你这样做时,屏幕上的很多词会掠过。现在尝试以下操作:
```py
>>> sorted(set(text3))
......@@ -220,7 +220,7 @@ Note
>>>
```
`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 个元素中包括标点符号,所以我们把这些叫做唯一元素类型而不是词类型。
`sorted()`包裹起 Python 表达式`set(text3)`❶,我们得到一个词汇项的排序表,这个表以各种标点符号开始,然后是以`A`开头的词汇。大写单词排在小写单词前面。我们通过求集合中元素的个数间接获得词汇表的大小,再次使用`len`来获得这个数值❷。尽管小说中有 44,764 个词符,但只有 2,789 个不同的单词或“词类型”。一个词类型是指一个词在一个文本中独一无二的出现形式或拼写 —— 也就是说,这个词在词汇表中是唯一的。我们计数的 2,789 个元素中包括标点符号,所以我们把这些叫做唯一元素类型而不是词类型。
现在,让我们对文本词汇丰富度进行测量。下一个例子向我们展示,不同的单词数目只是单词总数的 6%,或者每个单词平均被使用了 16 次(记住,如果你使用的是 Python 2,请在开始输入`from __future__ import division`)。
......@@ -259,7 +259,7 @@ Note
当遇到第一行末尾的冒号后,Python 解释器提示符由`>>>`变为`...``...`提示符表示 Python 期望在后面是一个缩进代码块 。缩进是输入四个空格还是敲击 Tab 键,这由你决定。要结束一个缩进代码段,只需输入一个空行。
`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)
`lexical_diversity()`❶的定义中,我们指定了一个`text`参数。这个参数是我们想要计算词汇多样性的实际文本的一个“占位符”,并在用到这个函数的时候出现在将要运行的代码块中❷。类似地,`percentage()`定义了两个参数,`count``total`
只要 Python 知道了`lexical_diversity()``percentage()`是指定代码段的名字,我们就可以继续使用这些函数:
......@@ -290,7 +290,7 @@ Note
>>>
```
在提示符后面,我们输入自己命名的`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)
在提示符后面,我们输入自己命名的`sent1`,后跟一个等号,然后是一些引用的词汇,中间用逗号分割并用括号包围。这个方括号内的东西在 Python 中叫做列表:它就是我们存储文本的方式。我们可以通过输入它的名字来查阅它❶。我们可以查询它的长度❷。我们甚至可以对它调用我们自己的函数`lexical_diversity()`
```py
>>> sent1
......@@ -318,7 +318,7 @@ Note
**轮到你来:** 通过输入名字、等号和一个单词列表, 组建几个你自己的句子,如`ex1 = ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail']`。重复一些我们先前在第 1 节看到的其他 Python 操作,如:`sorted(ex1)`, `len(set(ex1))`, `ex1.count('the')`
令人惊喜的是,我们可以对列表使用 Python 加法运算。两个列表相加[](http://www.nltk.org/book/ch01.html#list-plus-list)创造出一个新的列表,包括第一个列表的全部,后面跟着第二个列表的全部。
令人惊喜的是,我们可以对列表使用 Python 加法运算。两个列表相加创造出一个新的列表,包括第一个列表的全部,后面跟着第二个列表的全部。
```py
>>> ['Monty', 'Python'] + ['and', 'the', 'Holy', 'Grail']
......@@ -426,7 +426,7 @@ IndexError: list index out of range
>>>
```
按照惯例,`m:n`表示元素 m…n-1。正如下一个例子显示的那样,如果切片从列表第一个元素开始,我们可以省略第一个数字[](http://www.nltk.org/book/ch01.html#slice2), 如果切片到列表最后一个元素处结尾,我们可以省略第二个数字 [](http://www.nltk.org/book/ch01.html#slice3)
按照惯例,`m:n`表示元素 m…n-1。正如下一个例子显示的那样,如果切片从列表第一个元素开始,我们可以省略第一个数字❶, 如果切片到列表最后一个元素处结尾,我们可以省略第二个数字❷
```py
>>> sent[:3]
......@@ -441,7 +441,7 @@ IndexError: list index out of range
>>>
```
我们可以通过赋值给它的索引值来修改列表中的元素。在接下来的例子中,我们把`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)
我们可以通过赋值给它的索引值来修改列表中的元素。在接下来的例子中,我们把`sent[0]`放在等号左侧❶。我们也可以用新内容替换掉一整个片段❷。最后一个尝试报错的原因是这个链表只有四个元素而要获取其后面的元素就产生了错误❸
```py
>>> sent[0] = 'First'
......@@ -518,7 +518,7 @@ SyntaxError: invalid syntax
## 2.4 字符串
我们用来访问列表元素的一些方法也可以用在单独的词或字符串上。例如可以把一个字符串指定给一个变量[](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'
......@@ -599,7 +599,7 @@ what output do you expect here?
>>>
```
第一次调用`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)
第一次调用`FreqDist`时,传递文本的名称作为参数❶。我们可以看到已经被计算出来的《白鲸记》中的总的词数(“outcomes”)—— 260,819❷。表达式`most_common(50)`给出文本中 50 个出现频率最高的单词类型❸
注意
......@@ -702,7 +702,7 @@ FreqDist({3: 50223, 1: 47933, 4: 42345, 2: 38513, 5: 26597, 6: 17111, 7: 14399,
>>>
```
我们以导出`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 的词多?等等)。下面我们回答这个问题:
我们以导出`text1`中每个词的长度的列表开始❶,然后`FreqDist`计数列表中每个数字出现的次数❷。结果❸是一个包含 25 万左右个元素的分布,每一个元素是一个数字,对应文本中一个词标识符。但是只有 20 个不同的元素,从 1 到 20,因为只有 20 个不同的词长。也就是说,有由 1 个字符,2 个字符,...,20 个字符组成的词,而没有由 21 个或更多字符组成的词。有人可能会问不同长度的词的频率是多少?(例如,文本中有多少长度为 4 的词?长度为 5 的词是否比长度为 4 的词多?等等)。下面我们回答这个问题:
```py
>>> fdist.most_common()
......@@ -775,7 +775,7 @@ NLTK 频率分布类中定义的函数
## 4.2 对每个元素进行操作
[3](http://www.nltk.org/book/ch01.html#sec-computing-with-language-simple-statistics)节中,我们看到计数词汇以外的其他项目的一些例子。让我们仔细看看我们所使用的符号:
3节中,我们看到计数词汇以外的其他项目的一些例子。让我们仔细看看我们所使用的符号:
```py
>>> [len(w) for w in text1]
......@@ -828,7 +828,7 @@ word length is less than 5
>>>
```
使用 Python 解释器时,我们必须添加一个额外的空白行[](http://www.nltk.org/book/ch01.html#blank-line),这样它才能检测到嵌套块结束。
使用 Python 解释器时,我们必须添加一个额外的空白行,这样它才能检测到嵌套块结束。
注意
......
......@@ -59,7 +59,7 @@ SELECT City FROM city_table WHERE Country="china"
canton chungking dairen harbin kowloon mukden peking shanghai sian tientsin
```
由于每行`r`是一个单元素的元组,我们输出元组的成员,而不是元组本身[](./ch10.html#tuple-val)
由于每行`r`是一个单元素的元组,我们输出元组的成员,而不是元组本身
总结一下,我们已经定义了一个任务:计算机对自然语言查询做出反应,返回有用的数据。我们通过将英语的一个小的子集翻译成 SQL 来实现这个任务们可以说,我们的 NLTK 代码已经“理解”SQL,只要 Python 能够对数据库执行 SQL 查询,通过扩展,它也“理解”如 What cities are located in China 这样的查询。这相当于自然语言理解的例子能够从荷兰语翻译成英语。假设你是一个英语为母语的人,已经开始学习荷兰语。你的老师问你是否理解[(3)](./ch10.html#ex-sem1)的意思:
......@@ -509,7 +509,7 @@ IV[SEM=<\x.bark(x)>] -> 'barks'
## 4.2 λ演算
[3](./ch01.html#sec-computing-with-language-simple-statistics)中,我们指出数学集合符号对于制定我们想从文档中选择的词的属性 P 很有用。我们用[(31)](./ch10.html#ex-set-comprehension-math2)说明这个,它是“所有 w 的集合,其中 w 是 V(词汇表)的元素且 w 有属性 P”的表示。
3中,我们指出数学集合符号对于制定我们想从文档中选择的词的属性 P 很有用。我们用[(31)](./ch10.html#ex-set-comprehension-math2)说明这个,它是“所有 w 的集合,其中 w 是 V(词汇表)的元素且 w 有属性 P”的表示。
```py
>>> read_expr = nltk.sem.Expression.fromstring
......@@ -679,7 +679,7 @@ all x.(girl(x) -> exists z4.(dog(z4) & chase(x,z4)))
```
我们可以使用`draw()`方法[](./ch10.html#draw-drs)可视化结果,如 5.2 所示。
我们可以使用`draw()`方法可视化结果,如 5.2 所示。
```py
>>> drs1.draw()
......
......@@ -80,7 +80,7 @@ TIMIT 的第四个特点是语料库的层次结构。每个句子 4 个文件
## 2.1 语料库创建的三种方案
语料库的一种类型是设计在创作者的探索过程中逐步展现。这是典型的传统“领域语言学”模式,即来自会话的材料在它被收集的时候就被分析,明天的想法往往基于今天的分析中产生的问题。。在随后几年的研究中产生的语料不断被使用,并可能用作不确定的档案资源。计算机化明显有利于这种类型的工作,以广受欢迎的程序 Shoebox 为例,它作为 Toolbox 重新发布,现在已有超过二十年的历史(见[4](./ch02.html#sec-lexical-resources))。其他的软件工具,甚至是简单的文字处理器和电子表格,通常也可用于采集数据。在下一节,我们将着眼于如何从这些来源提取数据。
语料库的一种类型是设计在创作者的探索过程中逐步展现。这是典型的传统“领域语言学”模式,即来自会话的材料在它被收集的时候就被分析,明天的想法往往基于今天的分析中产生的问题。。在随后几年的研究中产生的语料不断被使用,并可能用作不确定的档案资源。计算机化明显有利于这种类型的工作,以广受欢迎的程序 Shoebox 为例,它作为 Toolbox 重新发布,现在已有超过二十年的历史(见4)。其他的软件工具,甚至是简单的文字处理器和电子表格,通常也可用于采集数据。在下一节,我们将着眼于如何从这些来源提取数据。
另一种语料库创建方案是典型的实验研究,其中一些精心设计的材料被从一定范围的人类受试者中收集,然后进行分析来评估一个假设或开发一种技术。此类数据库在实验室或公司内被共享和重用已很常见,经常被更广泛的发布。这种类型的语料库是“共同任务”的科研管理方法的基础,这在过去的二十年已成为政府资助的语言技术研究项目。在前面的章节中,我们已经遇到很多这样的语料库;我们将看到如何编写 Python 程序实践这些语料库发布前必要的一些任务。
......@@ -221,9 +221,9 @@ f_out.write(bytes(s, 'UTF-8'))
已标注语言数据很少以最方便的格式保存,往往需要进行各种格式转换。字符编码之间的转换已经讨论过(见 3.3)。在这里,我们专注于数据结构。
最简单的情况,输入和输出格式是同构的。例如,我们可能要将词汇数据从 Toolbox 格式转换为 XML,可以直接一次一个的转换词条([4](./ch11.html#sec-working-with-xml))。数据结构反映在所需的程序的结构中:一个`for`循环,每次循环处理一个词条。
最简单的情况,输入和输出格式是同构的。例如,我们可能要将词汇数据从 Toolbox 格式转换为 XML,可以直接一次一个的转换词条(4)。数据结构反映在所需的程序的结构中:一个`for`循环,每次循环处理一个词条。
另一种常见的情况,输出是输入的摘要形式,如一个倒置的文件索引。有必要在内存中建立索引结构(见 4.8),然后把它以所需的格式写入一个文件。下面的例子构造一个索引,映射字典定义的词汇到相应的每个词条[](./ch11.html#map-word-lexeme)的语意[](./ch11.html#lexical-entry),已经对定义文本分词[](./ch11.html#definition-text),并丢弃短词[](./ch11.html#short-words)。一旦该索引建成,我们打开一个文件,然后遍历索引项,以所需的格式输出行[](./ch11.html#required-format)
另一种常见的情况,输出是输入的摘要形式,如一个倒置的文件索引。有必要在内存中建立索引结构(见 4.8),然后把它以所需的格式写入一个文件。下面的例子构造一个索引,映射字典定义的词汇到相应的每个词条❶的语意❷,已经对定义文本分词❸,并丢弃短词❹。一旦该索引建成,我们打开一个文件,然后遍历索引项,以所需的格式输出行❺
```py
>>> idx = nltk.Index((defn_word, lexeme)
......@@ -259,7 +259,7 @@ sleep: wake
发布的语料库中所包含的信息的丰富性差别很大。语料库最低限度通常会包含至少一个声音或字形符号的序列。事情的另一面,一个语料库可以包含大量的信息,如句法结构、形态、韵律、每个句子的语义、加上段落关系或对话行为的标注。标注的这些额外的层可能正是有人执行一个特定的数据分析任务所需要的。例如,如果我们可以搜索特定的句法结构,找到一个给定的语言模式就更容易;如果每个词都标注了意义,为语言模式归类就更容易。这里提供一些常用的标注层:
* 分词:文本的书写形式不能明确地识别它的词符。分词和规范化的版本作为常规的正式版本的补充可能是一个非常方便的资源。
* 断句:正如我们在[3](./ch03.html#chap-words)中看到的,断句比它看上去的似乎更加困难。因此,一些语料库使用明确的标注来断句。
* 断句:正如我们在3中看到的,断句比它看上去的似乎更加困难。因此,一些语料库使用明确的标注来断句。
* 分段:段和其他结构元素(标题,章节等)可能会明确注明。
* 词性:文档中的每个单词的词类。
* 句法结构:一个树状结构,显示一个句子的组成结构。
......@@ -272,7 +272,7 @@ sleep: wake
一个用途广泛的语料库需要支持广泛的格式。然而,NLP 研究的前沿需要各种新定义的没有得到广泛支持的标注。一般情况下,并没有广泛使用的适当的创作、发布和使用语言数据的工具。大多数项目都必须制定它们自己的一套工具,供内部使用,这对缺乏必要的资源的其他人没有任何帮助。此外,我们还没有一个可以胜任的普遍接受的标准来表示语料库的结构和内容。没有这样的标准,就不可能有通用的工具——同时,没有可用的工具,适当的标准也不太可能被开发、使用和接受。
针对这种情况的一个反应就是开拓未来开发一种通用的能充分表现捕获多种标注类型(见[8](./ch11.html#sec-further-reading-data)的例子)的格式。NLP 的挑战是编写程序处理这种格式的泛化。例如,如果编程任务涉及树数据,文件格式允许任意有向图,那么必须验证输入数据检查树的属性如根、连通性、无环。如果输入文件包含其他层的标注,该程序将需要知道数据加载时如何忽略它们,将树数据保存到文件时不能否定或抹杀这些层。
针对这种情况的一个反应就是开拓未来开发一种通用的能充分表现捕获多种标注类型(见8的例子)的格式。NLP 的挑战是编写程序处理这种格式的泛化。例如,如果编程任务涉及树数据,文件格式允许任意有向图,那么必须验证输入数据检查树的属性如根、连通性、无环。如果输入文件包含其他层的标注,该程序将需要知道数据加载时如何忽略它们,将树数据保存到文件时不能否定或抹杀这些层。
另一种反应一直是写一个一次性的脚本来操纵语料格式;这样的脚本将许多 NLP 研究人员的文件夹弄得乱七八糟。在语料格式解析工作应该只进行一次(每编程语言)的前提下,NLTK 中的语料库阅读器是更系统的方法。
......@@ -340,7 +340,7 @@ sleep: wake
['anicular', 'inocular', 'nucellar', 'nuclear', 'unocular', 'uniocular', 'unicolor']
```
这仅仅是一个演示,其中一个简单的程序就可以方便的访问语言书写系统可能不规范或语言的使用者可能拼写的不是很好的上下文中的词汇数据。其他简单的 NLP 在这个领域的应用包括:建立索引以方便对数据的访问,从文本中拾取词汇表,构建词典时定位词语用法的例子,在知之甚少的数据中检测普遍或特殊模式,并在创建的数据上使用各种语言的软件工具执行专门的验证。我们将在[5](./ch11.html#sec-working-with-toolbox-data)返回到其中的最后一个。
这仅仅是一个演示,其中一个简单的程序就可以方便的访问语言书写系统可能不规范或语言的使用者可能拼写的不是很好的上下文中的词汇数据。其他简单的 NLP 在这个领域的应用包括:建立索引以方便对数据的访问,从文本中拾取词汇表,构建词典时定位词语用法的例子,在知之甚少的数据中检测普遍或特殊模式,并在创建的数据上使用各种语言的软件工具执行专门的验证。我们将在5返回到其中的最后一个。
## 4 使用 XML
......@@ -372,7 +372,7 @@ sleep: wake
Python 的 ElementTree 模块提供了一种方便的方式访问存储在 XML 文件中的数据。ElementTree 是 Python 标准库(自从 Python 2.5)的一部分,也作为 NLTK 的一部分提供,以防你在使用 Python 2.4。
我们将使用 XML 格式的莎士比亚戏剧集来说明 ElementTree 的使用方法。让我们加载 XML 文件并检查原始数据,首先在文件的顶部[](./ch11.html#top-of-file),在那里我们看到一些 XML 头和一个名为`play.dtd`的模式,接着是根元素`PLAY`。我们从 Act 1[](./ch11.html#start-act-one)再次获得数据。(输出中省略了一些空白行。)
我们将使用 XML 格式的莎士比亚戏剧集来说明 ElementTree 的使用方法。让我们加载 XML 文件并检查原始数据,首先在文件的顶部❶,在那里我们看到一些 XML 头和一个名为`play.dtd`的模式,接着是根元素`PLAY`。我们从 Act 1❷再次获得数据。(输出中省略了一些空白行。)
```py
>>> merchant_file = nltk.data.find('corpora/shakespeare/merchant.xml')
......@@ -394,7 +394,7 @@ Python 的 ElementTree 模块提供了一种方便的方式访问存储在 XML
我们刚刚访问了作为一个字符串的 XML 数据。正如我们看到的,在 Act 1 开始处的字符串包含 XML 标记 title、scene、stage directions 等。
下一步是作为结构化的 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)
下一步是作为结构化的 XML 数据使用`ElementTree`处理文件的内容。我们正在处理一个文件(一个多行字符串),并建立一棵树,所以方法的名称是`parse`❶并不奇怪。变量`merchant`包含一个 XML 元素`PLAY`❷。此元素有内部结构;我们可以使用一个索引来得到它的第一个孩子,一个`TITLE`元素❸。我们还可以看到该元素的文本内容:戏剧的标题❹。要得到所有的子元素的列表,我们使用`getchildren()`方法❺
```py
>>> from xml.etree.ElementTree import ElementTree
......@@ -475,7 +475,7 @@ Act 5 Scene 1 Speech 32: No better a musician than the wren.
('GRATIANO', 48), ('LORENZO', 47)]
```
我们也可以查看对话中谁跟着谁的模式。由于有 23 个演员,我们需要首先使用[3](./ch05.html#sec-dictionaries)中描述的方法将“词汇”减少到可处理的大小。
我们也可以查看对话中谁跟着谁的模式。由于有 23 个演员,我们需要首先使用3中描述的方法将“词汇”减少到可处理的大小。
```py
>>> from collections import defaultdict
......@@ -499,7 +499,7 @@ SHYL 15 15 2 26 21 0
## 4.4 使用 ElementTree 访问 Toolbox 数据
[4](./ch02.html#sec-lexical-resources)中,我们看到了一个访问 Toolbox 数据的简单的接口,Toolbox 数据是语言学家用来管理数据的一种流行和行之有效的格式。这一节中,我们将讨论以 Toolbox 软件所不支持的方式操纵 Toolbox 数据的各种技术。我们讨论的方法也可以应用到其他记录结构化数据,不必管实际的文件格式。
4中,我们看到了一个访问 Toolbox 数据的简单的接口,Toolbox 数据是语言学家用来管理数据的一种流行和行之有效的格式。这一节中,我们将讨论以 Toolbox 软件所不支持的方式操纵 Toolbox 数据的各种技术。我们讨论的方法也可以应用到其他记录结构化数据,不必管实际的文件格式。
我们可以用`toolbox.xml()`方法来访问 Toolbox 文件,将它加载到一个`elementtree`对象中。此文件包含一个巴布亚新几内亚罗托卡特语的词典。
......@@ -527,7 +527,7 @@ SHYL 15 15 2 26 21 0
'kaakasi', 'kaakau', 'kaakauko', 'kaakito', 'kaakuupato', ..., 'kuvuto']
```
让我们查看 XML 格式的 Toolbox 数据。`ElementTree``write()`方法需要一个文件对象。我们通常使用 Python 内置的`open()`函数创建。为了屏幕上显示输出,我们可以使用一个特殊的预定义的文件对象称为`stdout` [](./ch11.html#sys-stdout) (标准输出),在 Python 的`sys`模块中定义的。
让我们查看 XML 格式的 Toolbox 数据。`ElementTree``write()`方法需要一个文件对象。我们通常使用 Python 内置的`open()`函数创建。为了屏幕上显示输出,我们可以使用一个特殊的预定义的文件对象称为`stdout` (标准输出),在 Python 的`sys`模块中定义的。
```py
>>> import sys
......@@ -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 中的语法使用我们在[8.](./ch08.html#chap-parse)看到的 CFG 格式。这样的语法模型隐含 Toolbox 条目的嵌套结构,建立一个树状结构,树的叶子是单独的字段名。最后,我们遍历条目并报告它们与语法的一致性,如 5.2 所示。那些被语法接受的在前面加一个`'+'` [](./ch11.html#accepted-entries),那些被语法拒绝的在前面加一个`'-'` [](./ch11.html#rejected-entries)。在开发这样一个文法的过程中,它可以帮助过滤掉一些标签[](./ch11.html#ignored-tags)
检查完高频字段序列后,我们可以设计一个词汇条目的上下文无关语法。在 5.2 中的语法使用我们在[8.](./ch08.html#chap-parse)看到的 CFG 格式。这样的语法模型隐含 Toolbox 条目的嵌套结构,建立一个树状结构,树的叶子是单独的字段名。最后,我们遍历条目并报告它们与语法的一致性,如 5.2 所示。那些被语法接受的在前面加一个`'+'`❶,那些被语法拒绝的在前面加一个`'-'`❷。在开发这样一个文法的过程中,它可以帮助过滤掉一些标签❸
```py
grammar = nltk.CFG.fromstring('''
......
此差异已折叠。
......@@ -93,7 +93,7 @@ Project Gutenberg; Dmitri Prokofitch; Andrey Semyonovitch; Hay Market
0
```
方法`find()``rfind()`(反向的 find)帮助我们得到字符串切片需要用到的正确的索引值[](./ch03.html#raw-slice)。我们用这个切片重新给`raw`赋值,所以现在它以“PART I”开始一直到(但不包括)标记内容结尾的句子。
方法`find()``rfind()`(反向的 find)帮助我们得到字符串切片需要用到的正确的索引值。我们用这个切片重新给`raw`赋值,所以现在它以“PART I”开始一直到(但不包括)标记内容结尾的句子。
这是我们第一次接触到网络的实际内容:在网络上找到的文本可能含有不必要的内容,并没有一个自动的方法来去除它。但只需要少量的额外工作,我们就可以提取出我们需要的材料。
......@@ -299,7 +299,7 @@ TypeError: cannot concatenate 'str' and 'list' objects
### 字符串的基本操作
可以使用单引号[](./ch03.html#single-quotes)或双引号[](./ch03.html#double-quotes)来指定字符串,如下面的例子代码所示。如果一个字符串中包含一个单引号,我们必须在单引号前加反斜杠[](./ch03.html#backslash-escape)让 Python 知道这是字符串中的单引号,或者也可以将这个字符串放入双引号中[](./ch03.html#double-quotes)。否则,字符串内的单引号[](./ch03.html#unescaped-quote)将被解释为字符串结束标志,Python 解释器会报告一个语法错误:
可以使用单引号❶或双引号❷来指定字符串,如下面的例子代码所示。如果一个字符串中包含一个单引号,我们必须在单引号前加反斜杠❸让 Python 知道这是字符串中的单引号,或者也可以将这个字符串放入双引号中❷。否则,字符串内的单引号❹将被解释为字符串结束标志,Python 解释器会报告一个语法错误:
```py
>>> monty = 'Monty Python'
......@@ -318,7 +318,7 @@ TypeError: cannot concatenate 'str' and 'list' objects
SyntaxError: invalid syntax
```
有时字符串跨好几行。Python 提供了多种方式表示它们。在下面的例子中,一个包含两个字符串的序列被连接为一个字符串。我们需要使用反斜杠[](./ch03.html#string-backslash)或者括号[](./ch03.html#string-parentheses),这样解释器就知道第一行的表达式不完整。
有时字符串跨好几行。Python 提供了多种方式表示它们。在下面的例子中,一个包含两个字符串的序列被连接为一个字符串。我们需要使用反斜杠❶或者括号❷,这样解释器就知道第一行的表达式不完整。
```py
>>> couplet = "Shall I compare thee to a Summer's day?"\
......@@ -346,7 +346,7 @@ Rough winds do shake the darling buds of May,
And Summer's lease hath all too short a date:
```
现在我们可以定义字符串,也可以在上面尝试一些简单的操作。首先,让我们来看看`+`操作,被称为连接 [](./ch03.html#string-concatenation)。此操作产生一个新字符串,它是两个原始字符串首尾相连粘贴在一起而成。请注意,连接不会做一些比较聪明的事,例如在词汇之间插入空格。我们甚至可以对字符串用乘法[](./ch03.html#string-multiplication)
现在我们可以定义字符串,也可以在上面尝试一些简单的操作。首先,让我们来看看`+`操作,被称为连接❶。此操作产生一个新字符串,它是两个原始字符串首尾相连粘贴在一起而成。请注意,连接不会做一些比较聪明的事,例如在词汇之间插入空格。我们甚至可以对字符串用乘法❷
```py
>>> 'very' + 'very' + 'very'
......@@ -406,7 +406,7 @@ Monty Python and the Holy Grail
### 访问单个字符
正如我们在[2](./ch01.html#sec-a-closer-look-at-python-texts-as-lists-of-words)看到的列表,字符串也是被索引的,从零开始。当我们索引一个字符串时,我们得到它的一个字符(或字母)。一个单独的字符并没有什么特别,它只是一个长度为`1`的字符串。
正如我们在2看到的列表,字符串也是被索引的,从零开始。当我们索引一个字符串时,我们得到它的一个字符(或字母)。一个单独的字符并没有什么特别,它只是一个长度为`1`的字符串。
```py
>>> monty[0]
......@@ -426,7 +426,7 @@ Traceback (most recent call last):
IndexError: string index out of range
```
也与列表一样,我们可以使用字符串的负数索引,其中`-1`是最后一个字符的索引[](./ch03.html#last-character)。正数和负数的索引给我们两种方式指示一个字符串中的任何位置。在这种情况下,当一个字符串长度为 12 时,索引`5``-7`都指示相同的字符(一个空格)。(请注意,`5 = len(monty) - 7`。)
也与列表一样,我们可以使用字符串的负数索引,其中`-1`是最后一个字符的索引。正数和负数的索引给我们两种方式指示一个字符串中的任何位置。在这种情况下,当一个字符串长度为 12 时,索引`5``-7`都指示相同的字符(一个空格)。(请注意,`5 = len(monty) - 7`。)
```py
>>> monty[-1]
......@@ -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`替换掉[](./ch03.html#unicode-info)中的`c.encode('utf8')`,如果你的系统支持 UTF-8,你应该看到类似下面的输出:
如果你使用`c`替换掉中的`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
......@@ -707,7 +707,7 @@ NLTK 分词器允许 Unicode 字符串作为输入,并输出相应地 Unicode
介绍正则表达式的其他出版物有很多,它们围绕正则表达式的语法组织,应用于搜索文本文件。我们不再赘述这些,只专注于在语言处理的不同阶段如何使用正则表达式。像往常一样,我们将采用基于问题的方式,只在解决实际问题需要时才介绍新特性。在我们的讨论中,我们将使用箭头来表示正则表达式,就像这样:«`patt`»。
在 Python 中使用正则表达式,需要使用`import re`导入`re`库。我们还需要一个用于搜索的词汇列表;我们再次使用词汇语料库([4](./ch02.html#sec-lexical-resources))。我们将对它进行预处理消除某些名称。
在 Python 中使用正则表达式,需要使用`import re`导入`re`库。我们还需要一个用于搜索的词汇列表;我们再次使用词汇语料库(4)。我们将对它进行预处理消除某些名称。
```py
>>> import re
......@@ -963,7 +963,7 @@ v 93 27 105 48 49
### 搜索已分词文本
你可以使用一种特殊的正则表达式搜索一个文本中多个词(这里的文本是一个词符列表)。例如,`"&lt;a&gt; &lt;man&gt;"`找出文本中所有 a man 的实例。尖括号用于标记词符的边界,尖括号之间的所有空白都被忽略(这只对 NLTK 中的`findall()`方法处理文本有效)。在下面的例子中,我们使用`<.*>`[](./ch03.html#single-token-wildcard),它将匹配所有单个词符,将它括在括号里,于是只匹配词(例如 monied)而不匹配短语(例如,a monied man)会生成。第二个例子找出以词 bro 结尾的三个词组成的短语[](./ch03.html#three-word-phrases)。最后一个例子找出以字母 l 开始的三个或更多词组成的序列[](./ch03.html#letter-l)
你可以使用一种特殊的正则表达式搜索一个文本中多个词(这里的文本是一个词符列表)。例如,`"&lt;a&gt; &lt;man&gt;"`找出文本中所有 a man 的实例。尖括号用于标记词符的边界,尖括号之间的所有空白都被忽略(这只对 NLTK 中的`findall()`方法处理文本有效)。在下面的例子中,我们使用`<.*>`❶,它将匹配所有单个词符,将它括在括号里,于是只匹配词(例如 monied)而不匹配短语(例如,a monied man)会生成。第二个例子找出以词 bro 结尾的三个词组成的短语❷。最后一个例子找出以字母 l 开始的三个或更多词组成的序列❸
```py
>>> from nltk.corpus import gutenberg, nps_chat
......@@ -985,7 +985,7 @@ la la; lovely lol lol love; lol lol lol.; la la la; la la la
**轮到你来:**巩固你对正则表达式模式与替换的理解,使用`nltk.re_show(`_p, s_`)`,它能标注字符串 *s* 中所有匹配模式 *p* 的地方,以及`nltk.app.nemo()`,它能提供一个探索正则表达式的图形界面。更多的练习,可以尝试本章尾的正则表达式的一些练习。
当我们研究的语言现象与特定词语相关时建立搜索模式是很容易的。在某些情况下,一个小小的创意可能会花很大功夫。例如,在大型文本语料库中搜索 x and other ys 形式的表达式能让我们发现上位词(见[5](./ch02.html#sec-wordnet)):
当我们研究的语言现象与特定词语相关时建立搜索模式是很容易的。在某些情况下,一个小小的创意可能会花很大功夫。例如,在大型文本语料库中搜索 x and other ys 形式的表达式能让我们发现上位词(见5):
```py
>>> from nltk.corpus import brown
......@@ -1099,7 +1099,7 @@ WordNet 词形归并器只在产生的词在它的词典中时才删除词缀。
... well without--Maybe it's always pepper that makes people hot-tempered,'..."""
```
我们可以使用`raw.split()`在空格符处分割原始文本。使用正则表达式能做同样的事情,匹配字符串中的所有空格符[](./ch03.html#split-space)是不够的,因为这将导致分词结果包含`\n`换行符;我们需要匹配任何数量的空格符、制表符或换行符[](./ch03.html#split-whitespace)
我们可以使用`raw.split()`在空格符处分割原始文本。使用正则表达式能做同样的事情,匹配字符串中的所有空格符❶是不够的,因为这将导致分词结果包含`\n`换行符;我们需要匹配任何数量的空格符、制表符或换行符❷
```py
>>> re.split(r' ', raw)
......@@ -1232,7 +1232,7 @@ WordNet 词形归并器只在产生的词在它的词典中时才删除词缀。
断句是困难的,因为句号会被用来标记缩写而另一些句号同时标记缩写和句子结束,就像发生在缩写如 U.S.A.上的那样。
断句的另一种方法见[2](./ch06.html#sec-further-examples-of-supervised-classification)节。
断句的另一种方法见2节。
### 分词
......@@ -1432,7 +1432,7 @@ Lee wants a pancake right now
### 对齐
到目前为止,我们的格式化字符串可以在页面(或屏幕)上输出任意的宽度。我们可以通过插入一个冒号`':'`跟随一个整数来添加空白以获得指定宽带的输出。所以`{:6}`表示我们想让字符串对齐到宽度 6。数字默认表示右对齐[](./ch03.html#right-justified),单我们可以在宽度指示符前面加上`'&lt;'`对齐选项来让数字左对齐[](./ch03.html#left-justified)
到目前为止,我们的格式化字符串可以在页面(或屏幕)上输出任意的宽度。我们可以通过插入一个冒号`':'`跟随一个整数来添加空白以获得指定宽带的输出。所以`{:6}`表示我们想让字符串对齐到宽度 6。数字默认表示右对齐❶,单我们可以在宽度指示符前面加上`'&lt;'`对齐选项来让数字左对齐❷
```py
>>> '{:6}'.format(41)
......@@ -1470,7 +1470,7 @@ Unexpected indentation.
'accuracy for 9375 words: 34.1867%'
```
格式化字符串的一个重要用途是用于数据制表。回想一下,在[1](./ch02.html#sec-extracting-text-from-corpora)中,我们看到从条件频率分布中制表的数据。让我们自己来制表,行使对标题和列宽的完全控制,如 3.11 所示。注意语言处理工作与结果制表之间是明确分离的。
格式化字符串的一个重要用途是用于数据制表。回想一下,在1中,我们看到从条件频率分布中制表的数据。让我们自己来制表,行使对标题和列宽的完全控制,如 3.11 所示。注意语言处理工作与结果制表之间是明确分离的。
```py
def tabulate(cfdist, words, categories):
......
此差异已折叠。
......@@ -324,7 +324,7 @@ NNS-TL-HL [('Nations', 1)]
请注意 often 后面最高频率的词性是动词。名词从来没有在这个位置出现(在这个特别的语料中)。
接下来,让我们看一些较大范围的上下文,找出涉及特定标记和词序列的词(在这种情况下,`"&lt;Verb&gt; to &lt;Verb&gt;"`)。在 code-three-word-phrase 中,我们考虑句子中的每个三词窗口[❶](./ch05.html#three-word),检查它们是否符合我们的标准[❷](./ch05.html#verb-to-verb)。如果标记匹配,我们输出对应的词[❸](./ch05.html#print-words)
接下来,让我们看一些较大范围的上下文,找出涉及特定标记和词序列的词(在这种情况下,`"&lt;Verb&gt; to &lt;Verb&gt;"`)。在 code-three-word-phrase 中,我们考虑句子中的每个三词窗口❶,检查它们是否符合我们的标准❷。如果标记匹配,我们输出对应的词❸
```py
from nltk.corpus import brown
......@@ -393,7 +393,7 @@ that CNJ V WH DET
图 3.1:列表查找:一个整数索引帮助我们访问 Python 列表的内容。
对比这种情况与频率分布([3](./ch01.html#sec-computing-with-language-simple-statistics)),在那里我们指定一个词然后取回一个数字,如`fdist['monstrous']`,它告诉我们一个给定的词在文本中出现的次数。用词查询对任何使用过字典的人都很熟悉。3.2 展示一些更多的例子。
对比这种情况与频率分布(3),在那里我们指定一个词然后取回一个数字,如`fdist['monstrous']`,它告诉我们一个给定的词在文本中出现的次数。用词查询对任何使用过字典的人都很熟悉。3.2 展示一些更多的例子。
![Images/maps02.png](Images/484180fc6abc244116b30e57cb6c0cf5.jpg)
......@@ -419,7 +419,7 @@ that CNJ V WH DET
{'furiously': 'ADV', 'ideas': 'N', 'colorless': 'ADJ', 'sleep': 'V'}
```
所以,例如,[❶](./ch05.html#pos-colorless)说的是 colorless 的词性是形容词,或者更具体地说:在字典`pos`中,键`'colorless'`被分配了值`'ADJ'`。当我们检查`pos`的值时[❷](./ch05.html#pos-inspect),我们看到一个键-值对的集合。一旦我们以这样的方式填充了字典,就可以使用键来检索值:
所以,例如,❶说的是 colorless 的词性是形容词,或者更具体地说:在字典`pos`中,键`'colorless'`被分配了值`'ADJ'`。当我们检查`pos`的值时❷,我们看到一个键-值对的集合。一旦我们以这样的方式填充了字典,就可以使用键来检索值:
```py
>>> pos['ideas']
......@@ -437,9 +437,9 @@ Traceback (most recent call last):
KeyError: 'green'
```
这就提出了一个重要的问题。与列表和字符串不同,我们可以用`len()`算出哪些整数是合法索引,我们如何算出一个字典的合法键?如果字典不是太大,我们可以简单地通过查看变量`pos`检查它的内容。正如在前面([❷](./ch05.html#pos-inspect)行)所看到,这为我们提供了键-值对。请注意它们的顺序与最初放入它们的顺序不同;这是因为字典不是序列而是映射(参见 3.2),键没有固定地排序。
这就提出了一个重要的问题。与列表和字符串不同,我们可以用`len()`算出哪些整数是合法索引,我们如何算出一个字典的合法键?如果字典不是太大,我们可以简单地通过查看变量`pos`检查它的内容。正如在前面(行)所看到,这为我们提供了键-值对。请注意它们的顺序与最初放入它们的顺序不同;这是因为字典不是序列而是映射(参见 3.2),键没有固定地排序。
换种方式,要找到键,我们可以将字典转换成一个列表[❶](./ch05.html#dict-to-list)——要么在期望列表的上下文中使用字典,如作为`sorted()`的参数[❷](./ch05.html#dict-sorted),要么在`for`循环中[❸](./ch05.html#dict-for-loop)
换种方式,要找到键,我们可以将字典转换成一个列表❶——要么在期望列表的上下文中使用字典,如作为`sorted()`的参数❷,要么在`for`循环中❸
```py
>>> list(pos) ❶
......@@ -466,7 +466,7 @@ sleep: V
ideas: N
```
最后,字典的方法`keys()`、`values()`和`items()`允许我们以单独的列表访问键、值以及键-值对。我们甚至可以排序元组[❶](./ch05.html#sort-tuples),按它们的第一个元素排序(如果第一个元素相同,就使用它们的第二个元素)。
最后,字典的方法`keys()`、`values()`和`items()`允许我们以单独的列表访问键、值以及键-值对。我们甚至可以排序元组,按它们的第一个元素排序(如果第一个元素相同,就使用它们的第二个元素)。
```py
>>> list(pos.keys())
......@@ -495,7 +495,7 @@ sleep: V
'N'
```
最初,`pos['sleep']`给的值是`'V'`。但是,它立即被一个新值`'N'`覆盖。换句话说,字典中只能有`'sleep'`的一个条目。然而,有一个方法可以在该项目中存储多个值:我们使用一个列表值,例如`pos['sleep'] = ['N', 'V']`。事实上,这就是我们在[4](./ch02.html#sec-lexical-resources)中看到的 CMU 发音字典,它为一个词存储多个发音。
最初,`pos['sleep']`给的值是`'V'`。但是,它立即被一个新值`'N'`覆盖。换句话说,字典中只能有`'sleep'`的一个条目。然而,有一个方法可以在该项目中存储多个值:我们使用一个列表值,例如`pos['sleep'] = ['N', 'V']`。事实上,这就是我们在4中看到的 CMU 发音字典,它为一个词存储多个发音。
## 3.3 定义字典
......@@ -535,7 +535,7 @@ TypeError: list objects are unhashable
这些默认值实际上是将其他对象转换为指定类型的函数(例如`int("2")`, `list("2")`)。当它们不带参数被调用时——`int()`, `list()`——它们分别返回`0`和`[]` 。
前面的例子中指定字典项的默认值为一个特定的数据类型的默认值。然而,也可以指定任何我们喜欢的默认值,只要提供可以无参数的被调用产生所需值的函数的名子。让我们回到我们的词性的例子,创建一个任一条目的默认值是`'N'`的字典[](./ch05.html#default-noun)。当我们访问一个不存在的条目时[](./ch05.html#non-existent),它会自动添加到字典[](./ch05.html#automatically-added)
前面的例子中指定字典项的默认值为一个特定的数据类型的默认值。然而,也可以指定任何我们喜欢的默认值,只要提供可以无参数的被调用产生所需值的函数的名子。让我们回到我们的词性的例子,创建一个任一条目的默认值是`'N'`的字典❶。当我们访问一个不存在的条目时❷,它会自动添加到字典❸
```py
>>> pos = defaultdict(lambda: 'NOUN') ❶
......@@ -684,7 +684,7 @@ TypeError: list objects are unhashable
defaultdict(<class 'int'>, {'ADJ': 11, 'NOUN': 5})
```
这个例子使用一个字典,它的条目的默认值也是一个字典(其默认值是`int()`,即 0)。请注意我们如何遍历已标注语料库的双连词,每次遍历处理一个词-标记对[](./ch05.html#processing-pairs)。每次通过循环时,我们更新字典`pos`中的条目`(t1, w2)`,一个标记和它*后面*的词[](./ch05.html#tag-word-update)。当我们在`pos`中查找一个项目时,我们必须指定一个复合键[](./ch05.html#compound-key),然后得到一个字典对象。一个词性标注器可以使用这些信息来决定词 right,前面是一个限定词时,应标注为`ADJ`
这个例子使用一个字典,它的条目的默认值也是一个字典(其默认值是`int()`,即 0)。请注意我们如何遍历已标注语料库的双连词,每次遍历处理一个词-标记对❶。每次通过循环时,我们更新字典`pos`中的条目`(t1, w2)`,一个标记和它*后面*的词❷。当我们在`pos`中查找一个项目时,我们必须指定一个复合键❸,然后得到一个字典对象。一个词性标注器可以使用这些信息来决定词 right,前面是一个限定词时,应标注为`ADJ`。
## 3.7 反转字典
......@@ -807,7 +807,7 @@ Python 字典方法:常用的方法与字典相关习惯用法的总结。
注意
**轮到你来:**看看你能不能想出一些模式,提高上面所示的正则表达式标注器的性能。(请注意[1](./ch06.html#sec-supervised-classification)描述部分自动化这类工作的方法。)
**轮到你来:**看看你能不能想出一些模式,提高上面所示的正则表达式标注器的性能。(请注意1描述部分自动化这类工作的方法。)
## 4.3 查询标注器
......@@ -839,7 +839,7 @@ Python 字典方法:常用的方法与字典相关习惯用法的总结。
('of', 'IN'), ('this', 'DT'), ('city', None), ("''", "''"), ('.', '.')]
```
许多词都被分配了一个`None`标签,因为它们不在 100 个最频繁的词之中。在这些情况下,我们想分配默认标记`NN`。换句话说,我们要先使用查找表,如果它不能指定一个标记就使用默认标注器,这个过程叫做回退([5](./ch05.html#sec-n-gram-tagging))。我们可以做到这个,通过指定一个标注器作为另一个标注器的参数,如下所示。现在查找标注器将只存储名词以外的词的词-标记对,只要它不能给一个词分配标记,它将会调用默认标注器。
许多词都被分配了一个`None`标签,因为它们不在 100 个最频繁的词之中。在这些情况下,我们想分配默认标记`NN`。换句话说,我们要先使用查找表,如果它不能指定一个标记就使用默认标注器,这个过程叫做回退(5)。我们可以做到这个,通过指定一个标注器作为另一个标注器的参数,如下所示。现在查找标注器将只存储名词以外的词的词-标记对,只要它不能给一个词分配标记,它将会调用默认标注器。
```py
>>> baseline_tagger = nltk.UnigramTagger(model=likely_tags,
......@@ -890,7 +890,7 @@ def display():
## 5.1 一元标注
一元标注器基于一个简单的统计算法:对每个标识符分配这个独特的标识符最有可能的标记。例如,它将分配标记`JJ`给词 frequent 的所有出现,因为 frequent 用作一个形容词(例如 a frequent word)比用作一个动词(例如 I frequent this cafe)更常见。一个一元标注器的行为就像一个查找标注器([4](./ch05.html#sec-automatic-tagging)),除了有一个更方便的建立它的技术,称为训练。在下面的代码例子中,我们训练一个一元标注器,用它来标注一个句子,然后评估:
一元标注器基于一个简单的统计算法:对每个标识符分配这个独特的标识符最有可能的标记。例如,它将分配标记`JJ`给词 frequent 的所有出现,因为 frequent 用作一个形容词(例如 a frequent word)比用作一个动词(例如 I frequent this cafe)更常见。一个一元标注器的行为就像一个查找标注器(4),除了有一个更方便的建立它的技术,称为训练。在下面的代码例子中,我们训练一个一元标注器,用它来标注一个句子,然后评估:
```py
>>> from nltk.corpus import brown
......@@ -1001,7 +1001,7 @@ N-gram 标注器不应考虑跨越句子边界的上下文。因此,NLTK 的
我们标注生词的方法仍然是回退到一个正则表达式标注器或一个默认标注器。这些都无法利用上下文。因此,如果我们的标注器遇到词 blog,训练过程中没有看到过,它会分配相同的标记,不论这个词出现的上下文是 the blog 还是 to blog。我们怎样才能更好地处理这些生词,或词汇表以外的项目?
一个有用的基于上下文标注生词的方法是限制一个标注器的词汇表为最频繁的 n 个词,使用[3](./ch05.html#sec-dictionaries)中的方法替代每个其他的词为一个特殊的词 UNK。训练时,一个一元标注器可能会学到 UNK 通常是一个名词。然而,n-gram 标注器会检测它的一些其他标记中的上下文。例如,如果前面的词是 to(标注为`TO`),那么 UNK 可能会被标注为一个动词。
一个有用的基于上下文标注生词的方法是限制一个标注器的词汇表为最频繁的 n 个词,使用3中的方法替代每个其他的词为一个特殊的词 UNK。训练时,一个一元标注器可能会学到 UNK 通常是一个名词。然而,n-gram 标注器会检测它的一些其他标记中的上下文。例如,如果前面的词是 to(标注为`TO`),那么 UNK 可能会被标注为一个动词。
## 5.6 存储标注器
......@@ -1152,7 +1152,7 @@ Statement User121 18/m pm me if u tryin to chat
6. ☼ 尝试从字典`d`删除一个元素,使用语法`del d['abc']`。检查被删除的项目。
7. ☼ 创建两个字典,`d1`和`d2`,为每个添加一些条目。现在发出命令`d1.update(d2)`。这做了什么?它可能是有什么用?
8. ☼ 创建一个字典`e`,表示你选择的一些词的一个单独的词汇条目。定义键如`headword`、`part-of-speech`、`sense`和`example`,分配给它们适当的值。
9. ☼ 自己验证 go 和 went 在分布上的限制,也就是说,它们不能自由地在[7](./ch05.html#sec-how-to-determine-the-category-of-a-word)中的[(3d)](./ch05.html#ex-go)演示的那种上下文中互换。
9. ☼ 自己验证 go 和 went 在分布上的限制,也就是说,它们不能自由地在7中的[(3d)](./ch05.html#ex-go)演示的那种上下文中互换。
10. ☼ 训练一个一元标注器,在一些新的文本上运行。观察有些词没有分配到标记。为什么没有?
11. ☼ 了解词缀标注器(输入`help(nltk.AffixTagger)`)。训练一个词缀标注器,在一些新的文本上运行。设置不同的词缀长度和最小词长做实验。讨论你的发现。
12. ☼ 训练一个没有回退标注器的二元标注器,在一些训练数据上运行。下一步,在一些新的数据运行它。标注器的准确性会发生什么?为什么呢?
......@@ -1186,7 +1186,7 @@ Statement User121 18/m pm me if u tryin to chat
24. ◑ 数据稀疏问题有多严重?调查 n-gram 标注器当 n 从 1 增加到 6 时的准确性。为准确性得分制表。估计这些标注器需要的训练数据,假设词汇量大小为 10&lt;sup&gt;5&lt;/sup&gt;而标记集的大小为 10&lt;sup&gt;2&lt;/sup&gt;。
25. ◑ 获取另一种语言的一些已标注数据,在其上测试和评估各种标注器。如果这种语言是形态复杂的,或者有词类的任何字形线索(如),可以考虑为它开发一个正则表达式标注器(排在一元标注器之后,默认标注器之前)。对比同样的运行在英文数据上的标注器,你的标注器的准确性如何?讨论你在运用这些方法到这种语言时遇到的问题。
26. ◑ 4.1 绘制曲线显示查找标注器的性能随模型的大小增加的变化。绘制当训练数据量变化时一元标注器的性能曲线。
27. ◑ 检查[5](./ch05.html#sec-n-gram-tagging)中定义的二元标注器`t2`的混淆矩阵,确定简化的一套或多套标记。定义字典做映射,在简化的数据上评估标注器。
27. ◑ 检查5中定义的二元标注器`t2`的混淆矩阵,确定简化的一套或多套标记。定义字典做映射,在简化的数据上评估标注器。
28. ◑ 使用简化的标记集测试标注器(或制作一个你自己的,通过丢弃每个标记名中除第一个字母外所有的字母)。这种标注器需要做的区分更少,但由它获得的信息也更少。讨论你的发现。
29. ◑ 回顾一个二元标注器训练过程中遇到生词,标注句子的其余部分为`None`的例子。一个二元标注器可能只处理了句子的一部分就失败了,即使句子中没有包含生词(即使句子在训练过程中使用过)。在什么情况下会出现这种情况呢?你可以写一个程序,找到一些这方面的例子吗?
30. ◑ 预处理布朗新闻数据,替换低频词为 UNK,但留下标记不变。在这些数据上训练和评估一个二元标注器。这样有多少帮助?一元标注器和默认标注器的贡献是什么?
......@@ -1204,11 +1204,11 @@ Statement User121 18/m pm me if u tryin to chat
1. 创建一种新的一元标注器,查看前一个词的标记,而忽略当前词。(做到这一点的最好办法是修改`UnigramTagger()`的源代码,需要 Python 中的面向对象编程的知识。
2. 将这个标注器加入到回退标注器序列(包括普通的三元和二元标注器),放在常用默认标注器的前面。
3. 评价这个新的一元标注器的贡献。
38. ★ 思考[5](./ch05.html#sec-n-gram-tagging)中的代码,它确定一个三元标注器的准确性上限。回顾 Abney 的关于精确标注的不可能性的讨论[(Church, Young, & Bloothooft, 1996)](./bibliography.html#abney1996pst)。解释为什么正确标注这些例子需要获取词和标记以外的其他种类的信息。你如何估计这个问题的规模?
38. ★ 思考5中的代码,它确定一个三元标注器的准确性上限。回顾 Abney 的关于精确标注的不可能性的讨论[(Church, Young, & Bloothooft, 1996)](./bibliography.html#abney1996pst)。解释为什么正确标注这些例子需要获取词和标记以外的其他种类的信息。你如何估计这个问题的规模?
39. ★ 使用`nltk.probability`中的一些估计技术,例如 *Lidstone* 或 *Laplace* 估计,开发一种统计标注器,它在训练中没有遇到而测试中遇到的上下文中表现优于 n-gram 回退标注器。
40. ★ 检查 Brill 标注器创建的诊断文件`rules.out`和`errors.out`。通过访问源代码(`http://www.nltk.org/code`)获得演示代码,创建你自己版本的 Brill 标注器。并根据你从检查`rules.out`了解到的,删除一些规则模板。增加一些新的规则模板,这些模板使用那些可能有助于纠正你在`errors.out`看到的错误的上下文。
41. ★ 开发一个 n-gram 回退标注器,允许在标注器初始化时指定“anti-n-grams”,如`["the", "the"]`。一个 anti-n-grams 被分配一个数字 0,被用来防止这个 n-gram 回退(如避免估计 P(the | the)而只做 P(the))。
42. ★ 使用布朗语料库开发标注器时,调查三种不同的方式来定义训练和测试数据之间的分割:genre (`category`)、source (`fileid`)和句子。比较它们的相对性能,并讨论哪种方法最合理。(你可能要使用 n-交叉验证,在[3](./ch06.html#sec-evaluation)中讨论的,以提高评估的准确性。)
42. ★ 使用布朗语料库开发标注器时,调查三种不同的方式来定义训练和测试数据之间的分割:genre (`category`)、source (`fileid`)和句子。比较它们的相对性能,并讨论哪种方法最合理。(你可能要使用 n-交叉验证,在3中讨论的,以提高评估的准确性。)
43. ★ 开发你自己的`NgramTagger`,从 NLTK 中的类继承,封装本章中所述的已标注的训练和测试数据的词汇表缩减方法。确保一元和默认回退标注器有机会获得全部词汇。
关于本文档...
......
# 6\. 学习分类文本
模式识别是自然语言处理的一个核心部分。以-ed 结尾的词往往是过去时态动词([5.](./ch05.html#chap-tag))。频繁使用 will 是新闻文本的暗示([3](./ch03.html#chap-words))。这些可观察到的模式——词的结构和词频——恰好与特定方面的含义关联,如时态和主题。但我们怎么知道从哪里开始寻找,形式的哪一方面关联含义的哪一方面?
模式识别是自然语言处理的一个核心部分。以-ed 结尾的词往往是过去时态动词([5.](./ch05.html#chap-tag))。频繁使用 will 是新闻文本的暗示(3)。这些可观察到的模式——词的结构和词频——恰好与特定方面的含义关联,如时态和主题。但我们怎么知道从哪里开始寻找,形式的哪一方面关联含义的哪一方面?
本章的目的是要回答下列问题:
......@@ -30,7 +30,7 @@
## 1.1 性别鉴定
[4](./ch02.html#sec-lexical-resources)中,我们看到,男性和女性的名字有一些鲜明的特点。以 a,e 和 i 结尾的很可能是女性,而以 k,o,r,s 和 t 结尾的很可能是男性。让我们建立一个分类器更精确地模拟这些差异。
4中,我们看到,男性和女性的名字有一些鲜明的特点。以 a,e 和 i 结尾的很可能是女性,而以 k,o,r,s 和 t 结尾的很可能是男性。让我们建立一个分类器更精确地模拟这些差异。
创建一个分类器的第一步是决定输入的什么样的特征是相关的,以及如何为那些特征编码。在这个例子中,我们一开始只是寻找一个给定的名称的最后一个字母。以下特征提取器函数建立一个字典,包含有关给定名称的相关信息:
......@@ -148,7 +148,7 @@ def gender_features2(name):
图 1.3:用于训练有监督分类器的语料数据组织图。语料数据分为两类:开发集和测试集。开发集通常被进一步分为训练集和开发测试集。
已经将语料分为适当的数据集,我们使用训练集训练一个模型[](./ch06.html#err-analysis-train),然后在开发测试集上运行[](./ch06.html#err-analysis-run)
已经将语料分为适当的数据集,我们使用训练集训练一个模型❶,然后在开发测试集上运行❷
```py
>>> train_set = [(gender_features(n), gender) for (n, gender) in train_names]
......@@ -213,7 +213,7 @@ correct=male guess=female name=Rich
## 1.3 文档分类
[1](./ch02.html#sec-extracting-text-from-corpora)中,我们看到了语料库的几个例子,那里文档已经按类别标记。使用这些语料库,我们可以建立分类器,自动给新文档添加适当的类别标签。首先,我们构造一个标记了相应类别的文档清单。对于这个例子,我们选择电影评论语料库,将每个评论归类为正面或负面。
1中,我们看到了语料库的几个例子,那里文档已经按类别标记。使用这些语料库,我们可以建立分类器,自动给新文档添加适当的类别标签。首先,我们构造一个标记了相应类别的文档清单。对于这个例子,我们选择电影评论语料库,将每个评论归类为正面或负面。
```py
>>> from nltk.corpus import movie_reviews
......@@ -223,7 +223,7 @@ correct=male guess=female name=Rich
>>> random.shuffle(documents)
```
接下来,我们为文档定义一个特征提取器,这样分类器就会知道哪些方面的数据应注意(1.4)。对于文档主题识别,我们可以为每个词定义一个特性表示该文档是否包含这个词。为了限制分类器需要处理的特征的数目,我们一开始构建一个整个语料库中前 2000 个最频繁词的列表[](./ch06.html#document-classify-all-words)。然后,定义一个特征提取器[](./ch06.html#document-classify-extractor),简单地检查这些词是否在一个给定的文档中。
接下来,我们为文档定义一个特征提取器,这样分类器就会知道哪些方面的数据应注意(1.4)。对于文档主题识别,我们可以为每个词定义一个特性表示该文档是否包含这个词。为了限制分类器需要处理的特征的数目,我们一开始构建一个整个语料库中前 2000 个最频繁词的列表❶。然后,定义一个特征提取器❷,简单地检查这些词是否在一个给定的文档中。
```py
all_words = nltk.FreqDist(w.lower() for w in movie_reviews.words())
......@@ -239,9 +239,9 @@ def document_features(document): ❷
注意
[](./ch06.html#document-classify-set)中我们计算文档的所有词的集合,而不仅仅检查是否`word in document`,因为检查一个词是否在一个集合中出现比检查它是否在一个列表中出现要快的多(4.7)。
中我们计算文档的所有词的集合,而不仅仅检查是否`word in document`,因为检查一个词是否在一个集合中出现比检查它是否在一个列表中出现要快的多(4.7)。
现在,我们已经定义了我们的特征提取器,可以用它来训练一个分类器,为新的电影评论加标签(1.5)。为了检查产生的分类器可靠性如何,我们在测试集上计算其准确性[](./ch06.html#document-classify-test)。再一次的,我们可以使用`show_most_informative_features()`来找出哪些特征是分类器发现最有信息量的[](./ch06.html#document-classify-smif)
现在,我们已经定义了我们的特征提取器,可以用它来训练一个分类器,为新的电影评论加标签(1.5)。为了检查产生的分类器可靠性如何,我们在测试集上计算其准确性❶。再一次的,我们可以使用`show_most_informative_features()`来找出哪些特征是分类器发现最有信息量的❷
```py
featuresets = [(document_features(d), c) for (d,c) in documents]
......@@ -286,7 +286,7 @@ classifier = nltk.NaiveBayesClassifier.train(train_set)
特征提取函数的行为就像有色眼镜一样,强调我们的数据中的某些属性(颜色),并使其无法看到其他属性。分类器在决定如何标记输入时,将完全依赖它们强调的属性。在这种情况下,分类器将只基于一个给定的词拥有(如果有)哪个常见后缀的信息来做决定。
现在,我们已经定义了我们的特征提取器,可以用它来训练一个新的“决策树”的分类器(将在[4](./ch06.html#sec-decision-trees)讨论):
现在,我们已经定义了我们的特征提取器,可以用它来训练一个新的“决策树”的分类器(将在4讨论):
```py
>>> tagged_words = brown.tagged_words(categories='news')
......@@ -351,11 +351,11 @@ def pos_features(sentence, i): ❶
为了捕捉相关的分类任务之间的依赖关系,我们可以使用联合分类器模型,收集有关输入,选择适当的标签。在词性标注的例子中,各种不同的序列分类器模型可以被用来为一个给定的句子中的所有的词共同选择词性标签。
一种序列分类器策略,称为连续分类或贪婪序列分类,是为第一个输入找到最有可能的类标签,然后使用这个问题的答案帮助找到下一个输入的最佳的标签。这个过程可以不断重复直到所有的输入都被贴上标签。这是[5](./ch05.html#sec-n-gram-tagging)中的二元标注器采用的方法,它一开始为句子的第一个词选择词性标记,然后为每个随后的词选择标记,基于词本身和前面词的预测的标记。
一种序列分类器策略,称为连续分类或贪婪序列分类,是为第一个输入找到最有可能的类标签,然后使用这个问题的答案帮助找到下一个输入的最佳的标签。这个过程可以不断重复直到所有的输入都被贴上标签。这是5中的二元标注器采用的方法,它一开始为句子的第一个词选择词性标记,然后为每个随后的词选择标记,基于词本身和前面词的预测的标记。
在 1.7 中演示了这一策略。首先,我们必须扩展我们的特征提取函数使其具有参数`history`,它提供一个我们到目前为止已经为句子预测的标记的列表[](./ch06.html#consec-pos-tag-features)`history`中的每个标记对应`sentence`中的一个词。但是请注意,`history`将只包含我们已经归类的词的标记,也就是目标词左侧的词。因此,虽然是有可能查看目标词右边的词的某些特征,但查看那些词的标记是不可能的(因为我们还未产生它们)。
在 1.7 中演示了这一策略。首先,我们必须扩展我们的特征提取函数使其具有参数`history`,它提供一个我们到目前为止已经为句子预测的标记的列表`history`中的每个标记对应`sentence`中的一个词。但是请注意,`history`将只包含我们已经归类的词的标记,也就是目标词左侧的词。因此,虽然是有可能查看目标词右边的词的某些特征,但查看那些词的标记是不可能的(因为我们还未产生它们)。
已经定义了特征提取器,我们可以继续建立我们的序列分类器[](./ch06.html#consec-pos-tagger)。在训练中,我们使用已标注的标记为征提取器提供适当的历史信息,但标注新的句子时,我们基于标注器本身的输出产生历史信息。
已经定义了特征提取器,我们可以继续建立我们的序列分类器。在训练中,我们使用已标注的标记为征提取器提供适当的历史信息,但标注新的句子时,我们基于标注器本身的输出产生历史信息。
```py
def pos_features(sentence, i, history):
......@@ -464,7 +464,7 @@ def segment_sentences(words):
处理对话时,将对话看作说话者执行的*行为*是很有用的。对于表述行为的陈述句这种解释是最直白的,例如"I forgive you"或"I bet you can't climb that hill"。但是问候、问题、回答、断言和说明都可以被认为是基于语言的行为类型。识别对话中言语下的对话行为是理解谈话的重要的第一步。
NPS 聊天语料库,在[1](./ch02.html#sec-extracting-text-from-corpora)中的展示过,包括超过 10,000 个来自即时消息会话的帖子。这些帖子都已经被贴上 15 种对话行为类型中的一种标签,例如“陈述”,“情感”,“yn 问题”和“Continuer”。因此,我们可以利用这些数据建立一个分类器,识别新的即时消息帖子的对话行为类型。第一步是提取基本的消息数据。我们将调用`xml_posts()`来得到一个数据结构,表示每个帖子的 XML 注释:
NPS 聊天语料库,在1中的展示过,包括超过 10,000 个来自即时消息会话的帖子。这些帖子都已经被贴上 15 种对话行为类型中的一种标签,例如“陈述”,“情感”,“yn 问题”和“Continuer”。因此,我们可以利用这些数据建立一个分类器,识别新的即时消息帖子的对话行为类型。第一步是提取基本的消息数据。我们将调用`xml_posts()`来得到一个数据结构,表示每个帖子的 XML 注释:
```py
>>> posts = nltk.corpus.nps_chat.xml_posts()[:10000]
......@@ -494,7 +494,7 @@ NPS 聊天语料库,在[1](./ch02.html#sec-extracting-text-from-corpora)中的
## 2.3 识别文字蕴含
识别文字蕴含(RTE)是判断文本 *T* 的一个给定片段是否蕴含着另一个叫做“假设”的文本(已经在[5](./ch01.html#sec-automatic-natural-language-understanding)讨论过)。迄今为止,已经有 4 个 RTE 挑战赛,在那里共享的开发和测试数据会提供给参赛队伍。这里是挑战赛 3 开发数据集中的文本/假设对的两个例子。标签 *True* 表示蕴含成立,*False* 表示蕴含不成立。
识别文字蕴含(RTE)是判断文本 *T* 的一个给定片段是否蕴含着另一个叫做“假设”的文本(已经在5讨论过)。迄今为止,已经有 4 个 RTE 挑战赛,在那里共享的开发和测试数据会提供给参赛队伍。这里是挑战赛 3 开发数据集中的文本/假设对的两个例子。标签 *True* 表示蕴含成立,*False* 表示蕴含不成立。
> Challenge 3, Pair 34 (True)
>
......@@ -602,7 +602,7 @@ Python 提供了一个良好的环境进行基本的文本处理和特征提取
0.75
```
解释一个分类器的准确性得分,考虑测试集中单个类标签的频率是很重要的。例如,考虑一个决定词 bank 每次出现的正确的词意的分类器。如果我们在金融新闻文本上评估分类器,那么我们可能会发现,`金融机构`的意思 20 个里面出现了 19 次。在这种情况下,95%的准确度也难以给人留下深刻印象,因为我们可以实现一个模型,它总是返回`金融机构`的意义。然而,如果我们在一个更加平衡的语料库上评估分类器,那里的最频繁的词意只占 40%,那么 95%的准确度得分将是一个更加积极的结果。(在[2](./ch11.html#sec-life-cycle-of-a-corpus)测量标注一致性程度时也会有类似的问题。)
解释一个分类器的准确性得分,考虑测试集中单个类标签的频率是很重要的。例如,考虑一个决定词 bank 每次出现的正确的词意的分类器。如果我们在金融新闻文本上评估分类器,那么我们可能会发现,`金融机构`的意思 20 个里面出现了 19 次。在这种情况下,95%的准确度也难以给人留下深刻印象,因为我们可以实现一个模型,它总是返回`金融机构`的意义。然而,如果我们在一个更加平衡的语料库上评估分类器,那里的最频繁的词意只占 40%,那么 95%的准确度得分将是一个更加积极的结果。(在2测量标注一致性程度时也会有类似的问题。)
## 3.3 精确度和召回率
......@@ -625,7 +625,7 @@ Python 提供了一个良好的环境进行基本的文本处理和特征提取
## 3.4 混淆矩阵
当处理有 3 个或更多的标签的分类任务时,基于模型错误类型细分模型的错误是有信息量的。一个混淆矩阵是一个表,其中每个 cells [i,j]表示正确的标签 i 被预测为标签 j 的次数。因此,对角线项目(即 cells [|ii|](./ch06.html#id17))表示正确预测的标签,非对角线项目表示错误。在下面的例子中,我们为[4](./ch05.html#sec-automatic-tagging)中开发的一元标注器生成一个混淆矩阵:
当处理有 3 个或更多的标签的分类任务时,基于模型错误类型细分模型的错误是有信息量的。一个混淆矩阵是一个表,其中每个 cells [i,j]表示正确的标签 i 被预测为标签 j 的次数。因此,对角线项目(即 cells [|ii|](./ch06.html#id17))表示正确预测的标签,非对角线项目表示错误。在下面的例子中,我们为4中开发的一元标注器生成一个混淆矩阵:
```py
>>> def tag_list(tagged_sents):
......
......@@ -54,7 +54,7 @@
图 2.1:词符和词块级别的分割与标注
在本节中,我们将在较深的层面探讨词块划分,以词块的定义和表示开始。我们将看到正则表达式和 N-gram 的方法来词块划分,使用 CoNLL-2000 词块划分语料库开发和评估词块划分器。我们将在[(5)](./ch07.html#sec-ner)[6](./ch07.html#sec-relextract)回到命名实体识别和关系抽取的任务。
在本节中,我们将在较深的层面探讨词块划分,以词块的定义和表示开始。我们将看到正则表达式和 N-gram 的方法来词块划分,使用 CoNLL-2000 词块划分语料库开发和评估词块划分器。我们将在[(5)](./ch07.html#sec-ner)6回到命名实体识别和关系抽取的任务。
## 2.1 名词短语词块划分
......@@ -96,7 +96,7 @@ Panamanian/JJ dictator/NN Manuel/NNP Noriega/NNP
要找到一个给定的句子的词块结构,`RegexpParser`词块划分器以一个没有词符被划分的平面结构开始。词块划分规则轮流应用,依次更新词块结构。一旦所有的规则都被调用,返回生成的词块结构。
2.3 显示了一个由 2 个规则组成的简单的词块语法。第一条规则匹配一个可选的限定词或所有格代名词,零个或多个形容词,然后跟一个名词。第二条规则匹配一个或多个专有名词。我们还定义了一个进行词块划分的例句[](./ch07.html#code-chunker1-ex),并在此输入上运行这个词块划分器[](./ch07.html#code-chunker1-run)
2.3 显示了一个由 2 个规则组成的简单的词块语法。第一条规则匹配一个可选的限定词或所有格代名词,零个或多个形容词,然后跟一个名词。第二条规则匹配一个或多个专有名词。我们还定义了一个进行词块划分的例句❶,并在此输入上运行这个词块划分器❷
```py
grammar = r"""
......@@ -130,7 +130,7 @@ sentence = [("Rapunzel", "NNP"), ("let", "VBD"), ("down", "RP"), ❶
## 2.4 探索文本语料库
[2](./ch05.html#sec-tagged-corpora)中,我们看到了我们如何在已标注的语料库中提取匹配的特定的词性标记序列的短语。我们可以使用词块划分器更容易的做同样的工作,如下:
2中,我们看到了我们如何在已标注的语料库中提取匹配的特定的词性标记序列的短语。我们可以使用词块划分器更容易的做同样的工作,如下:
```py
>>> cp = nltk.RegexpParser('CHUNK: {<V.*> <TO> <V.*>}')
......@@ -268,9 +268,9 @@ ChunkParse score:
F-Measure: 69.2%
```
正如你看到的,这种方法达到相当好的结果。但是,我们可以采用更多数据驱动的方法改善它,在这里我们使用训练语料找到对每个词性标记最有可能的块标记(`I`, `O`或`B`)。换句话说,我们可以使用*一元标注器*([4](./ch05.html#sec-automatic-tagging))建立一个词块划分器。但不是尝试确定每个词的正确的词性标记,而是根据每个词的词性标记,尝试确定正确的词块标记。
正如你看到的,这种方法达到相当好的结果。但是,我们可以采用更多数据驱动的方法改善它,在这里我们使用训练语料找到对每个词性标记最有可能的块标记(`I`, `O`或`B`)。换句话说,我们可以使用*一元标注器*(4)建立一个词块划分器。但不是尝试确定每个词的正确的词性标记,而是根据每个词的词性标记,尝试确定正确的词块标记。
在 3.1 中,我们定义了`UnigramChunker`类,使用一元标注器给句子加词块标记。这个类的大部分代码只是用来在 NLTK 的`ChunkParserI`接口使用的词块树表示和嵌入式标注器使用的 IOB 表示之间镜像转换。类定义了两个方法:一个构造函数[❶](./ch07.html#code-unigram-chunker-constructor),当我们建立一个新的 UnigramChunker 时调用;以及`parse`方法[❸](./ch07.html#code-unigram-chunker-parse),用来给新句子划分词块。
在 3.1 中,我们定义了`UnigramChunker`类,使用一元标注器给句子加词块标记。这个类的大部分代码只是用来在 NLTK 的`ChunkParserI`接口使用的词块树表示和嵌入式标注器使用的 IOB 表示之间镜像转换。类定义了两个方法:一个构造函数❶,当我们建立一个新的 UnigramChunker 时调用;以及`parse`方法❸,用来给新句子划分词块。
```py
class UnigramChunker(nltk.ChunkParserI):
......@@ -288,9 +288,9 @@ class UnigramChunker(nltk.ChunkParserI):
return nltk.chunk.conlltags2tree(conlltags)
```
构造函数[❶](./ch07.html#code-unigram-chunker-constructor)需要训练句子的一个列表,这将是词块树的形式。它首先将训练数据转换成适合训练标注器的形式,使用`tree2conlltags`映射每个词块树到一个`word,tag,chunk`三元组的列表。然后使用转换好的训练数据训练一个一元标注器,并存储在`self.tagger`供以后使用。
构造函数需要训练句子的一个列表,这将是词块树的形式。它首先将训练数据转换成适合训练标注器的形式,使用`tree2conlltags`映射每个词块树到一个`word,tag,chunk`三元组的列表。然后使用转换好的训练数据训练一个一元标注器,并存储在`self.tagger`供以后使用。
`parse`方法[❸](./ch07.html#code-unigram-chunker-parse)接收一个已标注的句子作为其输入,以从那句话提取词性标记开始。它然后使用在构造函数中训练过的标注器`self.tagger`,为词性标记标注 IOB 词块标记。接下来,它提取词块标记,与原句组合,产生`conlltags`。最后,它使用`conlltags2tree`将结果转换成一个词块树。
`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-buildit)构造一个`BigramTagger`而不是`UnigramTagger`。由此产生的词块划分器的性能略高于一元词块划分器:
建立了一个一元分块器,很容易建立一个二元分块器:我们只需要改变类的名称为`BigramChunker`,修改 3.1 行构造一个`BigramTagger`而不是`UnigramTagger`。由此产生的词块划分器的性能略高于一元词块划分器:
```py
>>> bigram_chunker = BigramChunker(train_sents)
......@@ -431,7 +431,7 @@ ChunkParse score:
F-Measure: 86.7%
```
最后,我们尝试用多种附加特征扩展特征提取器,例如预取特征[❶](./ch07.html#chunk-fe-lookahead)、配对特征[❷](./ch07.html#chunk-fe-paired)和复杂的语境特征[❸](./ch07.html#chunk-fe-complex)。这最后一个特征,称为`tags-since-dt`,创建一个字符串,描述自最近的限定词以来遇到的所有词性标记,或如果没有限定词则在索引`i`之前自语句开始以来遇到的所有词性标记。
最后,我们尝试用多种附加特征扩展特征提取器,例如预取特征❶、配对特征❷和复杂的语境特征❸。这最后一个特征,称为`tags-since-dt`,创建一个字符串,描述自最近的限定词以来遇到的所有词性标记,或如果没有限定词则在索引`i`之前自语句开始以来遇到的所有词性标记。
```py
>>> def npchunk_features(sentence, i, history):
......@@ -496,7 +496,7 @@ sentence = [("Mary", "NN"), ("saw", "VBD"), ("the", "DT"), ("cat", "NN"),
("sit", "VB"), ("on", "IN"), ("the", "DT"), ("mat", "NN")]
```
不幸的是,这一结果丢掉了 saw 为首的`VP`。它还有其他缺陷。当我们将此词块划分器应用到一个有更深嵌套的句子时,让我们看看会发生什么。请注意,它无法识别[❶](./ch07.html#saw-vbd)开始的`VP`词块。
不幸的是,这一结果丢掉了 saw 为首的`VP`。它还有其他缺陷。当我们将此词块划分器应用到一个有更深嵌套的句子时,让我们看看会发生什么。请注意,它无法识别开始的`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`的值被指定[❶](./ch07.html#relsym)
如前文所示,`conll2002`命名实体语料库的荷兰语部分不只包含命名实体标注,也包含词性标注。这允许我们设计对这些标记敏感的模式,如下面的例子所示。`clause()`方法以分条形式输出关系,其中二元关系符号作为参数`relsym`的值被指定
```py
>>> from nltk.corpus import conll2002
......@@ -704,7 +704,7 @@ VAN('annie_lennox', 'eurythmics')
注意
**轮到你来:**替换最后一行[❶](./ch07.html#relsym)为`print(rtuple(rel, lcon=True, rcon=True))`。这将显示实际的词表示两个 NE 之间关系以及它们左右的默认 10 个词的窗口的上下文。在一本荷兰语词典的帮助下,你也许能够找出为什么结果`VAN('annie_lennox', 'eurythmics')`是个误报。
**轮到你来:**替换最后一行为`print(rtuple(rel, lcon=True, rcon=True))`。这将显示实际的词表示两个 NE 之间关系以及它们左右的默认 10 个词的窗口的上下文。在一本荷兰语词典的帮助下,你也许能够找出为什么结果`VAN('annie_lennox', 'eurythmics')`是个误报。
## 7 小结
......
......@@ -14,7 +14,7 @@
## 1.1 语言数据和无限可能性
前面的章节中已经为你讲述了如何处理和分析的文本语料库,我们一直强调处理大量的每天都在增加的电子语言数据是 NLP 的挑战。让我们更加细致的思考这些数据,做一个思想上的实验,我们有一个巨大的语料库,包括在过去 50 年中英文表达或写成的一切。我们称这个语料库为“现代英语”合理吗?有许多为什么我们的回答可能是否定的的原因。回想一下,在[3](./ch03.html#chap-words)中,我们让你搜索网络查找 the of 模式的实例。虽然很容易在网上找到包含这个词序列的例子,例如 New man at the of IMG (见`http://www.telegraph.co.uk/sport/2387900/New-man-at-the-of-IMG.html`),说英语的人会说大多数这样的例子是错误的,因此它们根本不是英语。
前面的章节中已经为你讲述了如何处理和分析的文本语料库,我们一直强调处理大量的每天都在增加的电子语言数据是 NLP 的挑战。让我们更加细致的思考这些数据,做一个思想上的实验,我们有一个巨大的语料库,包括在过去 50 年中英文表达或写成的一切。我们称这个语料库为“现代英语”合理吗?有许多为什么我们的回答可能是否定的的原因。回想一下,在3中,我们让你搜索网络查找 the of 模式的实例。虽然很容易在网上找到包含这个词序列的例子,例如 New man at the of IMG (见`http://www.telegraph.co.uk/sport/2387900/New-man-at-the-of-IMG.html`),说英语的人会说大多数这样的例子是错误的,因此它们根本不是英语。
因此,我们可以说,“现代英语”并不等同于我们想象中的语料库中的非常大的词序列的集合。说英语的人可以判断这些序列,并将拒绝其中一些不合语法的。
......@@ -410,7 +410,7 @@ NLTK 语料库也收集了*中央研究院树库语料*,包括 10,000 句已
(S (NP (NP fish) (Sbar (NP fish) (V fish))) (V fish) (NP fish))
```
随着句子长度增加到(3,5,7,...),我们得到的分析树的数量是:1; 2; 5; 14; 42; 132; 429; 1,430; 4,862; 16,796; 58,786; 208,012; ….(这是 Catalan 数,我们在[4](./ch04.html#chap-structured-programming)的练习中见过)。最后一个是句子长度为 23 的分析树的数目,这是宾州树库 WSJ 部分的句子的平均长度。对于一个长度为 50 的句子有超过 10&lt;sup&gt;12&lt;/sup&gt;的解析,这只是 Piglet 句子长度的一半([1](./ch08.html#sec-dilemmas)),这些句子小孩可以毫不费力的处理。没有实际的自然语言处理系统可以为一个句子构建数以百万计的树,并根据上下文选择一个合适的。很显然,人也不会这样做!
随着句子长度增加到(3,5,7,...),我们得到的分析树的数量是:1; 2; 5; 14; 42; 132; 429; 1,430; 4,862; 16,796; 58,786; 208,012; ….(这是 Catalan 数,我们在4的练习中见过)。最后一个是句子长度为 23 的分析树的数目,这是宾州树库 WSJ 部分的句子的平均长度。对于一个长度为 50 的句子有超过 10&lt;sup&gt;12&lt;/sup&gt;的解析,这只是 Piglet 句子长度的一半(1),这些句子小孩可以毫不费力的处理。没有实际的自然语言处理系统可以为一个句子构建数以百万计的树,并根据上下文选择一个合适的。很显然,人也不会这样做!
请注意,这个问题不是只在我们选择的例子中存在。[(Church & Patil, 1982)](./bibliography.html#church1982csa)指出`PP`附着句法歧义在像[(18)](./ch08.html#ex-pp)这样的句子中也是按 Catalan 数的比例增长。
......@@ -521,9 +521,9 @@ grammar = nltk.PCFG.fromstring("""
16. ◑ 挑选一些常用动词,完成以下任务:
1. 写一个程序在 PP 附着语料库`nltk.corpus.ppattach`找到那些动词。找出任何这样的情况,相同的动词有两种不同的附着,其中第一个是名词,或者第二个是名词,或者介词保持不变(正如我们在[2](./ch08.html#sec-whats-the-use-of-syntax)句法歧义中讨论过的)。
1. 写一个程序在 PP 附着语料库`nltk.corpus.ppattach`找到那些动词。找出任何这样的情况,相同的动词有两种不同的附着,其中第一个是名词,或者第二个是名词,或者介词保持不变(正如我们在2句法歧义中讨论过的)。
2. 制定 CFG 语法产生式涵盖其中一些情况。
17. ◑ 写一个程序,比较自上而下的图表分析器与递归下降分析器的效率([4](./ch08.html#sec-parsing))。使用相同的语法和输入的句子。使用`timeit`模块比较它们的性能(见 4.7,如何做到这一点的一个例子)。
17. ◑ 写一个程序,比较自上而下的图表分析器与递归下降分析器的效率(4)。使用相同的语法和输入的句子。使用`timeit`模块比较它们的性能(见 4.7,如何做到这一点的一个例子)。
18. 比较自上而下、自下而上和左角落分析器的性能,使用相同的语法和 3 个符合语法的测试句子。使用`timeit`记录每个分析器在同一个句子上花费的时间。写一个函数,在这三句话上运行这三个分析器,输出 3×3 格的时间,以及行和列的总计。讨论你的发现。
......
......@@ -90,7 +90,7 @@ N[NUM=pl]
一般情况下,即使我们正在尝试开发很小的语法,把产生式放在一个文件中我们可以编辑、测试和修改是很方便的。我们将 1.1 以 NLTK 的数据格式保存为文件`'feat0.fcfg'`。你可以使用`nltk.data.load()`制作你自己的副本进行进一步的实验。
1.2 说明了基于特征的语法图表解析的操作。为输入分词之后,我们导入`load_parser`函数[](./ch09.html#load_parser1),以语法文件名为输入,返回一个图表分析器`cp` [](./ch09.html#load_parser2)。调用分析器的`parse()`方法将迭代生成的分析树;如果文法无法分析输入,`trees`将为空,并将会包含一个或多个分析树,取决于输入是否有句法歧义。
1.2 说明了基于特征的语法图表解析的操作。为输入分词之后,我们导入`load_parser`函数❶,以语法文件名为输入,返回一个图表分析器`cp`。调用分析器的`parse()`方法将迭代生成的分析树;如果文法无法分析输入,`trees`将为空,并将会包含一个或多个分析树,取决于输入是否有句法歧义。
```py
>>> tokens = 'Kim likes children'.split()
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册