您的位置:首页 > 其它

R语言实现viterbi算法

2013-12-31 12:50 260 查看

数据集介绍

最近初学HMM(Hidden Markov Model),老师让我自己试着用Java去实现viterbi算法,结果试了一下,发现数据的输入太麻烦,因为数据集是一些各种字符串和标点符号的矩阵吧,然后才开始学习R语言的, 首先先看看数据集。

http://www.clips.uantwerpen.be/conll2000/chunking/

数据集是从上面这个网站下载的,有一个train.txt还有一个test.txt。

格式大概如下:

He        PRP  B-NP
reckons   VBZ  B-VP
the       DT   B-NP
current   JJ   I-NP
account   NN   I-NP
deficit   NN   I-NP
will      MD   B-VP
narrow    VB   I-VP
to        TO   B-PP
only      RB   B-NP
#         #    I-NP
1.8       CD   I-NP
billion   CD   I-NP
in        IN   B-PP
September NNP  B-NP
.         .    O

这是一整句话的数据。 第一列我们称为symbol或者是word,第二列称为tag或者是标签(tag又称作状态state),第三列在这个算法中没用到,所以先不提及。


然后首先要知道viterbi算法的输入和输出,

算法输入输出



Input:

tp: transition probability matrix  (不同tag之间的转移概率矩阵, tp[i,j]表示从第i个标签,下一个单词的标签是第j个标签的概率 )

ep: emission probability matrix (表示tag和word之间的发射概率矩阵,ep[v,k]表示第v个标签下,这个标签会输出第k个单词(word/symbol)的概率 )

startp: start state probability vector (表示某个tag作为一句话开头的概率, starp[i]表示第i个tag作为句子sequence开头的概率)

test: the observation sequence need to be tagged (test 就是输入进去的一句话,是向量形式输入)

Output:

outp: it’s also a probability matrix, each row denotes each word(i.e. symbol) of thetest sequence and the column contains all the tags.(43 tags in all)

  Each element(outp[i,j]) in this matrix denotes the probability of the ithword would be tagged in the jth tag.

(我这里定义的输出可能和其他人的viterbi算法输出不太一样,这个输出了之后还要做一些后续的处理,这里的outp是一个矩阵,行是代表输入的test的每一个单词,列是代表每一个tag,然后outp[i,j]就表示第i个单词用第j个标签进行tag的概率,所以最后只需要再做一步统计每一行中最大的那一列就是这一行的那个单词的tag)


算法输入参数预测和计算

熟悉可以跳过下面这三个代码段,因为下面我要首先计算出 tp,ep,startp这三个矩阵里面的数值,首先是计算转移概率矩阵。

# This part is used to calculate the transition probability matrix tag.tp 计算转移概率矩阵
data <- read.table('train.txt',sep=" ",quote="")  #read the data
tags_num <- table(data[,2])                       #table函数可以统计在data中每个tag出现了多少次,相当于SQL里面的group的作用
tag.transform<-table(data[,2],data[,2])		      #transform这里是想弄成一个矩阵,行和列都是tag,tag.transform[i,j]表示第i行转移到第j列的频数
for(i in 1:length(tag.transform[1,])){            #循环初始化每一个频数都是0
tag.transform[i,i] <- 0
}

for(i in 1:length(data[,1])){			  #这里通过扫描data里面总的单词,统计前一个tag和后一个tag的连接频数。
if(data[i,2]=='.') next
tag.transform[data[i,2],data[i+1,2]]<-tag.transform[data[i,2],data[i+1,2]]+1
}

tag.tp<-tag.transform				  #tag.tp就是最后的转移概率矩阵,通过频数除以总数得到转移概率
for(i in 1:43){
for(j in 1:43){
tag.tp[i,j]<-tag.tp[i,j]/tags_num[[i]]
}
}
#######################End of this part#####################


然后下面这部分计算输出概率矩阵

