1.4 词汇计数
关于前面例子中出现的文本,最明显的事实是它们所使用的词汇不同。在本节中,我们将看到如何使用计算机以各种有用的方式计数词汇。像以前一样,你将会马上开始用 Python 解释器进行试验,即使你可能还没有系统的研究过 Python。通过修改这些例子测试一下你是否理解它们,尝试一下本章结尾处的练习。
首先,让我们算出文本从头到尾的长度,包括文本中出现的词和标点符号。我们使用函数len
获取长度,请看在《创世纪》中使用的例子:
>>> len(text3)
44764
>>>
《创世纪》有 44764 个词和标点符号或者叫“词符”。词符 表示一个我们想要整体对待的字符序列 —— 例如hairy
,his
或 :)
。当我们计数文本如 to be or not to be 这个短语中词符的个数时,我们计数这些序列出现的次数。因此,我们的例句中出现了 to 和 be 各两次,or 和 not 各一次。然而在例句中只有 4 个不同的词。《创世纪》中有多少不同的词?要用 Python 来回答这个问题,我们处理问题的方法将稍有改变。一个文本词汇表只是它用到的词符的 集合 ,因为在集合中所有重复的元素都只算一个。Python 中我们可以使用命令:set(text3)
获得text3
的词汇表。当你这样做时,屏幕上的很多词会掠过。现在尝试以下操作:
>>> sorted(set(text3)) ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
['!', "'", '(', ')', ',', ',)', '.', '.)', ':', ';', ';)', '?', '?)',
'A', 'Abel', 'Abelmizraim', 'Abidah', 'Abide', 'Abimael', 'Abimelech',
'Abr', 'Abrah', 'Abraham', 'Abram', 'Accad', 'Achbor', 'Adah', ...]
>>> len(set(text3)) ![[2]](/projects/nlp-py-2e-zh/Images/3a93e0258a010fdda935b4ee067411a5.jpg)
2789
>>>
用sorted()
包裹起 Python 表达式set(text3)
,我们得到一个词汇项的排序表,这个表以各种标点符号开始,然后是以 A 开头的词汇。大写单词排在小写单词前面。我们通过求集合中元素的个数间接获得词汇表的大小,再次使用len
来获得这个数值。尽管小说中有 44,764 个词符,但只有 2,789 个不同的单词或“词类型”。一个词类型是指一个词在一个文本中独一无二的出现形式或拼写 —— 也就是说,这个词在词汇表中是唯一的。我们计数的 2,789 个元素中包括标点符号,所以我们把这些叫做唯一元素类型而不是词类型。
现在,让我们对文本词汇丰富度进行测量。下一个例子向我们展示,不同的单词数目只是单词总数的 6%,或者每个单词平均被使用了 16 次(记住,如果你使用的是 Python 2,请在开始输入from __future__ import division
)。
>>> len(set(text3)) / len(text3)
0.06230453042623537
>>>
接下来,让我们专注于特定的词。我们可以计数一个词在文本中出现的次数,计算一个特定的词在文本中占据的百分比:
>>> text3.count("smote")
5
>>> 100 * text4.count('a') / len(text4)
1.4643016433938312
>>>
注
轮到你来: text5
中 lol 出现了多少次?它占文本全部词数的百分比是多少?
你也许想要对几个文本重复这些计算,但重新输入公式是乏味的。你可以自己命名一个任务,如“lexical_diversity”或“percentage”,然后用一个代码块关联它。现在,你只需输入一个很短的名字就可以代替一行或多行 Python 代码,而且你想用多少次就用多少次。执行一个任务的代码段叫做一个函数,我们使用关键字def
给函数定义一个简短的名字。下面的例子演示如何定义两个新的函数,lexical_diversity()
和percentage()
:
>>> def lexical_diversity(text): ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
... return len(set(text)) / len(text) ![[2]](/projects/nlp-py-2e-zh/Images/3a93e0258a010fdda935b4ee067411a5.jpg)
...
>>> def percentage(count, total): ![[3]](/projects/nlp-py-2e-zh/Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
... return 100 * count / total
...
小心!
当遇到第一行末尾的冒号后,Python 解释器提示符由>>>
变为...
。...
提示符表示 Python 期望在后面是一个缩进代码块 。缩进是输入四个空格还是敲击 Tab 键,这由你决定。要结束一个缩进代码段,只需输入一个空行。
lexical_diversity()
的定义中,我们指定了一个text
参数。这个参数是我们想要计算词汇多样性的实际文本的一个“占位符”,并在用到这个函数的时候出现在将要运行的代码块中 。类似地,percentage()
定义了两个参数,count
和total
。
只要 Python 知道了lexical_diversity()
和percentage()
是指定代码段的名字,我们就可以继续使用这些函数:
>>> lexical_diversity(text3)
0.06230453042623537
>>> lexical_diversity(text5)
0.13477005109975562
>>> percentage(4, 5)
80.0
>>> percentage(text4.count('a'), len(text4))
1.4643016433938312
>>>
扼要重述一下,我们使用或调用一个如lexical_diversity()
这样的函数,只要输入它的名字后面跟一个左括号,再输入文本名字,然后是右括号。这些括号经常出现,它们的作用是分割任务名—— 如lexical_diversity()
,与任务将要处理的数据 ——如text3
。调用函数时放在参数位置的数据值叫做函数的实参。
在本章中你已经遇到了几个函数,如len()
, set()
和sorted()
。通常我们会在函数名后面加一对空括号,像len()
中的那样,这只是为了表明这是一个函数而不是其他的 Python 表达式。函数是编程中的一个重要概念,我们在一开始提到它们,是为了让新同学体会编程的强大和富有创造力。如果你现在觉得有点混乱,请不要担心。
稍后我们将看到如何使用函数列表显示数据,像表1.1显示的那样。表的每一行将包含不同数据的相同的计算,我们用函数来做这种重复性的工作。
表 1.1:
Brown 语料库 中各种文体的词汇多样性
>>> sent1 = ['Call', 'me', 'Ishmael', '.']
>>>
在提示符后面,我们输入自己命名的sent1
,后跟一个等号,然后是一些引用的词汇,中间用逗号分割并用括号包围。这个方括号内的东西在 Python 中叫做列表:它就是我们存储文本的方式。我们可以通过输入它的名字来查阅它。我们可以查询它的长度。我们甚至可以对它调用我们自己的函数lexical_diversity()
。
>>> sent1 ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
['Call', 'me', 'Ishmael', '.']
>>> len(sent1) ![[2]](/projects/nlp-py-2e-zh/Images/3a93e0258a010fdda935b4ee067411a5.jpg)
4
>>> lexical_diversity(sent1) ![[3]](/projects/nlp-py-2e-zh/Images/334be383b5db7ffe3599cc03bc74bf9e.jpg)
1.0
>>>
还定义了其它几个列表,分别对应每个文本开始的句子,sent2
… sent9
。在这里我们检查其中的两个;你可以自己在 Python 解释器中尝试其余的(如果你得到一个错误说sent2
没有定义,你需要先输入from nltk.book import *
)。
>>> sent2
['The', 'family', 'of', 'Dashwood', 'had', 'long',
'been', 'settled', 'in', 'Sussex', '.']
>>> sent3
['In', 'the', 'beginning', 'God', 'created', 'the',
'heaven', 'and', 'the', 'earth', '.']
>>>
注意
轮到你来: 通过输入名字、等号和一个单词列表, 组建几个你自己的句子,如ex1 = ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail']
。重复一些我们先前在第1 节看到的其他 Python 操作,如:sorted(ex1)
, len(set(ex1))
, ex1.count('the')
。
令人惊喜的是,我们可以对列表使用 Python 加法运算。两个列表相加创造出一个新的列表,包括第一个列表的全部,后面跟着第二个列表的全部。
>>> ['Monty', 'Python'] + ['and', 'the', 'Holy', 'Grail'] ![[1]](/projects/nlp-py-2e-zh/Images/4b5cae275c53c53ccc8f2f779acada3e.jpg)
['Monty', 'Python', 'and', 'the', 'Holy', 'Grail']
>>>
注意
这种加法的特殊用法叫做连接;它将多个列表组合为一个列表。我们可以把句子连接起来组成一个文本。
不必逐字的输入列表,可以使用简短的名字来引用预先定义好的列表。
>>> sent4 + sent1
['Fellow', '-', 'Citizens', 'of', 'the', 'Senate', 'and', 'of', 'the',
'House', 'of', 'Representatives', ':', 'Call', 'me', 'Ishmael', '.']
>>>
如果我们想要向链表中增加一个元素该如何?这种操作叫做追加。当我们对一个列表使用append()
时,列表自身会随着操作而更新。
>>> sent1.append("Some")
>>> sent1
['Call', 'me', 'Ishmael', '.', 'Some']
>>>