一种没有语料字典的分词方法

原创
2016/04/26 01:24
阅读数 567
前几天在网上闲逛,看到一篇美文,说的是怎么在没有语料库的情况下从文本中提取中文词汇,理论部分讲得比较多,但都还是很浅显易懂的,其中涉及一部分信息论的理论,其实只要大学开过信息论这门课的话,看起来还是挺简单的。

信息论我忘得差不多了,但是其中主要的内容还记得,信息论最主要的就是信息其实是可以度量的,一个事件包含的信息和它发生的概率成反比,简单的说,同样一个事件,产生A结果的概率为Pa,产生B结果的概率为Pb,如果Pa大于Pb,那A所包含的信息量就比B要大。

打个简单的比喻,比如中国队和西班牙队踢足球比赛,大家都知道,西班牙队赢的概率大概是99.9999%,中国队赢的概率大概是0.0001%,假如最后的结果是中国队赢了(靠实力)的话,那这个事件(中国队赢了西班牙)就是个信息量非常巨大的事件,我相信各大报纸的头条都会报道,反而如果西班牙赢了,估计没有报纸会报道这个消息,这就是信息论的核心,也就是信息熵。

扯远了,上面只是说说我对信息论的理解,在分词技术上,目前的分词技术基本上都是基于字典的,就是看文章中有没有字典含有的词语,如果有,就把这个当成一个词来分,这样也衍生了很多分词技术,大家有兴趣可以自己去查一查。

如果我们没有词典,需要分词就比较麻烦了,假设我们最大的词长度设定为5,能想到的办法就是,从第一个字开始,把文本中所有连在一起的两个字,三个字,四个字,五个字的片段找出来,然后看这些片段在文本中出现的频率,频率高的就当成一个词汇,这样确实能分出词来,但是这样同样也分出了一些不是词的词汇,比如上面的文章中,“的话”这个词就出现的相对比较多,显然,这不是一个词。

在这种基础上怎么把类似“的话”这样的词去掉就是我们要做的工作了。这涉及到两个重要的部分,也是那篇美文提到的两个部分。

第一,怎么去掉类似于“的中国”和“中国队”这样的差异,这三个词可能出现的频率都差不多,甚至“的中国”出现的频率更高,假设对于一个长文本(假设为10000,并且是描述足球比赛的),
“的中国”出现了100次,概率是1%
“中国队”出现了60次,概率是0.6%
“中国”   出现了200次,概率为2%
“的”       出现了500次,概率为5%
“队”       出现了70次,概率为0.7%
这样的数据情况下,“的”字和“中国”随机组合在一起的概率为5%*2%=1%,而“队”和“中国”随机组合在一起的概率为0.7%*2%=0.14%,显然“的中国”的出现概率和他们组合在一起的概率差不多,所以我们认为“的中国”更像是随机组合在一起的词而不是一个固定的词汇,但“中国队”出现的概率比他们组合在一起的概率高了4.28倍,所以我们认为“中国队”更像一个词汇。
通过上面的计算,我们就可以把“的中国”这样的词丢掉了,就算他出现了很多次,但是我们一样不认为它是一个词,这就是第一部分,我们把它叫词语的凝聚程度,“的中国”显然凝聚力不够。


第二,像类似“了一”这样的词在文章中肯定也出现得很多,因为是足球比赛文章,所以经常出现摔了一跤,进了一球等等,显然,“了一”也不是一个词汇,但是单独看“了”和“一”,“了一”的概率可能比他们单独组合的概率高不少,像这样的词怎么弄呢,这就要用到开头我们讲的信息论中的信息熵了。

“信息熵”能够反映知道一个事件的结果后平均会给你带来多大的信息量。如果某个结果的发生概率为 p ,当你知道它确实发生了,你得到的信息量就被定义为 - log(p) 。 p 越小,你得到的信息量就越大。在“了一”这个词中,他的前缀为“摔”,“进”,概率分别是p1,p2那他的前缀信息熵就是- log(p1)*p1-log(p2)*p2,同样后缀的信息熵也可以算出来,算出来以后我们取其中较小的那个作为这个词的前后缀信息熵,结果算出来是一个非常小的数,比如0.2,比普通真实的词汇,比如“摔了一跤”这样的词的信息熵都要小,所以显然可能“了一”就不是一个词汇了,反而“摔了一跤”这样的词更像一个词汇。

有了这两个理论,那我们找出一个词,然后按这两个条件给出一个阈值,就能抽取出一批我们认为是词汇的词语了,不需要字典,而且从抽取出来的词根据词频排列的话,再把一般性的词如“应该”,“如果”,“似乎”这样的介词去掉的话,一般是这个文本的关键字集合了。

嗯,理论部分大概就是这样了,具体的大家要是有兴趣,可以看看这篇blog,我都是看到这个的,顺便推荐一个这个作者,他的文章基本上都是原创,而且对数学理解得很到位,很适合各种程序猿们,呵呵。

