Python实现mmseg分词算法和吐嘈

原创 2012年03月24日 16:43:47

前两天一直在写爬虫,也是一直有心写个简单的搜索引擎吧,写出来肯定是没有Web界面的,本人不知道怎么地,对Web的代码一直有抵触心理。

搜索引擎嘛,我想要写出来必须要懂五个部分:

1、基础的多线程爬虫

2、分词算法,对用户输入的查找文本进行切割

3、MapReduce,用来统计词语出现的次数,这个直接关系到URL在队列中的等级

4、PageRank,用来给网页排名

5、Web界面

上述的五个部分,打算全部用自己的代码实现,不用任何第三方类库,用Python语言实现

爬虫大家都会写,这里就不说了,待会把自己花了1个点编码+调试的优先队列放出来吧,好久没写算法了,写个最大顶堆都花了老久。

下面继续说分词吧,咱是白手学习,啥也不会,就去Google了一下,先学了个正向最大匹配算法

啥正向最大匹配啊,说白了,就是拿着你的字典然后对文本进行暴力切割,这种算法切割出来的错误率肯定超级高,算法原理满大街都是,就不说了,时间复杂度倒挺低O(n)。

接着就换算法,学习了个也比较简单的mmseg算法,这是台湾同胞提出来的,其实就是对正向最大匹配算法进行了优化,这里的优化不是指优化时间复杂度,是优化降低错误率的,但是也会出现很奇葩的错误,让人看了都蛋疼。

算法的原文:http://technology.chtsai.org/mmseg/,中文有翻译的。

其实就是用了四个判断的法则:

这些我也就不去复制粘贴了,算法的说明也是满大街都是

简单给个博客:http://www.byywee.com/page/M0/S602/602088.html

斯坦福大学有个具体的实现包:http://nlp.stanford.edu/software/segmenter.shtml


这里举个正向最大匹配和mmseg算法对同样的文本切割后的效果吧:

1、南京市长江大桥欢迎您

正向最大匹配:南京/市长/江/大桥/欢迎您

mmseg:南京市/长江大桥/欢迎您

2、研究生命科学

正向最大匹配:研究生/命/科学

mmseg:研究/生命/科学

3、长春市长春药店

正向最大匹配:长春/市长/春药/店

mmseg:长春市/长春/药店

4、台湾国中学生

正向最大匹配:台湾国/中学生

mmseg:台湾/国中/学生

当然这些匹配的结果都取决于您的字典


大家在看了上面的博客,理解了什么是mmseg算法再往下看

具体Python代码如下:PyMmseg.py

此代码参照google code的一个代码,经过了我自己的修改和自己去其的理解

代码结构说明:

Word类就相当于C语言中的结构体,用来存单词和词频的

Chunk类是用来实现具体的切割判断方法的前期预处理计算的

ComplexCompare类是用来具体实现mmseg算法的四种评估方法的

有几个全局变量和全局函数是用来加载字典的,用全局是为了不让字典多次的加载

Analysis类是用来具体实现切割算法的

代码里的注释已经比较清楚了,在理解正向最大匹配算法的基础上再理解mmseg算法的原理就应该很容易明白下面的源码,具体不清楚的可以留言。

class Word:
    def __init__(self,text = '',freq = 0):
        self.text = text
        self.freq = freq
        self.length = len(text)

class Chunk:
    def __init__(self,w1,w2 = None,w3 = None):
        self.words = []
        self.words.append(w1)
        if w2:
            self.words.append(w2)
        if w3:
            self.words.append(w3)
    
	#计算chunk的总长度
    def totalWordLength(self):
        length = 0
        for word in self.words:
            length += len(word.text)
        return length
    
	#计算平均长度
    def averageWordLength(self):
        return float(self.totalWordLength()) / float(len(self.words))
    
	#计算标准差
    def standardDeviation(self):
        average = self.averageWordLength()
        sum = 0.0
        for word in self.words:
            tmp = (len(word.text) - average)
            sum += float(tmp) * float(tmp)
        return sum
    
	#自由语素度
    def wordFrequency(self):
        sum = 0
        for word in self.words:
            sum += word.freq
        return sum

