转自:http://blog.qq.com/qzone/95007909/1274760124.htm
目前正在编写手机平台上的输入法,在输入法中要向实现句子级别的智能输入,必须借助统计语言模型,依靠统计语言模型中的词概率值,输出最可能的句子。下面将介绍统计语言模型的建立以及平滑。
目前输入法中常用的语言模型有trigram(三元)和bigram(二元),其中微软拼音、智能狂拼使用的是trigram,谷歌拼音、搜狗拼音和紫光则是bigram。本文以二元模型为例来说明模型的建立和平滑。
下面分三个方面来介绍:
语料库的预处理
模型的建立
模型的平滑
一.语料库的预处理
原始语料库来源于北京大学语言研究所提供的98年1月人民日报标注语料。下载的已切分的语料都是形如“19980131-04-012-001/m 现实/n 的/u 顿悟/vn 却/d 被/p 描/v 出/v 形/Ng 来/v 。/w ” ,有的前面还保留了日期编号,因为这些切分语料的来源是人民日报。预处理主要是按标点符号分句,句子简单定义为( 。?! : ;)这五种标点符号结尾的词串,句子首尾分别添加<BOS>和<EOS>这两个表示句子开始和结束的标记,这在2-gram建模时要用的,后面会提到。处理过程中,忽略词类信息和前面的日期信息,因为我这个切分系统不考虑词类标注。如前面这句预处理后应该为下面形式 “<BOS>现实 的 顿悟 却 被 描 出 形来 。<EOS>” ,当然切分词之间你可以用你想用的符号标记,而不必是空格。因为考虑到所有的英文字符和数字的ASCII,我用了下面方法实现之
(至于为什么切分成这样,请参照马尔科夫模型的概率归一性)
FileReader fi=new FileReader(f);
BufferedReader bf=new BufferedReader(fi);
FileWriter fo=new FileWriter(ff);
StringBuffer s1 = new StringBuffer();
String s;
while((s=bf.readLine())!=null)
{
for(int i=0;i<s.length();i++)
{
char a=s.charAt(i);
if ((a == '。' || a == '?' || a == '!' || a == ':' || a == ';' )) //一句结束
{
String s2 = new String(s1);
fo.write("<BOS>"); //在句子前加 <BOS>
fo.write(s2);
fo.write("<EOS>"); //在句子末尾加 <EOS>
fo.write("\r\n");
fo.flush();
s1 = new StringBuffer();
}
else if ( a == '/')
s1 = s1.append((char)32); //分词位置空格
else if (a > 256 )
s1 = s1.append(a);
}
}
二:模型的建立
在这里首先简单介绍一下n-gram模型和2-gram模型。
根据语言样本估计出的概率分布P就称为语言L的语言模型。对给定的句子s = w1w2…wn,(数字,n,i都为下标,wi为句子s的一个词)。由链式规则(Chain rule),P(s) = p(w1)p(w2|w1)p(w3|w1w2)……p(wn|w1w2w3…w(n-1)) , 对p(wi|w1w2…w(i-1))而言,(w1w2…w(i-1))即为wi的历史。考虑前面n-1个词构成历史的模型即为n-gram模型。 n越大,提供的语境信息也越多,但代价就越大,且需训练语料多;n较小时,提供的信息比较少,但计算代价小,且无需太多训练语料。
令c(w1,…,wi)表示词串w1,w2…wi在训练语料中出现的次数,则由最大似然估计, P(wn|w1,…,w(n-1)) = c(w1,…,wn) / c(w1,…,w(n- 1)). 同理,则2-gram为 P(wn|w(n-1)) = c(w(n-1),wn) / c(w(n-1)).
回归项目:) 训练语料一共有5万多个不同的词。建立2-gram统计模型时不断要把每个词在训练语料中出现频率统计出来,还要把每个词及其后面的那个词组成的2-gram在训练语料中出现频率统计出来。因为在切分时会频繁的在建立的2-gram模型中查找相关的数据,所有,存储这个2-gram模型数据的数据结构一定要能提供高效的查找。故选择hash表,它能提供常数时间的查找。Java类库里提供了HashMap类,基于数据两还不是非常大,故可直接拿来用。在存储时,每一个key值对应一个在训练语料中出现过的词语,而每一个key值对应的value值又是一个HashMap。暂且称为子hashmap.这个结构有点类似文件结构里的二级索引。 其相关代码如下:
怎么在预处理文件里把词分别读出来就不罗嗦了,方法:每读入一行,按空格分成String数组,用个正则表达式匹配下即能得到。
下面这个方法传入的两个词组成一个2-gram,prewd为前一个词,currwd为紧随其后的词
public void add(String prewd , String currwd){
String key = prewd;
String curr = currwd;
if(mainhm.containsKey(key)==false)
{ //若 主map 中无,则添加
HashMap hm = new HashMap(); //首先,新构造一个 子MAP
hm.put(key , new Double(1.0));
mainhm.put(key,hm); //将 主KEY 和对应的 子MAP 放入 主MAP 中
}
else //若 主map 中含有该词
{
HashMap temp = (HashMap)mainhm.get(key);
Double count = ((Double)temp.get(key)).doubleValue() + 1.0; temp.put(key , new Double(count));
if (temp.containsKey(curr)) //判断 子map 中是否含有该词
{
Double value = ((Double)temp.get(curr)).doubleValue() + 1.0;
temp.put(curr , new Double(value));
}
else
temp.put(curr, new Double(1.0)); //若无,则将其存入子map
mainhm.put(key,temp);
}
}
三.模型的平滑
因为语言中的大部分词属于低频词,所以稀疏问题肯定存在。而MLE(最大似然估计)给在训练语料中没有出现的2-gram的赋给0概率。所以还得对2-gram模型进行数据平滑,以期得到更好的参数。目前平滑技术比较多,如Add-one,Add-delta,Witten-Bell,held-out留存平滑等。本文采用Katz方法进行平滑:
Katz算法的平滑公式是:
在我们实现算法的时候,先遍历n-gram中的数据,先将C(Wi,Wi-1) > 0
时候的Pkz
都算出来(即分子上面的),然后再从unigram中,将分母中所有的Pml
都算出来,然后代入上式,计算 并将 保存到n-gram的数据结构中。
部分代码如下:
Iterator it = mainhm.keySet().iterator();
while(it.hasNext())
{
String key = (String)it.next();
HashMap sonhm= (HashMap)mainhm.get(key); //子Map
Double n= (Double)sonhm.get(key);
Iterator itr = sonhm.keySet().iterator();
itr.next();
while(itr.hasNext())
{
//套平滑公式
String s=(String)itr.next();
Double value=(Double)sonhm.get(s);
Double newvalue=(value-0.3)/n;
sonhm.put(s,newvalue);
}
}
以上讲述了统计语言模型的建立以及平滑算法,输入法正在制作当中,按上面讲述的方法,目前准备率还不得而知,仅仅是个人的一些理解。