分词学习(1)--正向最大匹配分词

汉字分词最简单的就是正向最大匹配分词了,其基本原理很简单,而且经常作为笔试题

     该算法主要分两个步骤:

1 一般从一个字符串的开始位置,选择一个最大长度的词长的片段,如果序列不足最大词长,则选择全部序列。

2 首先看该片段是否在词典中,如果是,则算为一个分出来的词,如果不是,则从右边开始,减少一个字符,然后看短一点的这个片段是否在词典中,依次循环,逐到只剩下一个字。

3 序列变为第2步骤截取分词后,剩下的部分序列。


      然后就开始写代码了,这里使用英文进行分词,如“itisatest”,需要分为“it is a test”,词典文件为count_1w.txt,数据来源于beautiful data(数据之美)这本书,附件中有。

之所以经常会面试写这个,主要是这个是个典型的应用分治思路的题目,只要分清楚了,代码其实非常简单易懂,如果几个步骤混在一起考虑,就非常容易糊涂。

对于这样稍微有点逻辑算法的题目,一般是思路就是case方法,就是用一个例子,看下算法怎么跑,然后再分析终止条件和一些特殊情况。当然,标准的设计初始条件,找出循环不变式的方法也是可以的,但是太麻烦了。。。。。。。。。。,一般都是先把代码写出来了,再来证明代码的正确性@#¥#@¥

          画个简单的例子图,这样写就容易的多,人的思维很难同时思考很多情况很多步骤,case方法是最直接的:


      


这里就是注意,黑点的位置就是字母的索引下标,就是唯一字母的前一个空位,这样理解更清楚一些。特别是做分词,黑点就是所有可能的分割位置。

有了这个case,就是分别写两个步骤的处理函数,然后主函数设置初始条件,并循环,调用两个函数,并判断终止条件即可。

具体代码如下(python):


#!/usr/bin/env python
#coding=utf-8
#############################################################
#function: 最大正向分词
##############################################################
import sys 
import math

#global parameter
DELIMITER = " " #分词之后的分隔符

class MLSegment:
    def __init__(self):
        self.word_dict = {} #记录单词
        self.gmax_word_length = 0 #单词的最大长度

    #功能:从sequence中选取一个片段,
    #      如果sequence长度大于最大词长,则从头取最大词长的片段
    #      否则,则取整个sequence作为片段
    #输入:sequence 需要处理的序列
    #返回:(选择的片段,剩下的片段)
    def get_segment(self, sequence):
        if len(sequence) > self.gmax_word_length:
            return (sequence[0:self.gmax_word_length], \
                    sequence[self.gmax_word_length:])
        else:
            return (sequence,"")

    #功能:从sequence选择一个最长的词
    #      基本方法,先取整个sequence,看是否是词,如果不是,依次从右边
    #      减少字母,看缩短的片段是否是词,如果减少到只剩下一个字母,则直接
    #      作为一个词
    #返回:(词,剩下的片段)
    def select_max_length_word(self, sequence):
        for length in range(len(sequence),1,-1):
            word = sequence[0:length]
            if self.word_dict.has_key(word):
                return (word, sequence[length:] )

        #至少分出一个字母
        return (sequence[0:1],sequence[1:])

    #最大长度分词
    def ml_seg(self, sequence):
        sequence = sequence.strip()
        #初始化
        segment_start_pos = 0

        leave_segment = sequence
        word_list = []
        #开始循环
        while True:
            #step 1,获取一段片段,用来找词
            (select_segment, leave_segment) = self.get_segment(leave_segment)

            #step 2, 从选择的片段中获得可能的最长的词,以及剩余的部分片段
            (word,word_right) = self.select_max_length_word(select_segment)
            word_list.append(word)

            #step 3,将剩余的词片段与剩下的片段组合,重新进入下一轮循环
            leave_segment = word_right + leave_segment
            #如果剩下的部分为空,退出循环
            if leave_segment == "":
                break

        return DELIMITER.join(word_list)

    #加载词典,为词\t词频的格式
    def initial_dict(self,filename):
        dict_file = open(filename, "r")
        for line in dict_file:
            sequence = line.strip()
            key = sequence.split('\t')[0]
            self.word_dict[key] = 1
        #获取最大词长
        self.gmax_word_length = max(len(key) for key in self.word_dict.iterkeys())

#测试
if __name__=='__main__':
    myseg = MLSegment()
    myseg.initial_dict("count_1w.txt")
    sequence = "itisatest"
    seg_sequence = myseg.ml_seg(sequence)
    print "original sequence: " + sequence
    print "segment result: " + seg_sequence
                                               

这个是完全按照上面的思路写的,当然,因为截取片段的时候,其实只要记录截取的起始位置即可,然后逐步更新这个变量就行,不用记录所有的片段字符串,因此可以写一个改进方法,代码比上面的更清楚,但是理解起来会稍微麻烦一些。写代码一般都是从基础思路写起,然后慢慢改进的,不能一下就找出最优的写法:

#!/usr/bin/env python
#coding=utf-8
#############################################################
#function: 正向最大分词
##############################################################
import sys
import math

#global parameter
DELIMITER = " " #分词之后的分隔符

class MLSegment:
    def __init__(self):
        self.word_dict = {} #记录单词
        self.gmax_word_length = 0 #单词的最大长度

    #最大长度分词
    def ml_seg(self, sequence):
        sequence = sequence.strip()
        #初始化
        segment_start_pos = 0 #开始
        word_list = []

        #循环体
        while True:
            #step 1,确定候选片段的最大长度
            if  len(sequence) - segment_start_pos >  self.gmax_word_length :
                max_segment_length = self.gmax_word_length
            else:
                max_segment_length =  len(sequence) - segment_start_pos

            #step 2,从候选片段中选择候选词
            for word_length in range(max_segment_length, 0, -1):
                word = sequence[segment_start_pos:segment_start_pos+word_length]
                if self.word_dict.has_key(word):
                    word_list.append(word)
                    break
                if 1 == word_length and not self.word_dict.has_key(word):
                    word_list.append(word)

            #候选片段位置前移
            segment_start_pos += word_length

            #如果移动到了末尾位置,则退出
            if segment_start_pos == len(sequence):
                break

        return DELIMITER.join(word_list)

    #加载词典,为词\t词频的格式
    def initial_dict(self,filename):
        dict_file = open(filename, "r")
        for line in dict_file:
            sequence = line.strip()
            key = sequence.split('\t')[0]
            self.word_dict[key] = 1
        #获取最大词长
        self.gmax_word_length = max(len(key) for key in self.word_dict.iterkeys())

#测试
if __name__=='__main__':
    myseg = MLSegment()
    myseg.initial_dict("count_1w.txt")
    sequence = "itisatest"
    seg_sequence = myseg.ml_seg(sequence)
    print "original sequence: " + sequence
    print "segment result: " + seg_sequence

当然,还可以进一步的写递归的形式,会更简洁,但递归基本都是奇淫技巧,实际项目很少用到。


对"itisatest", 利用正向最大匹配,分割出来的是“itis atest”, 现在不符合需求,但是一个最基本的分词方法,在汉语分词上也有不错的效果,一般都作为分词算法的baseline。

后面一节就介绍最大概率分词,就可以解决这个问题。


字典文件和代码baidupan下载:http://pan.baidu.com/s/1dDikxg9


猜你喜欢

转载自blog.csdn.net/wangliang_f/article/details/17527915