指针生成网络:不抄不是好学生!

一:今日吐槽

Seq2Seq横空出世,给文本生成式摘要带来了很大的想象空间。

某天,Seq2Seq和TextRank在陆家嘴软件园不期而遇。

Seq2Seq顾盼自雄:天不生夫子,万古如长夜。

TextRank:你不就和那个华晨宇一副德性吗?好好的《光年之外》,非要改编,听得我尴尬癌都犯了。

Seq2Seq:复读机也有资格评价我?

PGN出场:看把你给能的。邓紫棋都发现你的毛病了,一是无法准确再现文章的事实细节,二是无法解决未登录词问题,三是 倾向于生成重复的语句。

Seq2Seq:现在的90后这么严格?

PGN:我有指针网络 和Coverage机制 ,保证治好你的毛病。

Seq2Seq:你个数典忘祖的东西,忘了你爸爸是谁了?看我不打死你!

......

二:内容预告

本文介绍生成式文本摘要的经典模型:指针生成网络(Pointer-Generator Network,PGN)。

斯坦福大学的Manning组出品,没错,就是那个讲CS224N的老师!

指针生成网络结合了抽取式和生成式的优点,从原文中复制词语的同时,保留了生成文本的能力。

指针生成网络来自这篇论文:

《Get To The Point: Summarization with Pointer-Generator Networks》

论文地址:https://arxiv.org/abs/1704.04368。

源码地址:https://github.com/abisee/pointer-generator

本文关注以下内容:

  • 有哪些文本摘要的评价指标

  • 什么是指针网络

  • 什么是coverage机制

三:文本摘要的评价指标

我们首先来看论文中用到的评价指标。

文本自动摘要的主要评价指标有:BLEU、ROUGE和METEOR。

这里只介绍ROUGE。

01

ROUGE-N

ROUGE(Recall-Oriented Understudy for Gisting Evaluation),是基于召回率的文本摘要评价指标。

ROUGE-N表示一个候选摘要和多条参考摘要之间,共现N-grams的召回率。

下面是ROUGE-N的计算公式:

这个公式看起来有点小复杂,分母是多条参考摘要中,N-grams出现的总数,而分子是候选摘要中,匹配的N-grams的个数。

为啥说是基于召回率的评价指标呢?

因为分母是参考摘要中N-grams出现的总数,那么评估的是候选摘要对N-grams是否覆盖得够全面。

相比之下,BLEU是基于精准率的评价指标,分母是候选摘要中,N-grams出现的总数,分子是参考摘要中,匹配的N-grams的个数。

02

ROUGE-L

ROUGE-L,是通过统计匹配的最长公共子序列的个数,来评价摘要质量的。

什么是最长公共子序列呢 (Longest Common Subsequence,LCS)

举个例子。

参考摘要:我今年下半年有空了,想去看邓紫棋的演唱会。

候选摘要:我下半年要去观看邓紫棋的演唱会。

最长公共子序列:我 | 下半年 ||| 邓紫棋的演唱会。

和N-gram有什么区别?

最长公共子序列和N-gram不同,不要求是连续的,可以由断断续续的共有词语拼成,但是要求共有词语的顺序,在参考摘要和候选摘要中,必须保持一致。

ROUGE-L需要计算Recall和 Precision ,然后得到类似于F1值的指标:

X表示参考摘要,Y表示候选摘要,m、n分别表示二者的长度,LCS(X,Y)表示最长公共子序列的长度。

四:指针生成网络的贡献

01

Seq2Seq的问题

指针生成网络这篇论文,首先回顾了抽取式摘要和生成式摘要的优缺点,然后指出,尽管Seq2Seq框架给生成式摘要带来了曙光,但也存在以下三个问题:

  • 无法准确地再现文章的事实细节

  • 无法解决摘要中的未登录词(OOV)问题

  • 倾向于生成重复的摘要语句

因此论文提出了一种Seq2Seq的改进结构,称为指针生成网络, 运用指针网络(Point Network)和Coverage机制(Coverage Mechanism) ,试图在难度更大的多句摘要任务中,解决上述三个问题。

