当前位置:   article > 正文

jieba分词入门_ieba.analyse.extract_tags

ieba.analyse.extract_tags

(主要内容均来源于github上的说明)

特点

  • 支持三种分词模式:
    • 精确模式,试图将句子最精确地切开,适合文本分析;
    • 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
    • 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
  • 支持繁体分词。
  • 支持自定义词典。
  • MIT授权协议。

安装说明

  • 全自动安装:easy_install jieba 或者 pip install jieba / pip3 install jieba。
  • 半自动安装:先下载 http://pypi.python.org/pypi/jieba/ ,解压后运行 python setup.py install。
  • 手动安装:将 jieba 目录放置于当前目录或者 site-packages 目录。
  • 通过 import jieba 来引用。

算法

  • 基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图 (DAG)。
  • 采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合。
  • 对于未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法。

主要功能-分词

  • jieba.cut 方法接受三个输入参数: 需要分词的字符串;cut_all 参数用来控制是否采用全模式;HMM参数用来控制是否使用HMM模型。
  • jieba.cut_for_search 方法接受两个参数:需要分词的字符串;是否使用HMM模型。该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细。
    • jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用for 循环来获得分词后得到的每一个词语(unicode),或者用jieba.lcut以及jieba.cut_for_search直接返回list。
  • jieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定义分词器,可用于同时使用不同词典。
  • jieba.dt 为默认分词器,所有全局分词相关函数都是该分词器的映射。

注:待分词的字符串可以是 unicode 或 UTF-8 字符串、GBK 字符串。注意:不建议直接输入 GBK 字符串,可能无法预料地错误解码成 UTF-8

分词示例代码:

seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
print("Full Mode: " + "/ ".join(seg_list))  # 全模式
输出:
Full Mode: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学

seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  # 精确模式,不写cut_all,即默认也是精确模式
输出:
Default Mode: 我/ 来到/ 北京/ 清华大学

seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")  # 搜索引擎模式
print(", ".join(seg_list))
输出:小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, ,, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
载入词典:
  • 开发者可以指定自己自定义的词典,以便包含 jieba 词库里没有的词。虽然 jieba 有新词识别能力,但是自行添加新词可以保证更高的正确率。
  • 用法:jieba.load_userdict(file_name) 。说明:file_name 为文件类对象或自定义词典的路径。
  • 词典格式和 dict.txt 一样,一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。file_name 若为路径或二进制方式打开的文件,则文件必须为 UTF-8 编码。
  • 词频省略时使用自动计算的能保证分出该词的词频。
    例如:
创新办 3 i
云计算 5
凱特琳 nz
台中
  • 1
  • 2
  • 3
  • 4
  • 更改分词器(默认为 jieba.dt)的 tmp_dir 和 cache_file 属性,可分别指定缓存文件所在的文件夹及其文件名,用于受限的文件系统。
  • 使用 add_word(word, freq=None, tag=None) 和 del_word(word) 可在程序中动态修改词典。
  • 使用 suggest_freq(segment, tune=True) 可调节单个词语的词频,使其能(或不能)被分出来。

注意:自动计算的词频在使用 HMM 新词发现功能时可能无效。

自定义词典示例代码:

首先,创建一个名为dict_hrjlk_test.dt词典文件,内容如下:

云计算 5
李小福 2 nr
创新办 3 i
easy_install 3 eng
好用 300
韩玉赏鉴 3 nz
八一双鹿 3 nz
台中
凱特琳 nz
Edu Trust认证 2000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

示例代码如下:

import jieba
jieba.load_userdict("/Users/roy/Run/jieba-dict/dict_hrjlk_test.dt")
jieba.add_word('石墨烯')
  • 1
  • 2
  • 3
  • 此时去检查字典文件,发现’石墨烯’并没有被写入到字典文件中,可见只能是保存到内存中了,或者在jieba中称为缓存*
testStr='「台中」正確應該不會被切開。mac上可分出「石墨烯」;此時又可以分出來凱特琳了。'
words = jieba.cut(testStr)
print('/'.join(words))
输出:
「/台中/」/正確/應該/不會/被/切開/。/mac/上/可/分出/「/石墨烯/」/。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 这时’石墨烯’可以被分出来
jieba.del_word('石墨烯')
words = jieba.cut(testStr)
print('/'.join(words))
「/台中/」/正確/應該/不會/被/切開/。/mac/上/可/分出/「/石墨/烯/」/。
  • 1
  • 2
  • 3
  • 4
  • 从字典中动态删除’石墨烯’后,不会被识别为一个词了,被分开。