按照这个东西,我自己写了个python脚本,写得很着急,而且不知道理论理解得对不对,后来我对《明朝那些事》抽了一把,出现了这些:
危险 优秀沉默李景隆影响 杨继盛 痛苦 脑袋 徐有贞厉害支持 蓝玉杨廷和瓦剌申时行 判断 记载 孙承宗坚持愤怒奏疏 锦衣卫 严世蕃 祁钰 倭寇 东林党麻烦朋友 努尔哈赤  选择喜欢容易 首辅 御史李如松 父亲戚继光投降 陈友谅 厚照 希望 蒙古简单估计消息彻底 胡宗宪 祁镇 毕竟 魏忠贤 告诉 袁崇焕 嘉靖 似乎 准备 。。。。。。。。

而对于《时间简史》,出现了这些:
空间时间 吸引增加基本尺度 效应 东西描述轨道
位置 边界产生解释夸克 广义相对论坍缩 辐射 部分
知道 状态区域距离爆炸 问题 奇点模型太阳
事件 科学预言运动膨胀 任何 必须恒星黑洞.......

你修改约束条件,就会出现不同的词语,怎么平衡这个约束条件也是个问题。

另外,如果你有兴趣,可以分析分析一些人的blog和微博之类的,再和你自己的对比一下,说不定能找到很多关键词一样哦,还有,要是你有海量的数据,也可以做一些非常有意思的事情哦,哈哈。


最后,再次推荐一下matirx67的blog:  http://www.matrix67.com/     


程序做得太快了,不好,还是把关键函数贴出来吧,就是暴力的编码,用了python自带的方便的数据结构来快速开发。所以速度上比较慢,而且内存上。。呵呵。。说不定会爆哦。。。。完整程序:
https://github.com/wyh267/ChineseWordSegmentation

# -*- coding=utf-8 -*-
'''
Created on 2013-5-9

@author: Wu YingHao

@email:  wyh817@gmail.com

该程序可以在没有语料库的情况下从文本中抽取出中文词汇
理论支持:
http://www.matrix67.com/blog/archives/5044

'''


import math


"""
    计算每个字和词的出现频率
    输入: words 字符串内容
      num 需要截取的最长字串
    输出: split_words 分割好的所有子串的数据,以字典形式返回
      split_words 说明
      {"字串" : [ 出现次数,出现概率,凝固程度,凝固程度*出现次数,自由程度,前缀集合,后缀集合] .....}
"""
def find_words(words,num=6):
    split_words={}
    lens=len(words)
    for i in range(0,lens):
        for j in range(1,num+1):
            if i+j < lens -num-2:
                if words[i:i+j] in split_words:
                    split_words[words[i:i+j]][0]+=1
                    split_words[words[i:i+j]][1]=float(split_words[words[i:i+j]][0])/float(lens)
                    split_words[words[i:i+j]][6].append(words[i-1])
                    split_words[words[i:i+j]][7].append(words[i+j])
                else:
                    split_words[words[i:i+j]]=[1,
                                               1/float(lens),
                                               words[i:i+j],
                                               1,
                                               1,
                                               0,
                                               [words[i-1]],
                                               [words[i+j]]
                                               ]
                    
                                               
        if(i%10000==0):
            print "完成 :" + str(float(i)/float(len(words))*100) + " %"
                
    return split_words
                


"""
    计算凝聚程度
    输入:words_dic 已经拆分好的字符串字典
    输出:填充好凝聚程度的字典
"""
def find_nh(words_dic):
    for key in words_dic.keys():
        if(len(key)>1):
            #左凝聚程度
            left_p=words_dic[key][1]/(words_dic[key[:1]][1]*words_dic[key[1:]][1])
            #右凝聚程度
            right_p=words_dic[key][1]/(words_dic[key[:-1]][1]*words_dic[key[:-1]][1])
                 
            
            if(left_p<right_p):
                words_dic[key][3]=left_p
            else:
                words_dic[key][3]=right_p
    



"""
    计算自由程度
    输入: word_dic 字典文件
    返回:word_dic 添加自由程度以后的字典
"""
def calc_free(word_dic):
    for key in word_dic.keys():
        front_free=0
        end_free=0
        for front in word_dic[key][6]:
            if front in word_dic:
                front_free-=math.log(word_dic[front][1])*word_dic[front][1]
        
        for end in word_dic[key][7]:
            if end in word_dic:
                end_free-=math.log(word_dic[end][1])*word_dic[end][1]
            
        if(front_free < end_free):
            word_dic[key][5]=front_free
        else:
            word_dic[key][5]=end_free

    return word_dic  


注:本文虽然不是转帖,但是内容参考了matrix67的博客,地址为  http://www.matrix67.com/blog/archives/5044  ,特此声明。


===========================================================================================
欢迎大家关注我的微信号: XJJ267    西加加语言
或者扫描下面的二维码也行哦。


展开阅读全文
打赏
1
25 收藏
分享
加载中
N-gram,HMM。。。
2016/04/29 08:57
回复
举报
不错
2016/04/26 17:42
回复
举报
更多评论
打赏
2 评论
25 收藏
1
分享
返回顶部
顶部