目录

1.textrank源码解析

2.textrank源码中UndirectWeightedGraph类方法分解解析

(1)初始化函数

(2)添加边的函数def addEdge(self, start, end, weight)

(3)def rank(self)函数(个人觉得在这个无向有权图类中最重要的一部分)

3.textrank源码中TextRank(KeywordExtractor)类的代码分片解释

(1)类的初始化片段

(2) def pairfilter(self, wp)函数

(3)def textrank()非常重要


1.textrank源码解析

#!/usr/bin/env python
# -*- coding: utf-8 -*-from __future__ import absolute_import, unicode_literals
import sys
from operator import itemgetter
from collections import defaultdict
import jieba.posseg
from .tfidf import KeywordExtractor
from .._compat import *class UndirectWeightedGraph:d = 0.85def __init__(self):self.graph = defaultdict(list)#这是进行分词后的一个词典def addEdge(self, start, end, weight):# use a tuple (start, end, weight) instead of a Edge objectself.graph[start].append((start, end, weight))self.graph[end].append((end, start, weight))def rank(self):ws = defaultdict(float)#权值list表outSum = defaultdict(float)# 初始化各个结点的权值# 统计各个结点的出度的次数之和wsdef = 1.0 / (len(self.graph) or 1.0)for n, out in self.graph.items():ws[n] = wsdefoutSum[n] = sum((e[2] for e in out), 0.0)#e[2]是什么?# this line for build stable iterationsorted_keys = sorted(self.graph.keys())# 遍历若干次for x in xrange(10):  # 10 iters#遍历各个节点for n in sorted_keys:s = 0# 遍历结点的入度结点for e in self.graph[n]:# 将这些入度结点贡献后的权值相加# 贡献率 = 入度结点与结点n的共现次数 / 入度结点的所有出度的次数s += e[2] / outSum[e[1]] * ws[e[1]]# 更新结点n的权值ws[n] = (1 - self.d) + self.d * s(min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])# 获取权值的最大值和最小值for w in itervalues(ws):if w < min_rank:min_rank = wif w > max_rank:max_rank = w# 对权值进行归一化for n, w in ws.items():# to unify the weights, don't *100.ws[n] = (w - min_rank / 10.0) / (max_rank - min_rank / 10.0)return wsclass TextRank(KeywordExtractor):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.dtself.stop_words = self.STOP_WORDS.copy()self.pos_filt = frozenset(('ns', 'n', 'vn', 'v'))self.span = 5def pairfilter(self, wp):return (wp.flag in self.pos_filt and len(wp.word.strip()) >= 2and wp.word.lower() not in self.stop_words)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.cutif False, return a list of words"""self.pos_filt = frozenset(allowPOS)#定义无向有权图g = UndirectWeightedGraph()#定义共现词典cm = defaultdict(int)#分词words = tuple(self.tokenizer.cut(sentence))#一次遍历每个词for i, wp in enumerate(words):#词i满足过滤条件if self.pairfilter(wp):# 依次遍历词i 之后窗口范围内的词for j in xrange(i + 1, i + self.span):# 词j 不能超出整个句子if j >= len(words):break# 词j不满足过滤条件,则跳过if not self.pairfilter(words[j]):continue# 将词i和词j作为key,出现的次数作为value,添加到共现词典中if allowPOS and withFlag:cm[(wp, words[j])] += 1else:cm[(wp.word, words[j].word)] += 1# 依次遍历共现词典的每个元素,将词i,词j作为一条边起始点和终止点,共现的次数作为边的权重for terms, w in cm.items():g.addEdge(terms[0], terms[1], w)# 运行textrank算法nodes_rank = g.rank()# 根据指标值进行排序if withWeight:tags = sorted(nodes_rank.items(), key=itemgetter(1), reverse=True)else:tags = sorted(nodes_rank, key=nodes_rank.__getitem__, reverse=True)# 输出topK个词作为关键词if topK:return tags[:topK]else:return tagsextract_tags = textrank

