ICTCLAS分词关键技术



总体流程
考虑输入的一句话,sSentence="张华平欢迎您",分词流程如下
1、分词 "张/华/平/欢迎/您"
2、posTagging "张/q 华/j 平/j 欢迎/v 您/r"
3、NE识别:人名识别,音译名识别,地名识别 "张/q 华/j 平/j 欢迎/v 您/r" "张华平/nr"
4、重新分词:"张华平/欢迎/您"
5、重新posTagging: "张华平/nr 欢迎/v 您/r"
 
技术细节
一、分词
1、原始句子:sSentence=张华平欢迎您
2、在头末添加开始符和结束符:sSentence="始##始张华平欢迎您末##末"
3、进行分词,基本思想就是分词的得到的词的联合概率最大:
     假设 "张华平欢迎您" 分为 "w_1/w_2/.../w_k" 则
     w_1/w_2/.../w_k=argmax_{w_1'/w_2'/.../w_k'}P(w_1',w_2',...,w_k')=argmax_{w_1'/w_2'/.../w_k'}P(w_1')P(w_2')...P(w_k')

 细节:
 首先给原句按字划分,所有汉字一个一段,连续的字母,数字一段,

         比如"始##始张华平2006欢迎您asdf末##末" 被划为

        "始##始/张/华/平/2006/欢/迎/您/asdf/末##末"
 接着找出这个句子中所有可能出现的词,

        比如"始##始张华平欢迎您末##末",

        出现的词有"始##始","张","华","平","欢","迎","您","末##末","欢迎" (最细粒度分词),并查找这些词所有可能的词性和这些词出现的频率。
 将这些词保存在一个结构中,具体实现如下:
        m_segGraph中有一个(PARRAY_CHAIN)m_pHead,是一个链       

(PARRAY_CHAIN)p->row//记录该词的头位置
(PARRAY_CHAIN)p->col//记录该词的末位置
 (PARRAY_CHAIN)p->value//记录该词的-log(出现的概率),出现的频率指所有该词的所有词性下出现的概率的总和。
(PARRAY_CHAIN)p->nPos//记录该词的词性,比如人名标记为'nr',则对应的nPos='n'*256+'r',如果该词有很多词性,则nPos=0
(PARRAY_CHAIN)p->sWord//记录该词
(PARRAY_CHAIN)p->nWordLen//记录该词的长度
       举个例子:
               "0 始##始 1 张 2 华 3 平 4 欢 5 迎 6 您 7 末##末 8"
               对于"张"来说,
                        row=1
                        col=2
                       value=-log[("张"出现的频率+1)/(MAX_FREQUENCE)]
                       nPos=0//"张"有5种词性
                      sWord="张"
                     nWordLen=2
     保存的顺序是按col升序row升序的次序排列
     m_segGraph.m_pHead "始##始"
                                           "张"
                                           "华"
                                           "平"
                                           "欢"
                                           "欢迎"
                                           "迎"
                                           "您"
                                          "末##末"
  
      m_segGraph.m_nRow=7
      m_segGraph.m_nCol=8

    然后是生成一幅给予各种组合情况的图,并按照出现的概率大小保存概率最大的前m_nValueKind个结果。

    细节:
    初始化,         

(CNShortPath)sp.m_apCost=m_segGraph;
(CNShortPath)sp.m_nVertex=m_segGraph.m_nCol+1
(CNShortPath)sp.m_pParent=CQueue[m_segGraph.m_nCol][m_nValueKind]
(CNShortPath)sp.m_pWeight=ELEMENT_TYPE[m_segGraph.m_nCol][m_nValueKind]//m_pWeight[0][0]表示1处的weight

  sp.ShortPath()函数中,

for(nCurNode=1;nCurNode<sp.m_nVertex;nCurNode++)
{
  CQueue queWork;//临时的CQueue
  eWeight=m_apCost->GetElement(-1,nCurNode,0,&pEdgeList);//取出col=nCurNode的第一个PARRAY_CHAIN的value,比如nCurNode=6,则pEdgeList指向"欢迎",eWeight="pEdgeList->value
  while(pEdgeList&&pEdgeList->col==nCurNode)//对每一个col=nCurNode的pEdgeList
  {
    for(i=0;i<m_nValueKind;i++)
    {
      queWork.Push(pEdgeList->row,0,eWeight+m_pWeight[pEdgeList->row-1][i]);
      //将所有col=nCurNode的pEdgeList按照其weight升序放到queWork中
     }
   }//比如
}
  
    /*
     "欢迎"      m_pWeight[3][0]=0.2     eWight=0.2    =>queWork.Push(4,0,0.4);
     "0 始##始 1 张 2 华  3 平  4 欢  5 迎 6 您 7 末##末 8"
     "欢"        m_pWeight[4][0]=0.5 eWight=0.1  =>queWork.Push(5,0,0.6);
                   m_pWeight[4][1]=0.6 eWight=0.1  =>queWork.Push(5,0,0.7);
   
    queWork  "欢迎"  0.4
                    "迎"  0.6
                    "迎"  0.7   
    */  
