跳转至

自然语言处理 - 2.分词

分词(Word Segmentation): 将连续的自然语言文本,切分成具有语义合理性和完整性的词汇序列

例句: 致毕业和尚未毕业的同学。

  1. 毕业 尚未 毕业 同学
  2. 毕业 和尚 毕业 同学

今天我们聊聊 jieba 结巴分词器(牛逼)

  • 第一个不靠吹嘘学校or公司,完全靠实力开源的一个项目
  • 知乎上网友评论是腾讯,而最近GitHub上看到的是百度邮箱
  • “结巴”中文分词: 做最好的 Python 中文分词组件(这个的确没吹牛!)
  • GitHub上面 README.md 为中文版本(别看这个小事,很多中国公司开源项目都是英文)

部署使用

安装

pip install jieba

引用

import jieba

特点

  • 四种分词模式
    • 精确模式: 试图将句子最精确地切开,适合文本分析;
    • 全模式: 把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义
    • 搜索引擎模式: 在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词
    • paddle模式: 利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词,同时支持词性标注
      • paddle模式使用需安装paddlepaddle-tiny: pip install paddlepaddle-tiny==1.6.1
      • 目前paddle模式支持jieba v0.40及以上版本。
      • jieba v0.40以下版本,请升级 jieba: pip install jieba --upgrade
  • 支持繁体分词
  • 支持自定义词典
  • MIT 授权协议

四种分词模式

1.精确模式

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

# encoding=utf-8
import jieba

seg_list = jieba.cut("他来到了网易杭研大厦")  # 默认是精确模式
print(", ".join(seg_list))

seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  # 精确模式

# 输出结果
#【精确模式】: 我/ 来到/ 北京/ 清华大学

2.全模式

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

# encoding=utf-8
import jieba

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


# 输出结果
#【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学

3.搜索引擎模式

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

# encoding=utf-8
import jieba

seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")  # 搜索引擎模式
print(", ".join(seg_list))

# 输出结果
#【搜索引擎模式】:  小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造

4.paddle 模式

paddle 模式: 利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词,同时支持词性标注

  • paddle模式使用需安装paddlepaddle-tiny: pip install paddlepaddle-tiny==1.6.1
  • 目前paddle模式支持jieba v0.40及以上版本。
  • jieba v0.40以下版本,请升级 jieba: pip install jieba --upgrade
# encoding=utf-8
import jieba

jieba.enable_paddle()  # 启动paddle模式。 0.40版之后开始支持,早期版本不支持
strs=["我来到北京清华大学", "乒乓球拍卖完了", "中国科学技术大学"]
for str in strs:
    seg_list = jieba.cut(str,use_paddle=True)  # 使用paddle模式
    print("Paddle Mode: " + '/'.join(list(seg_list)))

# 输出结果
#【Paddle 模式】 我/来到/北京/清华大学
#【Paddle 模式】 乒乓球/拍卖/完/了
#【Paddle 模式】 中国/科学技术/大学

添加自定义词典

延迟加载机制

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

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

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

案例

# encoding=utf-8
import sys
sys.path.append("../")
import jieba

def cuttest(test_sent):
    result = jieba.cut(test_sent)
    print("  ".join(result))

def testcase():
    cuttest("这是一个伸手不见五指的黑夜。我叫孙悟空,我爱北京,我爱Python和C++。")
    cuttest("我不喜欢日本和服。")
    cuttest("雷猴回归人间。")
    cuttest("工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作")
    cuttest("我需要廉租房")
    cuttest("永和服装饰品有限公司")
    cuttest("我爱北京天安门")
    cuttest("abc")
    cuttest("隐马尔可夫")
    cuttest("雷猴是个好网站")

if __name__ == "__main__":
    testcase()
    """foobar.txt 格式
    的 3188252 uj
    了 883634 ul
    是 796991 v
    """
    jieba.set_dictionary("foobar.txt")
    print("================================")
    testcase()

切换其他词典

