2010年5月3日 星期一

用Python也能輕鬆玩自然語言處理(序言+1.1)


Steven Bird, Ewan Klein, and Edward Loper. (2009)Natural Language Processing with Python--- Analyzing Text with the Natural Language Toolkit. O'Reilly Media.

這本書就像是我的課本一樣,超貼心的作者群還把他貢獻在網站上!
我想說既然也要看,乾脆順便翻譯整理一下~
順便讓「自然語言處理」在中文世界多點東西!
不過我也還在學習中啦..對內容有疑義的話請盡量提出來討論!謝謝
我會一小節一小節發表,因為內容實在太豐富了
ps.歐萊禮應該不會來騷擾我才是...XD

這是這本書的第一章「語言處理與Python
(第零章的部份我就跳過啦..)
透過這一章節你可以了解...

  1. 如何透過簡單的程式去處理大量的文本(texts)
  2. 如何從文本中自動擷取具有摘要特性的關鍵字(keywords)
  3. 有什麼工具可以讓Python程式語言發揮上述的這些效用
  4. 自然語言處理目前有哪些重大的挑戰

----------------------------------------------------------

1.1   語言也可以計算:文本與文字

我們都對文本(texts)不感到陌生,因為每天多多少少都會閱讀到一些資訊。現在我們要把這些文本視為「原始資料(raw data)」,並撰寫適當的程式去控制與分析這些原始資料以便運用在許多有趣的地方。但是在作到這一步之前,我們必須先了解一下這個Python直譯器要怎麼用...


初次使用Python...


你可以免費地取得友善又方便的Python直譯器,而且是使用圖形化介面的IDLE(Interactive Development Environment). 如果的電腦的作業系統是麥金塔系統(Mac)的話,你可以在「應用程式」以下找到「MacPython」;如果是微軟的Windows則可在「應用程式」下找到「Python」;Unix的朋友們則可以敲入「idle」指令(尚未安裝的話試試敲入python)。安裝NLTK部份的詳情可以參見nltk網站


安裝完畢,執行後如果可以看到類似以上的畫面就應該算是裝好一半了(簡單說,總共要安裝三樣東西Python, PyYAML, NLTK工具包)。然後就可以開始玩一些簡單的運算,「>>>」是Python的提示符號,表示正在等你輸入,同時也可以他已經把前一個動作作完了。從上圖你可以發現每次下完指令後,印出答案的下一行都是此符號。當然還是有一些小細節會慢慢出現像是圖例中最後的語法錯誤,我只輸入「1+」,以及再上一個我輸入除不盡的「1/3」,必須要使用浮點數「1.0/3.0」才行等都是比較細節的學習,大家可以去買書學或是上網查一查。


初次使用NLTK(自然語言工具包)

稍微對Python有點互動之後,就開始將這本書的範例檔下載進來,透過以下的指令應該可以正常去RUN「NLTK Downloader」以及下載資料(如果有安裝好的話)。

>>> import nltk
>>> nltk.download()

然後會跳出下面的畫面:


「all-corpus」就是下載全部的語料庫(後面章節會慢慢用到),「book」就是馬上要用到的一些測試NLTK的好用電子書,另外「all」應該不用多說吧(笑)。下載好了以後,就馬上利用Python來載入看看吧!透過以下的指令,可以發現文本一個一個地載入進來,「from nltk.book import *」的意思就是告訴我們從nltk的book模組中把全部的資料都載入進來給python用!這些文本(text1~text9)將在這一章派上用場。

>>> from nltk.book import *
*** Introductory Examples for the NLTK Book *** Loading text1, ..., text9 and sent1, ..., sent9 Type the name of the text or sentence to view it. Type: 'texts()' or 'sents()' to list the materials.
text1: Moby Dick by Herman Melville 1851
text2: Sense and Sensibility by Jane Austen 1811
text3: The Book of Genesis
text4: Inaugural Address Corpus
text5: Chat Corpus
text6: Monty Python and the Holy Grail
text7: Wall Street Journal
text8: Personals Corpus
text9: The Man Who Was Thursday by G . K . Chesterton 1908
>>>

你可以隨時在敲入文本名字進行查詢:
>>> text1
<Text: Moby Dick by Herman Melville 1851>
>>> text2
<Text: Sense and Sensibility by Jane Austen 1811>
>>>