#This part is used to calculate the emission probability using both train data and test data
symbol_data <- read.table('train_and_test.txt',sep=" ",quote="")    #这个输出概率矩阵的计算把训练集和测试集的数据都拿了出来,因为assignment的文档说有些单词train data上没有,所以必须拿出来
symbol<-table(symbol_data[,1])                              #统计相同单词的数量
symbol_tags_num<-table(symbol_data[,2])                     #统计相同tag的数量
symbol_name<-names(symbol)                                  #names函数的作用比较重要,把下标提取出来,不要数字在里面
symbol.epmatrix<-table(symbol_data[,2],symbol_data[,1])     #这是用来计算的,行是tag,列是单词,ep[i,j],第i个tag输出第j个单词的概率
symbol.ep<-symbol.epmatrix
for(i in 1:43){                                             #这里是43的原因是我之前debug程序早就知道总共43个tag,就懒得写length(tag)了
for(j in 1:length(symbol)){
symbol.ep[i,j]<-symbol.ep[i,j]/symbol_tags_num[[i]]
}
}
####################End of This part#########################


最后计算起始概率向量

#calculate the start probability of state(i.e. tags) 这部分看上去比较复杂,但只是有些地方要注意
startp<-tags_num                            #整个程序是连续的,所以tags_num是引用上面的
for(i in 1:length(startp)){                 #初始化每个tag的作为起始的概率为0
startp[i]<-0
}
sumstart<-0                                 #sumstart作为统计train的数据集中,总的作为开始的tag数量或者说sentence的数量
for(i in 1:length(symbol_data[,2])){
if(i==1){                                 #如果是i=1因为没有‘.’去指示下一个就是开头,所以这里先判断i=1也是开头
startp[symbol_data[i,2]]<-1
sumstart<-1
next
}
if(i==length(symbol_data[,2])){
break
}
if(symbol_data[i,2]=='.'){                #如果这个tag='.' 表示下一个tag就是下一个sentence的开头
startp[symbol_data[i+1,2]]<-startp[symbol_data[i+1,2]]+1
sumstart<-sumstart+1
}
}
for(i in 1:length(startp)){
startp[i]<-startp[i]/sumstart             #最后再除以总数就得到概率
}
########################End of This Part#####################


Viterbi算法

然后就是重点的viterbi算法了,我用了一个function去表示这个算法。
#The start of viterbi algorithm
firsttest <- read.table('test.txt',sep=" ",quote="")        #这里读进来的就是测试集的数据,不大
words<-as.character(firsttest[,1])                          #把单词转换成字符型的
p=array(0,dim=c(length(words),43))                          #再强调一下43是tag的数量,下面就不强调了,outp是输出的概率矩阵

hmm<-function(tp,ep,p,startp,test){                     #整个算法的复杂度和理论相同O(单词数*tag数的平方)
outp<-p
for(k in 1:length(test)){
for(v in 1:43){
if(k==1 || test[k-1]=='.'){
outp[k,v]<-startp[v]*ep[v,test[k]]              #这里是计算每句话初始的概率因为当k=1时不能用k=0算,此时只能用startp去计算
}
else{
for(u in 1:43){                                 #如果并不是开始的那个单词,就可以用公式算找出最大的那个outp,在u中遍历
maxvalue<-outp[k-1,u]*tp[u,v]*ep[v,test[k]]
if(maxvalue>outp[k,v]){
outp[k,v]<-maxvalue
}
}
}
}
}
return(outp)                                          #最后得到的是一个结果的概率矩阵,outp[i,j]就是第i个单词被第j个标签tagged的概率,所以只需要统计出每一行的最大的那个概率的那一列,就是这一行的单词对应的tag了
}


因为是老师叫我写的自己练习练习的,当初就不知道R竟然有hmm包,然后后来用了hmm包发现结果也是差不多。
最后统计出的错误 tag:381个 在训练集中总共有47366个词,
所以准确率应该就是 381/47366= 99.19% 吧。

结尾

因为在这里我不太能区分准确率和召回率的概念,所以就简单的一除了,希望有能人帮我解答一下哈!!!

by the way, 原来发现java也有那个hmm的包,才发现自己实现和debug的时候是有多累啊。。
不过。欢迎讨论哈。。

还有整段代码都是可以运行的哈~~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  R语言 HMM Viterbi