ACL 2019 Sentence Centrality Revisited for Unsupervised Summarization

github

单文档的文本摘要任务已经取得了不错的进展,不同的研究人员纷纷提出了不同的解决思路和摘要模型,有关进展可浏览之前的几篇博文,这里就不再赘述:

  • Development of Neural Network Models in Text Summarization - 1
  • Development of Neural Network Models in Text Summarization - 2
  • Development of Neural Network Models in Text Summarization - 3
  • Development of Neural Network Models in Text Summarization - 4
  • Template Based Neural Summarization Models
  • Generate summaries in an unsupervised manner

由于大规模、高质量的标注数据集不易获取,因为本文提出了一种新的无监督文本摘要模型。模型的整体思想建立在图结构上,文档中的句子做为图中顶点,句子之间的相似性分数做为边的权值。模型的运行流程如下:

  • 通过BERT获取句子的表示向量
  • 根据句子的表示向量和彼此之间的相似度分数构建有向图,任意两个节点对于中心性(centrality)的贡献取决于它们在文档中的相对位置

最后在CNN/Daily MailNYTTTNews三个数据集进行试验证明了模型的有效性,并进一步的显示了文档中句子的位置信息对于抽取式摘要生成任务的重要性。

假设所处理的文档记为D={s1,...,sn}D = \{ s_{1},...,s_{n}\}D={s1​,...,sn​},eije_{ij}eij​表示句子对(si,sj)(s_{i},s{j})(si​,sj)之间的相似性分数。每一个句子的中心性是通过计算它和其他句子的相似度分数的和来度量的,具体计算公式如下所示:
centrality(si)=∑j∈{1,...,i−1,i+1,...,n}eij\text{centrality}(s_{i}) = \sum_{j \in \{ 1,...,i-1,i+1,...,n\}} e_{ij}centrality(si​)=j∈{1,...,i−1,i+1,...,n}∑​eij​

然后根据得到的结果降序排列,最后选择分数最高的句子作为最后结果的一部分。前面说到数据的表示整体上基于图结构,因此这里使用了PageRank来实现分数的排序。

直觉上来说,一篇文档中的句子并不具有相等的重要性,即最后的摘要中不可能包含所有的句子,因此可以采用类似于RST(Rhetorical Structure Theory)的思想构建图。

RST是一种语篇结构的构建模式,它逐渐的将基本的语篇单元合并为更大的语篇单元,直到最终覆盖全文档。

因此,通过图的构建我们可以知道哪些句子是重要的,哪些是相对来说没那么重要的,接着不断的从重要的句子开始抽取摘要中的部分,直到满足设定的长度需求。

为了实现语篇单元的划分,一种方法是使用语篇解析器(discourse parser),但是它严重依赖于标注语料库的存在,同时需要额外的使用一些相关的工具,较为麻烦。另一种方法相比就很简单且直接,即直接使用句子在文档中的相对位置来逼近真实中心性的度量。因为通常来说位于文档前面的句子通常包含有更多的信息,因此通过句子之间的位置信息就可以简单的实现语篇单元的划分。

另外文中对于一个句子对(si,sj)(s_{i},s_{j})(si​,sj​)来说构建的是有向边,对应的权重分别是eije_{ij}eij​和ejie_{ji}eji​,这样句子的中心性计算就使用了一种线性插值的方式,通过超参数λ1\lambda_{1}λ1​和λ2\lambda_{2}λ2​来进行权衡。
centrality(si)=λ1∑j<ieij+λ2∑j>ieij\text{centrality}(s_{i}) = \lambda_{1} \sum_{j<i} e_{ij} + \lambda_{2} \sum_{j>i} e_{ij}centrality(si​)=λ1​j<i∑​eij​+λ2​j>i∑​eij​

其中λ1\lambda_{1}λ1​和λ2\lambda_{2}λ2​分别负责前向边和后向边。而超参数的值可以根据验证集来自动微调,也可以根据人的先验知识进行手工的调整。文中为了方便后续的实验,规定λ1+λ2=1\lambda_{1} + \lambda_{2} = 1λ1​+λ2​=1,同时这里也只考虑权值为正的情况。

