close

我的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]
print(sqr_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):
    mydict=dict()
    for ch in mylist:
        mydict[ch]=mydict.get(ch,0)+1
    return mydict

sentence='吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮。'
chlist=[ch for ch in sentence]

print(chlist)
chfreqdict=list2freqdict(chlist)
print(chfreqdict)

要注意的是:只有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]

chfreqsorted3=list()
for (ch,num) in chfreqsorted:
    if num > 1:
        chfreqsorted3.append((ch,num))    

print(chfreqsorted2)
print(chfreqsorted3)

得到結果如下:

[('葡', 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):
    return [mylist[i:i+2] for i in range(0,len(mylist)-1)]

def list2trigram(mylist):
    return [mylist[i:i+3] for i in range(0,len(mylist)-2)]

chbigram=list2bigram(chlist)
chtrigram=list2trigram(chlist)
print(chbigram)
print(chtrigram)

得到結果如下:

[[' 吃', '葡'], ['葡', '萄'], ['萄', '不'], ['不', '吐'], ['吐', '葡'], ['葡', '萄'], ['萄', '皮'], ['皮', ','], [',', '不'], ['不', '吃'], ['吃', '葡'], ['葡', '萄'], ['萄', '倒'], ['倒', '吐'], ['吐', '葡'], ['葡', '萄'], ['萄', '皮'], ['皮', '。']]


[[' 吃', '葡', '萄'], ['葡', '萄', '不'], ['萄', '不', '吐'], ['不', '吐', '葡'], ['吐', '葡', '萄'], ['葡', '萄', '皮'], ['萄', '皮', ','], ['皮', ',', '不'], [',', '不', '吃'], ['不', '吃', '葡'], ['吃', '葡', '萄'], ['葡', '萄', '倒'], ['萄', '倒', '吐'], ['倒', '吐', '葡'], ['吐', '葡', '萄'], ['葡', '萄', '皮'], ['萄', '皮', '。']]

然後來計算頻率。原本我以為list2freqdict()這個function可以直接拿來用,後來發現它的input只能是簡單的list,不能是list of lists,所以又重複造了兩個輪子:

def bigram2freqdict(mybigram):
    mydict=dict()
    for (ch1,ch2) in mybigram:
        mydict[(ch1,ch2)]=mydict.get((ch1,ch2),0)+1
    return mydict

def trigram2freqdict(mytrigram):
    mydict=dict()
    for (ch1,ch2,ch3) in mytrigram:
        mydict[(ch1,ch2,ch3)]=mydict.get((ch1,ch2,ch3),0)+1
    return mydict

bigramfreqdict=bigram2freqdict(chbigram)
trigramfreqdict=trigram2freqdict(chtrigram)
print(bigramfreqdict)
print(trigramfreqdict)

結果如下:

{(' 不', '吃'): 1, (',', '不'): 1, ('皮', ','): 1, ('皮', '。'): 1, ('萄', '不'): 1, ('倒', '吐'): 1, ('萄', '倒'): 1, ('吃', '葡'): 2, ('萄', '皮'): 2, ('不', '吐'): 1, ('葡', '萄'): 4, ('吐', '葡'): 2}


{(' 萄', '倒', '吐'): 1, ('葡', '萄', '不'): 1, ('葡', '萄', '倒'): 1, ('倒', '吐', '葡'): 1, ('皮', ',', '不'): 1, ('不', '吃', '葡'): 1, ('萄', '皮', '。'): 1, ('萄', '不', '吐'): 1, (',', '不', '吃'): 1, ('不', '吐', '葡'): 1, ('葡', '萄', '皮'): 2, ('萄', '皮', ','): 1, ('吃', '葡', '萄'): 2, ('吐', '葡', '萄'): 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)]


[(('葡', '萄', '皮'), 2), (('吃', '葡', '萄'), 2), (('吐', '葡', '萄'), 2), (('萄', '倒', '吐'), 1), (('葡', '萄', '不'), 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
=============
葡      4
萄      4
吃      2
不      2
皮      2
吐      2
。      1
,      1
倒      1

Char(s)    Count
=============
葡萄      4
吃葡      2
萄皮      2
吐葡      2
不吃      1
,不      1
皮,      1
皮。      1
萄不      1
倒吐      1
萄倒      1
不吐      1

Char(s)    Count
=============
葡萄皮      2
吃葡萄      2
吐葡萄      2
萄倒吐      1
葡萄不      1
葡萄倒      1
倒吐葡      1
皮,不      1
不吃葡      1
萄皮。      1
萄不吐      1
,不吃      1
不吐葡      1
萄皮,      1

到這裡應該算是完美達成任務。如果語料再多一點,應該會看到更有趣的現象。

arrow
arrow
    全站熱搜

    hambao 發表在 痞客邦 留言(3) 人氣()