1. 程式人生 > >Python自然語言處理—演算法基礎

Python自然語言處理—演算法基礎

本章主要介紹文字分析的演算法設計過程中會用到的一些技巧,我只把書中對我來說有意思的例子拿出來了。

一  遞迴

遞迴就是迴圈的一種,為了實現某種目的反覆呼叫自身。下面這個例子的有意思的地方不僅限於迭代,還用了yield,可以參考廖雪峰老師關於Yield的解釋https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/。為了讓大家理解yield,我添加了Print函式。

#  迭代
def permutations(seq):

    if len(seq) <= 1:
        print(seq)
        yield seq  # 如果序列長度為1,無需操作直接返回自身

    else:
        for perm in permutations(seq[1:]):  # 如果序列不為1,呼叫函式生成[1:]的所有排序方法
            for i in range(len(perm)+1):    # 將第一位數迴圈放到後幾位數的中間,生成多種排序組合
                print(perm[:i] + seq[0:1] + perm[i:])
                yield perm[:i] + seq[0:1] + perm[i:]

list(permutations(['a','b', 'c']))

結果如下

['c']
['b', 'c']
['a', 'b', 'c']
['b', 'a', 'c']
['b', 'c', 'a']
['c', 'b']
['a', 'c', 'b']
['c', 'a', 'b']
['c', 'b', 'a']

我們來分析一下整個程式如何執行的

第一步 開始permutations(['a','b', 'c']),因為seq長度為3所以 程式跑到了 for perm in permutations(seq[1:])這裡,開始第一次迴圈

第二步 開始permutations(['b', 'c']),因為seq長度為2所以 程式跑到了 程式跑到了 for perm in permutations(seq[1:])這裡,開始第一次迴圈

第三步 開始開始permutations(['c']),因為seq長度1,所以yield seq,同時列印了['c']

第四步 這裡yield出來的['c'],其實是第二步程式中需要的結果,所以返回第二步程式中,開始for perm in [['c']]

第五步 重點來了yield的效果開始體現,for i in range(2) 其實會有2個序列生成[['b', 'c'],['c', 'b']],但是yield是惰性的。程式只會跑出一個['b', 'c'],同時打印出['b', 'c']

第六步 開始第一步的程式,因為 perm現在等於['b', 'c']了,所以開始把a依次放入 bc前面,中間和後面。結果依次yield['a', 'b', 'c']['b', 'a', 'c']['b', 'c', 'a'],這裡yield也是惰性的,但是list(permutations(['a','b', 'c']))這個了,所以結果都出來了

第七步 開始for perm in permutations(seq[1:])這裡,開始第二次迴圈,又呼叫了permutations(['b', 'c']),這時候惰性的yield會從上一次結束的地方繼續執行,吐出第二結果['c', 'b']

第八步 把a 依次放入['c', 'b'] 前中後,吐出結果

如果把程式中的yield換成return,是會報錯的。

二  建立輔助的資料結構

有時候原始資料並不方便我們實現某一種功能,這時候不妨在原始資料基礎上建立一個輔助的資料結構。比如下面的例子,如果在大量文章中找出某些詞的上下文呢?不妨就先建立索引,儲存著每個詞出現過在哪些文章中。原書中是通過open函式直接開打zip資料夾中的大量電影評論資料,但是我python3不行我又懶得解決,所以我直接把zip中的檔案都解壓出來了,這樣直接open就沒問題了。

# 建立輔助資料結構
def raw(file):  #開啟檔案去除符號

    contents = open(path + r'\\' + file, 'r').read()

    contents = re.sub(r'<.*?>', ' ', contents)

    contents = re.sub('\s+', ' ', contents)

    return contents

 

def snippet(doc, term): # 列印結果

    text = ' '*30 + raw(doc) + ' '*30  # 防止文字過短列印報錯

    pos = text.index(term)  # 獲取term的第一個index

    return text[pos-30:pos+30]  # 列印上下文

 

print ("Building Index...")

import os
path = r'C:\Users\BF\AppData\Roaming\nltk_data\corpora\movie_reviews\neg'  
files = os.listdir(path)  # 獲取該檔案下的檔名
# files = nltk.corpus.movie_reviews.abspaths()


idx = nltk.Index((w, f) for f in files for w in raw(f).split())  # 建立輔助資料結構,結果就是字典每個詞屬於哪些文章

 
#  這裡是根據你輸入的詞實時列印結果
query = ''

while query != "quit":  # 輸入quit則退出當前程式

    query = input("query> ")  # 捕捉你當前獲取的詞

    if query in idx:  #首先判斷這個詞是不是當前文字中包含的

        for doc in idx[query]:  # 找出這個詞所在的文章

            print (snippet(doc, query))  # 列印這個詞上下文

    else:

        print ("Not found")

 

循行程式後就可以在開始輸入你想查詢的詞了。

三  動態規劃

用於解決多個重疊子問題的問題,解決的核心思想就是不能重複的計算每個子問題,而是應該將子問題的結果儲存在一個查詢表裡。有兩種音節SL,S佔1個長度,L佔2個長度,問有多少組合方式能組成20個長度。

# 硬遞迴,不做任何過程資料的儲存,最慢
def virahanka1(n):

    if n == 0:

        return [""]

    elif n == 1:

        return ["S"]

    else:

        s = ["S" + prosody for prosody in virahanka1(n-1)]

        l = ["L" + prosody for prosody in virahanka1(n-2)]

        return s + l

 
# 將過程資料apend到一個list中
def virahanka2(n):

    lookup = [[""], ["S"]]

    for i in range(n-1):

        s = ["S" + prosody for prosody in lookup[i+1]]

        l = ["L" + prosody for prosody in lookup[i]]

        lookup.append(s + l)

    return lookup[n]

 
# 將過程資料儲存到一個字典中
def virahanka3(n, lookup={0:[""], 1:["S"]}):

    if n not in lookup:

        s = ["S" + prosody for prosody in virahanka3(n-1)]

        l = ["L" + prosody for prosody in virahanka3(n-2)]

        lookup[n] = s + l

    return lookup[n]

 

from nltk import memoize
# 使用裝飾器將函式的過程資料儲存下來
@memoize  # 裝飾器用於儲存迭代過程中產生的值

def virahanka4(n):

    if n == 0:

        return [""]

    elif n == 1:

        return ["S"]

    else:

        s = ["S" + prosody for prosody in virahanka4(n-1)]

        l = ["L" + prosody for prosody in virahanka4(n-2)]

        return s + l


 

virahanka1(10)


virahanka2(10)


virahanka3(10)


virahanka4(20)

程式碼中涉及到一個裝飾器的概念,本章呼叫的裝飾器是儲存中間結果,還有各式各樣的裝飾器。裝飾器可以理解成

這樣memoize (virahanka4(20)),簡單把他想成一個函式或者一個類,具體如何寫一個裝飾器可以參考https://blog.csdn.net/xiangxianghehe/article/details/77170585