在明确了图的构建方式后,接下来就需要考虑句子表示向量的获取和相似性度量方式的选择。

  • 表示向量:直接使用了BERT来获取句子的表示向量,借助预训练模型的强大表示能力,希望它能更好的捕捉上下文信息。同时借鉴负采样思想,将sis_{i}si​的前一句si−1s_{i-1}si−1​和后一句si+1s_{i+1}si+1​作为正样本,将语料库中的其他随机选择的句子作为负样本,通过优化如下的目标函数来训练得到sis_{i}si​的表示向量

    其中viv_{i}vi​和vi′v_{i}'vi′​是参数不同的BERT对于同句的不同表示

  • 相似性度量:文中试验了点乘(pair-wise dot product)和余弦相似度两种方式,发现简单的点乘效果更好一些。
    Eij=viTvjE_{ij} = v_{i}^T v_{j}Eij​=viT​vj​

    另外对于相似度分数进行了标准化,通过这样的方式可以强调不同相似度分数的相对分布

其中β∈[0,1]\beta \in[0,1]β∈[0,1]。

实验结果

更详细的实验结果可见原文。

下面我们来看一下github上的开源代码,整个代码的结构如下所示:

其中gensim_preprocess.py中主要是使用gensim这个库对文本进行的一系列预处理操作,其中的多个正则式可以重复用在自己的项目中

RE_PUNCT = re.compile(r'([%s])+' % re.escape(string.punctuation), re.UNICODE)
RE_TAGS = re.compile(r"<([^>]+)>", re.UNICODE)
RE_NUMERIC = re.compile(r"[0-9]+", re.UNICODE)
RE_NONALPHA = re.compile(r"\W", re.UNICODE)
RE_AL_NUM = re.compile(r"([a-z]+)([0-9]+)", flags=re.UNICODE)
RE_NUM_AL = re.compile(r"([0-9]+)([a-z]+)", flags=re.UNICODE)
RE_WHITESPACE = re.compile(r"(\s)+", re.UNICODE)

utils.py主要是使用pyrouge来计算ROUGE分数的部分

def evaluate_rouge(summaries, references, remove_temp=False, rouge_args=[]):'''Args:summaries: [[sentence]]. Each summary is a list of strings (sentences)references: [[[sentence]]]. Each reference is a list of candidate summaries.remove_temp: bool. Whether to remove the temporary files created during evaluation.rouge_args: [string]. A list of arguments to pass to the ROUGE CLI.'''temp_dir = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))temp_dir = os.path.join("temp",temp_dir)print(temp_dir)system_dir = os.path.join(temp_dir, 'system')model_dir = os.path.join(temp_dir, 'model')# directory for generated summariesos.makedirs(system_dir)# directory for reference summariesos.makedirs(model_dir)print(temp_dir, system_dir, model_dir)assert len(summaries) == len(references)for i, (summary, candidates) in enumerate(zip(summaries, references)):summary_fn = '%i.txt' % ifor j, candidate in enumerate(candidates):candidate_fn = '%i.%i.txt' % (i, j)with open(os.path.join(model_dir, candidate_fn), 'w') as f:f.write('\n'.join(candidate))with open(os.path.join(system_dir, summary_fn), 'w') as f:f.write('\n'.join(summary))args_str = ' '.join(map(str, rouge_args))rouge = Rouge155(rouge_args=args_str)rouge.system_dir = system_dirrouge.model_dir = model_dirrouge.system_filename_pattern = '(\d+).txt'rouge.model_filename_pattern = '#ID#.\d+.txt'output = rouge.convert_and_evaluate()r = rouge.output_to_dict(output)print(output)return r

tokenizer.py主要完成分词的任务,其中包含了BERT中使用的两种tokenizer:FullTokenizerWprdpieceTokenizer

bert_model.py中包含构建一个BERT所需的一些基本组件,这个部分并不需要自己重写,只需使用已有的代码学会怎么得到一个BERT即可。

data_iterator.py完成批数据的产生任务,从而实现为不同的Extractor提供数据进行训练。

接下来看一下run.py,从中可以看出整个项目主要是使用了PacSumExtractorWithTfIdfPacSumExtractorWithBert这两种抽取模型,而且都可以使用参数的自动微调。

if args.rep == 'tfidf':# 获取 tf-idf 摘要抽取模型extractor = PacSumExtractorWithTfIdf(beta = args.beta,lambda1=args.lambda1,lambda2=args.lambda2)#tuneif args.mode == 'tune':tune_dataset = Dataset(args.tune_data_file)tune_dataset_iterator = tune_dataset.iterate_once_doc_tfidf()extractor.tune_hparams(tune_dataset_iterator)#testtest_dataset = Dataset(args.test_data_file)test_dataset_iterator = test_dataset.iterate_once_doc_tfidf()extractor.extract_summary(test_dataset_iterator)elif args.rep == 'bert':extractor = PacSumExtractorWithBert(bert_model_file = args.bert_model_file,bert_config_file = args.bert_config_file,beta = args.beta,lambda1=args.lambda1,lambda2=args.lambda2)#tuneif args.mode == 'tune':tune_dataset = Dataset(args.tune_data_file, vocab_file = args.bert_vocab_file)tune_dataset_iterator = tune_dataset.iterate_once_doc_bert()extractor.tune_hparams(tune_dataset_iterator)#testtest_dataset = Dataset(args.test_data_file, vocab_file = args.bert_vocab_file)test_dataset_iterator = test_dataset.iterate_once_doc_bert()extractor.extract_summary(test_dataset_iterator)

