Transformer常见面试问题

关于Transformer的代码,直接就可以运行的代码,可以看看这个:

https://github.com/wenjtop/transformer​github.com/wenjtop/transformer

wenjtop:【Transformer】10分钟学会Transformer | Pytorch代码讲解 | 代码可运行597 赞同 · 216 评论文章正在上传…重新上传取消

1. 描述一下Transformer的基本结构吧。

基本结构包括Encoder部分以及Decoder部分,其中Encoder部分的结构包括一个多头注意力网络,以及一个普通的线性层网络,另外Decoder部分的结构包括三个部分,分别是Self Attention,Cross Attention,以及普通的前向传播网络。需要注意的是,每个子网络都有普通的这种残差网络结构,为了维护网络的稳定性。

2. Decoder 与 Attention的关系

2.1 Decoder 中 Cross Attention 的QKV分别来自于哪里?

Q来自于Decoder,KV都来自于Encoder的输出结果。Q意味着query,也即是需要查询的变量,所以这个部分在解码的时候会这样子做。本质上的意义是,针对于之前的编码器,在解码的部分一点一点的解码,所以Q也就是类似于信号的作用,提取出关键的信息。

另外需要注意的是,KV都是统一来源,并非来自Encoder部分的Attention中的KV矩阵,而是来自相同的输入,也即Encoder的输出结果。

2.2 Decoder 中不同的 Layer 和 Encoder 中不同的Layer有什么关系?是一一对应的关系吗?

并没有一一对应的关系,一一对应指的是Encoder中的每一层的输出,都对应Decoder中的一个输入。实际上Decoder的每一层的输入,都含有Encoder最终的输出结果。详见下图中的红色曲线即可。

3. Attention 的代该怎么写呢?

class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        self.W_Q = nn.Linear(d_model, d_k * n_heads, bias=False)
        self.W_K = nn.Linear(d_model, d_k * n_heads, bias=False)
        self.W_V = nn.Linear(d_model, d_v * n_heads, bias=False)
        self.fc = nn.Linear(n_heads * d_v, d_model, bias=False)

    def forward(self, input_Q, input_K, input_V, attn_mask):    # input_Q: [batch_size, len_q, d_model]
                                                                # input_K: [batch_size, len_k, d_model]
                                                                # input_V: [batch_size, len_v(=len_k), d_model]
                                                                # attn_mask: [batch_size, seq_len, seq_len]
        residual, batch_size = input_Q, input_Q.size(0)         # 先摊平了,然后再用头给拆分开
        Q = self.W_Q(input_Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2)    # Q: [batch_size, n_heads, len_q, d_k]
        K = self.W_K(input_K).view(batch_size, -1, n_heads, d_k).transpose(1, 2)    # K: [batch_size, n_heads, len_k, d_k]
        V = self.W_V(input_V).view(batch_size, -1, n_heads, d_v).transpose(1,
                                                                           2)       # V: [batch_size, n_heads, len_v(=len_k), d_v]
        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1,
                                                  1)                                # attn_mask : [batch_size, n_heads, seq_len, seq_len]
        context, attn = ScaledDotProductAttention()(Q, K, V, attn_mask)             # context: [batch_size, n_heads, len_q, d_v]
                                                                                    # attn: [batch_size, n_heads, len_q, len_k]
        context = context.transpose(1, 2).reshape(batch_size, -1,
                                                  n_heads * d_v)                    # context: [batch_size, len_q, n_heads * d_v]
        output = self.fc(context)                                                   # [batch_size, len_q, d_model]
        return nn.LayerNorm(d_model)(output + residual), attn

3.1 QKV三个矩阵为什么不需要bias?

原有的论文就是这么写的,如果加了的话,学到的偏执信息可能影响了转换结果的表达。

3.2 多头注意力是如何实现的?是分片吗?

代码的实现是这样的,有统一的W参数矩阵,获取到QKV之后,变换shape,类似于直接分片的表达。

4. Position Wise Feed Forward Network