本人主要研究的是关键词提取,之前看了很多博客,但是都没有研究过textrank源码,仅是是用别人的代码来跑程序,今天是对源码的一个学习,其主调函数是TextRank.textrank函数,主要是在jieba/analyse/textrank.py中实现。

这是源代码主要的目录,包含两个类,就是UndirectWeightedGraph和TextRank(KeywordExtractor)。下面进行分别阐述

2.textrank源码中UndirectWeightedGraph类方法分解解析

(1)初始化函数

    def __init__(self):self.graph = defaultdict(list)

其初始化函数__init__中的self.graph = defaultdict(list)实质上就是一个字典,这个字典是嵌套,是将列表存储在字典中,词典的Key是后续要添加的词,词典的value则是一个由(起始点,终止点,边的权重)构成的三元组所组成的列表,表示已这个词作为起始点的所有的边。

(2)添加边的函数def addEdge(self, start, end, weight)

def addEdge(self, start, end, weight):# use a tuple (start, end, weight) instead of a Edge objectself.graph[start].append((start, end, weight))self.graph[end].append((end, start, weight))

无向有权图添加边的操作是在addEdge函数中完成的,因为是无向图,所以我们需要依次将start作为起始点,end作为终止点,然后再将start作为终止点,end作为起始点,这两条边的权重是相同的。

(3)def rank(self)函数(个人觉得在这个无向有权图类中最重要的一部分)

这个代码中的解释是博客搜索中常规的解释,但是有些部分解释的没有那么详细,本人凭自己的理解做一个详细的分析:

def rank(self):ws = defaultdict(float)outSum = defaultdict(float)wsdef = 1.0 / (len(self.graph) or 1.0)# 初始化各个结点的权值# 统计各个结点的出度的次数之和for n, out in self.graph.items():ws[n] = wsdefoutSum[n] = sum((e[2] for e in out), 0.0)# this line for build stable iterationsorted_keys = sorted(self.graph.keys())# 遍历若干次for x in xrange(10):  # 10 iters# 遍历各个结点for n in sorted_keys:s = 0# 遍历结点的入度结点for e in self.graph[n]:# 将这些入度结点贡献后的权值相加# 贡献率 = 入度结点与结点n的共现次数 / 入度结点的所有出度的次数s += e[2] / outSum[e[1]] * ws[e[1]]# 更新结点n的权值ws[n] = (1 - self.d) + self.d * s(min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])# 获取权值的最大值和最小值for w in itervalues(ws):if w < min_rank:min_rank = wif w > max_rank:max_rank = w# 对权值进行归一化for n, w in ws.items():# to unify the weights, don't *100.ws[n] = (w - min_rank / 10.0) / (max_rank - min_rank / 10.0)return ws

昨天晚上写的一部分:在UndirectWeightedGraph类中的函数rank是textrank算法的迭代,首先对每个节点赋予相同的权重,并统计该节点的所有出度的次数之和,接着就是进行迭代计算,在每次的迭代中,依次遍历每个节点;对于节点n,首先要根据无向有权图得到节点n的所有入度节点,对于无向有权图,入度节点和出度节点是相同的,都是与节点n相连的节点, (实际上无向有权图可以看做是双向图,也就是双箭头,节点之间有入度那就有出度),在这里比较疑惑地是outSum[n] = sum((e[2] for e in out), 0.0)中的e[2]是什么,在源码中也没有找到e[2]来源于哪里?在前面我们已经计算出这个入度节点的所有出度的次数,而它对于节点的权值的共现等于它本身的权值  乘以 它与节点n的共现次数/和各节点的所有出度的次数,将各个入度节点得到的权值相加,再乘以一定的阻尼系数,即可得到节点n的权值,迭代完成后,对权值进行归一化,并返回各个节点及其对应的权重。(一知半解状态)

今天:一句一句进行分析

        ws = defaultdict(float)outSum = defaultdict(float)

