文本摘要方法
- 早在20世纪50年代,自动文本摘要已经吸引了人们的关注。在20世纪50年代后期,Hans Peter Luhn发表了一篇名为《The automatic creation of literature abstract》的研究论文,它利用词频和词组频率等特征从文本中提取重要句子,用于总结内容。
- 由Harold P Edmundson在20世纪60年代后期完成,他使用线索词的出现(文本中出现的文章题目中的词语)和句子的位置等方法来提取重要句子用于文本摘要。此后,许多重要和令人兴奋的研究已经发表,以解决自动文本摘要的挑战。
文本摘要可以大致分为两类——抽取型摘要和抽象型摘要:
- 抽取型摘要:这种方法依赖于从文本中提取几个部分,例如短语、句子,把它们堆叠起来创建摘要。因此,这种抽取型的方法最重要的是识别出适合总结文本的句子。
- 抽象型摘要:这种方法应用先进的NLP技术生成一篇全新的总结。可能总结中的文本甚至没有在原文中出现。
相关知识–PageRank算法
PageRank算法启发了TextRank,PageRank主要用于对在线搜索结果中的网页进行排序,通过计算网页链接的数量和质量来粗略估计网页的重要性。
PageRank对于每个网页页面都给出一个正实数,表示网页的重要程度,PageRank值越高,表示网页越重要,在互联网搜索的排序中越可能被排在前面。
抽取型文本摘要算法TextRank
- TextRank算法是一种基于图的用于关键词抽取和文档摘要的排序算法,由谷歌的网页重要性排序算法PageRank算法改进而来,它利用一篇文档内部的词语间的共现信息(语义)便可以抽取关键词,它能够从一个给定的文本中抽取出该文本的关键词、关键词组,并使用抽取式的自动文摘方法抽取出该文本的关键句。通过把文本分割成若干组成单元(句子),构建节点连接图,用句子之间的相似度作为边的权重,通过循环迭代计算句子的TextRank值,最后抽取排名高的句子组合成文本摘要。
- 用TextRank提取来提取关键词,用PageRank的思想来解释它:
如果一个单词出现在很多单词后面的话,那么说明这个单词比较重要
一个TextRank值很高的单词后面跟着的一个单词,那么这个单词的TextRank值会相应地因此而提高
- 第一步是把所有文章整合成文本数据
- 接下来把文本分割成单个句子
- 然后,我们将为每个句子找到向量表示(词向量)。
- 计算句子向量间的相似性并存放在矩阵中
- 然后将相似矩阵转换为以句子为节点、相似性得分为边的图结构,用于句子TextRank计算。
- 最后,一定数量的排名最高的句子构成最后的摘要。
源码
主调函数是TextRank.textrank函数,主要是在jieba/analyse/textrank.py中实现。
#!/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.85
def __init__(self):
#初始化函数
self.graph = defaultdict(list)#这是进行分词后的一个词典
def addEdge(self, start, end, weight):
#添加边的函数
# use a tuple (start, end, weight) instead of a Edge object
#无向有权图添加边的操作是在addEdge函数中完成的,因为是无向图,所以我们需要依次将start作为起 始点,end作为终止点,然后再将start作为终止点,end作为起始点,这两条边的权重是相同的。
self.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] = wsdef
outSum[n] = sum((e[2] for e in out), 0.0)#e[2]是什么?
# this line for build stable iteration
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
(min_rank, max_rank) = (sys.float_info[0], sys.float_info[3])
# 获取权值的最大值和最小值
for w in itervalues(ws):
if w < min_rank:
min_rank = w
if 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
class 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.dt
self.stop_words = self.STOP_WORDS.copy()
self.pos_filt = frozenset(('ns', 'n', 'vn', 'v'))
self.span = 5
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)
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
"""
# frozenset()函数的含义是:返回一个冻结的集合,冻结后集合不能再添加或删除任何元素
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])] += 1
else:
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 tags
extract_tags = textrank```
函数:jieba.analyse.textrank(string, topK=20, withWeight=True, allowPOS=())
string:待处理语句
topK:关键字的个数,重要性从高到低排序,默认20
withWeight:是否返回权重值,默认false
allowPOS:词性过滤,是否仅返回指定类型,默认为空,为空表示不过滤,若提供则仅返回符合词性要求的关键词,allowPOS(‘ns’, ‘n’, ‘vn’, ‘v’) 地名、名词、动名词、动词
# 导入文件并分词
def lcut(Intro_movie):
segment=[]
segs = jieba.lcut(Intro_movie) # jiaba.lcut()
for seg in segs:
if len(seg)>1 and seg!='\r\n':
segment.append(seg)
return segment
#去停用词
def dropstopword(segment):
words_df = pd.DataFrame({'segment':segment})
stopwords = pd.read_csv("../movie_sentiment/stopwords.txt"
,index_col=False
,quoting=3
,sep="\t"
,names=['stopword']
,encoding='utf-8') # quoting=3 全不引用
return words_df[~words_df.segment.isin(stopwords.stopword)].segment.values.tolist()
# 基于TextRank算法的关键词抽取(仅动词和动名词)
import jieba.analyse as analyse
data_item['keywords'] = data_item.intro.apply(lcut)\
.apply(dropstopword)\
.apply(lambda x : " ".join(x))\
.apply(lambda x:" ".join(analyse.textrank(x, topK=8, withWeight=False, allowPOS=('n','ns','vn', 'v'))))
data_item.sort_values('rating_num', ascending=False)[['movie_id','movie_title','keywords']].head(20)
应用
- 和 LDA、HMM 等模型不同, TextRank不需要事先对多篇文档进行学习训练, 因其简洁有效而得到广泛应用。
- 在多篇单领域文本数据中抽取句子组成摘要
- TextRank算法有jieba分词和TextRank4zh这2个开源库的写法,但是两者无论写法和运算规则都有很大出入,结合公式来说jieba做的更符合公式,TextRank4zh更具有准确性,因为TextRank4zh在公式上面做了一定的优化。
基于Jieba的TextRank算法实现:
- 对每个句子进行分词和词性标注处理;
- 过滤掉除指定词性外的其他单词,过滤掉出现在停用词表的单词,过滤掉长度小于2的单词;
- 将剩下的单词中循环选择一个单词,将其与其后面4个单词分别组合成4条边;
- 构建出候选关键词图G=(V,E),把2个单词组成的边,和其权值记录了下来;
- 套用TextRank的公式,迭代传播各节点的权值,直至收敛;
- 对结果中的Rank值进行倒序排序,筛选出前面的几个单词,就是需要的关键词。
参考文献
独家 | 基于TextRank算法的文本摘要(附Python代码)