4.1 为什么叫这个名字呢?

文中的描述指的是卷积为1的CNN结构,实现的时候利用FFN来实现的。使用内核大小为1的两个卷积。输入和输出的维度为dmodel=512,内部层的维度为dff=2048。所以能够体现出,在dimension中,不同的位置计算的方式不同,和卷积的原理类似,所以中文名字才叫做 位置感知前馈神经网络。

4.2 构成是什么

简单的两个线性层。加两个激活函数即可。

class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        self.fc = nn.Sequential(
            nn.Linear(d_model, d_ff, bias=False),
            nn.ReLU(),
            nn.Linear(d_ff, d_model, bias=False))

    def forward(self, inputs):                                  # inputs: [batch_size, seq_len, d_model]
        residual = inputs
        output = self.fc(inputs)
        return nn.LayerNorm(d_model)(output + residual)  # [batch_size, seq_len, d_model]

5. Decoder 部分Attention 网络

5.1 总共有几个Attention,为什么?

Decoder部分总共有两个Attention网络,第一个是self-attention,而第二个是cross-attention,两个的作用是不太一样的,第一个指的是把当前的输入全部进行attention机制的学习,找到权重来代表当前的输入,第二个是针对于解码的部分,通过Mask来实现逐个token的预测。下面我们来详细讲解下代码部分。

# 单个Decoder层的网络
class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.dec_self_attn = MultiHeadAttention()
        self.dec_enc_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask,
                dec_enc_attn_mask):                                             # dec_inputs: [batch_size, tgt_len, d_model]
                                                                                # enc_outputs: [batch_size, src_len, d_model]
                                                                                # dec_self_attn_mask: [batch_size, tgt_len, tgt_len]
                                                                                # dec_enc_attn_mask: [batch_size, tgt_len, src_len]
        dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs,
                                                        dec_inputs,
                                                        dec_self_attn_mask)     # dec_outputs: [batch_size, tgt_len, d_model]
                                                                                # dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len]
        dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, # Q自于Decoder,K和V来自于Encoder里面即可,Query为查询向量
                                                      enc_outputs,
                                                      dec_enc_attn_mask)        # dec_outputs: [batch_size, tgt_len, d_model]
                                                                                # dec_enc_attn: [batch_size, h_heads, tgt_len, src_len]
        dec_outputs = self.pos_ffn(dec_outputs)                                 # dec_outputs: [batch_size, tgt_len, d_model]
        return dec_outputs, dec_self_attn, dec_enc_attn

# Decoder的整个网络
class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)
        self.pos_emb = PositionalEncoding(d_model)
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])

    def forward(self, dec_inputs, enc_inputs, enc_outputs):                         # dec_inputs: [batch_size, tgt_len]
                                                                                    # enc_intpus: [batch_size, src_len]
                                                                                    # enc_outputs: [batsh_size, src_len, d_model]
        dec_outputs = self.tgt_emb(dec_inputs)                                      # [batch_size, tgt_len, d_model]
        dec_outputs = self.pos_emb(dec_outputs)                              # [batch_size, tgt_len, d_model]
        dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)   # [batch_size, tgt_len, tgt_len]
        dec_self_attn_subsequence_mask = get_attn_subsequence_mask(dec_inputs)  # [batch_size, tgt_len, tgt_len]
        dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask +
                                       dec_self_attn_subsequence_mask), 0)   # [batch_size, tgt_len, tgt_len]
        dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)               # [batc_size, tgt_len, src_len]
        dec_self_attns, dec_enc_attns = [], []
        for layer in self.layers:                                                   # dec_outputs: [batch_size, tgt_len, d_model]
                                                                                    # dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len]
                                                                                    # dec_enc_attn: [batch_size, h_heads, tgt_len, src_len]
            dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask,
                                                             dec_enc_attn_mask)
            dec_self_attns.append(dec_self_attn)
            dec_enc_attns.append(dec_enc_attn)
        return dec_outputs, dec_self_attns, dec_enc_attns

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值