NLP-结巴分词

结巴分词

结巴分词是有国内程序员(https://github.com/fxsjy/jieba)做的一个分词工具,刚开始是Python版本的,后来由anderscui(https://github.com/anderscui/jieba.NET )移植到.Net上面。

结巴分词的分词过程大致为:

·        前缀词典(Trie:用于存储主词典,也可以动态增删词条,这个词典可以理解为jieba知道的词,或者说已登录词;

·        有向无环图(DAG:通过前缀词典,可以找出句子所有可能的成词结果;

·        最大概率路径:通过DAG,可以了解所有的成词结果,每个结果对应于一条路径及其概率。由于不同词条的出现概率不同,不同的结果就对应了不同的概率,我们找出概率最大的那条路径。到这里,我们对于已登录词做出了最合理的划分;

·        HMM模型Viterbi算法:最大概率路径之后,我们可能会遇到一些未登录词(不包含在前缀词典中的词),这时通过HMMViterbi尝试进一步的划分,得到最终结果

刚开始结巴分词只有分词功能,后来加入了词性标注、关键词提取等功能,加上其开源的属性,还是很好用的。

安装方法

通过NuGet包管理器安装jieba.NET

1.png

在当前项目安装了结巴分词之后,可以在当前项目的packages\jieba.NET\文件夹下看到一个Resource文件夹,里面是结巴分词所需要的各种数据文件,需要对数据文件的位置进行配置才能使用。有几种方法:

    方法一、对于VS项目来说,直接将Resource文件夹拷贝到项目的bin\Debug\目录下。

                   方法二、修改VS的配置文件app.config,加入如下配置项。

<appSettings>

    <add key="JiebaConfigFileDir" value=fileDir />

</appSettings>

其中的fileDir就是Resource文件夹的内容所在的目录


Jieba.NET使用

分词

结巴提供了三种分词的方法:

精确模式:试图将句子最精确地切开,适合文本分析;

全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义问题;

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

对于未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法。

下面请看详细用法:

1.      精确模式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JiebaNet.Analyser;
using JiebaNet.Segmenter;
namespace JiebaTest
{
    class Program
    {
        static void Main(string[] args)
               {          
                       string text = "当人类得知宇宙的黑暗森林状态后,这个在篝火旁大喊的孩子立刻 浇灭了火,在黑暗中瑟瑟发抖,连一颗火星都害怕了。在最初的几天里,甚至民用移动通信都被禁止,全球大部分通信基站 都被强令关闭。";
            var segmenter = new JiebaSegmenter();
            //使用精确模式,函数参数:待分词文本,关闭全模式,不使用hmm模型
            var result = segmenter.Cut(text,cutAll: false, hmm: false);
            Console.WriteLine("{0}", string.Join("/", result));
            Console.ReadKey();
                }
        }
}

运行结果:

2.png

2.      全模式

与精确模式不同的只有如下一句:

var result1 = segmenter.Cut(text, cutAll:true, hmm: false);


3.png

可以看到会有重复分词的现象,这是因为结巴分词把歧义词项一并列出来的缘故。

3.      搜索模式

            var result2 = segmenter.CutForSearch(text,hmm:true);
            Console.WriteLine("{0}", string.Join("/", result2));

CutForSearch()只有两个参数,意义也和精确模式一样,程序运行结果:

4.png

 

自定义分词

很多时候我们需要针对自己的场景进行分词,会有一些领域内的专有词汇。这时候我们需要让分词器能识别特定的词或者不识别特定的词。有两种方法:

1. 对于少量的词汇,我们可以通过AddWord()函数添加新词和调整词频,通过DeleteWord()函数删除词典中的某一词使分词器不再将其作为一个词;若AddWord()的参数freq不是正整数,则使用自动计算出的词频,计算出的词频可保证该词被分出来。

2. 通过LoadUserDict(“usr_dict_path”)函数添加自定义的词典。开发者可以指定自定义的词典,以便包含jieba词库里没有的词。虽然jieba有新词识别能力,但是自行添加新词可以保证更高的正确率。词典格式与主词典格式相同,即一行包含:词、词频(可省略)、词性(可省略),用空格隔开。词频省略时,分词器将使用自动计算出的词频保证该词被分出。

下面请看详细介绍:

1.AddWord()DeleteWord()的使用

程序运行结果:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JiebaNet.Analyser;
using JiebaNet.Segmenter;
 
namespace JiebaTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var segmenter = new JiebaSegmenter();
            string hello = "大家好,这个人叫陈建驱";
            var result_hello = segmenter.Cut(hello,hmm:false);
            Console.WriteLine("{0}", string.Join("/", result_hello));
 
            segmenter.AddWord("陈建驱",freq:0,tag:null);
            var result_hello1 = segmenter.Cut(hello, hmm: false);
            Console.WriteLine("{0}", string.Join("/", result_hello1));
 
            segmenter.DeleteWord("陈建驱"); //使用JiebaSegmenter.DeleteWord(word)可移除一个词,使其不能被分出来
            var result_hello2 = segmenter.Cut(hello, hmm: false);
            Console.WriteLine("{0}", string.Join("/", result_hello2));
            Console.ReadKey();
        }
    }
}

