1.4 词汇计数

关于前面例子中出现的文本,最明显的事实是它们所使用的词汇不同。在本节中,我们将看到如何使用计算机以各种有用的方式计数词汇。像以前一样,你将会马上开始用 Python 解释器进行试验,即使你可能还没有系统的研究过 Python。通过修改这些例子测试一下你是否理解它们,尝试一下本章结尾处的练习。

首先,让我们算出文本从头到尾的长度,包括文本中出现的词和标点符号。我们使用函数len获取长度,请看在《创世纪》中使用的例子:

  1. >>> len(text3)
  2. 44764
  3. >>>

《创世纪》有 44764 个词和标点符号或者叫“词符”。词符 表示一个我们想要整体对待的字符序列 —— 例如hairyhis:)。当我们计数文本如 to be or not to be 这个短语中词符的个数时,我们计数这些序列出现的次数。因此,我们的例句中出现了 to 和 be 各两次,or 和 not 各一次。然而在例句中只有 4 个不同的词。《创世纪》中有多少不同的词?要用 Python 来回答这个问题,我们处理问题的方法将稍有改变。一个文本词汇表只是它用到的词符的 集合 ,因为在集合中所有重复的元素都只算一个。Python 中我们可以使用命令:set(text3) 获得text3 的词汇表。当你这样做时,屏幕上的很多词会掠过。现在尝试以下操作:

  1. >>> sorted(set(text3)) ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
  2. ['!', "'", '(', ')', ',', ',)', '.', '.)', ':', ';', ';)', '?', '?)',
  3. 'A', 'Abel', 'Abelmizraim', 'Abidah', 'Abide', 'Abimael', 'Abimelech',
  4. 'Abr', 'Abrah', 'Abraham', 'Abram', 'Accad', 'Achbor', 'Adah', ...]
  5. >>> len(set(text3)) ![[2]](/projects/nlp-py-2e-zh/Images/3a93e0258a010fdda935b4ee067411a5.jpg)
  6. 2789
  7. >>>

sorted() 包裹起 Python 表达式set(text3) [1],我们得到一个词汇项的排序表,这个表以各种标点符号开始,然后是以 A 开头的词汇。大写单词排在小写单词前面。我们通过求集合中元素的个数间接获得词汇表的大小,再次使用len来获得这个数值[2]。尽管小说中有 44,764 个词符,但只有 2,789 个不同的单词或“词类型”。一个词类型是指一个词在一个文本中独一无二的出现形式或拼写 —— 也就是说,这个词在词汇表中是唯一的。我们计数的 2,789 个元素中包括标点符号,所以我们把这些叫做唯一元素类型而不是词类型。

现在,让我们对文本词汇丰富度进行测量。下一个例子向我们展示,不同的单词数目只是单词总数的 6%,或者每个单词平均被使用了 16 次(记住,如果你使用的是 Python 2,请在开始输入from __future__ import division)。

  1. >>> len(set(text3)) / len(text3)
  2. 0.06230453042623537
  3. >>>

接下来,让我们专注于特定的词。我们可以计数一个词在文本中出现的次数,计算一个特定的词在文本中占据的百分比:

  1. >>> text3.count("smote")
  2. 5
  3. >>> 100 * text4.count('a') / len(text4)
  4. 1.4643016433938312
  5. >>>

轮到你来: text5 中 lol 出现了多少次?它占文本全部词数的百分比是多少?