class ComplexCompare:
    
    def takeHightest(self,chunks,comparator):
        i = 1
        for j in range(1, len(chunks)):
            rlt = comparator(chunks[j], chunks[0])
            if rlt > 0:
                i = 0
            if rlt >= 0:
                chunks[i], chunks[j] = chunks[j], chunks[i]
                i += 1
        return chunks[0:i]
    
	#以下四个函数是mmseg算法的四种过滤原则,核心算法
    def mmFilter(self,chunks):
        def comparator(a,b):
            return a.totalWordLength() - b.totalWordLength()
        return self.takeHightest(chunks,comparator)
    
    def lawlFilter(self,chunks):
        def comparator(a,b):
            return a.averageWordLength() - b.averageWordLength()
        return self.takeHightest(chunks,comparator)
    
    def svmlFilter(self,chunks):
        def comparator(a,b):
            return b.standardDeviation() - a.standardDeviation()
        return self.takeHightest(chunks, comparator)
    
    def logFreqFilter(self,chunks):
        def comparator(a,b):
            return a.wordFrequency() - b.wordFrequency()
        return self.takeHightest(chunks, comparator)
 
 
#加载词组字典和字符字典
dictWord = {}
maxWordLength = 0
    
def loadDictChars(filepath):
    global maxWordLength
    fsock = file(filepath)
    for line in fsock.readlines():
        freq, word = line.split(' ')
        word = unicode(word.strip(), 'utf-8')
        dictWord[word] = (len(word), int(freq))
        maxWordLength = maxWordLength < len(word) and len(word) or maxWordLength
    fsock.close()
    
def loadDictWords(filepath):
    global maxWordLength
    fsock = file(filepath)
    for line in fsock.readlines():
        word = unicode(line.strip(), 'utf-8')
        dictWord[word] = (len(word), 0)
        maxWordLength = maxWordLength < len(word) and len(word) or maxWordLength
    fsock.close()

#判断该词word是否在字典dictWord中    
def getDictWord(word):
    result = dictWord.get(word)
    if result:
        return Word(word,result[1])
    return None
	
#开始加载字典
def run():
    from os.path import join, dirname
    loadDictChars(join(dirname(__file__), 'data', 'chars.dic'))
    loadDictWords(join(dirname(__file__), 'data', 'words.dic'))

