# 11\. 語言學數據管理
已標注的語言數據的結構化集合在 NLP 的大部分領域都是至關重要的,但是,我們使用它們仍然面臨著許多障礙。本章的目的是要回答下列問題:
1. 我們如何設計一種新的語言資源,并確保它的覆蓋面、平衡以及支持廣泛用途的文檔?
2. 現有數據對某些分析工具格式不兼容,我們如何才能將其轉換成合適的格式?
3. 有什么好的方法來記錄我們已經創建的資源的存在,讓其他人可以很容易地找到它?
一路上,我們將研究當前語料庫的設計、創建一個語料庫的典型工作流程,及語料庫的生命周期。與在其他章節中一樣,會有語言數據管理實際實驗的很多例子,包括在語言學現場教學課程、實驗室的工作和網絡爬取中收集的數據。
## 1 語料庫結構:一個案例研究
TIMIT 語料庫是第一個廣泛發布的已標注語音數據庫,它有一個特別清晰的組織結構。TIMIT 由一個包括克薩斯儀器公司和麻省理工學院的財團開發,它也由此得名。它被設計用來為聲學-語音知識的獲取提供數據,并支持自動語音識別系統的開發和評估。
## 1.1 TIMIT 的結構
與布朗語料庫顯示文章風格和來源的平衡選集一樣,TIMIT 包括方言、說話者和材料的平衡選集。對 8 個方言區中的每一種方言,具有一定年齡范圍和教育背景的 50 個男性和女性的說話者每人讀 10 個精心挑選的句子。設計中有兩句話是所有說話者都讀的,帶來方言的變化:
```py
>>> phonetic = nltk.corpus.timit.phones('dr1-fvmh0/sa1')
>>> phonetic
['h#', 'sh', 'iy', 'hv', 'ae', 'dcl', 'y', 'ix', 'dcl', 'd', 'aa', 'kcl',
's', 'ux', 'tcl', 'en', 'gcl', 'g', 'r', 'iy', 's', 'iy', 'w', 'aa',
'sh', 'epi', 'w', 'aa', 'dx', 'ax', 'q', 'ao', 'l', 'y', 'ih', 'ax', 'h#']
>>> nltk.corpus.timit.word_times('dr1-fvmh0/sa1')
[('she', 7812, 10610), ('had', 10610, 14496), ('your', 14496, 15791),
('dark', 15791, 20720), ('suit', 20720, 25647), ('in', 25647, 26906),
('greasy', 26906, 32668), ('wash', 32668, 37890), ('water', 38531, 42417),
('all', 43091, 46052), ('year', 46052, 50522)]
```
除了這種文本數據,TIMIT 還包括一個詞典,提供每一個詞的可與一個特定的話語比較的規范的發音:
```py
>>> timitdict = nltk.corpus.timit.transcription_dict()
>>> timitdict['greasy'] + timitdict['wash'] + timitdict['water']
['g', 'r', 'iy1', 's', 'iy', 'w', 'ao1', 'sh', 'w', 'ao1', 't', 'axr']
>>> phonetic[17:30]
['g', 'r', 'iy', 's', 'iy', 'w', 'aa', 'sh', 'epi', 'w', 'aa', 'dx', 'ax']
```
這給了我們一點印象:語音處理系統在處理或識別這種特殊的方言(新英格蘭)的語音中必須做什么。最后,TIMIT 包括說話人的人口學統計,允許細粒度的研究聲音、社會和性別特征。
```py
>>> nltk.corpus.timit.spkrinfo('dr1-fvmh0')
SpeakerInfo(id='VMH0', sex='F', dr='1', use='TRN', recdate='03/11/86',
birthdate='01/08/60', ht='5\'05"', race='WHT', edu='BS',
comments='BEST NEW ENGLAND ACCENT SO FAR')
```
## 1.2 主要設計特點
TIMIT 演示了語料庫設計中的幾個主要特點。首先,語料庫包含語音和字形兩個標注層。一般情況下,文字或語音語料庫可能在多個不同的語言學層次標注,包括形態、句法和段落層次。此外,即使在給定的層次仍然有不同的標注策略,甚至標注者之間也會有分歧,因此我們要表示多個版本。TIMIT 的第二個特點是:它在多個維度的變化與方言地區和二元音覆蓋范圍之間取得平衡。人口學統計的加入帶來了許多更獨立的變量,這可能有助于解釋數據中的變化,便于以后出于在建立語料庫時沒有想到的目的使用語料庫,例如社會語言學。第三個特點是:將原始語言學事件作為錄音來捕捉和作為標注來捕捉之間有明顯的區分。兩者一致表示文本語料庫正確,原始文本通常有被認為是不可改變的作品的外部來源。那個作品的任何包含人的判斷的轉換——即使如分詞一樣簡單——也是后來的修訂版,因此以盡可能接近原始的形式保留源材料是十分重要的。