5.png

2.加载自定义词典

首先加载这个词典,我把它放到了运行目录下:

6.png

然后对比加载词典前后的区别,代码如下:

segmenter.LoadUserDict("usr_dict_file_path");
var result_hello1 = segmenter.Cut(hello, hmm: false);
Console.WriteLine("{0}", string.Join("/", result_hello1));

运行结果:

待续。。。

 

关键词提取

Jieba有两种关键词提取的算法,一种是基于TF-IDF算法的关键词提取,另一种是基于TextRank算法的关键词提取。后者在语料库小时效果较差。

TF-IDF关键词抽取基于逆向文件频率(IDF),组件内置一个IDF语料库,可以配置为其它自定义的语料库。关键词抽取会过滤停用词(Stop Words),组件内置一个停用词语料库,这个语料库合并了NLTK的英文停用词和哈工大的中文停用词。

1.      基于TF-IDF的关键词提取

通过TfidfExtractor类的ExtractTags()方法调用该算法,刚方法有四个参数,各个参数的意义如下:

text:待提取的文本

count:选取权值最高的count个词返回

allowPos:对哪些词进行选取,null为默认值,表示不过滤特定的词


代码实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JiebaNet.Analyser;
using JiebaNet.Segmenter;
 
namespace JiebaTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string text1 = "在塔的二层,被剑钉在墙上的女魔法师死了,她可能是人类历史上唯 一真正的魔法师。而在这之前约十小时,短暂的魔法时代也结束了。魔 法时代开始于公元 1453 年 5 月 3 日 16 时,那时高维碎块首次接触地球; 结束于 1453 年 5 月 28 日 21 时,这时碎块完全离开地球;历时二十五天 五小时。之后,这个世界又回到了正常的轨道上。29 日傍晚,君士坦丁堡陷落了。在一天的惨烈血战接近尾声时,君士坦丁十一世面对着蜂拥而来的 奥斯曼军队,高喊一声:“滩道就没有一个基督徒来砍下我的头吗?!”然后 皇帝掀下紫袍,拔剑冲人敌阵,他那银色的盔甲像扔进暗红色镪水的一小 片锡箔,转瞬间无影无踪。。。。。。君士坦丁堡陷落的历史意义许久之后才显现出来,事情发生时人们 首先想到的,就是罗马帝国终于完全消失了。拜占庭是古罗马拖在身后 的长达千年的车辙,虽也有过辉煌,但还是终于像烈日下的水渍一样蒸发 了。当年,古罗马人在宏伟华丽的浴宫中吹着口哨,认为帝国就像身下的 浴池一样,建在整块花岗岩上,将永世延续。现在人们知道,没有不散的宴席,一切都有个尽头。";
            var tf_idf = new TfidfExtractor();
            var tem = tf_idf.ExtractTags(text1, count: 10, allowPos: null);
            Console.WriteLine("{0}", string.Join("/", tem));
            Console.WriteLine("\n");
            //带权重的关键词提取
            var temW = tf_idf.ExtractTagsWithWeight(text1, count: 10, allowPos: null);
            foreach(var t in temW)
                Console.WriteLine("{0} {1}",t.Word,t.Weight);
            Console.WriteLine("\n");
            Console.ReadKey();
        }
    }
}