現在我們已經會用Python直譯器,身邊也有一些資料可以跑跑看了,還等什麼呢?

搜尋文本

可以使用NLTK與Python後,我們就能夠用各種方式去檢視一個文本(不僅只是單純地閱讀)。像下面的範例就是利用「concordance」指令來查詢在「text1(Moby Dick)」中「monstrous」出現的上下文情況:


除了瞭解某特定字詞的出現情況以外,還可以利用「similar」指令去查出在「text1、text2」當中與「monstrous」出現類似上下文的字詞群(如有一些可以稍微互通的形容詞):

>>> text1.similar("monstrous")
Building word-context index...
subtly impalpable pitiable curious imperial perilous trustworthy abundant untoward singular lamentable few maddens horrible loving lazy mystifying christian exasperate puzzled
>>> text2.similar("monstrous")
Building word-context index...
very exceedingly so heartily a great good amazingly as sweet remarkably extremely vast
>>>

觀察這些不同的文本的結果,我們可以發現一些有趣的事情。如Austen(text2作者)與Melville(text1作者)兩者的用語差異,Austen比較常將「monstrous」用在上面的意涵中,有時則甚至把它與當成「very」在使用。為了瞭解這兩個詞怎麼被互用的情況,我們可以直接用「common.contexts」指令來檢視(將兩個要查詢的詞各自放在引號中,並用括號括起來):

>>> text2.common_contexts(["monstrous", "very"])
be_glad am_glad a_pretty is_pretty a_lucky
>>>

另外,還可以善用「字詞出現的位置」來得到有意義的結果。如我們使用text4(過去220年美國總統演講稿)來進行測試「dispersion_plot(散佈圖)」指令,並敲入一些字詞去檢視哪些字詞隨歷史時間影響其出現的密集程度或一些消長情形(注意!要使用散佈圖必須安裝NumPy 、Matplotlib兩個套件才行唷參見):

>>> text4.dispersion_plot(["citizens", "democracy", "freedom", "duties", "America"])
>>>


現在應該開始覺得這玩意兒挺有意思了吧!?現在我們隨便選擇一個文本,就只是為了隨機取得某一段字串就好,試試看「generate」指令吧!它可是每次執行都會有不一樣的結果唷(後面必須要加一個空括號,為什麼呢?等後頭我們正式進入Python之後會再多介紹原理的)

>>> text3.generate()
In the beginning of his brother is a hairy man , whose top may reach
unto heaven ; and ye shall sow the land of Egypt there was no bread in
all that he was taken out of the month , upon the earth . So shall thy
wages be ? And they made their father ; and Isaac was old , and kissed
him : and Laban with his cattle in the midst of the hands of Esau thy
first born , and Phichol the chief butler unto his son Isaac , she
>>>

計算詞彙

接下來這階段,我們來學學怎麼用各種有用的方式來計算文本中的詞彙。如同之前,我們不用有系統地去探究Python直譯器的原理所在,只需要透過練習與範例的測試就好!讓我們先來測試整個文本3(Genesis)從頭到尾的每一個字與符號的字數(或是稱為文本的長度),這件事用「len()」指令就可做到:

>>> len(text3)
44764
>>> sorted(set(text3))
['!', "'", '(', ')', ',', ',)', '.', '.)', ':', ';', ';)', '?', '?)', 'A', 'Abel', 'Abelmizraim', 'Abidah', 'Abide', 'Abimael', 'Abimelech', 'Abr', 'Abrah', 'Abraham', 'Abram', 'Accad', 'Achbor', 'Adah', ...]
>>> len(set(text3))
2789
>>>

執行len()指令過後,有沒有開始發現括號好像都是拿來放一些要執行程式的資訊來源?那就叫做參數啦,後面會越來越熟悉的!結果發現文本3(Genesis)的長度是44764,這可以給我一個概念就是這本書大概有多少個組成的。但其實這個說法並不精準,因為我們前面有說過除了一個個的字詞會被計算以外,還有一些符號也會被視為計算的單位,這個單位其實有一個術語叫做「Token(記號)」!例如hairy、his、:) 等都會被視為一個token。