载入自定义词典

  • 开发者可以指定自己自定义的词典,以便包含 jieba 词库里没有的词
    • 虽然 jieba 有新词识别能力,但是自行添加新词可以保证更高的正确率
  • 用法: jieba.load_userdict(file_name) # file_name 为文件类对象或自定义词典的路径
    • 词典格式和 dict.txt 一样,一个词占一行
    • 每一行分三部分: 词语、词频(可省略)、词性(可省略),用空格隔开顺序不可颠倒
    • file_name 若为路径或二进制方式打开的文件,则文件必须为 UTF-8 编码
    • 词频省略时使用自动计算的能保证分出该词的词频
""" filename 内容为:
创新办 3 i
云计算 5
凱特琳 nz
台中
"""
# encoding=utf-8
import jieba

seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  # 精确模式

jieba.load_userdict(file_name)

seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list))  # 精确模式

# 加载自定义词库前:  李小福 / 是 / 创新 / 办 / 主任 / 也 / 是 / 云 / 计算 / 方面 / 的 / 专家 /
# 加载自定义词库后:  李小福 / 是 / 创新办 / 主任 / 也 / 是 / 云计算 / 方面 / 的 / 专家 /

调整词典的词

  • 使用 add_word(word, freq=None, tag=None)del_word(word) 可在程序中动态修改词典。
  • 使用 suggest_freq(segment, tune=True) 可调节单个词语的词频,使其能(或不能)被分出来。

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

代码示例:

print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))
# 如果/放到/post/中将/出错/。

jieba.suggest_freq(('中', '将'), True)
# 494
print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))
# 如果/放到/post/中/将/出错/。


print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))
#「/台/中/」/正确/应该/不会/被/切开

jieba.suggest_freq('台中', True)
# 69
print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))
#「/台中/」/正确/应该/不会/被/切开

关键词提取

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

  • 基于该框架的 TF-IDF 效果一般
    • 1.它使用的是他默认的 IDF 值的文件【不是针对我们的项目】
    • 2.我们先得有词,你才能计算 IDF的值。而框架是要先提供IDF值才能计算最终的 TF-IDF 值。
  • 如果你有计算IDF的值存成文件,再加载进来,计算TF-IDF值,才能得到适合这个类型数据的值!

TF 优化点: 分子/分母 都加1

TF: term frequency 短期频率, 用于衡量一个词在一个文件中的出现频率。因为每个文档的长度的差别可以很大,因而一个词在某个文档中出现的次数可能远远大于另一个文档,所以词频通常就是一个词出现的次数除以文档的总长度,相当于是做了一次归一化。

公式: TF(t) = (词t在某个文档中出现的总次数) / (某个文档的词总数)

IDF 优化点: 分子/分母 都加1

IDF: inverse document frequency 逆向文件频率,用于衡量一个词的重要性/区分度。计算词频TF的时候,所有的词语都被当做一样重要的,但是某些词,比如”is”, “of”, “that”很可能出现很多很多次,但是可能根本并不重要,因此我们需要减轻在多个文档中都频繁出现的词的权重。

公式: IDF = log_e(总文档数/词t出现的文档数)

TF-IDF(term frequency–inverse document frequency)

公式: TF-IDF = TF*IDF

注意

非常短的文本很可能影响 tf-idf 值

行业用途

TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与文本挖掘的常用加权技术。
TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
TF-IDF加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。
除了TF-IDF以外,互联网上的搜索引擎还会使用基于连结分析的评级方法,以确定文件在搜寻结果中出现的顺序。
import jieba.analyse

# allowPOS('ns', 'n', 'vn', 'v') 地名、名词、动名词、动词
tags = 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 频率文件
    # idf是jieba通过语料库统计得到的
    # idf的值时通过语料库统计得到的,所以,实际使用时,可能需要依据使用环境,替换为使用对应的语料库统计得到的idf值。
