我的Python筆記,只是在執行任務的過程中將所用到的技巧寫下來,供以後參考。本次的任務是:
「根據某個中文字串,計算每個漢字的出現頻率以及此中文字串的bigram以及trigram的出現頻率」
這裡會用到兩個Python最重要的資料結構:list與dictionary,list就不用說了,幾乎所有的程式語言都有,dictionary則像是Perl中的hash。在這邊我從資料庫的角度,把list跟dictionary都視為欄位與欄位的對應,稱為key欄位與value欄位。key欄位就像資料表格中的主鍵值(primary key),必須是獨一無二。list跟dictionary的差別只在於:
- list中的key欄位必須為零開始以及自然數(0,1,2,...),value欄位則可以是任何資料形態。list的樣子為(value0, value1, value2, ...)
- dictionary中的key欄位可以是任何資料形態,value欄位也可以是任何資料形態。dictionary的樣子為{key0: value0, key1:value1, key2:value2, ...}
要存取內容(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 |
到這裡應該算是完美達成任務。如果語料再多一點,應該會看到更有趣的現象。