然而知道一本書裡頭會重複用到相同的token,比起去瞭解所有的token,我們更關心的是這本書用了多少寫出來的,因此希望重複出現的token把他視為一個就好。這裡則可以利用「set()」指令來進行,它就是數學上集合的概念,只把整個範圍中出現的元素名稱告訴我們就好了!執行的同時,還可以順便把它「包」在剛剛講的len()裡面,這樣就可以計算文本3用了多少種不同的token寫成的了!(為什麼是說token?因為還是有夾雜符號在裡頭)在上面的三個例子中,第二例子出現了「sorted()」,顧名思義就是排序顯示的意思啦!

現在!我們試著利用前面教過的內容來組合一下,就可以計算出文本3裡頭平均每個字(token)出現幾次!(注意面對會除不盡的情況,要先導入浮點數的除法模組)
>>> from __future__ import division
>>> len(text3) / len(set(text3))
16.050197203298673
>>>

同樣地,你也可以試試看去計算個別字詞的出現次數,或是計算個別字母出現的百分比:

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

你可能會重複去用到這些計算方法去處理不同的文本,但是一直打這些指令實在很煩,所以我們現在要建立一些偷懶的方法,就是「定義函數」!這裡我們需要瞭解Python定義函數的方法,首先是「def(定義)」表示接下來會宣告一個function(函數)的區塊,這個區塊會不只一行來描述,所以在換行時(壓下Enter時),Python直譯器會判定為區塊,並繼續讓使用者輸入程式碼(這就是「...」出現的原因,而非「>>>」)。當然!要建立函數,就要幫它取名字,像下面的兩個例子,分別是「lexical_diversity」、「percentage」:

>>> def lexical_diversity(text):
...           return len(text) / len(set(text))
...
>>> def percentage(count, total):
...           return 100 * count / total
...

不過這裡有些小細節要稍微交代一下才行,在「...」後如果直接輸入Enter的話就會執行了。另外一方面,在Python互動式的直譯器環境外,將來可能還會在各種文字編輯器撰寫一些程式碼,然後進行測試,然而我們在編輯這些程式碼時,如果是上述這種「區塊」則不會出現「...」,而必須自己空4個空白鍵(一些IDE發展工具會自動空),這種「縮排」的概念十分重要,因為它是Python用來表達程式中每個結構的描述方式。


再來,我們看一下這兩個函數要怎麼運作,在「lexical_diversity」函數中,需要一個參數給它,就是「text」!括號後面千萬不要忘記「冒號『』」。接著執行「len(text) / len(set(text))」這段我們剛剛已經瞭解它的功能!但是前面多了「return」,顧名思義他是用來把這個運算式的結果回傳出來。所以整個函數意味著「丟一個文本給它,它會幫我計算該文本中的平均字詞的出現次數(專業一點的說法:語意的多樣性)」!而第二個「percentage」函數的語法結構也差不多,只不過它需要兩個參數「count 與 total」,其實很好理解,整個意思就是「丟給它一個要評估的次數、一個整體的次數,然後它會去計算百分比,並回傳出來」!知道這些東西後,以後碰到函數就不用怕了!看得懂之後,我們就來使用吧:


>>> lexical_diversity(text3)
16.050197203298673
>>> lexical_diversity(text5)
7.4200461589185629
>>> percentage(4, 5)
80.0
>>>
percentage(text4.count('a'), len(text4))

1.4643016433938312
>>>

懂了函數的運作方式,執行起來就非常愉快了!如同函數、定義之類的術語一般,我們會把執行函數的動作稱為「呼叫(call)」!就是把它叫出來幫我做事的感覺~到此是不是開始對Python的人性化運作開始有點感覺了?什麼?沒有?...好吧畢竟這只是一開始,而且這本書也不是一本程式語言書,它只是幫助我們解決自然語言處理的議題而已。


4 則留言:

Trigger Liu 提到...

我正在學習自然語言 ! 非常感謝您不辭辛勞,相關資料對我幫助很大 ! 謝謝 !!!

qheroq 提到...

歡迎一起討論吧 我也正在學習!

匿名 提到...

原PO有沒有想過要跟Oreilly承包翻譯?感覺你翻的還不錯 :)

qheroq 提到...

哈哈我退伍之後可以考慮看看,但是我英文還很菜XDD