统计分词/无字典分词学习(2):n-gram词频统计

来源:互联网 发布:autodesk 设计造型软件 编辑:程序博客网 时间:2024/05/21 13:16

 我们现在面对的是“wheninthecourseofhumaneventsitbecomesnecessary”这样一堆语料,要获取词典,怎么办?

第一步肯定是找到所有可能是词的片段了,常用的方法就是n-gram切分了,如假设词的最大长度是3,则句子“abcd”的n-gram切分就是:

1-gram切分:a b c d

2-gram切分:ab bc cd

3-gram切分:abc bcd

         这些切分包含了句子序列中所有可能的长度小于等于3的片段,也就是说,所有的可能成为“单词”的片段,都在这些ngram切分片段里面了,我们后续的工作就是从这些候选片段中过滤出“单词”即可。

          这些候选片段的一个基本特征现在就是词频了,就是出现的个数,这个显然是一个片段能够成为词的一个基本特征,如果一个片段出现的次数非常少,我们认为它很难构成词。

        因此,为了从这些未标注的生语料中构造一个词典,我们首先需要构造所有的n-gram片段,然后统计片段的出现次数,然后选择一个阈值,把低频的片段干掉。这里就是一个简单的词频统计问题,用Python的dict统计即可。但由于语料规模一般都非常大,因此,这里将代码写成Hadoop streaming的形式,既可以本地运行,也可以放在hadoop运行。

     首先是mapper的代码(ngram_count_mapper.py),其功能就是切分出所有可能的ngram片段即可:

#!/usr/bin/env python#coding=utf-8import sysimport os#获得ngram切分g_contain_se = 0#是否加入开始和结束字符g_max_word_length = 12 #最大的切分片段的长度#输入 line  一个句子#输入 word_length  词的长度#coding=utf-8import sysimport os#获得ngram切分g_contain_se = 0#是否加入开始和结束字符g_max_word_length = 12 #最大的切分片段的长度,也就是最大的词长度#输入 line  一个句子#输入 word_length  词的长度#输出 ngram 切分#返回值 0,正确,其它错误def build_ngram_word(line, word_length):    #进行ngram交叉切分    if g_contain_se == 1: #判断是否加入开始和结束标志,开关控制        letter_list = ["<"] #句子开始标记        letter_list.extend(line) #加入所有的字        letter_list.append(">") #句子结束标记    else:        letter_list = line    for i in range(0,len(letter_list)-word_length+1):        word = "".join(letter_list[i:i+word_length])        print word + "\t" + "1"#构建所有长度的切分片段def build_all_ngram_word(line):    for word_length in range(1,g_max_word_length+2):        build_ngram_word(line, word_length)for line in sys.stdin:    line = line.strip()    build_all_ngram_word(line)

        然后是reducer代码,就是mapper处理之后的数据,是按照key排序的数据,key相同的数据都放在连续的行上,如果不适用python hadoop的api,也是简单的利用这个特性,当key变化的时候,就处理上一个key的所有数据即可,然后更新当前处理的key。

reducer的代码(ngram_count_reducer.py):

#!/usr/bin/env python#coding=utf-8import sysimport os#ngram统计,reducer部分current_word = Nonecurrent_count = 0for line in sys.stdin:    line = line.strip()    (word,count) = line.split("\t")    count = int(count)    if current_word == word:#如果词没有变,则累加        current_count = current_count + count    else:#如果词发生变化了,则输出        if current_word != None:             #如果读的是第一行,和 None不一致,但不输出,不是第一行才输出            #输出上一个词            print current_word + "\t" + str(current_count)        #初始化current_word,current_count为当前词        current_word = word        current_count = count#最后一行处理,如果是和前面一行的词,后续没有词了,则前面没有输出print current_word + "\t" + str(current_count)
  然后就可以运行了,先用一个小数据测试下:

数据简单写几行,看看处理结果是否正确即可,相当于单元测试:

测试数据(test1.dat)

12345abcde1abc2

然后运行本地测试,写一个脚本(local_run.sh)

#!/bin/shtest_data=test1.datmapper=./ngram_count_mapper.pyreducer=./ngram_count_reducer.pycat ${test_data}|${mapper}|sort -k1,1|${reducer}

跑hadoop的脚本也基本类似,首先要把测试数据test1.dat放到你的hadoop文件中,然后执行即可,这里数据路径要写成你自己的:)

hadoop 运行脚本(hadoop_run.sh)

#!/bin/shhadoop fs -rmr mariswang/outputhadoop org.apache.hadoop.streaming.HadoopStreaming \-input mariswang/data/test1.dat \-output mariswang/output \-mapper "ngram_count_mapper.py" \-reducer "ngram_count_reducer.py" \-file ngram_count_mapper.py \-file ngram_count_reducer.py \-jobconf mapred.reduce.tasks=1\-jobconf mapred.job.name="mariswang_count"#get datahadoop fs -get mariswang/output/part-00000 ./

测试数据没问题了,就可以跑上一篇文章中的正式数据了。

跑出来的数据有18G,大概13亿个候选词,显然要处理这些词还是太多了,我们就大概选一个词频阈值,大于这个阈值的才进行进一步的处理,这选择40做为阈值。

过滤之后的词大概有1千万+,大概只有原来的1%。我们重点关注头部的词条,从词频最高到低对这些片段就行排序,以10万个为间隔,看这些区间内的片段属于标准词典的比例,具体如下图所示:


可以看到,词频最高的10万个片段中,有个将近27%的都是词典中的词,而第二个区间内,有10%的片段是词,而到了第10个区间,就只有不到3%的片段是词典中的词了,因此,词频是区分一个ngram片段是否是词的非常好的特征。

而具体到词频最高的10个片段中,频度最高的top1万的片段中,有67%都是词,后续的1万个词中,有40%。

词典里面总共有30万+个词,ngram切分的频度最高的前30万个片段中包含其中4万个词,钱100万个片段,包括其中17万个词。

如果我们只关注最常用的3万个词,也就是词频最高的前3万个词,则可以看到,ngram切分频度最高的前30万个片段中,包括其中1.8万个词,前100万个片段,包括2.2万个。更进一步的,只看最常用的1万个词,前30万个词,包括其中8100个词,前100万个词,包括9400个词。

因此我们后续只需要关注前100万个频率最高的ngram片段即可,因为选择更多的片段,可以小幅度增加覆盖率,但准确率明显会下降很多,会对后续的方法造成很大的困难,因此,在第一步,就为后续的步骤做好数据过滤关。

现在我们有100万个ngram片段,后续主要就是从中找出是词的片段。

原创粉丝点击