运行结果:

7.png

 

2.      基于TextRank的关键词提取

该算法通过TextRankExtractor类的ExtractTags()类调用,方法参数和上面的一样。下面的代码中对提取的范围进行了限制,只分析动词和名词。

代码实现:


List<string> strlist = new List<string>();
strlist.Add("n");
strlist.Add("v");
IEnumerable<string> allow=strlist;
//基于TextRank的关键词提取,接口和TF-IDF一样
var textRankExtractor = new TextRankExtractor();
//textRankExtractor.Span = 5;//调整固定窗口的大小
var tem1 = textRankExtractor.ExtractTags(text1,count:10,allowPos:allow);
Console.WriteLine("{0}", string.Join("/", tem1));
Console.WriteLine("\n");
var tem2 = textRankExtractor.ExtractTagsWithWeight(text1, count: 10, allowPos: null);
foreach (var t in tem2)
  Console.WriteLine("{0} {1}", t.Word, t.Weight);
Console.WriteLine("\n");

 

运行结果:

8.png

词性标注

通过jieba.posseg.POSTokenizer(tokenizer=None) 可以自定义分词器,tokenizer 参数可指定内部使用的 jieba.Tokenizer 分词器。jieba.posseg.dt 为默认词性标注分词器。标注句子分词后每个词的词性,采用和 ictclas 兼容的标记法。

代码如下:

var posSegmenter = new JiebaNet.Segmenter.PosSeg.PosSegmenter();
var tokens = posSegmenter.Cut(text);
Console.WriteLine(string.Join(" ", tokens.Select(token => string.Format("{0}/{1}", token.Word, token.Flag))));

运行结果:

9.png

词性符号采用《计算所汉语词性标记集》,详情可查看附录。

 

获取单词位置

代码实现:

var tk = segmenter.Tokenize(text);
//var tk1 = segmenter.Tokenize(text,TokenizerMode.Search);//搜索模式
foreach(var t in tk)
    Console.WriteLine("word{0,-12} start:{1,-3} end:{2,-3}", t.Word, t.StartIndex, t.EndIndex);

运行结果:

10.png

 

参考文献

