diff --git a/4.md b/4.md index b24829a1f4d53480761908e28e0d643891eecc06..9aa30f2b85193d220e352afb739fb7eb4febe54e 100644 --- a/4.md +++ b/4.md @@ -173,7 +173,7 @@ True 小心! -元组使用逗号操作符来构造。括号是一个 Python 语法的一般功能,设计用于分组。定义一个包含单个元素`'snark'`的元组是通过添加一个尾随的逗号,像这样:"`'snark',`"。空元组是一个特殊的情况下,使用空括号`()`定义。 +元组使用逗号操作符来构造。括号是一个 Python 语法的一般功能,设计用于分组。定义一个包含单个元素`'snark'`的元组是通过添加一个尾随的逗号,像这样:`'snark',`。空元组是一个特殊的情况下,使用空括号`()`定义。 让我们直接比较字符串、列表和元组,在各个类型上做索引、切片和长度操作: @@ -245,7 +245,7 @@ lorry: 4; red: 1; .: 1; ,: 3; Red: 1; yellow: 2 注意 -只在需要的时候进行计算(或者叫做“惰性计算”特性),这是 Python 3 和 NLTK 3 的一个普遍特点。当你期望看到一个序列时,如果你看到的却是类似``这样的结果, 你可以强制求值这个对象,只要把它放在一个期望序列的上下文中,比如`list(`x`)`或`for item in` x。 +只在需要的时候进行计算(或者叫做“惰性计算”特性),这是 Python 3 和 NLTK 3 的一个普遍特点。当你期望看到一个序列时,如果你看到的却是类似``这样的结果, 你可以强制求值这个对象,只要把它放在一个期望序列的上下文中,比如`list(x)`或`for item in x`。 对于一些 NLP 任务,有必要将一个序列分割成两个或两个以上的部分。例如,我们可能需要用 90% 的数据来“训练”一个系统,剩余 10% 进行测试。要做到这一点,我们指定想要分割数据的位置❶,然后在这个位置分割序列❷。 @@ -322,7 +322,7 @@ True 'word' ``` -第二行使用了生成器表达式。这不仅仅是标记方便:在许多语言处理的案例中,生成器表达式会更高效。在❶中,列表对象的存储空间必须在 max()的值被计算之前分配。如果文本非常大的,这将会很慢。在❷中,数据流向调用它的函数。由于调用的函数只是简单的要找最大值——按字典顺序排在最后的词——它可以处理数据流,而无需存储迄今为止的最大值以外的任何值。 +第二行使用了生成器表达式。这不仅仅是标记方便:在许多语言处理的案例中,生成器表达式会更高效。在❶中,列表对象的存储空间必须在`max()`的值被计算之前分配。如果文本非常大的,这将会很慢。在❷中,数据流向调用它的函数。由于调用的函数只是简单的要找最大值——按字典顺序排在最后的词——它可以处理数据流,而无需存储迄今为止的最大值以外的任何值。 ## 4.3 风格的问题 @@ -332,7 +332,7 @@ True 编写程序时,你会做许多微妙的选择:名称、间距、注释等等。当你在看别人编写的代码时,风格上的不必要的差异使其难以理解。因此,Python 语言的设计者发表了 Python 代码风格指南,http`http://www.python.org/dev/peps/pep-0008/`。风格指南中提出的基本价值是一致性,目的是最大限度地提高代码的可读性。我们在这里简要回顾一下它的一些主要建议,并请读者阅读完整的指南,里面有对实例的详细的讨论。 -代码布局中每个缩进级别应使用 4 个空格。你应该确保当你在一个文件中写 Python 代码时,避免使用 tab 缩进,因为它可能由于不同的文本编辑器的不同解释而产生混乱。每行应少于 80 个字符长;如果必要的话,你可以在圆括号、方括号或花括号内换行,因为 Python 能够探测到该行与下一行是连续的。如果你需要在圆括号、方括号或大括号中换行,通常可以添加额外的括号,也可以在行尾需要换行的地方添加一个反斜杠: +代码布局中每个缩进级别应使用 4 个空格。你应该确保当你在一个文件中写 Python 代码时,避免使用`tab`缩进,因为它可能由于不同的文本编辑器的不同解释而产生混乱。每行应少于 80 个字符长;如果必要的话,你可以在圆括号、方括号或花括号内换行,因为 Python 能够探测到该行与下一行是连续的。如果你需要在圆括号、方括号或大括号中换行,通常可以添加额外的括号,也可以在行尾需要换行的地方添加一个反斜杠: ```py >>> if (len(syllables) > 4 and len(syllables[2]) == 3 and @@ -527,7 +527,7 @@ def get_text(file): 'Monty Python Monty Python Monty Python' ``` -一个 Python 函数并不是一定需要有一个 return 语句。有些函数做它们的工作的同时会附带输出结果、修改文件或者更新参数的内容。(这种函数在其他一些编程语言中被称为“过程”)。 +一个 Python 函数并不是一定需要有一个`return`语句。有些函数做它们的工作的同时会附带输出结果、修改文件或者更新参数的内容。(这种函数在其他一些编程语言中被称为“过程”)。 考虑以下三个排序函数。第三个是危险的,因为程序员可能没有意识到它已经修改了给它的输入。一般情况下,函数应该修改参数的内容(`my_sort1()`)或返回一个值(`my_sort2()`),而不是两个都做(`my_sort3()`)。 @@ -570,7 +570,7 @@ def get_text(file): '' ``` -让我们来看看列表`p`上发生了什么。当我们调用`set_up(w, p)`,`p`的值(一个空列表的引用)被分配到一个新的本地变量`properties`,所以现在这两个变量引用相同的内存位置。函数修改`properties`,而这种变化也反映在`p`值上,正如我们所看到的。函数也分配给 properties 一个新的值(数字`5`);这并不能修改该内存位置上的内容,而是创建了一个新的局部变量。这种行为就好像是我们做了下列赋值序列: +让我们来看看列表`p`上发生了什么。当我们调用`set_up(w, p)`,`p`的值(一个空列表的引用)被分配到一个新的本地变量`properties`,所以现在这两个变量引用相同的内存位置。函数修改`properties`,而这种变化也反映在`p`值上,正如我们所看到的。函数也分配给`properties`一个新的值(数字`5`);这并不能修改该内存位置上的内容,而是创建了一个新的局部变量。这种行为就好像是我们做了下列赋值序列: ```py >>> p = [] @@ -631,7 +631,7 @@ def get_text(file): 结构良好的程序通常都广泛使用函数。当一个程序代码块增长到超过 10-20 行,如果将代码分成一个或多个函数,每一个有明确的目的,这将对可读性有很大的帮助。这类似于好文章被划分成段,每段话表示一个主要思想。 -函数提供了一种重要的抽象。它们让我们将多个动作组合成一个单一的复杂的行动,并给它关联一个名称。(比较我们组合动作 go 和 bring back 为一个单一的更复杂的动作 fetch。)当我们使用函数时,主程序可以在一个更高的抽象水平编写,使其结构更透明,例如 +函数提供了一种重要的抽象。它们让我们将多个动作组合成一个单一的复杂的行动,并给它关联一个名称。(比较我们组合动作“去”和“带回”为一个单一的更复杂的动作“取回”。)当我们使用函数时,主程序可以在一个更高的抽象水平编写,使其结构更透明,例如 ```py >>> data = load_corpus() @@ -641,7 +641,7 @@ def get_text(file): 适当使用函数使程序更具可读性和可维护性。另外,重新实现一个函数已成为可能——使用更高效的代码替换函数体——不需要关心程序的其余部分。 -思考 4.3 中`freq_words`函数。它更新一个作为参数传递进来的频率分布的内容,并输出前 n 个最频繁的词的列表。 +思考 4.3 中`freq_words`函数。它更新一个作为参数传递进来的频率分布的内容,并输出前`n`个最频繁的词的列表。 ```py from urllib import request @@ -987,7 +987,7 @@ As metrics, they must satisfy the following three requirements: 繁琐浮躁不谈,调试代码是很难的,因为有那么多的方式出现故障。我们对输入数据、算法甚至编程语言的理解可能是错误的。让我们分别来看看每种情况的例子。 -首先,输入的数据可能包含一些意想不到的字符。例如,WordNet 的同义词集名称的形式是`tree.n.01`,由句号分割成 3 个部分。最初 NLTK 的 WordNet 模块使用`split('.')`分解这些名称。然而,当有人试图寻找词 PhD 时,这种方法就不能用了,它的同义集名称是`ph.d..n.01`,包含 4 个逗号而不是预期的 2 个。解决的办法是使用`rsplit('.', 2)`利用最右边的句号最多分割两次,留下字符串`ph.d.`不变。虽然在模块发布之前已经测试过,但就在几个星期前有人检测到这个问题(见`http://code.google.com/p/nltk/issues/detail?id=297`)。 +首先,输入的数据可能包含一些意想不到的字符。例如,WordNet 的同义词集名称的形式是`tree.n.01`,由句号分割成 3 个部分。最初 NLTK 的 WordNet 模块使用`split('.')`分解这些名称。然而,当有人试图寻找词`PhD`时,这种方法就不能用了,它的同义集名称是`ph.d..n.01`,包含 4 个逗号而不是预期的 2 个。解决的办法是使用`rsplit('.', 2)`利用最右边的句号最多分割两次,留下字符串`ph.d.`不变。虽然在模块发布之前已经测试过,但就在几个星期前有人检测到这个问题(见`http://code.google.com/p/nltk/issues/detail?id=297`)。 第二,提供的函数可能不会像预期的那样运作。例如,在测试 NLTK 中的 WordNet 接口时,一名作者注意到没有同义词集定义了反义词,而底层数据库提供了大量的反义词的信息。这看着像是 WordNet 接口中的一个错误,结果却是对 WordNet 本身的误解:反义词在词条中定义,而不是在义词集中。唯一的“bug”是对接口的一个误解(参见`http://code.google.com/p/nltk/issues/detail?id=98`)。 @@ -1007,7 +1007,7 @@ As metrics, they must satisfy the following three requirements: ['omg', 'teh', 'teh', 'mat', 'omg', 'teh', 'teh', 'mat'] ``` -我们第一次调用`find_words()`❶,我们得到所有预期的三个字母的词。第二次,我们为 result 指定一个初始值,一个单元素列表`['ur']`,如预期,结果中有这个词连同我们的文本中的其他双字母的词。现在,我们再次使用❶中相同的参数调用`find_words()`❸,但我们得到了不同的结果!我们每次不使用第三个参数调用`find_words()`,结果都只会延长前次调用的结果,而不是以在函数定义中指定的空链表 result 开始。程序的行为并不如预期,因为我们错误地认为在函数被调用时会创建默认值。然而,它只创建了一次,在 Python 解释器加载这个函数时。这一个列表对象会被使用,只要没有给函数提供明确的值。 +我们第一次调用`find_words()`❶,我们得到所有预期的三个字母的词。第二次,我们为`result`指定一个初始值,一个单元素列表`['ur']`,如预期,结果中有这个词连同我们的文本中的其他双字母的词。现在,我们再次使用❶中相同的参数调用`find_words()`❸,但我们得到了不同的结果!我们每次不使用第三个参数调用`find_words()`,结果都只会延长前次调用的结果,而不是以在函数定义中指定的空链表`result`开始。程序的行为并不如预期,因为我们错误地认为在函数被调用时会创建默认值。然而,它只创建了一次,在 Python 解释器加载这个函数时。这一个列表对象会被使用,只要没有给函数提供明确的值。 ### 调试技术 @@ -1027,7 +1027,7 @@ Python 提供了一个调试器,它允许你监视程序的执行,指定程 它会给出一个提示`(Pdb)`,你可以在那里输入指令给调试器。输入`help`来查看命令的完整列表。输入`step`(或只输入`s`)将执行当前行然后停止。如果当前行调用一个函数,它将进入这个函数并停止在第一行。输入`next`(或只输入`n`)是类似的,但它会在当前函数中的下一行停止执行。`break`(或`b`)命令可用于创建或列出断点。输入`continue`(或`c`)会继续执行直到遇到下一个断点。输入任何变量的名称可以检查它的值。 -我们可以使用 Python 调试器来查找`find_words()`函数的问题。请记住问题是在第二次调用函数时产生的。我们一开始将不使用调试器而调用该函数[first-run_](./ch04.html#id9),使用可能的最小输入。第二次我们使用调试器调用它[second-run_](./ch04.html#id11)。.. doctest-ignore: +我们可以使用 Python 调试器来查找`find_words()`函数的问题。请记住问题是在第二次调用函数时产生的。我们一开始将不使用调试器而调用该函数,使用可能的最小输入。第二次我们使用调试器调用它。 ```py >>> import pdb @@ -1051,7 +1051,7 @@ result = ['cat'] 一旦你觉得你发现了错误,作为一个假设查看你的解决方案。在重新运行该程序之前尝试预测你修正错误的影响。如果 bug 不能被修正,不要陷入盲目修改代码希望它会奇迹般地重新开始运作的陷阱。相反,每一次修改都要尝试阐明错误是什么和为什么这样修改会解决这个问题的假设。如果这个问题没有解决就撤消这次修改。 -当你开发你的程序时,扩展其功能,并修复所有 bug,维护一套测试用例是有益的。这被称为回归测试,因为它是用来检测代码“回归”的地方——修改代码后会带来一个意想不到的副作用是以前能运作的程序不运作了的地方。Python 以`doctest`模块的形式提供了一个简单的回归测试框架。这个模块搜索一个代码或文档文件查找类似与交互式 Python 会话这样的文本块,这种形式你已经在这本书中看到了很多次。它执行找到的 Python 命令,测试其输出是否与原始文件中所提供的输出匹配。每当有不匹配时,它会报告预期值和实际值。有关详情,请查询在 documentation at `http://docs.python.org/library/doctest.html`上的`doctest`文档。除了回归测试它的值,`doctest`模块有助于确保你的软件文档与你的代码保持同步。 +当你开发你的程序时,扩展其功能,并修复所有 bug,维护一套测试用例是有益的。这被称为回归测试,因为它是用来检测代码“回归”的地方——修改代码后会带来一个意想不到的副作用是以前能运作的程序不运作了的地方。Python 以`doctest`模块的形式提供了一个简单的回归测试框架。这个模块搜索一个代码或文档文件查找类似与交互式 Python 会话这样的文本块,这种形式你已经在这本书中看到了很多次。它执行找到的 Python 命令,测试其输出是否与原始文件中所提供的输出匹配。每当有不匹配时,它会报告预期值和实际值。有关详情,请查询在`http://docs.python.org/library/doctest.html`上的`doctest`文档。除了回归测试它的值,`doctest`模块有助于确保你的软件文档与你的代码保持同步。 也许最重要的防御性编程策略是要清楚的表述你的代码,选择有意义的变量和函数名,并通过将代码分解成拥有良好文档的接口的函数和模块尽可能的简化代码。 @@ -1061,7 +1061,7 @@ result = ['cat'] 解决算法问题的一个重要部分是为手头的问题选择或改造一个合适的算法。有时会有几种选择,能否选择最好的一个取决于对每个选择随数据增长如何执行的知识。关于这个话题的书很多,我们只介绍一些关键概念和精心描述在自然语言处理中最普遍的做法。 -最有名的策略被称为分而治之。我们解决一个大小为 *n* 的问题通过将其分成两个大小为 *n/2* 的问题,解决这些问题,组合它们的结果成为原问题的结果。例如,假设我们有一堆卡片,每张卡片上写了一个词。我们可以排序这一堆卡片,通过将它分成两半分别给另外两个人来排序(他们又可以做同样的事情)。然后,得到两个排好序的卡片堆,将它们并成一个单一的排序堆就是一项容易的任务了。参见 4.8 这个过程的说明。 +最有名的策略被称为分而治之。我们解决一个大小为`n`的问题通过将其分成两个大小为`n/2`的问题,解决这些问题,组合它们的结果成为原问题的结果。例如,假设我们有一堆卡片,每张卡片上写了一个词。我们可以排序这一堆卡片,通过将它分成两半分别给另外两个人来排序(他们又可以做同样的事情)。然后,得到两个排好序的卡片堆,将它们并成一个单一的排序堆就是一项容易的任务了。参见 4.8 这个过程的说明。 ![Images/mergesort.png](Images/1094084b61ac3f0e4416e92869c52ccd.jpg) @@ -1073,9 +1073,9 @@ result = ['cat'] ### 递归 -上面的关于排序和搜索的例子有一个引人注目的特征:解决一个大小为 n 的问题,可以将其分成两半,然后处理一个或多个大小为 n/2 的问题。实现这种方法的一种常见方式是使用递归。我们定义一个函数 f,从而简化了问题,并调用自身来解决一个或多个同样问题的更简单的实例。然后组合它们的结果成为原问题的解答。 +上面的关于排序和搜索的例子有一个引人注目的特征:解决一个大小为`n`的问题,可以将其分成两半,然后处理一个或多个大小为`n/2`的问题。实现这种方法的一种常见方式是使用递归。我们定义一个函数`f`,从而简化了问题,并调用自身来解决一个或多个同样问题的更简单的实例。然后组合它们的结果成为原问题的解答。 -例如,假设我们有 n 个词,要计算出它们结合在一起有多少不同的方式能组成一个词序列。如果我们只有一个词(n=1),只是一种方式组成一个序列。如果我们有 2 个词,就有 2 种方式将它们组成一个序列。3 个词有 6 种可能性。一般的,n 个词有 n × n-1 × … × 2 × 1 种方式(即 n 的阶乘)。我们可以将这些编写成如下代码: +例如,假设我们有`n`个词,要计算出它们结合在一起有多少不同的方式能组成一个词序列。如果我们只有一个词(`n=1`),只是一种方式组成一个序列。如果我们有 2 个词,就有 2 种方式将它们组成一个序列。3 个词有 6 种可能性。一般的,`n`个词有`n × n-1 × … × 2 × 1`种方式(即`n`的阶乘)。我们可以将这些编写成如下代码: ```py >>> def factorial1(n): @@ -1085,7 +1085,7 @@ result = ['cat'] ... return result ``` -但是,也可以使用一种递归算法来解决这个问题,该算法基于以下观察。假设我们有办法为 n-1 不同的词构建所有的排列。然后对于每个这样的排列,有 n 个地方我们可以插入一个新词:开始、结束或任意两个词之间的 n-2 个空隙。因此,我们简单的将 n-1 个词的解决方案数乘以 n 的值。我们还需要基础案例,也就是说,如果我们有一个词,只有一个顺序。我们可以将这些编写成如下代码: +但是,也可以使用一种递归算法来解决这个问题,该算法基于以下观察。假设我们有办法为`n-1`不同的词构建所有的排列。然后对于每个这样的排列,有`n`个地方我们可以插入一个新词:开始、结束或任意两个词之间的`n-2`个空隙。因此,我们简单的将`n-1`个词的解决方案数乘以`n`的值。我们还需要基础案例,也就是说,如果我们有一个词,只有一个顺序。我们可以将这些编写成如下代码: ```py >>> def factorial2(n): @@ -1095,7 +1095,7 @@ result = ['cat'] ... return n * factorial2(n-1) ``` -这两种算法解决同样的问题。一个使用迭代,而另一个使用递归。我们可以用递归处理深层嵌套的对象,例如 WordNet 的上位词层次。让我们计数给定同义词集 s 为根的上位词层次的大小。我们会找到 s 的每个下位词的大小,然后将它们加到一起(我们也将加 1 表示同义词集本身)。下面的函数`size1()`做这项工作;注意函数体中包括`size1()`的递归调用: +这两种算法解决同样的问题。一个使用迭代,而另一个使用递归。我们可以用递归处理深层嵌套的对象,例如 WordNet 的上位词层次。让我们计数给定同义词集`s`为根的上位词层次的大小。我们会找到`s`的每个下位词的大小,然后将它们加到一起(我们也将加 1 表示同义词集本身)。下面的函数`size1()`做这项工作;注意函数体中包括`size1()`的递归调用: ```py >>> def size1(s): @@ -1114,7 +1114,7 @@ result = ['cat'] ... return total ``` -迭代解决方案不仅代码更长而且更难理解。它迫使我们程序式的思考问题,并跟踪`layer`和`total`随时间变化发生了什么。让我们满意的是两种解决方案均给出了相同的结果。我们将使用 import 语句的一个新的形式,允许我们缩写名称`wordnet`为`wn`: +迭代解决方案不仅代码更长而且更难理解。它迫使我们程序式的思考问题,并跟踪`layer`和`total`随时间变化发生了什么。让我们满意的是两种解决方案均给出了相同的结果。我们将使用`import`语句的一个新的形式,允许我们缩写名称`wordnet`为`wn`: ```py >>> from nltk.corpus import wordnet as wn @@ -1125,7 +1125,7 @@ result = ['cat'] 190 ``` -作为递归的最后一个例子,让我们用它来构建一个深嵌套的对象。一个字母查找树是一种可以用来索引词汇的数据结构,一次一个字母。(这个名字来自于单词 retrieval)。例如,如果`trie`包含一个字母的查找树,那么`trie['c']`是一个较小的查找树,包含所有以 c 开头的词。4.9 演示了使用 Python 字典(3)构建查找树的递归过程。若要插入词 chien(dog 的法语),我们将 c 分类,递归的掺入 hien 到`trie['c']`子查找树中。递归继续直到词中没有剩余的字母,于是我们存储的了预期值(本例中是词 dog)。 +作为递归的最后一个例子,让我们用它来构建一个深嵌套的对象。一个字母查找树是一种可以用来索引词汇的数据结构,一次一个字母。(这个名字来自于单词`retrieval`)。例如,如果`trie`包含一个字母的查找树,那么`trie['c']`是一个较小的查找树,包含所有以`c`开头的词。4.9 演示了使用 Python 字典(3)构建查找树的递归过程。若要插入词`chien`(`dog`的法语),我们将`c`分类,递归的掺入`hien`到`trie['c']`子查找树中。递归继续直到词中没有剩余的字母,于是我们存储的了预期值(本例中是词`dog`)。 ```py def insert(trie, key, value): @@ -1209,7 +1209,7 @@ def preprocess(tagged_corpus): 动态规划是一种自然语言处理中被广泛使用的算法设计的一般方法。“programming”一词的用法与你可能想到的感觉不同,是规划或调度的意思。动态规划用于解决包含多个重叠的子问题的问题。不是反复计算这些子问题,而是简单的将它们的计算结果存储在一个查找表中。在本节的余下部分,我们将介绍动态规划,在一个相当不同的背景下来句法分析。 -Pingala 是大约生活在公元前 5 世纪的印度作家,作品有被称为《Chandas Shastra》的梵文韵律专著。Virahanka 大约在公元 6 世纪延续了这项工作,研究短音节和长音节组合产生一个长度为 *n* 的旋律的组合数。短音节,标记为 *S*,占一个长度单位,而长音节,标记为 *L*,占 2 个长度单位。例如,Pingala 发现,有 5 种方式构造一个长度为 4 的旋律:*V[4] = {LL, SSL, SLS, LSS, SSSS}*。请看,我们可以将 *V*[4]分成两个子集,以 *L* 开始的子集和以 *S* 开始的子集,如[(1)](./ch04.html#ex-v4)所示。 +Pingala 是大约生活在公元前 5 世纪的印度作家,作品有被称为《Chandas Shastra》的梵文韵律专著。Virahanka 大约在公元 6 世纪延续了这项工作,研究短音节和长音节组合产生一个长度为`n`的旋律的组合数。短音节,标记为`S`,占一个长度单位,而长音节,标记为`L`,占 2 个长度单位。例如,Pingala 发现,有 5 种方式构造一个长度为 4 的旋律:`V[4] = {LL, SSL, SLS, LSS, SSSS}`。请看,我们可以将`V[4]`分成两个子集,以`L`开始的子集和以`S`开始的子集,如`(1)`所示。 ```py V4 = @@ -1220,7 +1220,7 @@ V4 = ``` -有了这个观察结果,我们可以写一个小的递归函数称为`virahanka1()`来计算这些旋律,如 4.12 所示。请注意,要计算 *V*[4],我们先要计算 *V*[3]和 *V*[2]。但要计算 *V*[3],我们先要计算 *V*[2]和 *V*[1]。在[(2)](./ch04.html#ex-call-structure)中描述了这种调用结构。 +有了这个观察结果,我们可以写一个小的递归函数称为`virahanka1()`来计算这些旋律,如 4.12 所示。请注意,要计算`V[4]`,我们先要计算`V[3]`和`V[2]`。但要计算`V[3]`,我们先要计算`V[2]`和`V[1]`。在`(2)`中描述了这种调用结构。 ```py from numpy import arange @@ -1248,7 +1248,7 @@ def bar_chart(categories, words, counts): 图 4.14:条形图显示布朗语料库中不同部分的情态动词频率:这个可视化图形由 4.13 中的程序产生。 -从该柱状图可以立即看出 may 和 must 有几乎相同的相对频率。could 和 might 也一样。 +从该柱状图可以立即看出`may`和`must`有几乎相同的相对频率。`could`和`might`也一样。 也可以动态的产生这些数据的可视化图形。例如,一个使用表单输入的网页可以允许访问者指定搜索参数,提交表单,看到一个动态生成的可视化图形。要做到这一点,我们必须为`matplotlib`指定`Agg`后台,它是一个产生栅格(像素)图像的库❶。下一步,我们像以前一样使用相同的 Matplotlib 方法,但不是用`pyplot.show()`显示结果在图形终端,而是使用`pyplot.savefig()`把它保存到一个文件❷。我们指定文件名,然后输出一些 HTML 标记指示网页浏览器来加载该文件。 @@ -1391,7 +1391,7 @@ Python 网站提供大量文档。理解内置的函数和标准类型是很重 5. ☼ 通过输入`help(cmp)`阅读关于内置的比较函数`cmp`的内容。它与比较运算符在行为上有何不同? -6. ☼ 创建一个 n-grams 的滑动窗口的方法在下面两种极端情况下是否正确:n = 1 和 n = `len(sent)`? +6. ☼ 创建一个 n-grams 的滑动窗口的方法在下面两种极端情况下是否正确:`n = 1`和`n = len(sent)`? 7. ☼ 我们指出当空字符串和空链表出现在`if`从句的条件部分时,它们的判断结果是`False`。在这种情况下,它们被说成出现在一个布尔上下文中。实验各种不同的布尔上下文中的非布尔表达式,看它们是否被判断为`True`或`False`。 @@ -1412,7 +1412,7 @@ Python 网站提供大量文档。理解内置的函数和标准类型是很重 13. ◑ 写代码初始化一个称为`word_vowels`的二维数组的集合,处理一个词列表,添加每个词到`word_vowels[l][v]`,其中`l`是词的长度,`v`是它包含的元音的数量。 -14. ◑ 写一个函数`novel10(text)`输出所有在一个文本最后 10%出现而之前没有遇到过的词。 +14. ◑ 写一个函数`novel10(text)`输出所有在一个文本最后 10% 出现而之前没有遇到过的词。 15. ◑ 写一个程序将一个句子表示成一个单独的字符串,分割和计数其中的词。让它输出每一个词和词的频率,每行一个,按字母顺序排列。 @@ -1430,7 +1430,7 @@ Python 网站提供大量文档。理解内置的函数和标准类型是很重 3. 写一个函数`decode()`来处理文本,随机替换词汇为它们的 Gematria 等值的词,以发现文本的“隐藏的含义”。 -17. ◑ 写一个函数`shorten(text, n)`处理文本,省略文本中前 n 个最频繁出现的词。它的可读性会如何? +17. ◑ 写一个函数`shorten(text, n)`处理文本,省略文本中前`n`个最频繁出现的词。它的可读性会如何? 18. ◑ 写代码输出词汇的索引,允许别人根据其含义查找词汇(或它们的发言;词汇条目中包含的任何属性)。 @@ -1446,13 +1446,13 @@ Python 网站提供大量文档。理解内置的函数和标准类型是很重 24. ◑ 阅读关于“关键字联动”的内容([(Scott & Tribble, 2006)](./bibliography.html#scott2006)的第 5 章)。从 NLTK 的莎士比亚语料库中提取关键字,使用 NetworkX 包,画出关键字联动网络。 -25. ◑ 阅读有关字符串编辑距离和 Levenshtein 算法的内容。尝试`nltk.edit_distance()`提供的实现。这用的是动态规划的何种方式?它使用的是自下而上还是自上而下的方法?[另见`http://norvig.com/spell-correct.html`] +25. ◑ 阅读有关字符串编辑距离和 Levenshtein 算法的内容。尝试`nltk.edit_distance()`提供的实现。这用的是动态规划的何种方式?它使用的是自下而上还是自上而下的方法?(另见`http://norvig.com/spell-correct.html`) -26. ◑ Catalan 数出现在组合数学的许多应用中,包括解析树的计数(6)。该级数可以定义如下:C[0] = 1, and C<sub>n+1</sub> = Σ<sub>0..n</sub> (C<sub>i</sub>C<sub>n-i</sub>)。 +26. ◑卡塔兰数出现在组合数学的许多应用中,包括解析树的计数`(6)`。该级数可以定义如下:`C[0] = 1`,`C[n+1] = Σ0..n (C[i]C[n-i])`。 - 1. 编写一个递归函数计算第 n 个 Catalan 数 C<sub>n</sub>。 + 1. 编写一个递归函数计算第`n`个卡塔兰数`C[n]`。 2. 现在写另一个函数使用动态规划做这个计算。 - 3. 使用`timeit`模块比较当 n 增加时这些函数的性能。 + 3. 使用`timeit`模块比较当`n`增加时这些函数的性能。 27. ★ 重现有关著作权鉴定的[(Zhao & Zobel, 2007)](./bibliography.html#zhao07)中的一些结果。 28. ★ 研究性别特异词汇选择,看你是否可以重现一些`http://www.clintoneast.com/articles/words.php`的结果 diff --git a/8.md b/8.md index 99a3b92df5fd025ba9f0a7d603a138c33569ce16..1a60425a1fbe6c44d3c10b8897b98b5dcae1f851 100644 --- a/8.md +++ b/8.md @@ -410,9 +410,9 @@ 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 的练习中见过)。最后一个是句子长度为 23 的分析树的数目,这是宾州树库 WSJ 部分的句子的平均长度。对于一个长度为 50 的句子有超过 10<sup>12</sup>的解析,这只是 Piglet 句子长度的一半(1),这些句子小孩可以毫不费力的处理。没有实际的自然语言处理系统可以为一个句子构建数以百万计的树,并根据上下文选择一个合适的。很显然,人也不会这样做! +随着句子长度增加到(3,5,7,...),我们得到的分析树的数量是:1; 2; 5; 14; 42; 132; 429; 1,430; 4,862; 16,796; 58,786; 208,012; ….(这是卡塔兰数,我们在 4 的练习中见过)。最后一个是句子长度为 23 的分析树的数目,这是宾州树库 WSJ 部分的句子的平均长度。对于一个长度为 50 的句子有超过 10<sup>12</sup>的解析,这只是 Piglet 句子长度的一半(1),这些句子小孩可以毫不费力的处理。没有实际的自然语言处理系统可以为一个句子构建数以百万计的树,并根据上下文选择一个合适的。很显然,人也不会这样做! -请注意,这个问题不是只在我们选择的例子中存在。[(Church & Patil, 1982)](./bibliography.html#church1982csa)指出`PP`附着句法歧义在像[(18)](./ch08.html#ex-pp)这样的句子中也是按 Catalan 数的比例增长。 +请注意,这个问题不是只在我们选择的例子中存在。[(Church & Patil, 1982)](./bibliography.html#church1982csa)指出`PP`附着句法歧义在像[(18)](./ch08.html#ex-pp)这样的句子中也是按卡塔兰数的比例增长。 ```py def give(t):