了解中文分词编程技术
7212字,阅读需时25分钟

中文和英文不同,英文通常采用空格和标点符号将词隔开,具有天然的分隔符,对英文文本进行相似性分析时,词的获取非常简单。中文虽然句子之间有分隔符,但词与词之间没有分隔符,需要编写专门的分词程序,拆分句子获取单词。

对中文句子进行分词时,有不同的切分方案,对于搜索来说,经常需要同时输出多种粒度的切分结果。例如“世界卫生组织会议”,最粗的切分结果是:“世界卫生组织会议”,稍微细一些的切分结果是“世界/卫生组织/会议”,更细的切分结果是:“世界/卫生/组织/会议”。

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

若使用Python编写相似性分析程序,一般使用列表来存储切分好的词,单个分词可以使用类来描述。

分词的存储结构:

# 分词结构
class SegmentWord:
    def __init__(self,word,wordAtt,index):
        # 分词
        self.word = word
        # 分词词性
        self.wordAtt = wordAtt
        # 分词在文本中的位置
        self.index = index

最常见的分词方法是基于词典匹配,词典存储已经确定的中文单词,对中文语句进行分词时,从语句的指定位置向后查找在词典中匹配的最长词,也就是最大长度查找方法。例如:词典中包括8个词语:{大,大学,大学生,生活,中,中心,心}。句子“大学生活动中心落成了”从位置0开始最长的词是“大学生”。有时候,也需要找出待切分句子指定位置开始所有的词。例如,句子“大学生活动中心落成了”从位置0开始所有的词是{大,大学,大学生}。

中文分词实际使用的词典规模往往在几十万词以上,采用逐个匹配词典中的词,其查找效率是非常低下的,为了保证分词效率,需要选择一个好的查找词典算法。

词典存储结构一般采用Trie树。Trie树通常又称为字典树、单词查找树或前缀树,是一种用于快速检索的多叉树结构。如下图所示:

图片7.png

上图是存储了三个词的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等,这些分词程序都是开源软件,在许可协议下,可以免费使用这些分词程序。

我要评论
全部评论