zoukankan      html  css  js  c++  java
  • CRF分词的纯Java实现

    与基于隐马尔可夫模型的最短路径分词、N-最短路径分词相比,基于随机条件场(CRF)的分词对未登录词有更好的支持。本文(HanLP)使用纯Java实现CRF模型的读取与维特比后向解码,内部特征函数采用 双数组Trie树(DoubleArrayTrie)储存,得到了一个高性能的中文分词器。

    CRF简介

    CRF是序列标注场景中常用的模型,比HMM能利用更多的特征,比MEMM更能抵抗标记偏置的问题。

    CRF训练

    这类耗时的任务,还是交给了用C++实现的CRF++。关于CRF++输出的CRF模型,请参考《CRF++模型格式说明》。

    CRF解码

    解码采用维特比算法实现。并且稍有改进,用中文伪码与白话描述如下:

    首先任何字的标签不仅取决于它自己的参数,还取决于前一个字的标签。但是第一个字前面并没有字,何来标签?所以第一个字的处理稍有不同,假设第0个字的标签为X,遍历X计算第一个字的标签,取分数最大的那一个。

    如何计算一个字的某个标签的分数呢?某个字根据CRF模型提供的模板生成了一系列特征函数,这些函数的输出值乘以该函数的权值最后求和得出了一个分数。该分数只是“点函数”的得分,还需加上“边函数”的得分。边函数在本分词模型中简化为f(s',s),其中s'为前一个字的标签,s为当前字的标签。于是该边函数就可以用一个4*4的矩阵描述,相当于HMM中的转移概率。

    实现了评分函数后,从第二字开始即可运用维特比后向解码,为所有字打上BEMS标签。

    实例

    还是取经典的“商品和服务”为例,首先HanLP的CRFSegment分词器将其拆分为一张表:

    1
    2
    3
    4
    5
    商   null   
    品   null   
    和   null   
    服   null   
    务   null   

    null表示分词器还没有对该字标注。

    代码

    上面说了这么多,其实我的实现非常简练:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /**
     * 维特比后向算法标注
     * @param table
     */
    public void tag(Table table)
    {
        int size = table.size();
        double bestScore = 0;
        int bestTag = 0;
        int tagSize = id2tag.length;
        LinkedList<double[]> scoreList = computeScoreList(table, 0);    // 0位置命中的特征函数
        for (int i = 0; i < tagSize; ++i)   // -1位置的标签遍历
        {
            for (int j = 0; j < tagSize; ++j)   // 0位置的标签遍历
            {
                double curScore = matrix[i][j] + computeScore(scoreList, j);
                if (curScore > bestScore)
                {
                    bestScore = curScore;
                    bestTag = j;
                }
            }
        }
        table.setLast(0, id2tag[bestTag]);
        int preTag = bestTag;
        // 0位置打分完毕,接下来打剩下的
        for (int i = 1; i < size; ++i)
        {
            scoreList = computeScoreList(table, i);    // i位置命中的特征函数
            bestScore = Double.MIN_VALUE;
            for (int j = 0; j < tagSize; ++j)   // i位置的标签遍历
            {
                double curScore = matrix[preTag][j] + computeScore(scoreList, j);
                if (curScore > bestScore)
                {
                    bestScore = curScore;
                    bestTag = j;
                }
            }
            table.setLast(i, id2tag[bestTag]);
            preTag = bestTag;
        }
    }

    标注结果

    标注后将table打印出来:

    1
    2
    3
    4
    5
    6
    CRF标注结果
    商   B  
    品   E  
    和   S  
    服   B  
    务   E

    最终处理

    将BEMS该合并的合并,得到:

    1
    [商品/null, 和/null, 服务/null]

    然后将词语送到词典中查询一下,没查到的暂时当作nx,并记下位置(因为这是个新词,为了表示它的特殊性,最后词性设为null),再次使用维特比标注词性:

    1
    [商品/n, 和/cc, 服务/vn]

    新词识别

    CRF对分词有很好的识别能力,比如:

    1
    2
    3
    CRFSegment segment = new CRFSegment();
    segment.enableSpeechTag(true);
    System.out.println(segment.seg("你看过穆赫兰道吗"));

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CRF标注结果
    你   S  
    看   S  
    过   S  
    穆   B  
    赫   M  
    兰   M  
    道   E  
    吗   S  
    [你/rr, 看/v, 过/uguo, 穆赫兰道/null, 吗/y]

    null表示新词。

  • 相关阅读:
    MySQL 查询各科前三的数据
    MySQL 分时间段查询
    MySQL 查询同一字段中同时满足多个条件
    MySQL 分组累加
    快速搭建LNMP
    打开页面默认弹出软键盘,同时兼容iOS和Android
    linux 系统的ssh服务
    linux 磁盘
    linux系统基础网络配置
    discuz中方法
  • 原文地址:https://www.cnblogs.com/DjangoBlog/p/4224542.html
Copyright © 2011-2022 走看看