你也许想要对几个文本重复这些计算,但重新输入公式是乏味的。你可以自己命名一个任务,如“lexical_diversity”或“percentage”,然后用一个代码块关联它。现在,你只需输入一个很短的名字就可以代替一行或多行 Python 代码,而且你想用多少次就用多少次。执行一个任务的代码段叫做一个函数,我们使用关键字def 给函数定义一个简短的名字。下面的例子演示如何定义两个新的函数,lexical_diversity()percentage()

  1. >>> def lexical_diversity(text): ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
  2. ... return len(set(text)) / len(text) ![[2]](/projects/nlp-py-2e-zh/Images/3a93e0258a010fdda935b4ee067411a5.jpg)
  3. ...
  4. >>> def percentage(count, total): ![[3]](/projects/nlp-py-2e-zh/Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
  5. ... return 100 * count / total
  6. ...

小心!

当遇到第一行末尾的冒号后,Python 解释器提示符由>>> 变为......提示符表示 Python 期望在后面是一个缩进代码块 。缩进是输入四个空格还是敲击 Tab 键,这由你决定。要结束一个缩进代码段,只需输入一个空行。

lexical_diversity() [1]的定义中,我们指定了一个text 参数。这个参数是我们想要计算词汇多样性的实际文本的一个“占位符”,并在用到这个函数的时候出现在将要运行的代码块中 [2]。类似地,percentage() 定义了两个参数,counttotal [3]

只要 Python 知道了lexical_diversity()percentage() 是指定代码段的名字,我们就可以继续使用这些函数:

  1. >>> lexical_diversity(text3)
  2. 0.06230453042623537
  3. >>> lexical_diversity(text5)
  4. 0.13477005109975562
  5. >>> percentage(4, 5)
  6. 80.0
  7. >>> percentage(text4.count('a'), len(text4))
  8. 1.4643016433938312
  9. >>>

扼要重述一下,我们使用或调用一个如lexical_diversity() 这样的函数,只要输入它的名字后面跟一个左括号,再输入文本名字,然后是右括号。这些括号经常出现,它们的作用是分割任务名—— 如lexical_diversity(),与任务将要处理的数据 ——如text3。调用函数时放在参数位置的数据值叫做函数的实参。

在本章中你已经遇到了几个函数,如len(), set()sorted()。通常我们会在函数名后面加一对空括号,像len()中的那样,这只是为了表明这是一个函数而不是其他的 Python 表达式。函数是编程中的一个重要概念,我们在一开始提到它们,是为了让新同学体会编程的强大和富有创造力。如果你现在觉得有点混乱,请不要担心。

稍后我们将看到如何使用函数列表显示数据,像表1.1显示的那样。表的每一行将包含不同数据的相同的计算,我们用函数来做这种重复性的工作。

表 1.1:

Brown 语料库 中各种文体的词汇多样性

  1. >>> sent1 = ['Call', 'me', 'Ishmael', '.']
  2. >>>

在提示符后面,我们输入自己命名的sent1,后跟一个等号,然后是一些引用的词汇,中间用逗号分割并用括号包围。这个方括号内的东西在 Python 中叫做列表:它就是我们存储文本的方式。我们可以通过输入它的名字来查阅它[1]。我们可以查询它的长度[2]。我们甚至可以对它调用我们自己的函数lexical_diversity()[3]

  1. >>> sent1 ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
  2. ['Call', 'me', 'Ishmael', '.']
  3. >>> len(sent1) ![[2]](/projects/nlp-py-2e-zh/Images/3a93e0258a010fdda935b4ee067411a5.jpg)
  4. 4
  5. >>> lexical_diversity(sent1) ![[3]](/projects/nlp-py-2e-zh/Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
  6. 1.0
  7. >>>

还定义了其它几个列表,分别对应每个文本开始的句子,sent2sent9。在这里我们检查其中的两个;你可以自己在 Python 解释器中尝试其余的(如果你得到一个错误说sent2 没有定义,你需要先输入from nltk.book import *)。

  1. >>> sent2
  2. ['The', 'family', 'of', 'Dashwood', 'had', 'long',
  3. 'been', 'settled', 'in', 'Sussex', '.']
  4. >>> sent3
  5. ['In', 'the', 'beginning', 'God', 'created', 'the',
  6. 'heaven', 'and', 'the', 'earth', '.']
  7. >>>

注意

轮到你来: 通过输入名字、等号和一个单词列表, 组建几个你自己的句子,如ex1 = ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail']。重复一些我们先前在第1 节看到的其他 Python 操作,如:sorted(ex1), len(set(ex1)), ex1.count('the')

令人惊喜的是,我们可以对列表使用 Python 加法运算。两个列表相加[1]创造出一个新的列表,包括第一个列表的全部,后面跟着第二个列表的全部。

  1. >>> ['Monty', 'Python'] + ['and', 'the', 'Holy', 'Grail'] ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
  2. ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail']
  3. >>>

注意

这种加法的特殊用法叫做连接;它将多个列表组合为一个列表。我们可以把句子连接起来组成一个文本。

不必逐字的输入列表,可以使用简短的名字来引用预先定义好的列表。

  1. >>> sent4 + sent1
  2. ['Fellow', '-', 'Citizens', 'of', 'the', 'Senate', 'and', 'of', 'the',
  3. 'House', 'of', 'Representatives', ':', 'Call', 'me', 'Ishmael', '.']
  4. >>>

如果我们想要向链表中增加一个元素该如何?这种操作叫做追加。当我们对一个列表使用append()时,列表自身会随着操作而更新。

  1. >>> sent1.append("Some")
  2. >>> sent1
  3. ['Call', 'me', 'Ishmael', '.', 'Some']
  4. >>>