自然语言处理NLP--TextRank算法

文本摘要方法

文本摘要可以大致分为两类——抽取型摘要和抽象型摘要:

  • 抽取型摘要:这种方法依赖于从文本中提取几个部分,例如短语、句子,把它们堆叠起来创建摘要。因此,这种抽取型的方法最重要的是识别出适合总结文本的句子。
  • 抽象型摘要:这种方法应用先进的NLP技术生成一篇全新的总结。可能总结中的文本甚至没有在原文中出现。

相关知识–PageRank算法

PageRank算法启发了TextRank,PageRank主要用于对在线搜索结果中的网页进行排序,通过计算网页链接的数量和质量来粗略估计网页的重要性。
PageRank对于每个网页页面都给出一个正实数,表示网页的重要程度,PageRank值越高,表示网页越重要,在互联网搜索的排序中越可能被排在前面。

抽取型文本摘要算法TextRank

  • TextRank算法是一种基于图的用于关键词抽取和文档摘要的排序算法,由谷歌的网页重要性排序算法PageRank算法改进而来,它利用一篇文档内部的词语间的共现信息(语义)便可以抽取关键词,它能够从一个给定的文本中抽取出该文本的关键词、关键词组,并使用抽取式的自动文摘方法抽取出该文本的关键句。通过把文本分割成若干组成单元(句子),构建节点连接图,用句子之间的相似度作为边的权重,通过循环迭代计算句子的TextRank值,最后抽取排名高的句子组合成文本摘要。

  • 用TextRank提取来提取关键词,用PageRank的思想来解释它:
    如果一个单词出现在很多单词后面的话,那么说明这个单词比较重要
    一个TextRank值很高的单词后面跟着的一个单词,那么这个单词的TextRank值会相应地因此而提高
    在这里插入图片描述

  1. 第一步是把所有文章整合成文本数据
  2. 接下来把文本分割成单个句子
  3. 然后,我们将为每个句子找到向量表示(词向量)。
  4. 计算句子向量间的相似性并存放在矩阵中
  5. 然后将相似矩阵转换为以句子为节点、相似性得分为边的图结构,用于句子TextRank计算。
  6. 最后,一定数量的排名最高的句子构成最后的摘要。

源码

主调函数是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算法实现:

  1. 对每个句子进行分词词性标注处理;
  2. 过滤掉除指定词性外的其他单词,过滤掉出现在停用词表的单词,过滤掉长度小于2的单词;
  3. 将剩下的单词中循环选择一个单词,将其与其后面4个单词分别组合成4条边
  4. 构建出候选关键词图G=(V,E),把2个单词组成的边,和其权值记录了下来;
  5. 套用TextRank的公式,迭代传播各节点的权值,直至收敛;
  6. 对结果中的Rank值进行倒序排序,筛选出前面的几个单词,就是需要的关键词

参考文献
独家 | 基于TextRank算法的文本摘要(附Python代码)
TextRank算法原理简析、代码实现
TextRank源码的学习与详细解析

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值