for(i=0;i<m_nValueKind;i++)m_pWeight[nCurNode-1][i]=INFINITE_VALUE;//初始化当前的m_pWeight[nCurNode-1]
    while(i<m_nValueKind&&queWork.Pop(&nPreNode,&nIndex,&eWeight)!=-1)//从queWork中顺序取出每个pEdgeList的row,nIndex的取值从0到m_nValueKind-1,eWeight=pEdgeList->value
    {
     m_pWeight[nCurNode-1][i]=eWeight;//取前m_nValueKind个结果
     m_pParent[nCurNode-1][i].Push(nPreNode,nIndex);//按照pEdgeList->value的升序,也就是P的降序放入m_pParent
    }
}

     得到m_pParent之后,按照m_pWeight[m_segGraph.m_nCol-1]的升序,生成path
     CNShortPath::GetPaths(unsigned int nNode,unsigned int nIndex,int **nResult,bool bBest)
     //nNode=m_segGraph.m_nCol,nIndex从0取到m_nValueKind-1,nResult输出结果,bBest=true只输出最佳结果
     比如"始##始张华平欢迎您末##末"的结果为
             nResult[0]={0,1,2,3,4,6,7,8,-1}  "始##始/张/华/平/欢迎/您/末##末"
             nResult[1]={0,1,2,3,4,5,6,7,8,-1} "始##始/张/华/平/欢/迎/您/末##末"
            没有第三种结果
     取出所有nResult[i]作为分词结果,结果保存在m_graphOptimum中,m_graphOptimum和m_segGraph结构一样,只不过只存nResult[i]中的结果:

    如果m_nValueKind=1则
    m_graphOptimum.m_pHead "始##始"
                                                  "张"
                                                  "华"
                                                  "平"
                                                  "欢迎"
                                                  "您"
                                                  "末##末"
  
     m_graphOptimum.m_nRow=7
     m_graphOptimum.m_nCol=8

     如果m_nValueKind=2则
     m_graphOptimum.m_pHead "始##始"
                                                    "张"
                                                    "华"
                                                    "平"
                                                    "欢"
                                                    "欢迎"
                                                   "迎"
                                                   "您"
                                                   "末##末"
  
     m_graphOptimum.m_nRow=7
     m_graphOptimum.m_nCol=8

     见 bool CSegment::GenerateWord(int **nSegRoute, int nIndex)这里的nSegRoute=上面的nResult,是输入参数;nIndex表示第nIndex个分词结果

     同时,CResult.m_Seg.m_pWordSeg[nIndex][k]中保存了第nIndex个结果的第k个词的信息:
 

 CResult.m_Seg.m_pWordSeg[nIndex][k].sWord//词
 CResult.m_Seg.m_pWordSeg[nIndex][k].nHandle//词性
 CResult.m_Seg.m_pWordSeg[nIndex][k].dValue//-logP