主要功能-关键词提取

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

这里提取的是关键词,即能很好的标识出文章的词,不是高频词

import jieba.analyse
  • 1
  • jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
    • sentence 为待提取的文本。
    • topK 为返回几个 TF/IDF 权重最大的关键词,默认值为 20。
    • withWeight 为是否一并返回关键词权重值,默认值为 False。
    • allowPOS 仅包括指定词性的词,默认值为空,即不筛选。
  • jieba.analyse.TFIDF(idf_path=None) 新建 TFIDF 实例,idf_path 为 IDF 频率文件

例1(extract_tags):

testStr = '职位内容:\
1.作为大前端一员参与到六哥核心业务组件的重构与开发,包括Web端、移动端和数据可视化等;\
2.基于各类成熟技术,如PWA,WebAssembly,VUE,RN等,提升产品体验;\
3.持续思考提升工程质量的方式,如版本管理、代码重构·Code Review和自动化测试等;\
4.通过实现SSO、渐进增强、代码复用等方式实现工作自动化;\
5.分享和推动更科学的项目管理方式,帮助项目更好地成功。'
tags = jieba.analyse.extract_tags(testStr)
print(",".join(tags))
输出:
重构,自动化,方式,代码,六哥,Web,PWA,WebAssembly,VUE,RN,Code,Review,SSO,成熟技术,可视化,提升,复用,工程质量,项目管理,组件

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里是基本的提取方式,下面介绍逆向文件频率(IDF)


IDF的主要思想是:如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能力。
  • 1
  • 2

语料文件示例:

劳动防护 13.900677652
勞動防護 13.900677652
生化学 13.900677652
生化學 13.900677652
奥萨贝尔 13.900677652
奧薩貝爾 13.900677652
考察队员 13.900677652
考察隊員 13.900677652
岗上 11.5027823792
崗上 11.5027823792
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

代码示例:

testStr ='职位内容:\
1.作为大前端一员参与到六哥核心业务组件的重构与开发,包括Web端、移动端和数据可视化等;\
2.基于各类成熟技术,如PWA,WebAssembly,VUE,RN等,提升产品体验;\
3.持续思考提升工程质量的方式,如版本管理、代码重构·Code Review和自动化测试等;\
4.通过实现SSO、渐进增强、代码复用等方式实现工作自动化;\
5.分享和推动更科学的项目管理方式,帮助项目更好地成功。'

jieba.analyse.set_idf_path("/Users/roy/Run/jieba-dict/idf.dt");
tags = jieba.analyse.extract_tags(testStr,topK=10)
print(",".join(tags))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

停用词

语料文件示例

one
has
or
that
的
了
和
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

代码示例:

testStr ='职位内容:\
1.作为大前端一员参与到六哥核心业务组件的重构与开发,包括Web端、移动端和数据可视化等;\
2.基于各类成熟技术,如PWA,WebAssembly,VUE,RN等,提升产品体验;\
3.持续思考提升工程质量的方式,如版本管理、代码重构·Code Review和自动化测试等;\
4.通过实现SSO、渐进增强、代码复用等方式实现工作自动化;\
5.分享和推动更科学的项目管理方式,帮助项目更好地成功。'

jieba.analyse.set_stop_words("/Users/roy/Run/jieba-dict/stop_words.dt")
tags = jieba.analyse.extract_tags(testStr,topK=10)
print(",".join(tags))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

关键词一并返回关键词权重值

testStr = '职位内容:\
1.作为大前端一员参与到六哥核心业务组件的重构与开发,包括Web端、移动端和数据可视化等;\
2.基于各类成熟技术,如PWA,WebAssembly,VUE,RN等,提升产品体验;\
3.持续思考提升工程质量的方式,如版本管理、代码重构·Code Review和自动化测试等;\
4.通过实现SSO、渐进增强、代码复用等方式实现工作自动化;\
5.分享和推动更科学的项目管理方式,帮助项目更好地成功。'

tags = jieba.analyse.extract_tags(testStr, topK=10, withWeight=True)
for tag in tags:
    print("tag: %s\t\t weight: %f" % (tag[0],tag[1]))