extractor.py中包含了整个模型的实现逻辑,它主要包含PacSumExtractor这个基类和PacSumExtractorWithBertPacSumExtractorWithTfIdf两种不同的实现。

PacSumExtractorWithBert
class PacSumExtractorWithBert(PacSumExtractor):def __init__(self, bert_model_file, bert_config_file, extract_num = 3, beta = 3, lambda1 = -0.2, lambda2 = -0.2):super(PacSumExtractorWithBert, self).__init__(extract_num, beta, lambda1, lambda2)self.model = self._load_edge_model(bert_model_file, bert_config_file)def _calculate_similarity_matrix(self,  x, t, w, x_c, t_c, w_c, pair_indice):#doc: a list of sequences, each sequence is a list of wordsdef pairdown(scores, pair_indice, length):#1 for self scoreout_matrix = np.ones((length, length))for pair in pair_indice:out_matrix[pair[0][0]][pair[0][1]] = scores[pair[1]]out_matrix[pair[0][1]][pair[0][0]] = scores[pair[1]]return out_matrixscores = self._generate_score(x, t, w, x_c, t_c, w_c)doc_len = int(math.sqrt(len(x)*2)) + 1similarity_matrix = pairdown(scores, pair_indice, doc_len)return similarity_matrixdef _generate_score(self, x, t, w, x_c, t_c, w_c):#score =  log PMI -log kscores = torch.zeros(len(x)).cuda()step = 20for i in range(0,len(x),step):batch_x = x[i:i+step]batch_t = t[i:i+step]batch_w = w[i:i+step]batch_x_c = x_c[i:i+step]batch_t_c = t_c[i:i+step]batch_w_c = w_c[i:i+step]inputs = tuple(t.to('cuda') for t in (batch_x, batch_t, batch_w, batch_x_c, batch_t_c, batch_w_c))batch_scores, batch_pros = self.model(*inputs)scores[i:i+step] = batch_scores.detach()return scoresdef _load_edge_model(self, bert_model_file, bert_config_file):bert_config = BertConfig.from_json_file(bert_config_file)model = BertEdgeScorer(bert_config)model_states = torch.load(bert_model_file)print(model_states.keys())model.bert.load_state_dict(model_states)model.cuda()model.eval()return model
PacSumExtractorWithTfIdf
class PacSumExtractorWithTfIdf(PacSumExtractor):def __init__(self, extract_num = 3, beta = 3, lambda1 = -0.2, lambda2 = -0.2):super(PacSumExtractorWithTfIdf, self).__init__(extract_num, beta, lambda1, lambda2)def _calculate_similarity_matrix(self, doc):idf_score = self._calculate_idf_scores(doc)tf_scores = [Counter(sentence) for sentence in doc]length = len(doc)similarity_matrix = np.zeros([length] * 2)for i in range(length):for j in range(i, length):similarity = self._idf_modified_dot(tf_scores, i, j, idf_score)if similarity:similarity_matrix[i, j] = similaritysimilarity_matrix[j, i] = similarityreturn similarity_matrixdef _idf_modified_dot(self, tf_scores, i, j, idf_score):if i == j:return 1tf_i, tf_j = tf_scores[i], tf_scores[j]words_i, words_j = set(tf_i.keys()), set(tf_j.keys())score = 0for word in words_i & words_j:idf = idf_score[word]score += tf_i[word] * tf_j[word] * idf ** 2return scoredef _calculate_idf_scores(self, doc):doc_number_total = 0.df = {}for i, sen in enumerate(doc):tf = Counter(sen)for word in tf.keys():if word not in df:df[word] = 0df[word] += 1doc_number_total += 1idf_score = {}for word, freq in df.items():idf_score[word] = math.log(doc_number_total - freq + 0.5) - math.log(freq + 0.5)return idf_score

