瞭解中文分詞編程技術
中文和英文不同,英文通常採用空格和標點符號將詞隔開,具有天然的分隔符,對英文文本進行相似性分析時,詞的獲取非常簡單。中文雖然句子之間有分隔符,但詞與詞之間沒有分隔符,需要編寫專門的分詞程序,拆分句子獲取單詞。
對中文句子進行分詞時,有不同的切分方案,對於搜索來說,經常需要同時輸出多種粒度的切分結果。例如 “世界衛生組織會議”,最粗的切分結果是:“世界衛生組織會議”,稍微細一些的切分結果是 “世界 / 衛生組織 / 會議”,更細的切分結果是:“世界 / 衛生 / 組織 / 會議”。
分詞除了返回詞本身,還可以返回詞所在的位置,詞性信息。
若使用 Python 編寫相似性分析程序,一般使用列表來存儲切分好的詞,單個分詞可以使用類來描述。
分詞的存儲結構:
# 分詞結構
class SegmentWord:
def __init__(self,word,wordAtt,index):
# 分詞
self.word = word
# 分詞詞性
self.wordAtt = wordAtt
# 分詞在文本中的位置
self.index = index
最常見的分詞方法是基於詞典匹配,詞典存儲已經確定的中文單詞,對中文語句進行分詞時,從語句的指定位置向後查找在詞典中匹配的最長詞,也就是最大長度查找方法。例如:詞典中包括 8 個詞語:{大,大學,大學生,生活,中,中心,心}。句子 “大學生活動中心落成了” 從位置 0 開始最長的詞是 “大學生”。有時候,也需要找出待切分句子指定位置開始所有的詞。例如,句子“大學生活動中心落成了” 從位置 0 開始所有的詞是{大,大學,大學生}。
中文分詞實際使用的詞典規模往往在幾十萬詞以上,採用逐個匹配詞典中的詞,其查找效率是非常低下的,爲了保證分詞效率,需要選擇一個好的查找詞典算法。
詞典存儲結構一般採用 Trie 樹。Trie 樹通常又稱爲字典樹、單詞查找樹或前綴樹,是一種用於快速檢索的多叉樹結構。如下圖所示:
上圖是存儲了三個詞的 Trie 樹,Trie 樹的一個節點只保留一個字符,如果一個單詞比一個字符長,則包含第一個字符的節點記錄指向下一個字符的節點的屬性,以此類推,構成一個層次結構的樹,樹的第一層包括所有單詞的第一個字符,樹的第二層包括所有單詞的第二個字符,第三層包括所有單詞的……。Trie 樹的最大高度是詞典中最長單詞的長度。
使用 Python 實現 Trie 樹,需要一個樹類和一個節點類,然後用逐個增加單詞到 Trie 樹的方法構建詞典樹。
節點類代碼:
# 節點類
class TrieNode(object):
def __init__(self):
# 字典的key爲字符
# 字典的value爲TrieNode
self.child = {}
# 標誌位,爲1表示單詞結束
self.flag = None
Trie樹類
# 樹類
class Trie(object):
def __init__(self):
self.root = TrieNode()
# 添加單詞
def add(self, words):
curNode = self.root
for word in words:
if curNode.child.get(word) is None:
nextNode = TrieNode()
curNode.child[word] = nextNode
curNode = curNode.child[word]
curNode.flag = 1
# 精準查找
def search_exact(self, words):
curNode = self.root
for word in words:
if curNode.child.get(word) is None:
return False
else:
print(word)
curNode = curNode.child[word]
if curNode.flag == 1:
return True
# 模糊查找
def search_fuzzy(self, sentence):
curNode = self.root
for word in sentence:
if curNode.child.get(word) is None:
return False
else:
print(word)
if curNode.child[word].flag == 1:
return True
else:
curNode = curNode.child[word]
return True
# 輸出單詞
def print_words(self):
rootnode = self.root
for key in rootnode.child.keys():
print(key)
self.print_sub_words(rootnode.child[key])
# 輸出子節點字符
def print_sub_words(self,node):
for key in node.child.keys():
print(key)
self.print_sub_words(node.child[key])
構建 Trie 詞典樹,需要讀取詞典文件,將詞典文件的所有單詞添加到 Trie 詞典樹,詞典文件存儲格式一般是可方便查看和編輯的文本文件格式,最基本的詞典的文本文件格式就是每行一個詞,從文本文件讀入詞典的代碼如下:
# 讀取詞典文件
def read_text(trie):
filename = "sample.txt"
try:
#使用r模式打開文本文件
fp = open(filename,"r",encoding='utf-8')
print("%s 文件打開成功" % filename)
done = False
#使用while循環讀取文本文件所有行數
while not done:
#按行讀取文件內容
aline = fp.readline()
if(aline != ""):
# 過濾換行符
words = aline.replace('\r','').replace('\n','').replace('\t','')
# 添加單詞到Trie樹
trie.add(words)
else:
done = True
#關閉文件對象
fp.close()
except IOError:
print("文件打開失敗,%s文件不存在" % filename)
sample.txt 和 Python 程序在同一個目錄,sample.txt 存儲的單詞如下:
大
大學
大學生
活動
生活
中
中心
心
存儲單詞的文本文件也稱爲詞表,分詞程序分出來的詞都來自詞表,詞典就是現成的詞表,也可以從加工後的語料庫得到詞表,例如 “人民日報語料庫”,中文分詞最簡單的方法是:直接匹配詞表,返回詞表中最長的詞。
正向最大長度匹配法就是從詞表中,查找和待匹配串前綴最長匹配的詞,如果找到匹配詞,則把這個詞作爲切分詞,待匹配串減去該詞,如果詞典沒有匹配上,就按單字切分。
例如:Trie 詞典樹包括如下的 8 個詞語:
大 大學 大學生 活動 生活 中 中心 心
句子 “大學生活動中心”,首先會匹配開始的最長詞“大學生”,然後再匹配“活動” 嗎,最後匹配“中心”。
正向最大匹配算法代碼如下:
def forward_max_matching(sentence,trie):
words = []
# 句子的字符數
sentence_len = len(sentence)
if sentence_len <= 0:
return
# 起始索引
start = 0
# 終止索引
end = 1
temp = ""
while True:
# 提取分詞
word = sentence[start:end]
# 若當前分詞匹配成功,添加字符繼續匹配
if trie.search_exact(word):
end += 1
temp = word
if end >= sentence_len:
words.append(temp)
break
else:
# 若temp不爲空,添加分詞到words
if len(temp) > 0:
words.append(temp)
start += len(temp)
temp = ""
# 終止索引加1
end += 1
if end > sentence_len:
break
return words
上述代碼是簡單的分詞程序,實際的分詞程序要複雜的多。在編寫相似性分析、機器翻譯等自然語言處理程序時,我們並不需要專門編寫分詞程序。
Python 分詞工具很多,包括盤古分詞、Yaha 分詞、Jieba 分詞、清華 THULAC 等,這些分詞程序都是開源軟件,在許可協議下,可以免費使用這些分詞程序。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/e1aXH3CrWbJrdErok1x31Q