二、posTagging
   m_POSTagger.POSTagging(m_Seg.m_pWordSeg[nIndex],m_dictCore,m_dictCore);//对第nIndex个分词结果用标准的字典标注
   方便起见,下面假设m_nValueKind=1
 
    m_POSTagger用HMM对分词进行标注,这里
    输出概率为P(w_i|c_i),c_i为词性,w_i为词;
    转移概率为P(c_i|c_{i-1}),
    初始状态为P(c_0)即P("始##始"的词性)
   用维特比算法求出一个c_1/c_2/.../c_k=argmax_{c_1'/c_2'/.../c_k'}P(w_1',w_2',...,w_k')

  
   将句子分成若干段,每段以有唯一pos的w结尾,也就是分词中CResult.m_Seg.m_pWordSeg[0][k].nHandle>0的那些词
    比如,举个例子
    "0 始##始 1 张  2   华   3   平   4   欢迎   5   您   6 末##末 7"
          pos1   pos1   pos1     pos1      pos1      pos1     pos1
          pos2   pos2     pos2      pos2
         pos3   pos3               pos3
         pos4
         pos5
   则该句被划分为
    "0 始##始"
   "1 张  2   华   3   平 4  欢迎   5   您"
   "6 末##末"
   对每一段用维特比算法确定一个唯一的postag
 
   细节:
   首先P(w,c)的输出概率存储在dict中,比如dictCore,dictUnknow,
   通过dict.GetFrequency(char *sWord, int nHandle)函数获取 sWord pos为nHandle的函数
   概率P(c)存储在context中,比如m_context,
   通过context.GetFrequency(int nKey, int nSymbol)函数获取 pos为nSymbol的函数,nKey=0
   转移概率P(c|c')存储在context中,比如m_context,
   通过context.GetContextPossibility(int nKey, int nPrev, int nCur)函数获取 c'=nPrev,c=nCur的转移概率,nKey=0
 
   重要的数据结构
   m_nTags[i][k]表示第i个w的第k个pos
   在GetFrom函数中表示 -log(第i个w的第k个pos的输出概率)
   在CSpan::Disamb()函数中
   m_dFrequency[i][k]表示 -log(从第0个w到第i个w的第k个pos的联合最大输出概率),比如

    w_j   w_{j+1}
   m_dFrequency[j][0]-- m_dFrequency[j+1][0]
   m_dFrequency[j][1]  -- m_dFrequency[j+1][1]
   --m_dFrequency[j+1][2]
 
    则 图中的路径的权重为W([j,0]->[j+1,2])=m_dFrequency[j][0]-log(m_context.GetContextPossibility(0,m_nTags[j][0],m_nTags[j+1][2]))
    这样,选择 m_dFrequency[j+1][2]=min{W([j,0]->[j+1,2]),W([j,1]->[j+1,2])}

     m_nCurLength表示当前段的w个数+1
    在m_POSTagger.POSTagging中,以上面的例子为例
   

while(i>-1&&pWordItems[i].sWord[0]!=0)//将执行段的个数次,比如上例中将执行3次
{
     i=GetFrom(pWordItems,nStartPos,dictCore,dictUnknown);//i=GetFrom(pWordItems,0,dictCore,dictUnknown)=1
     //i=GetFrom(pWordItems,1,dictCore,dictUnknown)=6
     //i=GetFrom(pWordItems,6,dictCore,dictUnknown)=7
    //从nStartPos向前取w,一直取到一个有唯一pos的w为止,该过程中记录每个w的pos,保存在m_nTags中,记录log(w|c)输出概率保存在m_dFrequency中
     GetBestPOS();//调用Disamb()函数,用维特比算法找出该段的最佳(联合输出概率最大)的标注,最佳路径保存在m_nBestTag中
     //通过读取m_nBestTag对pWordItems.nHandle进行赋值
 }

三、NE识别:人名识别,音译名识别,地名识别
       其基本思路和PosTagging一样,只不过词性c换成了role r,以人名识别为例:
       首先识别出人名的tag(即pos),见 "Chinese Named Entity Recognition Using Role Model"
       在函数CUnknowWord::Recognition(PWORD_RESULT pWordSegResult, CDynamicArray &graphOptimum,CSegGraph &graphSeg,CDictionary &dictCore)中每个被切开的段被识别完之后,用m_roleTag.POSTagging(pWordSegResult,dictCore,m_dict);对第一步分词的结果进行一次标记。
 

       首先用dictUnknown.GetHandle(m_sWords[i],&nCount,aPOS,aFreq);获得m_sWords[i]在NE词典中的role,
       接着用dictCore.GetHandle(m_sWords[i],&nCount,aPOS,aFreq);获得m_sWords[i]在标准词典中的tag,这里只要m_sWords[i]在标准词典中有tag,那么tag一律标记为0,该tag下的输出概率为P(w|c)=P(sum_{aFreq}|c=0)
       接下来用SplitPersonPOS(dictUnknown)函数将其中tag为LH和TR的w拆成两个

      比如"张/SS 华/GH 平欢/TR 迎/RC 您/RC"中"平欢"被拆成"平/GT" "欢/12"
      接着在PersonRecognize(dictUnknown);函数中,用一些模板进行匹配,"SS/GH/TR"将匹配到"张华平"。匹配得到的片断保存在m_nUnknownWords中,其nHandle被设置为人名,地名,音译名中的一个
      对第一步中的graphOptimum,加入m_nUnknownWords的边:
 

 graphOptimum.GetElement(nAtomStart,nAtomEnd,&dValue,&nPOSOriginal);
 if(dValue>m_roleTag.m_dWordsPossibility[i])//Set the element with less frequency
 graphOptimum.SetElement(nAtomStart,nAtomEnd,m_roleTag.m_dWordsPossibility[i],m_nPOS);

四、重新分词
       对上一步的graphOptimum,用第一步中对m_segGraph分词的方法,找出一个联合概率最大的分词结果:
       m_Seg.OptimumSegmet(nCount);

五、重新标注
      对于四中分好的结果,用标准词典对其进行posTagging:
 

   for(nIndex=0;nIndex<m_Seg.m_nSegmentCount;nIndex++)//m_Seg.m_nSegmentCount是第四步中的分词结果个数
   {
      m_POSTagger.POSTagging(m_Seg.m_pWordSeg[nIndex],m_dictCore,m_dictCore);
   }

    最后,用Sort();对标注结果按照联合输出概率的大小降序排序,并按照用户的需求输出前几个

你可能感兴趣的:(技术,分词)