输出:
tag: 重构		 weight: 0.335018
tag: 自动化		 weight: 0.254009
tag: 方式		 weight: 0.240075
tag: 代码		 weight: 0.239148
tag: 六哥		 weight: 0.220126
tag: Web		 weight: 0.199246
tag: PWA		 weight: 0.199246
tag: WebAssembly		 weight: 0.199246
tag: VUE		 weight: 0.199246
tag: RN		 weight: 0.199246
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

基于TextRank算法的关键词抽取

  • jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=(‘ns’, ‘n’, ‘vn’, ‘v’)) 直接使用,接口相同,注意默认过滤词性。
  • jieba.analyse.TextRank() 新建自定义 TextRank 实例

基本思想:

  • 将待抽取关键词的文本进行分词
  • 以固定窗口大小(默认为5,通过span属性调整),词之间的共现关系,构建图
  • 计算图中节点的PageRank,注意是无向带权图

主要功能-词性标注

  • jieba.posseg.POSTokenizer(tokenizer=None) 新建自定义分词器,tokenizer 参数可指定内部使用的 jieba.Tokenizer 分词器。jieba.posseg.dt 为默认词性标注分词器。
  • 标注句子分词后每个词的词性,采用和 ictclas 兼容的标记法。
  • 按词性过滤结果,是个有效的技巧

代码如下

import jieba
import jieba.posseg as pseg
words = pseg.cut('我爱北京天安门')
for word, flag in words:
    print('%s %s' % (word, flag))
输出:
我 r
爱 v
北京 ns
天安门 ns
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

尝试改变词性

jieba.add_word('天安门', tag='nnn')
for word, flag in words:
    print('%s %s' % (word, flag))
输出:
我 r
爱 v
北京 ns
天安门 nnn

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

主要功能-并行分词

  • 原理:将目标文本按行分隔后,把各行文本分配到多个 Python 进程并行分词,然后归并结果,从而获得分词速度的可观提升
  • 基于 python 自带的 multiprocessing 模块,目前暂不支持 Windows
jieba.enable_parallel(4) # 开启并行分词模式,参数为并行进程数
jieba.disable_parallel() # 关闭并行分词模式
  • 1
  • 2
  • 实验结果:在 4 核 3.4GHz Linux 机器上,对金庸全集进行精确分词,获得了 1MB/s 的速度,是单进程版的 3.3 倍。

注意:并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt。

主要功能-返回词语在原文的起止位置

默认模式

result = jieba.tokenize('永和服装饰品有限公司')
for tk in result:
    print('word %s\t\t start: %d \t\t end:%d' % (tk[0],tk[1],tk[2]))
输出:
word 永和		 start: 0 		 end:2
word 服装		 start: 2 		 end:4
word 饰品		 start: 4 		 end:6
word 有限公司		 start: 6 		 end:10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

搜索模式

result = jieba.tokenize('永和服装饰品有限公司', mode='search')
for tk in result:
    print('word %s\t\t start: %d \t\t end:%d' % (tk[0],tk[1],tk[2]))
输出:
word 永和                start: 0                end:2
word 服装                start: 2                end:4
word 饰品                start: 4                end:6
word 有限                start: 6                end:8
word 公司                start: 8                end:10
word 有限公司            start: 6                end:10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

##主要功能-ChineseAnalyzer for Whoosh 搜索引擎
(不了解whoosh的,可以看另一篇关于whoosh的文章)

from __future__ import unicode_literals
import sys,os
sys.path.append("../")
from whoosh.index import create_in,open_dir
from whoosh.fields import *
from whoosh.qparser import QueryParser

from jieba.analyse.analyzer import ChineseAnalyzer

analyzer = ChineseAnalyzer()

schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True, analyzer=analyzer))
if not os.path.exists("tmp"):
    os.mkdir("tmp")

ix = create_in("tmp", schema) # for create new index
#ix = open_dir("tmp") # for read only
writer = ix.writer()

writer.add_document(
    title="document1",
    path="/a",
    content="This is the first document we’ve added!"
)

writer.add_document(
    title="document2",
    path="/b",
    content="The second one 你 中文测试中文 is even more interesting! 吃水果"
)

writer.add_document(
    title="document3",
    path="/c",
    content="买水果然后来世博园。"
)

writer.add_document(
    title="document4",
    path="/c",
    content="工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作"
)