指针网络可以从原文中复制词语,不仅可以再现原文的重要细节,而且在一定程度上可以解决未登录词的问题。

这与抽取式摘要类似。

同时,指针网络具有生成摘要的能力,可以生成原文中没有的词语。

因此,指针网络与CopyNet有异曲同工之妙,可以视为抽取式摘要和生成式摘要的结合。

此外,论文运用Coverage机制,对已经出现在摘要中的词语,降低其注意力权重,从而降低其再次被生成的概率,在解决摘要语句重复的问题上,取得了不错的效果。

02

指针生成网络的效果

我们来看指针网络和Coverage机制的效果。

原文如下:

下面这三段摘要分别由以下三个模型生成:

Seq2Seq+Attention :原文中存在事实性错误。而且,原文中存在未登录词 muhammadu buhari,导致摘要中也出现了 UNK 标记。此外,nigeria(尼日利亚)这个词重复出现了很多次。

Pointer-Network :复现了原文中的词语和句子,把原文中的未登录词 muhammadu buhari 复制到摘要中,提升了准确率。但是 in the northeast part of nigeria 这段摘要重复出现了两次。

Pointer-Netword+Coverage :摘要中未出现未登录词和重复的语句,生成的摘要质量比较高。

五:Seq2Seq+Attention

指针生成网络是在Seq2Seq的基础上,做了一些改进,所以我们先来看Seq2Seq+Attention的baseline,以便于后面进行比较。

假设我们有一篇文章 w i ,里面的一句是:

Germany emerge victorious in 2-0 win against Argentina on Saturday.

把这句话输入到 Seq2Seq+Attention 模型中,示意图如下。

01

Encoder

首先是编码阶段,我们把文章序列输入到Encoder(一个双向LSTM)中,得到每个token对应的隐状态(Hidden State) h i (i=1,..,n)。

敲黑板!编码阶段每个token对应的隐状态 h i ,在接下来的解码过程中,都保持不变。

02

Decoder

在解码阶段,我们首先在Decoder中输入一个<START>标记,用于预测第一个词,从第二个词开始,用前一个词来预测下一个词。

用前一个词预测下一个词,在训练阶段和测试阶段的做法不同。

训练阶段是有监督的,有参考摘要,那么前一个词来自于参考摘要,这叫做 Teacher Forcing。

测试阶段是无监督的,那么前一个词来自于Decoder预测的词(Emitted Word)。

那么在解码的第t步,我们输入前一个词,会得到Decoder的隐状 态s t

以下代码来自Tensorflow 2.0的机器翻译教程。

class Decoder(tf.keras.Model):
def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):
super(Decoder, self).__init__()
self.batch_sz = batch_sz
self.dec_units = dec_units
self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
self.gru = tf.keras.layers.GRU(self.dec_units,
return_sequences=True,
return_state=True,
recurrent_initializer='glorot_uniform')
self.fc = tf.keras.layers.Dense(vocab_size)

""" 1: 使用注意力机制 """
self.attention = BahdanauAttention(self.dec_units)

def call(self, x, hidden, enc_output):

""" 2: 用decoder的隐状态和encoder的输出,得到注意力向量和注意力权重 """
context_vector, attention_weights = self.attention(hidden, enc_output)
x = self.embedding(x)

""" 3: decoder的输入与注意力向量拼接,用于计算输出单词的概率分布 """
x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)

""" 4: 得到decoder的隐状态,用于计算下一步的注意力向量 """
output, state = self.gru(x)
output = tf.reshape(output, (-1, output.shape[2]))

""" 5: 输出的形状(batch-size,vocab)"""
x = self.fc(output)

return x, state, attention_weights

有了编码阶段输入序列的 隐状态序列(Hidden State)h i (i=1,..,n) ,以及解码阶段第t步的隐状态 s t ,我们就可以求注意力权重了。

03

Attention

论文使用Bahdanau Attention来计算注意力分布:

注意分布 a i t (i=1,..,n) 会告诉解码器,在预测下一个词语时,Encoder中输入的词语,哪个更重要。

