ICTCLAS代码学习笔记之CSpan类

CSpan是标注器,词性标注和角色标注都是使用这个类来完成的,使用的是相同结构的词典,另标注算法是隐马模型(HMM)完成的。
类中使用的一些宏变量如下:
#define MAX_WORDS_PER_SENTENCE 120 //!用于记录每个句子中最多词的个数
#define MAX_UNKNOWN_PER_SENTENCE 200 //!用于定义每个句子中的最多未登陆词
#define MAX_POS_PER_WORD 20 //!用于定义每个词的最多词性
#define LITTLE_FREQUENCY 6 //!频率?
enum TAG_TYPE{
TT_NORMAL,
TT_PERSON,
TT_PLACE,
TT_TRANS_PERSON
};
//!标注的类型,如果是词性标注用的是第一个,否则是未登陆词,目前只处理这些类型
CSpan类的公有成员变量里面,用一个未登陆词的计数m_nUnknownIndex和二维数组
m_nUnknownWords来配合记录未登陆词的个数及每个未登陆词起始位置,数组m_dWordsPossibility则配合记录每个未登陆词的概率值(即dValue)。为什么感觉m_nUnknownIndex的名字有些名不符实呢?似乎应该是m_nUnknownCount才对,呵呵。
另外一个公有成员m_context是CContextStat类的,用于处理上下文文法吧,呵呵。待定//!
私有成员变量比较多,依次说明。m_tagType表示标记的类型,如果是TT_NORMAL就是词性标记,否则则是各个不同命名实体的角色标注;m_nStartPos记录起始位置;而数组m_nBestTag记录每个词的最佳标注结果;一起配合使用的几个数组m_sWords记录切出来的词,m_nTags记录每个词的所有可能词性(或者标注角色类型),m_nWordPosition记录词的位置,而m_nBestPrev是用于Viterbi解码算法中记录最佳路径的数组;m_dFrequency记录每个词的每个词性(标注类型)的概率。最后,m_nCurLenght记录当前长度,其实也可以看作是一个上述几个数组的有效元素的个数。
下面是重头戏,即各个成员函数的分析。
构造函数就是将上述数组、变量初始化,先看看这段代码再说要注意的事项:
if(m_tagType!=TT_NORMAL)
m_nTags[0][0]=100;//Begin tag
else
m_nTags[0][0]=0;//Begin tag
m_nTags[0][1]=-1;
m_dFrequency[0][0]=0;
m_nCurLength=1;
m_nUnknownIndex=0;
m_nStartPos=0;
m_nWordPosition[1]=0;
m_sWords[0][0]=0;
m_tagType=TT_NORMAL;//Default tagging type
第一就是构造函数的第一句对m_tgType的判断,由于是在构造函数中m_tagType其实未被赋值,那么m_tagType就是编译器的默认值(这里就是枚举类型的第一个值TT_NORMAL),而且最后还对m_tagType又赋了个初值TT_NORMAL,从最终结果上看似乎不错,但感觉赋值很无厘头。
另外就是m_nCurLength的初值不是0而是1,m_nWordPosition初值不是m_nWordPosition[0]而是m_nWordPosition [1]。具体的含义看看代码再解释//!
POSTagging函数应该是进行标注的主函数部分,在这之前要做的首先是初始化,即读入相应的未登陆词词典和上下文规则,这部分应该是在系统的初始化过程中完成的,对应的函数为LoadContext。传入的参数用做m_context初始化时使用。
下面正式说POSTagging函数。传入的第一个参数是NSP算法生成的粗分结果,后面的两个词典分别是核心词典和未登陆词词典。读分词结果,从当前读到的词开始,猜测该词的最好词性(或角色标注结果),然后根据当前标注器的类型(标词性或者相应的未登陆词)进行相应的处理。如果是词性,则根据猜测的结果对词性进行相应的更新;如果是命名实体,则调用相应的函数进一步处理,这里有一点要注意的是TT_PLACE(地名)和TT_TRANS_PERSON(音译人名)用的都是PlaceRecognize函数,不知道是写错了还是怎么着。每猜完一次都要对变量进行重置Reset()。大概过程就是这样,这里面比较核心的部分是猜测词性以及命名实体识别对应的几个函数。
GetFrom函数用于从传入的词链中根据起始位置nIndex读入一个词,读入的信息存在相应的成员变量中。主循环中有两个索引变量,I从1开始取值,控制的是m_sWords、m_nWordPosition、m_nTags、m_nBestPrev、m_dFrequency等成员变量的索引值,最后的值记录在m_nCurLength中用于表示标注后词的个数。而nWordsIndex则从传入的起始位置nIndex开始取值,控制的是传入的原始词串数据结构pWordItems的索引。I的取值不会超过MAX_WORDS_PER_SENTENCE(120)而nWordsIndex的取值不会超过MAX_WORDS(650)。主循环对I的最大取值进行了判断但是没有判断nWordsIndex的最大取值,只是根据对应位置的字符串是否为空来决定的,如果前面的计算中没有清空工作现场或者传入参数非法则有可能非法访问造成系统崩溃。
在标注的主循环中,如果进行词性标记或者未登陆词典中没有出现当前处理的这个词pWordItems[nWordsIndex].sWord,那么简单的将词拷入m_sWords[i]中,并记录下位置信息在m_nWordPosition[I+1]中,这也是为什么构造函数初始化时m_nWordPosition是从m_nWordPosition [1]开始赋值的,因为I的取值从1开始。这里同样存在一个隐患,因为只判断i<MAX_WORDS_PER_SENTENCE,当I的取值为MAX_WORDS_PER_SENTENCE-1时,对m_nWordPosition[I+1]的访问会造成越界。对于角色标注且在词典中找到词的情况,会做一个切分,把第一个字和该串的其余部分分开考虑。
记录下当前词的起始位置在m_nStartPos中,然后根据标注的不同类型进行处理。对于角色标注,如果是外国人名标注,会对半角符号“.”和“-”分别替换为相应的全角符号“.”“-”进行处理。从未登陆词词典中读入该词的所有词性及相应概率值,并记录在相应的m_nTags和m_dFrequency中。如果当前词为开始标记或者结束标记且从未登陆词词典中只出现了一次要更新m_nTags和m_dFrequency的相应值,注意对于开始标记更新的是[i][j-1]而结束标记更新的是[i][j]。其他的需要标记角色的词,从核心词典中读入该词的词性及相应频率,如果词典中已经有相应的词及词性,则将角色标记结果置为0,概率结果也重置。注意这里更新了j的值,以便后面判断是否已有标注结果。相对而言词性标注的工作则比较简单,如果当前词条的nHandle值大于0,即只有一个唯一确定的词性那么记录下来就OK了;否则,属于有多个词性的情况,记录词性的时候取个负值即可。然后从核心词典中读入该词的多个词性,依次记录入m_nTags和m_dFrequency的后面的位置。
上面两种类型的标记都做完以后,如果j的取值仍然为0,即什么标记也没有得到,那么调用GuessPOS猜一个词性。至此会有一个标记了,不要忘记给m_nTags[i][j]添上结束标记-1。如果此时j的取值为1即只有一个标注结果且又不是开始标注(结束标记就可以退出了)那么表示无歧
义了可以跳出循环,否则继续。最后要做一些清理工作,包括判断是否已到词串的结尾,下面这段代码的含义没有看太明白,先记录下后补:
if(m_nTags[i-1][1]!=-1)//||m_sWords[i][0]==0
{//Set end for words like "张/华/平"
if(m_tagType!=TT_NORMAL)
m_nTags[i][0]=101;
else
m_nTags[i][0]=1;
m_dFrequency[i][0]=0;
m_sWords[i][0]=0;//Set virtual ending
m_nTags[i++][1]=-1;
}
GuessPos函数只对未登陆词进行角色的猜测,如果是词性标记则直接忽略掉了。对于中国人名、音译人名和地名,分别有更新m_nTags和m_dFrequency的计算方法,但是全部使用的是数值不清楚确切含义。
Disamb()函数用于消歧,即使用Viterbi算法选取全局最优的标注结果。对每个词的每个标注结果m_nTags[i][j],考察当前词的前面一个词的每个词性标注结果m_nTags[i-1][k],得到最小代价的路径之后将路径和频率值分别记录在m_nBestPrev[i][j]和m_dFrequency[i][j]之中。
Reset函数顾名思义用来重置内部变量,参数bContinue是用来控制重置的程度,如果bContinue为真则只是将上次的结果拿来做为初始状态,否则清空重新开始。
GetBestPOS()函数就很简单的,先进行消歧(调用Disamb函数),根据计算得到的结果从后向前回溯,将最好结果记录在m_nBestTag中。
PersonRecognize函数是专门用来做中国人名识别的,几种不同模式的定义在代码中都有说明,例如BBCD是表示姓+姓+名1+名2,这种模式的参数值是0.003606,模式的长度为4。先将计算得到的最佳角色标注结果的数值转换为字符。然后对于切好的词串进行于全排列的方式进行组合来匹配所有的模式,如果匹配到则更新相应的m_nUnknownWords和m_dWordsPossibility的值,并增加m_nUnknownIndex的计数。
PlaceRecognize的过程类似,但代码写得没有人名部分那么清楚。只有在m_nBestTag[i]的值为1或者2的时候才会引发识别过程,这两个数字代表的含义目前未知。程序同时引入了惩罚因子来对长度进行惩罚。最终更新m_nUnknownWords和m_dWordsPossibility的值。这个函数中使用多处while循环,并没有索引的值进行判断,可能会出现溢出的情况吧。
ComputePossibility函数就是计算从传入的起始位置nStartPos长度为nLength的词串的相应概论,用的是词的最佳标注结果来进行计算,没有更多可说的内容。
CSpan类的总结:
至此,CSpan的内容基本上写完了,因为最初写的时候也没有完整的概念体系所以比较凌乱,现在再总结一下。这个类是一个“通用的”进行标注类,使用的算法是隐马模型,解码用的是Viterbi算法。使用此类可进行词性的标注,也可以进行角色的标注,在部分代码中需要一定的判断。类的对外接口有两种功能类型,一种是初始化的时候读入相应的词典(未登陆词词典)及相应的上下文无关信息,另外一种就是对传入的词串进行标注,可能是词性也可能是命名实体,这部分的框架在POSTagging函数即可看到。读入一个词串和相应的词性概率等信息,用Viterbi算法计算得到最好的标注结果后,如果是词性标记则修改相应的词性标注结果,否则调用相应的命名实体单元来进行进一步的识别工作。直到处理完所有的结果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值