文章内容

2018/9/27 16:12:22,作 者: 黄兵

无监督文本自动摘要野生技术

前言

本文简单介绍文本自动摘要的概念,提供一些简单可行的思路和解决方法,主要功能是记录一些心得和希望读者能得到一些启发。现阶段有监督的文本自动摘要,存在非常成熟和强大的解决方法,奈何巧妇难为无米之炊,没有人工的摘要标记,有监督的方法寸步难行。在缺少标记数据的情况下,无监督的方法更加实用。

基本概念

什么是文本自动摘要?

Automatic text summarization is the task of producing a concise
and fluent summary while preserving key information content and
overall meaning.

简而言之,文本自动摘要就是从源文本中生成简明流畅的总结,同时保留关键内容和主旨的一项任务。一般来说,文本自动摘要的来源可以是一个或多个文本,摘要后的字数是原文的一半左右。

文本自动摘要的两种方法

1、Extraction

顾名思义,就是识别出文本中的重要部分并且抽取出来作为文本的摘要。

2、Abstraction

这种方法则是,用比较先进的自然语言处理技术去生成包含原文重要信息的摘要,这种方法涉及语意表征、推断和自然语言生成等,这些都是比较难解决的问题。

一般来说extraction的效果要比abstraction要好,更况且现成的abstractive summarizers或多或少都要包括一些extraction的成分,我们重点来看exraction方法。

Extraction summarization方法论

Exraction自动摘要系统可以大致分为一下独立的三步:

1、构建一个包含文本主要信息的表现形式(表征)。
2、基于构建的表现形式对句子评分。
3、根据评分选出构成摘要的句子。

这样我们就把自动摘要这个复杂问题,转化成三个独立的子问题,当第一个和第二个问题解决后,第三部便是选择top k问题,也就是说,解决了前两个问题,第三个问题便迎刃而解。

表征方法介绍

概览

常用的文本表征方式基本可分为两种:topic representation和 indicator representation。

topic representation就是把文本转化为诠释文本涉及的话题的表征形式,其中topic representation中有比较基本的词频驱动方法、LSA和LDA等等,表征完成后用统计的方法确定阈值来筛选重要的句子。

indicator representation就是把文本转化成某些特征,例如句子长度、是否含有某些词汇和句子的位置等等。然后直接评分排序抽取出重要的句子。

下面重点介绍两种简单的indicator representation和评分方法.

基于图的方法:从pagerank到textrank

基本思路:

图的方法受大名鼎鼎的pagerank算法影响,pagerank算法的主要思想是一个网页的重要程度取决于指向该网页的所有其它网页的重要程度,以及这些网页中包含的链接数。同样地,在文本中,一个句子的重要程度也取决于周围句子的重要程度。通常,在构建图的时候,我们以顶点表示句子,以边表示句子之间的相似关系。最常用的方法是度量两个句子间TFIDF的cosine相似度,超过某阈值便把两个句子连接起来。在这里采用一种更加trivial,naive的方法,就是用窗口滑动的方式建立句子间的局部关系。

首先我们的输入是一段长文本,如:

虽然至今夏普智能手机在市场上无法排得上号,已经完全没落,并于 2013 年退出中国市场,但是今年 3 月份官方突然宣布回归中国,预示着很快就有夏普新机在中国登场了。那么,第一款夏普手机什么时候登陆中国呢?又会是怎么样的手机呢?\r\n近日,一款型号为 FS8016 的夏普神秘新机悄然出现在 GeekBench 的跑分库上。从其中相关信息了解到,这款机子并非旗舰定位,所搭载的是高通骁龙 660 处理器,配备有 4GB 的内存。骁龙 660 是高通今年最受瞩目的芯片之一,采用 14 纳米工艺,八个 Kryo 260 核心设计,集成 Adreno 512 GPU 和 X12 LTE 调制解调器。\r\n当前市面上只有一款机子采用了骁龙 660 处理器,那就是已经上市销售的 OPPO R11。骁龙 660 尽管并非旗舰芯片,但在多核新能上比去年骁龙 820 强,单核改进也很明显,所以放在今年仍可以让很多手机变成高端机。不过,由于 OPPO 与高通签署了排他性协议,可以独占两三个月时间。\r\n考虑到夏普既然开始测试新机了,说明只要等独占时期一过,夏普就能发布骁龙 660 新品了。按照之前被曝光的渲染图了解,夏普的新机核心竞争优势还是全面屏,因为从 2013 年推出全球首款全面屏手机 EDGEST 302SH 至今,夏普手机推出了多达 28 款的全面屏手机。\r\n在 5 月份的媒体沟通会上,惠普罗忠生表示:“我敢打赌,12 个月之后,在座的各位手机都会换掉。因为全面屏时代的到来,我们怀揣的手机都将成为传统手机。”

