DanceFire的专栏

天地不仁,以万物为刍狗

用户操作
[即时聊天] [发私信] [加为好友]
舞焰ID:DanceFire
266927次访问,排名230好友0人,关注者1
DanceFire的文章
原创 51 篇
翻译 4 篇
转载 0 篇
评论 928 篇
DanceFire的公告
最近评论
hqfmyway:出现过同样的问题,谢谢解惑!
phhwr85:非常感谢,正郁闷中!
herry0628:A gold website for wow gold and
buy wow gold sevise.
herry0628:A gold website for wow gold and
buy wow gold sevise.
herry0628:A gold website for wow gold and
buy wow gold sevise.
文章分类
收藏
    相册
    Unix家族族谱图
    编程语言家族族谱
    操作系统相关图片
    插图
    麒麟操作系统相关图片
    L4 微内核实现
    Fiasco - DROPS的底层微内核 (TU Dresden)
    L4Ka::Pistachio (UKa, UNSW)
    seL4 - Secure Embedded L4 (UNSW)
    L4 微内核研究组
    L4 总部
    UKa的L4研究组
    UNSW/NICTA的L4研究组
    L4 文档
    L4 X.2 API的用户手册(UNSW)
    L4-embedded 参考手册 N1 (UNSW)
    基于 L4 的操作系统
    Darwin在L4上的移植 - Darbat (UNSW)
    DROPS - 基于L4的嵌入式实时操作系统 (TU Dresden)
    GNU Hurd在L4上的移植
    Linux在L4上的移植 - L4Linux (TU Dresden)
    Linux在L4上的移植 - Wombat (UNSW)
    Mungi - Single Address Space OS based on L4 (UNSW)
    朋友
    Dancefire's website
    ralph623的专栏(RSS)
    sinboy的菜地(RSS)
    Sunwear(RSS)
    Xinsoft :应用之美,在于药到病除(RSS)
    吕震宇(RSS)
    强强专栏(RSS)
    旁观生活的BT(RSS)
    潇寒的Blog(RSS)
    龙真先生(RSS)
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 中科院中文分词系统ICTCLAS之CSegment的GenerateWord()详细分析收藏

    新一篇: 中科院中文分词系统ICTCLAS之人名识别词典分析 | 旧一篇: 中科院中文分词系统ICTCLAS之NShortPath代码的详细分析

    一、简介

    这次分析的是ICTCLAS中的

    //Generate Word according the segmentation route
    bool CSegment::GenerateWord(int **nSegRoute, int nIndex)

    本来这个函数没有必要详细分析,但是我注意到中科院论文中并没有描述这个函数、而Sinboy和吕震宇也基本上跳过这个函数不讲了,所以这个函数还没有有人详细的分析过呢。在这里,我具体的分析一下这个函数,另外,也提出一些问题供打算重写ICTCLAS的朋友来考虑。

    二、功能介绍

    这个函数虽然叫做GenerateWord,但是事实上并不仅仅是生成词,准确说,它大部分的工作不是为了将已经计算好的结果以词的形式显示出来,而是处理各种格式的数字。

    数字实际上属于Named Entity的一种,属于未登录词识别的一部分。按理说应该像ICTCLAS处理人名、地名的方法一样,利用隐马,利用概率来处理。不过可惜的是,作者在对于数字的处理上,没有能够使用概率的优势来排除歧义,而采取了另一种方式,规则,来进行数字的合并和切分。在稍后,我们会讲述ICTCLAS在数字处理上出现的一些问题。我们先看看它所处理的规则:

    1. 合并所有的数字
    2. 针对 [数字][-—][数字] 的形式把减号从中分离出来。
    3. 合并:[数字]([月日时分秒]|月份)
    4. 合并:[数字][年]
    5. 处理时间格式:.*[点]$
    6. 处理数字格式:[∶·././]$
    GenerateWord()对满足上述条件的字符串进行拆分和合并的操作,这就是GenerateWord()的主要工作。

    三、代码分析

    //Generate Word according the segmentation route
    bool CSegment::GenerateWord(int **nSegRoute, int nIndex)
    {
        unsigned 
    int i=0,k=0;
        
    int j,nStartVertex,nEndVertex,nPOS;
        
    char sAtom[WORD_MAXLENGTH],sNumCandidate[100],sCurWord[100];
        ELEMENT_TYPE fValue;
        
    //    nSegRoute的是原子位置的数组
        
    //    循环,i初始为0,判断(i)和(i+1)有效,并判断nSegRoute[nIndex][i] < nSegRoute[nIndex][i+1]
        
    //    这里为什么要加一个小于来判断?必然前面的小于后面的。
        while(nSegRoute[nIndex][i]!=-1&&nSegRoute[nIndex][i+1]!=-1&&nSegRoute[nIndex][i]<nSegRoute[nIndex][i+1])
        
    {
            nStartVertex
    =nSegRoute[nIndex][i];
            j
    =nStartVertex;//Set the start vertex
            nEndVertex=nSegRoute[nIndex][i+1];//Set the end vertex
            nPOS=0;
            
    //    取得该分段(粗分的词)的词性(nPOS)和词频(fValue)
            m_graphSeg.m_segGraph.GetElement(nStartVertex,nEndVertex,&fValue,&nPOS);
            
            
    //    将该分段对应的词保存进sAtom
            sAtom[0]=0;
            
    while(j<nEndVertex)
            
    {//Generate the word according the segmentation route
                strcat(sAtom,m_graphSeg.m_sAtom[j]);
                j
    ++;
            }


            
    //    将sAtom的值赋给sNumCandidate
            m_pWordSeg[nIndex][k].sWord[0]=0;//Init the result ending
            strcpy(sNumCandidate,sAtom);

            
    //    如果sNumCandidate全是数字的话,进行特殊处理。
            
    //    这里判断了sNumCandidate是不是全是半角数字或者全角数字。
            
    //    *需要注意的是*,IsAllChineseNum() 还有部分日期数字的判断功能,不仅仅是全角判断
            while(sAtom[0]!=0&&(IsAllNum((unsigned char *)sNumCandidate)||IsAllChineseNum(sNumCandidate)))
            
    {
                
    //Merge all seperate continue num into one number
                
    //sAtom[0]!=0: add in 2002-5-9
                
    //    k? 在遥远的前方,在那函数入口的地方,被初始为0
                
    //    将sNumCandidate对应的词放到结果集m_pWordSeg[nIndex][k].sWord中
                strcpy(m_pWordSeg[nIndex][k].sWord,sNumCandidate);
                
    //Save them in the result segmentation

                
    //    开始看下一个分段,将下一段的文字放到sAtom中
                i++;//Skip to next atom now 
                sAtom[0]=0;
                
    //    *注意* 这里是[i+1],而不是i,也就是说sAtom里面是下一个词啦。
                while(j<nSegRoute[nIndex][i+1])
                
    {//Generate the word according the segmentation route
                    strcat(sAtom,m_graphSeg.m_sAtom[j]);
                    j
    ++;
                }

                
                
    //    将sAtom追加到sNumCandidate中。
                
    //    下一个循环的时候依旧再看一下这个sNumCandidate是否是数字。
                
    //    如果是数字,就替换原有的m_pWordSeg[nIndex][k].sWord
                strcat(sNumCandidate,sAtom);
            }

            
    //    点评:如果仅仅是为了合并所有的数字,这个循环臃肿了。
            
    //    可以直接循环判断出数字所在的范围,然后一次性的追加即可。
            
    //    合并数字这件事情应该在原子处理的时候进行处理,唯一需要注意的是含有数字的日期的合并放在原子上可能并不合适。
            
    //    至于合并含有除了[0-90-9零-九]以外的字符,比如分之,大写数字,[几数第上成] *,应该放在这里,但不能是原子切分那里。

            
    //    
            unsigned int nLen=strlen(m_pWordSeg[nIndex][k].sWord);
            
    if
                (nLen
    ==4 && CC_Find("第上成±—+∶·./",m_pWordSeg[nIndex][k].sWord))
                
    ||
                (nLen
    ==1 && strchr("+-./",m_pWordSeg[nIndex][k].sWord[0]))
            )
            
    {
                
    //    这里的判断很不解。
                
    //    第一条是判断前缀是否是数字的前缀,但是为什么长度是4?如果长度是4的话,永远都无法满足啊?恐怕这里的长度应该是写2的。
                
    //    第二条是判断如果只有一个字符而且还是数字的前缀。
                
    //Only one word
                strcpy(sCurWord,m_pWordSeg[nIndex][k].sWord);//Record current word
                
    //    i--?为啥让i退一步呢?
                
    //    什么情况能进这个条件判断语句块呢?我们在上面的循环得到了字符的前缀,可是却发现后面的字符不是数字。
                
    //    i--,是说既然后面不是数字,那么我们在前面while loop里面的那个i++就盲目了。我们需要退回到这个"第上成"这个字接着考虑其他case。
                i--;
            }

            
    else if(m_pWordSeg[nIndex][k].sWord[0]==0)//Have never entering the while loop
            {
                
    //    因为当前词不是数字,没能够进入前面的循环。
                
    //    将当前词放入结果,并且记录当前词。
                strcpy(m_pWordSeg[nIndex][k].sWord,sAtom);
                
    //Save them in the result segmentation
                strcpy(sCurWord,sAtom);//Record current word
            }

            
    else
            
    {
                
    //    到这里就说明进入了前面的while loop,而且不仅仅是一个前缀而已,是个真的数字。
                
    //    真的么?看下面两个if判断的意思,还是可能出现不是数字的,因此还需要i--退一步考虑。
                
    //It is a num
                if(
                    
    //    看保存在记录里的词是不是"--","—"或者仅仅是一个"-"
                    strcmp("--",m_pWordSeg[nIndex][k].sWord)==0
                    
    ||
                    strcmp(
    "",m_pWordSeg[nIndex][k].sWord)==0
                    
    ||
                    m_pWordSeg[nIndex][k].sWord[
    0]=='-' && m_pWordSeg[nIndex][k].sWord[1]==0
                )
    //The delimiter "--"
                {
                    
    //    设置词性为 'w' : 标点符号。显然也不是数字了,所以i--,退回一个词。
                    nPOS=30464;//'w'*256;Set the POS with 'w'
                    i--;//Not num, back to previous word
                }

                
    else
                
    {
                    
    //Adding time suffix

                    
    char sInitChar[3];
                    unsigned 
    int nCharIndex=0;
                    
    //Get first char
                    
    //    取第一个字符。这里是通过判断char是否小于零,从而判断是不是汉字,需不需要追加一个字符的。
                    sInitChar[nCharIndex]=m_pWordSeg[nIndex][k].sWord[nCharIndex];
                    
    if(sInitChar[nCharIndex]<0)
                    
    {
                        nCharIndex
    +=1;
                        sInitChar[nCharIndex]
    =m_pWordSeg[nIndex][k].sWord[nCharIndex];
                    }

                    nCharIndex
    +=1;
                    sInitChar[nCharIndex]
    ='';
                    
                    
                    
    //    这个长长的判断是干嘛的呢?我改写为缩进格式,更利于逻辑上的理解。
                    
    //    其实就是为了把原来的[数字], [-][数字],的分词调整为:[数字]、[-]、[数字]
                    
    //    我们看看具体的实现。先进行条件判断:
                    if(
                        
    //    1、只考虑第二个词及其以后的词,因为这里需要考虑前一个词的词性
                        k > 0
                        
    &&
                        
    //    2、前一个词的词性是0x6D00('m')数字或0x7400
                        (
                            abs(m_pWordSeg[nIndex][k
    -1].nHandle) == 27904
                            
    ||
                            abs(m_pWordSeg[nIndex][k
    -1].nHandle) == 29696
                        )
                        
    &&
                        
    //    3、第一个字符是减号
                        (
                            strcmp(sInitChar,
    ""== 0
                            
    ||
                            sInitChar[
    0== '-'
                        )
                        
    //    4、除了第一个字符还有别的字符。呵呵,其实只有负号的已经在前面被过滤了,按理说这里不该再担心这个问题了。
                        &&
                        (
                            strlen(m_pWordSeg[nIndex][k].sWord)
    >nCharIndex)
                        )
                    
    {
                        
    //    这个条件判断到底是什么条件?下面这个注释注释的好,无非就是针对:
                        
    //        [数字][-—][数字]
                        
    //    的形式把减号从中分离出来。汗一下……
                        
    //3-4月
                        
    //27904='m'*256
                        
    //Split the sInitChar from the original word
                        strcpy(m_pWordSeg[nIndex][k+1].sWord,m_pWordSeg[nIndex][k].sWord+nCharIndex);
                        m_pWordSeg[nIndex][k
    +1].dValue=m_pWordSeg[nIndex][k].dValue;
                        m_pWordSeg[nIndex][k
    +1].nHandle=27904;
                        m_pWordSeg[nIndex][k].sWord[nCharIndex]
    =0;
                        m_pWordSeg[nIndex][k].dValue
    =0;
                        m_pWordSeg[nIndex][k].nHandle
    =30464;//'w'*256;
                        
                        
    //    将分离出的减号加入优化后的图。
                        m_graphOptimum.SetElement(
                            nStartVertex,
                            nStartVertex
    +1,
                            m_pWordSeg[nIndex][k].dValue,
                            m_pWordSeg[nIndex][k].nHandle,
                            m_pWordSeg[nIndex][k].sWord
                            );
                        nStartVertex
    +=1;
                        k
    +=1;
                    }


                    
    //    取得第k个词的长度。如果进了上述循环,那么nLen长度是第二个[数字]的长度。
                    nLen=strlen(m_pWordSeg[nIndex][k].sWord);
                    
    //    如果sAtom是 [月日时分秒]或者 "月份" 的话。
                    
    //    等等~,sAtom和m_pWordSeg[nIndex][k].sWord难道还不一样么?
                    
    //    从前面的代码看,sAtom最多也就比sWord多一个减号啊?
                    
    //    我们再回去看第一个循环的时候就会注意到,在那里,sAtom被赋予了[i+1]的字符串
                    
    //    也就是说sAtom实际上已经是下一个词了。
                    
    //    那么重新解释一下下面判断的意思就是:
                    
    //        [数字]([月日时分秒]|月份)
                    if(
                        (
                            strlen(sAtom) 
    == 2
                            
    &&
                            CC_Find(
    "月日时分秒",sAtom)
                        )
                        
    ||
                        strcmp(sAtom,
    "月份"== 0
                    )
                    
    {
                        
    //    如果是如下模式:
                        
    //        [数字]([月日时分秒]|月份)
                        
    //    将他们视为同一个词,加入到m_pWordSeg里,
                        
    //    并且将sCurWord设置为"未##时",词性为't'
                        
    //2001年
                        
    //       ^--- 啊?兄弟弄错了吧?下一个条件才是年呢。这个是月啊。:)
                        strcat(m_pWordSeg[nIndex][k].sWord,sAtom);
                        strcpy(sCurWord,
    "未##时");
                        nPOS
    =-29696;//'t'*256;//Set the POS with 'm'
                    }

                    
    else if(strcmp(sAtom,"")==0)
                    
    {
                        
    //    同上表示的话,应该是这个模式:
                        
    //        [数字][年]
                        
    //    通过IsYearTime确认[数字]是合法的数字
                        
    //    如果满足就将年追加其后,将当前词改为特征词"未##时",并且词性改为't'
                         if(IsYearTime(m_pWordSeg[nIndex][k].sWord))//strncmp(sAtom,"年",2)==0&&
                         {//1998年,
                            strcat(m_pWordSeg[nIndex][k].sWord,sAtom);
                            strcpy(sCurWord,
    "未##时");
                            nPOS
    =-29696;//Set the POS with 't'
                         }

                         
    else
                         
    {
                             
    //    如果不满足,那么这些数字就仅仅是数字
                             
    //    将当前词改为特征词"未##数",词性设为'm'--数字。
                             
    //    并且因为不是时间,所以就得i--退一步。
                            strcpy(sCurWord,"未##数");
                            nPOS
    =-27904;//Set the POS with 'm'
                            i--;//Can not be a time word
                         }

                    }

                     
    else
                    
    {
                        
    //    又不是月份,又不是年,那现在看看是不是时分秒。
                        
    //早晨/t  五点/t 
                        
    //        .*[点]$
                        
    //    看看是不是以"点"结尾的
                        if(strcmp(m_pWordSeg[nIndex][k].sWord + strlen(m_pWordSeg[nIndex][k].sWord) - 2,""== 0)
                        
    {
                            
    //    如果是的话就改sCurWord为特征词"未##时",词性为't'
                            strcpy(sCurWord,"未##时");
                            nPOS
    =-29696;//Set the POS with 't'
                        }
        
                        
    else 
                        
    {
                            
    //    如果不是以[∶·././]结尾的,那就改sCurWord为特征词"未##数",词性为'm'
                            if(
                                
    !CC_Find("∶·./",m_pWordSeg[nIndex][k].sWord+nLen-2)
                                
    &&