以我的理解,最簡單的分詞程式,應該是先將中文文本切成最小的單位--漢字--再從詞典裡找詞,將這些字按照最左最長原則(與正則精神暗合),合并為以詞為單位的集合。這樣的應該是最快的,只按照給定的資料劃分合并即可,不必考慮文法元素的權重(詞性:名動形數量代等等,文法:主謂賓定狀補),以及內容相關的出現次數。
關於源文本的切分,就參照《統計漢字/英文單詞數》一文的思路,使用Regexr"(?x) (?: [w-]+ | [x80-xff]{3} )")來匹配即可。
關於詞典,我使用的是CC-CEDICT的詞典,原因有三:沒有著作權問題;速度較快;Chrome也在用它(發現了吧:在Chrome上雙擊中文句子,會自動選擇中文詞彙而不是單字或整行進行反選高亮)。
接下來是如何分詞。經過思考,我發現搜尋樹的原理可以拿來就用。原理請見此文:Trie in Python。具體方法是,將詞庫逐字讀入記憶體,建立搜尋樹;然後對目標文本進行逐字分析,如果該字之後還可搜尋,則繼續搜尋;否則停止,作為一個詞彙單位處理。
這樣的演算法理論上比較快(未進行benchmark),原因有三:使用Trie結構,本質上是雜湊表,空間換時間,是O(0)級的搜尋;詞庫只有800K,可以輕易載入,記憶體空間沒佔多少;演算法最慢的部分是載入Trie的階段,之後速度就不再受影響。
不過,談到它的擴充性,目前只能在words.txt中手動添加新詞,而不能實現機器學習。
源碼
完整的程式(包括我處理過的詞庫列表)放在github上了。有興趣的可以把玩一下。這裡列出主程式:
代碼如下 |
複製代碼 |
#!/usr/bin/python # -*- coding: utf-8 -*- # #author: rex #blog: http://iregex.org #filename nlp.py #created: 2010-09-26 19:15 import re import sys regex=re.compile(r"(?x) (?: [w-]+ | [x80-xff]{3} )") def init_wordslist(fn="./words.txt"): f=open(fn) lines=sorted(f.readlines()) f.close() return lines def words_2_trie(wordslist): d={} for word in wordslist: ref=d chars=regex.findall(word) for char in chars: ref[char]=ref.has_key(char) and ref[char] or {} ref=ref[char] ref['']=1 return d def search_in_trie(chars, trie): ref=trie index=0 for char in chars: if ref.has_key(char): print char, ref=ref[char] index+=1 else: if index==0: index=1 print char, print '*', try: chars=chars[index:] search_in_trie(chars, trie) except: pass break def main(): #init words=init_wordslist() trie=words_2_trie(words) #read content fn=sys.argv[1] string=open(fn).read() chars=regex.findall(string) #do the job search_in_trie(chars, trie) if __name__=='__main__': main() |
本機測試
測試的文本如下:
只聽得一個女子低低應了一聲。綠竹翁道:“姑姑請看,這部琴譜可有些古怪。”那
女子又嗯了一聲,琴音響起,調了調弦,停了一會,似是在將斷了的琴弦換去,又調了調
弦,便奏了起來。初時所奏和綠竹翁相同,到後來越轉越高,那琴韻竟然履險如夷,舉重
若輕,毫不費力的便轉了上去。令狐沖又驚又喜,依稀記得便是那天晚上所聽到曲洋所奏
的琴韻。這一曲時而慷慨激昂,時而溫柔雅緻,令狐沖雖不明樂理,但覺這位婆婆所奏,
和曲洋所奏的曲調雖同,意趣卻大有差別。這婆婆所奏的曲調平和中正,令人聽著只覺音
樂之美,卻無曲洋所奏熱血如沸的激奮。奏了良久,琴韻漸緩,似乎樂音在不住遠去,倒
像奏琴之人走出了數十丈之遙,又走到數裡之外,細微幾不可再聞。
理性愛國
性愛體驗
我愛Regex
請留意末尾三行。
再看一下程式處理的結果:(*表示詞彙間的分隔)
1
只 * 聽 得 * 一 個 * 女 子 * 低 低 * 應 * 了 * 一 聲 * 。 * 綠 * 竹 * 翁 * 道 * : * “ * 姑 姑 * 請 看 * , * 這 * 部 * 琴 * 譜 * 可 有 * 些 * 古 怪 * 。 * ” * 那 * 女 子 * 又 * 嗯 * 了 * 一 聲 * , * 琴 * 音 響 * 起 * , * 調 * 了 * 調 * 弦 * , * 停 * 了 * 一 會 * , * 似 是 * 在 * 將 * 斷 * 了 * 的 * 琴 弦 * 換 * 去 * , * 又 * 調 * 了 * 調 * 弦 * , * 便 * 奏 * 了 * 起 來 * 。 * 初 * 時 * 所 * 奏 * 和 * 綠 * 竹 * 翁 * 相 同 * , * 到 * 後 來 * 越 * 轉 * 越 * 高 * , * 那 * 琴 * 韻 * 竟 然 * 履 險 如 夷 * , * 舉 重 * 若 * 輕 * , * 毫 不 費 力 * 的 * 便 * 轉 * 了 * 上 去 * 。 * 令 狐 * 沖 * 又 * 驚 * 又 * 喜 * , * 依 稀 * 記 得 * 便 是 * 那 天 * 晚 上 * 所 * 聽 到 * 曲 * 洋 * 所 * 奏 * 的 * 琴 * 韻 * 。 * 這 一 * 曲 * 時 而 * 慷 慨 * 激 昂 * , * 時 而 * 溫 柔 * 雅 致 * , * 令 狐 * 沖 * 雖 * 不 明 * 樂 理 * , * 但 * 覺 * 這 位 * 婆 婆 * 所 * 奏 * , * 和 * 曲 * 洋 * 所 * 奏 * 的 * 曲 調 * 雖 * 同 * , * 意 趣 * 卻 * 大 有 * 差 別 * 。 * 這 * 婆 婆 * 所 * 奏 * 的 * 曲 調 * 平 和 * 中 正 * , * 令 人 * 聽 * 著 * 只 * 覺 * 音 樂 之 * 美 * , * 卻 * 無 * 曲 * 洋 * 所 * 奏 * 熱 血 * 如 * 沸 * 的 * 激 * 奮 * 。 * 奏 * 了 * 良 久 * , * 琴 * 韻 * 漸 * 緩 * , * 似 乎 * 樂 音 * 在 * 不 住 * 遠 * 去 * , * 倒 像 * 奏 * 琴 * 之 * 人 * 走 出 * 了 * 數 十 * 丈 * 之 * 遙 * , * 又 * 走 * 到 * 數 * 裡 * 之 外 * , * 細 微 * 幾 * 不 可 再 * 聞 * 。 * 理 性 * 愛 國 * 性 愛 * 體 驗 * 我 * 愛 * 正 則 * 表 達 式
1,實用,能滿足絕大部分網路文章的分詞需要。
2,快速,分詞過程中不會拋出DeadlineExceededError錯誤。
3,低記憶體佔用,不會因為記憶體佔用超過限制而每個執行個體運行一次之後就被強制kill掉。
最初的思路是:將分詞詞庫排序好儲存在一個list對象裡,然後用bisect庫對詞庫進行快速尋找。因為bisect預設是c實現的,所以匹配速度非常快,但是list對象儲存的詞庫過於耗費記憶體,載入速度非常慢。完全不適合在google app engine上使用。
解決的辦法是:把詞庫中不同長度的詞分開儲存在不用的str對象中,使用跟bisect庫同樣的二分法對詞庫進行匹配。
.新版論壇系列介紹之二——功能介紹篇 公告:CSDN部落格頻道部落格搬家功能上線! JavaEE快速開發平台G4Studio作者熊春專訪
中國最大規模移動開發人員高水平盛會 沒有重量只有品質:iPad版《程式員雜誌》應用上線 “第一次親密接觸”——有獎徵文活動
python 中文分詞——FMM 演算法 .
分類: 各種指令碼包括(python) 資料結構&演算法 2009-06-23 12:04 2842人閱讀 評論(2) 收藏 舉報
FMM演算法的最簡單思想是使用貪心演算法向前找n個,如果這n個組成的詞在詞典中出現,就ok,如果沒有出現,那麼找n-1個...然後繼續下去。假如n個詞在詞典中出現,那麼從n+1位置繼續找下去,知道句子結束。
代碼如下 |
複製代碼 |
.import re def PreProcess(sentence,edcode="utf-8"): sentence = sentence.decode(edcode) sentence=re.sub(u"[。,,!……!《》<>/"'::?/?、/|“”‘’;]"," ",sentence) return sentence
.def FMM(sentence,diction,result = [],maxwordLength = 4,edcode="utf-8"): . i = 0 sentence = PreProcess(sentence,edcode) length = len(sentence) while i < length: # find the ascii word tempi=i tok=sentence[i:i+1] while re.search("[0-9A-Za-z/-/+#@_/.]{1}",tok)<>None: i= i+1 tok=sentence[i:i+1] if i-tempi>0: result.append(sentence[tempi:i].lower().encode(edcode)) # find chinese word left = len(sentence[i:]) if left == 1: """go to 4 step over the FMM""" """should we add the last one? Yes, if not blank""" if sentence[i:] <> " ": result.append(sentence[i:].encode(edcode)) return result m = min(left,maxwordLength) for j in xrange(m,0,-1): leftword = sentence[i:j+i].encode(edcode) # print leftword.decode(edcode) if LookUp(leftword,diction): # find the left word in dictionary # it's the right one i = j+i result.append(leftword) break elif j == 1: """only one word, add into result, if not blank""" if leftword.decode(edcode) <> " ": result.append(leftword) i = i+1 else: continue return result def LookUp(word,dictionary): if dictionary.has_key(word): return True return False def ConvertGBKtoUTF(sentence): return sentence.decode('gbk').encode('utf-8') import re def PreProcess(sentence,edcode="utf-8"): sentence = sentence.decode(edcode) sentence=re.sub(u"[。,,!……!《》<>/"'::?/?、/|“”‘’;]"," ",sentence) return sentence def FMM(sentence,diction,result = [],maxwordLength = 4,edcode="utf-8"): i = 0 sentence = PreProcess(sentence,edcode) length = len(sentence) while i < length: # find the ascii word tempi=i tok=sentence[i:i+1] while re.search("[0-9A-Za-z/-/+#@_/.]{1}",tok)<>None: i= i+1 tok=sentence[i:i+1] if i-tempi>0: result.append(sentence[tempi:i].lower().encode(edcode)) # find chinese word left = len(sentence[i:]) if left == 1: """go to 4 step over the FMM""" """should we add the last one? Yes, if not blank""" if sentence[i:] <> " ": result.append(sentence[i:].encode(edcode)) return result m = min(left,maxwordLength) for j in xrange(m,0,-1): leftword = sentence[i:j+i].encode(edcode) # print leftword.decode(edcode) if LookUp(leftword,diction): # find the left word in dictionary # it's the right one i = j+i result.append(leftword) break elif j == 1: """only one word, add into result, if not blank""" if leftword.decode(edcode) <> " ": result.append(leftword) i = i+1 else: continue return result def LookUp(word,dictionary): if dictionary.has_key(word): return True return False def ConvertGBKtoUTF(sentence): return sentence.decode('gbk').encode('utf-8') |
測試代碼:
代碼如下 |
複製代碼 |
[c-sharp] view plaincopyprint? .dictions = {} .dictions["ab"] = 1 .dictions["cd"] = 2 .dictions["abc"] = 1 .dictions["ss"] = 1 .dictions[ConvertGBKtoUTF("好的")] = 1 .dictions[ConvertGBKtoUTF("真的")] = 1 .sentence = "asdfa好的是這樣嗎vasdiw呀真的daf dasfiw asid是嗎?" .s = FMM(ConvertGBKtoUTF(sentence),dictions) .for i in s: . print i.decode("utf-8") dictions = {} dictions["ab"] = 1 dictions["cd"] = 2 dictions["abc"] = 1 dictions["ss"] = 1 dictions[ConvertGBKtoUTF("好的")] = 1 dictions[ConvertGBKtoUTF("真的")] = 1 sentence = "asdfa好的是這樣嗎vasdiw呀真的daf dasfiw asid是嗎?" s = FMM(ConvertGBKtoUTF(sentence),dictions) for i in s: print i.decode("utf-8") 文本測試代碼: [c-sharp] view plaincopyprint? .test = open("test.txt","r") .for line in test: . s = FMM(CovertGBKtoUTF(line),dictions) . for i in s: . print i.decode("utf-8") |