系统学习NLP(二十二)--关键词提取算法总结

先说一下自动文摘的方法。自动文摘(Automatic Summarization)的方法主要有两种:Extraction和Abstraction。其中Extraction是抽取式自动文摘方法,通过提取文档中已存在的关键词,句子形成摘要;Abstraction是生成式自动文摘方法,通过建立抽象的语意表示,使用自然语言生成技术,形成摘要。由于生成式自动摘要方法需要复杂的自然语言理解和生成技术支持,应用领域受限。一般采用抽取式的自动文摘方法。

目前主要方法有:

  • 基于统计:统计词频,位置等信息,计算句子权值,再选取权值高的句子作为文摘,特点:简单易用,但对词句的使用大多仅停留在表面信息。
  • 基于图模型:构建拓扑结构图,对词句进行排序。例如,TextRank/LexRank
  • 基于潜在语义:使用主题模型,挖掘词句隐藏信息。例如,采用LDA,HMM
  • 基于整数规划:将文摘问题转为整数线性规划,求全局最优解。

TextRank:

算法TextRank受到网页之间关系PageRank算法启发,利用局部词汇之间关系(共现窗口)构建网络,计算词的重要性,选取权重大的做为关键词。常用流程: 原始语料 → 分词 → 停用词过滤 → 关键词抽取

先说下PageRank

PageRank

PageRank最开始用来计算网页的重要性。整个www可以看作一张有向图图,节点是网页。如果网页A存在到网页B的链接,那么有一条从网页A指向网页B的有向边。构造完图后,使用下面的公式:

S(Vi)是网页i的重要性(PR值)。d是阻尼系数,一般设置为0.85。In(Vi)是存在指向网页i的链接的网页集合。Out(Vj)是网页j中的链接存在的链接指向的网页的集合。|Out(Vj)|是集合中元素的个数。
PageRank需要使用上面的公式多次迭代才能得到结果。初始时,可以设置每个网页的重要性为1。上面公式等号左边计算的结果是迭代后网页i的PR值,等号右边用到的PR值全是迭代前的。
举个例子:

上图表示了三张网页之间的链接关系,直觉上网页A最重要。可以得到下面的表:

   结束\起始 A B C
A 0 1 1
B 0 0 0
C 0 0 0


横栏代表其实的节点,纵栏代表结束的节点。若两个节点间有链接关系,对应的值为1。 
根据公式,需要将每一竖栏归一化(每个元素/元素之和),归一化的结果是: 

   结束\起始 A B C
A 0 1 1
B 0 0 0
C 0 0 0


上面的结果构成矩阵M。我们用matlab迭代100次看看最后每个网页的重要性:最终A的PR值为0.4050,B和C的PR值为0.1500。 如果把上面的有向边看作无向的(其实就是双向的),结果也是一样。 

TextRank

TextRank算法是一种用于文本的基于图的排序算法。其基本思想来源于谷歌的PageRank算法, 通过把文本分割成若干组成单元(单词、句子)并建立图模型, 利用投票机制对文本中的重要成分进行排序, 仅利用单篇文档本身的信息即可实现关键词提取、文摘。和 LDA、HMM 等模型不同, TextRank不需要事先对多篇文档进行学习训练, 因其简洁有效而得到广泛应用。

  • 如果一个单词出现在很多单词后面的话,那么说明这个单词比较重要
  • 一个TextRank值很高的单词后面跟着的一个单词,那么这个单词的TextRank值会相应地因此而提高

  TextRank 一般模型可以表示为一个有向有权图 G =(V, E), 由点集合 V和边集合 E 组成, E 是V ×V的子集。图中任两点 Vi , Vj 之间边的权重为 wji , 对于一个给定的点 Vi, In(Vi) 为 指 向 该 点 的 点 集 合 , Out(Vi) 为点 Vi 指向的点集合。点 Vi 的得分定义如下:

其中, d 为阻尼系数, 取值范围为 0 到 1, 一般取值为 0.85,作为每次迭代时的数据平滑,促进迭代稳定收敛。使用TextRank 算法计算图中各点的得分时, 需要给图中的点指定任意的初值, 并递归计算直到收敛, 即图中任意一点的误差率小于给定的极限值时就可以达到收敛, 一般该极限值取 0.0001。

1. 基于TextRank的关键词提取

  关键词抽取的任务就是从一段给定的文本中自动抽取出若干有意义的词语或词组。TextRank算法是利用局部词汇之间关系(共现窗口)对后续关键词进行排序,直接从文本本身抽取。其主要步骤如下:

  (1)把给定的文本T按照完整句子进行分割,即

  (2)对于每个句子,进行分词和词性标注处理,并过滤掉停用词,只保留指定词性的单词,如名词、动词、形容词,即,其中是保留后的候选关键词。

  (3)构建候选关键词图G = (V,E),其中V为节点集,由(2)生成的候选关键词组成,然后采用共现关系(co-occurrence)构造任两点之间的边,两个节点之间存在边仅当它们对应的词汇在长度为K的窗口中共现,K表示窗口大小,即最多共现K个单词。

  (4)根据上面公式,迭代传播各节点的权重,直至收敛。

  (5)对节点权重进行倒序排序,从而得到最重要的T个单词,作为候选关键词。 

  (6)由(5)得到最重要的T个单词,在原始文本中进行标记,若形成相邻词组,则组合成多词关键词。例如,文本中有句子“Matlab code for plotting ambiguity function”,如果“Matlab”和“code”均属于候选关键词,则组合成“Matlab code”加入关键词序列。