writer.add_document(
    title="document4",
    path="/c",
    content="咱俩交换一下吧。"
)

writer.commit()
searcher = ix.searcher()
parser = QueryParser("content", schema=ix.schema)

for keyword in ("水果世博园","你","first","中文","交换机","交换"):
    print("result of ",keyword)
    q = parser.parse(keyword)
    results = searcher.search(q)
    for hit in results:
        print(hit.highlights("content"))
    print("="*10)

for t in analyzer("我的好朋友是李明;我爱北京天安门;IBM和Microsoft; I have a dream. this is intetesting and interested me a lot"):
    print(t.text)
  • 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
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

##主要功能-命令行接口
实例

python -m jieba news.txt > cut_result.txt
  • 1

命令行选项(翻译):

使用: python -m jieba [options] filename
固定参数:
  filename              输入文件
可选参数:
  -h, --help            显示此帮助信息并退出
  -d [DELIM], --delimiter [DELIM]
                        使用 DELIM 分隔词语,而不是用默认的' / '。
                        若不指定 DELIM,则使用一个空格分隔。
  -p [DELIM], --pos [DELIM]
                        启用词性标注;如果指定 DELIM,词语和词性之间
                        用它分隔,否则用 _ 分隔
  -D DICT, --dict DICT  使用 DICT 代替默认词典
  -u USER_DICT, --user-dict USER_DICT
                        使用 USER_DICT 作为附加词典,与默认词典或自定义词典配合使用
  -a, --cut-all         全模式分词(不支持词性标注)
  -n, --no-hmm          不使用隐含马尔可夫模型
  -q, --quiet           不输出载入信息到 STDERR
  -V, --version         显示版本信息并退出

如果没有指定文件名,则使用标准输入。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

延时加载机制

jieba 采用延迟加载,import jieba 和 jieba.Tokenizer() 不会立即触发词典的加载,一旦有必要才开始加载词典构建前缀字典。如果你想手工初始 jieba,也可以手动初始化。

import jieba
jieba.initialize()  # 手动初始化(可选)
  • 1
  • 2

在 0.28 之前的版本是不能指定主词典的路径的,有了延迟加载机制后,你可以改变主词典的路径:

jieba.set_dictionary('data/dict.txt.big')
  • 1

其它词典

1.占用内存较小的词典文件 https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.small

2.支持繁体分词更好的词典文件 https://github.com/fxsjy/jieba/raw/master/extra_dict/dict.txt.big

3.下载你所需要的词典,然后覆盖 jieba/dict.txt 即可;或者用 jieba.set_dictionary(‘data/dict.txt.big’)

算法释义

HMM(Hidden Markov Model): 隐式马尔科夫模型

HMM的典型介绍就是这个模型是一个五元组:

  • StatusSet: 状态值集合
  • ObservedSet: 观察值集合
  • TransProbMatrix: 转移概率矩阵
  • EmitProbMatrix: 发射概率矩阵
  • InitStatus: 初始状态分布

HMM模型可以用来解决三种问题:

  • 参数(StatusSet,TransProbMatrix,EmitRobMatrix,InitStatus)已知的情况下,求解观察值序列。(Forward-backward算法)
  • 参数(ObservedSet,TransProbMatrix,EmitRobMatrix,InitStatus)已知的情况下,求解状态值序列。(viterbi算法)
  • 参数(ObservedSet)已知的情况下,求解(TransProbMatrix,EmitRobMatrix,InitStatus)。(Baum-Welch算法)

其中,第三种问题最玄乎也最不常用,第二种问题最常用,【中文分词】,【语音识别】, 【新词发现】, 【词性标注】 都有它的一席之地。所以本文主要介绍第二种问题,即【viterbi算法求解状态值序列】的方法。

StatusSet & ObservedSet

状态值集合为(B, M, E, S): {B:begin, M:middle, E:end, S:single}。分别代表每个状态代表的是该字在词语中的位置,B代表该字是词语中的起始字,M代表是词语中的中间字,E代表是词语中的结束字,S则代表是单字成词。

观察值集合为就是所有汉字(东南西北你我他…),甚至包括标点符号所组成的集合。

状态值也就是我们要求的值,在HMM模型中文分词中,我们的输入是一个句子(也就是观察值序列),输出是这个句子中每个字的状态值。 比如:

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

输出的状态序列为