我把这两句理解为对结点权值和节点出度之和分别定义了一个字典

wsdef = 1.0 / (len(self.graph) or 1.0)

这句代码就是初始化各个结点的权值

for n, out in self.graph.items():ws[n] = wsdefoutSum[n] = sum((e[2] for e in out), 0.0)

这是个循环结构,对n各结点的权值赋值为初始值=》然后统计各个结点的出度次数之和,e应该是结点指向其他节点的边,而e[2]应该是代表结点出度的边的值,因为无向图可以视作一个双向图,既有指出的也有链入的,如图。

sorted_keys = sorted(self.graph.keys())# 遍历若干次for x in xrange(10):  # 10 iters# 遍历各个结点for n in sorted_keys:s = 0# 遍历结点的入度结点for e in self.graph[n]:# 将这些入度结点贡献后的权值相加# 贡献率 = 入度结点与结点n的共现次数 / 入度结点的所有出度的次数s += e[2] / outSum[e[1]] * ws[e[1]]# 更新结点n的权值ws[n] = (1 - self.d) + self.d * s

这一部分博客上只做了简单的介绍,个人觉得应该与textrank公式对应起来理解,首先代码的第一行是对图keys进行一个排序,重要的是遍历中的内容,首先限定了迭代的次数,然后在每次迭代中依次遍历排序keys中的每个节点,接下来的s的计算应该与textrank的公式对照起来,如图所示:

代码中的e[2]应该对应公式中Wji,即结点vj到结点vi的边的权重,而outSum[e[1]] * ws[e[1]]则对应公式中的分母部分,就是将节点vj指定其他节点的边的权重相加作为分母,结点vj到结点vi的边的权重在vj出度中的占比,而s+= e[2] / outSum[e[1]] * ws[e[1]]就是textrank中所对应的d的后半部分,应该这样就比较清晰了。

 (min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])# 获取权值的最大值和最小值for w in itervalues(ws):if w < min_rank:min_rank = wif w > max_rank:max_rank = w# 对权值进行归一化for n, w in ws.items():# to unify the weights, don't *100.ws[n] = (w - min_rank / 10.0) / (max_rank - min_rank / 10.0)return ws

剩下的就是权值大小的获取,但是看不懂的是(min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])这句代码,为什么要选取这两个值作为比较的基准值呢sys.float_info[0], sys.float_info[3]?(需要思考)

3.textrank源码中TextRank(KeywordExtractor)类的代码分片解释

(1)类的初始化片段

    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.dtself.stop_words = self.STOP_WORDS.copy()self.pos_filt = frozenset(('ns', 'n', 'vn', 'v'))self.span = 5

类在初始化时,默认加载了分词函数和词性标注函数tokenizer = postokenizer = jieba.posseg.dt、停用词表stop_words = self.STOP_WORDS.copy()、词性过滤集合pos_filt = frozenset((‘ns’, ‘n’, ‘vn’, ‘v’)),窗口span = 5,((“ns”, “n”, “vn”, “v”))表示词性为地名、名词、动名词、动词。

(2) def pairfilter(self, wp)函数

    def pairfilter(self, wp):return (wp.flag in self.pos_filt and len(wp.word.strip()) >= 2and wp.word.lower() not in self.stop_words)#strip()该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。

这个函数在网上没有找到类似的答案,本人根据下文的程序代码,将其理解为是定义了一个分词过滤条件,wp.flag应该是词所对应的词性要满足词性过滤条件,wp.word的词长要大于2,且词是非停用词。疑问;wp是个具体的什么形式?暂时没想明白。

(3)def textrank()非常重要

def textrank(self, sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False):

在def textrank(self, sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False):中传入的参数withFlag如何理解,它与withWeight为false的情况下返回的结果有何差异?需要好好考虑。

在做遍历前的代码解释;

        self.pos_filt = frozenset(allowPOS)g = UndirectWeightedGraph()cm = defaultdict(int)words = tuple(self.tokenizer.cut(sentence))#tuple是一个元组,tuple是一种有序列表,它和list非常相似,但tuple一旦初始化就不能修改,而且没有append() insert()这些方法,可以获取元素但不能赋值变成另外的元素

