(Arxiv, 2020)序列逐渐变短的Transformer

(Arxiv, 2020)Funnel-Transformer: Filtering out Sequential Redundancy for Efficient Language Processing

Under review的preprint paper.

本文工作:

  1. 设计了一个新的Transformer结构,其处理序列能够逐步缩短(抽象),减少计算代价,在sequence级别的任务上取得了较好的效果。
  2. 通过设计了一个decoder结构,能够为每个token都给出一个最终表示,使得模型在token级别的任务上也能work。

主要思想

起初看到香侬读 | Funnel-Transformer:让Transformer更高效地处理长序列标题的时候,还以为是能够处理更长的序列,但实际上这并不是这篇文章的目标。

我们知道传统的Transformer结构由多个相同的layer堆叠而成,而每一个layer又是前后两大部分:Multi-head Self-Attention模块和Position-wise Feed Forward Network。整个结构输入一个长度为\(L\)的序列,输出也是等长的序列。

而实际上,对很多sequence-level的task来说,我们最后只需要整个序列表示即可,即使给我们一整个序列我们也会自己去做pooling或是其他方式(如BERT只取[CLS]的表示)。对于这种问题,Transformer的结构似乎有一些冗余。

于是,本文提出一个序列长度逐渐压缩的新结构,避免了每次都对原长度序列进行的冗余的操作,节省了计算时间,并将节省下来的时间用于将模型做的更深,使模型效果更好。模型结构如下图所示,很简洁易懂。

Encoder

本文的重点放在Encoder部分,即序列的压缩上。模型将Encoder分成了几个不同的block。其中每个block内部序列长度始终不变,结构也和传统的Transformer层相同。

关键的部分在于连续两个Block之间的操作,主要有两个特殊的地方:

Pool

一个是图中绿色部分的Pool操作。这个操作就类似于CNN在图像中一层一层的提取逐渐高维的特征。本文实验发现在滑动窗口内做mean pooling效果很好,例如步长和窗口大小都为2的pool操作就是每两个词聚合为一个新的表示了。

需要注意的是,由于序列的第一个token通常都是特殊token [CLS],所以在进行pooling的时候,都是将[CLS]首先单独拿出来,对后面的序列做pooling,然后再拼上。即[CLS]不参与pooling

"pool-query-only" Design

另外还有一点是途中没有画出来的,即本文提出了一个"pool-query-only"的设计。在一个Block的第一层的Self-Attention模块中,只有Query是由pool后的短序列构建而成,而Key和Value则都是由pool之前的长序列来构建的: \[ \mathbf{h}^{\prime} \leftarrow \text { Pooling }(\mathbf{h}) \tag 1 \]

\[ \mathbf{h} \leftarrow \text { LayerNorm }\left(\mathbf{h}^{\prime}+\mathbf{S}-\mathrm{Attn}\left(\mathbf{Q}=\mathbf{h}^{\prime}, \mathbf{K} \mathbf{V}=\mathbf{h}\right)\right) \tag 2 \]

而对于一个block中的后几层,则还是正常操作。

对于这个设计,作者的解释是:如果用pool之后的短序列作为self-attention的qkv三者的话,会导致self-attention结果过于依赖pool的结果。而如果key、value都是pool前的长序列的话,则在self-attention时能让短序列的各个token再去attend到旧的长序列的各个token,能把新block第一层的self-attention和block间的pool操作融合在一起的感觉。

Decoder

由于Decoder不是重点,所以设计比较简单。

第一步是蓝色的模块,不像Encoder是逐步减小的方式,这里是直接从最小的序列扩展到原序列的长度。方式大致如下: \[ \forall i=1, \cdots, T, \quad h_{i}^{\mathrm{up}}=h_{i / / 2^{M-1}}^{M} \tag 3 \] 举个例子,起始长度为8的序列进行两次压缩得到了8->4->2的序列。那么在扩展之后,前4个token对应的表示是压缩后的第1个新token(重复4次),而后4个token的表示则是第二个新token重复4次。

但是这样显然是不合适的,得到token之间会有太多重复。所以本文通过一个残差操作(红色虚线)将第一个block的最后一层输出(未压缩的序列表示,low-level feature)和压缩后的最后一层输出(high-level feature)相加,作为该token最后的表示。

另外,在这之后,还可以加上几层Transformer layer以促成low-level & high-level feature更好的融合。

实验与结果

从论文报告的结果和GLUE榜单的结果来看,本文模型的效果还是很不错的。

Trade-offs

虽然该结构看起来很清晰易懂,结果看起来也很不错,但还是有一些不足,需要做trade-off:

  1. Depth-length trade-off,即序列长度、模型深度、每次压缩程度等设置。
  2. Gain-cost trade-off,即虽然本文的操作能节省计算时间也能提高模型效果,但会带来更多的参数,也就对应更高的显存占用和算力。如何做好这两方面的trade-off也是很重要的。

总结

总的来说,个人感觉这篇文章的结构挺有意思的,也很intuitive。