文章目录

  • 自然语言处理系列十八
  • 分词工具实战
  • Java的HanLP分词
  • 总结


自然语言处理系列十八

分词工具实战

分词工具有Java、Python、C++实现的,这里给大家推荐目前最流行的分词工具。CRF++是采用C++语言编写,但可以用Python来调用。HanLP是用Java编写的,也可以用Python调用。IK分词和mmseg4j分词也是用Java编写,经常集成在搜索引擎Solr和Elasticsearch里。下面分别进行讲解这几个开源分词包。

Java的HanLP分词

HanLP是一系列模型与算法组成的NLP工具包,使用Java语言开发,并支持Python语言调用,目标是普及自然语言处理在生产环境中的应用。HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。
HanLP提供下列功能:
中文分词:
HMM-Bigram(速度与精度最佳平衡;一百兆内存)
最短路分词、N-最短路分词
由字构词(侧重精度,全世界最大语料库,可识别新词;适合NLP任务)
感知机分词、CRF分词
词典分词(侧重速度,每秒数千万字符;省内存)
极速词典分词
所有分词器都支持:
索引全切分模式
用户自定义词典
兼容繁体中文
训练用户自己的领域模型
词性标注:
HMM词性标注(速度快)
感知机词性标注、CRF词性标注(精度高)
命名实体识别:
基于HMM角色标注的命名实体识别 (速度快)
中国人名识别、音译人名识别、日本人名识别、地名识别、实体机构名识别
基于线性模型的命名实体识别(精度高)
感知机命名实体识别、CRF命名实体识别
关键词提取:
TextRank关键词提取
自动摘要:
TextRank自动摘要
短语提取:
基于互信息和左右信息熵的短语提取
拼音转换:
多音字、声母、韵母、声调
简繁转换:
简繁分歧词(简体、繁体、臺灣正體、香港繁體)
文本推荐:
语义推荐、拼音推荐、字词推荐
依存句法分析:
基于神经网络的高性能依存句法分析器
基于ArcEager转移系统的柱搜索依存句法分析器
文本分类:
情感分析
文本聚类:
KMeans、Repeated Bisection、自动推断聚类数目k
word2vec:
词向量训练、加载、词语相似度计算、语义运算、查询、KMeans聚类
文档语义相似度计算
语料库工具:
部分默认模型训练自小型语料库,鼓励用户自行训练。所有模块提供训练接口,语料可参考98年人民日报语料库。
在提供丰富功能的同时,HanLP内部模块坚持低耦合、模型坚持惰性加载、服务坚持静态提供、词典坚持明文发布,使用非常方便。默认模型训练自全世界最大规模的中文语料库,同时自带一些语料处理工具,帮助用户训练自己的模型。支持自定义词典。
下面我们通过Java代码来演示HanLP的几种中文分词使用,代码如下所示:
【代码6.14】 PrefixSpanJob.scala