Sentence Centrality Revisited for Unsupervised Summarization相关推荐

  1. bert做文本摘要_Fine-tune BERT for Summarization: BERT和文本摘要

    BERT论文系列导读 导读 文本摘要主要分为抽取式文本摘要和生成式文本摘要,抽取式文本摘要因为发展的时间比较长,因此在工业界应用的范围比较广.比较常用的抽取式文本摘要的算法就是Textrank,但是呢 ...

  2. ACL2019代码开源论文

    来自: GitHub yizhen20133868 WeChat Subscription 程序员遇见GitHub #### Incremental Transformer with Delibera ...

  3. 主题论文总结1:structured text summarization(持续更新ing...)

    诸神缄默不语-个人CSDN博文目录 最近更新时间:2022.6.4 最早更新时间:2022.5.16 文章目录 1. 对structured text summarization这一概念的定义 2. ...

  4. 论文泛读笔记《Neural Document Summarization by Jointly Learning to Score and Select Sentences》

    文章目录 0 摘要 1 介绍 0 摘要 此论文将句子打分与选择作为主要的任务而不仅是子任务, 使用分层编码器获取句子的分布式表示,作者将打分系统融入模型,根据前面的句子直接预测当前句的相对重要性.达到 ...

  5. ACL2022论文分类汇总-Prompt、句子表征、检索排序摘要

    写在前面 大家好,我是刘聪NLP. ACL2022会议的论文已经出来一阵子了,将论文列表过了一边,筛选了一些自己正在做或者感兴趣方向的相关论文,包括:Prompt(35篇).句子表征(21篇).检索排 ...

  6. 一周新论文 | 2020年第10周 | 自然语言处理相关

    <一周新论文>系列之2020年第10周:自然语言处理相关 本周重点关注: Microsoft: [29], [32], [43], [59], [63] Amazon: [14] Goog ...

  7. Self-Supervised Learning (ELMO, BERT, GPT, Auto-encoder)

    目录 The models become larger and larger - Self-supervised Learning ELMO (feature-based) How to repres ...

  8. 【转载】文本自动生成研究进展与趋势

    CCF 中文信息技术专业委员会 万小军 冯岩松 孙薇薇 北京大学计算机科学技术研究所,北京 摘要 我们期待未来有一天计算机能够像人类一样会写作,能够撰写出高质量的自然语言文本.文 本自动生成就是实现这 ...

  9. ICLR2020放榜 687篇入选34篇得满分! 且看OpenReview数据图文详解

    深度学习的顶级会议ICLR 2020将于明年 4 月 26 日于埃塞俄比亚首都亚的斯亚贝巴举行. 今日ICLR 2020放榜,最终2594篇论文中共有687篇被接收,其中48篇orals,108篇sp ...

最新文章

  1. HashMap是如何工作的
  2. 一篇文章带你搞懂 DEX 文件的结构
  3. 怎么在Java里辨别小数_求教java中如何判断一个数是不是小数,求详细代码及解释...
  4. leetcode刷题:循环队列
  5. 萌新的Python练习菜鸟100例(九)暂停一秒输出
  6. Qt QDataTime QString 两个类的使用
  7. I.MX6 2G DDR3 16G eMMC
  8. 现在工作和技术一般,想下班后充充电多学点东西。然而事实却相反,怎么让自己的学习更加有毅力?...
  9. LeetCode每周刷题(2019.6.24-2019.6.30)
  10. java基础总结03-进制
  11. 光伏并网matlab,基于MATLAB的光伏并网设计
  12. JavaScript--对象类型详解
  13. asp毕业设计——基于asp+access的工资管理系统设计与实现(毕业论文+程序源码)——工资管理系统
  14. java把在线图片转化流_图片转换图片流方法(二进制流)
  15. 找不到移动硬盘解决办法
  16. Android浮窗权限判断
  17. Visualising Residuals
  18. 我经常关注的博客 - 黎波 - 博客园
  19. 0基础自学软件测试的渠道你知道哪些?
  20. 服务器上Kafka启动报错:error=‘Cannot allocate memory‘ (errno=12)

热门文章

  1. 送书 | 新书《Python量化金融编程从入门到精通》
  2. 用python画多啦a梦源码_python 画多啦A梦
  3. win10/win11快速隐藏/显示桌面图标快捷方式
  4. TOJ 5238: C实验:变量交换函数
  5. 信息学奥赛一本通-1042
  6. METTLER TOLEDO托利多Bplus 标签格式设置教程(scale manager)
  7. 让微信 8.0 「裂开」「炸弹」的特效代码来了
  8. Kudu 原理、API使用、代码
  9. 新款Macbook Pro可以升级固态硬盘吗?
  10. 【git 报错】git add添加到暂存区报错:fatal: pathspec ‘xxx‘ did not match any files