接着以注意力分布为权重,对Encoder的隐状态序列进行加权求和,得到上下文向量:

前面说了,编码阶段每个token对应的隐状态h i 是不变的,变化的是解码阶段的隐状态 s t ,相应的,解码阶段每一步的上下文向量,也是变化的。

这体现了注意力的本质:在解码的每一步,会关注原文中不同的单词。

class BahdanauAttention(tf.keras.layers.Layer):
def __init__(self, units):
super(BahdanauAttention, self).__init__()
self.W1 = tf.keras.layers.Dense(units)
self.W2 = tf.keras.layers.Dense(units)
self.V = tf.keras.layers.Dense(1)

def call(self, query, values):

hidden_with_time_axis = tf.expand_dims(query, 1)

""" 1: 计算注意力分布,形状 == (批大小,最大长度,1) """
score = self.V(tf.nn.tanh(self.W1(values) + self.W2(hidden_with_time_axis)))
attention_weights = tf.nn.softmax(score, axis=1)

""" 2: 计算上下文向量,形状 == (批大小,隐藏层大小)"""
context_vector = attention_weights * values
context_vector = tf.reduce_sum(context_vector, axis=1)

return context_vector, attention_weights

上面Decoder的代码中,是把Decoder的输入和上下文向量拼接,用于计算输出单词的概率分布。

而论文中略有不同,是把Decoder的隐状态和上下文向量拼接,经过两个线性层,来计算输出单词的概率分布。

04

损失函数

Decoder是一步一步进行预测的,每预测一个词,就计算一个loss,预测完毕后,再把每一步的loss相加,作为总的loss。

比如第t步,参考摘要中的词是:beat,而计算的概率分布中,beat的概率为0.9,那么该步的loss为-log(0.9),是一个很小的数;如果beat的概率为0.09,那么loss非常大。

回到上面的例子。

输入:Germany emerge victorious in 2-0 win against Argentina on Saturday.

第二步预测的单词为 beat,是因为对 victorious 和 win 这两个单词给予了更多关注。

六:指针生成网络

铺垫了这么多,主人公终于要上场了。

指针生成网络首先引入指针网络(Pointer Network),用于从原文中复制单词,解决OOV问题,然后引入Coverage机制,解决摘要中语句重复的问题。

01

指针网络

上面的图看着还蛮复杂,还是看公式比较清晰。

首先像 Seq2Seq+Attention那样 ,计算注意力分布、上下文向量和基于原词表的单词概率分布。

这个词表是事先构建好的,不包含输入文本中的OOV词。

不同的是,指针网络需要在解码的每一步,都计算一个概率。

在第t步, 这个概率由上下文向量、Decoder的隐状态和Decoder的输入 ,经过一个sigmoid函数计算而得:

这个概率就像一个开关, 用来决定是按原词表的单词概率分布,生成单词,还是按照原文单词的注意力分布,复制单词:

注意,公式左边的 P vocab (w)代表按原词表计算的单词概率分布,而右边的P(w)表示按加入OOV词后的新词表,计算的单词概率分布。

如果预测的单词是 win,而这个单词不在事先构建好的词表中,那么 P vocab (win) 为0,而win有一个注意力权重,乘上( 1- P gen ),作为该单词的概率。

于是从原文中复制win到摘要中。

而如果预测的词是beat,这个单词在事先构建好的词表中,而不在原文中,那么beat没有注意力权重,只有按原词表计算的概率。

于是按原词表生成beat到摘要中。

这样就解决了OOV问题。

最后再用 P(w) 计算loss。

用 Tensorflow 2.0 实现 Pointer 的代码如下:

class Pointer(tf.keras.layers.Layer):

def __init__(self):
super(Pointer, self).__init__()
self.w_s_reduce = tf.keras.layers.Dense(1)
self.w_i_reduce = tf.keras.layers.Dense(1)
self.w_c_reduce = tf.keras.layers.Dense(1)

def __call__(self, context_vector, dec_hidden, dec_inp):

dec_inp = tf.squeeze(dec_inp, axis=1)