"""

# 关键词提取所使用逆向文件频率(IDF)文本语料库可以切换成自定义语料库的路径
jieba.analyse.set_idf_path(file_name) # file_name为自定义语料库的路径

# 关键词提取所使用停止词(Stop Words)文本语料库可以切换成自定义语料库的路径
jieba.analyse.set_stop_words(file_name) # file_name为自定义语料库的路径


# 测试案例
# 下载地址: https://github.com/fxsjy/jieba/blob/master/extra_dict/stop_words.txt
"""
the
of
is
"""
jieba.analyse.set_stop_words("~/work/data/nlp/jieba/extra_dict/stop_words.txt")
# 下载地址: https://raw.githubusercontent.com/fxsjy/jieba/master/extra_dict/idf.txt.big
"""格式如下:
劳动防护 13.900677652
勞動防護 13.900677652
奥萨贝尔 13.900677652
"""
jieba.analyse.set_idf_path("~/work/data/nlp/jieba/extra_dict/idf.txt.big")
content = "此外,公司拟对全资子公司吉林欧亚置业有限公司增资4.3亿元,增资后,吉林欧亚置业注册资本由7000万元增加到5亿元。吉林欧亚置业主要经营范围为房地产开发及百货零售等业务。目前在建吉林欧亚城市商业综合体项目。2013年,实现营业收入0万元,实现净利润-139.13万元。"
# content = open("~/work/data/nlp/jieba/extra_dict/test_content.txt", 'rb').read()
for word, weight in jieba.analyse.extract_tags(content, withWeight=True):
    print('%s %s' % (word, weight))

"""输出结果:
吉林 1.0174270215234043
欧亚 0.7300142700289363
增资 0.5087135107617021
实现 0.5087135107617021
置业 0.4887134522112766
万元 0.3392722481859574
此外 0.25435675538085106
全资 0.25435675538085106
有限公司 0.25435675538085106
4.3 0.25435675538085106
注册资本 0.25435675538085106
7000 0.25435675538085106
增加 0.25435675538085106
主要 0.25435675538085106
房地产 0.25435675538085106
业务 0.25435675538085106
目前 0.25435675538085106
城市 0.25435675538085106
综合体 0.25435675538085106
2013 0.25435675538085106
"""

基于 TextRank 算法的关键词抽取

基本思想:

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

举例说明

例如:  sentence = "A B C A D B C B A"

第一次 index = 0
dict[(A, B)] = 2
dict[(A, C)] = 1
dict[(A, A)] = 1
dict[(A, D)] = 2

第一次 index = 1
dict[(B, A)] = 1
dict[(B, B)] = 1
dict[(B, C)] = 2
dict[(B, D)] = 1

由于是 无向带权图

graph[start].append((start, end, weight))
graph[end].append((end, start, weight))

假设:
A B => 20
所有 B => 50
A, C => 5
所有 C => 15
总共: 10个单词
A 权重PR值: 1/10 = 0.1
s_A = 20/50 * 0.1 + 5/15 * 0.1
d阻尼系数即按照超链接进行浏览的概率一般取经验值为0.85
1d浏览者随机跳转到一个新网页的概率
A 权重PR值: (1 - d) + d * s_A

代码案例

# encoding=utf-8
import jieba

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


# 测试案例
content = "此外,公司拟对全资子公司吉林欧亚置业有限公司增资4.3亿元,增资后,吉林欧亚置业注册资本由7000万元增加到5亿元。吉林欧亚置业主要经营范围为房地产开发及百货零售等业务。目前在建吉林欧亚城市商业综合体项目。2013年,实现营业收入0万元,实现净利润-139.13万元。"
# content = open("~/work/data/nlp/jieba/extra_dict/test_content.txt", 'rb').read()
for x, w in jieba.analyse.textrank(content, withWeight=True):
    print('%s %s' % (x, w))
"""
吉林 1.0
欧亚 0.9966893354178172
置业 0.6434360313092776
实现 0.5898606692859626
收入 0.43677859947991454
增资 0.4099900531283276
子公司 0.35678295947672795
城市 0.34971383667403655
商业 0.34817220716026936
业务 0.3092230992619838
在建 0.3077929164033088
营业 0.3035777049319588
全资 0.303540981053475
综合体 0.29580869172394825
注册资本 0.29000519464085045
有限公司 0.2807830798576574
零售 0.27883620861218145
百货 0.2781657628445476
开发 0.2693488779295851
经营范围 0.2642762173558316
"""

词性标注

  • jieba.posseg.POSTokenizer(tokenizer=None) 新建自定义分词器, tokenizer 参数可指定内部使用的 jieba.Tokenizer 分词器, jieba.posseg.dt 为默认词性标注分词器
  • 标注句子分词后每个词的词性,采用和 ictclas 兼容的标记法
  • 除了jieba默认分词模式,提供paddle模式下的词性标注功能。paddle模式采用延迟加载方式,通过 enable_paddle() 安装 paddlepaddle-tiny,并且import相关代码

paddle模式词性标注对应表如下:

paddle模式词性和专名类别标签集合如下表,其中词性标签 24 个(小写字母),专名类别标签 4 个(大写字母)

标签 含义 标签 含义 标签 含义 标签 含义
n 普通名词 f 方位名词 s 处所名词 t 时间
nr 人名 ns 地名 nt 机构名 nw 作品名
nz 其他专名 v 普通动词 vd 动副词 vn 名动词
a 形容词 ad 副形词 an 名形词 d 副词
m 数量词 q 量词 r 代词 p 介词
c 连词 u 助词 xc 其他虚词 w 标点符号
PER 人名 LOC 地名 ORG 机构名 TIME 时间
# encoding=utf-8
import jieba
import jieba.posseg as pseg


words = jieba.posseg.cut("我爱北京天安门")
for word, flag in words:
    print('%s %s' % (word, flag))

"""
我 r
爱 v
北京 ns
天安门 ns
"""

words = pseg.cut("我爱北京天安门") #jieba默认模式
jieba.enable_paddle() #启动paddle模式。 0.40版之后开始支持,早期版本不支持
words = pseg.cut("我爱北京天安门",use_paddle=True) #paddle模式
for word, flag in words:
   print('%s %s' % (word, flag))

"""
我 r
爱 v
北京 ns
天安门 ns
"""

并行分词

原理: 将目标文本按行分隔后,把各行文本分配到多个 Python 进程并行分词,然后归并结果,从而获得分词速度的可观提升

基于 python 自带的 multiprocessing 模块,目前暂不支持 Windows

用法:

  • jieba.enable_parallel(4) # 开启并行分词模式,参数为并行进程数
  • jieba.disable_parallel() # 关闭并行分词模式
import sys
import time
import jieba

jieba.enable_parallel()

url = sys.argv[1]
content = open(url, "rb").read()
t1 = time.time()
words = "/ ".join(jieba.cut(content))

t2 = time.time()
tm_cost = t2-t1

log_f = open("1.log","wb")
log_f.write(words.encode('utf-8'))

print('speed %s bytes/second' % (len(content)/tm_cost))

实验结果: 在 4 核 3.4GHz Linux 机器上,对金庸全集进行精确分词,获得了 1MB/s 的速度,是单进程版的 3.3 倍。

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

Tokenize: 返回词语在原文的起止位置

注意,输入参数只接受 unicode

默认模式

# encoding=utf-8
import jieba


result = jieba.tokenize(u'永和服装饰品有限公司')
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
"""
# encoding=utf-8
import jieba