class Analysis:
    
    def __init__(self,text):
        if isinstance(text,unicode):
            self.text = text
        else:
            self.text = text.encode('utf-8')
        self.cacheSize = 3
        self.pos = 0
        self.textLength = len(self.text)
        self.cache = []
        self.cacheIndex = 0
        self.complexCompare = ComplexCompare()
        
		#简单小技巧,用到个缓存,不知道具体有没有用处
        for i in range(self.cacheSize):
            self.cache.append([-1,Word()])
        
		#控制字典只加载一次
        if not dictWord:
            run()
    
    def __iter__(self):
        while True:
            token = self.getNextToken()
            if token == None:
                raise StopIteration
            yield token
            
    def getNextChar(self):
        return self.text[self.pos]
		
	#判断该字符是否是中文字符(不包括中文标点)	
    def isChineseChar(self,charater):
        return 0x4e00 <= ord(charater) < 0x9fa6
		
	#判断是否是ASCII码
    def isASCIIChar(self, ch):
        import string
        if ch in string.whitespace:
            return False
        if ch in string.punctuation:
            return False
        return ch in string.printable
    
	#得到下一个切割结果
    def getNextToken(self):
        while self.pos < self.textLength:
            if self.isChineseChar(self.getNextChar()):
                token = self.getChineseWords()
            else :
                token = self.getASCIIWords()+'/'
            if len(token) > 0:
                return token
        return None
    
	#切割出非中文词
    def getASCIIWords(self):
        # Skip pre-word whitespaces and punctuations
		#跳过中英文标点和空格
        while self.pos < self.textLength:
            ch = self.getNextChar()
            if self.isASCIIChar(ch) or self.isChineseChar(ch):
                break
            self.pos += 1
		#得到英文单词的起始位置	
        start = self.pos
        
		#找出英文单词的结束位置
        while self.pos < self.textLength:
            ch = self.getNextChar()
            if not self.isASCIIChar(ch):
                break
            self.pos += 1
        end = self.pos
        
        #Skip chinese word whitespaces and punctuations
		#跳过中英文标点和空格
        while self.pos < self.textLength:
            ch = self.getNextChar()
            if self.isASCIIChar(ch) or self.isChineseChar(ch):
                break
            self.pos += 1
			
		#返回英文单词
        return self.text[start:end]
    
	#切割出中文词,并且做处理,用上述4种方法
    def getChineseWords(self):
        chunks = self.createChunks()
        if len(chunks) > 1:
            chunks = self.complexCompare.mmFilter(chunks)
        if len(chunks) > 1:
            chunks = self.complexCompare.lawlFilter(chunks)
        if len(chunks) > 1:
            chunks = self.complexCompare.svmlFilter(chunks)
        if len(chunks) > 1:
            chunks = self.complexCompare.logFreqFilter(chunks)
        if len(chunks) == 0 :
            return ''
        
		#最后只有一种切割方法
        word = chunks[0].words
        token = ""
        length = 0
        for x in word:
            if x.length <> -1:
                token += x.text + "/"
                length += len(x.text)
        self.pos += length
        return token
    
	#三重循环来枚举切割方法,这里也可以运用递归来实现
    def createChunks(self):
        chunks = []
        originalPos = self.pos
        words1 = self.getMatchChineseWords()
        
        for word1 in words1:
            self.pos += len(word1.text)
            if self.pos < self.textLength:
                words2 = self.getMatchChineseWords()
                for word2 in words2:
                    self.pos += len(word2.text)
                    if self.pos < self.textLength:
                        words3 = self.getMatchChineseWords()
                        for word3 in words3:
                            print word3.length,word3.text
                            if word3.length == -1:
                                chunk = Chunk(word1,word2)
                                print "Ture"
                            else :
                                chunk = Chunk(word1,word2,word3)
                            chunks.append(chunk)
                    elif self.pos == self.textLength:
                        chunks.append(Chunk(word1,word2))
                    self.pos -= len(word2.text)
            elif self.pos == self.textLength:
                chunks.append(Chunk(word1))
            self.pos -= len(word1.text)
                                
        self.pos = originalPos
        return chunks
    
	#运用正向最大匹配算法结合字典来切割中文文本	
    def getMatchChineseWords(self):
        #use cache,check it 
        for i in range(self.cacheSize):
            if self.cache[i][0] == self.pos:
                return self.cache[i][1]
            
        originalPos = self.pos
        words = []
        index = 0
        while self.pos < self.textLength:
            if index >= maxWordLength :
                break
            if not self.isChineseChar(self.getNextChar()):
                break
            self.pos += 1
            index += 1
            
            text = self.text[originalPos:self.pos]
            word = getDictWord(text)
            if word:
                words.append(word)
                
        self.pos = originalPos
		#没有词则放置个‘X’,将文本长度标记为-1
        if not words:
            word = Word()
            word.length = -1
            word.text = 'X'
            words.append(word)
        
        self.cache[self.cacheIndex] = (self.pos,words)
        self.cacheIndex += 1
        if self.cacheIndex >= self.cacheSize:
            self.cacheIndex = 0
        return words

接下来给出测试代码:

#-*- coding: utf-8 -*-

from PyMmseg import Analysis

def cuttest(text):
    #cut =  Analysis(text)
    wlist = [word for word in Analysis(text)]
    tmp = ""
    for w in wlist:
        tmp += w
    print tmp
    print "================================"
        