[1]CSDN博客:山鹰的天空. jieba.NETjieba中文分词的.NET版本(C#实现)。.

https://blog.csdn.net/lansetiankong12/article/details/53485816. 2016-12-06

[2]博客园:Ander Cui. jieba中文分词的.NET版本:jieba.NET.

https://www.cnblogs.com/anderslly/p/jiebanet.html .2015-09-08

 

附录

计算所汉语词性标记集

Version 3.0

制订人:刘群 张华平 张浩

计算所汉语词性标记集

0.    说明

1.    名词  (1个一类,7个二类,5个三类)

2.    时间词(1个一类,1个二类)

3.    处所词(1个一类)

4.    方位词(1个一类)

5.    动词(1个一类,9个二类)

6.    形容词(1个一类,4个二类)

7.    区别词(1个一类,2个二类)

8.    状态词(1个一类)

9.    代词(1个一类,4个二类,6个三类)

10.          数词(1个一类,1个二类)

11.          量词(1个一类,2个二类)

12.          副词(1个一类)

13.          介词(1个一类,2个二类)

14.          连词(1个一类,1个二类)

15.          助词(1个一类,15个二类)

16.          叹词(1个一类)

17.          语气词(1个一类)

18.          拟声词(1个一类)

19.          前缀(1个一类)

20.          后缀(1个一类)

21.          字符串(1个一类,2个二类)

22.          标点符号(1个一类,16个二类)

 

0.          说明

计算所汉语词性标记集(共计99个,22个一类,66个二类,11个三类)主要用于中国科学院计算技术研究所研制的汉语词法分析器、句法分析器和汉英机器翻译系统。

1.          名词  (1个一类,7个二类,5个三类)

名词分为以下子类:

名词

nr 人名

nr1 汉语姓氏

nr2 汉语名字

nrj 日语人名

nrf 音译人名

ns 地名

nsf 音译地名

nt 机构团体名

nz 其它专名

nl 名词性惯用语

ng 名词性语素

2.          时间词(1个一类,1个二类)

时间词

tg 时间词性语素

3.          处所词(1个一类)

处所词

4.          方位词(1个一类)

方位词

5.          动词(1个一类,9个二类)

动词

vd 副动词

vn 名动词

vshi 动词“是”

vyou 动词“有”

vf 趋向动词

vx 形式动词

vi 不及物动词(内动词)

vl 动词性惯用语

vg 动词性语素

6.          形容词(1个一类,4个二类)

形容词

ad 副形词

an 名形词

ag 形容词性语素

al 形容词性惯用语

7.          区别词(1个一类,2个二类)

区别词

 

bl 区别词性惯用语

8.          状态词(1个一类)

状态词

9.          代词(1个一类,4个二类,6个三类)

代词

rr 人称代词

rz 指示代词

rzt 时间指示代词

rzs 处所指示代词

rzv 谓词性指示代词

ry 疑问代词

ryt 时间疑问代词

rys 处所疑问代词

ryv 谓词性疑问代词

rg 代词性语素

10.     数词(1个一类,1个二类)

数词

mq 数量词

11.     量词(1个一类,2个二类)

量词

qv 动量词

qt 时量词

12.     副词(1个一类)

副词

13.     介词(1个一类,2个二类)

介词

pba 介词“把”

pbei 介词“被”

14.     连词(1个一类,1个二类)

连词

    cc 并列连词

15.     助词(1个一类,15个二类)

助词

uzhe 

ule  

uguo 

ude1  

ude2 

ude3 

usuo 

udeng  等等 云云

uyy 一样 一般 似的 

udh 的话

uls 来讲 来说 而言 说来

 

uzhi 

ulian  (“连小学生都会”)

 

16.     叹词(1个一类)

叹词

17.     语气词(1个一类)

语气词(delete yg)

18.     拟声词(1个一类)

拟声词

19.     前缀(1个一类)

前缀

20.     后缀(1个一类)

后缀

21.     字符串(1个一类,2个二类)

字符串

    xe  Email字符串

xs 微博会话分隔符

xm 表情符合

xu 网址URL

 

 

22.     标点符号(1个一类,16个二类)

标点符号

wkz 左括号,全角:(              半角:( [ { <

wky 右括号,全角:)          半角: ) ] { >

wyz 左引号,全角:“   

wyy 右引号,全角:”  

wj 句号,全角:。

ww 问号,全角:? 半角:?

wt 叹号,全角:! 半角:!

wd 逗号,全角:, 半角:,

wf 分号,全角:; 半角: ;

wn 顿号,全角:、

wm 冒号,全角:: 半角: :

ws 省略号,全角:……  

wp 破折号,全角:――   --   ――-   半角:---  ----

wb 百分号千分号,全角:%    半角:%

wh 单位符号,全角:¥    °    半角:$

 


上一篇:

首页 所有文章 机器人 计算机视觉 自然语言处理 机器学习 编程随笔 关于