目前分類:數位生活 (5)

瀏覽方式: 標題列表 簡短摘要

雖然可能有一陣子了,不過總之是個好消息。Python3開始,對於Unicode的處理更直覺,因此能配合Python3的NLTK3還是令人引頸期盼。看來離正式版的NLTK3已經不遠了。

下載網頁:https://pypi.python.org/pypi/nltk

 NLTK首頁:http://www.nltk.org/

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

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的用法,則「吾道一以貫之」,可以用這把瑞士刀做許多事情。

 

 

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

我的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

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

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

今天早上,到了學校,打開電腦,隨即電腦傳來刺耳的聲音,聽起來很像風扇卡卡。中午拿去HP維修中心,證實風扇異常。工程師把硬碟跟電池拆下來還我,電腦留下來處理。相信大家都有電腦壞掉的經驗,怕的不是電腦整個掛掉,而是重要檔案毀損,所謂硬體有價,資料無價。我當下想到的問題就是,如果整台電腦要重灌,我會損失多少重要資料?

對於需要在不同電腦中處理同樣文件的人來說,帶個隨身碟備份或許是一個常用的方法。不過,同步是一個大麻煩,要先從A電腦拷貝到隨身碟,再從隨身碟拷貝到B電腦,當然你也可以從頭到尾都把文件放在隨身碟,不拷貝到本地電腦硬碟,但人總是有疏忽的時候,有時急著出門,隨身碟忘了拔,到了目的地才在懊惱。這樣的問題,我也一直很困擾,直到Dropbox出現之後。

http://www.dropbox.com/

Dropbox是一個網路備份服務,用一個帳號控管。任何電腦裝了Dropbox之後,用同一個帳號登入,就會同步Dropbox目錄底下的任何東西。Dropbox是以本地硬碟目錄的形式出現,因此存取速度比隨身碟快,不用煩惱隨身碟帶了沒有,不用煩惱檔案新舊的問題,它都幫你處理好。

目前容量大約是2.5GB,沒記錯的話。將你每天工作都需要的檔案放在Dropbox當中,到哪裡都可以存取你的檔案。當然網路備份就會有隱私外洩的可能,這個就要自行取捨了。

回到電腦壞掉的問題,如果這次我的硬碟整個壞了,的確我會損失放在桌面還有我的文件底下的檔案,但是所有放在Dropbox底下的重要檔案,都安全無恙。因此在這裡推薦大家使用。

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

記得以前唸書的時候,要轉個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安裝過一兩次,但使用的時間沒那麼久,因此說不上有太多感情,也沒啥可分享。只是覺得不介紹它有點對不起良心。

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