目标

查看jieba分词组件源码,分析源码各个模块的功能,找到分词模块,实现能自定义分词字典,且优先级大于系统自带的字典等级,以医疗词语邻域词语为例。

jieba分词地址:github地址:https://github.com/fxsjy/jieba

jieba四种分词模式

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

    • 按照优先级只显示一次需要划分的词语。

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

    • 比如清华大学,会划词显示 清华/ 清华大学/ 华大/ 大学 四个词

  • 搜索引擎模式,在精确模式的基础上,对长词再次切分。

    • 如中国科学院计算所,会分词为 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所。

  • 使用了Paddlepaddle框架,暂时跳过。

根据任务需求,因为只需要将优先级高的特有名词显示一次即可,所以定位在精确模式

相关API

1. 自定义词典
Tokenizer.suggest_freq(segment, tune=False) -> int
Suggest word frequency to force the characters in a word to be joined or splitted.

Parameter: - segment : The segments that the word is expected to be cut into, If the word should be treated as a whole, use a str. - tune : If True, tune the word frequency.
- 如果需要将两个词当成连贯的词语,则传入一个str。
- 如果需要把一个词语中的两个词当成不同的词语,则传入一个元组。
- tune设为True,表示启用。            
Note that HMM may affect the final result. If the result doesn't change, set HMM=False.
2.关键词提取 jieba.analyse.extract_tags
3.词性标注 jieba.posseg.cut
4.返回词语在原文的起止位置 jieba.tokenize

方案一

将自己需要的分词的词语,加入自定义词典当中

  • 开发者可以指定自己自定义的词典,以便包含 jieba 词库里没有的词。虽然 jieba 有新词识别能力,但是自行添加新词可以保证更高的正确率

  • 用法: jieba.load_userdict(file_name) # file_name 为文件类对象或自定义词典的路径

  • 词典格式和 dict.txt 一样,一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。file_name 若为路径或二进制方式打开的文件,则文件必须为 UTF-8 编码。

  • 词频省略时使用自动计算的能保证分出该词的词频。

分为以下几步

  1. 构建自定义词典为相应的格式 部分结果如下:

    21-羟化酶缺陷症
    CO2潴留
    E字征
    HIV感染
    Howship-Romberg征
    Korsakov综合征
    Moro反应迟钝
    Q-T间期延长
    畏寒
  2. 调用相应的方法

  3. 测试相应的数据

#encoding=utf-8
from __future__ import print_function, unicode_literals
import sys
sys.path.append("../")
import jieba
import jieba.posseg as pseg
import os
path = os.getcwd()
# 添加用户词典
jieba.load_userdict(path + "\\userdict.txt")

test_sent = (
"无畏寒一过性心尖部收缩期杂音测试"
)
words = jieba.cut(test_sent)
print('/'.join(words))

添加前 无畏寒一过性心尖部收缩期杂音测试 => 无畏/寒一/过性/心尖/部/收缩期/杂音/测试 添加后 => 无畏/寒/一过性心尖部收缩期杂音/测试

分析

添加前后,jiaba分词能将一过性心尖部收缩期杂音分词成功,但是无畏寒没有成功,可能是因为无畏在系统中的频度较高,(词频省略时使用自动计算的能保证分出该词的词频。)而用户词典的词语频度相对较低。所以下一步,我将尝试提高用户词典频度,或者降低系统词典频度。如若不行,可进一步查看源码。

继续测试

jieba.add_word('畏寒', freq=10, tag=None) # 通过添加畏寒的频度为10
结果仍为:无畏/寒/一过性心尖部收缩期杂音/测试
jieba.add_word('畏寒', freq=100, tag=None) # 添加畏寒的频度为100
结果为:无/畏寒/一过性心尖部收缩期杂音/测试

由上述结果可知确实若用户词典省略词频,则频度相对系统词典较低,无法正确分出结果,所以只需要在用户添加频度,并设置在100,便可以实现分词功能,但是不能确定是不是100就能覆盖所有的系统词汇,所以进一步查看系统词汇的词频。在源码中有dict.txt其中包含了所有的词语的系统词频,查看其中的最大值。