if __name__=="__main__":
#    cuttest(u"研究生命来源")
#    cuttest(u"南京市长江大桥欢迎您")
#    cuttest(u"请把手抬高一点儿")
#    cuttest(u"长春市长春节致词。")
#    cuttest(u"长春市长春药店。")
#    cuttest(u"我的和服务必在明天做好。")
#    cuttest(u"我发现有很多人喜欢他。")
#    cuttest(u"我喜欢看电视剧大长今。")
#    cuttest(u"半夜给拎起来陪看欧洲杯糊着两眼半晌没搞明白谁和谁踢。")
#    cuttest(u"李智伟高高兴兴以及王晓薇出去玩,后来智伟和晓薇又单独去玩了。")
#    cuttest(u"一次性交出去很多钱。 ")
#    cuttest(u"这是一个伸手不见五指的黑夜。我叫孙悟空,我爱北京,我爱Python和C++。")
#    cuttest(u"我不喜欢日本和服。")
#    cuttest(u"雷猴回归人间。")
#    cuttest(u"工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作")
#    cuttest(u"我需要廉租房")
#    cuttest(u"永和服装饰品有限公司")
#    cuttest(u"我爱北京天安门")
#    cuttest(u"abc")
#    cuttest(u"隐马尔可夫")
#    cuttest(u"雷猴是个好网站")
#    cuttest(u"“Microsoft”一词由“MICROcomputer(微型计算机)”和“SOFTware(软件)”两部分组成")
#    cuttest(u"草泥马和欺实马是今年的流行词汇")
#    cuttest(u"伊藤洋华堂总府店")
#    cuttest(u"中国科学院计算技术研究所")
#    cuttest(u"罗密欧与朱丽叶")
#    cuttest(u"我购买了道具和服装")
#    cuttest(u"PS: 我觉得开源有一个好处,就是能够敦促自己不断改进,避免敞帚自珍")
#    cuttest(u"湖北省石首市")
#    cuttest(u"总经理完成了这件事情")
#    cuttest(u"电脑修好了")
#    cuttest(u"做好了这件事情就一了百了了")
#    cuttest(u"人们审美的观点是不同的")
#    cuttest(u"我们买了一个美的空调")
#    cuttest(u"线程初始化时我们要注意")
#    cuttest(u"一个分子是由好多原子组织成的")
#    cuttest(u"祝你马到功成")
#    cuttest(u"他掉进了无底洞里")
#    cuttest(u"中国的首都是北京")
#    cuttest(u"孙君意")
#    cuttest(u"外交部发言人马朝旭")
#    cuttest(u"领导人会议和第四届东亚峰会")
#    cuttest(u"在过去的这五年")
#    cuttest(u"还需要很长的路要走")
#    cuttest(u"60周年首都阅兵")
#    cuttest(u"你好人们审美的观点是不同的")
#    cuttest(u"买水果然后来世博园")
#    cuttest(u"买水果然后去世博园")
#    cuttest(u"但是后来我才知道你是对的")
#    cuttest(u"存在即合理")
#    cuttest(u"的的的的的在的的的的就以和和和")
#    cuttest(u"I love你,不以为耻,反以为rong")
#    cuttest(u" ")
#    cuttest(u"")
#    cuttest(u"hello你好人们审美的观点是不同的")
#    cuttest(u"很好但主要是基于网页形式")
#    cuttest(u"hello你好人们审美的观点是不同的")
#    cuttest(u"为什么我不能拥有想要的生活")
#    cuttest(u"后来我才")
#    cuttest(u"此次来中国是为了")
#    cuttest(u"使用了它就可以解决一些问题")
#    cuttest(u",使用了它就可以解决一些问题")
#    cuttest(u"其实使用了它就可以解决一些问题")
#    cuttest(u"好人使用了它就可以解决一些问题")
#    cuttest(u"是因为和国家")
#    cuttest(u"老年搜索还支持")
    cuttest(u"干脆就把那部蒙人的闲法给废了拉倒!RT @laoshipukong : 27日,全国人大常委会第三次审议侵权责任法草案,删除了有关医疗损害责任“举证倒置”的规定。在医患纠纷中本已处于弱势地位的消费者由此将陷入万劫不复的境地。 ")

