雖然可能有一陣子了,不過總之是個好消息。Python3開始,對於Unicode的處理更直覺,因此能配合Python3的NLTK3還是令人引頸期盼。看來離正式版的NLTK3已經不遠了。
下載網頁:https://pypi.python.org/pypi/nltk
NLTK首頁:http://www.nltk.org/
雖然可能有一陣子了,不過總之是個好消息。Python3開始,對於Unicode的處理更直覺,因此能配合Python3的NLTK3還是令人引頸期盼。看來離正式版的NLTK3已經不遠了。
下載網頁:https://pypi.python.org/pypi/nltk
NLTK首頁:http://www.nltk.org/
Python當中最重要的資料結構應該算是list跟dictionary了。要存取list內部的元素,傳統上都是使用index:假設有一個list叫做a,長度為10則它的所有元素為a[0], a[1], a[2], ..., a[8], a[9]。Python除了傳統的index外,又提供了一個像瑞士小刀一樣的東西叫slice,本篇稍微介紹一下slice的威力。
slice的語法是:a[m:n],代表從第m個元素開始取,取到第n-1個元素為止。不過slice的用途遠大於存取元素,它還可以存取「看不見的東西」,這樣講是有點毛毛的。不過,要真正了解slice的威力,或許不要從「元素」的角度來看,而是從「位置」的角度來看。如果我們將list當中,元素之間的空隙(最前面跟最後面也算)也標上數字,大概是這個樣子:P0, a[0], P1, a[1], P2, a[2], P3, ..., P8, a[8], P9, a[9], P10。則a[m:n]就代表從第m個位置開始取,取到第n個位置為止。m跟n可以都出現,可以只出現其一,也可以都不出現。a[m:]指的是第m個位置到最後面,a[:n]指的是最前面到第n個位置,a[:]指的是最前面到最後面,其實就等同於a本身,但注意到只是「值」的相等。
如果我們寫:
a=[0,1,2,3,4]
b=a
c=a[:]
則b跟a都指向同一個address,當a的內容改變,b的內容跟著變,但c是指向另一個address,因此當a的內容改變,c還是等於[0,1,2,3,4]。
有了這個觀念,接下來討論list的彈性,list可以取代/增加/刪除/排序(sort)/逆轉(reverse),此處我只針對取代/增加/刪除,因為排序跟逆轉剛好就有兩個同名的method可以用。
一、用slice來取代元素
我們先講index的取代。若a=[0,1,2,3,4],m=2,n=4:
元素被元素所取代:a[m]=p,取代之後a變成[0,1,p,3,4]
元素被list所取代:a[n]=(p,q),取代之後a變成[0,1,p,3,(p,q)]
注意到list可以容許不同資料型態做為它的元素,因此這裡的p,q可以是數字或字串,甚至是另一個list (p,q)。
用slice來做取代,寫法大同小異,但其功能更強大,且沒有規定取代者或是被取代者的長度,也就是說,取代完之後的list,長度可能不變(跟用index來做取代一樣),但也可能變長或變短。上述兩個例子,我們用slice來做:
a[m:m+1]=(p),取代之後a變成[0,1,p,3,4]
a[n:n+1]=(p,q),取代之後a變成[0,1,p,3,p,q]
我們發現,此時(p,q)成為a的兩個元素,而非一個做為a的元素的list,跟前面用index來做會得到不一樣的結果。a的長度變成6,增加了1。同樣地,若我們寫:
a[n:n+2]=(r),取代之後a變成[0,1,p,3,r],原本的(p,q)被(r)所取代。
二、用slice來增加元素
index有其限制:我們無法存取看不見的東西。slice的出現彌補了這個缺憾,用slice來處理取代更具有靈活性。list的增加刪減,都可以化約為取代。我們舉例如下:
如果a=[0,1,2,3,4]
則a[:0]=(p),取代之後a變成[p,0,1,2,3,4]
則a[len(a):]=(q),取代之後a變成[p,0,1,2,3,4,q]
其實前者就相當於insert(0,p),後者就相當於insert(len(a),q)或是append(q)。
由於slice必須視為一種sub-list,因此後面對應到的(p)跟(q)都必須以list的形式來表示。
聰明的你一定馬上知道,插入的值可以是好幾個元素,不妨試試看以下的assignment:
a[:0]=(p,q,r)
a[len(a):]=(s,t,u)
前者在Python中無對應的method,只能一次insert一個元素,後者就相當於extend((s,t,u))。
若要在特定位置插入一到多個元素,可以這樣寫:
a[m:m]=(p,q,r,...)
這個寫法比insert(m,x)來得強,因為insert一次只能插入一個元素,而用slice來寫,隨便你要插入幾個元素都沒問題。
這裡再補充一點,如果我們將list看做一個集合,則index只是取當中的一個元素,其地位就是一個元素。但slice是取零個到多個元素,其地位是一個集合,更明確來說,它是list的一個子集合(subset)。
三、用slice來刪除元素
要將一個到多個元素刪除,用slice來做很簡單:
a[m:n]=()
所以如果寫:
a=[0,1,2,3,4]
a[2:4]=()
我們會得到:
a=[0,1,4]
同樣地,Python內建了一個method,叫做pop(),但pop()每次只能刪除一個元素,好處是它會回傳該元素的值。若不需知道欲刪除元素的值,則使用slice比較方便。
總之,雖然Python已經內建了許多處理list的method,但功能都有限。若能熟悉slice的用法,則「吾道一以貫之」,可以用這把瑞士刀做許多事情。
我的Python筆記,只是在執行任務的過程中將所用到的技巧寫下來,供以後參考。本次的任務是:
「根據某個中文字串,計算每個漢字的出現頻率以及此中文字串的bigram以及trigram的出現頻率」
這裡會用到兩個Python最重要的資料結構:list與dictionary,list就不用說了,幾乎所有的程式語言都有,dictionary則像是Perl中的hash。在這邊我從資料庫的角度,把list跟dictionary都視為欄位與欄位的對應,稱為key欄位與value欄位。key欄位就像資料表格中的主鍵值(primary key),必須是獨一無二。list跟dictionary的差別只在於:
要存取內容(value欄位)的方法是利用主鍵值:list[key], dictionary[key]。
I. List Indexation
list除了直接指定key外,也可以指定範圍,list[m:n]表示第m個元素開始一直到第n-1個元素。所以如果series=(0,1,2,3,4),series[2:4]會等於(2,3)。m跟n可以省略其一,m省略代表從頭開始取,一直到第n-1個元素,n省略代表從第m開始取,一直到尾。當然囉,list[:]就等於list本身,不過是「值」等同,「身分」並不等同。
II. List Comprehension
List comprehension在許多程式語言都有,是個很簡潔的語法。我們根據一個原有的list,產生另外一個list,很像某種集合表示式。所以,假設我們想將series裡面每個元素平方,形成新元素,可以這樣寫:
series=(0,1,2,3,4) sqr_series=[num**2 for num in series] |
就可以得到如下結果:
[0, 1, 4, 9, 16] |
III. Dictionary method: get
傳統function在呼叫的時候是這樣寫的:
function(Data) |
Python中有一種跟資料型態依存度很高的function,叫做method。我想這跟物件導向有些關係吧!總之就是不同的object有不同的method。method的寫法如下:(雖然Data改成Object可能更適當,但要跟上面對比,還是用Data)
Data.method() |
這邊介紹一個叫做get的method,只適用於dictionary。
有時候,我們想設定或更改某個dictionary裡面的某個key的value,但卻不知道這個key是否已經存在了。若貿然去存取該key的內容,很有可能出錯。get的語法是:
get(key,value) |
其中key是我們想要測試的key,value則是在萬一dictionary並沒有這個key的時候,我們要回傳的值。這個等一下我們會用實例來說明。
Task 1: Frequency Distribution of Characters
第一個任務,要先算出漢字的頻率分布。我們先定義一個function,input是個list,output是個dictionary:
def list2freqdict(mylist): sentence='吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮。' print(chlist) |
要注意的是:只有Python 3.1 之後的版本可以直接處理Unicode而不需轉換。因此若你是用Python 2.6/2.7,上述程式碼會錯誤。不過若你非用nltk不可,建議還是在Python 2.6/2.7之下執行。
注意到我們在list2freqdict這個function中,要先用dict()這個function,說明mydict是一個 dictionary,然後就是一個for loop,去檢查每一個中文字串。後面的get若翻成白話文,就是:若該漢字已經在我的dictionary裡面,請將以該漢字為key的value加 1,若還沒有,則回傳0,因為後面還有加1,因此會將1設定給第一次出現在dictionary裡面的漢字key的value。
sentence是一個string,我們這邊先不談如何讀檔寫檔,而是先在程式碼中指定。接著,我們用list comprehension將sentence轉成一個叫做chlist的list。從印出來的結果可以看到chlist的長相,以及頻率分布的字典 chfreqdict。
執行結果如下:
['吃', '葡', '萄', '不', '吐', '葡', '萄', '皮', ',', '不', '吃', '葡', '萄', '倒', '吐', '葡', '萄', '皮', '。'] {'葡': 4, '吃': 2, '。': 1, '萄': 4, '不': 2, ',': 1, '皮': 2, '吐': 2, '倒': 1}
這個chfreqdict有點醜,原因是dictionary並沒有順序的概念,因此印出來的順序是隨機。而且有時候我們只對高頻字有興趣,或是漢字太多,這時應該要有排序,然後只印出次數大於m以上的漢字的次數,或根據次數,印出前n個漢字的次數。
由於我們要sort的標準是dictionary裡面的value,不是key,所以會有點麻煩。我們找到其他人已經造好的輪子拿來用:
from operator import itemgetter chfreqsorted=sorted(chfreqdict.items(), key=itemgetter(1), reverse=True) print(chfreqsorted) |
得到結果如下:
[('葡', 4), ('萄', 4), ('吃', 2), ('不', 2), ('皮', 2), ('吐', 2), ('。', 1), (',', 1), ('倒', 1)] |
若我們想限定只印出前幾個最高次數的漢字及其次數,可以用list indexation。若想只印出出現次數高於某個數字的漢字及其次數,則必須另起爐灶,這邊我們會用到一個list的method,叫做append,要記得由於我們的資料形態是list of lists,而append一次只能附加一個元素,因此要寫成append((ch,num))而不是append(ch,num):前者是說:附加一個叫做(ch,num)的元素,後者是說:附加兩個元素叫做ch與num。這邊我們只印出前5個結果,以及出現次數大於1的結果:
chfreqsorted2=chfreqsorted[:5] print(chfreqsorted2) |
得到結果如下:
[('葡', 4), ('萄', 4), ('吃', 2), ('不', 2), ('皮', 2)] [('葡', 4), ('萄', 4), ('吃', 2), ('不', 2), ('皮', 2), ('吐', 2)] |
Task 2: Frequency Distribution of Bigrams and Trigrams
N-gram是自然語言處理常用到的方法,一般是用來計算共現(collocation)。在英文中可以用來計算word與word之間的共現關係,中文則比較常用來計算字與字之見的共現關係,對於斷詞(segmentation)或是計算詞彙的孳生性(productivity)來說非常重要。
我們先定義兩個簡單的function,可以根據一個list產生bigram以及trigram:
def list2bigram(mylist): chbigram=list2bigram(chlist) |
得到結果如下:
[[' 吃', '葡'], ['葡', '萄'], ['萄', '不'], ['不', '吐'], ['吐', '葡'], ['葡', '萄'], ['萄', '皮'], ['皮', ','], [',', '不'], ['不', '吃'], ['吃', '葡'], ['葡', '萄'], ['萄', '倒'], ['倒', '吐'], ['吐', '葡'], ['葡', '萄'], ['萄', '皮'], ['皮', '。']]
|
然後來計算頻率。原本我以為list2freqdict()這個function可以直接拿來用,後來發現它的input只能是簡單的list,不能是list of lists,所以又重複造了兩個輪子:
def bigram2freqdict(mybigram): bigramfreqdict=bigram2freqdict(chbigram) |
結果如下:
{(' 不', '吃'): 1, (',', '不'): 1, ('皮', ','): 1, ('皮', '。'): 1, ('萄', '不'): 1, ('倒', '吐'): 1, ('萄', '倒'): 1, ('吃', '葡'): 2, ('萄', '皮'): 2, ('不', '吐'): 1, ('葡', '萄'): 4, ('吐', '葡'): 2}
|
一樣排序,然後只取前5筆資料:
bigramfreqsorted=sorted(bigramfreqdict.items(), key=itemgetter(1), reverse=True) trigramfreqsorted=sorted(trigramfreqdict.items(), key=itemgetter(1), reverse=True) print(bigramfreqsorted[:5]) print(trigramfreqsorted[:5]) |
結果如下:
[(('葡', '萄'), 4), (('吃', '葡'), 2), (('萄', '皮'), 2), (('吐', '葡'), 2), (('不', '吃'), 1)]
|
最後,我們寫一個簡單的列印function,將頻率統計弄得比較user-friendly:
def freq2report(freqlist): chs=str() print('Char(s)\tCount') print('=============') for (token,num) in freqlist: for ch in token: chs=chs+ch print(chs,'\t',num) chs='' return freq2report(chfreqsorted) freq2report(bigramfreqsorted) freq2report(trigramfreqsorted) |
結果如下:
Char(s) Count Char(s) Count Char(s) Count |
到這裡應該算是完美達成任務。如果語料再多一點,應該會看到更有趣的現象。
今天早上,到了學校,打開電腦,隨即電腦傳來刺耳的聲音,聽起來很像風扇卡卡。中午拿去HP維修中心,證實風扇異常。工程師把硬碟跟電池拆下來還我,電腦留下來處理。相信大家都有電腦壞掉的經驗,怕的不是電腦整個掛掉,而是重要檔案毀損,所謂硬體有價,資料無價。我當下想到的問題就是,如果整台電腦要重灌,我會損失多少重要資料?
對於需要在不同電腦中處理同樣文件的人來說,帶個隨身碟備份或許是一個常用的方法。不過,同步是一個大麻煩,要先從A電腦拷貝到隨身碟,再從隨身碟拷貝到B電腦,當然你也可以從頭到尾都把文件放在隨身碟,不拷貝到本地電腦硬碟,但人總是有疏忽的時候,有時急著出門,隨身碟忘了拔,到了目的地才在懊惱。這樣的問題,我也一直很困擾,直到Dropbox出現之後。
Dropbox是一個網路備份服務,用一個帳號控管。任何電腦裝了Dropbox之後,用同一個帳號登入,就會同步Dropbox目錄底下的任何東西。Dropbox是以本地硬碟目錄的形式出現,因此存取速度比隨身碟快,不用煩惱隨身碟帶了沒有,不用煩惱檔案新舊的問題,它都幫你處理好。
目前容量大約是2.5GB,沒記錯的話。將你每天工作都需要的檔案放在Dropbox當中,到哪裡都可以存取你的檔案。當然網路備份就會有隱私外洩的可能,這個就要自行取捨了。
回到電腦壞掉的問題,如果這次我的硬碟整個壞了,的確我會損失放在桌面還有我的文件底下的檔案,但是所有放在Dropbox底下的重要檔案,都安全無恙。因此在這裡推薦大家使用。
記得以前唸書的時候,要轉個PDF檔還要用「阿土伯」他家出的Acrobat,要錢的。後來陸續出了一堆處理PDF的免費軟體,在這邊要分享兩套我認為很不錯的免費軟體。
第一個是pdf995,原本聽諧音「pdf救救我」還以為是台灣人寫的軟體。網頁在這裡:http://www.pdf995.com/。
安裝完之後,會多出一個虛擬印表機。因此不只我們常用的文字編輯軟體可以將文件轉PDF,瀏覽器或是繪圖軟體都可以。步驟就只是「假裝」要去用pdf995虛擬印表機來列印。這個軟體有免費版跟付費版,免費版在轉檔的時候會有廣告,不過功能完全一樣。
說到這裡,就忍不住要抱怨一下Microsoft Word。這個全世界不知有多少人在用的軟體,仍然無法直接輸出PDF。相反地,免費OpenOffice.org Writer本身就內建輸出PDF的功能。
這個軟體有進階版,pdfEdit995以及Signature995,我只用過前者。pdfEdit995可以讓你將多個pdf合併,或從一個pdf中抽取幾頁下來,這是我使用它的主要用途,其他用途請自行玩玩。
另一個軟體是PDFill PDF Editor,這個軟體功能更強大,包含了上述所有功能,也免費,更沒有廣告。值得大家試試。網頁在這裡:http://www.pdfill.com/。
那我為什麼不介紹PDFill PDF Editor就好了?
因為我是個很念舊的人,過去很長一段時間裡,我都用pdf995,也推薦別人使用。PDFill PDF Editor安裝過一兩次,但使用的時間沒那麼久,因此說不上有太多感情,也沒啥可分享。只是覺得不介紹它有點對不起良心。