result = jieba.tokenize(u'永和服装饰品有限公司', 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
"""

ChineseAnalyzer for Whoosh 搜索引擎

引用: from jieba.analyse import ChineseAnalyzer

  • pip install whoosh
  • Whoosh是一个用来索引文本并能根据索引搜索的的包含类和方法的类库。它允许你开发一个针对自己内容的搜索引擎。
  • 例如,如果你想创建一个博客软件,你可以使用Whoosh添加一个允许用户搜索博客类目的搜索功能。
# -*- coding: UTF-8 -*-
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)


"""
result of  水果世博园
买<b class="match term0">水果</b>然后来<b class="match term1">世博园</b>
==========
result of  你
second one <b class="match term0">你</b> 中文测试中文 is even more interesting
==========
result of  first
<b class="match term0">first</b> document we’ve added
==========
result of  中文
second one 你 <b class="match term0">中文</b>测试<b class="match term0">中文</b> is even more interesting
==========
result of  交换机
干事每月经过下属科室都要亲口交代24口<b class="match term0">交换机</b>等技术性器件的安装工作
==========
result of  交换
咱俩<b class="match term0">交换</b>一下吧
干事每月经过下属科室都要亲口交代24口<b class="match term0">交换</b>机等技术性器件的安装工作
==========


朋友

李明


北京
天安
天安门
ibm
microsoft
dream
intetest
interest
me
lot
"""

测试过:

呆测试:


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组