#encoding=utf-8
from __future__ import print_function, unicode_literals
import sys
sys.path.append("../")
import jieba
import jieba.posseg as pseg
import os
path = os.getcwd()
# 获取系统词典路径
rpath = path + "\\jieba\\dict.txt"
# 读取词典
import pandas as pd
res = pd.read_csv(rpath,sep=' ',header=None,names = ['name','frequence','type'])
print(res.head())
print(res['frequence'].max())
# 得到结果883634 ,也就是如果将词频设置为大于883634的数则用户词典绝对优先于系统词典

设置代码如下

import numpy as np
import pandas as pd

import os
path = os.getcwd() #获取当前工作路径
print(path)
output_file = os.path.join(path,'userdict.txt')

# 处理词典
res = pd.read_csv('zhengzhuang.txt',sep=' ',header = None,names=['name','type','frequence'])
print(res.head())

res = res.drop(labels=['type', 'frequence'],axis=1)
print(res.head())
# 添加频度
res['frequence'] = 883635
# 转化为txt文件
res.to_csv(output_file,sep=' ',index=False,header=False)

在添加症状的词条后测试如下

患者1月前无明显诱因及前驱症状下出现腹泻,起初稀便,后为水样便,无恶心呕吐,每日2-3次,无呕血,无腹痛,无畏寒寒战,无低热盗汗,无心悸心慌,无大汗淋漓,否认里急后重感,否认蛋花样大便,当时未重视,未就诊。
患者/1/月前/无/明显/诱因/及/前驱/症状/下/出现/腹泻/,/起初/稀便/,/后/为/水样便/,/无/恶心/呕吐/,/每日/2/-/3/次/,/无/呕血/,/无/腹痛/,/无/畏寒/ 寒战/,/无/低热/盗汗/,/无/心悸/心慌/,/无/大汗淋漓/,/否认/里急后重/感/,/否认/蛋/花样/大便/,/当时/未/重视/,/未/就诊/。

方案二

查看源码,从cut入手一步步查看其内部如何调用的

__init__.py
cut = dt.cut # cut为全局方法
# 关键方法
def cut(self, sentence, cut_all=False, HMM=True, use_paddle=False):
       """
      The main function that segments an entire sentence that contains
      Chinese characters into separated words.

      Parameter:
          - sentence: The str(unicode) to be segmented.
          - cut_all: Model type. True for full pattern, False for accurate pattern.
          - HMM: Whether to use the Hidden Markov Model.
      """
        # 判断是存在paddle
       is_paddle_installed = check_paddle_install['is_paddle_installed']
       # 转码,英文utf8 中文gbk
       sentence = strdecode(sentence)
       # paddle相关
       if use_paddle and is_paddle_installed:
           # if sentence is null, it will raise core exception in paddle.
           if sentence is None or len(sentence) == 0:
               return
           import jieba.lac_small.predict as predict
           results = predict.get_sent(sentence)
           for sent in results:
               if sent is None:
                   continue
               yield sent
           return
       
       re_han = re_han_default
       re_skip = re_skip_default
       # 判断cut 模式
       if cut_all:
           cut_block = self.__cut_all # 全模式
       elif HMM:
           cut_block = self.__cut_DAG # HMM模型 默认为这种
       else:
           cut_block = self.__cut_DAG_NO_HMM # 无HMM模型
       
       blocks = re_han.split(sentence)  # 正则表达式获取相应的字符串 现根据标点符号分词
       print(blocks)
       for blk in blocks:
           if not blk:
               continue
           if re_han.match(blk): # 没有空格
               for word in cut_block(blk):
                   yield word
           else:
               tmp = re_skip.split(blk) # 去空格
               for x in tmp:
                   if re_skip.match(x):
                       yield x
                   elif not cut_all:
                       for xx in x:
                           yield xx
                   else:
                       yield x

由上述代码可知默认的模型__cut_DAG, 输入的字符串,首先根据标点符号分词,然后cut_block负责处理每一个初步拆分过后的字符串,具体的拆分方法为以下两个函数

 # 得到的最大概率路径的概率。这里即为动态规划查找最大概率路径
   def calc(self, sentence, DAG, route):
       N = len(sentence)
       route[N] = (0, 0)
       # 通过词频搜索
       logtotal = log(self.total) #利用total进行动态规划
       for idx in xrange(N - 1, -1, -1):
           route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -
                             logtotal + route[x + 1][0], x) for x in DAG[idx])

   # 函数功能为把输入的句子生成有向无环图, 得到所有可能的词语
   def get_DAG(self, sentence):
       self.check_initialized()
       DAG = {}
       N = len(sentence)
       for k in xrange(N):
           tmplist = []
           i = k
           frag = sentence[k]
           while i < N and frag in self.FREQ: # 查询FREQ中的词
               if self.FREQ[frag]:
                   tmplist.append(i)
               i += 1
               frag = sentence[k:i + 1]
           if not tmplist:
               tmplist.append(k)
           DAG[k] = tmplist
       return DAG