首先是引入词性限制集合,即:self.pos_filt = frozenset(allowPOS),其中 frozenset()函数的含义是:返回一个冻结的集合,冻结后集合不能再添加或删除任何元素。然后定义一个无向有权图,以及共现词典,然后使用结巴分词器tokenizer分词器进行分词,tuple()是一个元组,tuple也是一种有序列表,它和list非常相似, 但tuple一旦初始化就不能修改,而且没有append() insert()这些方法,可以获取元素但不能赋值变成另外的元素,分词结果应该是带有词性的分词结果;

下面是两个遍历;

第一个遍历:

        for i, wp in enumerate(words):#词i满足过滤条件if self.pairfilter(wp):# 依次遍历词i 之后窗口范围内的词for j in xrange(i + 1, i + self.span):# 词j 不能超出整个句子if j >= len(words):break# 词j不满足过滤条件,则跳过if not self.pairfilter(words[j]):continue# 将词i和词j作为key,出现的次数作为value,添加到共现词典中if allowPOS and withFlag:cm[(wp, words[j])] += 1else:cm[(wp.word, words[j].word)] += 1

依次遍历分词结果,如果某个词满足过滤条件(词性在词性过滤集合中 allowPos,并且长度大于等于2,且不是停用词),将这个词之后窗口范围内的词j(也要满足前面所述条件),将它们两两(词i和词j)作为key,出现的次数作为value添加到共现词典中,即cm词典格式[(词i,词j):次数];

第二个遍历;

        for terms, w in cm.items():g.addEdge(terms[0], terms[1], w)# 运行textrank算法nodes_rank = g.rank()# 根据指标值进行排序if withWeight:#如果有权值,则返回单词和权值列表,从大到小排序输出tags = sorted(nodes_rank.items(), key=itemgetter(1), reverse=True)else:tags = sorted(nodes_rank, key=nodes_rank.__getitem__, reverse=True)# 输出topK个词作为关键词if topK:return tags[:topK]else:return tagsextract_tags = textrank

然后,依次遍历共现词典,将词典中的每个元素,key = (词i,词j),value = 词i和词j共现的次数,其中词i,词j作为一条边的起始点和终止点,共现次数作为边的权重,添加到之前定义的无向有权图中。

然后对这个无向有权图进行迭代计算textrank,最终经过若干次迭代后,收敛,每个词都对应一个指标值;

如果设置了权重标志位,则根据指标值对无向有权图中的词进行降序排序,最后输出top K个词作为关键词。

这里简单讲解一下key=itemgetter(1)中的itemgetter函数,因为自己不懂,就进行了粗略的的查询,它是operator模块中的函数,用于获取对象的哪些维度的数据,参数为一些序号(即需要获取的数据在对象中的序号);

还查了一下__getitem__但是没有看懂,后续查了再补充。

如果有对源码有更好理解的,希望能够彼此互相学习!

下面是学习的博客链接:

https://www.cnblogs.com/tsdblogs/p/9982886.html

https://blog.csdn.net/Atishoo_13/article/details/86616607

https://www.cnblogs.com/zhbzz2007/p/6177832.html

https://blog.csdn.net/qq_41664845/article/details/82869596

