ICTCLAS代码学习笔记之Csegment类

Csegment类的相关学习笔记,重拾炉灶,嘿嘿。
CSegment类是一个比较重要的类,词语切分的核心工作是在这一部分完成的。成员变量m_pWordSeg是一个指向WORD_RESULT 的二维指针([MAX_SEGMENT_NUM][MAX_WORDS])。注意二维数组的大小是在构造函数中预定义的,MAX_SEGMENT_NUM为10,MAX_WORDS为650,有可能会溢出,程序对于MAX_SEGMENT_NUM做了控制,但是MAX_WORDS却没有做控制,存在潜在危险。这个二维数组用来存切分以后的词而且是个公有的成员变量。另外,类还有两个主要的公有成员变量m_graphOptimum(CDynamicArray类型)和m_graphSeg(CSegGraph类型),分别存优化的分词词图和词图。注意二者的类型不同,具体使用见后。
另外,还有两个私有成员变量,m_npWordPosMapTable是一个中间结果,用于存储候选词的位置,而m_nWordCount则用来做个计数。
CSegment类的成员变量总结起来如下:
class CSegment
{
public:
PWORD_RESULT *m_pWordSeg; //!分词的词串结果
int m_nSegmentCount; //!N-ShortPath的个数N
//The segmentation result
CDynamicArray m_graphOptimum;//The optimumized segmentation graph
CSegGraph m_graphSeg;//The segmentation graph
private:
int *m_npWordPosMapTable;//Record the position map of possible words
int m_nWordCount;//Record the End position of possible words
};
使用CSegment类的相应函数涵盖着HHMM多层结构中的第5层(原子切分)、第4层(简单未登陆词识别)和第3层(递归未登陆词识别)。前后数据处理均在此类中实现。其处理过程及各个函数分析如下:
对于传入的一个句子的子句(这里是指被预定义的分隔符切开的部分),先对其进行2元切分(BiSegment),然后对于生成的每个结果(m_nSegmentCount),分别进行未登陆词识别(人名、外国人名及地名)识别结束后对结果进行优化(BiOptimumSegment)。优化的结果通过m_pWordSeg传给词性标注器进行词性标注,最后对结果进行排序并输出。
构造函数和析构函数就是分配和释放相应的空间,因为空间是根据预定义的宏的大小来分配的,可能会出现溢出的情况。另外就是原始代码中对空间的释放不完全,对于m_pWordSeg[i]的释放
应该使用delete[],而且也没有释放m_npWordPosMapTable的相应空间,在修改的代码中均已做了处理。
Segment函数在实际的代码中并没有用到,和BiSegment函数的功能类似,但是后者多利用了一些信息。Segment函数首先分配存储路径的空间,然后根据传入的子句和核心词典生成词图,并根据词图的结果进行NSP算法计算相应的路径,最后输出结果。而BiSegment函数还引入了平滑参数dSmoothingPara及二元词典dictBinary来进行切词。因此在生成一元词图之后还要根据二元词图进一步生成二元词图以传入NSP算法类中进行路径计算。生成的结果也要先经过一步由二元路径到一元路径的转换之后才输出结果。
GenerateWord函数是一个重要的函数,它根据传入的二维数组nSegRoute(指针)中相应的路径将分好的词的相关信息依次拷贝到m_pWordSeg[nIndex]的各个元素,注意这里没有对nSegRoute[nIndex][i]和[m_pWordSeg[nIndex][k]中的i及k做任何越界的测试,而实际上这两个buffer的大小都是有限制的,这也就为日后较大较长的句子切分造成越界埋下了隐患。这里的nIndex是指生成多个分词结果时的结果索引,一般我们只得到一个结果也就多为0,而且nIndex不会超过最多结果MAX_SEGMENT_NUM(在生成时做了判断)因此不会越界。
在生成词的过程中,主要是考虑到了数词串及时间词串的差别及合并问题。即根据当前切词的结果,考虑其后面的连续若干个词合并的结果,如果合并结果是一个数词的话,那么就保留这个合并结果。这里有一个小的问题就在于诸如“二分之一”这样的例子,会在前面的粗分时被切为“二分”“之一”,前面一个候选词送入此循环时不被判断为数词因此合并后面结果之后的“二分之一”不会进行循环被判断为数词,造成切分错误。另外就是通过一些规则来判断类型是否为时间串“未##时”等。在时间词部分的判断上,对于“月份”、“点钟”、“刻钟”这一类较明确的时间词后缀没有做处理,另外对于时间词串的判断也过于粗略,例如“3﹒15分”、“2000分”等也被判断为时间词。对于“点”这个容易引起歧义的时间词后缀(数词表示小数点)的判断也不够完善,诸如“三点五十分“这种例子前面的“三点“词性标注并不正确也是由于此处判断不足造成的。对于年代词专门有一个成员函数来进行判断,而其他时间词的判断不够完善。
函数的最终结果是根据路径信息nSegRoute[nIndex],将切好的每个词都送入m_pWordSeg[nIndex]的相应位置。个人觉得这部分代码显得比较凌乱,一是没有越界的判断,二是规则多,代码本身也较长,容易潜藏错误。
2006-9-6的学习笔记
继续读CSegment,感觉时间上压力好大,唉。

OptimumSegmet函数和后面的BiOptimumSegment函数也是对应的关系,前者处理一元切词,二者则是二元的,分别对应Segment和BiSegment。(所以OptimumSegmet在目前的代码中其实也没有用到)。其实过程都很简单,就是新开一块空间,使用优化词图m_graphOptimum中的内容重新进行一次NSP算法,抛弃原来的词图,使用优化词图并根据新开辟空间中的路径结果生成切分结果(调用GenerateWord函数)。BiOptimumSegment函数的基本过程如上,就是类似BiSegment函数中加入了二元词典、平滑参数,并且NSP算法用的是二元词图,后成的路径结果还要改为一元路径。最后,不要忘记释放分配的空间。

GetResultCount函数比较简单,就是根据传入的“链”计算“有效”元素的个数。这里的“有效”就是指词串非空,那么之前的清理工作就一定要注意了。程序本身没有对传入的参数的有效性做判断,不是很好。
GetLastWord函数也如其名,根据传入的“链”求最后一个“有效”元素的词串。要么通过调用GetResultCount来求得最后一个元素的索引值来直接定位,要么一边搜索一边保存现场。程序本身采用的是第二种方法,但是中间过程的拷贝其实都是没有必要的,浪费了时间。另外,程序本身也没有对传入参数的有效性做判断(指针是否为空),有潜在的危险。
IsYearTime函数用于判断传入的字符串是否为年代词,接受大概以下几种格式的输入为年份:
1. 诸如XXXX,其中X为单字节,这里判断可能有误,例如输入为AAAA此类;
2. 诸如XX,其中X为单字节且第一个字符的值大于’4’,形如50年,92年之类;这里的4不知道是怎么确定的;同样存在上面的误判断的问题;
3. 全是数字且长度不小于6,例如“588年” 、“1989年”
4. 全是数字且长度为4且头一个字符是“56789”中的某个,例如56年;
5. 由“零○〇一二三四五六七八九壹贰叁肆伍陆柒捌玖”组成的两字以上串,程序中判断组成字符数时稍微有些问题,不应该nLen/2而是把2乘到等号左边,因为除2存在取整问题;
6. 四字串且包含两个“千仟零○〇“,例如二仟零二年
7. 一字串为“千”或“仟”,后面跟年也构成时间串
8. 两字串为古时年代表示方法,诸如“甲子年”。
BiGraphGenerate函数用于生成二元词图,传入的参数aWord中已经包含了一元词图的信息,而生成的结果则写入aBinaryWordNet中。函数中通过一个动态大小的一维数组m_npWordPosMapTable记录下一元词图中的路径信息,然后遍历一元词图中的所有结点,对于每个结点,获取词的频率值,求得当前结点词的后一个词,如果允许其组合,则查二元词典DictBinary获得其组合概率并更新aBinaryWordNet的相应位置的值。 程序中的变量pTail似乎没有啥用处。
BiPath2UniPath函数将二元路径改写为接受输出的一元路径。根据原始的一元词图位置信息及后来生成的二元词图切词位置信息更新得到。注意BiSegment函数以相应的BiOptimumSegment函数中,二元词图生成之后,根据NSP算法计算相应的路径信息并输出到了预先开好的空间nSegRoute中,对于每个nSegRoute[i]即为一种切分方法,BiPath2UniPath对于输入的这个一维数组nSegRoute[i]做处理。m_npWordPosMapTable中的信息包含词图的行值及列值,而nSegRoute[i]则只是列值,所以根据m_npWordPosMapTable中当前结点的行值和列值取余后更新结点的列值,即下一个切分的位置。这里只需要注意收尾的问题。
原始的程序没有做清理数据的操作,因此在新的版本中加入了一个ClearSegmentWord函数,主要是用于将m_pWordSeg中的所有字符串sWord重置以便诸如GetResultCount、GetLastWord一类使用sWord作为结束判断依据的函数不会出错。
CSegment类的总结:
这个类的内容稍微有些多,除了一些辅助功能性的成员函数,个人认为最主要的是四个,即BiSegment、BiOptimumSegment、BiGraphGenerate和GenerateWord。前两个函数用于做二元切分,过程基本上是一致的,都是先分配存储路径的一块空间,根据句子、核心词典及二元词典生成二元词图,然后将生成的词图送去做NSP算法求得粗分结果存储在预分配的空间中,然后对粗分的二元结果进行二元路径到一元路径的转换并切成词输出,最后释放分配的空间,只不过BiSegment用的是m_graphSeg.m_segGraph做词图,BiOptimumSegment 用的是m_graphOptimum并且最后抛弃了原来的m_graphSeg.m_segGraph。这两个函数完全可以合并大部分。BiGraphGenerate的主要技巧在于用一个一维数组来存储切分结果的二维信息(这是由于二维数组的行数是固定的),另外就是根据二元词典估算二元结果的概率了。GenerateWord函数根据切分结果再做一些时间词数词的组合,代码写得有些凌乱,但主要含义还是出来了,希望可以对其中的一些规则部分提出来做一定的整合以方便阅读。
至此,Segment目录下的所有文件全部分析完了,这也是比较核心的一部分代码,长出一口气!在粗分结束之后,要进行的就是未登录词(包括命名实体)部分的工作了,这一部分的代码主要是在Unknown目录下的UnknowWord.h和UnknowWord.cpp文件中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值