圖 1.2:發布的 TIMIT 語料庫的結構:CD-ROM 包含文檔、頂層的訓練和測試目錄;訓練和測試目錄都有 8 子目錄,每個方言區一個;這些目錄又包含更多子目錄,每個說話者一個;列出的目錄是女性說話者`aks0`的目錄的內容,顯示 10 個`wav`文件配以一個錄音文本文件、一個錄音文本詞對齊文件和一個音標文件。
TIMIT 的第四個特點是語料庫的層次結構。每個句子 4 個文件,500 個說話者每人 10 個句子,有 20,000 個文件。這些被組織成一個樹狀結構,示意圖如[1.2](./ch11.html#fig-timit-structure)所示。在頂層分成訓練集和測試集,用于開發和評估統計模型。
最后,請注意雖然 TIMIT 是語音語料庫,它的錄音文本和相關數據只是文本,可以使用程序處理了,就像任何其他的文本語料庫那樣。因此,許多在這本書中所描述的計算方法都適用。此外,注意 TIMIT 語料庫包含的所有數據類型分為詞匯和文字兩個基本類別,我們將在下面討論。說話者人口學統計數據只不過是詞匯數據類型的另一個實例。
當我們考慮到文字和記錄結構是計算機科學中關注數據管理的兩個子領域首要內容,即全文檢索領域和數據庫領域,這最后的觀察就不太令人驚訝了。語言數據管理的一個顯著特點是往往將這兩種數據類型放在一起,可以利用這兩個領域的成果和技術。
## 1.3 基本數據類型

圖 1.3:基本語言數據類型——詞匯和文本:它們的多樣性中,詞匯具有記錄結構,而已標注文本具有時間組織。
不考慮它的復雜性,TIMIT 語料庫只包含兩種基本數據類型,詞典和文本。正如我們在[2.](./ch02.html#chap-corpora)中所看到的,大多數詞典資源都可以使用記錄結構表示,即一個關鍵字加一個或多個字段,如[1.3](./ch11.html#fig-datatypes)所示。詞典資源可能是一個傳統字典或比較詞表,如下所示。它也可以是一個短語詞典,其中的關鍵字是一個短語而不是一個詞。詞典還包括記錄結構化的數據,我們可以通過對應主題的非關鍵字字段來查找條目。我們也可以構造特殊的表格(稱為范例)來進行對比和說明系統性的變化,[1.3](./ch11.html#fig-datatypes)顯示了三個動詞。TIMIT 的說話者表也是一種詞典資源。
在最抽象的層面上,文本是一個真實的或虛構的講話事件的表示,該事件的時間過程也在文本本身存在。一個文本可以是一個小單位,如一個詞或句子,也可以是一個完整的敘述或對話。它可能會有標注如詞性標記、形態分析、話語結構等。正如我們在 IOB 標注([7.](./ch07.html#chap-chunk))中所看到的可以使用單個詞的標記表示更高層次的成分。因此,[1.3](./ch11.html#fig-datatypes)所示的文本的抽象就足夠了。
不考慮單獨的語料庫的復雜性和特質,最基本的,它們是帶有記錄結構化數據的文本集合。語料庫的內容往往偏重于這些類型中的一種或多種。例如:布朗語料庫包含 500 個文本文件,但我們仍然可以使用表將這些文件與 15 種不同風格關聯。在事情的另一面,WordNet 包含 117659 個同義詞集記錄,也包含許多例子句子(小文本)來說明詞的用法。TIMIT 處在中間,含有大量的獨立的文本和詞匯類型的材料。
## 2 語料庫生命周期
語料庫并不是從天而降的,需要精心的準備和許多人長時期的輸入。原始數據需要進行收集、清理、記錄并以系統化的結構存儲。標注可分為各種層次,一些需要語言的形態或句法的專門知識。要在這個階段成功取決于建立一個高效的工作流程,包括適當的工具和格式轉換器。質量控制程序可以將尋找標注中的不一致落實到位,確保盡最大可能在標注者之間達成一致。由于任務的規模和復雜性,大型語料庫可能需要幾年的準備,包括幾十或上百人多年的努力。在本節中,我們簡要地回顧語料庫生命周期的各個階段。
## 2.1 語料庫創建的三種方案
語料庫的一種類型是設計在創作者的探索過程中逐步展現。這是典型的傳統“領域語言學”模式,即來自會話的材料在它被收集的時候就被分析,明天的想法往往基于今天的分析中產生的問題。。在隨后幾年的研究中產生的語料不斷被使用,并可能用作不確定的檔案資源。計算機化明顯有利于這種類型的工作,以廣受歡迎的程序 Shoebox 為例,它作為 Toolbox 重新發布,現在已有超過二十年的歷史(見[4](./ch02.html#sec-lexical-resources))。其他的軟件工具,甚至是簡單的文字處理器和電子表格,通常也可用于采集數據。在下一節,我們將著眼于如何從這些來源提取數據。
另一種語料庫創建方案是典型的實驗研究,其中一些精心設計的材料被從一定范圍的人類受試者中收集,然后進行分析來評估一個假設或開發一種技術。此類數據庫在實驗室或公司內被共享和重用已很常見,經常被更廣泛的發布。這種類型的語料庫是“共同任務”的科研管理方法的基礎,這在過去的二十年已成為政府資助的語言技術研究項目。在前面的章節中,我們已經遇到很多這樣的語料庫;我們將看到如何編寫 Python 程序實踐這些語料庫發布前必要的一些任務。
最后,還有努力為一個特定的語言收集“參考語料”,如 _ 美國國家語料庫 _(ANC)和 _ 英國國家語料庫 _(BNC)。這里的目標已經成為產生各種形式、風格和語言的使用的一個全面的記錄。除了規模龐大的挑戰,還嚴重依賴自動標注工具和后期編輯共同修復錯誤。然而,我們可以編寫程序來查找和修復錯誤,還可以分析語料庫是否平衡。
## 2.2 質量控制
自動和手動的數據準備的好的工具是必不可少的。然而,一個高質量的語料庫的建立很大程度取決于文檔、培訓和工作流程等平凡的東西。標注指南確定任務并記錄標記約定。它們可能會定期更新以覆蓋不同的情況,同時制定實現更一致的標注的新規則。在此過程中標注者需要接受訓練,包括指南中沒有的情況的解決方法。需要建立工作流程,盡可能與支持軟件一起,跟蹤哪些文件已被初始化、標注、驗證、手動檢查等等。可能有多層標注,由不同的專家提供。不確定或不一致的情況可能需要裁決。
大的標注任務需要多個標注者,由此產生一致性的問題。一組標注者如何能一致的處理呢?我們可以通過將一部分獨立的原始材料由兩個人分別標注,很容易地測量標注的一致性。這可以揭示指南中或標注任務的不同功能的不足。在對質量要求較高的情況下,整個語料庫可以標注兩次,由專家裁決不一致的地方。
報告標注者之間對語料庫達成的一致性被認為是最佳實踐(如通過兩次標注 10%的語料庫)。這個分數作為一個有用的在此語料庫上訓練的所有自動化系統的期望性能的上限。
小心!
應謹慎解釋標注者之間一致性得分,因為標注任務的難度差異巨大。例如,90%的一致性得分對于詞性標注是可怕的得分,但對語義角色標注是可以預期的得分。
Kappa 系數 K 測量兩個人判斷類別和修正預期的期望一致性的一致性。例如,假設要標注一個項目,四種編碼選項可能性相同。這種情況下,兩個人隨機編碼預計有 25%可能達成一致。因此,25%一致性將表示為 k = 0,相應的較好水平的一致性將依比例決定。對于一個 50%的一致性,我們將得到 k = 0.333,因為 50 是從 25 到 100 之間距離的三分之一。還有許多其他一致性測量方法;詳情請參閱`help(nltk.metrics.agreement)`。

圖 2.1:一個序列的三種分割:小矩形代表字、詞、句,總之,任何可能被分為語言單位的序列;S<sub>1</sub>和 S<sub>2</sub>是接近一致的,兩者都與 S<sub>3</sub>顯著不同。
我們還可以測量語言輸入的兩個獨立分割的一致性,例如分詞、句子分割、命名實體識別。在[2.1](./ch11.html#fig-windowdiff)中,我們看到三種可能的由標注者(或程序)產生的項目序列的分割。雖然沒有一個完全一致,S<sub>1</sub>和 S<sub>2</sub>是接近一致的,我們想要一個合適的測量。Windowdiff 是評估兩個分割一致性的一個簡單的算法,通過在數據上移動一個滑動窗口計算近似差錯的部分得分。如果我們將詞符預處理成 0 和 1 的序列,當詞符后面跟著邊界符號時記錄下來,我們就可以用字符串表示分割,應用 windowdiff 打分器。
```py
>>> s1 = "00000010000000001000000"
>>> s2 = "00000001000000010000000"
>>> s3 = "00010000000000000001000"
>>> nltk.windowdiff(s1, s1, 3)
0.0
>>> nltk.windowdiff(s1, s2, 3)
0.190...
>>> nltk.windowdiff(s2, s3, 3)
0.571...
```
上面的例子中,窗口大小為 3。Windowdiff 計算在一對字符串上滑動這個窗口。在每個位置它計算兩個字符串在這個窗口內的邊界的總數,然后計算差異。最后累加這些差異。我們可以增加或縮小窗口的大小來控制測量的敏感度。
## 2.3 維護與演變
隨著大型語料庫的發布,研究人員立足于均衡的從為完全不同的目的而創建的語料庫中派生出的子集進行調查的可能性越來越大。例如,Switchboard 數據庫,最初是為識別說話人的研究而收集的,已被用作語音識別、單詞發音、口吃、句法、語調和段落結構研究的基礎。重用語言語料庫的動機包括希望節省時間和精力,希望在別人可以復制的材料上工作,有時希望研究語言行為的更加自然的形式。為這樣的研究選擇子集的過程本身可視為一個不平凡的貢獻。
除了選擇語料庫的適當的子集,這個新的工作可能包括重新格式化文本文件(如轉換為 XML),重命名文件,重新為文本分詞,選擇數據的一個子集來充實等等。多個研究小組可以獨立的做這項工作,如[2.2](./ch11.html#fig-evolution)所示。在以后的日子,應該有人想要組合不同的版本的源數據,這項任務可能會非常繁重。

圖 2.2:語料庫隨著時間的推移而演變:語料庫發布后,研究小組將獨立的使用它,選擇和豐富不同的部分;然后研究努力整合單獨的標注,面臨校準注釋的艱巨的挑戰。
由于缺乏有關派生的版本如何創建的,哪個版本才是最新的等記錄,使用派生的語料庫的任務變得更加困難。
這種混亂情況的改進方法是集中維護語料庫,專家委員會定期修訂和擴充它,考慮第三方的意見,不時發布的新版本。出版字典和國家語料庫可能以這種方式集中維護。然而,對于大多數的語料庫,這種模式是完全不切實際的。
原始語料庫的出版的一個中間過程是要有一個能識別其中任何一部分的規范。每個句子、樹、或詞條都有一個全局的唯一標識符,每個詞符、節點或字段(分別)都有一個相對偏移。標注,包括分割,可以使用規范的標識符(一個被稱為對峙注釋的方法)引用源材料。這樣,新的標注可以與源材料獨立分布,同一來源的多個獨立標注可以對比和更新而不影響源材料。
如果語料庫出版提供了多個版本,版本號或日期可以是識別規范的一部分。整個語料的版本標識符之間的對應表,將使任何對峙的注釋更容易被更新。
小心!
有時一個更新的語料包含對一直在外部標注的基本材料的修正。詞符可能會被分拆或合并,成分可能已被重新排列。新老標識符之間可能不會一一對應。使對峙標注打破新版本的這些組件比默默允許其標識符指向不正確的位置要好。
## 3 數據采集
## 3.1 從網上獲取數據
網絡是語言分析的一個豐富的數據源。我們已經討論了訪問單個文件,如 RSS 訂閱、搜索引擎的結果(見[3.1](./ch03.html#sec-accessing-text))的方法。然而,在某些情況下,我們要獲得大量的 Web 文本。
最簡單的方法是獲得出版的網頁文本的文集。Web 語料庫 ACL 特別興趣組(SIGWAC)在`http://www.sigwac.org.uk/`維護一個資源列表。使用定義好的 Web 語料庫的優點是它們有文檔、穩定并允許重復性實驗。
如果所需的內容在一個特定的網站,有許多實用程序能捕獲網站的所有可訪問內容,如 _GNU Wget_ `http://www.gnu.org/software/wget/`。For maximal flexibility and control, a web crawler can be used, such as _Heritrix_ `http://crawler.archive.org/`. 為了最大的靈活性和可控制,可以使用網絡爬蟲如[(Croft, Metzler, & Strohman, 2009)](./bibliography.html#croft2009)。例如:如果我們要編譯雙語文本集合,對應兩種語言的文檔對,爬蟲需要檢測站點的結構以提取文件之間的對應關系,它需要按照捕獲的對應方式組織下載的頁面。寫你自己的網頁爬蟲可能使很有誘惑力的,但也有很多陷阱需要克服,如檢測 MIME 類型、轉換相對地址為絕對 URL、避免被困在循環鏈接結構、處理網絡延遲、避免使站點超載或被禁止訪問該網站等。
## 3.2 從字處理器文件獲取數據
文字處理軟件通常用來在具有有限的可計算基礎設施的項目中手工編制文本和詞匯。這些項目往往提供數據錄入模板,通過字處理軟件并不能保證數據結構正確。例如,每個文本可能需要有一個標題和日期。同樣,每個詞條可能有一些必須的字段。隨著數據規模和復雜性的增長,用于維持其一致性的時間的比重也增大。
我們怎樣才能提取這些文件的內容,使我們能夠在外部程序中操作?此外,我們如何才能驗證這些文件的內容,以幫助作者創造結構良好的數據,在原始的創作過程中最大限度提高數據的質量?
考慮一個字典,其中的每個條目都有一個詞性字段,從一個 20 個可能值的集合選取,在發音字段顯示,以 11 號黑體字呈現。傳統的文字處理器沒有能夠驗證所有的詞性字段已正確輸入和顯示的搜索函數或宏。這個任務需要徹底的手動檢查。如果字處理器允許保存文檔為一種非專有的格式,如 text、HTML 或 XML,有時我們可以寫程序自動做這個檢查。
思考下面的一個詞條的片段:“sleep [sli:p] **v.i.**_condition of body and mind..._"。我們可以在 MSWord 中輸入這些詞,然后“另存為網頁”,然后檢查生成的 HTML 文件:
```py
<p class=MsoNormal>sleep
<span style='mso-spacerun:yes'> </span>
[<span class=SpellE>sli:p</span>]
<span style='mso-spacerun:yes'> </span>
<b><span style='font-size:11.0pt'>v.i.</span></b>
<span style='mso-spacerun:yes'> </span>
<i>a condition of body and mind ...<o:p></o:p></i>
</p>
```
這個簡單的程序只是冰山一角。我們可以開發復雜的工具來檢查字處理器文件的一致性,并報告錯誤,使字典的維護者可以 _ 使用原來的文字處理器 _ 糾正的原始文件。
只要我們知道數據的正確格式,就可以編寫其他程序將數據轉換成不同格式。[3.1](./ch11.html#code-html2csv)中的程序使用`nltk.clean_html()`剝離 HTML 標記,提取詞和它們的發音,以“逗號分隔值”(CSV)格式生成輸出。
```py
from bs4 import BeautifulSoup
def lexical_data(html_file, encoding="utf-8"):
SEP = '_ENTRY'
html = open(html_file, encoding=encoding).read()
html = re.sub(r'<p', SEP + '<p', html)
text = BeautifulSoup(html).get_text()
text = ' '.join(text.split())
for entry in text.split(SEP):
if entry.count(' ') > 2:
yield entry.split(' ', 3)
```
with gzip.open(fn+".gz","wb") as f_out:
f_out.write(bytes(s, 'UTF-8'))
注意
更多 HTML 復雜的處理可以使用`http://www.crummy.com/software/BeautifulSoup/`上的 _Beautiful Soup_ 的包。
## 3.3 從電子表格和數據庫中獲取數據
電子表格通常用于獲取詞表或范式。例如,一個比較詞表可以用電子表格創建,用一排表示每個同源組,每種語言一列(見`nltk.corpus.swadesh`和`www.rosettaproject.org`)。大多數電子表格軟件可以將數據導出為 CSV 格式。正如我們將在下面看到的,使用`csv`模塊 Python 程序可以很容易的訪問它們。
有時詞典存儲在一個完全成熟的關系數據庫。經過適當的標準化,這些數據庫可以確保數據的有效性。例如,我們可以要求所有詞性都來自指定的詞匯,通過聲明詞性字段為 _ 枚舉類型 _ 或用一個外鍵引用一個單獨的詞性表。然而,關系模型需要提前定義好的數據(模式)結構,這與高度探索性的構造語言數據的主導方法相違背。被認為是強制性的和獨特的字段往往需要是可選的、可重復。只有當數據類型提前全都知道時關系數據庫才是適用的,如果不是,或者幾乎所有的屬性都是可選的或重復的,關系的做法就行不通了。
然而,當我們的目標只是簡單的從數據庫中提取內容時,完全可以將表格(或 SQL 查詢結果)轉換成 CSV 格式,并加載到我們的程序中。我們的程序可能會執行不太容易用 SQL 表示的語言學目的的查詢,如 _select all words that appear in example sentences for which no dictionary entry is provided_。對于這個任務,我們需要從記錄中提取足夠的信息,使它連同詞條和例句能被唯一的識別。讓我們假設現在這個信息是在一個 CSV 文件`dict.csv`中:
```py
"sleep","sli:p","v.i","a condition of body and mind ..."
"walk","wo:k","v.intr","progress by lifting and setting down each foot ..."
"wake","weik","intrans","cease to sleep"
```
然后,這些信息將可以指導正在進行的工作來豐富詞匯和更新關系數據庫的內容。
## 3.4 轉換數據格式
已標注語言數據很少以最方便的格式保存,往往需要進行各種格式轉換。字符編碼之間的轉換已經討論過(見[3.3](./ch03.html#sec-unicode))。在這里,我們專注于數據結構。
最簡單的情況,輸入和輸出格式是同構的。例如,我們可能要將詞匯數據從 Toolbox 格式轉換為 XML,可以直接一次一個的轉換詞條([4](./ch11.html#sec-working-with-xml))。數據結構反映在所需的程序的結構中:一個`for`循環,每次循環處理一個詞條。
另一種常見的情況,輸出是輸入的摘要形式,如一個倒置的文件索引。有必要在內存中建立索引結構(見[4.8](./ch04.html#code-search-documents)),然后把它以所需的格式寫入一個文件。下面的例子構造一個索引,映射字典定義的詞匯到相應的每個詞條[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch11.html#map-word-lexeme)的語意[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch11.html#lexical-entry),已經對定義文本分詞[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch11.html#definition-text),并丟棄短詞[![[4]](https://img.kancloud.cn/cc/20/cc20c265de5e95a94eb351ef368f3277_15x15.jpg)](./ch11.html#short-words)。一旦該索引建成,我們打開一個文件,然后遍歷索引項,以所需的格式輸出行[![[5]](https://img.kancloud.cn/06/76/0676c0b71e61211d1e72c60abc3ea39e_15x15.jpg)](./ch11.html#required-format)。
```py
>>> idx = nltk.Index((defn_word, lexeme) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
... for (lexeme, defn) in pairs ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
... for defn_word in nltk.word_tokenize(defn) ![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)
... if len(defn_word) > 3) ![[4]](https://img.kancloud.cn/cc/20/cc20c265de5e95a94eb351ef368f3277_15x15.jpg)
>>> with open("dict.idx", "w") as idx_file:
... for word in sorted(idx):
... idx_words = ', '.join(idx[word])
... idx_line = "{}: {}".format(word, idx_words) ![[5]](https://img.kancloud.cn/06/76/0676c0b71e61211d1e72c60abc3ea39e_15x15.jpg)
... print(idx_line, file=idx_file)
```
由此產生的文件`dict.idx`包含下面的行。(如果有更大的字典,我們希望找到每個索引條目中列出的多個語意)。
```py
body: sleep
cease: wake
condition: sleep
down: walk
each: walk
foot: walk
lifting: walk
mind: sleep
progress: walk
setting: walk
sleep: wake
```
## 3.5 決定要包含的標注層
發布的語料庫中所包含的信息的豐富性差別很大。語料庫最低限度通常會包含至少一個聲音或字形符號的序列。事情的另一面,一個語料庫可以包含大量的信息,如句法結構、形態、韻律、每個句子的語義、加上段落關系或對話行為的標注。標注的這些額外的層可能正是有人執行一個特定的數據分析任務所需要的。例如,如果我們可以搜索特定的句法結構,找到一個給定的語言模式就更容易;如果每個詞都標注了意義,為語言模式歸類就更容易。這里提供一些常用的標注層:
* 分詞:文本的書寫形式不能明確地識別它的詞符。分詞和規范化的版本作為常規的正式版本的補充可能是一個非常方便的資源。
* 斷句:正如我們在[3](./ch03.html#chap-words)中看到的,斷句比它看上去的似乎更加困難。因此,一些語料庫使用明確的標注來斷句。
* 分段:段和其他結構元素(標題,章節等)可能會明確注明。
* 詞性:文檔中的每個單詞的詞類。
* 句法結構:一個樹狀結構,顯示一個句子的組成結構。
* 淺層語義:命名實體和共指標注,語義角色標簽。
* 對話與段落:對話行為標記,修辭結構
不幸的是,現有的語料庫之間在如何表示標注上并沒有多少一致性。然而,兩個大類的標注表示應加以區別。內聯標注通過插入帶有標注信息的特殊符號或控制序列修改原始文檔。例如,為文檔標注詞性時,字符串`"fly"`可能被替換為字符串`"fly/NN"`來表示詞 _fly_ 在文中是名詞。相比之下,對峙標注不修改原始文檔,而是創建一個新的文檔,通過使用指針引用原始文檔來增加標注信息。例如,這個新的文檔可能包含字符串`"<token id=8 pos='NN'/>"`,表示 8 號詞符是一個名詞。(我們希望可以確保的分詞本身不會變化,因為它會導致默默損壞這種引用。)
## 3.6 標準和工具
一個用途廣泛的語料庫需要支持廣泛的格式。然而,NLP 研究的前沿需要各種新定義的沒有得到廣泛支持的標注。一般情況下,并沒有廣泛使用的適當的創作、發布和使用語言數據的工具。大多數項目都必須制定它們自己的一套工具,供內部使用,這對缺乏必要的資源的其他人沒有任何幫助。此外,我們還沒有一個可以勝任的普遍接受的標準來表示語料庫的結構和內容。沒有這樣的標準,就不可能有通用的工具——同時,沒有可用的工具,適當的標準也不太可能被開發、使用和接受。
針對這種情況的一個反應就是開拓未來開發一種通用的能充分表現捕獲多種標注類型(見[8](./ch11.html#sec-further-reading-data)的例子)的格式。NLP 的挑戰是編寫程序處理這種格式的泛化。例如,如果編程任務涉及樹數據,文件格式允許任意有向圖,那么必須驗證輸入數據檢查樹的屬性如根、連通性、無環。如果輸入文件包含其他層的標注,該程序將需要知道數據加載時如何忽略它們,將樹數據保存到文件時不能否定或抹殺這些層。
另一種反應一直是寫一個一次性的腳本來操縱語料格式;這樣的腳本將許多 NLP 研究人員的文件夾弄得亂七八糟。在語料格式解析工作應該只進行一次(每編程語言)的前提下,NLTK 中的語料庫閱讀器是更系統的方法。

圖 3.2:通用格式對比通用接口
不是集中在一種共同的格式,我們認為更有希望開發一種共同的接口(參見`nltk.corpus`)。思考 NLP 中的一個重要的語料類型 treebanks 的情況。將短語結構樹存儲在一個文件中的方法很多。我們可以使用嵌套的括號、或嵌套的 XML 元素、或每行帶有一個(child-id,parent-id)對的依賴符號、或一個 XML 版本的依賴符號等。然而,每種情況中的邏輯結構幾乎是相同的。很容易設計一種共同的接口,使應用程序員編寫代碼使用如`children()`、`leaves()`、`depth()`等方法來訪問樹數據。注意這種做法來自計算機科學中已經接受的做法,即即抽象數據類型、面向對象設計、三層結構([3.2](./ch11.html#fig-three-layer-arch))。其中的最后一個——來自關系數據庫領域——允許終端用戶應用程序使用通用的模型(“關系模型”)和通用的語言(SQL)抽象出文件存儲的特質,并允許新的文件系統技術的出現,而不會干擾到終端用戶的應用。以同樣的方式,一個通用的語料庫接口將應用程序從數據格式隔離。
在此背景下,創建和發布一個新的語料庫時,盡可能使用現有廣泛使用的格式是權宜之計。如果這樣不可能,語料庫可以帶有一些軟件——如`nltk.corpus`模塊——支持現有的接口方法。
## 3.7 處理瀕危語言時特別注意事項
語言對科學和藝術的重要性體現在文化寶庫包含在語言中。世界上大約 7000 種人類語言中的每一個都是豐富的,在它獨特的方面,在它口述的歷史和創造的傳說,在它的文法結構和它的變化的詞匯和它們含義中的細微差別。受威脅殘余文化中的詞能夠區分具有科學家未知的治療用途的植物亞種。當人們互相接觸,每個人都為之前的語言提供一個獨特的窗口,語言隨著時間的推移而變化。世界許多地方,小的語言變化從一個鎮都另一個鎮,累加起來在一個半小時的車程的空間中成為一種完全不同的語言。對于其驚人的復雜性和多樣性,人類語言猶如豐富多彩的掛毯隨著時間和空間而伸展。
然而,世界上大多數語言面臨滅絕。對此,許多語言學家都在努力工作,記錄語言,構建這個世界語言遺產的重要方面的豐富記錄。在 NLP 的領域能為這方面的努力提供什么幫助嗎?開發標注器、分析器、命名實體識別等不是最優先的,通常沒有足夠的數據來開發這樣的工具。相反,最經常提出的是需要更好的工具來收集和維護數據,特別是文本和詞匯。
從表面看,開始收集瀕危語言的文本應該是一件簡單的事情。即使我們忽略了棘手的問題,如誰擁有文本,文本中包含的文化知識有關敏感性,轉錄仍然有很多明顯的實際問題。大多數語言缺乏標準的書寫形式。當一種語言沒有文學傳統時,拼寫和標點符號的約定也沒有得到很好的建立。因此,通常的做法是與文本收集一道創建一個詞典,當在文本中出現新詞時不斷更新詞典。可以使用文字處理器(用于文本)和電子表格(用于詞典)來做這項工作。更妙的是,SIL 的自由語言軟件 Toolbox 和 Fieldworks 對文本和詞匯的創建集成提供了很好的支持。
當瀕危語言的說話者學會自己輸入文本時,一個共同的障礙就是對正確的拼寫的極度關注。有一個詞典大大有助于這一進程,但我們需要讓查找的方法不要假設有人能確定任意一個詞的引文形式。這個問題對具有復雜形態的包括前綴的語言可能是很急迫的。這種情況下,使用語義范疇標注詞項,并允許通過語義范疇或注釋查找是十分有益的。
允許通過相似的發音查找詞項也是很有益的。下面是如何做到這一點的一個簡單的演示。第一步是確定易混淆的字母序列,映射復雜的版本到更簡單的版本。我們還可以注意到,輔音群中字母的相對順序是拼寫錯誤的一個來源,所以我們將輔音字母順序規范化。
```py
>>> mappings = [('ph', 'f'), ('ght', 't'), ('^kn', 'n'), ('qu', 'kw'),
... ('[aeiou]+', 'a'), (r'(.)\1', r'\1')]
>>> def signature(word):
... for patt, repl in mappings:
... word = re.sub(patt, repl, word)
... pieces = re.findall('[^aeiou]+', word)
... return ''.join(char for piece in pieces for char in sorted(piece))[:8]
>>> signature('illefent')
'lfnt'
>>> signature('ebsekwieous')
'bskws'
>>> signature('nuculerr')
'nclr'
```
下一步,我們對詞典中的所有詞匯創建從特征到詞匯的映射。我們可以用這為一個給定的輸入詞找到候選的修正(但我們必須先計算這個詞的特征)。
```py
>>> signatures = nltk.Index((signature(w), w) for w in nltk.corpus.words.words())
>>> signatures[signature('nuculerr')]
['anicular', 'inocular', 'nucellar', 'nuclear', 'unicolor', 'uniocular', 'unocular']
```
最后,我們應該按照與原詞相似程度對結果排序。通過函數`rank()`完成。唯一剩下的函數提供給用戶一個簡單的接口:
```py
>>> def rank(word, wordlist):
... ranked = sorted((nltk.edit_distance(word, w), w) for w in wordlist)
... return [word for (_, word) in ranked]
>>> def fuzzy_spell(word):
... sig = signature(word)
... if sig in signatures:
... return rank(word, signatures[sig])
... else:
... return []
>>> fuzzy_spell('illefent')
['olefiant', 'elephant', 'oliphant', 'elephanta']
>>> fuzzy_spell('ebsekwieous')
['obsequious']
>>> fuzzy_spell('nucular')
['anicular', 'inocular', 'nucellar', 'nuclear', 'unocular', 'uniocular', 'unicolor']
```
這僅僅是一個演示,其中一個簡單的程序就可以方便的訪問語言書寫系統可能不規范或語言的使用者可能拼寫的不是很好的上下文中的詞匯數據。其他簡單的 NLP 在這個領域的應用包括:建立索引以方便對數據的訪問,從文本中拾取詞匯表,構建詞典時定位詞語用法的例子,在知之甚少的數據中檢測普遍或特殊模式,并在創建的數據上使用各種語言的軟件工具執行專門的驗證。我們將在[5](./ch11.html#sec-working-with-toolbox-data)返回到其中的最后一個。
## 4 使用 XML
可擴展標記語言(XML)為設計特定領域的標記語言提供了一個框架。它有時被用于表示已標注的文本和詞匯資源。不同于 HTML 的標簽是預定義的,XML 允許我們組建自己的標簽。不同于數據庫,XML 允許我們創建的數據而不必事先指定其結構,它允許我們有可選的、可重復的元素。在本節中,我們簡要回顧一下 XML 的一些與表示語言數據有關的特征,并說明如何使用 Python 程序訪問 XML 文件中存儲的數據。
## 4.1 語言結構中使用 XML
由于其靈活性和可擴展性,XML 是表示語言結構的自然選擇。下面是一個簡單的詞匯條目的例子。
```py
<entry>
<headword>whale</headword>
<pos>noun</pos>
<gloss>any of the larger cetacean mammals having a streamlined
body and breathing through a blowhole on the head</gloss>
</entry>
```
## 4.2 XML 的作用
我們可以用 XML 來表示許多種語言信息。然而,靈活性是要付出代價的。每次我們增加復雜性,如允許一個元素是可選的或重復的,我們對所有訪問這些數據的程序都要做出更多的工作。我們也使它更難以檢查數據的有效性,或使用一種 XML 查詢語言來查詢數據。
因此,使用 XML 來表示語言結構并不能神奇地解決數據建模問題。我們仍然需要解決如何結構化數據,然后用一個模式定義結構,并編寫程序讀取和寫入格式,以及把它轉換為其他格式。同樣,我們仍然需要遵循一些有關數據規范化的標準原則。這是明智的,可以避免相同信息的重復復制,所以當只有一個副本變化時,不會導致數據不一致。例如,交叉引用表示為`<xref>headword</xref>`將重復存儲一些其他詞條的核心詞,如果在其他位置的字符串的副本被修改,鏈接就會被打斷。信息類型之間存在的依賴關系需要建模,使我們不能創建沒有根的元素。例如,如果 sense 的定義不能作為詞條獨立存在,那么`sense`就要嵌套在`entry`元素中。多對多關系需要從層次結構中抽象出來。例如,如果一個 word 可以有很多對應的 senses,一個 sense 可以有幾個對應的 words,而 words 和 senses 都必須作為(word, sense)對的列表分別枚舉。這種復雜的結構甚至可以分割成三個獨立的 XML 文件。
正如我們看到的,雖然 XML 提供了一個格式方便和用途廣泛的工具,但它不是能解決一切問題的靈丹妙藥。
## 4.3 ElementTree 接口
Python 的 ElementTree 模塊提供了一種方便的方式訪問存儲在 XML 文件中的數據。ElementTree 是 Python 標準庫(自從 Python 2.5)的一部分,也作為 NLTK 的一部分提供,以防你在使用 Python 2.4。
我們將使用 XML 格式的莎士比亞戲劇集來說明 ElementTree 的使用方法。讓我們加載 XML 文件并檢查原始數據,首先在文件的頂部[![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch11.html#top-of-file),在那里我們看到一些 XML 頭和一個名為`play.dtd`的模式,接著是根元素 `PLAY`。我們從 Act 1[![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch11.html#start-act-one)再次獲得數據。(輸出中省略了一些空白行。)
```py
>>> merchant_file = nltk.data.find('corpora/shakespeare/merchant.xml')
>>> raw = open(merchant_file).read()
>>> print(raw[:163]) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="shakes.css"?>
<!-- <!DOCTYPE PLAY SYSTEM "play.dtd"> -->
<PLAY>
<TITLE>The Merchant of Venice</TITLE>
>>> print(raw[1789:2006]) ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
<TITLE>ACT I</TITLE>
<SCENE><TITLE>SCENE I. Venice. A street.</TITLE>
<STAGEDIR>Enter ANTONIO, SALARINO, and SALANIO</STAGEDIR>
<SPEECH>
<SPEAKER>ANTONIO</SPEAKER>
<LINE>In sooth, I know not why I am so sad:</LINE>
```
我們剛剛訪問了作為一個字符串的 XML 數據。正如我們看到的,在 Act 1 開始處的字符串包含 XML 標記 title、scene、stage directions 等。
下一步是作為結構化的 XML 數據使用`ElementTree`處理文件的內容。我們正在處理一個文件(一個多行字符串),并建立一棵樹,所以方法的名稱是`parse` [![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch11.html#xml-parse)并不奇怪。變量`merchant`包含一個 XML 元素`PLAY` [![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch11.html#element-play)。此元素有內部結構;我們可以使用一個索引來得到它的第一個孩子,一個`TITLE`元素[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch11.html#element-title)。我們還可以看到該元素的文本內容:戲劇的標題[![[4]](https://img.kancloud.cn/cc/20/cc20c265de5e95a94eb351ef368f3277_15x15.jpg)](./ch11.html#element-text)。要得到所有的子元素的列表,我們使用`getchildren()`方法[![[5]](https://img.kancloud.cn/06/76/0676c0b71e61211d1e72c60abc3ea39e_15x15.jpg)](./ch11.html#getchildren-method)。
```py
>>> from xml.etree.ElementTree import ElementTree
>>> merchant = ElementTree().parse(merchant_file) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
>>> merchant
<Element 'PLAY' at 0x10ac43d18> # [_element-play]
>>> merchant[0]
<Element 'TITLE' at 0x10ac43c28> # [_element-title]
>>> merchant[0].text
'The Merchant of Venice' # [_element-text]
>>> merchant.getchildren() ![[5]](https://img.kancloud.cn/06/76/0676c0b71e61211d1e72c60abc3ea39e_15x15.jpg)
[<Element 'TITLE' at 0x10ac43c28>, <Element 'PERSONAE' at 0x10ac43bd8>,
<Element 'SCNDESCR' at 0x10b067f98>, <Element 'PLAYSUBT' at 0x10af37048>,
<Element 'ACT' at 0x10af37098>, <Element 'ACT' at 0x10b936368>,
<Element 'ACT' at 0x10b934b88>, <Element 'ACT' at 0x10cfd8188>,
<Element 'ACT' at 0x10cfadb38>]
```
這部戲劇由標題、角色、一個場景的描述、字幕和五幕組成。每一幕都有一個標題和一些場景,每個場景由臺詞組成,臺詞由行組成,有四個層次嵌套的結構。讓我們深入到第四幕:
```py
>>> merchant[-2][0].text
'ACT IV'
>>> merchant[-2][1]
<Element 'SCENE' at 0x10cfd8228>
>>> merchant[-2][1][0].text
'SCENE I. Venice. A court of justice.'
>>> merchant[-2][1][54]
<Element 'SPEECH' at 0x10cfb02c8>
>>> merchant[-2][1][54][0]
<Element 'SPEAKER' at 0x10cfb0318>
>>> merchant[-2][1][54][0].text
'PORTIA'
>>> merchant[-2][1][54][1]
<Element 'LINE' at 0x10cfb0368>
>>> merchant[-2][1][54][1].text
"The quality of mercy is not strain'd,"
```
注意
**輪到你來:**對語料庫中包含的其他莎士比亞戲劇,如《羅密歐與朱麗葉》或《麥克白》,重復上述的一些方法;方法列表請參閱`nltk.corpus.shakespeare.fileids()`。
雖然我們可以通過這種方式訪問整個樹,使用特定名稱查找子元素會更加方便。回想一下頂層的元素有幾種類型。我們可以使用`merchant.findall('ACT')`遍歷我們感興趣的類型(如幕)。下面是一個做這種特定標記在每一個級別的嵌套搜索的例子:
```py
>>> for i, act in enumerate(merchant.findall('ACT')):
... for j, scene in enumerate(act.findall('SCENE')):
... for k, speech in enumerate(scene.findall('SPEECH')):
... for line in speech.findall('LINE'):
... if 'music' in str(line.text):
... print("Act %d Scene %d Speech %d: %s" % (i+1, j+1, k+1, line.text))
Act 3 Scene 2 Speech 9: Let music sound while he doth make his choice;
Act 3 Scene 2 Speech 9: Fading in music: that the comparison
Act 3 Scene 2 Speech 9: And what is music then? Then music is
Act 5 Scene 1 Speech 23: And bring your music forth into the air.
Act 5 Scene 1 Speech 23: Here will we sit and let the sounds of music
Act 5 Scene 1 Speech 23: And draw her home with music.
Act 5 Scene 1 Speech 24: I am never merry when I hear sweet music.
Act 5 Scene 1 Speech 25: Or any air of music touch their ears,
Act 5 Scene 1 Speech 25: By the sweet power of music: therefore the poet
Act 5 Scene 1 Speech 25: But music for the time doth change his nature.
Act 5 Scene 1 Speech 25: The man that hath no music in himself,
Act 5 Scene 1 Speech 25: Let no such man be trusted. Mark the music.
Act 5 Scene 1 Speech 29: It is your music, madam, of the house.
Act 5 Scene 1 Speech 32: No better a musician than the wren.
```
不是沿著層次結構向下遍歷每一級,我們可以尋找特定的嵌入的元素。例如,讓我們來看看演員的順序。我們可以使用頻率分布看看誰最能說:
```py
>>> from collections import Counter
>>> speaker_seq = [s.text for s in merchant.findall('ACT/SCENE/SPEECH/SPEAKER')]
>>> speaker_freq = Counter(speaker_seq)
>>> top5 = speaker_freq.most_common(5)
>>> top5
[('PORTIA', 117), ('SHYLOCK', 79), ('BASSANIO', 73),
('GRATIANO', 48), ('LORENZO', 47)]
```
我們也可以查看對話中誰跟著誰的模式。由于有 23 個演員,我們需要首先使用[3](./ch05.html#sec-dictionaries)中描述的方法將“詞匯”減少到可處理的大小。
```py
>>> from collections import defaultdict
>>> abbreviate = defaultdict(lambda: 'OTH')
>>> for speaker, _ in top5:
... abbreviate[speaker] = speaker[:4]
...
>>> speaker_seq2 = [abbreviate[speaker] for speaker in speaker_seq]
>>> cfd = nltk.ConditionalFreqDist(nltk.bigrams(speaker_seq2))
>>> cfd.tabulate()
ANTO BASS GRAT OTH PORT SHYL
ANTO 0 11 4 11 9 12
BASS 10 0 11 10 26 16
GRAT 6 8 0 19 9 5
OTH 8 16 18 153 52 25
PORT 7 23 13 53 0 21
SHYL 15 15 2 26 21 0
```
忽略 153 的條目,因為是前五位角色(標記為`OTH`)之間相互對話,最大的值表示 Othello 和 Portia 的相互對話最多。
## 4.4 使用 ElementTree 訪問 Toolbox 數據
在[4](./ch02.html#sec-lexical-resources)中,我們看到了一個訪問 Toolbox 數據的簡單的接口,Toolbox 數據是語言學家用來管理數據的一種流行和行之有效的格式。這一節中,我們將討論以 Toolbox 軟件所不支持的方式操縱 Toolbox 數據的各種技術。我們討論的方法也可以應用到其他記錄結構化數據,不必管實際的文件格式。
我們可以用`toolbox.xml()`方法來訪問 Toolbox 文件,將它加載到一個`elementtree`對象中。此文件包含一個巴布亞新幾內亞羅托卡特語的詞典。
```py
>>> from nltk.corpus import toolbox
>>> lexicon = toolbox.xml('rotokas.dic')
```
有兩種方法可以訪問 lexicon 對象的內容:通過索引和通過路徑。索引使用熟悉的語法;`lexicon[3]`返回 3 號條目(實際上是從 0 算起的第 4 個條目);`lexicon[3][0]`返回它的第一個字段:
```py
>>> lexicon[3][0]
<Element 'lx' at 0x10b2f6958>
>>> lexicon[3][0].tag
'lx'
>>> lexicon[3][0].text
'kaa'
```
第二種方式訪問 lexicon 對象的內容是使用路徑。lexicon 是一系列`record`對象,其中每個都包含一系列字段對象,如`lx`和`ps`。使用路徑`record/lx`,我們可以很方便地解決所有的語意。這里,我們使用`findall()`函數來搜索路徑`record/lx`的所有匹配,并且訪問該元素的文本內容,將其規范化為小寫。
```py
>>> [lexeme.text.lower() for lexeme in lexicon.findall('record/lx')]
['kaa', 'kaa', 'kaa', 'kaakaaro', 'kaakaaviko', 'kaakaavo', 'kaakaoko',
'kaakasi', 'kaakau', 'kaakauko', 'kaakito', 'kaakuupato', ..., 'kuvuto']
```
讓我們查看 XML 格式的 Toolbox 數據。`ElementTree`的`write()`方法需要一個文件對象。我們通常使用 Python 內置的`open()`函數創建。為了屏幕上顯示輸出,我們可以使用一個特殊的預定義的文件對象稱為`stdout` [![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch11.html#sys-stdout) (標準輸出),在 Python 的`sys`模塊中定義的。
```py
>>> import sys
>>> from nltk.util import elementtree_indent
>>> from xml.etree.ElementTree import ElementTree
>>> elementtree_indent(lexicon)
>>> tree = ElementTree(lexicon[3])
>>> tree.write(sys.stdout, encoding='unicode') ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
<record>
<lx>kaa</lx>
<ps>N</ps>
<pt>MASC</pt>
<cl>isi</cl>
<ge>cooking banana</ge>
<tkp>banana bilong kukim</tkp>
<pt>itoo</pt>
<sf>FLORA</sf>
<dt>12/Aug/2005</dt>
<ex>Taeavi iria kaa isi kovopaueva kaparapasia.</ex>
<xp>Taeavi i bin planim gaden banana bilong kukim tasol long paia.</xp>
<xe>Taeavi planted banana in order to cook it.</xe>
</record>
```
## 4.5 格式化條目
我們可以使用在前一節看到的同樣的想法生成 HTML 表格而不是純文本。這對于將 Toolbox 詞匯發布到網絡上非常有用。它產生 HTML 元素`<table>`,`<tr>`(表格的行)和`<td>`(表格數據)。
```py
>>> html = "<table>\n"
>>> for entry in lexicon[70:80]:
... lx = entry.findtext('lx')
... ps = entry.findtext('ps')
... ge = entry.findtext('ge')
... html += " <tr><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (lx, ps, ge)
>>> html += "</table>"
>>> print(html)
<table>
<tr><td>kakae</td><td>???</td><td>small</td></tr>
<tr><td>kakae</td><td>CLASS</td><td>child</td></tr>
<tr><td>kakaevira</td><td>ADV</td><td>small-like</td></tr>
<tr><td>kakapikoa</td><td>???</td><td>small</td></tr>
<tr><td>kakapikoto</td><td>N</td><td>newborn baby</td></tr>
<tr><td>kakapu</td><td>V</td><td>place in sling for purpose of carrying</td></tr>
<tr><td>kakapua</td><td>N</td><td>sling for lifting</td></tr>
<tr><td>kakara</td><td>N</td><td>arm band</td></tr>
<tr><td>Kakarapaia</td><td>N</td><td>village name</td></tr>
<tr><td>kakarau</td><td>N</td><td>frog</td></tr>
</table>
```
## 5 使用 Toolbox 數據
鑒于 Toolbox 在語言學家中十分流行,我們將討論一些使用 Toolbox 數據的進一步的方法。很多在前面的章節講過的方法,如計數、建立頻率分布、為同現制表,這些都可以應用到 Toolbox 條目的內容上。例如,我們可以為每個條目計算字段的平均個數:
```py
>>> from nltk.corpus import toolbox
>>> lexicon = toolbox.xml('rotokas.dic')
>>> sum(len(entry) for entry in lexicon) / len(lexicon)
13.635...
```
在本節中我們將討論記錄語言學的背景下出現的都不被 Toolbox 軟件支持的兩個任務。
## 5.1 為每個條目添加一個字段
添加一個自動從現有字段派生出的新的字段往往是方便的。這些字段經常使搜索和分析更加便捷。例如,在[5.1](./ch11.html#code-add-cv-field)中我們定義了一個函數`cv()`,將輔音和元音的字符串映射到相應的 CV 序列,即`kakapua`將映射到`CVCVCVV`。這種映射有四個步驟。首先,將字符串轉換為小寫,然后將所有非字母字符`[^a-z]`用下劃線代替。下一步,將所有元音替換為`V`。最后,所有不是`V`或下劃線的必定是一個輔音,所以我們將它替換為`C`。現在,我們可以掃描詞匯,在每個`lx`字段后面添加一個新的`cv`字段。[5.1](./ch11.html#code-add-cv-field)顯示了它對一個特定條目上做的內容;注意輸出的最后一行表示新的`cv`字段。
```py
from xml.etree.ElementTree import SubElement
def cv(s):
s = s.lower()
s = re.sub(r'[^a-z]', r'_', s)
s = re.sub(r'[aeiou]', r'V', s)
s = re.sub(r'[^V_]', r'C', s)
return (s)
def add_cv_field(entry):
for field in entry:
if field.tag == 'lx':
cv_field = SubElement(entry, 'cv')
cv_field.text = cv(field.text)
```
注意
如果一個 Toolbox 文件正在不斷更新,code-add-cv-field 中的程序將需要多次運行。可以修改`add_cv_field()`來修改現有條目的內容。使用這樣的程序為數據分析創建一個附加的文件比替換手工維護的源文件要安全。
## 5.2 驗證 Toolbox 詞匯
Toolbox 格式的許多詞匯不符合任何特定的模式。有些條目可能包括額外的字段,或以一種新的方式排序現有字段。手動檢查成千上萬的詞匯條目是不可行的。我們可以在`Counter`的幫助下很容易地找出頻率異常的字段序列:
```py
>>> from collections import Counter
>>> field_sequences = Counter(':'.join(field.tag for field in entry) for entry in lexicon)
>>> field_sequences.most_common()
[('lx:ps:pt:ge:tkp:dt:ex:xp:xe', 41), ('lx:rt:ps:pt:ge:tkp:dt:ex:xp:xe', 37),
('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](./ch11.html#code-toolbox-validation)中的語法使用我們在[8.](./ch08.html#chap-parse)看到的 CFG 格式。這樣的語法模型隱含 Toolbox 條目的嵌套結構,建立一個樹狀結構,樹的葉子是單獨的字段名。最后,我們遍歷條目并報告它們與語法的一致性,如[5.2](./ch11.html#code-toolbox-validation)所示。那些被語法接受的在前面加一個`'+'` [![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)](./ch11.html#accepted-entries),那些被語法拒絕的在前面加一個`'-'` [![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)](./ch11.html#rejected-entries)。在開發這樣一個文法的過程中,它可以幫助過濾掉一些標簽[![[3]](https://img.kancloud.cn/69/fc/69fcb1188781ff9f726d82da7988a139_15x15.jpg)](./ch11.html#ignored-tags)。
```py
grammar = nltk.CFG.fromstring('''
S -> Head PS Glosses Comment Date Sem_Field Examples
Head -> Lexeme Root
Lexeme -> "lx"
Root -> "rt" |
PS -> "ps"
Glosses -> Gloss Glosses |
Gloss -> "ge" | "tkp" | "eng"
Date -> "dt"
Sem_Field -> "sf"
Examples -> Example Ex_Pidgin Ex_English Examples |
Example -> "ex"
Ex_Pidgin -> "xp"
Ex_English -> "xe"
Comment -> "cmt" | "nt" |
''')
def validate_lexicon(grammar, lexicon, ignored_tags):
rd_parser = nltk.RecursiveDescentParser(grammar)
for entry in lexicon:
marker_list = [field.tag for field in entry if field.tag not in ignored_tags]
if list(rd_parser.parse(marker_list)):
print("+", ':'.join(marker_list)) ![[1]](https://img.kancloud.cn/97/aa/97aa34f1d446f0c464068d0711295a9a_15x15.jpg)
else:
print("-", ':'.join(marker_list)) ![[2]](https://img.kancloud.cn/c7/9c/c79c435fbd088cae010ca89430cd9f0c_15x15.jpg)
```
另一種方法是用一個詞塊分析器([7.](./ch07.html#chap-chunk)),因為它能識別局部結構并報告已確定的局部結構,會更加有效。在[5.3](./ch11.html#code-chunk-toolbox)中我們為詞匯條目建立一個詞塊語法,然后解析每個條目。這個程序的輸出的一個示例如[5.4](./ch11.html#fig-iu-mien)所示。
```py
grammar = r"""
lexfunc: {<lf>(<lv><ln|le>*)*}
example: {<rf|xv><xn|xe>*}
sense: {<sn><ps><pn|gv|dv|gn|gp|dn|rn|ge|de|re>*<example>*<lexfunc>*}
record: {<lx><hm><sense>+<dt>}
"""
```

圖 5.4:一個詞條的 XML 表示,對 Toolbox 記錄的詞塊分析的結果
## 6 使用 OLAC 元數據描述語言資源
NLP 社區的成員的一個共同需要是發現具有很高精度和召回率的語言資源。數字圖書館社區目前已開發的解決方案包括元數據聚集。
## 6.1 什么是元數據?
元數據最簡單的定義是“關于數據的結構化數據”。元數據是對象或資源的描述信息,無論是物理的還是電子的。而術語“元數據”本身是相對較新的,只要收集的信息被組織起來,元數據下面隱含的意義卻一直在被使用。圖書館目錄是一種行之有效的元數據類型;它們已經作為資源管理和發現工具有幾十年了。元數據可以由“手工”產生也可以使用軟件自動生成。
都柏林核心元數據倡議于 1995 年開始開發在網絡上進行資源發現的約定。都柏林核心元數據元素表示一個廣泛的、跨學科一致的元素核心集合,這些元素核心集合有可能對資源發現有廣泛作用。都柏林核心由 15 個元數據元素組成,其中每個元素都是可選的和可重復的,它們是:標題,創建者,主題,描述,發布者,參與者,日期,類型,格式,標識符,來源,語言,關系,覆蓋范圍和版權。此元數據集可以用來描述數字或傳統的格式中存放的資源。
開放檔案倡議(OAI)提供了一個跨越數字化的學術資料庫的共同框架,不考慮資源的類型,包括文檔,資料,軟件,錄音,實物制品,數碼代替品等等。每個庫由一個網絡訪問服務器提供歸檔項目的公共訪問。每個項目都有一個唯一的標識符,并與都柏林核心元數據記錄(也可以是其他格式的記錄)關聯。OAI 為元數據搜索服務定義了一個協議來“收獲”資源庫的內容。
## 6.2 OLAC:開放語言檔案社區
開放語言檔案社區(OLAC)是正在創建的一個世界性語言資源的虛擬圖書館的機構和個人的國際伙伴關系:(i)制訂目前最好的關于語言資源的數字歸檔實施的共識,(ii )開發存儲和訪問這些資源的互操作信息庫和服務的網絡。OLAC 在網上的主頁是`http://www.language-archives.org/`。
OLAC 元數據是描述語言資源的標準。通過限制某些元數據元素的值為使用受控詞表中的術語,確保跨庫描述的統一性。OLAC 元數據可用于描述物理和數字格式的數據和工具。OLAC 元數據擴展了都柏林核心元數據集(一個描述所有類型的資源被廣泛接受的標準)。對這個核心集,OLAC 添加了語言資源的基本屬性,如主題語言和語言類型。下面是一個完整的 OLAC 記錄的例子:
```py
<?xml version="1.0" encoding="UTF-8"?>
<olac:olac xmlns:olac="http://www.language-archives.org/OLAC/1.1/"
xmlns="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.language-archives.org/OLAC/1.1/
http://www.language-archives.org/OLAC/1.1/olac.xsd">
<title>A grammar of Kayardild. With comparative notes on Tangkic.</title>
<creator>Evans, Nicholas D.</creator>
<subject>Kayardild grammar</subject>
<subject xsi:type="olac:language" olac:code="gyd">Kayardild</subject>
<language xsi:type="olac:language" olac:code="en">English</language>
<description>Kayardild Grammar (ISBN 3110127954)</description>
<publisher>Berlin - Mouton de Gruyter</publisher>
<contributor xsi:type="olac:role" olac:code="author">Nicholas Evans</contributor>
<format>hardcover, 837 pages</format>
<relation>related to ISBN 0646119966</relation>
<coverage>Australia</coverage>
<type xsi:type="olac:linguistic-type" olac:code="language_description"/>
<type xsi:type="dcterms:DCMIType">Text</type>
</olac:olac>
```
## 6.3 傳播語言資源
語言數據財團存放 NLTK 數據存儲庫,一個開發的歸檔,社區成員可以上傳語料庫和保存好的模型。這些資源可以使用 NLTK 的下載工具方便地訪問。
## 7 小結
* 大多數語料庫中基本數據類型是已標注的文本和詞匯。文本有時間結構,而詞匯有記錄結構。
* 語料庫的生命周期,包括數據收集、標注、質量控制以及發布。發布后生命周期仍然繼續,因為語料庫會在研究過程中被修改和豐富。
* 語料庫開發包括捕捉語言使用的代表性的樣本與使用任何一個來源或文體都有足夠的材料之間的平衡;增加變量的維度通常由于資源的限制而不可行。
* XML 提供了一種有用的語言數據的存儲和交換格式,但解決普遍存在的數據建模問題沒有捷徑。
* Toolbox 格式被廣泛使用在語言記錄項目中;我們可以編寫程序來支持 Toolbox 文件的維護,將它們轉換成 XML。
* 開放語言檔案社區(OLAC)提供了一個用于記錄和發現語言資源的基礎設施。
## 8 深入閱讀
本章的附加材料發布在`http://nltk.org/`,包括網絡上免費提供的資源的鏈接。
語言學語料庫的首要來源是 _ 語言數據聯盟 _ 和 _ 歐洲語言資源局 _,兩者都有廣泛的在線目錄。本書中提到的主要語料庫的細節也有介紹:美國國家語料庫[(Reppen, Ide, & Suderman, 2005)](./bibliography.html#reppen2005anc)、英國國家語料庫[({BNC}, 1999)](./bibliography.html#bnc1999),Thesaurus Linguae Graecae[({TLG}, 1999)](./bibliography.html#tlg1999)、兒童語言數據交換系統 (CHILDES) [(MacWhinney, 1995)](./bibliography.html#macwhinney1995childes)和 TIMIT[(S., Lamel, & William, 1986)](./bibliography.html#garofolo1986timit)。
計算語言學協會定期組織研討會發布論文集,它的兩個特別興趣組:SIGWAC 和 SIGANN;前者推動使用網絡作為語料,發起去除 HTML 標記的 CLEANEVAL 任務;后者鼓勵對語言注解的互操作性的努力。
[(Buseman, Buseman, & Early, 1996)](./bibliography.html#buseman1996shoebox)提供 Toolbox 數據格式的全部細節,最新的發布可以從`http://www.sil.org/computing/toolbox/`免費下載。構建一個 Toolbox 詞典的過程指南參見`http://www.sil.org/computing/ddp/`。我們在 Toolbox 上努力的更多的例子記錄在[(Tamanji, Hirotani, & Hall, 1999)](./bibliography.html#bird1999nels)和[(Robinson, Aumann, & Bird, 2007)](./bibliography.html#robinson2007toolbox)。[(Bird & Simons, 2003)](./bibliography.html#bird2003portability)調查了語言數據管理的幾十個其他工具。也請參閱關于文化遺產數據的語言技術的 LaTeCH 研討會的論文集。
有很多優秀的 XML 資源(如`http://zvon.org/`)和編寫 Python 程序處理 XML 的資源。許多編輯器都有 XML 模式。XML 格式的詞匯信息包括 OLIF`http://www.olif.net/`和 LIFT`http://code.google.com/p/lift-standard/`。
對于語言標注軟件的調查,見`http://www.ldc.upenn.edu/annotation/`的 _ 語言標注頁 _。對峙注解最初的提出是[(Thompson & McKelvie, 1997)](./bibliography.html#thompson1997standoff)。語言標注的一個抽象的數據模型稱為“標注圖”在[(Bird & Liberman, 2001)](./bibliography.html#bird2001annotation)提出。語言描述的一個通用本體(GOLD)記錄在`http://www.linguistics-ontology.org/`中。
有關規劃和建設語料庫的指導,請參閱[(Meyer, 2002)](./bibliography.html#meyer2002)和[(Farghaly, 2003)](./bibliography.html#farghaly2003) 。關于標注者之間一致性得分的方法的更多細節,見[(Artstein & Poesio, 2008)](./bibliography.html#artsteinpoesio2008)和[(Pevzner & Hearst, 2002)](./bibliography.html#pevzner2002windowdiff)。
Rotokas 數據由 Stuart Robinson 提供,勉方言數據由 Greg Aumann 提供。
有關開放語言檔案社區的更多信息,請訪問`http://www.language-archives.org/`,或參見[(Simons & Bird, 2003)](./bibliography.html#simonsbird2003llc)。
## 9 練習
1. ? 在[5.1](./ch11.html#code-add-cv-field)中新字段出現在條目底部。修改這個程序使它就在`lx`字段后面插入新的子元素。(提示:使用`Element('cv')`創建新的`cv`字段,分配給它一個文本值,然后使用父元素的`insert()`方法。)
2. ? 編寫一個函數,從一個詞匯條目刪除指定的字段。(我們可以在把數據給別人之前用它做些清潔,如刪除包含無關或不確定的內容的字段。)
3. ? 寫一個程序,掃描一個 HTML 字典文件,找出具有非法詞性字段的條目,并報告每個條目的 _ 核心詞 _。
4. ? 寫一個程序,找出所有出現少于 10 次的詞性(`ps`字段)。或許有打字錯誤?
5. ? We saw a method for discovering cases of whole-word reduplication. Write a function to find words that may contain partial reduplication. Use the `re.search()` method, and the following regular expression: `(..+)\1`
6. ? 我們看到一個增加`cv`字段的方法。一件有趣的問題是當有人修改的`lx`字段的內容時,保持這個字段始終最新。為這個程序寫一個版本,添加`cv`字段,取代所有現有的`cv`字段。
7. ? 寫一個函數,添加一個新的字段`syl`,計數一個詞中的音節數。
8. ? 寫一個函數,顯示一個詞位的完整條目。當詞位拼寫錯誤時,它應該顯示拼寫最相似的詞位的條目。
9. ? 寫一個函數,從一個詞典中找出最頻繁的連續字段對(如`ps`后面往往是`pt`)。(這可以幫助我們發現一些詞條的結構。)
10. ? 使用辦公軟件創建一個電子表格,每行包含一個詞條,包括一個中心詞,詞性和注釋。以 CSV 格式保存電子表格。寫 Python 代碼來讀取 CSV 文件并以 Toolbox 格式輸出,使用`lx`表示中心詞,`ps`表示詞性,`gl`表示注釋。
11. ? 在`nltk.Index`幫助下,索引莎士比亞的戲劇中的詞。產生的數據結構允許按單個詞查找,如 music,返回演出、場景和臺詞的引用的列表,`[(3, 2, 9), (5, 1, 23), ...]`的形式,其中`(3, 2, 9)`表示第 3 場演出場景 2 臺詞 9。
12. ? 構建一個條件頻率分布記錄《威尼斯商人》中每段臺詞的詞長,以角色名字為條件,如`cfd['PORTIA'][12]`會給我們 Portia 的 12 個詞的臺詞的數目。
13. ★ 獲取 CSV 格式的比較詞表,寫一個程序,輸出相互之間至少有三個編輯距離的同源詞。
14. ★ 建立一個出現在例句的詞位的索引。假設對于一個給定條目的詞位是 _w_。然后為這個條目添加一個單獨的交叉引用字段`xrf`,引用其它有例句包含 _w_ 的條目的中心詞。對所有條目做這個,結果保存為 Toolbox 格式文件。
15. ? 寫一個遞歸函數將任意樹轉換為對應的 XML,其中非終結符不能表示成 XML 元素,葉子表示文本內容,如:
```py
<S>
<NP type="SBJ">
<NP>
<NNP>Pierre</NNP>
<NNP>Vinken</NNP>
</NP>
<COMMA>,</COMMA>
```