赞
踩
2021SC@SDUSC
2021SC
在一篇文章中已经提到从这篇文章开始会对jieba库中的源代码实现进行分析,首先从TextRank算法开始进行,具体算法内容及源代码分析如下:
jieba库中用于关键词提取的算法主要有两种,一种是TF-IDF算法,一种是TextRank算法。其中Text Rank算法是基于用于解决网页排名的PageRank算法。
TextRank算法思想通过词之间的相邻关系构建网络,然后用PageRank迭代计算每个节点的rank值,排序rank值即可得到关键词。
算法可以这样理解:
1.每个单词有一个rank值,rank值越高代表其重要性越高,即关键词
2.如果一个单词出现在很多单词后面的话,那么说明这个单词比较重要,其rank值升高
3.一个TextRank值很高的单词后面跟着的一个单词,那么这个单词的TextRank值会相应地因此而提高
也可以表示成如下公式(在PageRank的基础上):
S(vi)表示TextRank值,j、i在这里表示任意两个单词,Wij表示的是边的权重;
公式表示的主要意思就是:一个单词i的权重取决于与在i前面的各个点j组成的 (j,i) 这条边的权重,以及 j这个点到其他其他边的权重之和。
具体实现过程可以概括为以下三点:
1.将待抽取关键词的文本进行分词
2.以固定窗口大小(默认为5,通过span属性调整),词之间的共现关系,构建图
3.计算图中节点的PageRank,注意是无向带权图
打开textrank.py可以看到里面主要有两个类UndirectWeightedGraph和TextRank,其实分别对应于textrank实现过程中的二、三步,第一步则是使用了jieba.cut方法,在textrank方法中使用。
首先分析TextRank类,类中先进行初始化
def __init__(self):
#初始化时,默认加载分词函数tokenizer = jieba.dt以及词性标注工具jieba.posseg.dt,停用词stop_words = self.STOP_WORDS.copy(),
#词性过滤集合pos_filt = frozenset(('ns', 'n', 'vn', 'v')),窗口span = 5,(("ns", "n", "vn", "v"))表示词性为地名、名词、动名词、动词。
self.tokenizer = self.postokenizer = jieba.posseg.dt #默认加载分词函数tokenizer,词性标注工具jieba.posseg.dt
self.stop_words = self.STOP_WORDS.copy() #停用词stop_words
self.pos_filt = frozenset(('ns', 'n', 'vn', 'v')) #词性过滤集合,"ns", "n", "vn", "v"分别表示词性为地名、名词、动名词、动词
self.span = 5 #窗口大小,与第二步中关键词与上下文关键词的联系有关
之后定义函数方法pairfilter用于过滤关键词不在初始化中规定的词性,和词的长度小于2不符合条件的关键词。
def pairfilter(self, wp):
return (wp.flag in self.pos_filt and len(wp.word.strip()) >= 2
and wp.word.lower() not in self.stop_words)
TextRank中分词的具体实现是对每个句子进行分词和词性标注处理,过滤掉除指定词性外的其他单词,过滤掉出现在停用词表的单词,过滤掉长度小于2的单词
首先我们可以看到TextRank算法的源代码如下(部分):
def textrank(self, sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False): """ Extract keywords from sentence using TextRank algorithm. Parameter: - topK: return how many top keywords. `None` for all possible words. - withWeight: if True, return a list of (word, weight); if False, return a list of words. - allowPOS: the allowed POS list eg. ['ns', 'n', 'vn', 'v']. if the POS of w is not in this list, it will be filtered. - withFlag: if True, return a list of pair(word, weight) like posseg.cut if False, return a list of words """ self.pos_filt = frozenset(allowPOS) g = UndirectWeightedGraph() cm = defaultdict(int) words = tuple(self.tokenizer.cut(sentence))
首先是定义函数,然后是有关参数的介绍,然后是正式代码:
self.pos_filt = frozenset(allowPOS)
g = UndirectWeightedGraph() #定义无向有权图
cm = defaultdict(int) #定义共现词典
words = tuple(self.tokenizer.cut(sentence)) #分词
那么可以看到有关实现TextRank算法第一步——对待抽取关键词的的文本进行分词的源代码为:
words = tuple(self.tokenizer.cut(sentence))
#这句用于分词
在这里就需要先暂停对textrank.py中具体代码的分析,先进行对jieba.cut,也就是textrank算法实现过程第一步的分析。
jieba.cut是用于分词的方法,其接受四个输入参数: 需要分词的字符串;cut_all 参数用来控制是否采用全模式;HMM 参数用来控制是否使用 HMM 模型;use_paddle 参数用来控制是否使用paddle模式下的分词模式,paddle模式采用延迟加载方式,通过enable_paddle接口安装paddlepaddle-tiny,并且import相关代码。
在_init_.py中可以找到jieba.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. """ is_paddle_installed = check_paddle_install['is_paddle_installed'] sentence = strdecode(sentence) 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 if cut_all: cut_block = self.__cut_all elif HMM: cut_block = self.__cut_DAG else: cut_block = self.__cut_DAG_NO_HMM blocks = re_han.split(sentence) 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
代码片段较长,我们可以逐步分析。
首先看到代码中的解释部分:
""" 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. """ #其翻译如下: ''' jieba分词主函数,返回generator 参数: - sentence: 待切分文本. - cut_all: 切分模式. True 全模式, False 精确模式. - HMM: 是否使用隐式马尔科夫. '''
其中HMM即用于新词发现的隐式马尔科夫模型。
简单了解各变量含义之后对代码进行逐行分析
首先是第一部分:
is_paddle_installed = check_paddle_install['is_paddle_installed']
sentence = strdecode(sentence)
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
第一行中is_paddle_installed判断是否使用paddle模式下的分词模式,paddle模式最主要的特点是采用延迟加载方式。
第二行的strdecode是将sentence解码为Unicode。
若jieba 采用延迟加载,import jieba 和 jieba.Tokenizer()(用于新建自定义分词器,可用于同时使用不同词典) 不会立即触发词典的加载,一旦有必要才开始加载词典构建前缀字典。如果你想手工初始 jieba,也可以手动初始化,if结构中便是对使用paddle模式下分词的控制。
分析之后的代码可以看到与未使用paddle模式相比,paddle模式下,只使用了一层for循环,最后返回yield sent。
那么什么是yield呢?
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
那么再通看jieba.cut全部代码可知:
jieba.cut返回的是一个可迭代的generator,可以使用 for 循环来获得分词后得到的每一个词语。
以上就是今天要讲的内容,本文仅仅主要介绍了TextRank算法的第一步分词中的主要方法jieba.cut的paddle模式和其相关的yield方法,那么下篇博客会继续对textrank算法实现第一步的具体方法——jieba.cut源代码进行详细研究.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。