关于窗口的补充:

网页之间的链接关系可以用图表示,那么怎么把一个句子(可以看作词的序列)构建成图呢?TextRank将某一个词与其前面的N个词、以及后面的N个词均具有图相邻关系(类似于N-gram语法模型)。具体实现:设置一个长度为N的滑动窗口,所有在这个窗口之内的词都视作词结点的相邻结点;则TextRank构建的词图为无向图。下图给出了由一个文档构建的词图(去掉了停用词并按词性做了筛选):

网上别人的评估:

接下来将评估TextRank在关键词提取任务上的准确率、召回率与F1-Measure,并与TFIDF做对比;准确率计算公式如下:

测试集是由刘知远老师提供的网易新闻标注数据集,共有13702篇文档。Jieba完整地实现了关键词提取TFIDF与TextRank算法,基于Jieba-0.39的评估实验代码如下:

import jieba.analyse
import json
import codecs


def precision_recall_fscore_support(y_true, y_pred):
    """
    evaluate macro precision, recall and f1-score.
    """
    doc_num = len(y_true)
    p_macro = 0.0
    r_macro = 0.0
    for i in range(doc_num):
        tp = 0
        true_len = len(y_true[i])
        pred_len = len(y_pred[i])
        for w in y_pred[i]:
            if w in y_true[i]:
                tp += 1
        if pred_len == 0:
            p = 1.0 if true_len == 0 else 0.0
        else:
            p = tp / pred_len
        r = 1.0 if true_len == 0 else tp / true_len
        p_macro += p
        r_macro += r
    p_macro /= doc_num
    r_macro /= doc_num
    return p_macro, r_macro, 2 * p_macro * r_macro / (p_macro + r_macro)


file_path = 'data/163_chinese_news_dataset_2011.dat'
with codecs.open(file_path, 'r', 'utf-8') as fr:
    y_true = []
    y_pred = []
    for line in fr.readlines():
        d = json.loads(line)
        content = d['content']
        true_key_words = [w for w in set(d['tags'])]
        y_true.append(true_key_words)
        # for w in true_key_words:
        #     jieba.add_word(w)
        key_word_pos = ['x', 'ns', 'n', 'vn', 'v', 'l', 'j', 'nr', 'nrt', 'nt', 'nz', 'nrfg', 'm', 'i', 'an', 'f', 't',
                        'b', 'a', 'd', 'q', 's', 'z']
        extract_key_words = jieba.analyse.extract_tags(content, topK=2, allowPOS=key_word_pos)
        # trank = jieba.analyse.TextRank()
        # trank.span = 5
        # extract_key_words = trank.textrank(content, topK=2, allowPOS=key_word_pos)
        y_pred.append(extract_key_words)
    prf = precision_recall_fscore_support(y_true, y_pred)
    print('precision: {}'.format(prf[0]))
    print('recall: {}'.format(prf[1]))
    print('F1: {}'.format(prf[2]))

其中,每个文档提取的关键词数为2,并按词性做过滤;span表示TextRank算法中的滑动窗口的大小。评估结果如下:

方法 Precision Recall F1-Measure
TFIDF 0.2697 0.2256 0.2457
TextRank span=5 0.2608 0.2150 0.2357
TextRank span=7 0.2614 0.2155 0.2363

如果将标注关键词添加到自定义词典,则评估结果如下:

方法 Precision Recall F1-Measure
TFIDF 0.3145 0.2713 0.2913
TextRank span=5 0.2887 0.2442 0.2646
TextRank span=7 0.2903 0.2455 0.2660

直观感受下关键词提取结果(添加了自定义词典):

// TFIDF, TextRank, labelled
['文强', '陈洪刚'] ['文强', '陈洪刚'] {'文强', '重庆'}
['内贾德', '伊朗'] ['伊朗', '内贾德'] {'制裁', '世博', '伊朗'}
['调控', '王珏林'] ['调控', '楼市'] {'楼市', '调控'}
['罗平县', '男子'] ['男子', '罗平县'] {'被砍', '副局长', '情感纠葛'}
['佟某', '黄玉'] ['佟某', '黄现忠'] {'盲井', '伪造矿难'}
['女生', '聚众淫乱'] ['女生', '聚众淫乱'] {'聚众淫乱', '东莞', '不雅视频'}
['马英九', '和平协议'] ['马英九', '推进'] {'国台办', '马英九', '和平协议'}
['东帝汶', '巡逻艇'] ['东帝汶', '中国'] {'东帝汶', '军舰', '澳大利亚'}
['墨西哥', '警方'] ['墨西哥', '袭击'] {'枪手', '墨西哥', '打死'}

从上述两组实验结果,可以发现:

  • TextRank与TFIDF均严重依赖于分词结果——如果某词在分词时被切分成了两个词,那么在做关键词提取时无法将两个词黏合在一起(TextRank有部分黏合效果,但需要这两个词均为关键词)。因此是否添加标注关键词进自定义词典,将会造成准确率、召回率大相径庭。
  • TextRank的效果并不优于TFIDF。
  • TextRank虽然考虑到了词之间的关系,但是仍然倾向于将频繁词作为关键词。

此外,由于TextRank涉及到构建词图及迭代计算,所以提取速度较慢。

猜你喜欢

转载自blog.csdn.net/App_12062011/article/details/89816154