一.NLG文本生成任务

       文本生成NLG,不同于文本理解NLU(例如分词、词向量、分类、实体提取),是重在文本生成的另一种关键技术(常用的有翻译、摘要、同义句生成等)。

       传统的文本生成NLG任务主要是抽取式的,生成式的方法看起来到现在使用也没有那么普遍。现在,我记录的是textrank,一种使用比较广泛的抽取式关键句提取算法。

二、TextRank前世今生

        要说TextRank(TextRank: Bringing Order into Texts),还得从它的爸爸PageRank说起。

        PageRank(The PageRank Citation Ranking: Bringing Order to the Web),是上世纪90年代末提出的一种计算网页权重的算法。当时,互联网技术突飞猛进,各种网页网站爆炸式增长,业界急需一种相对比较准确的网页重要性计算方法,是人们能够从海量互联网世界中找出自己需要的信息。

       百度百科如是介绍他的思想:PageRank通过网络浩瀚的超链接关系来确定一个页面的等级。Google把从A页面到B页面的链接解释为A页面给B页面投票,Google根据投票来源(甚至来源的来源,即链接到A页面的页面)和投票目标的等级来决定新的等级。简单的说,一个高等级的页面可以使其他低等级页面的等级提升。

       具体说来就是,PageRank有两个基本思想,也可以说是假设,即数量假设:一个网页被越多的其他页面链接,就越重);质量假设:一个网页越是被高质量的网页链接,就越重要。 

        总的来说就是一句话,从全局角度考虑,获取重要的信息。

三、TextRank原理

        首先不得不说的是原始论文的PageRank的公式,d是阻力系数,取0.85;In(Vi)是链入Vi页面的网页集合, Out(Vj)是链出Vi页面的网页集合:

在文本生成领域的评价指标 文本生成算法_在文本生成领域的评价指标

        TextRank的公式是这样的,Wji表示两节点边的重要程度,入度和初度的意思同PageRank:

在文本生成领域的评价指标 文本生成算法_在文本生成领域的评价指标_02

        当然,虽然TextRank和PageRank的思想相近,但是它们还是有不同点的。一是边权重的构建不同,PageRank利用的是网页的连接关系,而TextRank利用的是词的共现信息(或者是两两句子的文本相似度);二是边关系的不同,PageRank是有向无权边,而TextRank的是无向有权边。

