当前位置:   article > 正文

jieba库中基于 TextRank 算法的关键词抽取——源代码分析(一)_jieba textrank

jieba textrank

2021SC@SDUSC
2021SC


2021SC@SDUSC


前言

在一篇文章中已经提到从这篇文章开始会对jieba库中的源代码实现进行分析,首先从TextRank算法开始进行,具体算法内容及源代码分析如下:


一、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这个点到其他其他边的权重之和。

textrank公式描述

具体实现过程可以概括为以下三点:

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

二、具体实现

打开textrank.py可以看到里面主要有两个类UndirectWeightedGraph和TextRank,其实分别对应于textrank实现过程中的二、三步,第一步则是使用了jieba.cut方法,在textrank方法中使用。

类TextRank的初始化与pairfilter方法的定义

首先分析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  #窗口大小,与第二步中关键词与上下文关键词的联系有关
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

之后定义函数方法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)
  • 1
  • 2
  • 3

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))      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

首先是定义函数,然后是有关参数的介绍,然后是正式代码:

		self.pos_filt = frozenset(allowPOS)
        g = UndirectWeightedGraph() #定义无向有权图
        cm = defaultdict(int) #定义共现词典
        words = tuple(self.tokenizer.cut(sentence)) #分词
  • 1
  • 2
  • 3
  • 4

那么可以看到有关实现TextRank算法第一步——对待抽取关键词的的文本进行分词的源代码为:

words = tuple(self.tokenizer.cut(sentence))
#这句用于分词
  • 1
  • 2

在这里就需要先暂停对textrank.py中具体代码的分析,先进行对jieba.cut,也就是textrank算法实现过程第一步的分析。

用于分词的jieba.cut

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
  • 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

代码片段较长,我们可以逐步分析。

首先看到代码中的解释部分:

  """
        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: 是否使用隐式马尔科夫.
        '''
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

其中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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

第一行中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源代码进行详细研究.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/281562
推荐阅读
相关标签
  

闽ICP备14008679号