由此可知FREQ和total是函数计算的关键,所以找到FREQ和total是如何初始化的就可以明白计算的依据了

 self.FREQ, self.total = self.gen_pfdict(self.get_dict_file()) # 得到词语和词频
 # 通过get_dict_file获得
 def get_dict_file(self):
       if self.dictionary == DEFAULT_DICT:
           return get_module_res(DEFAULT_DICT_NAME)
       else:
           return open(self.dictionary, 'rb')
 # 默认为该目录下的dict.txt
 DEFAULT_DICT_NAME = "dict.txt"

上述推理可知,是dict.txt中的词语和词频,通过有向无环图和动态规划路径得到分词结果,而在之前通过用户词典调用的方法无非就是在此基础上加上新的词语和词频。

    def load_userdict(self, f):
       '''
      Load personalized dict to improve detect rate.

      Parameter:
          - f : A plain text file contains words and their ocurrences.
                Can be a file-like object, or the path of the dictionary file,
                whose encoding must be utf-8.

      Structure of dict file:
      word1 freq1 word_type1
      word2 freq2 word_type2
      ...
      Word type may be ignored
      '''
       self.check_initialized()
       if isinstance(f, string_types):
           f_name = f
           f = open(f, 'rb')
       else:
           f_name = resolve_filename(f)
       for lineno, ln in enumerate(f, 1):
           line = ln.strip()
           if not isinstance(line, text_type):
               try:
                   line = line.decode('utf-8').lstrip('\ufeff')
               except UnicodeDecodeError:
                   raise ValueError('dictionary file %s must be utf-8' % f_name)
           if not line:
               continue
           # match won't be None because there's at least one character
           word, freq, tag = re_userdict.match(line).groups() # 得到用户词典词语和词频
           if freq is not None:
               freq = freq.strip()
           if tag is not None:
               tag = tag.strip()
           self.add_word(word, freq, tag) # 添加到词典中
 def add_word(self, word, freq=None, tag=None):
       """
      Add a word to dictionary.

      freq and tag can be omitted, freq defaults to be a calculated value
      that ensures the word can be cut out.
      """
       self.check_initialized()
       word = strdecode(word)
       freq = int(freq) if freq is not None else self.suggest_freq(word, False)
       # 添加词
       self.FREQ[word] = freq
       self.total += freq
       if tag:
           self.user_word_tag_tab[word] = tag
       for ch in xrange(len(word)):
           wfrag = word[:ch + 1]
           if wfrag not in self.FREQ:
               self.FREQ[wfrag] = 0
       if freq == 0:
           finalseg.add_force_split(word)

结论

构建一个用户词典表,然后将词语的词频设置为大于883634的数,则用户词典绝对优先于系统词典,其中的工程量主要在如何构造一个合适的字典,在通过调用load_userdict 方法,在用cut方法即可得到设置后的结果,具体操作见方案一。接下来的研究目标:1.用户词典大小最大可以有多大 2.用户词典大小对速度的影响 3.有相同前缀和后缀的词汇如何区分 4. 和百度分词的API对比。