四、jieba,TextRank4ZH,gensim的TextRank实现比较(推荐TextRank4ZH

        1.jieba

               defaultdict字典存储出入度,10 iters循环,只有关键词的textrank

        2.TextRank4ZH

               数据预处理为(不变-去停用词-或保留重要tag)句子的词共现构建numpy矩阵, networkx的pagerank作为textrank优化,使用了networkx和jieba外部包。支持关键词和关键句。

        3.gensim

               数据预处理比较多且针对英文(比如说关键词取3,这样中文较多的二字的词就取不到,不如TextRank4ZH的保留重要tag;再比如切句全是英文的分隔符.?!,不如TextRank4ZH的。?!......);相似度计算为BM25比词共现矩阵先进;gensim自己实现构建Graph、PageRank等不需要额外包;输入最小句子长度设置为10。

 

五、码源分析

       主要参考jieba,TextRank4ZH,gensim,

       Jieba的TextRank使用defaultdict字典存储句子的相邻词语之间的共现关系,代码如下:

g = UndirectWeightedGraph()
        cm = defaultdict(int) # 这个字典, 不存在时候会返回空,而不像dict那样报错
        words = tuple(self.tokenizer.cut(sentence))
        for i, wp in enumerate(words): # textrank和pagerank一样,会有两个for循环
            if self.pairfilter(wp):
                for j in xrange(i + 1, i + self.span):
                    if j >= len(words):
                        break
                    if not self.pairfilter(words[j]):
                        continue
                    if allowPOS and withFlag:
                        cm[(wp, words[j])] += 1
                    else:
                        cm[(wp.word, words[j].word)] += 1 # 例如: dict[('我','喜欢')]=1

        for terms, w in cm.items():
            g.addEdge(terms[0], terms[1], w) # 保存(词1, 词1的下一个词[词2], 频次[词1词2的])
        nodes_rank = g.rank()

      TextRank4ZH使用的是numpy保存共现句子间相邻词语的共现信息,然后再用了networkx.pagerank计算句子得分,据说这是优化,加快了计算速度等。代码如下:

sorted_sentences = []
    _source = words
    sentences_num = len(_source)        
    graph = np.zeros((sentences_num, sentences_num))

    # 构建两两相似度矩阵
    for x in xrange(sentences_num):
        for y in xrange(x, sentences_num):
            similarity = sim_func( _source[x], _source[y] )
            graph[x, y] = similarity
            graph[y, x] = similarity
            
    nx_graph = nx.from_numpy_matrix(graph)   # 调用pagerank实现
    scores = nx.pagerank(nx_graph, **pagerank_config)              # this is a dict
    sorted_scores = sorted(scores.items(), key = lambda item: item[1], reverse=True)


# 切句
sentence_delimiters = ['?', '!', ';', '?', '!', '。', ';', '……', '…', '\n']
# 提取重要tag的中文词语
allow_speech_tags = ['an', 'i', 'j', 'l', 'n', 'nr', 'nrfg', 'ns', 'nt', 'nz', 't', 'v', 'vd', 'vn', 'eng']

 

    gensim的textrank是gensim.summarization.summarizer.summarize。

##########先是分句和数据预处理(依次是: 小写, html, 标点符号, 多个空格, 数字时间等, 英文停用词,大于3的句子,提取英文单词词干[如复数])
###############################
DEFAULT_FILTERS = [
    lambda x: x.lower(), strip_tags, strip_punctuation,
    strip_multiple_whitespaces, strip_numeric,
    remove_stopwords, strip_short, stem_text
]

# 1.切分句子, 非贪婪搜索, 以英文句号-感叹号-问号.!?加空格, 或者是换行符\n为标志,不适合中文
RE_SENTENCE = re.compile(r'(\S.+?[.!?])(?=\s+|$)|(\S.+?)(?=[\n]|$)', re.UNICODE)
# \w 的释义都是指包含大 小写字母数字和下划线
# \s 空格
# ^ 匹配字符串的开始
# $ 匹配字符串的结束


# 2.去除标点符号(punctuation),可以发现,有一些特殊字符和中文的
punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""


# 3.去除html标签
RE_TAGS = re.compile(r"<([^>]+)>", re.UNICODE)

# 4.空格,时间数字等, 时间等,例如"24.0hours"改为"24.0 hours"

# 5.大于len=3的单词, 感觉这点中文不适用,strip_short(s, minsize=2)比较合适



# 构建子典等
corpus = _build_corpus(sentences)
# 关键
most_important_docs = summarize_corpus(corpus, ratio=ratio if word_count is None else 1)
# page rank
def pagerank_weighted(graph, damping=0.85):
    """Get dictionary of `graph` nodes and its ranks.

    Parameters
    ----------
    graph : :class:`~gensim.summarization.graph.Graph`
        Given graph.
    damping : float
        Damping parameter, optional

    Returns
    -------
    dict
        Nodes of `graph` as keys, its ranks as values.

    """
    coeff_adjacency_matrix = build_adjacency_matrix(graph, coeff=damping)
    probabilities = (1 - damping) / float(len(graph))

    pagerank_matrix = coeff_adjacency_matrix.toarray()
    # trying to minimize memory allocations
    pagerank_matrix += probabilities

    vec = principal_eigenvector(pagerank_matrix.T)

    # Because pagerank_matrix is positive, vec is always real (i.e. not complex)
    return process_results(graph, vec.real)

 

希望对你有所帮助!