package com.chongdianleme.job;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.CRF.CRFSegment;
import com.hankcs.hanlp.seg.Dijkstra.DijkstraSegment;
import com.hankcs.hanlp.seg.NShort.NShortSegment;
import com.hankcs.hanlp.seg.Segment;
import com.hankcs.hanlp.seg.common.Term;
import com.hankcs.hanlp.tokenizer.IndexTokenizer;
import com.hankcs.hanlp.tokenizer.NLPTokenizer;
import com.hankcs.hanlp.tokenizer.SpeedTokenizer;
import com.hankcs.hanlp.tokenizer.StandardTokenizer;
import java.util.List;
/**
 * Created by 充电了么App - 陈敬雷
 * 充电了么App官网:http://chongdianleme.com/
 * 充电了么App - 专注上班族职业技能提升充电学习的在线教育平台
 * HanLP中文分词功能演示,开源地址:https://github.com/hankcs/HanLP
*/
public class HanLPDemo {
    public static void main(String[] args) {
        segment();//常用默认分词:HanLP.segment
        standardSegment();//标准分词:StandardTokenizer.segment
        NLPSegment();// NLP分词
        indexTokenizerSegment();//索引分词
        nShortSegment();//N-最短路径分词
        CRFSegment();//CRF分词
        highSpeedSegment();//极速词典分词
    }
    /**
     *1.常用默认分词:HanLP.segment
     * HanLP对词典的数据结构进行了长期的优化,可以应对绝大多数场景。哪怕HanLP的词典上百兆也无需担心,因为在内存中被精心压缩过。
     * 如果内存非常有限,请使用小词典。HanLP默认使用大词典,同时提供小词典。全部词典和模型都是惰性加载的,不使用的模型相当于不存在,可以自由删除。
     * HanLP.segment其实是对StandardTokenizer.segment标准分词的包装,和标准分词的结果是一样的。
     */
    public static void segment() {
        String s = "分布式机器学习实战(人工智能科学与技术丛书)深入浅出,逐步讲解分布式机器学习的框架及应用配套个性化推荐算法系统、人脸识别、对话机器人等实战项目。";
        List<Term> termList =HanLP.segment(s);
        System.out.println(termList);
        //输出结果如下:分词结果包含词性,比如分布式的词性b代表区别词,机器学习的词性gi代表计算机相关词汇,实战的词性n代表名称,
        // 后面每个词都返回了对应的词性,这里不一一举例,下章我们会单独讲词性标注,列出所有的词性表。
        //[分布式/b, 机器学习/gi, 实战/n, (/w, 人工智能/n, 科学/n, 与/p, 技术/n, 丛书/n, )/w, 深入浅出/i, ,/w, 逐步/d, 讲解/v, 分布式/b, 机器学习/gi, 的/uj, 框架/n, 及/c, 应用/vn, 配套/a, 个性化/v, 推荐/v, 算法/n, 系统/n, 、/w, 人脸/n, 识别/v, 、/w, 对话/vn, 机器人/n, 等/u, 实战/n, 项目/n, 。/w]
    }
    /**
     *2.标准分词:StandardTokenizer.segment
     * HanLP中有一系列“开箱即用”的静态分词器,以Tokenizer结尾,在接下来的例子中会继续介绍。
     * HanLP.segment其实是对StandardTokenizer.segment的包装。
     * 分词结果包含词性,每个词性的意思下章详细讲解。
     */
    public static void standardSegment() {
        String s = "分布式机器学习实战(人工智能科学与技术丛书)深入浅出,逐步讲解分布式机器学习的框架及应用配套个性化推荐算法系统、人脸识别、对话机器人等实战项目。";
        List<Term> termList = StandardTokenizer.segment(s);
        System.out.println(termList);
        termList = HanLP.segment(s);
        //输出结果如下:可以看到和上面的HanLP.segment结果是一样的。
        //[分布式/b, 机器学习/gi, 实战/n, (/w, 人工智能/n, 科学/n, 与/p, 技术/n, 丛书/n, )/w, 深入浅出/i, ,/w, 逐步/d, 讲解/v, 分布式/b, 机器学习/gi, 的/uj, 框架/n, 及/c, 应用/vn, 配套/a, 个性化/v, 推荐/v, 算法/n, 系统/n, 、/w, 人脸/n, 识别/v, 、/w, 对话/vn, 机器人/n, 等/u, 实战/n, 项目/n, 。/w]
    }
    /**
     *3.NLP分词:NLPTokenizer.segment
     NLP分词NLPTokenizer会执行词性标注和命名实体识别,由结构化感知机序列标注框架支撑。默认模型训练自9970万字的大型综合语料库,是已知范围内全世界最大的中文分词语料库。
     语料库规模决定实际效果,面向生产环境的语料库应当在千万字量级。用户可以在自己的语料上训练新模型以适应新领域、识别新的命名实体。
     */
    public static void NLPSegment() {
        String s = "分布式机器学习实战(人工智能科学与技术丛书)深入浅出,逐步讲解分布式机器学习的框架及应用配套个性化推荐算法系统、人脸识别、对话机器人等实战项目。";
        System.out.println(NLPTokenizer.segment(s));
        //输出结果如下:
        //[分布式/b, 机器学习/gi, 实战/n, (/w, 人工智能/n, 科学/n, 与/p, 技术/n, 丛书/n, )/w, 深入浅出/i, ,/w, 逐步/d, 讲解/v, 分布式/b, 机器学习/gi, 的/uj, 框架/n, 及/c, 应用/vn, 配套/a, 个性化/v, 推荐/v, 算法/n, 系统/n, 、/w, 人脸/n, 识别/v, 、/w, 对话/vn, 机器人/n, 等/u, 实战/n, 项目/n, 。/w]
    }
    /**
     *4.索引分词:IndexTokenizer.segment
     索引分词IndexTokenizer是面向搜索引擎的分词器,能够对长词全切分,另外通过term.offset可以获取单词在文本中的偏移量。
     任何分词器都可以通过基类Segment的enableIndexMode方法激活索引模式。
     */
    public static void indexTokenizerSegment() {
        String s = "分布式机器学习实战(人工智能科学与技术丛书)深入浅出,逐步讲解分布式机器学习的框架及应用配套个性化推荐算法系统、人脸识别、对话机器人等实战项目。";
        List<Term> termList = IndexTokenizer.segment(s);
        for (Term term : termList)
        {
            System.out.println(term + " [" + term.offset + ":" + (term.offset + term.word.length()) + "]");
        }
        //输出结果如下:
        /**
         分布式/b [0:3]
         分布/v [0:2]
         机器学习/gi [3:7]
         机器/n [3:5]
         学习/v [5:7]
         实战/n [7:9]
         (/w [9:10]
         人工智能/n [10:14]
         人工/n [10:12]
         智能/n [12:14]
         科学/n [14:16]
         与/p [16:17]
         技术/n [17:19]
         丛书/n [19:21]
         )/w [21:22]
         深入浅出/i [22:26]
         深入/v [22:24]
         ,/w [26:27]
         逐步/d [27:29]
         讲解/v [29:31]
         分布式/b [31:34]
         分布/v [31:33]
         机器学习/gi [34:38]
         机器/n [34:36]
         学习/v [36:38]
         的/uj [38:39]
         框架/n [39:41]
         及/c [41:42]
         应用/vn [42:44]
         配套/a [44:46]
         个性化/v [46:49]
         个性/n [46:48]
         推荐/v [49:51]
         算法/n [51:53]
         系统/n [53:55]
         、/w [55:56]
         人脸/n [56:58]
         识别/v [58:60]
         、/w [60:61]
         对话/vn [61:63]
         机器人/n [63:66]
         机器/n [63:65]
         等/u [66:67]
         实战/n [67:69]
         项目/n [69:71]
         。/w [71:72]
         */
    }
    /**
     * 5.N-最短路径分词:nShortSegment.seg
     N最短路分词器NShortSegment比最短路分词器慢,但是效果稍微好一些,对命名实体识别能力更强。
     一般场景下最短路分词的精度已经足够,而且速度比N最短路分词器快几倍,请酌情选择。
     */
    public static void nShortSegment() {
        String s = "分布式机器学习实战(人工智能科学与技术丛书)深入浅出,逐步讲解分布式机器学习的框架及应用配套个性化推荐算法系统、人脸识别、对话机器人等实战项目。";
        Segment nShortSegment = new NShortSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true);
        Segment shortestSegment = new DijkstraSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true);
        System.out.println("N-最短分词:" + nShortSegment.seg(s) + "\n最短路分词:" + shortestSegment.seg(s));
        //输出结果如下:
        //N-最短分词:[分布式/b, 机器/n, 学习/v, 实战/n, (/w, 人工智能/n, 科学/n, 与/p, 技术/n, 丛书/n, )/w, 深入浅出/i, ,/w, 逐步/d, 讲解/v, 分布式/b, 机器/n, 学习/v, 的/uj, 框架/n, 及/c, 应用/vn, 配套/a, 个性化/v, 推荐/v, 算法/n, 系统/n, 、/w, 人脸/n, 识别/v, 、/w, 对话/vn, 机器人/n, 等/u, 实战/n, 项目/n, 。/w]
        //最短路分词:[分布式/b, 机器/n, 学习/v, 实战/n, (/w, 人工智能/n, 科学/n, 与/p, 技术/n, 丛书/n, )/w, 深入浅出/i, ,/w, 逐步/d, 讲解/v, 分布式/b, 机器/n, 学习/v, 的/uj, 框架/n, 及/c, 应用/vn, 配套/a, 个性化/v, 推荐/v, 算法/n, 系统/n, 、/w, 人脸/n, 识别/v, 、/w, 对话/vn, 机器人/n, 等/u, 实战/n, 项目/n, 。/w]
    }
    /**
     *6.CRF分词:CRFSegment
     CRF对新词有很好的识别能力,但是开销较大。
     */
    public static void  CRFSegment() {
        String s = "分布式机器学习实战(人工智能科学与技术丛书)深入浅出,逐步讲解分布式机器学习的框架及应用配套个性化推荐算法系统、人脸识别、对话机器人等实战项目。";
        Segment crfSegment = new CRFSegment();
        System.out.println(crfSegment.seg(s));
    }
    /**
     *7.极速词典分词:SpeedTokenizer.segment
     极速分词是词典最长分词,速度极其快,精度一般。在i7-6700K上跑出了4500万字每秒的速度。
     */
    public static void  highSpeedSegment() {
        String s = "分布式机器学习实战(人工智能科学与技术丛书)深入浅出,逐步讲解分布式机器学习的框架及应用配套个性化推荐算法系统、人脸识别、对话机器人等实战项目。";
        System.out.println(SpeedTokenizer.segment(s));
        long start = System.currentTimeMillis();
        int pressure = 100;
        for (int i = 0; i < pressure; ++i)
        {
            SpeedTokenizer.segment(s);
        }
        double costTime = (System.currentTimeMillis() - start) / (double)1000;
        System.out.printf("分词速度:%.2f字每秒", s.length() * pressure / costTime);
    }
}