jieba分词原理解析:用户词典如何优先于系统词典的更多相关文章

  1. Jieba分词原理与解析

    https://www.jianshu.com/p/dfdfeaa7d01f 1 HMM模型   image.png 马尔科夫过程:   image.png   image.png 以天气判断为例:引 ...

  2. jieba分词原理-DAG(NO HMM)

    最近公司在做一个推荐系统,让我给论坛上的帖子找关键字,当时给我说让我用jieba分词,我周末回去看了看,感觉不错,还学习了一下具体的原理 首先,通过正则表达式,将文章内容切分,形成一个句子数组,这个比 ...

  3. 自然语言处理课程(二):Jieba分词的原理及实例操作

    上节课,我们学习了自然语言处理课程(一):自然语言处理在网文改编市场的应用,了解了相关的基础理论.接下来,我们将要了解一些具体的.可操作的技术方法. 作为小说爱好者的你,是否有设想过通过一些计算机工具 ...

  4. jieba分词的功能和性能分析

    jieba分词问题导引 用户词典大小最大可以有多大 用户词典大小对速度的影响 有相同前缀和后缀的词汇如何区分 对比百度分词的API 问题一:词典大小 从源码大小分析,整个jieba分词的源码总容量为8 ...

  5. jiba中文分词原理

    中文分词就是将一个汉字序列分成一个一个单独的词. 现有的分词算法有三大类: 基于字符串匹配的分词:机械分词方法,它是按照一定的策略将待分析的字符串与一个充分大的机器词典中的词条进行匹配,若在词典中找到 ...

  6. 中科院分词ICTCLAS导入用户词典后分词结果一样?

    package ICTCLAS.I3S.Test; import java.io.UnsupportedEncodingException; import ICTCLAS.I3S.AC.ICTCLAS ...

  7. python jieba分词(添加停用词,用户字典 取词频

    中文分词一般使用jieba分词 1.安装 pip install jieba 2.大致了解jieba分词 包括jieba分词的3种模式 全模式 import jieba seg_list = jieb ...

  8. Jieba分词包(一)——解析主函数cut

    1. 解析主函数cut Jieba分词包的主函数在jieba文件夹下的__init__.py中,在这个py文件中有个cut的函数,这个就是控制着整个jieba分词包的主函数.    cut函数的定义如 ...

  9. Request 接收参数乱码原理解析一:服务器端解码原理

    “Server.UrlDecode(Server.UrlEncode("北京")) == “北京””,先用UrlEncode编码然后用UrlDecode解码,这条语句永远为true ...

随机推荐

  1. redis在物理机部署模式下如何进行资源[cpu、网卡]隔离

    上周末晚上运营做直播,业务代码不规范,访问1个redis竟然把1台服务器的网卡打满了,这台服务器上的其他redis服务都受到了影响.之前没有做这方面的预案,当时又没有空闲的机器可以迁移,在当时一点办法 ...

  2. js常用框架原理

    (function(){         //存储已经创建的模块     var moduleMap = {};     //判断是否已经加载过     var fileMap   = {};     ...

  3. python跑机器学习时报错:Process finished with exit code -1073740791 (0xC0000409)

    emmm...第二次遇到这个错误了,好好的好好的卷积神经网络突然就跑不起了.就弹出一堆信息也不报那行代码错了... 记录一下: 两次解决方法相同,删h5py包 Process finished wit ...

  4. HashSet与TreeSet的区别

    HashSet元素唯一,无序,依靠hashcode(),toString()实现元素的唯一性 TreeSet元素唯一,有序,依靠bTo实现比较,即继承Comparable类并重写compareTo(O ...

  5. 攻防世界-MISC:wireshark-1

    这是攻防世界高手进阶区的第五题,题目如下: 点击下载附件一,得到一个压缩包,解压后得到一个流量包,用wireshark打开,分组字节流搜索字符串flag password的值即是flag,所以这道题的 ...

  6. 攻防世界-MISC:SimpleRAR

    这是攻防世界新手练习区的第十题,题目如下: 点击下载附件1,是一个RAR文件,解压一下,得到flag.txt文件,激动的点进去看一下 好吧,就知道没有这么简单,用010editor打开这个RAR文件, ...

  7. py文件加密打包成exe文件

    python的py.pyc.pyo.pyd文件区别 py是源文件: pyc是源文件编译后的文件: pyo是源文件优化编译后的文件: pyd是其他语言写的python库: 为什么选用Cpython .p ...

  8. python中的sort用法

    内置的列表类型提供sort的方法 可以根据多项指标给list实例中的元素排序.在默认情况下,sort方法总是按照自然升序排列列表内的元素 #升序排列 list1=[2,3,1,2,5] list1.s ...

  9. Wireshark抓包分析TCP“三次握手,四次挥手”

    1.目的 客户端与服务器之间建立TCP/IP连接,我们知道是通过三次握手,四次挥手实现的,但是很多地方对这个知识的描述仅限于理论层面,这次我们通过网络抓包的方式来看一下实际的TCP/IP传输过程. 2 ...

  10. scanf需要多输入一行是什么问题

    有大佬知道用scanf输入,执行程序要多输入一行才能运行一般是什么问题呢 scanf的问题,其中多了\n. scanf如果加入\n,会导致需要多输入一次数据. 错误实例: