docs/nlp_old/2.分词.md
分词(Word Segmentation): 将连续的自然语言文本,切分成具有语义合理性和完整性的词汇序列
例句: 致毕业和尚未毕业的同学。
致 毕业 和 尚未 毕业 的 同学致 毕业 和尚 未 毕业 的 同学今天我们聊聊 jieba 结巴分词器(牛逼)
安装
pip install jieba
引用
import jieba
特点
pip install paddlepaddle-tiny==1.6.1pip install jieba --upgrade1.精确模式
精确模式: 试图将句子最精确地切开,适合文本分析
# 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)网络模型实现分词,同时支持词性标注
pip install paddlepaddle-tiny==1.6.1pip 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 模式】 中国/科学技术/大学
延迟加载机制
import jieba 和 jieba.Tokenizer() 不会立即触发词典的加载,一旦有必要才开始加载词典构建前缀字典。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/dict.txt 即可;或者用 jieba.set_dictionary('data/dict.txt.big')载入自定义词典
jieba.load_userdict(file_name) # file_name 为文件类对象或自定义词典的路径
用空格隔开,顺序不可颠倒""" 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
优化点: 分子/分母 都加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
"""
基本思想:
举例说明
例如: 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
1−d浏览者随机跳转到一个新网页的概率
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 为默认词性标注分词器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.dt 和 jieba.posseg.dt
注意,输入参数只接受 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
"""
引用: from jieba.analyse import ChineseAnalyzer
pip install 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
"""
测试过:
呆测试: