我是靠谱客的博主 甜甜冬日,最近开发中收集的这篇文章主要介绍NLP经典论文:Attention、Self-Attention、Multi-Head Attention、Transformer 笔记论文介绍模型结构文章部分翻译相关视频相关的笔记相关代码pytorch API:tensorflow API,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

NLP经典论文:Attention、Self-Attention、Multi-Head Attention、Transformer 笔记

  • 论文
  • 介绍
    • 特点
  • 模型结构
    • 整体结构
      • 输入
      • 输出
    • Attention结构
      • 没有mask的情况
      • 有mask的情况
    • Input Embedding and Positional Encoding
      • 输入
      • Input Embedding
      • Positional Encoding
      • 输出
    • Encoder
      • 第一个子层
        • Multi-Head Attention
        • Add & Norm层
          • Add层
          • Norm层
      • 第二个子层
        • Position-wise Feed-Forward Networks
        • Add & Norm层
    • Decoder
      • 输入
      • 第一个子层
        • Masked Multi-Head Attention
        • Add & Norm层
      • 第二个子层
        • Multi-Head Attention
        • Add & Norm层
      • 第三个子层
        • Position-wise Feed-Forward Networks
        • Add & Norm层
    • Linear层
    • Softmax
  • 文章部分翻译
    • 3 Model Architecture
      • 3.1 Encoder and Decoder Stacks
      • 3.2 Attention
        • 3.2.1 Scaled Dot-Product Attention
        • 3.2.2 Multi-Head Attention
        • 3.2.3 Applications of Attention in our Model
      • 3.3 Position-wise Feed-Forward Networks
      • 3.4 Embeddings and Softmax
      • 3.5 Positional Encoding
    • 4 Why Self-Attention
  • 相关视频
  • 相关的笔记
  • 相关代码
    • pytorch
    • tensorflow
      • keras
  • pytorch API:
  • tensorflow API

论文

NLP论文笔记合集(持续更新)

原论文:《Attention is All you Need》

最早的提出attention模型的文章:NLP经典论文:最早的提出attention模型的文章 笔记
提出输入embedding和输出embedding共享的文章:NLP论文:Weight tying 笔记

介绍

2017年6月发表的文章,Attention 通常指 Self-Attention,Multi-Head Attention就是使用了几个并行的Self-Attention,相当于多通道。它不是由这篇文章最早提出,但由这篇文章发扬光大的。

Transformer 通常指这篇文章提出的模型结构,由 encoder 和decoder组成。

特点

RNN结构能够捕获时序信息,但不能并行计算;CNN结构能够并行,但不能捕获时序信息。Transformer使用 Attention 结构代替 RNN类结构,实现了运算的并行,加速了模型,同时引入 positional encoding 来引入时序信息。

模型结构

整体结构

在这里插入图片描述

输入

x = ( x 1 , … , x n ) mathbf{x}=(x_1,…,x_n) x=(x1,,xn) x i x_i xi 为one-hot表示的一个中文词, x mathbf{x} x为中文的一个句子。

输出

y = ( y 1 , … , y m ) mathbf{y}=(y_1,…,y_m) y=(y1,,ym) y i y_i yi 为one-hot表示的一个英文词, x mathbf{x} x为英文的一个句子。

Attention结构

该文章中,Attention 指的是Scaled Dot-Product Attention。
在这里插入图片描述
MatMul 是矩阵相乘。Mask(opt.)是可选的mask操作。矩阵 Q , K , V Q,K,V Q,K,V 分别代表query,key和value, Q , K ∈ R d k × n , V ∈ R d v × n Q,Kin R^{d_ktimes n},Vin R^{d_vtimes n} Q,KRdk×n,VRdv×n n n n 为输入句子的长度,即所含词的个数。

假设输入为 s x = ( s 1 , . . . , s n ) , s i ∈ R d e m b × 1 s_x=(s_1,...,s_n),s_iin R^{d_{emb}times 1} sx=(s1,...,sn),siRdemb×1 d e m b d_{emb} demb 为embedding的维度。经过矩阵变换:
Q = W Q s x = ( q 1 , . . . , q n ) K = W K s x = ( k 1 , . . . , k n ) V = W V s x = ( v 1 , . . . , v n ) Q=W_Qs_x=(q_1,...,q_n)\ K=W_Ks_x=(k_1,...,k_n)\ V=W_Vs_x=(v_1,...,v_n) Q=WQsx=(q1,...,qn)K=WKsx=(k1,...,kn)V=WVsx=(v1,...,vn)其中, W Q , W K ∈ R d k × d e m b , W V ∈ R d v × d e m b W_Q,W_Kin R^{d_ktimes d_{emb}},W_Vin R^{d_vtimes d_{emb}} WQ,WKRdk×demb,WVRdv×demb

没有mask的情况

attention可以比喻成做阅读理解, Q Q Q 为问题, K K K 为句子的意思, V V V 为句子。 Q Q Q K K K 的Dot-Product为:
K ⊤ Q = [ k 1 ⊤ q 1 k 1 ⊤ q 2 k 1 ⊤ q 3 ⋯ k 1 ⊤ q n k 2 ⊤ q 1 k 2 ⊤ q 2 k 2 ⊤ q 3 ⋯ k 2 ⊤ q n k 3 ⊤ q 1 k 3 ⊤ q 2 k 3 ⊤ q 3 ⋯ k 3 ⊤ q n ⋮ ⋮ ⋮ ⋱ ⋮ k n ⊤ q 1 k n ⊤ q 2 k n ⊤ q 3 ⋯ k n ⊤ q n ] = [ a 11 a 12 a 13 ⋯ a 1 n a 21 a 22 a 23 ⋯ a 2 n a 31 a 32 a 33 ⋯ a 3 n ⋮ ⋮ ⋮ ⋱ ⋮ a n 1 a n 2 a n 3 ⋯ a n n ] K^top Q= begin{bmatrix} {k_1^top q_1}&{k_1^top q_2}&{k_1^top q_3}&{cdots}&{k_1^top q_n}\ {k_2^top q_1}&{k_2^top q_2}&{k_2^top q_3}&{cdots}&{k_2^top q_n}\ {k_3^top q_1}&{k_3^top q_2}&{k_3^top q_3}&{cdots}&{k_3^top q_n}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {k_n^top q_1}&{k_n^top q_2}&k_n^top q_3&{cdots}&k_n^top q_n\ end{bmatrix}= begin{bmatrix} {a_{11}}&{a_{12}}&{a_{13}}&{cdots}&{a_{1n}}\ {a_{21}}&{a_{22}}&{a_{23}}&{cdots}&{a_{2n}}\ {a_{31}}&{a_{32}}&{a_{33}}&{cdots}&{a_{3n}}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {a_{n1}}&{a_{n2}}&a_{n3}&{cdots}&a_{nn}\ end{bmatrix} KQ=k1q1k2q1k3q1knq1k1q2k2q2k3q2knq2k1q3k2q3k3q3knq3k1qnk2qnk3qnknqn=a11a21a31an1a12a22a32an2a13a23a33an3a1na2na3nann
K ⊤ q i = ( k 1 ⊤ q i , . . . , k n ⊤ q i ) ⊤ = ( a 1 i , . . . , a n i ) ⊤ = a : , i K^top q_i=(k_1^top q_i,...,k_n^top q_i)^top=(a_{1i},...,a_{ni})^top=a_{:,i} Kqi=(k1qi,...,knqi)=(a1i,...,ani)=a:,i 代表用第 i i i 个词作为query,去匹配每一个key,得到每一个句子作为答案的分值,将分值 a : , i a_{:,i} a:,i 除以 d k sqrt{d_k} dk 后经过softmax得到比例 α : , i alpha_{:,i} α:,i,按照比例抄写每一个句子去构成第 i i i 个query的答案 V α : , i = [ v 1 v 2 v 3 ⋯ v n ] [ α 1 , i α 2 , i α 3 , i ⋯ α n , i ] = ∑ j = 1 n α j , i v j Valpha_{:,i}=begin{bmatrix} v_1&v_2&v_3&cdots&v_n end{bmatrix}begin{bmatrix} alpha_{1,i}\alpha_{2,i}\alpha_{3,i}\cdots\alpha_{n,i} end{bmatrix}=sum_{j=1}^nalpha_{j,i}v_j Vα:,i=[v1v2v3vn]α1,iα2,iα3,iαn,i=j=1nαj,ivj

因此Attention函数为:
A t t e n t i o n ( Q , K , V ) = V s o f t m a x ( K ⊤ Q d k ) = [ v 1 v 2 v 3 ⋯ v n ] [ α 11 α 12 α 13 ⋯ α 1 n α 21 α 22 α 23 ⋯ α 2 n α 31 α 32 α 33 ⋯ α 3 n ⋮ ⋮ ⋮ ⋱ ⋮ α n 1 α n 2 α n 3 ⋯ α n n ] Attention(Q,K,V)=Vsoftmax(frac{K^top Q}{sqrt{d_k}})=begin{bmatrix} v_1&v_2&v_3&cdots&v_n end{bmatrix}begin{bmatrix} {alpha_{11}}&{alpha_{12}}&{alpha_{13}}&{cdots}&{alpha_{1n}}\ {alpha_{21}}&{alpha_{22}}&{alpha_{23}}&{cdots}&{alpha_{2n}}\ {alpha_{31}}&{alpha_{32}}&{alpha_{33}}&{cdots}&{alpha_{3n}}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {alpha_{n1}}&{alpha_{n2}}&alpha_{n3}&{cdots}&alpha_{nn}\ end{bmatrix} Attention(Q,K,V)=Vsoftmax(dk KQ)=[v1v2v3vn]α11α21α31αn1α12α22α32αn2α13α23α33αn3α1nα2nα3nαnn = [ ∑ j = 1 n α j , 1 v j ∑ j = 1 n α j , 2 v j ∑ j = 1 n α j , 3 v j ⋯ ∑ j = 1 n α j , n v j ] =begin{bmatrix}sum_{j=1}^nalpha_{j,1}v_j&sum_{j=1}^nalpha_{j,2}v_j&sum_{j=1}^nalpha_{j,3}v_j&cdots&sum_{j=1}^nalpha_{j,n}v_jend{bmatrix} =[j=1nαj,1vjj=1nαj,2vjj=1nαj,3vjj=1nαj,nvj]Scaled 指除以 d k sqrt{d_k} dk

有mask的情况

前面不使用mask的情况是因为在解码的过程中,输入是一个完整的句子,因为每一个词都可以去匹配任何一个词。而解码的过程,我们不允许当前词去关注位置在其后面的词,因为预测模型是基于当前时刻以前的词去预测未来的词,而不可能基于未来的词去预测当前时刻的词。所以第 i i i 个词的query只能匹配比 i i i 位置小的词的key,mask会将非法匹配的分值替换成 − ∞ -infty ,那么它在softmax后就会变成0。此时 Q Q Q K K K 的Dot-Product为:
K ⊤ Q = [ k 1 ⊤ q 1 k 1 ⊤ q 2 k 1 ⊤ q 3 ⋯ k 1 ⊤ q n k 2 ⊤ q 1 k 2 ⊤ q 2 k 2 ⊤ q 3 ⋯ k 2 ⊤ q n k 3 ⊤ q 1 k 3 ⊤ q 2 k 3 ⊤ q 3 ⋯ k 3 ⊤ q n ⋮ ⋮ ⋮ ⋱ ⋮ k n ⊤ q 1 k n ⊤ q 2 k n ⊤ q 3 ⋯ k n ⊤ q n ] = [ a 11 a 12 a 13 ⋯ a 1 n − ∞ a 22 a 23 ⋯ a 2 n − ∞ − ∞ a 33 ⋯ a 3 n ⋮ ⋮ ⋮ ⋱ ⋮ − ∞ − ∞ − ∞ ⋯ a n n ] K^top Q= begin{bmatrix} {k_1^top q_1}&{k_1^top q_2}&{k_1^top q_3}&{cdots}&{k_1^top q_n}\ {k_2^top q_1}&{k_2^top q_2}&{k_2^top q_3}&{cdots}&{k_2^top q_n}\ {k_3^top q_1}&{k_3^top q_2}&{k_3^top q_3}&{cdots}&{k_3^top q_n}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {k_n^top q_1}&{k_n^top q_2}&k_n^top q_3&{cdots}&k_n^top q_n\ end{bmatrix}= begin{bmatrix} {a_{11}}&{a_{12}}&{a_{13}}&{cdots}&{a_{1n}}\ {-infty}&{a_{22}}&{a_{23}}&{cdots}&{a_{2n}}\ {-infty}&{-infty}&{a_{33}}&{cdots}&{a_{3n}}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {-infty}&{-infty}&-infty&{cdots}&a_{nn}\ end{bmatrix} KQ=k1q1k2q1k3q1knq1k1q2k2q2k3q2knq2k1q3k2q3k3q3knq3k1qnk2qnk3qnknqn=a11a12a22a13a23a33a1na2na3nann
Attention函数为:
A t t e n t i o n ( Q , K , V ) = V s o f t m a x ( K ⊤ Q d k ) = [ v 1 v 2 v 3 ⋯ v n ] [ α 11 α 12 α 13 ⋯ α 1 n 0 α 22 α 23 ⋯ α 2 n 0 0 α 33 ⋯ α 3 n ⋮ ⋮ ⋮ ⋱ ⋮ 0 0 0 ⋯ α n n ] Attention(Q,K,V)=Vsoftmax(frac{K^top Q}{sqrt{d_k}})=begin{bmatrix} v_1&v_2&v_3&cdots&v_n end{bmatrix}begin{bmatrix} {alpha_{11}}&{alpha_{12}}&{alpha_{13}}&{cdots}&{alpha_{1n}}\ {0}&{alpha_{22}}&{alpha_{23}}&{cdots}&{alpha_{2n}}\ {0}&{0}&{alpha_{33}}&{cdots}&{alpha_{3n}}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {0}&{0}&0&{cdots}&alpha_{nn}\ end{bmatrix} Attention(Q,K,V)=Vsoftmax(dk KQ)=[v1v2v3vn]α11000α12α2200α13α23α330α1nα2nα3nαnn
= [ ∑ j = 1 n α j , 1 v j ∑ j = 1 n α j , 2 v j ∑ j = 1 n α j , 3 v j ⋯ ∑ j = 1 n α j , n v j ] =begin{bmatrix}sum_{j=1}^nalpha_{j,1}v_j&sum_{j=1}^nalpha_{j,2}v_j&sum_{j=1}^nalpha_{j,3}v_j&cdots&sum_{j=1}^nalpha_{j,n}v_jend{bmatrix} =[j=1nαj,1vjj=1nαj,2vjj=1nαj,3vjj=1nαj,nvj]

Input Embedding and Positional Encoding

在这里插入图片描述

输入

x = ( x 1 , … , x n ) mathbf{x}=(x_1,…,x_n) x=(x1,,xn) x i x_i xi 为one-hot表示的一个中文词, x mathbf{x} x 为中文的一个句子, x i ∈ R C s × 1 x_iin R^{C_s times 1} xiRCs×1 C s C_s Cs 为源词汇表(source vocabulary)的大小。

Input Embedding

将输入投影到 embedding 空间中: W s , e m b x W_{s,emb}mathbf{x} Ws,embx W s , e m b W_{s,emb} Ws,emb 为源词汇表(source vocabulary)的 word embedding, W s , e m b ∈ R 512 × C s W_{s,emb}in R^{512 times C_s} Ws,embR512×Cs,embedding 维度为512。

Positional Encoding

位置编码 P E PE PE 与 embedding 具有相同的维度 d m o d e l = 512 d_{model}=512 dmodel=512,因此可以将两者相加。 P E ∈ R 512 × n PEin R^{512times n} PER512×n

使用不同频率的正弦和余弦函数:
P E ( p o s , i ) = { s i n ( p o s / 1000 0 f l o o r ( i / 2 ) / d m o d e l ) , i = 0 , 2 , . . . 2 k , . . . 510 c o s ( p o s / 1000 0 f l o o r ( i / 2 ) / d m o d e l ) , i = 1 , 3 , . . . 2 k + 1 , . . . 511 PE_{(pos,i)}=left{ begin{aligned} sin(pos/10000^{floor(i/2)/d_{model}})&,quad i=0,2,...2k,...510\ cos(pos/10000^{floor(i/2)/d_{model}})&,quad i=1,3,...2k+1,...511 end{aligned} right. PE(pos,i)={sin(pos/10000floor(i/2)/dmodel)cos(pos/10000floor(i/2)/dmodel),i=0,2,...2k,...510,i=1,3,...2k+1,...511其中 p o s pos pos代表输入词在句子中所处第 p o s pos pos 个位置, p o s = 1 , . . . , n pos=1,...,n pos=1,...,n i i i 代表模型第 i i i 个维度,即 embedding 的第 i i i 个维度, i = 0 , . . . , 511 i=0,...,511 i=0,...,511 f l o o r ( ⋅ ) floor(cdot) floor() 代表向下取整。

位置编码的每个维度对应一个正弦曲线。波长形成从 2 π 2π 2π 10000 ⋅ 2 π 10000cdot 2π 100002π 的几何级数。我们之所以选择这个函数,是因为我们假设它可以让模型通过相对位置轻松学习如何关注信息,因为对于任何固定偏移量 k k k P E p o s + k PE_{pos+k} PEpos+k可以表示为 P E p o s PE_{pos} PEpos的线性函数。

输出

最后得到 encoder 的输入为: s x 0 = W s , e m b x + P E , s x 0 ∈ R 512 × n s_{x0}=W_{s,emb}mathbf{x}+PE,s_{x0} in R^{512times n} sx0=Ws,embx+PE,sx0R512×n

Encoder

在这里插入图片描述
encoder由 N = 6 N=6 N=6 个相同层堆叠而成。每层有两个子层。第一个子层是multi-head self-attention结构,第二个子层是简单的、对应位置逐个相乘的全连接前馈网络。我们在两个子层的每个层周围使用residual connection[10],然后进行layer normalization[1]。也就是说,每个子层的输出是LayerNorm(x+Sublayer(x)),其中Sublayer(x)是由子层本身实现的函数。

第一个子层

Multi-Head Attention

在这里插入图片描述
h h h 为Head的个数。Multi-Head Attention通过把输入降维后,经过多通道即 h h h 个Head分别进行attention,再将各个输出拼接成原始的维度。Multi-Head 的用意是希望每一个Head能关注到不同的信息,就好像多个人一起做阅读理解,再把答案汇总,希望答案更加全面。

将输入 s x 0 s_{x0} sx0 转换成降维后的 Q i , K i , V i Q_i,K_i,V_i Qi,Ki,Vi
Q = W Q s x 0 = [ Q 1 Q 2 Q 3 ⋯ Q h ] , K = W K s x 0 = [ K 1 K 2 K 3 ⋯ K h ] , V = W V s x 0 = [ V 1 V 2 V 3 ⋯ V h ] , Q=W_Qs_{x0}=begin{bmatrix} Q_1\Q_2\Q_3\cdots\Q_h end{bmatrix},quad K=W_Ks_{x0}=begin{bmatrix} K_1\K_2\K_3\cdots\K_h end{bmatrix},quad V=W_Vs_{x0}=begin{bmatrix} V_1\V_2\V_3\cdots\V_h end{bmatrix},quad Q=WQsx0=Q1Q2Q3Qh,K=WKsx0=K1K2K3Kh,V=WVsx0=V1V2V3Vh,其中
Q i ∈ R d q × n , W Q ∈ R d m o d e l × d e m b , Q ∈ R d m o d e l × n , Q_iin R^{d_qtimes n}, W_Qin R^{d_{model}times d_{emb}},Qin R^{d_{model}times n}, QiRdq×n,WQRdmodel×demb,QRdmodel×n,
K i ∈ R d k × n , W K ∈ R d m o d e l × d e m b , K ∈ R d m o d e l × n , K_iin R^{d_ktimes n},W_Kin R^{d_{model}times d_{emb}},Kin R^{d_{model}times n}, KiRdk×n,WKRdmodel×demb,KRdmodel×n,
V i ∈ R d v × n , W V ∈ R d m o d e l × d e m b , V ∈ R d m o d e l × n , V_iin R^{d_vtimes n},W_Vin R^{d_{model}times d_{emb}},Vin R^{d_{model}times n}, ViRdv×n,WVRdmodel×demb,VRdmodel×n,
h = 8 h=8 h=8 d q = d k = d v = d m o d e l / h = 64 d_q=d_k=d_v=d_{model}/h=64 dq=dk=dv=dmodel/h=64。然后使用上文中的attention函数公式:
H e a d i = A t t e n t i o n ( Q i , K i , V i ) = V i s o f t m a x ( K i ⊤ Q i d k ) Head_i=Attention(Q_i,K_i,V_i)=V_isoftmax(frac{K_i^top Q_i}{sqrt{d_k}}) Headi=Attention(Qi,Ki,Vi)=Visoftmax(dk KiQi) M u l t i H e a d ( Q , K , V ) = W O [ h e a d 1 h e a d 2 h e a d 3 ⋯ h e a d h ] MultiHead(Q, K, V ) = W^Obegin{bmatrix} head_1\head_2\head_3\cdots\head_h end{bmatrix} MultiHead(Q,K,V)=WOhead1head2head3headh其中 W O ∈ R d m o d e l × d m o d e l , M u l t i H e a d ∈ R d m o d e l × n W^Oin R^{d_{model}times d_{model}},MultiHeadin R^{d_{model}times n} WORdmodel×dmodel,MultiHeadRdmodel×n

Add & Norm层

Add层

Add为residual connection,残差连接,即
r e s x 1 = s x 0 + M u l t i H e a d ( s x 0 ) , r e s x 1 ∈ R d m o d e l × n res_{x1}=s_{x0}+MultiHead(s_{x0}),quad res_{x1}in R^{d_{model}times n} resx1=sx0+MultiHead(sx0),resx1Rdmodel×n

Norm层

Norm为layer normalization,它与batch normalization可以参考NLP中 batch normalization与 layer normalization,CV经典论文:Batch Normalization 笔记,NLP经典论文:Layer Normalization 笔记。
来源:mingo_敏 https://blog.csdn.net/shanglianlm/article/details/85075706来源:mingo_敏 https://blog.csdn.net/shanglianlm/article/details/85075706

layer normalization就是对这个 d m o d e l d_{model} dmodel 维的embedding进行normalization。具体来说, r e s x 1 , 1 res_{x1,1} resx1,1 r e s x 1 res_{x1} resx1 第一个位置的列向量, r e s x 1 , 1 ∈ R d m o d e l × 1 res_{x1,1}in R^{d_{model}times 1} resx1,1Rdmodel×1,对它进行layer normalization:
r e s x 1 , 1 = [ r 1 r 2 r 3 ⋯ r d m o d e l ] T , res_{x1,1}=begin{bmatrix}r_1&r_2&r_3&cdots&r_{d_{model}}end{bmatrix}^T , resx1,1=[r1r2r3rdmodel]T, r ‾ = ∑ i = 1 d m o d e l r i , σ = 1 d m o d e l ∑ i = 1 d m o d e l ( r i − r ‾ ) 2 , overline{r}=sumlimits_{i=1}^{d_{model}}r_i,quad sigma=sqrt{frac{1}{d_{model}}sumlimits_{i=1}^{d_{model}}(r_i-overline{r})^2}, r=i=1dmodelri,σ=dmodel1i=1dmodel(rir)2 , r e s ^ x 1 , 1 = [ r ^ 1 r ^ 2 r ^ 3 ⋯ r ^ d m o d e l ] T = [ r 1 − r ‾ σ r 2 − r ‾ σ r 3 − r ‾ σ ⋯ r d m o d e l − r ‾ σ ] T , hat{res}_{x1,1}=begin{bmatrix}hat{r}_1&hat{r}_2&hat{r}_3&cdots&hat{r}_{d_{model}}end{bmatrix}^T =begin{bmatrix}frac{r_1-overline{r}}{sigma}&frac{r_2-overline{r}}{sigma}&frac{r_3-overline{r}}{sigma}&cdots&frac{r_{d_{model}}-overline{r}}{sigma}end{bmatrix}^T, res^x1,1=[r^1r^2r^3r^dmodel]T=[σr1rσr2rσr3rσrdmodelr]T, L N ( r e s x 1 , 1 ) = γ r e s ^ x 1 , 1 + β LN(res_{x1,1})=gamma hat{res}_{x1,1}+beta LN(resx1,1)=γres^x1,1+β γ , β ∈ R d m o d e l × 1 gamma,beta in R^{d_{model} times 1} γ,βRdmodel×1,是需要学习的2个参数, r e s x 1 res_{x1} resx1 的每一个列向量共享这2个参数。

所以Norm层的输出为: L N x 1 = L N ( r e s x 1 ) ∈ R d m o d e l × n LN_{x1}=LN(res_{x1})in R^{d_{model}times n} LNx1=LN(resx1)Rdmodel×n

第二个子层

在这里插入图片描述

Position-wise Feed-Forward Networks

一个全连接的前馈网络,该网络分别相同地应用于每个位置。这包括两个线性变换,中间有一个ReLU激活。 虽然线性变换在不同位置上是相同的,但它们在层与层之间使用不同的参数。对于 L N x 1 LN_{x1} LNx1 的第 i i i 个位置, L N x 1 , i ∈ R d m o d e l × 1 LN_{x1,i}in R^{d_{model}times 1} LNx1,iRdmodel×1,经过Feed-Forward Networks后:
F F N ( L N x 1 , i ) = W 2 R e L U ( W 1 L N x 1 , i + b 1 ) + b 2 FFN(LN_{x1,i}) = W_2ReLU(W_1LN_{x1,i}+b_1)+b_2 FFN(LNx1,i)=W2ReLU(W1LNx1,i+b1)+b2其中 W 1 ∈ R d f f × d m o d e l , b 1 ∈ R d f f × 1 , W_1in R^{d_{ff}times d_{model}},b_1in R^{d_{ff}times 1}, W1Rdff×dmodel,b1Rdff×1, W 2 ∈ R d m o d e l × d f f , b 2 ∈ R d m o d e l × 1 W_2in R^{d_{model}times d_{ff}},b_2in R^{d_{model}times 1} W2Rdmodel×dff,b2Rdmodel×1内层的维数为 d f f = 2048 d_{ff}=2048 dff=2048

所以此子层的输出为: F F N ( L N x 1 ) ∈ R d m o d e l × n FFN(LN_{x1})in R^{d_{model}times n} FFN(LNx1)Rdmodel×n

Add & Norm层

与前一个子层类似,经过residual connection(残差连接)和layer normalization后的输出为: L N x 2 ∈ R d m o d e l × n LN_{x2}in R^{d_{model}times n} LNx2Rdmodel×n

自此完成encoder一个层的输出,经过6个相同的层后,输出为: z ∈ R d m o d e l × n mathbf{z}in R^{d_{model}times n} zRdmodel×n

Decoder

在这里插入图片描述
decoder也由N=6个相同层的堆栈组成。除了encoder层中的两个子层之外,decoder还插入了子层,该子层对encoder堆的输出执行multi-head attention。

输入

在介绍decoder之前,要先介绍一下它的输入,即word embedding和positional encoding层的输出 s y 0 mathbf{s}_{y0} sy0 s y 0 ∈ R 512 × m s_{y0} in R^{512times m} sy0R512×m
在这里插入图片描述

必须注意的是
在训练过程中,outputs ∈ R d m o d e l × m in R^{d_{model}times m} Rdmodel×m m m m 为输出句子 y = ( y 1 , . . . , y m ) mathbf{y}=(y_1,...,y_m) y=(y1,...,ym) 的长度。(严格来说,在编程中,这个 m m m 应该是目标语句的最大长度,不够长度的句子需要使用 ′ P A D ′ 'PAD' PAD 补零。)而这个shifted right指的是outputs = y s h i f t e d = ( ′ B O S ′ , y 1 , . . . , y m − 1 ) =mathbf{y}_{shifted}=('BOS',y_1,...,y_{m-1}) =yshifted=(BOS,y1,...,ym1) ′ B O S ′ 'BOS' BOS 是begin of sentence,代表句子的起始,相当于给它一个初始值。像sequence to sequence那样的模型的decoder的输出维度是 R d m o d e l × 1 R^{d_{model}times 1} Rdmodel×1,而此模型的decoder的输出维度为 R d m o d e l × m R^{d_{model}times m} Rdmodel×m,对应的就是 y = ( y 1 , . . . , y m ) mathbf{y}=(y_1,...,y_m) y=(y1,...,ym) 的embedding,outputs叫做 y mathbf{y} y的shifted right。这就意味着它不像sequence to sequence那样需要每迭代一次只计算一个时间步的预测词,此模型的decoder的输出就是 m m m 个时间步的预测词的embedding。

在预测过程中,outputs是需要反复迭代的。

t=1时刻,outputs = ( ′ B O S ′ , ′ P A D ′ , . . . , ′ P A D ′ ) =('BOS','PAD',...,'PAD') =(BOS,PAD,...,PAD),decoder的输出为 = ( y ^ 1 , ′ P A D ′ , . . . , ′ P A D ′ ) =(hat{y}_1,'PAD',...,'PAD') =(y^1,PAD,...,PAD);
t时刻,outputs = ( ′ B O S ′ , y ^ 1 , . . . , y ^ t − 1 , . . . , ′ P A D ′ ) =('BOS',hat{y}_1,...,hat{y}_{t-1},...,'PAD') =(BOS,y^1,...,y^t1,...,PAD),decoder的输出为 = ( y ^ 1 , . . . , y ^ t − 1 , y ^ t , . . . , ′ P A D ′ ) =(hat{y}_1,...,hat{y}_{t-1},hat{y}_{t},...,'PAD') =(y^1,...,y^t1,y^t,...,PAD)

W t , e m b W_{t,emb} Wt,emb 为目标词汇表(target vocabulary)的word embedding, W t , e m b ∈ R 512 × C t W_{t,emb}in R^{512times C_t} Wt,embR512×Ct C t C_t Ct 为目标词汇表的大小。文章的引用文献[24]将模型的输入embedding和输出embedding tie在一起,即共享一个embedding空间,同一个词在输入embed时和输出embed时会embed成同一个embedding向量。在翻译模型中,如果2种语言的subword共享率非常高,甚至可以实现编码器的输入embedding、解码器的输入embedding和解码器的输出embedding 三者共享。详细见NLP论文:Weight tying 笔记。

所以word embedding和positional encoding层的输出 s y 0 = W t , e m b y s h i f t e d + P E ( y s h i f t e d ) , s y 0 ∈ R 512 × m mathbf{s}_{y0}=W_{t,emb}mathbf{y}_{shifted}+PE(mathbf{y}_{shifted}),s_{y0} in R^{512times m} sy0=Wt,embyshifted+PE(yshifted),sy0R512×m

第一个子层

在这里插入图片描述

Masked Multi-Head Attention

正如上文中masked attention的介绍, Q Q Q K K K 的Dot-Product为:
K ⊤ Q = [ k 1 ⊤ q 1 k 1 ⊤ q 2 k 1 ⊤ q 3 ⋯ k 1 ⊤ q n k 2 ⊤ q 1 k 2 ⊤ q 2 k 2 ⊤ q 3 ⋯ k 2 ⊤ q n k 3 ⊤ q 1 k 3 ⊤ q 2 k 3 ⊤ q 3 ⋯ k 3 ⊤ q n ⋮ ⋮ ⋮ ⋱ ⋮ k n ⊤ q 1 k n ⊤ q 2 k n ⊤ q 3 ⋯ k n ⊤ q n ] = [ a 11 a 12 a 13 ⋯ a 1 n − ∞ a 22 a 23 ⋯ a 2 n − ∞ − ∞ a 33 ⋯ a 3 n ⋮ ⋮ ⋮ ⋱ ⋮ − ∞ − ∞ − ∞ ⋯ a n n ] K^top Q= begin{bmatrix} {k_1^top q_1}&{k_1^top q_2}&{k_1^top q_3}&{cdots}&{k_1^top q_n}\ {k_2^top q_1}&{k_2^top q_2}&{k_2^top q_3}&{cdots}&{k_2^top q_n}\ {k_3^top q_1}&{k_3^top q_2}&{k_3^top q_3}&{cdots}&{k_3^top q_n}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {k_n^top q_1}&{k_n^top q_2}&k_n^top q_3&{cdots}&k_n^top q_n\ end{bmatrix}= begin{bmatrix} {a_{11}}&{a_{12}}&{a_{13}}&{cdots}&{a_{1n}}\ {-infty}&{a_{22}}&{a_{23}}&{cdots}&{a_{2n}}\ {-infty}&{-infty}&{a_{33}}&{cdots}&{a_{3n}}\ {vdots}&{vdots}&{vdots}&{ddots}&{vdots}\ {-infty}&{-infty}&-infty&{cdots}&a_{nn}\ end{bmatrix} KQ=k1q1k2q1k3q1knq1k1q2k2q2k3q2knq2k1q3k2q3k3q3knq3k1qnk2qnk3qnknqn=a11a12a22a13a23a33a1na2na3nann会把 i < j i<j i<j q i k j q_ik_j qikj 给mask掉,因为处于未来的词,是不能用于预测当前时刻的词的,意思就是计算 p ( y ^ t ∣ y 1 , . . . y t − 1 ) p(hat{y}_t|y_1,...y_{t-1}) p(y^ty1,...yt1)时不能基于大于等于 t t t 时刻的 y y y来预测。

所以此层的输出为: M a s k e d M u l t i H e a d ( s y 0 ) ∈ R 512 × m MaskedMultiHead(s_{y0})in R^{512times m} MaskedMultiHead(sy0)R512×m

Add & Norm层

与encoder层中类似,经过residual connection(残差连接)后的输出为: r e s y 1 = s y 0 + M a s k e d M u l t i H e a d ( s y 0 ) , r e s y 1 ∈ R d m o d e l × m res_{y1}=s_{y0}+MaskedMultiHead(s_{y0}),quad res_{y1}in R^{d_{model}times m} resy1=sy0+MaskedMultiHead(sy0),resy1Rdmodel×m

经过layer normalization后的输出为: L N y 1 = L N ( r e s y 1 ) ∈ R d m o d e l × m LN_{y1}=LN(res_{y1})in R^{d_{model}times m} LNy1=LN(resy1)Rdmodel×m

第二个子层

在这里插入图片描述

Multi-Head Attention

这里Attention中的 K , V K,V K,V使用encoder的输出 z ∈ R d m o d e l × n mathbf{z}in R^{d_{model}times n} zRdmodel×n作为输入, Q Q Q 使用上一个子层的输出 L N y 1 LN_{y1} LNy1 作为输入。

此层的输出为 M u l t i H e a d ( Q , K , V ) = M u l t i H e a d ( W Q z , W K z , W Q L N y 1 ) MultiHead(Q, K, V ) = MultiHead(W_Qmathbf{z}, W_Kmathbf{z}, W_QLN_{y1} ) MultiHead(Q,K,V)=MultiHead(WQz,WKz,WQLNy1)

Add & Norm层

与上一个子层中类似,经过residual connection(残差连接)和layer normalization后的输出为: L N y 2 = L N ( r e s y 2 ) ∈ R d m o d e l × m LN_{y2}=LN(res_{y2})in R^{d_{model}times m} LNy2=LN(resy2)Rdmodel×m

第三个子层

在这里插入图片描述

Position-wise Feed-Forward Networks

与encoder中类似,所以此子层的输出为: F F N ( L N y 2 ) ∈ R d m o d e l × n FFN(LN_{y2})in R^{d_{model}times n} FFN(LNy2)Rdmodel×n

Add & Norm层

经过residual connection(残差连接)和layer normalization后的输出为: L N y 3 = L N ( F F N ( L N y 2 ) ) ∈ R d m o d e l × m LN_{y3}=LN(FFN(LN_{y2}))in R^{d_{model}times m} LNy3=LN(FFN(LNy2))Rdmodel×m

自此完成decoder一个层的输出,经过6个相同的层后,输出为: h = ( h 1 , … , h m ) , h ∈ R d m o d e l × m mathbf{h}=(h_1,…,h_m),mathbf{h}in R^{d_{model}times m} h=(h1,,hm),hRdmodel×m h i h_i hi 就是sequence to sequence模型中的第 i i i 个 annotation, i i i 个 hidden state,也相对于预测词的embedding。

Linear层

在这里插入图片描述

这个层的输出为: e = W t , e m b ⊤ h mathbf{e}=W_{t,emb}topmathbf{h} e=Wt,embh W t , e m b ⊤ ∈ R C t × 512 , e ∈ R C t × m W_{t,emb}top in R^{C_ttimes 512},mathbf{e}in R^{C_ttimes m} Wt,embRCt×512,eRCt×m,因为decoder的输出embedding和它的输入embedding共享,所以这里线性层的参数为 W t , e m b ⊤ W_{t,emb}top Wt,emb

Softmax

e mathbf{e} e 的每一列 e t e_t et 进行softmax: p ( y t ∣ y t − 1 , . . . y 1 , x ) = e x p ( e t , y t ) ∑ j = 1 C t e x p ( e t , j ) p(y_t|y_{t-1},...y_1,mathbf{x})=frac{exp(e_{t,y_t})}{sumlimits_{j=1}^{C_t}exp(e_{t,j})} p(ytyt1,...y1,x)=j=1Ctexp(et,j)exp(et,yt)各列 e i e_i ei softmax 的最大值所对应的词就是预测结果。

文章部分翻译

3 Model Architecture

极具竞争力的神经序列转录模型都有encoder-decoder结构[5,2,29]。这里,encoder将以symbol表示的 ( x 1 , … , x n ) (x_1,…,x_n) (x1,,xn) 输入序列映射到连续表示 z = ( z 1 , … , z n ) boldsymbol{mathbf{z}}=(z_1,…,z_n) z=(z1,,zn) 的序列。给定 z boldsymbol{mathbf{z}} z,然后decoder一次生成symbol表示的输出序列 ( y 1 , … , y m ) (y_1,…,y_m) (y1,,ym)的一个元素。在每一步中,模型都是自回归的[9],在生成下一步时,使用先前生成的symbol作为额外输入。

Transformer 遵循这一总体架构,encoder和decoder都使用堆叠的self-attention和逐点全连接层,分别如图1的左半部分和右半部分所示。
在这里插入图片描述

3.1 Encoder and Decoder Stacks

E n c o d e r boldsymbol{mathbf{Encoder}} Encoder:encoder由N=6个相同层堆叠而成。每层有两个子层。第一个子层是multi-head self-attention结构,第二个子层是简单的、对应位置逐个相乘的全连接前馈网络。我们在两个子层的每个层周围使用residual connection[10],然后进行layer normalization[1]。也就是说,每个子层的输出是LayerNorm(x+Sublayer(x)),其中Sublayer(x)是由子层本身实现的函数。为了便于residual connection进行连接,模型中的所有子层以及embedding层都会生成尺寸为 d m o d e l = 512 d_{model}=512 dmodel=512 的输出。

D e c o d e r boldsymbol{mathbf{Decoder}} Decoder:decoder也由N=6个相同层的堆栈组成。除了每个encoder层中的两个子层之外,decoder还插入第三个子层,该子层对encoder堆的输出执行multi-head attention。与encoder类似,我们在每个子层周围使用residual connection,然后进行layer normalization。我们还修改了decoder堆栈中的self-attention子层,以防止当前位置关注到未来位置的信息。这种masking,基于输出的embedding偏移一个位置的事实,确保位置 i i i 的预测只能依赖于位置小于 i i i 的已知输出。

3.2 Attention

attention函数可以描述为将query和一组key-value对映射到输出的一个映射,其中query、key、value和输出都是向量。输出由value的加权计算得到,其中分配给每个value的权重由query的匹配函数与相应key计算得到。
在这里插入图片描述

3.2.1 Scaled Dot-Product Attention

我们称我们这个独特的attention为“Scaled Dot-Product Attention”(图2)。输入包括维度 d k d_k dk 的query和key以及维度 d v d_v dv 的value。我们用所有key计算query的点积,每个除以 d k sqrt{d_k} dk ,并应用softmax函数以获得value的权重。

在实践中,我们同时计算attention函数,基于一组query,将它们打包成矩阵 Q Q Q。key和value也打包成矩阵 K K K V V V。我们将输出矩阵计算为:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V)=softmax(frac{QK^T}{sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V
两种最常用的attention函数是加性attention[2]和点积(多重复制)attention。点积attention与我们的算法相同,只是比例因子 d k sqrt{d_k} dk 不同。加性attention使用带有单个隐藏层的前馈网络计算匹配函数。虽然两者在理论复杂性上相似,但由于可以使用高度优化的矩阵乘法代码来实现,因此在实践中,点积注意速度更快,空间效率更高。

而对于较小的 d k d_k dk值,这两种机制的表现类似,对于较大的 d k d_k dk值,加性attention优于点积attention[3]。我们怀疑,对于较大的 d k d_k dk值,点积的增长幅度较大,从而将softmax函数推到梯度非常小的区域 4 ^4 4。为了抵消这一影响,我们通过给点积乘上 1 d k frac{1}{sqrt{d_k}} dk 1

4 ^4 4为了说明点积变大的原因,假设组成元素 q q q k k k的平均值为0,方差为1的独立随机变量。那么它们的点积 q ⋅ k = ∑ i = 1 d k q i k i qcdot k=sum_{i=1}^{d_k} q_ik_i qk=i=1dkqiki的均值为0,方差为 d k d_k dk

3.2.2 Multi-Head Attention

我们发现,不直接使用 d m o d e l d_{model} dmodel 维的query、key和value输入到一个attention函数,而是使用不同的经过学习的线性投影矩阵 h h h 次将query、key和value分别线性投影到 d k 、 d k d_k、d_k dkdk d v d_v dv 维,会更有帮助的。在query、key和value的每个不同投影矩阵上,我们并行执行attention函数,生成 d v d_v dv 维输出值。如图2所示,这些值被连接并再次投影,从而得到最终值。

multi-head attention允许模型共同关注来自不同位置的不同表征子空间的信息。平均操作会抑制这种关注能力,只使用一个attention head的话。
M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 , . . . , h e a d h ) W O MultiHead(Q, K, V ) = Concat(head_1, ..., head_h)W^O MultiHead(Q,K,V)=Concat(head1,...,headh)WO w h e r e h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) wherequad head_i = Attention(QW_i^Q, KW_i^K , VW_i^V) whereheadi=Attention(QWiQ,KWiK,VWiV)
其中投影都是参数矩阵 W i Q ∈ R d m o d e l × d k , W i K ∈ R d m o d e l × d k , W i V ∈ R d m o d e l × d v W_i^Qin R^{d_{model}times d_k},W_i^Kin R^{d_{model}times d_k},W_i^Vin R^{d_{model}times d_v} WiQRdmodel×dk,WiKRdmodel×dk,WiVRdmodel×dv

在这项工作中,我们采用了 h = 8 h=8 h=8个平行的attention层或attention head。对于每一个,我们使用 d k = d v = d m o d e l / h = 64 d_k=d_v=d_{model}/h=64 dk=dv=dmodel/h=64。由于每个头部的维数减小,因此总的计算成本与全维single-head attention的计算成本相似。

3.2.3 Applications of Attention in our Model

Transformer以三种不同的方式使用multi-head attention:

•在“encoder-decoder attention”层中,query来自前一个decoder子层,key和value来自encoder的输出。这允许decoder中的每个位置都关注到输入序列的所有位置。这模仿了sequence-to-sequence模型中典型的encoder-decoder attention机制,如[31,2,8]。

•encoder包含self-attention层。在self-attention层中,所有query、key和value都来自同一个位置,在本例中,是encoder中前一层的输出。encoder中的每个位置都可以关注encoder前一层中的所有位置。

•类似地,decoder中的self-attention层允许decoder中的每个位置关注decoder中直到并包括该位置的所有位置。我们需要防止decoder中的信息向左流动,以保持自回归特性。在scaled dot-product attention中,我们通过mask softmax输入中,所有与非法连接对应的值(设置为 − ∞ infty ),来实现这一点 。参见图2。

3.3 Position-wise Feed-Forward Networks

除了attention的子层之外,还有encoder和decoder,他们的每一层都包含一个全连接的前馈网络,该网络分别相同地应用于每个位置。这包括两个线性变换,中间有一个ReLU激活。
F F N ( x ) = max ⁡ ( 0 , x W 1 + b 1 ) W 2 + b 2 FFN(x) = max(0, xW_1 + b_1)W_2 + b_2 FFN(x)=max(0,xW1+b1)W2+b2
虽然线性变换在不同位置上是相同的,但它们在层与层之间使用不同的参数。另一种描述方法是将其描述为kernel大小为1的两个卷积。输入和输出的维数为 d m o d e l = 512 d_{model}=512 dmodel=512,内层的维数为 d f f = 2048 d_{ff}=2048 dff=2048

3.4 Embeddings and Softmax

与其他序列转录模型类似,我们使用学习到的embedding将输入的token和输出的token转换为维度为 d m o d e l d_{model} dmodel的向量。我们还使用常见的学习权重线性变换和softmax函数将decoder输出转换为预测下一个token的概率。在我们的模型中,我们在两个embedding层和预softmax线性变换之间共享相同的权重矩阵,类似于[24]。在embedding层中,我们将这些权重乘以 d m o d e l sqrt {d_{model}} dmodel

(注:文章[24]将模型的输入embedding和输出embedding tie在一起,即共享一个embedding空间,同一个词在输入embed时和输出embed时会embed成同一个embedding向量。在翻译模型中,如果2种语言的subword共享率非常高,甚至可以实现编码器的输入embedding、解码器的输入embedding和解码器的输出embedding 三者共享。详细见NLP论文:Weight tying 笔记)

3.5 Positional Encoding

由于我们的模型不包含递归和卷积,为了使模型能够利用序列的顺序,我们必须注入一些关于token在序列中的相对或绝对位置的信息。为此,我们在encoder和decoder堆栈底部的输入embedding中添加“位置编码”。位置编码与embedding具有相同的维度 d m o d e l d_{model} dmodel,因此可以将两者相加。有许多位置编码的选择,学习的和固定的[8]。

在这项工作中,我们使用不同频率的正弦和余弦函数:
P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}}) PE(pos,2i)=sin(pos/100002i/dmodel) P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}}) PE(pos,2i+1)=cos(pos/100002i/dmodel)
其中 p o s pos pos是位置, i i i是维度。也就是说,位置编码的每个维度对应一个正弦曲线。波长形成从 2 π 2π 2π 10000 ⋅ 2 π 10000cdot 2π 100002π 的几何级数。我们之所以选择这个函数,是因为我们假设它可以让模型通过相对位置轻松学习如何关注信息,因为对于任何固定偏移量 k k k P E p o s + k PE_{pos+k} PEpos+k可以表示为 P E p o s PE_{pos} PEpos的线性函数。

我们还尝试使用习得的位置嵌入[8],发现这两个版本产生了几乎相同的结果(见表3第(E)行)。我们选择正弦版本,因为它可能允许模型推断出序列长度比训练期间遇到的序列长度更长。
在这里插入图片描述

4 Why Self-Attention

在这里插入图片描述

相关视频

Transformer论文逐段精读【论文精读】
李宏毅-Transformer
Sequence-to-sequence Learning
【李宏毅】Transformer Seq2seq

相关的笔记

相关代码

pytorch

Vaswani等人(2017)以及优秀指南,“注释的 transformer ”
MorvanZhou /NLP-Tutorials:代码中n代表,即包含batch size;step代表输入句子长度,即一句话包含多少个词;model_dim、dim、emb_dim代表word embedding的维度。

tensorflow

作者的代码

keras

pytorch API:

tensorflow API

最后

以上就是甜甜冬日为你收集整理的NLP经典论文:Attention、Self-Attention、Multi-Head Attention、Transformer 笔记论文介绍模型结构文章部分翻译相关视频相关的笔记相关代码pytorch API:tensorflow API的全部内容,希望文章能够帮你解决NLP经典论文:Attention、Self-Attention、Multi-Head Attention、Transformer 笔记论文介绍模型结构文章部分翻译相关视频相关的笔记相关代码pytorch API:tensorflow API所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(227)

评论列表共有 0 条评论

立即
投稿
返回
顶部