BEBEBMEBEBMEBES
  • 1

根据这个状态序列我们可以进行切词:

BE/BE/BME/BE/BME/BE/S
  • 1

所以切词结果如下:

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

同时我们可以注意到:

B后面只可能接(M or E),不可能接(B or E)。而M后面也只可能接(M or E),不可能接(B, S)。

这五元的关系是通过一个叫Viterbi的算法串接起来, ObservedSet序列值是Viterbi的输入, 而StatusSet序列值是Viterbi的输出, 输入和输出之间Viterbi算法还需要借助三个模型参数, 分别是InitStatus, TransProbMatrix, EmitProbMatrix, 接下来一一讲解:
####InitStatus
初始概率分布

#B
-0.26268660809250016
#E
-3.14e+100
#M
-3.14e+100
#S
-1.4652633398537678
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

示例数值是对概率值取对数之后的结果(可以让概率相乘的计算变成对数相加),其中-3.14e+100作为负无穷,也就是对应的概率值是0。下同。

也就是句子的第一个字属于{B,E,M,S}这四种状态的概率,如上可以看出,E和M的概率都是0,这和实际相符合,开头的第一个字只可能是词语的首字(B),或者是单字成词(S)。
####TransProbMatrix
转移概率是马尔科夫链很重要的一个知识点,大学里面学过概率论的人都知道,马尔科夫链最大的特点就是当前T=i时刻的状态Status(i),只和T=i时刻之前的n个状态有关。也就是:

{Status(i-1), Status(i-2), Status(i-3), ... Status(i - n)}
更进一步的说,HMM模型有三个基本假设(具体哪三个请看文末备注)作为模型的前提,其中有个【有限历史性假设】,也就是马尔科夫链的n=1。即Status(i)只和Status(i-1)相关,这个假设能大大简化问题。
  • 1
  • 2

回过头看TransProbMatrix,其实就是一个4x4(4就是状态值集合的大小)的二维矩阵,示例如下:

矩阵的横坐标和纵坐标顺序是BEMS x BEMS。(数值是概率求对数后的值,别忘了。)

-3.14e+100 -0.510825623765990 -0.916290731874155 -3.14e+100
-0.5897149736854513 -3.14e+100 -3.14e+100 -0.8085250474669937
-3.14e+100 -0.33344856811948514 -1.2603623820268226 -3.14e+100
-0.7211965654669841 -3.14e+100 -3.14e+100 -0.6658631448798212
  • 1
  • 2
  • 3
  • 4

比如TransProbMatrix[0][0]代表的含义就是从状态B转移到状态B的概率,由TransProbMatrix[0][0] = -3.14e+100可知,这个转移概率是0,这符合常理。由状态各自的含义可知,状态B的下一个状态只可能是ME,不可能是BS,所以不可能的转移对应的概率都是0,也就是对数值负无穷,在此记为-3.14e+100。

由上TransProbMatrix矩阵可知,对于各个状态可能转移的下一状态,且转移概率对应如下:

#B
#E:-0.510825623765990,M:-0.916290731874155
#E
#B:-0.5897149736854513,S:-0.8085250474669937
#M
#E:-0.33344856811948514,M:-1.2603623820268226
#S
#B:-0.7211965654669841,S:-0.6658631448798212
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
EmitProbMatrix

这里的发射概率(EmitProb)其实也是一个条件概率而已,根据HMM模型三个基本假设(哪三个请看文末备注)里的【观察值独立性假设】,观察值只取决于当前状态值,也就是:

P(Observed[i], Status[j]) = P(Status[j]) * P(Observed[i]|Status[j])
  • 1

其中P(Observed[i]|Status[j])这个值就是从EmitProbMatrix中获取。

EmitProbMatrix示例如下:

#B
耀:-10.460283,涉:-8.766406,谈:-8.039065,伊:-7.682602,洞:-8.668696,...
#E
耀:-9.266706,涉:-9.096474,谈:-8.435707,伊:-10.223786,洞:-8.366213,...
#M
耀:-8.47651,涉:-10.560093,谈:-8.345223,伊:-8.021847,洞:-9.547990,....
#S
蘄:-10.005820,涉:-10.523076,唎:-15.269250,禑:-17.215160,洞:-8.369527...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

