zoukankan      html  css  js  c++  java
  • cppjieba分词学习笔记

    cppjieba分词包主要提供中文分词、关键词提取、词性标注三种功能

    一、分词

      cppjieba分词用的方法是最大概率分词(MP)和隐马尔科夫模型(HMM),以及将MP和HMM结合成的MixSegment分词器。除此之外,cppjieba支持三种模式的分词:

    • 精确模式,试图将句子最精确地切开,适合文本分析;
    • 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;

    我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学

    • 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词

    小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造

    1.最大概率分词(MP)

    • 默认基于jieba.dict.utf8生成前缀词典,用户可以添加自己的字典,并且按照一定权重比重一起生成前缀词典。构建字典时将utf-8格式的输入转变为unicode格式
    • 分词器中有一个类Prefilter,pre_fileter会将输入的字符串转变为unicode格式,根据某些特殊的字符symbols_,将输入的字符串切分成一段一段,对每一段分别分词
    • 构建DAG图,从后往前的动态规划算法,回溯概率最大的切词方法
        void Cut(RuneStrArray::const_iterator begin,
                RuneStrArray::const_iterator end, vector<WordRange>& words,
                size_t max_word_len = MAX_WORD_LENGTH) const {
            vector<Dag> dags;
            dictTrie_->Find(begin, end, dags, max_word_len);  //构建DAG图
            CalcDP(dags);  //从后往前的动态规划算法
            CutByDag(begin, end, dags, words);//回溯
        }

    2.隐藏马尔科夫分词(HMM)

    参考:中文分词之HMM模型详解

    cppjieba分词中提供了HMM模型的参数文件,保存在hmm_model.utf8中。cppjieba的HMM分词器,实际上就是加载HMM模型,然后根据输入的句子(观察序列),计算可能性最大的状态序列。状态空间由B(开始)、M(中间)、E(结束)、S(单个字)构成。下面是Viterbi算法的过程。

    输入样例:

    小明硕士毕业于中国科学院计算所

    定义变量

    二维数组 weight[4][15],4是状态数(0:B,1:E,2:M,3:S),15是输入句子的字数。比如 weight[0][2] 代表 状态B的条件下,出现'硕'这个字的可能性。

    二维数组 path[4][15],4是状态数(0:B,1:E,2:M,3:S),15是输入句子的字数。比如 path[0][2] 代表 weight[0][2]取到最大时,前一个字的状态,比如 path[0][2] = 1, 则代表 weight[0][2]取到最大时,前一个字(也就是)的状态是E。记录前一个字的状态是为了使用viterbi算法计算完整个 weight[4][15] 之后,能对输入句子从右向左地回溯回来,找出对应的状态序列。

    使用InitStatus对weight二维数组进行初始化

    已知InitStatus如下:

    #B
    -0.26268660809250016
    #E
    -3.14e+100
    #M
    -3.14e+100
    #S
    -1.4652633398537678
    

    且由EmitProbMatrix可以得出

    Status(B) -> Observed(小)  :  -5.79545
    Status(E) -> Observed(小)  :  -7.36797
    Status(M) -> Observed(小)  :  -5.09518
    Status(S) -> Observed(小)  :  -6.2475
    

    所以可以初始化 weight[i][0] 的值如下:

    weight[0][0] = -0.26268660809250016 + -5.79545 = -6.05814
    weight[1][0] = -3.14e+100 + -7.36797 = -3.14e+100
    weight[2][0] = -3.14e+100 + -5.09518 = -3.14e+100
    weight[3][0] = -1.4652633398537678 + -6.2475 = -7.71276
    

    注意上式计算的时候是相加而不是相乘,因为之前取过对数的原因。

    遍历句子计算整个weight二维数组

    //遍历句子,下标i从1开始是因为刚才初始化的时候已经对0初始化结束了
    for(size_t i = 1; i < 15; i++)
    {
        // 遍历可能的状态
        for(size_t j = 0; j < 4; j++) 
        {
            weight[j][i] = MIN_DOUBLE;
            path[j][i] = -1;
            //遍历前一个字可能的状态
            for(size_t k = 0; k < 4; k++)
            {
                double tmp = weight[k][i-1] + _transProb[k][j] + _emitProb[j][sentence[i]];
                if(tmp > weight[j][i]) // 找出最大的weight[j][i]值
                {
                    weight[j][i] = tmp;
                    path[j][i] = k;
                }
            }
        }
    }
    

    如此遍历下来,weight[4][15] 和 path[4][15] 就都计算完毕。

    确定边界条件和路径回溯

    边界条件如下:

    对于每个句子,最后一个字的状态只可能是 E 或者 S,不可能是 M 或者 B。
    

    所以在本文的例子中我们只需要比较 weight[1(E)][14] 和 weight[3(S)][14] 的大小即可。

    在本例中:

    weight[1][14] = -102.492;
    weight[3][14] = -101.632;
    

    所以 S > E,也就是对于路径回溯的起点是 path[3][14]

    回溯的路径是:

    SEBEMBEBEMBEBEB
    

    倒序一下就是:

    BE/BE/BME/BE/BME/BE/S
    

    所以切词结果就是:

    小明/硕士/毕业于/中国/科学院/计算/所
    

    到此,一个HMM模型中文分词算法过程就阐述完毕了。

    也就是给定我们一个模型,我们对模型进行载入完毕之后,只要运行一遍Viterbi算法,就可以找出每个字对应的状态,根据状态也就可以对句子进行分词。

    3、MixSegment是MP和HMM的结合,首先使用MP分词,然后对MP分词的结果使用HMM分词。其实,第二次使用HMM再分对原有分词结果调整得并不多,只是将MP结果中单字顺序收集再分词。

        void Cut(RuneStrArray::const_iterator begin,
                RuneStrArray::const_iterator end, vector<WordRange>& res,
                bool hmm) const {
            if (!hmm) {
                mpSeg_.Cut(begin, end, res);
                return;
            }
            vector<WordRange> words;
            assert(end >= begin);
            words.reserve(end - begin);
            mpSeg_.Cut(begin, end, words);
    
            vector<WordRange> hmmRes;
            hmmRes.reserve(end - begin);
            for (size_t i = 0; i < words.size(); i++) {
                //if mp Get a word, it's ok, put it into result
                if (words[i].left != words[i].right
                        || (words[i].left == words[i].right
                                && mpSeg_.IsUserDictSingleChineseWord(
                                        words[i].left->rune))) {
                    res.push_back(words[i]);
                    continue;
                }
    
                // if mp Get a single one and it is not in userdict, collect it in sequence
                size_t j = i;
                while (j < words.size() && words[j].left == words[j].right
                        && !mpSeg_.IsUserDictSingleChineseWord(words[j].left->rune)) {
                    j++;
                }
    
                // Cut the sequence with hmm
                assert(j - 1 >= i);
                // TODO
                hmmSeg_.Cut(words[i].left, words[j - 1].left + 1, hmmRes);
                //put hmm result to result
                for (size_t k = 0; k < hmmRes.size(); k++) {
                    res.push_back(hmmRes[k]);
                }
    
                //clear tmp vars
                hmmRes.clear();
    
                //let i jump over this piece
                i = j - 1;
            }
        }

     二、关键词提取

    cpp结巴的提供了两种关键词提取方法,分别基于TF-IDF和TextRank算法,下面分别介绍

    1.基于TF-IDF算法的关键词抽取

    TF(term frequency):一个词语在单个文档出现的次数
    IDF(Inverse document frequency): 逆文档频率,是一个词语普遍度的度量。某一特定词语的IDF,有总文件数目除以包含该词语的文件数目,再将取得商取对数的得到。
    总的来说某一特定文件内的高频率词语,以及词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。
    cppjieba中提供接近26万词语的IDF(idf.utf8),对于在idf.utf8中没出现的词语,idf权重取平局值。除此之外,有些词语在文档的出现的频率TF很高,但是对于文档来说没什么实在的意义比如“的”,“是”等,因此cppjieba还提供了了一个1534个词语的stop_words.utf8。
    cppjieba中根据权重(tf*idf)排序,取出topN个关键词。
    partial_sort(keywords.begin(), keywords.begin() + topN, keywords.end(),Compare);

    参考:http://blog.csdn.net/awj3584/article/details/18604901

    2.基于TextRank的关键词提取

      PageRank根据网页之间的链接关系,建立网页之间的有向图,图中的节点表示每个网页,然后根据这个有向图迭代计算出每个网页的PER值,作为网页重要性排名的依据。TextRank方法与PageRank的方法非常类似,当使用TextRank方法做文本的关键字提取时,首先在给定的共现窗口内(cppjieba中默认为5)根据词语之间的共现关系建立链接关系图,图中的每个节点代表每个节点,每条边的权重表示词语共现的次数。然后根据词语之间链接关系图,迭代计算出每个词语的权重值,根据权重值选出文本中权重值大的几个关键词。

      TextRank 一般模型可以表示为一个有向有权图 G =(V, E), 由点集合 V和边集合 E 组成, E 是V ×V的子集。图中任两点 Vi , Vj 之间边的权重为 wji , 对于一个给定的点 Vi, In(Vi) 为 指 向 该 点 的 点 集 合 , Out(Vi) 为点 V指向的点集合。点 Vi 的得分定义如下:

      其中, d 为阻尼系数, 取值范围为 0 到 1, 代表从图中某一特定点指向其他任意点的概率, 一般取值为 0.85。使用TextRank 算法计算图中各点的得分时, 需要给图中的点指定任意的初值, 并递归计算直到收敛, 即图中任意一点的误差率小于给定的极限值时就可以达到收敛。cppjieba在实现默认迭代10次。

    参考:http://www.cnblogs.com/clover-siyecao/p/5726480.html
    参考:http://blog.sohu.com/s/MTAzMjM1NDY0/239636012.html(矩阵形式)

    三、词性标注

    cppjieba中的词性标注实现过程总的来说是基于词典的查询,词典中存有大约35万词汇的词性。单个词语的词性标注,直接查询词典,词典中不存在一个词语多个词性的问题。词典没有的词语,根据词语的特点的简单的标注为数字(m),英文(eng),以及x。对于整个句子的词性标注是用分词算法分词,然后用上述单个词语词性标注的方法逐个标注词性。由此可见cppjieba的词性标注非常依赖词典。对于句子的词性标注,没有考虑词性之间的关系。

    参考: CppJieba代码详解

              "结巴"(Jieba)中文分词系列性能评测

  • 相关阅读:
    【转】每天一个linux命令(52):ifconfig命令
    【转】每天一个linux命令(51):lsof命令
    linux挂载SD卡
    【转】每天一个linux命令(50):crontab命令
    【转】每天一个linux命令(49):at命令
    【转】每天一个linux命令(48):watch命令
    【转】每天一个linux命令(47):iostat命令
    【转】每天一个linux命令(46):vmstat命令
    【转】每天一个linux命令(45):free 命令
    【转】每天一个linux命令(44):top命令
  • 原文地址:https://www.cnblogs.com/wxquare/p/6666341.html
Copyright © 2011-2022 走看看