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