# 2\. 獲得文本語料和詞匯資源
在自然語言處理的實際項目中,通常要使用大量的語言數據或者語料庫。本章的目的是要回答下列問題:
1. 什么是有用的文本語料和詞匯資源,我們如何使用 Python 獲取它們?
2. 哪些 Python 結構最適合這項工作?
3. 編寫 Python 代碼時我們如何避免重復的工作?
本章繼續通過語言處理任務的例子展示編程概念。在系統的探索每一個 Python 結構之前請耐心等待。如果你看到一個例子中含有一些不熟悉的東西,請不要擔心。只需去嘗試它,看看它做些什么——如果你很勇敢——通過使用不同的文本或詞替換代碼的某些部分來進行修改。這樣,你會將任務與編程習慣用法關聯起來,并在后續的學習中了解怎么會這樣和為什么是這樣。
## 1 獲取文本語料庫
正如剛才提到的,一個文本語料庫是一大段文本。許多語料庫的設計都要考慮一個或多個文體間謹慎的平衡。我們曾在第[1.](./ch01.html#chap-introduction)章研究過一些小的文本集合,例如美國總統就職演說。這種特殊的語料庫實際上包含了幾十個單獨的文本——每個人一個演講——但為了處理方便,我們把它們頭尾連接起來當做一個文本對待。第[1.](./ch01.html#chap-introduction)章中也使用變量預先定義好了一些文本,我們通過輸入`from nltk.book import *`來訪問它們。然而,因為我們希望能夠處理其他文本,本節中將探討各種文本語料庫。我們將看到如何選擇單個文本,以及如何處理它們。
## 1.1 古騰堡語料庫
NLTK 包含古騰堡項目(Project Gutenberg)電子文本檔案的經過挑選的一小部分文本,該項目大約有 25,000 本免費電子圖書,放在`http://www.gutenberg.org/`上。我們先要用 Python 解釋器加載 NLTK 包,然后嘗試`nltk.corpus.gutenberg.fileids()`,下面是這個語料庫中的文件標識符:
```py
>>> import nltk
>>> nltk.corpus.gutenberg.fileids()
['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', 'bible-kjv.txt',
'blake-poems.txt', 'bryant-stories.txt', 'burgess-busterbrown.txt',
'carroll-alice.txt', 'chesterton-ball.txt', 'chesterton-brown.txt',
'chesterton-thursday.txt', 'edgeworth-parents.txt', 'melville-moby_dick.txt',
'milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt',
'shakespeare-macbeth.txt', 'whitman-leaves.txt']
```
讓我們挑選這些文本的第一個——簡·奧斯丁的 _《愛瑪》_——并給它一個簡短的名稱`emma`,然后找出它包含多少個詞:
```py
>>> emma = nltk.corpus.gutenberg.words('austen-emma.txt')
>>> len(emma)
192427
```
注意
在第[1](./ch01.html#sec-computing-with-language-texts-and-words)章中,我們演示了如何使用`text1.concordance()`命令對`text1`這樣的文本進行索引。然而,這是假設你正在使用由`from nltk.book import *`導入的 9 個文本之一。現在你開始研究`nltk.corpus`中的數據,像前面的例子一樣,你必須采用以下語句對來處理索引和第[1](./ch01.html#sec-computing-with-language-texts-and-words)章中的其它任務:
```py
>>> emma = nltk.Text(nltk.corpus.gutenberg.words('austen-emma.txt'))
>>> emma.concordance("surprize")
```
在我們定義`emma`, 時,我們調用了 NLTK 中的`corpus`包中的`gutenberg`對象的`words()`函數。但因為總是要輸入這么長的名字很繁瑣,Python 提供了另一個版本的`import`語句,示例如下:
```py
>>> from nltk.corpus import gutenberg
>>> gutenberg.fileids()
['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', ...]
>>> emma = gutenberg.words('austen-emma.txt')
```
讓我們寫一個簡短的程序,通過循環遍歷前面列出的`gutenberg`文件標識符列表相應的`fileid`,然后計算統計每個文本。為了使輸出看起來緊湊,我們將使用`round()`舍入每個數字到最近似的整數。
```py
>>> for fileid in gutenberg.fileids():
... num_chars = len(gutenberg.raw(fileid)) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... num_words = len(gutenberg.words(fileid))
... num_sents = len(gutenberg.sents(fileid))
... num_vocab = len(set(w.lower() for w in gutenberg.words(fileid)))
... print(round(num_chars/num_words), round(num_words/num_sents), round(num_words/num_vocab), fileid)
...
5 25 26 austen-emma.txt
5 26 17 austen-persuasion.txt
5 28 22 austen-sense.txt
4 34 79 bible-kjv.txt
5 19 5 blake-poems.txt
4 19 14 bryant-stories.txt
4 18 12 burgess-busterbrown.txt
4 20 13 carroll-alice.txt
5 20 12 chesterton-ball.txt
5 23 11 chesterton-brown.txt
5 18 11 chesterton-thursday.txt
4 21 25 edgeworth-parents.txt
5 26 15 melville-moby_dick.txt
5 52 11 milton-paradise.txt
4 12 9 shakespeare-caesar.txt
4 12 8 shakespeare-hamlet.txt
4 12 7 shakespeare-macbeth.txt
5 36 12 whitman-leaves.txt
```
這個程序顯示每個文本的三個統計量:平均詞長、平均句子長度和本文中每個詞出現的平均次數(我們的詞匯多樣性得分)。請看,平均詞長似乎是英語的一個一般屬性,因為它的值總是`4`。(事實上,平均詞長是`3`而不是`4`,因為`num_chars`變量計數了空白字符。)相比之下,平均句子長度和詞匯多樣性看上去是作者個人的特點。
前面的例子也表明我們怎樣才能獲取“原始”文本[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#raw-access)而不用把它分割成詞符。`raw()`函數給我們沒有進行過任何語言學處理的文件的內容。因此,例如`len(gutenberg.raw('blake-poems.txt'))`告訴我們文本中出現的 _ 字符 _ 個數,包括詞之間的空格。`sents()`函數把文本劃分成句子,其中每一個句子是一個單詞列表:
```py
>>> macbeth_sentences = gutenberg.sents('shakespeare-macbeth.txt')
>>> macbeth_sentences
[['[', 'The', 'Tragedie', 'of', 'Macbeth', 'by', 'William', 'Shakespeare',
'1603', ']'], ['Actus', 'Primus', '.'], ...]
>>> macbeth_sentences[1116]
['Double', ',', 'double', ',', 'toile', 'and', 'trouble', ';',
'Fire', 'burne', ',', 'and', 'Cauldron', 'bubble']
>>> longest_len = max(len(s) for s in macbeth_sentences)
>>> [s for s in macbeth_sentences if len(s) == longest_len]
[['Doubtfull', 'it', 'stood', ',', 'As', 'two', 'spent', 'Swimmers', ',', 'that',
'doe', 'cling', 'together', ',', 'And', 'choake', 'their', 'Art', ':', 'The',
'mercilesse', 'Macdonwald', ...]]
```
注意
除了`words()`, `raw()`和`sents()`之外,大多數 NLTK 語料庫閱讀器還包括多種訪問方法。一些語料庫提供更加豐富的語言學內容,例如:詞性標注,對話標記,語法樹等;在后面的章節中,我們將看到這些。
## 1.2 網絡和聊天文本
雖然古騰堡項目包含成千上萬的書籍,它代表既定的文學。考慮較不正式的語言也是很重要的。NLTK 的網絡文本小集合的內容包括 Firefox 交流論壇,在紐約無意聽到的對話, _ 加勒比海盜 _ 的電影劇本,個人廣告和葡萄酒的評論:
```py
>>> from nltk.corpus import webtext
>>> for fileid in webtext.fileids():
... print(fileid, webtext.raw(fileid)[:65], '...')
...
firefox.txt Cookie Manager: "Don't allow sites that set removed cookies to se...
grail.txt SCENE 1: [wind] [clop clop clop] KING ARTHUR: Whoa there! [clop...
overheard.txt White guy: So, do you have any plans for this evening? Asian girl...
pirates.txt PIRATES OF THE CARRIBEAN: DEAD MAN'S CHEST, by Ted Elliott & Terr...
singles.txt 25 SEXY MALE, seeks attrac older single lady, for discreet encoun...
wine.txt Lovely delicate, fragrant Rhone wine. Polished leather and strawb...
```
還有一個即時消息聊天會話語料庫,最初由美國海軍研究生院為研究自動檢測互聯網幼童虐待癖而收集的。語料庫包含超過 10,000 張帖子,以“UserNNN”形式的通用名替換掉用戶名,手工編輯消除任何其他身份信息,制作而成。語料庫被分成 15 個文件,每個文件包含幾百個按特定日期和特定年齡的聊天室(青少年、20 歲、30 歲、40 歲、再加上一個通用的成年人聊天室)收集的帖子。文件名中包含日期、聊天室和帖子數量,例如`10-19-20s_706posts.xml`包含 2006 年 10 月 19 日從 20 多歲聊天室收集的 706 個帖子。
```py
>>> from nltk.corpus import nps_chat
>>> chatroom = nps_chat.posts('10-19-20s_706posts.xml')
>>> chatroom[123]
['i', 'do', "n't", 'want', 'hot', 'pics', 'of', 'a', 'female', ',',
'I', 'can', 'look', 'in', 'a', 'mirror', '.']
```
## 1.3 布朗語料庫
布朗語料庫是第一個百萬詞級的英語電子語料庫的,由布朗大學于 1961 年創建。這個語料庫包含 500 個不同來源的文本,按照文體分類,如:_ 新聞 _、_ 社論 _ 等。表[1.1](./ch02.html#tab-brown-sources)給出了各個文體的例子(完整列表,請參閱`http://icame.uib.no/brown/bcm-los.html`)。
表 1.1:
布朗語料庫每一部分的示例文檔
```py
>>> from nltk.corpus import brown
>>> brown.categories()
['adventure', 'belles_lettres', 'editorial', 'fiction', 'government', 'hobbies',
'humor', 'learned', 'lore', 'mystery', 'news', 'religion', 'reviews', 'romance',
'science_fiction']
>>> brown.words(categories='news')
['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', ...]
>>> brown.words(fileids=['cg22'])
['Does', 'our', 'society', 'have', 'a', 'runaway', ',', ...]
>>> brown.sents(categories=['news', 'editorial', 'reviews'])
[['The', 'Fulton', 'County'...], ['The', 'jury', 'further'...], ...]
```
布朗語料庫是一個研究文體之間的系統性差異——一種叫做文體學的語言學研究——很方便的資源。讓我們來比較不同文體中的情態動詞的用法。第一步是產生特定文體的計數。記住做下面的實驗之前要`import nltk`:
```py
>>> from nltk.corpus import brown
>>> news_text = brown.words(categories='news')
>>> fdist = nltk.FreqDist(w.lower() for w in news_text)
>>> modals = ['can', 'could', 'may', 'might', 'must', 'will']
>>> for m in modals:
... print(m + ':', fdist[m], end=' ')
...
can: 94 could: 87 may: 93 might: 38 must: 53 will: 389
```
注意
我們需要包包含`結束 = ' '` 以讓 print 函數將其輸出放在單獨的一行。
注意
**輪到你來:** 選擇布朗語料庫的不同部分,修改前面的例子,計數包含 wh 的詞,如:what, when, where, who 和 why。
下面,我們來統計每一個感興趣的文體。我們使用 NLTK 提供的帶條件的頻率分布函數。在第[2](./ch02.html#sec-conditional-frequency-distributions)節中會系統的把下面的代碼一行行拆開來講解。現在,你可以忽略細節,只看輸出。
```py
>>> cfd = nltk.ConditionalFreqDist(
... (genre, word)
... for genre in brown.categories()
... for word in brown.words(categories=genre))
>>> genres = ['news', 'religion', 'hobbies', 'science_fiction', 'romance', 'humor']
>>> modals = ['can', 'could', 'may', 'might', 'must', 'will']
>>> cfd.tabulate(conditions=genres, samples=modals)
can could may might must will
news 93 86 66 38 50 389
religion 82 59 78 12 54 71
hobbies 268 58 131 22 83 264
science_fiction 16 49 4 12 8 16
romance 74 193 11 51 45 43
humor 16 30 8 8 9 13
```
請看,新聞文體中最常見的情態動詞是 will,而言情文體中最常見的情態動詞是 could。你能預言這些嗎?這種可以區分文體的詞計數方法將在[chap-data-intensive](./ch06.html#chap-data-intensive)中再次談及。
## 1.4 路透社語料庫
路透社語料庫包含 10,788 個新聞文檔,共計 130 萬字。這些文檔分成 90 個主題,按照“訓練”和“測試”分為兩組;因此,fileid 為`'test/14826'`的文檔屬于測試組。這樣分割是為了訓練和測試算法的,這種算法自動檢測文檔的主題,我們將在[chap-data-intensive](./ch06.html#chap-data-intensive)中看到。
```py
>>> from nltk.corpus import reuters
>>> reuters.fileids()
['test/14826', 'test/14828', 'test/14829', 'test/14832', ...]
>>> reuters.categories()
['acq', 'alum', 'barley', 'bop', 'carcass', 'castor-oil', 'cocoa',
'coconut', 'coconut-oil', 'coffee', 'copper', 'copra-cake', 'corn',
'cotton', 'cotton-oil', 'cpi', 'cpu', 'crude', 'dfl', 'dlr', ...]
```
與布朗語料庫不同,路透社語料庫的類別是有互相重疊的,只是因為新聞報道往往涉及多個主題。我們可以查找由一個或多個文檔涵蓋的主題,也可以查找包含在一個或多個類別中的文檔。為方便起見,語料庫方法既接受單個的 fileid 也接受 fileids 列表作為參數。
```py
>>> reuters.categories('training/9865')
['barley', 'corn', 'grain', 'wheat']
>>> reuters.categories(['training/9865', 'training/9880'])
['barley', 'corn', 'grain', 'money-fx', 'wheat']
>>> reuters.fileids('barley')
['test/15618', 'test/15649', 'test/15676', 'test/15728', 'test/15871', ...]
>>> reuters.fileids(['barley', 'corn'])
['test/14832', 'test/14858', 'test/15033', 'test/15043', 'test/15106',
'test/15287', 'test/15341', 'test/15618', 'test/15648', 'test/15649', ...]
```
類似的,我們可以以文檔或類別為單位查找我們想要的詞或句子。這些文本中最開始的幾個詞是標題,按照慣例以大寫字母存儲。
```py
>>> reuters.words('training/9865')[:14]
['FRENCH', 'FREE', 'MARKET', 'CEREAL', 'EXPORT', 'BIDS',
'DETAILED', 'French', 'operators', 'have', 'requested', 'licences', 'to', 'export']
>>> reuters.words(['training/9865', 'training/9880'])
['FRENCH', 'FREE', 'MARKET', 'CEREAL', 'EXPORT', ...]
>>> reuters.words(categories='barley')
['FRENCH', 'FREE', 'MARKET', 'CEREAL', 'EXPORT', ...]
>>> reuters.words(categories=['barley', 'corn'])
['THAI', 'TRADE', 'DEFICIT', 'WIDENS', 'IN', 'FIRST', ...]
```
## 1.5 就職演說語料庫
在第[1](./ch01.html#sec-computing-with-language-texts-and-words)章,我們看到了就職演說語料庫,但是把它當作一個單獨的文本對待。圖[fig-inaugural](./ch01.html#fig-inaugural)中使用的“詞偏移”就像是一個坐標軸;它是語料庫中詞的索引數,從第一個演講的第一個詞開始算起。然而,語料庫實際上是 55 個文本的集合,每個文本都是一個總統的演說。這個集合的一個有趣特性是它的時間維度:
```py
>>> from nltk.corpus import inaugural
>>> inaugural.fileids()
['1789-Washington.txt', '1793-Washington.txt', '1797-Adams.txt', ...]
>>> [fileid[:4] for fileid in inaugural.fileids()]
['1789', '1793', '1797', '1801', '1805', '1809', '1813', '1817', '1821', ...]
```
請注意,每個文本的年代都出現在它的文件名中。要從文件名中獲得年代,我們使用`fileid[:4]`提取前四個字符。
讓我們來看看詞匯 America 和 citizen 隨時間推移的使用情況。下面的代碼使用`w.lower()` [![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#lowercase-startswith)將就職演說語料庫中的詞匯轉換成小寫,然后用`startswith()` [![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#lowercase-startswith)檢查它們是否以“目標”詞匯`america` 或`citizen`開始。因此,它會計算如 American's 和 Citizens 等詞。我們將在第[2](./ch02.html#sec-conditional-frequency-distributions)節學習條件頻率分布,現在只考慮輸出,如圖[1.1](./ch02.html#fig-inaugural2)所示。
```py
>>> cfd = nltk.ConditionalFreqDist(
... (target, fileid[:4])
... for fileid in inaugural.fileids()
... for w in inaugural.words(fileid)
... for target in ['america', 'citizen']
... if w.lower().startswith(target)) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
>>> cfd.plot()
```

圖 1.1:條件頻率分布圖:計數就職演說語料庫中所有以`america` 或`citizen`開始的詞;每個演講單獨計數;這樣就能觀察出隨時間變化用法上的演變趨勢;計數沒有與文檔長度進行歸一化處理。
## 1.6 標注文本語料庫
許多文本語料庫都包含語言學標注,有詞性標注、命名實體、句法結構、語義角色等。NLTK 中提供了很方便的方式來訪問這些語料庫中的幾個,還有一個包含語料庫和語料樣本的數據包,用于教學和科研的話可以免費下載。表[1.2](./ch02.html#tab-corpora)列出了其中一些語料庫。有關下載信息請參閱`http://nltk.org/data`。關于如何訪問 NLTK 語料庫的其它例子,請在`http://nltk.org/howto`查閱語料庫的 HOWTO。
表 1.2:
NLTK 中的一些語料庫和語料庫樣本:關于下載和使用它們,請參閱 NLTK 網站的信息。
```py
>>> nltk.corpus.cess_esp.words()
['El', 'grupo', 'estatal', 'Electricit\xe9_de_France', ...]
>>> nltk.corpus.floresta.words()
['Um', 'revivalismo', 'refrescante', 'O', '7_e_Meio', ...]
>>> nltk.corpus.indian.words('hindi.pos')
['?????', '????????', '????', ':', '????', '???????', ...]
>>> nltk.corpus.udhr.fileids()
['Abkhaz-Cyrillic+Abkh', 'Abkhaz-UTF8', 'Achehnese-Latin1', 'Achuar-Shiwiar-Latin1',
'Adja-UTF8', 'Afaan_Oromo_Oromiffa-Latin1', 'Afrikaans-Latin1', 'Aguaruna-Latin1',
'Akuapem_Twi-UTF8', 'Albanian_Shqip-Latin1', 'Amahuaca', 'Amahuaca-Latin1', ...]
>>> nltk.corpus.udhr.words('Javanese-Latin1')[11:]
['Saben', 'umat', 'manungsa', 'lair', 'kanthi', 'hak', ...]
```
這些語料庫的最后,`udhr`,是超過 300 種語言的世界人權宣言。這個語料庫的 fileids 包括有關文件所使用的字符編碼,如`UTF8`或者`Latin1`。讓我們用條件頻率分布來研究`udhr`語料庫中不同語言版本中的字長差異。圖[1.2](./ch02.html#fig-word-len-dist) 中所示的輸出(自己運行程序可以看到一個彩色圖)。注意,`True`和`False`是 Python 內置的布爾值。
```py
>>> from nltk.corpus import udhr
>>> languages = ['Chickasaw', 'English', 'German_Deutsch',
... 'Greenlandic_Inuktikut', 'Hungarian_Magyar', 'Ibibio_Efik']
>>> cfd = nltk.ConditionalFreqDist(
... (lang, len(word))
... for lang in languages
... for word in udhr.words(lang + '-Latin1'))
>>> cfd.plot(cumulative=True)
```

圖 1.2:累積字長分布:世界人權宣言的 6 個翻譯版本;此圖顯示,5 個或 5 個以下字母組成的詞在 Ibibio 語言的文本中占約 80%,在德語文本中占 60%,在 Inuktitut 文本中占 25%。
注意
**輪到你來:**在`udhr.fileids()`中選擇一種感興趣的語言,定義一個變量`raw_text = udhr.raw(`_Language-Latin1_`)`。使用`nltk.FreqDist(raw_text).plot()`畫出此文本的字母頻率分布圖。
不幸的是,許多語言沒有大量的語料庫。通常是政府或工業對發展語言資源的支持不夠,個人的努力是零碎的,難以發現或重用。有些語言沒有既定的書寫系統,或瀕臨滅絕。(見第[7](./ch02.html#sec-further-reading-corpora)節有關如何尋找語言資源的建議。)
## 1.8 文本語料庫的結構
到目前為止,我們已經看到了大量的語料庫結構;[1.3](./ch02.html#fig-text-corpus-structure)總結了它們。最簡單的一種沒有任何結構,僅僅是一個文本集合。通常,文本會按照其可能對應的文體、來源、作者、語言等分類。有時,這些類別會重疊,尤其是在按主題分類的情況下,因為一個文本可能與多個主題相關。偶爾的,文本集有一個時間結構,新聞集合是最常見的例子。

圖 1.3:文本語料庫的常見結構:最簡單的一種語料庫是一些孤立的沒有什么特別的組織的文本集合;一些語料庫按如文體(布朗語料庫)等分類組織結構;一些分類會重疊,如主題類別(路透社語料庫);另外一些語料庫可以表示隨時間變化語言用法的改變(就職演說語料庫)。
表 1.3:
NLTK 中定義的基本語料庫函數:使用`help(nltk.corpus.reader)`可以找到更多的文檔,也可以閱讀`http://nltk.org/howto`上的在線語料庫的 HOWTO。
```py
>>> raw = gutenberg.raw("burgess-busterbrown.txt")
>>> raw[1:20]
'The Adventures of B'
>>> words = gutenberg.words("burgess-busterbrown.txt")
>>> words[1:20]
['The', 'Adventures', 'of', 'Buster', 'Bear', 'by', 'Thornton', 'W', '.',
'Burgess', '1920', ']', 'I', 'BUSTER', 'BEAR', 'GOES', 'FISHING', 'Buster',
'Bear']
>>> sents = gutenberg.sents("burgess-busterbrown.txt")
>>> sents[1:20]
[['I'], ['BUSTER', 'BEAR', 'GOES', 'FISHING'], ['Buster', 'Bear', 'yawned', 'as',
'he', 'lay', 'on', 'his', 'comfortable', 'bed', 'of', 'leaves', 'and', 'watched',
'the', 'first', 'early', 'morning', 'sunbeams', 'creeping', 'through', ...], ...]
```
## 1.9 加載你自己的語料庫
如果你有自己收集的文本文件,并且想使用前面討論的方法訪問它們,你可以很容易地在 NLTK 中的`PlaintextCorpusReader`幫助下加載它們。檢查你的文件在文件系統中的位置;在下面的例子中,我們假定你的文件在`/usr/share/dict`目錄下。不管是什么位置,將變量`corpus_root` [![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#corpus-root-dict)的值設置為這個目錄。`PlaintextCorpusReader`初始化函數[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#corpus-reader)的第二個參數可以是一個如`['a.txt', 'test/b.txt']`這樣的 fileids 列表,或者一個匹配所有 fileids 的模式,如`'[abc]/.*\.txt'`(關于正則表達式的信息見[3.4](./ch03.html#sec-regular-expressions-word-patterns)節)。
```py
>>> from nltk.corpus import PlaintextCorpusReader
>>> corpus_root = '/usr/share/dict' ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
>>> wordlists = PlaintextCorpusReader(corpus_root, '.*') ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
>>> wordlists.fileids()
['README', 'connectives', 'propernames', 'web2', 'web2a', 'words']
>>> wordlists.words('connectives')
['the', 'of', 'and', 'to', 'a', 'in', 'that', 'is', ...]
```
舉另一個例子,假設你在本地硬盤上有自己的賓州樹庫(第 3 版)的拷貝,放在`C:\corpora`。我們可以使用`BracketParseCorpusReader`訪問這些語料。我們指定`corpus_root`為存放語料庫中解析過的《華爾街日報》部分[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#corpus-root-treebank)的位置,并指定`file_pattern`與它的子文件夾中包含的文件匹配[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#file-pattern)(用前斜杠)。
```py
>>> from nltk.corpus import BracketParseCorpusReader
>>> corpus_root = r"C:\corpora\penntreebank\parsed\mrg\wsj" ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
>>> file_pattern = r".*/wsj_.*\.mrg" ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
>>> ptb = BracketParseCorpusReader(corpus_root, file_pattern)
>>> ptb.fileids()
['00/wsj_0001.mrg', '00/wsj_0002.mrg', '00/wsj_0003.mrg', '00/wsj_0004.mrg', ...]
>>> len(ptb.sents())
49208
>>> ptb.sents(fileids='20/wsj_2013.mrg')[19]
['The', '55-year-old', 'Mr.', 'Noriega', 'is', "n't", 'as', 'smooth', 'as', 'the',
'shah', 'of', 'Iran', ',', 'as', 'well-born', 'as', 'Nicaragua', "'s", 'Anastasio',
'Somoza', ',', 'as', 'imperial', 'as', 'Ferdinand', 'Marcos', 'of', 'the', 'Philippines',
'or', 'as', 'bloody', 'as', 'Haiti', "'s", 'Baby', Doc', 'Duvalier', '.']
```
## 2 條件頻率分布
我們在第[3](./ch01.html#sec-computing-with-language-simple-statistics)節介紹了頻率分布。我們看到給定某個詞匯或其他元素的列表`mylist`,`FreqDist(mylist)`會計算列表中每個元素項目出現的次數。在這里,我們將推廣這一想法。
當語料文本被分為幾類,如文體、主題、作者等時,我們可以計算每個類別獨立的頻率分布。這將允許我們研究類別之間的系統性差異。在上一節中,我們是用 NLTK 的`ConditionalFreqDist`數據類型實現的。條件頻率分布是頻率分布的集合,每個頻率分布有一個不同的“條件”。這個條件通常是文本的類別。[2.1](./ch02.html#fig-tally2)描繪了一個帶兩個條件的條件頻率分布的片段,一個是新聞文本,一個是言情文本。

圖 2.1:計數文本集合中單詞出現次數(條件頻率分布)
## 2.1 條件和事件
頻率分布計算觀察到的事件,如文本中出現的詞匯。條件頻率分布需要給每個事件關聯一個條件。所以不是處理一個單詞詞序列[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#seq-words),我們必須處理的是一個配對序列[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#seq-pairs):
```py
>>> text = ['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', ...] ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
>>> pairs = [('news', 'The'), ('news', 'Fulton'), ('news', 'County'), ...] ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
```
每個配對的形式是:`(條件, 事件)`。如果我們按文體處理整個布朗語料庫,將有 15 個條件(每個文體一個條件)和 1,161,192 個事件(每一個詞一個事件)。
## 2.2 按文體計數詞匯
在[1](./ch02.html#sec-extracting-text-from-corpora)中,我們看到一個條件頻率分布,其中條件為布朗語料庫的每一節,并對每節計數詞匯。`FreqDist()`以一個簡單的列表作為輸入,`ConditionalFreqDist()` 以一個配對列表作為輸入。
```py
>>> from nltk.corpus import brown
>>> cfd = nltk.ConditionalFreqDist(
... (genre, word)
... for genre in brown.categories()
... for word in brown.words(categories=genre))
```
讓我們拆開來看,只看兩個文體,新聞和言情。對于每個文體[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#each-genre),我們遍歷文體中的每個詞[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch02.html#each-word),以產生文體與詞的配對[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#genre-word-pairs) :
```py
>>> genre_word = [(genre, word) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... for genre in ['news', 'romance'] ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
... for word in brown.words(categories=genre)] ![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)
>>> len(genre_word)
170576
```
因此,在下面的代碼中我們可以看到,列表`genre_word`的前幾個配對將是 (`'news'`, _word_) [![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#start-genre)的形式,而最后幾個配對將是 (`'romance'`, _word_) [![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#end-genre)的形式。
```py
>>> genre_word[:4]
[('news', 'The'), ('news', 'Fulton'), ('news', 'County'), ('news', 'Grand')] # [_start-genre]
>>> genre_word[-4:]
[('romance', 'afraid'), ('romance', 'not'), ('romance', "''"), ('romance', '.')] # [_end-genre]
```
現在,我們可以使用此配對列表創建一個`ConditionalFreqDist`,并將它保存在一個變量`cfd`中。像往常一樣,我們可以輸入變量的名稱來檢查它[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#inspect-cfd),并確認它有兩個條件[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#conditions-cfd):
```py
>>> cfd = nltk.ConditionalFreqDist(genre_word)
>>> cfd ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
<ConditionalFreqDist with 2 conditions>
>>> cfd.conditions()
['news', 'romance'] # [_conditions-cfd]
```
讓我們訪問這兩個條件,它們每一個都只是一個頻率分布:
```py
>>> print(cfd['news'])
<FreqDist with 14394 samples and 100554 outcomes>
>>> print(cfd['romance'])
<FreqDist with 8452 samples and 70022 outcomes>
>>> cfd['romance'].most_common(20)
[(',', 3899), ('.', 3736), ('the', 2758), ('and', 1776), ('to', 1502),
('a', 1335), ('of', 1186), ('``', 1045), ("''", 1044), ('was', 993),
('I', 951), ('in', 875), ('he', 702), ('had', 692), ('?', 690),
('her', 651), ('that', 583), ('it', 573), ('his', 559), ('she', 496)]
>>> cfd['romance']['could']
193
```
## 2.3 繪制分布圖和分布表
除了組合兩個或兩個以上的頻率分布和更容易初始化之外,`ConditionalFreqDist`還為制表和繪圖提供了一些有用的方法。
[1.1](./ch02.html#fig-inaugural2)是基于下面的代碼產生的一個條件頻率分布繪制的。條件是詞 america 或 citizen[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#america-citizen),被繪圖的計數是在特定演講中出現的詞的次數。它利用了每個演講的文件名——例如`1865-Lincoln.txt` ——的前 4 個字符包含年代的事實[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#first-four-chars)。這段代碼為文件`1865-Lincoln.txt`中每個小寫形式以 america 開頭的詞——如 Americans——產生一個配對`('america', '1865')`。
```py
>>> from nltk.corpus import inaugural
>>> cfd = nltk.ConditionalFreqDist(
... (target, fileid[:4]) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... for fileid in inaugural.fileids()
... for w in inaugural.words(fileid)
... for target in ['america', 'citizen'] ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
... if w.lower().startswith(target))
```
圖[1.2](./ch02.html#fig-word-len-dist)也是基于下面的代碼產生的一個條件頻率分布繪制的。這次的條件是語言的名稱,圖中的計數來源于詞長[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#lang-len-word)。它利用了每一種語言的文件名是語言名稱后面跟`'-Latin1'`(字符編碼)的事實。
```py
>>> from nltk.corpus import udhr
>>> languages = ['Chickasaw', 'English', 'German_Deutsch',
... 'Greenlandic_Inuktikut', 'Hungarian_Magyar', 'Ibibio_Efik']
>>> cfd = nltk.ConditionalFreqDist(
... (lang, len(word)) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... for lang in languages
... for word in udhr.words(lang + '-Latin1'))
```
在`plot()`和`tabulate()`方法中,我們可以使用`conditions=`來選擇指定哪些條件顯示。如果我們忽略它,所有條件都會顯示。同樣,我們可以使用`samples=`parameter 來限制要顯示的樣本。這使得載入大量數據到一個條件頻率分布,然后通過選定條件和樣品,繪圖或制表的探索成為可能。這也使我們能全面控制條件和樣本的顯示順序。例如:我們可以為兩種語言和長度少于 10 個字符的詞匯繪制累計頻率數據表,如下所示。我們解釋一下上排最后一個單元格中數值的含義是英文文本中 9 個或少于 9 個字符長的詞有 1,638 個。
```py
>>> cfd.tabulate(conditions=['English', 'German_Deutsch'],
... samples=range(10), cumulative=True)
0 1 2 3 4 5 6 7 8 9
English 0 185 525 883 997 1166 1283 1440 1558 1638
German_Deutsch 0 171 263 614 717 894 1013 1110 1213 1275
```
注意
**輪到你來:** 處理布朗語料庫的新聞和言情文體,找出一周中最有新聞價值并且是最浪漫的日子。定義一個變量`days`,包含星期的列表,如`['Monday', ...]`。然后使用`cfd.tabulate(samples=days)`為這些詞的計數制表。接下來用`plot`替代`tabulate`嘗試同樣的事情。你可以在額外的參數`samples=['Monday', ...]`的幫助下控制星期輸出的順序。
你可能已經注意到:我們已經在使用的條件頻率分布看上去像列表推導,但是不帶方括號。通常,我們使用列表推導作為一個函數的參數,如`set([w.lower() for w in t])`,忽略掉方括號而只寫`set(w.lower() for w in t)`是允許的。(更多的講解請參見[4.2](./ch04.html#sec-sequences)節“生成器表達式”的討論。)
## 2.4 使用雙連詞生成隨機文本
我們可以使用條件頻率分布創建一個雙連詞表(詞對)。(我們在[3](./ch01.html#sec-computing-with-language-simple-statistics)中介紹過。)`bigrams()`函數接受一個單詞列表,并建立一個連續的詞對列表。記住,為了能看到結果而不是神秘的"生成器對象",我們需要使用`list()`函數︰
```py
>>> sent = ['In', 'the', 'beginning', 'God', 'created', 'the', 'heaven',
... 'and', 'the', 'earth', '.']
>>> list(nltk.bigrams(sent))
[('In', 'the'), ('the', 'beginning'), ('beginning', 'God'), ('God', 'created'),
('created', 'the'), ('the', 'heaven'), ('heaven', 'and'), ('and', 'the'),
('the', 'earth'), ('earth', '.')]
```
在[2.2](./ch02.html#code-random-text)中,我們把每個詞作為一個條件,對每個詞我們有效的創建它的后續詞的頻率分布。函數`generate_model()`包含一個簡單的循環來生成文本。當我們調用這個函數時,我們選擇一個詞(如`'living'`)作為我們的初始內容,然后進入循環,我們輸入變量`word`的當前值,重新設置`word`為上下文中最可能的詞符(使用`max()`);下一次進入循環,我們使用那個詞作為新的初始內容。正如你通過檢查輸出可以看到的,這種簡單的文本生成方法往往會在循環中卡住;另一種方法是從可用的詞匯中隨機選擇下一個詞。
```py
def generate_model(cfdist, word, num=15):
for i in range(num):
print(word, end=' ')
word = cfdist[word].max()
text = nltk.corpus.genesis.words('english-kjv.txt')
bigrams = nltk.bigrams(text)
cfd = nltk.ConditionalFreqDist(bigrams) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
```
條件頻率分布是一個對許多 NLP 任務都有用的數據結構。[2.1](./ch02.html#tab-conditionalfreqdist)總結了它們常用的方法。
表 2.1:
NLTK 中的條件頻率分布:定義、訪問和可視化一個計數的條件頻率分布的常用方法和習慣用法。
```py
print('Monty Python')
```
你也可以輸入`from monty import *`,它將做同樣的事情。
從現在起,你可以選擇使用交互式解釋器或文本編輯器來創建你的程序。使用解釋器測試你的想法往往比較方便,修改一行代碼直到達到你期望的效果。測試好之后,你就可以將代碼粘貼到文本編輯器(去除所有`>>>` 和`...`提示符),繼續擴展它。給文件一個小而準確的名字,使用所有的小寫字母,用下劃線分割詞匯,使用`.py`文件名后綴,例如`monty_python.py`。
注意
**要點:** 我們的內聯代碼的例子包含`>>>`和`...`提示符,好像我們正在直接與解釋器交互。隨著程序變得更加復雜,你應該在編輯器中輸入它們,沒有提示符,如前面所示的那樣在編輯器中運行它們。當我們在這本書中提供更長的程序時,我們將不使用提示符以提醒你在文件中輸入它而不是使用解釋器。你可以看到[2.2](./ch02.html#code-random-text)已經這樣了。請注意,這個例子還包括兩行代碼帶有 Python 提示符;它是任務的互動部分,在這里你觀察一些數據,并調用一個函數。請記住,像[2.2](./ch02.html#code-random-text)這樣的所有示例代碼都可以從`http://nltk.org/`下載。
## 3.2 函數
假設你正在分析一些文本,這些文本包含同一個詞的不同形式,你的一部分程序需要將給定的單數名詞變成復數形式。假設需要在兩個地方做這樣的事,一個是處理一些文本,另一個是處理用戶的輸入。
比起重復相同的代碼好幾次,把這些事情放在一個函數中會更有效和可靠。一個函數是命名的代碼塊,執行一些明確的任務,就像我們在[1](./ch01.html#sec-computing-with-language-texts-and-words)中所看到的那樣。一個函數通常被定義來使用一些稱為參數的變量接受一些輸入,并且它可能會產生一些結果,也稱為返回值。我們使用關鍵字`def`加函數名以及所有輸入參數來定義一個函數,接下來是函數的主體。這里是我們在[1](./ch01.html#sec-computing-with-language-texts-and-words)看到的函數(對于 Python 2,請包含`import`語句,這樣可以使除法像我們期望的那樣運算):
```py
>>> from __future__ import division
>>> def lexical_diversity(text):
... return len(text) / len(set(text))
```
我們使用關鍵字`return`表示函數作為輸出而產生的值。在這個例子中,函數所有的工作都在`return`語句中完成。下面是一個等價的定義,使用多行代碼做同樣的事。我們將把參數名稱從`text`變為`my_text_data`,注意這只是一個任意的選擇:
```py
>>> def lexical_diversity(my_text_data):
... word_count = len(my_text_data)
... vocab_size = len(set(my_text_data))
... diversity_score = vocab_size / word_count
... return diversity_score
```
請注意,我們已經在函數體內部創造了一些新的變量。這些是局部變量,不能在函數體外訪問。現在我們已經定義一個名為`lexical_diversity`的函數。但只定義它不會產生任何輸出!函數在被“調用”之前不會做任何事情:
```py
>>> from nltk.corpus import genesis
>>> kjv = genesis.words('english-kjv.txt')
>>> lexical_diversity(kjv)
0.06230453042623537
```
讓我們回到前面的場景,實際定義一個簡單的函數來處理英文的復數詞。[3.1](./ch02.html#code-plural)中的函數`plural()`接受單數名詞,產生一個復數形式,雖然它并不總是正確的。(我們將在[4.4](./ch04.html#sec-functions)中以更長的篇幅討論這個函數。)
```py
def plural(word):
if word.endswith('y'):
return word[:-1] + 'ies'
elif word[-1] in 'sx' or word[-2:] in ['sh', 'ch']:
return word + 'es'
elif word.endswith('an'):
return word[:-2] + 'en'
else:
return word + 's'
```
`endswith()`函數總是與一個字符串對象一起使用(如[3.1](./ch02.html#code-plural)中的`word`)。要調用此函數,我們使用對象的名字,一個點,然后跟函數的名稱。這些函數通常被稱為方法。
## 3.3 模塊
隨著時間的推移,你將會發現你創建了大量小而有用的文字處理函數,結果你不停的把它們從老程序復制到新程序中。哪個文件中包含的才是你要使用的函數的最新版本?如果你能把你的勞動成果收集在一個單獨的地方,而且訪問以前定義的函數不必復制,生活將會更加輕松。
要做到這一點,請將你的函數保存到一個文件`text_proc.py`。現在,你可以簡單的通過從文件導入它來訪問你的函數:
```py
>>> from text_proc import plural
>>> plural('wish')
wishes
>>> plural('fan')
fen
```
顯然,我們的復數函數明顯存在錯誤,因為 fan 的復數是 fans。不必再重新輸入這個函數的新版本,我們可以簡單的編輯現有的。因此,在任何時候我們的復數函數只有一個版本,不會再有使用哪個版本的困擾。
在一個文件中的變量和函數定義的集合被稱為一個 Python 模塊。相關模塊的集合稱為一個包。處理布朗語料庫的 NLTK 代碼是一個模塊,處理各種不同的語料庫的代碼的集合是一個包。NLTK 的本身是包的集合,有時被稱為一個庫。
小心!
如果你正在創建一個包含一些你自己的 Python 代碼的文件,一定 _ 不 _ 要將文件命名為`nltk.py`:這可能會在導入時占據“真正的”NLTK 包。當 Python 導入模塊時,它先查找當前目錄(文件夾)。
## 4 詞匯資源
詞典或者詞典資源是一個詞和/或短語以及一些相關信息的集合,例如:詞性和詞意定義等相關信息。詞典資源附屬于文本,通常在文本的幫助下創建和豐富。例如:如果我們定義了一個文本`my_text`,然后`vocab = sorted(set(my_text))`建立`my_text`的詞匯,同時`word_freq = FreqDist(my_text)`計數文本中每個詞的頻率。`vocab`和`word_freq`都是簡單的詞匯資源。同樣,如我們在[1](./ch01.html#sec-computing-with-language-texts-and-words)中看到的,詞匯索引為我們提供了有關詞語用法的信息,可能在編寫詞典時有用。[4.1](./ch02.html#fig-lexicon)中描述了詞匯相關的標準術語。一個詞項包括詞目(也叫詞條)以及其他附加信息,例如詞性和詞意定義。兩個不同的詞拼寫相同被稱為同音異義詞。

圖 4.1:詞典術語:兩個拼寫相同的詞條(同音異義詞)的詞匯項,包括詞性和注釋信息。
最簡單的詞典是除了一個詞匯列表外什么也沒有。復雜的詞典資源包括在詞匯項內和跨詞匯項的復雜的結構。在本節,我們來看看 NLTK 中的一些詞典資源。
## 4.1 詞匯列表語料庫
NLTK 包括一些僅僅包含詞匯列表的語料庫。詞匯語料庫是 Unix 中的`/usr/share/dict/words`文件,被一些拼寫檢查程序使用。我們可以用它來尋找文本語料中不尋常的或拼寫錯誤的詞匯,如[4.2](./ch02.html#code-unusual)所示。
```py
def unusual_words(text):
text_vocab = set(w.lower() for w in text if w.isalpha())
english_vocab = set(w.lower() for w in nltk.corpus.words.words())
unusual = text_vocab - english_vocab
return sorted(unusual)
>>> unusual_words(nltk.corpus.gutenberg.words('austen-sense.txt'))
['abbeyland', 'abhorred', 'abilities', 'abounded', 'abridgement', 'abused', 'abuses',
'accents', 'accepting', 'accommodations', 'accompanied', 'accounted', 'accounts',
'accustomary', 'aches', 'acknowledging', 'acknowledgment', 'acknowledgments', ...]
>>> unusual_words(nltk.corpus.nps_chat.words())
['aaaaaaaaaaaaaaaaa', 'aaahhhh', 'abortions', 'abou', 'abourted', 'abs', 'ack',
'acros', 'actualy', 'adams', 'adds', 'adduser', 'adjusts', 'adoted', 'adreniline',
'ads', 'adults', 'afe', 'affairs', 'affari', 'affects', 'afk', 'agaibn', 'ages', ...]
```
還有一個停用詞語料庫,就是那些高頻詞匯,如 the,to 和 also,我們有時在進一步的處理之前想要將它們從文檔中過濾。停用詞通常幾乎沒有什么詞匯內容,而它們的出現會使區分文本變困難。
```py
>>> from nltk.corpus import stopwords
>>> stopwords.words('english')
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours',
'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers',
'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves',
'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are',
'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does',
'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until',
'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into',
'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down',
'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here',
'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more',
'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so',
'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now']
```
讓我們定義一個函數來計算文本中 _ 沒有 _ 在停用詞列表中的詞的比例:
```py
>>> def content_fraction(text):
... stopwords = nltk.corpus.stopwords.words('english')
... content = [w for w in text if w.lower() not in stopwords]
... return len(content) / len(text)
...
>>> content_fraction(nltk.corpus.reuters.words())
0.7364374824583169
```
因此,在停用詞的幫助下,我們篩選掉文本中四分之一的詞。請注意,我們在這里結合了兩種不同類型的語料庫,使用詞典資源來過濾文本語料的內容。

圖 4.3:一個字母拼詞謎題::在由隨機選擇的字母組成的網格中,選擇里面的字母組成詞;這個謎題叫做“目標”。
一個詞匯列表對解決如圖[4.3](./ch02.html#fig-target)中這樣的詞的謎題很有用。我們的程序遍歷每一個詞,對于每一個詞檢查是否符合條件。檢查必須出現的字母[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#obligatory-letter)和長度限制[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#length-constraint)是很容易的(這里我們只查找 6 個或 6 個以上字母的詞)。只使用指定的字母組合作為候選方案,尤其是一些指定的字母出現了兩次(這里如字母 v)這樣的檢查是很棘手的。`FreqDist`比較法[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch02.html#freqdist-compare)允許我們檢查每個 _ 字母 _ 在候選詞中的頻率是否小于或等于相應的字母在拼詞謎題中的頻率。
```py
>>> puzzle_letters = nltk.FreqDist('egivrvonl')
>>> obligatory = 'r'
>>> wordlist = nltk.corpus.words.words()
>>> [w for w in wordlist if len(w) >= 6 ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... and obligatory in w ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
... and nltk.FreqDist(w) <= puzzle_letters] ![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)
['glover', 'gorlin', 'govern', 'grovel', 'ignore', 'involver', 'lienor',
'linger', 'longer', 'lovering', 'noiler', 'overling', 'region', 'renvoi',
'revolving', 'ringle', 'roving', 'violer', 'virole']
```
另一個詞匯列表是名字語料庫,包括 8000 個按性別分類的名字。男性和女性的名字存儲在單獨的文件中。讓我們找出同時出現在兩個文件中的名字,即性別曖昧的名字:
```py
>>> names = nltk.corpus.names
>>> names.fileids()
['female.txt', 'male.txt']
>>> male_names = names.words('male.txt')
>>> female_names = names.words('female.txt')
>>> [w for w in male_names if w in female_names]
['Abbey', 'Abbie', 'Abby', 'Addie', 'Adrian', 'Adrien', 'Ajay', 'Alex', 'Alexis',
'Alfie', 'Ali', 'Alix', 'Allie', 'Allyn', 'Andie', 'Andrea', 'Andy', 'Angel',
'Angie', 'Ariel', 'Ashley', 'Aubrey', 'Augustine', 'Austin', 'Averil', ...]
```
正如大家都知道的,以字母 a 結尾的名字幾乎都是女性。我們可以在[4.4](./ch02.html#fig-cfd-gender)中看到這一點以及一些其它的模式,該圖是由下面的代碼產生的。請記住`name[-1]`是`name`的最后一個字母。
```py
>>> cfd = nltk.ConditionalFreqDist(
... (fileid, name[-1])
... for fileid in names.fileids()
... for name in names.words(fileid))
>>> cfd.plot()
```

圖 4.4:條件頻率分布:此圖顯示男性和女性名字的結尾字母;大多數以 a,e 或 i 結尾的名字是女性;以 h 和 l 結尾的男性和女性同樣多;以 k, o, r, s 和 t 結尾的更可能是男性。
## 4.2 發音的詞典
一個稍微豐富的詞典資源是一個表格(或電子表格),在每一行中含有一個詞加一些性質。NLTK 中包括美國英語的 CMU 發音詞典,它是為語音合成器使用而設計的。
```py
>>> entries = nltk.corpus.cmudict.entries()
>>> len(entries)
133737
>>> for entry in entries[42371:42379]:
... print(entry)
...
('fir', ['F', 'ER1'])
('fire', ['F', 'AY1', 'ER0'])
('fire', ['F', 'AY1', 'R'])
('firearm', ['F', 'AY1', 'ER0', 'AA2', 'R', 'M'])
('firearm', ['F', 'AY1', 'R', 'AA2', 'R', 'M'])
('firearms', ['F', 'AY1', 'ER0', 'AA2', 'R', 'M', 'Z'])
('firearms', ['F', 'AY1', 'R', 'AA2', 'R', 'M', 'Z'])
('fireball', ['F', 'AY1', 'ER0', 'B', 'AO2', 'L'])
```
對每一個詞,這個詞典資源提供語音的代碼——不同的聲音不同的標簽——叫做 phones。請看 fire 有兩個發音(美國英語中):單音節`F AY1 R`和雙音節`F AY1 ER0`。CMU 發音詞典中的符號是從 _Arpabet_ 來的,更多的細節請參考`http://en.wikipedia.org/wiki/Arpabet`。
每個條目由兩部分組成,我們可以用一個復雜的`for`語句來一個一個的處理這些。我們沒有寫`for entry in entries:`,而是用 _ 兩個 _ 變量名`word, pron`替換`entry`[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#word-pron)。現在,每次通過循環時,`word`被分配條目的第一部分,`pron`被分配條目的第二部分:
```py
>>> for word, pron in entries: ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... if len(pron) == 3: ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
... ph1, ph2, ph3 = pron ![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)
... if ph1 == 'P' and ph3 == 'T':
... print(word, ph2, end=' ')
...
pait EY1 pat AE1 pate EY1 patt AE1 peart ER1 peat IY1 peet IY1 peete IY1 pert ER1
pet EH1 pete IY1 pett EH1 piet IY1 piette IY1 pit IH1 pitt IH1 pot AA1 pote OW1
pott AA1 pout AW1 puett UW1 purt ER1 put UH1 putt AH1
```
上面的程序掃描詞典中那些發音包含三個音素的條目[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#len-pron-three)。如果條件為真,就將`pron`的內容分配給三個新的變量:`ph1`, `ph2`和`ph3`。請注意實現這個功能的語句的形式并不多見[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch02.html#tuple-assignment)。
這里是同樣的`for`語句的另一個例子,這次使用內部的列表推導。這段程序找到所有發音結尾與 nicks 相似的詞匯。你可以使用此方法來找到押韻的詞。
```py
>>> syllable = ['N', 'IH0', 'K', 'S']
>>> [word for word, pron in entries if pron[-4:] == syllable]
["atlantic's", 'audiotronics', 'avionics', 'beatniks', 'calisthenics', 'centronics',
'chamonix', 'chetniks', "clinic's", 'clinics', 'conics', 'conics', 'cryogenics',
'cynics', 'diasonics', "dominic's", 'ebonics', 'electronics', "electronics'", ...]
```
請注意,有幾種方法來拼讀一個讀音:nics, niks, nix 甚至 ntic's 加一個無聲的 t,如詞 atlantic's。讓我們來看看其他一些發音與書寫之間的不匹配。你可以總結一下下面的例子的功能,并解釋它們是如何實現的?
```py
>>> [w for w, pron in entries if pron[-1] == 'M' and w[-1] == 'n']
['autumn', 'column', 'condemn', 'damn', 'goddamn', 'hymn', 'solemn']
>>> sorted(set(w[:2] for w, pron in entries if pron[0] == 'N' and w[0] != 'n'))
['gn', 'kn', 'mn', 'pn']
```
音素包含數字表示主重音(`1`),次重音(`2`)和無重音(`0`)。作為我們最后的一個例子,我們定義一個函數來提取重音數字,然后掃描我們的詞典,找到具有特定重音模式的詞匯。
```py
>>> def stress(pron):
... return [char for phone in pron for char in phone if char.isdigit()]
>>> [w for w, pron in entries if stress(pron) == ['0', '1', '0', '2', '0']]
['abbreviated', 'abbreviated', 'abbreviating', 'accelerated', 'accelerating',
'accelerator', 'accelerators', 'accentuated', 'accentuating', 'accommodated',
'accommodating', 'accommodative', 'accumulated', 'accumulating', 'accumulative', ...]
>>> [w for w, pron in entries if stress(pron) == ['0', '2', '0', '1', '0']]
['abbreviation', 'abbreviations', 'abomination', 'abortifacient', 'abortifacients',
'academicians', 'accommodation', 'accommodations', 'accreditation', 'accreditations',
'accumulation', 'accumulations', 'acetylcholine', 'acetylcholine', 'adjudication', ...]
```
注意
這段程序的精妙之處在于:我們的用戶自定義函數`stress()`調用一個內含條件的列表推導。還有一個雙層嵌套`for`循環。這里有些復雜,等你有了更多的使用列表推導的經驗后,你可能會想回過來重新閱讀。
我們可以使用條件頻率分布來幫助我們找到詞匯的最小受限集合。在這里,我們找到所有 p 開頭的三音素詞[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#p3-words),并按照它們的第一個和最后一個音素來分組[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#group-first-last)。
```py
>>> p3 = [(pron[0]+'-'+pron[2], word) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... for (word, pron) in entries
... if pron[0] == 'P' and len(pron) == 3] ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
>>> cfd = nltk.ConditionalFreqDist(p3)
>>> for template in sorted(cfd.conditions()):
... if len(cfd[template]) > 10:
... words = sorted(cfd[template])
... wordstring = ' '.join(words)
... print(template, wordstring[:70] + "...")
...
P-CH patch pautsch peach perch petsch petsche piche piech pietsch pitch pit...
P-K pac pack paek paik pak pake paque peak peake pech peck peek perc perk ...
P-L pahl pail paille pal pale pall paul paule paull peal peale pearl pearl...
P-N paign pain paine pan pane pawn payne peine pen penh penn pin pine pinn...
P-P paap paape pap pape papp paup peep pep pip pipe pipp poop pop pope pop...
P-R paar pair par pare parr pear peer pier poor poore por pore porr pour...
P-S pace pass pasts peace pearse pease perce pers perse pesce piece piss p...
P-T pait pat pate patt peart peat peet peete pert pet pete pett piet piett...
P-UW1 peru peugh pew plew plue prew pru prue prugh pshew pugh...
```
我們可以通過查找特定詞匯來訪問詞典,而不必遍歷整個詞典。我們將使用 Python 的詞典數據結構,在[3](./ch05.html#sec-dictionaries)節我們將系統的學習它。通過指定詞典的名字后面跟一個包含在方括號里的關鍵字(例如詞`'fire'`)來查詞典[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#dict-key)。
```py
>>> prondict = nltk.corpus.cmudict.dict()
>>> prondict['fire'] ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
[['F', 'AY1', 'ER0'], ['F', 'AY1', 'R']]
>>> prondict['blog'] ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'blog'
>>> prondict['blog'] = [['B', 'L', 'AA1', 'G']] ![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)
>>> prondict['blog']
[['B', 'L', 'AA1', 'G']]
```
如果我們試圖查找一個不存在的關鍵字[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#dict-key-error),就會得到一個`KeyError`。這與我們使用一個過大的整數索引一個列表時產生一個`IndexError`是類似的。詞 blog 在發音詞典中沒有,所以我們對我們自己版本的詞典稍作調整,為這個關鍵字分配一個值[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch02.html#dict-assign)(這對 NLTK 語料庫是沒有影響的;下一次我們訪問它,blog 依然是空的)。
我們可以用任何詞典資源來處理文本,如過濾掉具有某些詞典屬性的詞(如名詞),或者映射文本中每一個詞。例如,下面的文本到發音函數在發音詞典中查找文本中每個詞:
```py
>>> text = ['natural', 'language', 'processing']
>>> [ph for w in text for ph in prondict[w][0]]
['N', 'AE1', 'CH', 'ER0', 'AH0', 'L', 'L', 'AE1', 'NG', 'G', 'W', 'AH0', 'JH',
'P', 'R', 'AA1', 'S', 'EH0', 'S', 'IH0', 'NG']
```
## 4.3 比較詞表
表格詞典的另一個例子是比較詞表。NLTK 中包含了所謂的斯瓦迪士核心詞列表,幾種語言中約 200 個常用詞的列表。語言標識符使用 ISO639 雙字母碼。
```py
>>> from nltk.corpus import swadesh
>>> swadesh.fileids()
['be', 'bg', 'bs', 'ca', 'cs', 'cu', 'de', 'en', 'es', 'fr', 'hr', 'it', 'la', 'mk',
'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sw', 'uk']
>>> swadesh.words('en')
['I', 'you (singular), thou', 'he', 'we', 'you (plural)', 'they', 'this', 'that',
'here', 'there', 'who', 'what', 'where', 'when', 'how', 'not', 'all', 'many', 'some',
'few', 'other', 'one', 'two', 'three', 'four', 'five', 'big', 'long', 'wide', ...]
```
我們可以通過在`entries()` 方法中指定一個語言列表來訪問多語言中的同源詞。更進一步,我們可以把它轉換成一個簡單的詞典(我們將在[3](./ch05.html#sec-dictionaries)學到`dict()`函數)。
```py
>>> fr2en = swadesh.entries(['fr', 'en'])
>>> fr2en
[('je', 'I'), ('tu, vous', 'you (singular), thou'), ('il', 'he'), ...]
>>> translate = dict(fr2en)
>>> translate['chien']
'dog'
>>> translate['jeter']
'throw'
```
通過添加其他源語言,我們可以讓我們這個簡單的翻譯器更為有用。讓我們使用`dict()`函數把德語-英語和西班牙語-英語對相互轉換成一個詞典,然后用這些添加的映射 _ 更新 _ 我們原來的`翻譯`詞典:
```py
>>> de2en = swadesh.entries(['de', 'en']) # German-English
>>> es2en = swadesh.entries(['es', 'en']) # Spanish-English
>>> translate.update(dict(de2en))
>>> translate.update(dict(es2en))
>>> translate['Hund']
'dog'
>>> translate['perro']
'dog'
```
我們可以比較日爾曼語族和拉丁語族的不同:
```py
>>> languages = ['en', 'de', 'nl', 'es', 'fr', 'pt', 'la']
>>> for i in [139, 140, 141, 142]:
... print(swadesh.entries(languages)[i])
...
('say', 'sagen', 'zeggen', 'decir', 'dire', 'dizer', 'dicere')
('sing', 'singen', 'zingen', 'cantar', 'chanter', 'cantar', 'canere')
('play', 'spielen', 'spelen', 'jugar', 'jouer', 'jogar, brincar', 'ludere')
('float', 'schweben', 'zweven', 'flotar', 'flotter', 'flutuar, boiar', 'fluctuare')
```
## 4.4 詞匯工具:Shoebox 和 Toolbox
可能最流行的語言學家用來管理數據的工具是 _Toolbox_,以前叫做 _Shoebox_,因為它用滿滿的檔案卡片占據了語言學家的舊鞋盒。Toolbox 可以免費從`http://www.sil.org/computing/toolbox/`下載。
一個 Toolbox 文件由一個大量條目的集合組成,其中每個條目由一個或多個字段組成。大多數字段都是可選的或重復的,這意味著這個詞匯資源不能作為一個表格或電子表格來處理。
下面是一個羅托卡特語的詞典。我們只看第一個條目,詞 kaa 的意思是"to gag":
```py
>>> from nltk.corpus import toolbox
>>> toolbox.entries('rotokas.dic')
[('kaa', [('ps', 'V'), ('pt', 'A'), ('ge', 'gag'), ('tkp', 'nek i pas'),
('dcsv', 'true'), ('vx', '1'), ('sc', '???'), ('dt', '29/Oct/2005'),
('ex', 'Apoka ira kaaroi aioa-ia reoreopaoro.'),
('xp', 'Kaikai i pas long nek bilong Apoka bikos em i kaikai na toktok.'),
('xe', 'Apoka is gagging from food while talking.')]), ...]
```
條目包括一系列的屬性-值對,如`('ps', 'V')`表示詞性是`'V'`(動詞),`('ge', 'gag')`表示英文注釋是'`'gag'`。最后的 3 個配對包含一個羅托卡特語例句和它的巴布亞皮欽語及英語翻譯。
Toolbox 文件松散的結構使我們在現階段很難更好的利用它。XML 提供了一種強有力的方式來處理這種語料庫,我們將在[11.](./ch11.html#chap-data)回到這個的主題。
注意
羅托卡特語是巴布亞新幾內亞的布干維爾島上使用的一種語言。這個詞典資源由 Stuart Robinson 貢獻給 NLTK。羅托卡特語以僅有 12 個音素(彼此對立的聲音)而聞名。詳情請參考:`http://en.wikipedia.org/wiki/Rotokas_language`
## 5 WordNet
WordNet 是面向語義的英語詞典,類似與傳統辭典,但具有更豐富的結構。NLTK 中包括英語 WordNet,共有 155,287 個詞和 117,659 個同義詞集合。我們將以尋找同義詞和它們在 WordNet 中如何訪問開始。
## 5.1 意義與同義詞
考慮[(1a)](./ch02.html#ex-car1)中的句子。如果我們用 automobile 替換掉[(1a)](./ch02.html#ex-car1)中的詞 motorcar,變成[(1b)](./ch02.html#ex-car2),句子的意思幾乎保持不變:
```py
>>> from nltk.corpus import wordnet as wn
>>> wn.synsets('motorcar')
[Synset('car.n.01')]
```
因此,motorcar 只有一個可能的含義,它被定義為`car.n.01`,car 的第一個名詞意義。`car.n.01`被稱為 synset 或“同義詞集”,意義相同的詞(或“詞條”)的集合:
```py
>>> wn.synset('car.n.01').lemma_names()
['car', 'auto', 'automobile', 'machine', 'motorcar']
```
同義詞集中的每個詞可以有多種含義,例如:car 也可能是火車車廂、一個貨車或電梯廂。但我們只對這個同義詞集中所有詞來說最常用的一個意義感興趣。同義詞集也有一些一般的定義和例句:
```py
>>> wn.synset('car.n.01').definition()
'a motor vehicle with four wheels; usually propelled by an internal combustion engine'
>>> wn.synset('car.n.01').examples()
['he needs a car to get to work']
```
雖然定義幫助人們了解一個同義詞集的本意,同義詞集中的詞往往對我們的程序更有用。為了消除歧義,我們將這些詞標記為`car.n.01.automobile`,`car.n.01.motorcar`等。這種同義詞集和詞的配對叫做詞條。我們可以得到指定同義詞集的所有詞條[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch02.html#get-lemmas),查找特定的詞條[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch02.html#lookup-lemma),得到一個詞條對應的同義詞集[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch02.html#get-synset),也可以得到一個詞條的“名字”[![[4]](https://img.kancloud.cn/cc/20/cc20c265de5e95a94eb351ef368f3277_15x15.jpg)](./ch02.html#get-name):
```py
>>> wn.synset('car.n.01').lemmas() ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
[Lemma('car.n.01.car'), Lemma('car.n.01.auto'), Lemma('car.n.01.automobile'),
Lemma('car.n.01.machine'), Lemma('car.n.01.motorcar')]
>>> wn.lemma('car.n.01.automobile') ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
Lemma('car.n.01.automobile')
>>> wn.lemma('car.n.01.automobile').synset() ![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)
Synset('car.n.01')
>>> wn.lemma('car.n.01.automobile').name() ![[4]](https://img.kancloud.cn/cc/20/cc20c265de5e95a94eb351ef368f3277_15x15.jpg)
'automobile'
```
與詞 motorcar 意義明確且只有一個同義詞集不同,詞 car 是含糊的,有五個同義詞集:
```py
>>> wn.synsets('car')
[Synset('car.n.01'), Synset('car.n.02'), Synset('car.n.03'), Synset('car.n.04'),
Synset('cable_car.n.01')]
>>> for synset in wn.synsets('car'):
... print(synset.lemma_names())
...
['car', 'auto', 'automobile', 'machine', 'motorcar']
['car', 'railcar', 'railway_car', 'railroad_car']
['car', 'gondola']
['car', 'elevator_car']
['cable_car', 'car']
```
為方便起見,我們可以用下面的方式訪問所有包含詞 car 的詞條。
```py
>>> wn.lemmas('car')
[Lemma('car.n.01.car'), Lemma('car.n.02.car'), Lemma('car.n.03.car'),
Lemma('car.n.04.car'), Lemma('cable_car.n.01.car')]
```
注意
**輪到你來:**寫下詞 dish 的你能想到的所有意思。現在,在 WordNet 的幫助下使用前面所示的相同的操作探索這個詞。
## 5.2 WordNet 的層次結構
WordNet 的同義詞集對應于抽象的概念,它們并不總是有對應的英語詞匯。這些概念在層次結構中相互聯系在一起。一些概念也很一般,如 _ 實體 _、_ 狀態 _、_ 事件 _;這些被稱為唯一前綴或者根同義詞集。其他的,如 _ 油老虎 _ 和 _ 有倉門式后背的汽車 _ 等就比較具體的多。[5.1](./ch02.html#fig-wn-hierarchy)展示了一個概念層次的一小部分。

圖 5.1:WordNet 概念層次片段:每個節點對應一個同義詞集;邊表示上位詞/下位詞關系,即上級概念與從屬概念的關系。
WordNet 使在概念之間漫游變的容易。例如:一個如 _motorcar_ 這樣的概念,我們可以看到它的更加具體(直接)的概念——下位詞。
```py
>>> motorcar = wn.synset('car.n.01')
>>> types_of_motorcar = motorcar.hyponyms()
>>> types_of_motorcar[0]
Synset('ambulance.n.01')
>>> sorted(lemma.name() for synset in types_of_motorcar for lemma in synset.lemmas())
['Model_T', 'S.U.V.', 'SUV', 'Stanley_Steamer', 'ambulance', 'beach_waggon',
'beach_wagon', 'bus', 'cab', 'compact', 'compact_car', 'convertible',
'coupe', 'cruiser', 'electric', 'electric_automobile', 'electric_car',
'estate_car', 'gas_guzzler', 'hack', 'hardtop', 'hatchback', 'heap',
'horseless_carriage', 'hot-rod', 'hot_rod', 'jalopy', 'jeep', 'landrover',
'limo', 'limousine', 'loaner', 'minicar', 'minivan', 'pace_car', 'patrol_car',
'phaeton', 'police_car', 'police_cruiser', 'prowl_car', 'race_car', 'racer',
'racing_car', 'roadster', 'runabout', 'saloon', 'secondhand_car', 'sedan',
'sport_car', 'sport_utility', 'sport_utility_vehicle', 'sports_car', 'squad_car',
'station_waggon', 'station_wagon', 'stock_car', 'subcompact', 'subcompact_car',
'taxi', 'taxicab', 'tourer', 'touring_car', 'two-seater', 'used-car', 'waggon',
'wagon']
```
我們也可以通過訪問上位詞來瀏覽層次結構。有些詞有多條路徑,因為它們可以歸類在一個以上的分類中。`car.n.01`與`entity.n.01`間有兩條路徑,因為`wheeled_vehicle.n.01`可以同時被歸類為車輛和容器。
```py
>>> motorcar.hypernyms()
[Synset('motor_vehicle.n.01')]
>>> paths = motorcar.hypernym_paths()
>>> len(paths)
2
>>> [synset.name() for synset in paths[0]]
['entity.n.01', 'physical_entity.n.01', 'object.n.01', 'whole.n.02', 'artifact.n.01',
'instrumentality.n.03', 'container.n.01', 'wheeled_vehicle.n.01',
'self-propelled_vehicle.n.01', 'motor_vehicle.n.01', 'car.n.01']
>>> [synset.name() for synset in paths[1]]
['entity.n.01', 'physical_entity.n.01', 'object.n.01', 'whole.n.02', 'artifact.n.01',
'instrumentality.n.03', 'conveyance.n.03', 'vehicle.n.01', 'wheeled_vehicle.n.01',
'self-propelled_vehicle.n.01', 'motor_vehicle.n.01', 'car.n.01']
```
我們可以用如下方式得到一個最一般的上位(或根上位)同義詞集:
```py
>>> motorcar.root_hypernyms()
[Synset('entity.n.01')]
```
注意
**輪到你來:** 嘗試 NLTK 中便捷的圖形化 WordNet 瀏覽器:`nltk.app.wordnet()`。沿著上位詞與下位詞之間的鏈接,探索 WordNet 的層次結構。
## 5.3 更多的詞匯關系
上位詞和下位詞被稱為詞匯關系,因為它們是同義集之間的關系。這個關系定位上下為“是一個”層次。WordNet 網絡另一個重要的漫游方式是從元素到它們的部件(部分)或到它們被包含其中的東西(整體)。例如,一棵樹的部分是它的樹干,樹冠等;這些都是`part_meronyms()`。一棵樹的 _ 實質 _ 是包括心材和邊材組成的,即`substance_meronyms()`。樹木的集合形成了一個森林,即`member_holonyms()`:
```py
>>> wn.synset('tree.n.01').part_meronyms()
[Synset('burl.n.02'), Synset('crown.n.07'), Synset('limb.n.02'),
Synset('stump.n.01'), Synset('trunk.n.01')]
>>> wn.synset('tree.n.01').substance_meronyms()
[Synset('heartwood.n.01'), Synset('sapwood.n.01')]
>>> wn.synset('tree.n.01').member_holonyms()
[Synset('forest.n.01')]
```
來看看可以獲得多么復雜的東西,考慮具有幾個密切相關意思的詞 mint。我們可以看到`mint.n.04`是`mint.n.02`的一部分,是組成`mint.n.05`的材質。
```py
>>> for synset in wn.synsets('mint', wn.NOUN):
... print(synset.name() + ':', synset.definition())
...
batch.n.02: (often followed by `of') a large number or amount or extent
mint.n.02: any north temperate plant of the genus Mentha with aromatic leaves and
small mauve flowers
mint.n.03: any member of the mint family of plants
mint.n.04: the leaves of a mint plant used fresh or candied
mint.n.05: a candy that is flavored with a mint oil
mint.n.06: a plant where money is coined by authority of the government
>>> wn.synset('mint.n.04').part_holonyms()
[Synset('mint.n.02')]
>>> wn.synset('mint.n.04').substance_holonyms()
[Synset('mint.n.05')]
```
動詞之間也有關系。例如,走路的動作包括抬腳的動作,所以走路蘊涵著抬腳。一些動詞有多個蘊涵:
```py
>>> wn.synset('walk.v.01').entailments()
[Synset('step.v.01')]
>>> wn.synset('eat.v.01').entailments()
[Synset('chew.v.01'), Synset('swallow.v.01')]
>>> wn.synset('tease.v.03').entailments()
[Synset('arouse.v.07'), Synset('disappoint.v.01')]
```
詞條之間的一些詞匯關系,如反義詞:
```py
>>> wn.lemma('supply.n.02.supply').antonyms()
[Lemma('demand.n.02.demand')]
>>> wn.lemma('rush.v.01.rush').antonyms()
[Lemma('linger.v.04.linger')]
>>> wn.lemma('horizontal.a.01.horizontal').antonyms()
[Lemma('inclined.a.02.inclined'), Lemma('vertical.a.01.vertical')]
>>> wn.lemma('staccato.r.01.staccato').antonyms()
[Lemma('legato.r.01.legato')]
```
你可以使用`dir()`查看詞匯關系和同義詞集上定義的其它方法,例如`dir(wn.synset('harmony.n.02'))`。
## 5.4 語義相似度
我們已經看到同義詞集之間構成復雜的詞匯關系網絡。給定一個同義詞集,我們可以遍歷 WordNet 網絡來查找相關含義的同義詞集。知道哪些詞是語義相關的,對索引文本集合非常有用,當搜索一個一般性的用語例如車輛時,就可以匹配包含具體用語例如豪華轎車的文檔。
回想一下每個同義詞集都有一個或多個上位詞路徑連接到一個根上位詞,如`entity.n.01`。連接到同一個根的兩個同義詞集可能有一些共同的上位詞(見圖[5.1](./ch02.html#fig-wn-hierarchy))。如果兩個同義詞集共用一個非常具體的上位詞——在上位詞層次結構中處于較低層的上位詞——它們一定有密切的聯系。
```py
>>> right = wn.synset('right_whale.n.01')
>>> orca = wn.synset('orca.n.01')
>>> minke = wn.synset('minke_whale.n.01')
>>> tortoise = wn.synset('tortoise.n.01')
>>> novel = wn.synset('novel.n.01')
>>> right.lowest_common_hypernyms(minke)
[Synset('baleen_whale.n.01')]
>>> right.lowest_common_hypernyms(orca)
[Synset('whale.n.02')]
>>> right.lowest_common_hypernyms(tortoise)
[Synset('vertebrate.n.01')]
>>> right.lowest_common_hypernyms(novel)
[Synset('entity.n.01')]
```
當然,我們知道,鯨魚是非常具體的(須鯨更是如此),脊椎動物是更一般的,而實體完全是抽象的一般的。我們可以通過查找每個同義詞集深度量化這個一般性的概念:
```py
>>> wn.synset('baleen_whale.n.01').min_depth()
14
>>> wn.synset('whale.n.02').min_depth()
13
>>> wn.synset('vertebrate.n.01').min_depth()
8
>>> wn.synset('entity.n.01').min_depth()
0
```
WordNet 同義詞集的集合上定義的相似度能夠包括上面的概念。例如,`path_similarity`是基于上位詞層次結構中相互連接的概念之間的最短路徑在`0`-`1`范圍的打分(兩者之間沒有路徑就返回`-1`)。同義詞集與自身比較將返回`1`。考慮以下的相似度:露脊鯨與小須鯨、逆戟鯨、烏龜以及小說。數字本身的意義并不大,當我們從海洋生物的語義空間轉移到非生物時它是減少的。
```py
>>> right.path_similarity(minke)
0.25
>>> right.path_similarity(orca)
0.16666666666666666
>>> right.path_similarity(tortoise)
0.07692307692307693
>>> right.path_similarity(novel)
0.043478260869565216
```
注意
還有一些其它的相似性度量方法;你可以輸入`help(wn)`獲得更多信息。NLTK 還包括 VerbNet,一個連接到 WordNet 的動詞的層次結構的詞典。It can be accessed with `nltk.corpus.verbnet`.
## 6 小結
* 文本語料庫是一個大型結構化文本的集合。NLTK 包含了許多語料庫,如布朗語料庫`nltk.corpus.brown`。
* 有些文本語料庫是分類的,例如通過文體或者主題分類;有時候語料庫的分類會相互重疊。
* 條件頻率分布是一個頻率分布的集合,每個分布都有一個不同的條件。它們可以用于通過給定內容或者文體對詞的頻率計數。
* 行數較多的 Python 程序應該使用文本編輯器來輸入,保存為`.py`后綴的文件,并使用`import`語句來訪問。
* Python 函數允許你將一段特定的代碼塊與一個名字聯系起來,然后重用這些代碼想用多少次就用多少次。
* 一些被稱為“方法”的函數與一個對象聯系在起來,我們使用對象名稱跟一個點然后跟方法名稱來調用它,就像:`x.funct(y)`或者`word.isalpha()`。
* 要想找到一些關于某個變量`v`的信息,可以在 Pyhon 交互式解釋器中輸入`help(v)`來閱讀這一類對象的幫助條目。
* WordNet 是一個面向語義的英語詞典,由同義詞的集合——或稱為同義詞集——組成,并且組織成一個網絡。
* 默認情況下有些函數是不能使用的,必須使用 Python 的`import`語句來訪問。
## 7 深入閱讀
本章的附加材料發布在`http://nltk.org/`,包括網絡上免費提供的資源的鏈接。語料庫方法總結請參閱`http://nltk.org/howto`上的語料庫 HOWTO,在線 API 文檔中也有更廣泛的資料。
公開發行的語料庫的重要來源是語言數據聯盟((LDC)和歐洲語言資源局(ELRA)。提供幾十種語言的數以百計的已標注文本和語音語料庫。非商業許可證允許這些數據用于教學和科研目的。其中一些語料庫也提供商業許可(但需要較高的費用)。
用于創建標注的文本語料庫的好工具叫做 Brat,可從`http://brat.nlplab.org/`訪問。
這些語料庫和許多其他語言資源使用 OLAC 元數據格式存檔,可以通過 `http://www.language-archives.org/`上的 OLAC 主頁搜索到。Corpora List 是一個討論語料庫內容的郵件列表,你可以通過搜索列表檔案來找到資源或發布資源到列表中。_Ethnologue_ 是最完整的世界上的語言的清單,`http://www.ethnologue.com/`。7000 種語言中只有幾十中有大量適合 NLP 使用的數字資源。
本章觸及語料庫語言學領域。在這一領域的其他有用的書籍包括[(Biber, Conrad, & Reppen, 1998)](./bibliography.html#biber1998), [(McEnery, 2006)](./bibliography.html#mcenery2006), [(Meyer, 2002)](./bibliography.html#meyer2002), [(Sampson & McCarthy, 2005)](./bibliography.html#sampson2005), [(Scott & Tribble, 2006)](./bibliography.html#scott2006)。在語言學中海量數據分析的深入閱讀材料有:[(Baayen, 2008)](./bibliography.html#baayen2008), [(Gries, 2009)](./bibliography.html#gries2009), [(Woods, Fletcher, & Hughes, 1986)](./bibliography.html#woods1986)。
WordNet 原始描述是[(Fellbaum, 1998)](./bibliography.html#fellbaum1998)。雖然 WordNet 最初是為心理語言學研究開發的,它目前在自然語言處理和信息檢索領域被廣泛使用。WordNets 正在開發許多其他語言的版本,在`http://www.globalwordnet.org/`中有記錄。學習 WordNet 相似性度量可以閱讀[(Budanitsky & Hirst, 2006)](./bibliography.html#budanitsky2006ewb)。
本章觸及的其它主題是語音和詞匯語義學,讀者可以參考[(Jurafsky & Martin, 2008)](./bibliography.html#jurafskymartin2008)的第 7 和第 20 章。
## 8 練習
1. ? 創建一個變量`phrase`包含一個詞的列表。實驗本章描述的操作,包括加法、乘法、索引、切片和排序。
2. ? 使用語料庫模塊處理`austen-persuasion.txt`。這本書中有多少詞符?多少詞型?
3. ? 使用布朗語料庫閱讀器`nltk.corpus.brown.words()`或網絡文本語料庫閱讀器`nltk.corpus.webtext.words()`來訪問兩個不同文體的一些樣例文本。
4. ? 使用`state_union`語料庫閱讀器,訪問 _《國情咨文報告》_ 的文本。計數每個文檔中出現的`men`、`women`和`people`。隨時間的推移這些詞的用法有什么變化?
5. ? 考查一些名詞的整體部分關系。請記住,有 3 種整體部分關系,所以你需要使用:`member_meronyms()`, `part_meronyms()`, `substance_meronyms()`, `member_holonyms()`, `part_holonyms()`和`substance_holonyms()`。
6. ? 在比較詞表的討論中,我們創建了一個對象叫做`translate`,通過它你可以使用德語和意大利語詞匯查找對應的英語詞匯。這種方法可能會出現什么問題?你能提出一個辦法來避免這個問題嗎?
7. ? 根據 Strunk 和 White 的 _《Elements of Style》_,詞 however 在句子開頭使用是“in whatever way”或“to whatever extent”的意思,而沒有“nevertheless”的意思。他們給出了正確用法的例子:However you advise him, he will probably do as he thinks best.(`http://www.bartleby.com/141/strunk3.html`) 使用詞匯索引工具在我們一直在思考的各種文本中研究這個詞的實際用法。也可以看 _LanguageLog_ 發布在`http://itre.cis.upenn.edu/~myl/languagelog/archives/001913.html`上的“Fossilized prejudices abou‘t however’”。
8. ? 在名字語料庫上定義一個條件頻率分布,顯示哪個 _ 首 _ 字母在男性名字中比在女性名字中更常用(參見[4.4](./ch02.html#fig-cfd-gender))。
9. ? 挑選兩個文本,研究它們之間在詞匯、詞匯豐富性、文體等方面的差異。你能找出幾個在這兩個文本中詞意相當不同的詞嗎,例如在 _《白鯨記》_ 與 _《理智與情感》_ 中的 monstrous?
10. ? 閱讀 BBC 新聞文章:_UK's Vicky Pollards 'left behind'_ `http://news.bbc.co.uk/1/hi/education/6173441.stm`。文章給出了有關青少年語言的以下統計:“使用最多的 20 個詞,包括 yeah, no, but 和 like,占所有詞的大約三分之一”。對于大量文本源來說,所有詞標識符的三分之一有多少詞類型?你從這個統計中得出什么結論?更多相關信息請閱讀`http://itre.cis.upenn.edu/~myl/languagelog/archives/003993.html`上的 _LanguageLog_。
11. ? 調查模式分布表,尋找其他模式。試著用你自己對不同文體的印象理解來解釋它們。你能找到其他封閉的詞匯歸類,展現不同文體的顯著差異嗎?
12. ? CMU 發音詞典包含某些詞的多個發音。它包含多少種不同的詞?具有多個可能的發音的詞在這個詞典中的比例是多少?
13. ? 沒有下位詞的名詞同義詞集所占的百分比是多少?你可以使用`wn.all_synsets('n')`得到所有名詞同義詞集。
14. ? 定義函數`supergloss(s)`,使用一個同義詞集`s`作為它的參數,返回一個字符串,包含`s`的定義和`s`所有的上位詞與下位詞的定義的連接字符串。
15. ? 寫一個程序,找出所有在布朗語料庫中出現至少 3 次的詞。
16. ? 寫一個程序,生成一個詞匯多樣性得分表(例如詞符/詞型的比例),如我們在[1.1](./ch01.html#tab-brown-types)所看到的。包括布朗語料庫文體的全集 (`nltk.corpus.brown.categories()`)。哪個文體詞匯多樣性最低(每個類型的標識符數最多)?這是你所期望的嗎?
17. ? 寫一個函數,找出一個文本中最常出現的 50 個詞,停用詞除外。
18. ? 寫一個程序,輸出一個文本中 50 個最常見的雙連詞(相鄰詞對),忽略包含停用詞的雙連詞。
19. ? 寫一個程序,按文體創建一個詞頻表,以[1](./ch02.html#sec-extracting-text-from-corpora)節給出的詞頻表為范例。選擇你自己的詞匯,并嘗試找出那些在一個文體中很突出或很缺乏的詞匯。討論你的發現。
20. ? 寫一個函數`word_freq()`,用一個詞和布朗語料庫中的一個部分的名字作為參數,計算這部分語料中詞的頻率。
21. ? 寫一個程序,估算一個文本中的音節數,利用 CMU 發音詞典。
22. ? 定義一個函數`hedge(text)`,處理一個文本和產生一個新的版本在每三個詞之間插入一個詞`'like'`。
23. ★ **齊夫定律**:_f(w)_ 是一個自由文本中的詞 _w_ 的頻率。假設一個文本中的所有詞都按照它們的頻率排名,頻率最高的在最前面。齊夫定律指出一個詞類型的頻率與它的排名成反比(即 _f_ × _r = k_,_k_ 是某個常數)。例如:最常見的第 50 個詞類型出現的頻率應該是最常見的第 150 個詞型出現頻率的 3 倍。
1. 寫一個函數來處理一個大文本,使用`pylab.plot`畫出相對于詞的排名的詞的頻率。你認可齊夫定律嗎?(提示:使用對數刻度會有幫助。)所繪的線的極端情況是怎樣的?
2. 隨機生成文本,如使用`random.choice("abcdefg ")`,注意要包括空格字符。你需要事先`import random`。使用字符串連接操作將字符累積成一個很長的字符串。然后為這個字符串分詞,產生前面的齊夫圖,比較這兩個圖。此時你怎么看齊夫定律?
24. ★ 修改例[2.2](./ch02.html#code-random-text)的文本生成程序,進一步完成下列任務:
1. 在一個列表`words`中存儲 _n_ 個最相似的詞,使用`random.choice()`從列表中隨機選取一個詞。(你將需要事先`import random`。)
2. 選擇特定的文體,如布朗語料庫中的一部分或者《創世紀》翻譯或者古騰堡語料庫中的文本或者一個網絡文本。在此語料上訓練一個模型,產生隨機文本。你可能要實驗不同的起始字。文本的可理解性如何?討論這種方法產生隨機文本的長處和短處。
3. 現在使用兩種不同文體訓練你的系統,使用混合文體文本做實驗。討論你的觀察結果。
25. ★ 定義一個函數`find_language()`,用一個字符串作為其參數,返回包含這個字符串作為詞匯的語言的列表。使用《世界人權宣言》`udhr`的語料,將你的搜索限制在 Latin-1 編碼的文件中。
26. ★ 名詞上位詞層次的分枝因素是什么?也就是說,對于每一個具有下位詞——上位詞層次中的子女——的名詞同義詞集,它們平均有幾個下位詞?你可以使用`wn.all_synsets('n')`獲得所有名詞同義詞集。
27. ★ 一個詞的多義性是它所有含義的個數。利用 WordNet,使用`len(wn.synsets('dog', 'n'))`我們可以判斷名詞 _dog_ 有 7 種含義。計算 WordNet 中名詞、動詞、形容詞和副詞的平均多義性。
28. ★使用預定義的相似性度量之一給下面的每個詞對的相似性打分。按相似性減少的順序排名。你的排名與這里給出的順序有多接近?[(Miller & Charles, 1998)](./bibliography.html#millercharles1998)實驗得出的順序: car-automobile, gem-jewel, journey-voyage, boy-lad, coast-shore, asylum-madhouse, magician-wizard, midday-noon, furnace-stove, food-fruit, bird-cock, bird-crane, tool-implement, brother-monk, lad-brother, crane-implement, journey-car, monk-oracle, cemetery-woodland, food-rooster, coast-hill, forest-graveyard, shore-woodland, monk-slave, coast-forest, lad-wizard, chord-smile, glass-magician, rooster-voyage, noon-string。
關于本文檔...
針對 NLTK 3.0 作出更新。本章來自于 _Natural Language Processing with Python_,[Steven Bird](http://estive.net/), [Ewan Klein](http://homepages.inf.ed.ac.uk/ewan/) 和[Edward Loper](http://ed.loper.org/),Copyright ? 2014 作者所有。本章依據 _Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License_ [[http://creativecommons.org/licenses/by-nc-nd/3.0/us/](http://creativecommons.org/licenses/by-nc-nd/3.0/us/)] 條款,與 _ 自然語言工具包 _ [`http://nltk.org/`] 3.0 版一起發行。
本文檔構建于星期三 2015 年 7 月 1 日 12:30:05 AEST