mmseg算法虽然比正向最大匹配算法在错误率上要好很多,但是也不是很完美,如果想让其比较好的运行需要一个很庞大的字典库外加词频库,其中词频库很重要,而且个人觉得判断依据3(标准差)的判断比较不靠谱,这里应该优化一下,能加上词频就更完美了。

关于mmseg的Python源码还是比较少的,除了google code 有一个外,其他的我没见过。倒是C++的代码比较多,但是代码写的比较乱,没有任何注释,STL乱用,也不说明用途,反正我是懒的看啦。

另外字典没有给出,我也不知道怎么上传压缩包,这类简单的字典网上到还是挺多的。

分词算法就研究到这里吧,这也只是用来自己娱乐,专业的分词包很多,更高级的分词算法也很多,当然最后直接拿来用就可以了,没有必要去纠结人家是怎么实现的,最基本的是要知道分词的原理和分词的算法,有算法还怕实现不了?




版权声明:本文为博主原创文章,未经博主允许不得转载。

MMSEG 中文分词算法

由于学习需要,我尝试翻译MMSEG算法,目前处于初稿状态,很许多地方的翻译仍不尽准确,在以下几天会加以修改。 算法原文位于:http://technology.chtsai.org/mmseg/...

浅谈MMSEG分词算法

最近看了下MMSEG分词算法,觉得这个算法简单高效,而且还非常准确 作者声称这个规则达到了99.69%的准确率并且93.21%的歧义能被这个规则消除。 核心思想是抽取3个可能的词(存在多个组合),然...
  • pwlazy
  • pwlazy
  • 2013年12月25日 20:38
  • 10435

mmseg分词算法及实现

一、分词方法关于中文分词 参考之前写的jieba分词源码分析 jieba中文分词。 中文分词算法大概分为两大类: 一是基于字符串匹配,即扫描字符串,使用如正向/逆向最大匹配,最小切分等策略(俗称基于...

MMSeg中文分词算法

Java中有一些开源的分词项目,比如:IK、Paoding、MMSEG4J等等。这里主要说的是MMSEG4J中使用的MMSeg算法。它的原文介绍在:http://technology.chtsai.o...

最简单的介绍如何使用mmseg进行自然语言处理

一共有4个文件: basedict.txt mmseg.ini mmseg test.txt basedict.txt里边是: 数据 1 x:1 程序 1 x:1 test.txt里边是:...

mmseg4j 中文分词器的一些简介整理

在 lucene 中,我们是使用 IndexWriter 调用 Analyzer 将文章切成以词为单位的 Stream,然后生成索引的。lucene 内建的分词器很多,比如:按空白字符分词的White...
  • ghj1976
  • ghj1976
  • 2010年05月14日 17:03
  • 6387

python MMSEG 分词 实现

实现参见http://blog.csdn.net/acceptedxukai/article/details/7390300 修改了其中的几个问题 1.取 自由语素度最大的chunk 这个是要取...

基于python的分词算法的实现(1) - 算法

从网络上搜索分词算法,可以找到一个很有名的开源项目ictclas(http://ictclas.org/)。这个算法是基于概率的。概率的确是个好玩意,很多语言层面难以简单概括的东西,用一个概率就可以描...

对Python中文分词模块结巴分词算法过程的理解和分析

结巴分词是国内程序员用python开发的一个中文分词模块, 源码已托管在github, 地址在: https://github.com/fxsjy/jieba 作者的文档写的不是很全, 只写了怎么用...
  • rav009
  • rav009
  • 2013年09月30日 15:23
  • 35928

中文分词的python实现-基于HMM算法

隐马尔科夫模型(HMM)模型介绍HMM模型是由一个“五元组”组成: StatusSet: 状态值集合 ObservedSet: 观察值集合 TransProbMatrix: 转移概率矩阵 EmitPr...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Python实现mmseg分词算法和吐嘈
举报原因:
原因补充:

(最多只允许输入30个字)