TextRank源码的学习与详细解析相关推荐

  1. 从另一个角度去解读Blinker,剖析精简源码,学习开源精神,菜鸟哥还是忍不住对它下手了

    文章目录 1.解读起因 2.解读点 2.1 解读硬性要求 3.解读过程 3.1 解读理念 3.1.1 官方说明 3.2 解读组合方式 3.2.1 绿色 -- 必选宏 BLINKER_BLE -- bl ...

  2. rust墙壁升级点什么_分享:如何在阅读Rust项目源码中学习

    今天做了一个Substrate相关的小分享,公开出来. 因为我平时也比较忙,昨天才选定了本次分享的主题,准备比较仓促,细节可能不是很充足,但分享的目的也是给大家提供一个学习的思路,更多的细节大家可以在 ...

  3. Python源码剖析[16] —— Pyc文件解析

    Python源码剖析[16] -- Pyc文件解析 2008-02-28 18:29:55|  分类: Python |举报 |字号 订阅 Python源码剖析 --Pyc文件解析 本文作者: Rob ...

  4. 【栖梧-源码-spring】@Bean从解析到注册到beanDefinitionMap

    [栖梧-源码-spring]@Bean从解析到注册到beanDefinitionMap 序幕 源码阅读技巧 本文说明 类 ConfigurationClassParser#doProcessConfi ...

  5. Vue 源码阅读学习(三)

    第三节:函数柯里化与渲染模型 嘿,朋友们,本节是 Vue 源码阅读的第三讲.Vue 源码阅读系列得到了赞赏,我很高兴,同时希望大家可以给予反馈!我虚心接纳您的意见! 如果没有看之前的第一讲和第二讲的内 ...

  6. Apollo源码剖析学习笔记2

    Apollo 源码剖析学习笔记2 Talker-ListenerNode 目录中包含了 Node 对象.Reader 对象和 Writer 对象.Node 对象主要对应 Ros 中的 Node 节点, ...

  7. 跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?(源码感悟)

    来自:源码笔记 1 前言 由于现在微服务很流行,越来越多企业采用了SpringCloud微服务架构,而SpringBoot则是快速构建微服务项目的利器.于是笔者以此为切入点,将SpringBoot作为 ...

  8. 为什么要学习源码?学习源码对我们有用吗?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 1 前言 由于现在微服务很流行,越来越多企业采用了Spr ...

  9. STL源码剖析学习七:stack和queue

    STL源码剖析学习七:stack和queue stack是一种先进后出的数据结构,只有一个出口. 允许新增.删除.获取最顶端的元素,没有任何办法可以存取其他元素,不允许有遍历行为. 缺省情况下用deq ...

最新文章

  1. 解题报告(二)多项式问题(多项式乘法及其各种运算)(ACM/ OI)超高质量题解
  2. html 点击一行变色,elementui点击table每一行会变色,当有固定列的时候,
  3. 分享:手机应用存5个严重的信息安全隐患你晓得吗?
  4. Android开源工具库
  5. Django小项目简单BBS论坛
  6. react16.8+的生命周期
  7. Zdenek Kalal的TLD Tracker(牛啊,学习!)
  8. 来自数据库的MVC 6动态导航菜单
  9. Java爬虫Crawler
  10. Mybatis常见配置错误总结
  11. 《阿里感悟》- 技术人员的职业规划
  12. 七种方法绕过安卓手机锁屏
  13. 什么Yate开放模式?
  14. R语言怎么写积分_2. 角速度的积分
  15. QT设置按钮QPushButton上图片加文字
  16. JAVA 中的修饰符的适用范围
  17. 深度学习在嵌入式设备上的应用
  18. 手机活动轨迹查询,究竟是什么原理?
  19. 我一定要把我stupid史纲论文发出来贻笑大方
  20. 如何设计千万级数据的java对账系统之一

热门文章

  1. WPS-Excel如何将xlsx表格文件转换为PDF文件 - 表格转图片 - 表格转PDF
  2. TurboMail邮件系统助力振华物流近十年
  3. 高德地图手动输入地址自动导航+webview加载JS
  4. python边玩边学_边干边学:协程—在Android中使用协程进行改装请求的指南
  5. 【iOS紫色警告】GPUImage启动摄像头耗时
  6. plt.savefig()保存图片打开却神魔都没有
  7. matlab删除三维数组中全零行或列
  8. 美女似茶说 女人30绿茶香
  9. Ice框架和简单实践-python
  10. 16年河北省职称计算机试题,河北省职称计算机考试真题..doc