""" 用上下文向量、Decoder的隐状态和输入,计算选择概率 """
return tf.nn.sigmoid(self.w_s_reduce(dec_hidden) + self.w_c_reduce(context_vector) + self.w_i_reduce(dec_inp))

02

Coverage机制

如果加入Coverage机制来减少摘要重复的问题,那么需要对Attention和loss进行修改。

(一)修正Attention

首先在解码的第t步,计算一个coverage向量 c t ,这个向量是由前t-1步的注意力分布求和而成。

这意味着,我们需要保存每一步计算的注意力分布,用于累加而得coverage向量。

这个coverage向量表示到第t-1步为止,原文中每个单词所受到的关注程度。

很显然,如果某个单词在前t-1步受到的关注太多,那么在第t步,注意力机制就应该给予其他单词更多关注。

于是,把这个coverage向量加入到注意力分布的计算中:

(二)修正loss

那么怎么做到减少关注呢?

这需要定义一个coverage loss,对已经给予太多关注的单词,进行惩罚。

这个coverage loss,就是在原文单词的注意力分布和coverage向量之间,取最小值,再求和。

为什么这样定义loss,可以惩罚关注过多的单词呢?

我们来看例子,假设原文有4个单词,在第t步,计算的coverage向量如下:

显然,第二个单词受到的关注最多。

如果我们继续关注第二个单词,比如注意力分布如下,那么计算得到的 coverage loss 较大:

如果我们不再关注第二个单词,比如关注第一个单词,那么计算得到的 coverage loss 较小:

把 coverage loss 加入到原先的loss函数中,就得到了每一步的loss。

03

实验细节

从上面的介绍来看,指针生成网络并不复杂,就是加了指针网络,修正了Attention,加了coverage loss,但是落实到代码,却巨复杂!

每输入一段文本,都需要计算OOV词表,从而得到拓展的词表。

需要计算基于原词表的概率分布和基于原文的注意力分布,得到最终的概率分布。

需要加入coverage loss,得到最终的loss。

关于实验细节,论文写得非常详细,我只选取部分重要的细节。

数据集为CNN/DailyMail。

作者选取的优化器为Adagrad,batch size 为16,学习率为0.15,进行梯度截断的最大梯度为2,没有做学习率衰减。

在测试集上,使用 beam search 进行解码。

作者把训练分成了两个阶段,第一阶段不加入 coverage loss,训练60万步,第二阶段加入 coverage loss,再训练3000步。

作者发现不加 coverage loss,以及在一开始就加入coverage loss,效果都不理想。

04

实验结果

以ROUGE F1为评价指标,指针生成网络以较大的优势,超过了其他生成式摘要模型。

Coverage机制的加入,的确减少了重复的摘要。

从下图可以看到,不加入Coverage机制的指针网络,生成了很多重复的N-gram。

而加入Coverage机制后,重复的N-gram的数量和参考摘要中的数量差不多。

参考资料:

1:《ROUGE: A Package for Automatic Evaluation of Summaries》

2: Get To The Point: Summarization with Pointer-Generator Networks》

推荐阅读

AINLP年度阅读收藏清单

数学之美中盛赞的 Michael Collins 教授,他的NLP课程要不要收藏?

自动作诗机&藏头诗生成器:五言、七言、绝句、律诗全了

From Word Embeddings To Document Distances 阅读笔记

模型压缩实践系列之——bert-of-theseus,一个非常亲民的bert压缩方法

这门斯坦福大学自然语言处理经典入门课,我放到B站了

可解释性论文阅读笔记1-Tree Regularization

征稿启示 | 稿费+GPU算力+星球嘉宾一个都不少

关于AINLP

AINLP 是一个有趣有AI的自然语言处理社区,专注于 AI、NLP、机器学习、深度学习、推荐算法等相关技术的分享,主题包括文本摘要、智能问答、聊天机器人、机器翻译、自动生成、知识图谱、预训练模型、推荐系统、计算广告、招聘信息、求职经验分享等,欢迎关注!加技术交流群请添加AINLPer(id:ainlper),备注工作/研究方向+加群目的。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章