到此,已经介绍完HMM模型的五元参数,假设现在手头上已经有这些参数的具体概率值,并且已经加载进来,那么我们只剩下Viterbi这个算法函数,这个模型就算可以开始使用了。所以接下来讲讲Viterbi算法。

HMM中文分词之Viterbi算法

输入样例:

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

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

且由EmitProbMatrix可以得出

Status(B) -> Observed(小)  :  -5.79545
Status(E) -> Observed(小)  :  -7.36797
Status(M) -> Observed(小)  :  -5.09518
Status(S) -> Observed(小)  :  -6.2475
  • 1
  • 2
  • 3
  • 4

所以可以初始化 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
  • 1
  • 2
  • 3
  • 4

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

遍历句子计算整个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;
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

确定边界条件和路径回溯
边界条件如下:

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

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

在本例中:

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

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

回溯的路径是:

SEBEMBEBEMBEBEB
  • 1

倒序一下就是:

BE/BE/BME/BE/BME/BE/S
所以切词结果就是:

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

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

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

模型的训练问题
以上讲的前提是基于模型来进行切词,也就是假设我们手头上的HMM模型已经是被训练好了的(也就是InitStatus, TransProbMatrix, EmitProbMatrix这三个模型的关键参数都是已知的),没有涉及到这三个参数是如何得到的。 这三个参数其实也是基于已分词完毕的语料进行统计计算,计算出相应的频率和条件概率就可以算出这三个参数。具体在此就不讲了。

备注
HMM模型的三个基本假设如下:

  • 有限历史性假设:
P(Status[i]|Status[i-1],Status[i-2],... Status[1]) = P(Status[i]|Status[i-1])
  • 1
  • 齐次性假设(状态和当前时刻无关):
P(Status[i]|Status[i-1]) = P(Status[j]|Status[j-1])
  • 1
  • 观察值独立性假设(观察值只取决于当前状态值):
P(Observed[i]|Status[i],Status[i-1],...,Status[1]) = P(Observed[i]|Status[i])
  • 1

词性说明

缩写词性说明
Ag形语素形容词性语素。形容词代码为 a,语素代码g前面置以A。
a形容词取英语形容词 adjective的第1个字母。
ad副形词直接作状语的形容词。形容词代码 a和副词代码d并在一起。
an名形词具有名词功能的形容词。形容词代码 a和名词代码n并在一起。
b区别词取汉字“别”的声母。
c连词取英语连词 conjunction的第1个字母。
dg副语素副词性语素。副词代码为 d,语素代码g前面置以D。
d副词取adverb的第2个字母,因其第1个字母已用于形容词。
e叹词取英语叹词exclamation的第1个字母。
f方位词取汉字“方”
g语素绝大多数语素都能作为合成词的“词根”,取汉字“根”的声母。
h前接成分取英语head的第1个字母。
i成语取英语成语idiom的第1个字母。
j简称略语取汉字“简”的声母。
k后接成分
l习用语习用语尚未成为成语,有点“临时性”,取“临”的声母。
m数词取英语numeral的第3个字母,n,u已有他用。
Ng名语素名词性语素。名词代码为 n,语素代码g前面置以N。
n名词取英语名词noun的第1个字母。
nr人名名词代码n和“人(ren)”的声母并在一起。
ns地名名词代码n和处所词代码s并在一起。
nt机构团体“团”的声母为t,名词代码n和t并在一起。
nz其他专名“专”的声母的第1个字母为z,名词代码n和z并在一起。
o拟声词取英语拟声词onomatopoeia的第1个字母。
p介词取英语介词prepositional的第1个字母。
q量词取英语quantity的第1个字母。
r代词取英语代词pronoun的第2个字母,因p已用于介词。
s处所词取英语space的第1个字母。
tg时语素时间词性语素。时间词代码为t,在语素的代码g前面置以T。
t时间词取英语time的第1个字母。
u助词取英语助词 auxiliary
vg动语素动词性语素。动词代码为 v。在语素的代码g前面置以V。
v动词取英语动词verb的第一个字母。
vd副动词直接作状语的动词。动词和副词的代码并在一起。
vn名动词指具有名词功能的动词。动词和名词的代码并在一起。
w标点符号
x非语素字非语素字只是一个符号,字母 x通常用于代表未知数、符号。
y语气词取汉字“语”的声母。
z状态词取汉字“状”的声母的前一个字母。
un未知词
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/348505
推荐阅读
相关标签
  

闽ICP备14008679号