瞭解中文分詞編程技術

中文和英文不同,英文通常採用空格和標點符號將詞隔開,具有天然的分隔符,對英文文本進行相似性分析時,詞的獲取非常簡單。中文雖然句子之間有分隔符,但詞與詞之間沒有分隔符,需要編寫專門的分詞程序,拆分句子獲取單詞。

對中文句子進行分詞時,有不同的切分方案,對於搜索來說,經常需要同時輸出多種粒度的切分結果。例如 “世界衛生組織會議”,最粗的切分結果是:“世界衛生組織會議”,稍微細一些的切分結果是 “世界 / 衛生組織 / 會議”,更細的切分結果是:“世界 / 衛生 / 組織 / 會議”。

分詞除了返回詞本身,還可以返回詞所在的位置,詞性信息。

若使用 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