ICTCLAS代码学习笔记之CResult类

来源:互联网 发布:华为大数据在贵州 编辑:程序博客网 时间:2024/06/06 15:03
最后一个核心文件CResult类。这个类是最外层的包装,私有成员变量包括一个切分器(m_Seg)一个词性标记器m_POSTagger、两个词典(核心词典m_dictCore和二元词典m_dictBigram)、三个未登陆词识别器(m_uPerson,m_uTransPerson,m_uPlace)。公有成员变量中的m_pResult记录切词及标注的结果,m_dResultPossibility用于记录生成多个结果是相应的概率值。可供用户进行设置的几个参数分别是m_dSmoothingPara用来做概率计算时的平滑参数,m_nResultCount记录生成多少个切分标注结果,这个数值被MAX_SEGMENT_NUM限制,即不超过10个,m_nOperateType记录生成结果的标注类型,如果为0则只切分不标注,如果为1表示上位标注,如果为2则为下位标注(更详细的标注类型),另外m_nOutputFormat控制输出结果的表示方法(以及词性标注集的转换),包括北大标准,973标准及XML表示方式。
构造函数用于初始化资源,这个过程会比较慢,包括给m_pResult分配存储分词及标注结果的空间,最多生成MAX_SEGMENT_NUM(10)个结果,每个结果最多包括MAX_WORDS(650)个词。然后是词典和标注器的初始化,m_dictCore和m_dictBigram分别读入词典,m_POSTagger读入词性标注的内容,并设置其标注类型为TT_NORMAL,即词性标记。三个未登陆词标注器分别读入相关数据并设置标注类型,默认的标注类型为下位词性,默认的输出标准为北大标准,默认的平滑参数
为0.1。感觉还落下了m_nResultCount和m_dResultPossibility的初始化,前者应该为1后者应该全为0。
析构函数释放所有的空间,这里主要是m_pResult的空间,其他词典及相应的标注器都会在析构函数中自行释放空间。该类的初始化和析构过程都非常慢,应该考虑是否有优化的办法。
提供了三个级别的切分及标注接口。FileProcessing读文件并将结果输出到指定的文件名中。ParagraphProcessing读段落并将结果输出到给定的buffer中。Processing则是将句子的切分标准结果输出到内置的m_pResult中,可以生成多个结果,但是还需要进一步使用Output函数输出到相应的buffer中。前两个函数最终都要调用Processing来进行真正的处理过程。
FileProcessing中使用fgets读入最多4*1024-1个字符,有可能一段文字非常长有可能发生截断成半个字符或者不完整的情况都没有处理。将读入的段落送入ParagraphProcessing函数中进行处理生成的结果写入指定的文件,如果是XML文件格式输出则还会控制前后的XML标签<>的生成。
ParagraphProcessing函数处理一段文字。它需要将一段文字根据标点符号切成若干子句。子句首尾会根据需要添加上句子开始结束标记后再送入Processing进行处理。使用了一些技巧来减少重复操作,加快速度。依次读入字符(可能是全角可能是半角,这个部分处理得不好,应该写一个函数来操作,否则代码变得很长)。如果不是全角的句子(子句)结束符、不是半角的句子(子句)结束符也不是是段落结束符,则追加到待处理的句子串中再取下一个字符,否则进一步处理。对于已经生成的待处理句子串,如果非空也不是只有开始标记送入Processing进行处理,只生成一个结果,并将生成的切分结果输出并追加到结果串中。注意如果待处理句子串的最后一个字符不是子句结束符则要追加一个句子结束符。处理结束之后,要重置待处理的句子,如果最后一个字符是换行符或者是句子结束符则重置新句子为开始标记,否则简单的把原来的最后一个字符拷入新句子。待用来读字符的索引超出段落长度之后,不要忘记把未处理完的句子送入Processing进行切分。
Processing的过程就相对简单了。应该首先清空m_Seg和m_pResult中相应的sWord的值,原始函数中没有可能造成一些错误在新版本中已改正。将传入的句子使用切分器m_Seg进行二元切分BiSegment,生成的结果会存储在m_Seg.m_pWordSeg中,结果个数存在m_Seg.m_nSegmentCount中。然后对于每个生成的结果(一般为1,最多10个),分别使用三个未登陆词识别器进行识别,结果存在m_Seg.m_graphOptimum中。然后调用m_Seg的二元优化切词(m_Seg.BiOptimumSegment),即根据m_Seg.m_graphOptimum的结果重新进行切词。对于生成的每个结果(存在m_Seg.m_pWordSeg中),调用词性标注器m_POSTagger进行词性标注。最后将生成的结果进行排序,最终的结果会按概率高低存储在m_pResult中。
Sort函数很简单,就是用冒泡法将每个结果的概率从大到小排序然后相应的按概率将结果存到m_pResult中。这里会调用两个函数,ComputePossibility用于计算传入的结果的概率值,Adjust用于调用输出的结果。
ComputePossibility的计算公式是,对于传入的一种切分结果,其概率值等于每一项的概率值dValue,再加上这个词和后面那个词的上下文无关概率值加1以后取log的值,再减去这个词本身词性标记的概率值加1以后取log的值。
Adjust函数则是在最后引入了一些规则来处理统计方法无法涵盖的情况。
规则一:对于中国人名,会将其切成姓加名的形式(还有可能是双姓);
规则二:对于ABB这种切散了的格式(一段段、一片片)会合并;
规则三:对于AA这种切散了的格式会合并,并且根据原始的词性重新设置新的词性,默认为a,如果原来都是名词则修改为名词,如果原来都是动词则修改为动词。另外,会进一步判断是否为AAB这种格式,如果符合则进一步合并。例如洗/洗/脸、蒙蒙亮。
规则四:对于没有切散的AAB格式,一般会切成A/AB的类型,例如洗/洗澡。这种情况下合并并重新设置词性。默认为’a’,如果原来为动词则仍然为动词。
规则五:如果原来词性为uj,ud,uv,uz,ul,ug等等,则统一输出为u。
规则六:对于AABB型的切散成A、AB、B的格式,则合并。词性取AB的词性;
规则七:对于后缀类型的词(28275),如果后面一个词在地名词典中出现则合并,或者后面一个词的长度为1(即一个汉字)且是“队语文字杯裔”中的任何一个则合并并相应的设置新的词性。
规则八:如果词性为30208或者28160且后面一个词是单字“员”则合并,例如运动员;
规则九:如果词性为28280(未登陆词)且后面的一个词是未登陆词、或者是全角或半角的符号点或者后面的词是数字串则合并起来,例如/www/nx ./w sina/nx
规则十:默认规则,即如果上述规则都不满足则简单的将原来的切词结果拷入。
使用上述规则进行处理后得到最终的切词结果。
Output函数将PWORD_RESULT类型数据中的切词结构转成字符串表示形式。依次读切词数据链,对于每个词条,将其词性标记转换为字符串表示方式。只在这个函数中涉及输出的格式问题和词性标注的程度问题。PKU2973POS函数就用来将北大标准的词性标记集转换为973标准,即做一个一一对应。
ChineseNameSplit是一个额外的函数用于将中国人名的姓和名两部分切分开。就是查人名典来确定姓与名的分割点。
另外,在新添的版本中增加了一个重置函数ResetWord,用于清理每次计算完成之后的m_pResult的相应sWord值。