我们需要在标点符号处把句子分开如

import re

def split_sentences(text,p='[。.,,?:]',filter_p='\s+'):
    f_p = re.compile(filter_p)
    text = re.sub(f_p,'',text)
    pattern = re.compile(p)
    split = re.split(pattern,text)
    return split

print(split_sentences(text)[:10])
#out:
'''
['虽然至今夏普智能手机在市场上无法排得上号', '已经完全没落', '并于2013年退出中国市场', '但是今年3月份官方突然宣布回归中国', '预示着很快就有夏普新机在中国登场了']
'''

这个函数会将文本分成一个个句子的list。

然后我们可以用networkx库构建图:

import networkx

def get_sen_graph(text,window=3):
    split_sen = split_sentences(text)
    sentences_graph = networkx.graph.Graph()
    for i,sen in enumerate(split_sen):
        sentences_graph.add_edges_from([(sen,split_sen[ii]) for ii in range(i-window,i+window+1)
                                        if ii >= 0 and ii < len(split_sen)])
    return sentences_graph

接着用pagerank算法计算出句子的评分并排序

def text_rank(text):
    sentences_graph = get_sen_graph(text)
    ranking_sentences = networkx.pagerank(sentences_graph)
    ranking_sentences_sorted = sorted(ranking_sentences.items(),key=lambda x:x[1],reverse=True)
    return ranking_sentences_sorted

最后是选择重要的句子并拼接起来

def get_summarization(text,score_fn,sum_len):
    sub_sentences = split_sentences(text)
    ranking_sentences = score_fn(text)
    selected_sen = set()
    current_sen = ''

    for sen, _ in ranking_sentences:
        if len(current_sen)<sum_len:
            current_sen += sen
            selected_sen.add(sen)
        else:
            break

    summarized = []
    for sen in sub_sentences:
        if sen in selected_sen:
            summarized.append(sen)
    return summarized

def get_summarization_by_text_rank(text,sum_len=200):
    return get_summarization(text,text_rank,sum_len)

print(' '.join(get_summarization_by_text_rank(text)))

#out:
'''
但是今年3月份官方突然宣布回归中国 预示着很快就有夏普新机在中国登场了 那么 第一款夏普手机什么时候登陆中国呢
又会是怎么样的手机呢 近日 一款型号为FS8016的夏普神秘新机悄然出现在GeekBench的跑分库上 从其中相关信息了解到
因为从2013年推出全球首款全面屏手机EDGEST302SH至今 夏普手机推出了多达28款的全面屏手机 在5月份的媒体沟通会上 
惠普罗忠生表示 “我敢打赌 12个月之后 在座的各位手机都会换掉
'''

这样就初步实现了一个基于图的简单文本摘要,但是按照这个实现,由于方法简单,效果也马马虎虎。但是构建图的时候,你是否有一些比较好的构建方式呢?这里,如何界定一个句子"周围"的句子显得至关重要。除了TFIDF之外,LSA是否是一个不错的表征方法呢,word2vec和Doc2vec也许值得一试呢!

基于sentence embedding(句嵌入)的方法

基本思路:

实际上,识别一个文本的重要句子,可以看作是测度文中每个句子和全文的相似度,相似度越高的话,表示这个句子越重要。所以我们只要对全文及其分句进行sentence embedding后,计算分句表征向量和全文表征向量的cosine相似度,就可以大致抽取出重要句子。

这部分需要做的准备工作比较多,首先读入数据进行分词,再训练词向量(word2vec),当然也可以使用预训练的词向量

import pandas as pd
import numpy as np
import jieba
from gensim.models import FastText

FILE_PATH = 'news.zip'
news_df = pd.read_csv(FILE_PATH,compression='zip',encoding='gb18030')
#定义分词函数
def cut(text): return ' '.join(jieba.cut(text)) 

main_content = pd.DataFrame()
main_content['title'] = news_df['title']
main_content['content'] = news_df['content'].fillna('')
main_content['tokenized_content'] = main_content['content'].apply(cut)

