2021SC@SDUSC
上一篇博客中,我们分析了基于tfidf实现的关键词抽取的方法,在本篇论文中,作者的实验部分有用该方法和其他对比方法进行对比,这里我们将分析对比方法textrank方法,该方法是无监督的以图为基础的关键词抽取方法,我们将结合该方法的原理分析方法的实现。
首先本篇论文采用的数据集如下:
有监督的copyRNN采用了文档-关键词的标签和validation set,其他方法都采用了kp20k训练集的原始文档作为输入,关于kp20k训练集的处理已在前面博客中说明。
作者的实验部分将该深度模型与已有的关键词抽取方法做了对比:
作者在5个数据集上用F1分数测试了不同的关键词抽取方法,其中采用了如TF-IDF,TextRank,SingleRank等对比方法,这里我们将分析 TextRank及其实现。
一、TextRank方法原理
TextRank基于是PageRank方法的,PageRank方法主要是为网页进行排序,PageRank值越高,表示网络值越重要,简单来说,PageRank意味着网页浏览者按照以下方式在网上随机游走:以概率d按照存在的超链接随机跳转,以等概率从超链接跳转到下一个页面;或以概率(1-d)进行完全随机跳转,这时以等概率(1/n)跳转到任意网页,以此为依据,迭代地计算该过程形成的马尔可夫链上元素的PageRank值,最后得到收敛的结果。计算公式如下:
图1:TextRank中网页构成的图的示意
图源:TextRank算法详细讲解与代码实现(完整) - 方格田 - 博客园
这里首先说明,每个网页对应于构建的图中的一个节点,PR(Vi)表示结点Vi的rank值,In(Vi)表示结点Vi的前驱结点集合(链接到该网页的网页),Out(Vj)表示结点Vj的后继结点集合(从网页链接出的网页)
简要介绍完PageRank后,我们来分析TextRank的原理。类似于PageRank,TextRank通过文档中词之间的相邻关系构建网络,然后计算每个节点的rank值,排序得到的rank值高的节点即为关键词。这里存在一个从网络链接形成的图到词序列形成的图的类比,TextRank中每个词与前N个词和后N个词存在图相邻关系,这里可以参考之前分析的N-gram语法模型。也就是可以实现一个长度为N的华东窗口,这个滑动窗口里的词都视作词节点的相邻节。这样,TextRank构建的词图是无向图。基于此,TextRank得到的计算公式如下:
说明:由于不同词的共同出现有不同的权重,这里增加了权重
,表示不同词一起出现的不同权重(也就是不同节点边连接的不同权重),
为阻尼因子。
接下来我们来看在实际处理中如何应用TextRank方法提取关键短语。首先需要将输入的文本拆分为句子,在每个句子中过滤掉stoplist中的单词(可选),并只保留指定词性的单词(可选)。由此可以得到句子的集合和单词的集合。之后,每个单词作为pagerank中的一个节点。设定窗口大小为k,假设一个句子依次由下面的单词组成:
w1, w2, w3, w4, w5, ..., wn
[
、[,
, ...,
]
、[,
, ...,
]
等都是一个窗口。在一个窗口中的任两个单词对应的节点之间存在一个无向无权的边。基于上面构成图,可以计算出每个单词节点的重要性。最重要的若干单词可以作为关键词。基于pagerank实现的关键词抽取、摘要抽取过程如下图:,
, ...,
]
图2:基于textrank的文本处理
将不同的文章文档结合成一个text,先实现分句,再分词得到对应的词向量(不考虑嵌入),让后经过相似性矩阵计算,构建图,得到sentence ranking之后就可以得到摘要。
我们结合jieba中实现的TextRank方法简单地看一下方法的实现。
import jieba
import jieba.analyse
tex='本发明公开了一种永磁电机驱动的纯电动大巴车坡道起步防溜策略,即本策略当制动踏板已踩下、永磁电机转速小于设定值并持续一定时间,整车控制单元产生一个刹车触发信号,当油门踏板开度小于设定值,且档位装置为非空档时,电机控制单元产生一个防溜功能使能信号并自动进入防溜控制使永磁电机进入转速闭环控制于某个目标转速,若整车控制单元检测到制动踏板仍然踩下,则限制永磁电机输出力矩,否则,恢复永磁电机输出力矩;当整车控制单元检测到油门踏板开度大于设置值、档位装置为空档或手刹装置处于驻车位置,则退出防溜控制,同时切换到力矩控制。本策略无需更改现有车辆结构或添加辅助传感器等硬件设备,实现车辆防溜目的。'
jieba.analyse.set_stop_words("../data/stopWord.txt")
# 加载自定义停用词表
keywords = jieba.analyse.textrank(tex, topK=10, allowPOS=('n','nz','v','vd','vn','l','a','d'))
print(keywords)
这里是简单地调用了jieba.analyse中已有的textrank方法,指定得到排名前10的关键短语,留下指定词性的词语,在这之前,加载自定义停用词表。运行结果如下:
接下来我们将分析TextRank4ZH的具体实现。
总结:TextRank需要构建词图并进行迭代计算,所以提取速度较慢,并且,TextRank还是会倾向于提取出现频率较高的词,即基于词频。
二、TextRank4ZH的安装及example
安装方式:以下方式4选1即可,Python 3下需要将上面的python改成python3,pip改成pip3。
$ python setup.py install --user
$ sudo python setup.py install
$ pip install textrank4zh --user
$ sudo pip install textrank4zh
安装成功
TextRank中对输入文本的实际处理以在上文中说明,接下来,我们来分析项目给的example
#-*- encoding:utf-8 -*-
from __future__ import print_function
import sys
try:
reload(sys)
sys.setdefaultencoding('utf-8')
except:
pass
import codecs
from textrank4zh import TextRank4Keyword, TextRank4Sentence
text = codecs.open('../test/doc/01.txt', 'r', 'utf-8').read()
tr4w = TextRank4Keyword() # 初始化TextRank4Keyword对象
tr4w.analyze(text=text, lower=True, window=2)
# py2中text必须是utf8编码的str或者unicode对象,py3中必须是utf8编码的bytes或者str对象
print( '关键词:' )
# 调用get_keywords方法,指定单词最小长度为1
for item in tr4w.get_keywords(20, word_min_len=1):
print(item.word, item.weight)
print()
print( '关键短语:' )
# 调用get_keyphrases方法,指定关键短语最小出现次数为2
for phrase in tr4w.get_keyphrases(keywords_num=20, min_occur_num= 2):
print(phrase)
tr4s = TextRank4Sentence() # 初始化TextRank4Sentence对象
tr4s.analyze(text=text, lower=True, source = 'all_filters') # 指定source为all_filters
print()
print( '摘要:' )
# 调用get_key_sentences方法
for item in tr4s.get_key_sentences(num=3):
print(item.index, item.weight, item.sentence)
# index是语句在文本中位置,weight是权重
说明:类TextRank4Keyword、TextRank4Sentence在处理一段文本时会将文本拆分成4种格式:
- sentences:由句子组成的列表。
- words_no_filter:对sentences中每个句子分词而得到的两级列表
- words_no_stop_words:去掉words_no_filter中的停止词而得到的二维列表
- words_all_filters:保留words_no_stop_words中指定词性的单词而得到的二维列表
在项目的example中,作者给出了以上4种格式的一个示例,这里采用words_all_filters 。
图2:四种拆分文本的格式
我们需要处理的文本为:
运行结果如下:
图3:关键词、关键短语抽取结果
图4:摘要提取结果
三、TextRank4Keyword类的分析
TextRank4Keyword类的结构如下,有初始化init函数以及实现文本预处理的analyze函数,get_keywords得到关键词,get_keyphrases得到关键短语。
1.init函数
def __init__(self, stop_words_file = None,
allow_speech_tags = util.allow_speech_tags,
delimiters = util.sentence_delimiters):
self.text = ''
self.keywords = None
self.seg = Segmentation(stop_words_file=stop_words_file,
allow_speech_tags=allow_speech_tags,
delimiters=delimiters)
self.sentences = None
self.words_no_filter = None # 2维列表
self.words_no_stop_words = None
self.words_all_filters = None
参数说明:
- stop_words_file:指定stoplist文件路径,若为None,则使用默认stoplist文件
- delimiters:默认值是'? ! ; 。… \n',用来将文本拆分为句子。
init函数非常简单,完成了对TextRank4Keyword类对象的keyword、sentences、text、words_no_filter、self.words_no_stop_words、self.words_all_filters等属性的初始化。
2.analyze函数
def analyze(self, text,
window = 2,
lower = False,
vertex_source = 'all_filters',
edge_source = 'no_stop_words',
pagerank_config = {'alpha': 0.85,}):
self.text = text # 获取输入text
# 类似于之前的Vocabulary类中的word2idx以及index2word方法
self.word_index = {} # 初始化从word到index索引为空字典
self.index_word = {} # 初始化从index索引到word为空字典
self.keywords = [] # 初始化TextRank4Keyword类对象的keywords为空列表
self.graph = None # 初始化TextRank4Keyword类对象的图为None
result = self.seg.segment(text=text, lower=lower)
self.sentences = result.sentences
self.words_no_filter = result.words_no_filter
self.words_no_stop_words = result.words_no_stop_words
self.words_all_filters = result.words_all_filters
util.debug(20*'*')
util.debug('self.sentences in TextRank4Keyword:\n', ' || '.join(self.sentences))
util.debug('self.words_no_filter in TextRank4Keyword:\n', self.words_no_filter)
util.debug('self.words_no_stop_words in TextRank4Keyword:\n', self.words_no_stop_words)
util.debug('self.words_all_filters in TextRank4Keyword:\n', self.words_all_filters)
# 将文本的处理方式整合为options
options = ['no_filter', 'no_stop_words', 'all_filters']
# 对options中的每种方式进行处理
if vertex_source in options:
_vertex_source = result['words_'+vertex_source]
else:
_vertex_source = result['words_all_filters']
if edge_source in options:
_edge_source = result['words_'+edge_source]
else:
_edge_source = result['words_no_stop_words']
# 指定window(之前理解的滑动窗口大小,用来创建图),和处理好的_vertex_source,_edge_source
self.keywords = util.sort_words(_vertex_source, _edge_source, window = window, pagerank_config = pagerank_config)
参数分析:
- text:文本内容,字符串
- window:窗口大小,用来构造单词之间的边。默认值为2
- lower:是否将文本转换为小写。默认为False
- vertex_source:选择使用words_no_filter, words_no_stop_words, words_all_filters中的哪一个来构造pagerank对应的图中的节点。默认值为'all_filters',可选值为'no_filter', 'no_stop_words', 'all_filters'。关键词也来自'vertex_source'
- edge_source:选择使用words_no_filter, words_no_stop_words, words_all_filters中的哪一个来构造pagerank对应的图中的节点之间的边。默认值为'no_stop_words',可选值为'no_filter', 'no_stop_words', 'all_filters'。边的构造要结合'window'参数
stopwords.txt内容如下:
3.get_keywords函数
def get_keywords(self, num = 6, word_min_len = 1):
result = [] # 初始化result为空列表
count = 0
for item in self.keywords:
# 已获得count个关键词,不再遍历
if count >= num:
break
if len(item.word) >= word_min_len:
result.append(item) # result中加入长度大于word_min_len长度的关键词
count += 1
return result
该函数获取最重要的num个长度大于等于word_min_len的关键词,并返回result为关键词列表。这里TextRank4Keyword类对象的keywords是由TextRank方法构建词图得到的关键词,并且已按rank值排序,故可以得到前num个重要的关键词。
4.get_keyphrases函数
def get_keyphrases(self, keywords_num = 12, min_occur_num = 2):
keywords_set = set([ item.word for item in self.get_keywords(num=keywords_num, word_min_len = 1)])
keyphrases = set() # 初始化keyphrases集合
# 处理self.words_no_filter列表中的sentence
for sentence in self.words_no_filter:
one = []
# 处理sentence中的word
for word in sentence:
# 判断word是否在keywords_set中
if word in keywords_set:
one.append(word)
else:
if len(one) > 1:
keyphrases.add(''.join(one))
if len(one) == 0:
continue
else:
one = []
if len(one) > 1:
keyphrases.add(''.join(one))
return [phrase for phrase in keyphrases
if self.text.count(phrase) >= min_occur_num]
# 判断出现次数是否大于min_occur_num
该函数可以获得关键短语,获取keywords_num个关键词构造的可能出现的关键短语,要求这个短语在原文中出现次数>min_occur_num,并返回关键短语的列表。
首先是初始化keywords_set为所有长度大于1的所有keywords的set,然后取类对象的words_no_filter列表,如果取出的word在keyword_set中,则one的list加入word。
5.TextRank4Keyword_test
from __future__ import print_function
import sys
try:
reload(sys)
sys.setdefaultencoding('utf-8')
except:
pass
import codecs
from textrank4zh import TextRank4Keyword
# 读入测试文件
text = codecs.open('./doc/02.txt', 'r', 'utf-8').read()
# 生成TextRank4Keyword类对象
tr4w = TextRank4Keyword()
# 调用analyze方法,指定窗口大小为3
tr4w.analyze(text=text,lower=True, window=3, pagerank_config={'alpha':0.85})
# 指定取出30个keywords,单词最小长度为2
for item in tr4w.get_keywords(30, word_min_len=2):
print(item.word, item.weight, type(item.word))
print('--phrase--')
# 取关键短语,评分前20
for phrase in tr4w.get_keyphrases(keywords_num=20, min_occur_num = 0):
print(phrase, type(phrase))
这里主要测试了我们之前分析的函数,指定读入的测试文件,生成TextRank4Keyword类对象,之后调用analyze方法,这里是指定window=3,单词需要小写,然后分别调用get_keywords和get_keyphrases方法得到关键词和关键短语。
其运行结果如下:
至此,我们完成了TextRank4Keyword类的分析,关于TextRank的实际词图的构建和Segmentation类中方法的实现我们将在下一篇博客中完成分析。