ICTCLAS1.0版的源码已经差不多看完了,除了上下文无关文法那块有些具体的含义没有明确以外其他的代码基本理清。下面应该是给出一些总体性的框架来进一步帮助整理思路的时候了,呵呵。



上面是一个简单的数据流程图,淡蓝色的部分为数据,淡绿色的部分为函数或相应函数的功能概括。很多细节无法在这张图中展示,但也可以基本看出数据的走向和整个系统的框架。
程序的实用化,要考虑如下几个方面的问题:
1. 如何更好的使用用户词典;
一般的策略有两种,预处理和后处理,无论是哪种,用户词典都很难给出我们所需要的相应概率。前处理的方法把用户词典看作普通词典并给出一个默认的概率值,统一进行切分;后处理的方法一般就只能是最大匹配了。两种方法的速度都不会很快,而且不可避免引入错误。尤其是组合型歧义。
2. 如何避免系统崩溃级的错误;
目前的版本因为采用的是预定义空间大小的方法,对于一些特别的例子非常容易出现数组越界的情况。能够减少数组越界的方法要么是增加预定义空间的大小,但是很可能申请不到那么多的内存;另外一种方法是增强越界的检测,但是模块之间的耦合变大,而且越界部分的
处理结果会丢失;再就是使用动态分配内存的方法,但是涉及到多次的构造析构操作速度会变慢。
3. 如何加快分词的速度。
仅从代码级而言就有很多地方可以改进系统的性能,例如使用一些更好的数据结构,对于一些常量的数组不要每次调用函数都重新分配而是使用static变量等。但是这些改进不足以使得系统的性能大幅度提高;速度减慢的主要原因一个是查词典速度很慢,虽然已经用了二分查找达到Logn的级别,但是还有提高的余地,例如使用双Trie树的数据结构;另外一个速度慢的原因是大量的内存申请释放操作。提高速度也可以考虑放弃一部分的正确性使得部分模块简化或者干脆不用。
对于程序的代码风格,不得不说存在两大问题:
1. 很多空间都是采用预定义大小的方式,但对于空间大小的检测并不完全,使得数组越界的潜在危险始终存在;
2. 函数传递时不用const类型,使得代码的可读性下降,尤其是词典类的使用和一些字符串参数的情况,建议还是应该使用const类型来帮助阅读。
原创粉丝点击