#训练词向量
with open('all_corpus.txt','w',encoding='utf-8'as f:
    f.write(' '.join(main_content['tokenized_content'].tolist()))

from gensim.models.word2vec import LineSentence
model = FastText(LineSentence('all_corpus.txt'),window=8,size=200,iter=10,min_count=1)
tokens = [token for line in main_content['tokenized_content'].tolist() for token in line.split()]

计算词频

from collections import Counter
token_counter = Counter(tokens)
word_frequency = {w:counts/len(tokens) for w,counts in token_counter.items()}

对于句嵌入,使用比较简单的SIF(smooth-inverse-frequency)embedding,具体算法


简单起见,跳过了其中的pca/svd部分

def SIF_sentence_embedding(text,alpha=1e-4):
    global word_frequency

    max_fre = max(word_frequency.values())
    sen_vec = np.zeros_like(model.wv['测试'])
    words = cut(text).split()
    words = [w for w in words if w in model]

    for w in words:
        fre = word_frequency.get(w,max_fre)
        weight = alpha/(fre+alpha)
        sen_vec += weight*model.wv[w]

    sen_vec /= len(words)
    #skip SVD
    return sen_vec

计算cosine得分并把抽取的句子组合起来

from scipy.spatial.distance import cosine

def get_corr(text,embed_fn=SIF_sentence_embedding):
    if isinstance(text,list): text = ' '.join(text)

    sub_sentences = split_sentences(text)
    sen_vec = embed_fn(text)

    corr_score = {}

    for sen in sub_sentences:
        sub_sen_vec = embed_fn(sen)
        corr_score[sen] = cosine(sen_vec,sub_sen_vec)

    return sorted(corr_score.items(),key=lambda x:x[1],reverse=True)

def get_summarization_by_sen_emb(text,max_len=200):
    return get_summarization(text,get_corr,max_len)

print(''.join(get_summarization_by_sen_emb(main_content['content'].iloc[6])))

#out:
'''
已经完全没落并于2013年退出中国市场 但是今年3月份官方突然宣布回归中国 预示着很快就有夏普新机在中国登场了 
那么 又会是怎么样的手机呢 近日从其中相关信息了解到 八个Kryo260核心设计 但在多核新能上比去年骁龙820强单核改进也很明显
不过由于OPPO与高通签署了排他性协议 可以独占两三个月时间 说明只要等独占时期一过 按照之前被曝光的渲染图了解 
在5月份的媒体沟通会上 惠普罗忠生表示 “我敢打赌12个月之后在座的各位手机都会换掉 因为全面屏时代的到来
'''

这样基于sentence embedding的文本摘要就完成,这里只是讨论了一个简单的方法,可以结合介绍的这两个方法,构建一个更加强大,效果更好的文本自动摘要系统。

改良思路

1、结合KNN算法的思路,使生成的文本摘要更流畅,更具可读性。根据句子的评分,画出句子评分的分布:


我们会发现,句子的评分分布是这样起伏很大的尖锐曲线,这样抽取的句子会断断续续,显得很突兀,因此我们需要根据句子自身的重要性和周围句子的重要性,结合KNN算法使得结果更加平滑。

2、考虑关键词。在文本中,存在关键词的句子往往包含了比较重要的信息,当一个句子包含比关键词时,应适当增加此句子的评分。

3、考虑标题。如果你文本存在标题,那么标题已经包含了其中的重要信息,摘要时应当把标题考虑进去。

4、考虑句子的位置。在阅读理解时,开头、结尾、段首和段尾等几乎都起着总领全文、总结或承上启下的作用,在摘要时,必须也要把句子的位置考虑进去。

5、结合LDA等话题模型。根据LDA总结出来的话题,我们可以用分句与其对比,把话题也考虑到摘要中。

讨论到这里,相信聪明的你已经有相当多的思路,可以建立自己的文本自动摘要系统啦!

下面展示经过一些细微的改良后的摘要:


等一下,还有一个问题,分句再合并后,原来的标点符号丢失了,怎么办?(提示:这里应该建立分句——标点之间的对应关系。)花点时间,思考一下,聪明的你就可以实现一个完整的文本摘要系统啦!

参考资料

1、Text Summarization Techniques: A Brief Survey
2、A SIMPLE BUT TOUGH-TO-BEAT BASELINE FOR SENTENCE EMBEDDINGS

分享到:

发表评论

评论列表

user-ico

moongy on 回复 有用(0

不知道大佬有没有上传Github,想学习一下如何综合考虑这些因素

游客hzYN on 2020-03-17 11:43:40

请问您有没有找到源码呢? 求分享 一起学习 非常感谢