Transformer实战-系列教程20:DETR 源码解读7(解码器:TransformerDecoder类/TransformerDecoderLayer类)

Transformer实战-系列教程总目录

有任何问题欢迎在下面留言
本篇文章的代码运行界面均在Pycharm中进行
本篇文章配套的代码资源已经上传
点我下载源码

DETR 算法解读
DETR 源码解读1(项目配置/CocoDetection类/ConvertCocoPolysToMask类)
DETR 源码解读2(DETR类)
DETR 源码解读3(位置编码:Joiner类/PositionEmbeddingSine类)
DETR 源码解读4(BackboneBase类/Backbone类)
DETR 源码解读5(Transformer类)
DETR 源码解读6(编码器:TransformerEncoder类/TransformerEncoderLayer类)
DETR 源码解读7(解码器:TransformerDecoder类/TransformerDecoderLayer类)
DETR 源码解读8(训练函数/损失函数)

12、TransformerDecoderLayer类

位置:models/transformer.py/TransformerDecoderLayer类

12.1 构造函数

class TransformerDecoderLayer(nn.Module):
    def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1, activation="relu", normalize_before=False):
        super().__init__()
        self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
        self.multihead_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.dropout3 = nn.Dropout(dropout)
        self.activation = _get_activation_fn(activation)
        self.normalize_before = normalize_before
  1. 继承PyTorch的nn.module
  2. 构造函数,传入6个参数:
    • d_model:Transformer向量维度
    • nhead:多头注意力头数
    • dim_feedforward:MLP输出维度
    • dropout:dropout比例
    • activation:激活函数
    • normalize_before:根据布尔值,选择进行归一化的地方
  3. 初始化
  4. self_attn ,多头注意力模块(PyTorch内部自带),用于解码器内部
  5. multihead_attn ,多头注意力模块,用于编码器与解码器之间
  6. linear1,MLP的第1层全连接,将维度从d_model映射到dim_feedforward
  7. dropout ,用于MLP第1层的dropout
  8. linear2 ,MLP的第2层全连接,将维度从dim_feedforward映射到d_model
  9. norm1 ,自注意力模块输出的层归一化
  10. norm2 , 交叉注意力模块输出的层归一化
  11. norm3 , MLP输出的层归一化
  12. dropout1 ,自注意力模块输出的dropout
  13. dropout2 ,交叉注意力模块输出的dropout
  14. dropout3 ,MLP输出层的dropout
  15. activation ,激活函数
  16. normalize_before ,根据布尔值,选择进行归一化的地方

12.2 前向传播

    def forward(self, tgt, memory, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None, tgt_key_padding_mask: Optional[Tensor] = None, memory_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None, query_pos: Optional[Tensor] = None):
        if self.normalize_before:
            return self.forward_pre(tgt, memory, tgt_mask, memory_mask, tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos)
        return self.forward_post(tgt, memory, tgt_mask, memory_mask, tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos)
  1. 前向传播函数,传入8个参数:
    • tgt:目标序列
    • memory:编码器输出
    • tgt_mask:目标序列对应的可选的掩码
    • memory_mask:编码器输出对应的可选的掩码
    • tgt_key_padding_mask:目标序列对应的可选的填充掩码
    • memory_key_padding_mask:编码器输出对应的可选的填充掩码
    • pos:编码器输出的可选的位置编码
    • query_pos:目标序列q向量的可选的位置编码
  2. normalize_before的值为True:
  3. 选择forward_pre函数执行前向传播
  4. 否则,选择forward_post函数执行前向传播

二者之间主要区别在于forward_pre函数的层归一化在自注意力和交叉注意力操作之前进行,forward_post函数的层归一化在自注意力和交叉注意力操作之后进行

12.3 forward_post函数

    def forward_post(self, tgt, memory, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None, tgt_key_padding_mask: Optional[Tensor] = None, memory_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None, query_pos: Optional[Tensor] = None):
        q = k = self.with_pos_embed(tgt, query_pos)
        tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,
                              key_padding_mask=tgt_key_padding_mask)[0]
        tgt = tgt + self.dropout1(tgt2)
        tgt = self.norm1(tgt)
        tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt, query_pos), key=self.with_pos_embed(memory, pos), value=memory, attn_mask=memory_mask, key_padding_mask=memory_key_padding_mask)[0]
        tgt = tgt + self.dropout2(tgt2)
        tgt = self.norm2(tgt)
        tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt))))
        tgt = tgt + self.dropout3(tgt2)
        tgt = self.norm3(tgt)
        return tgt
	def with_pos_embed(self, tensor, pos: Optional[Tensor]):
	        return tensor if pos is None else tensor + pos
  1. forward_post函数,传入和forward函数一样的8个参数:
  2. q,k,torch.Size([100, 2, 256]),将位置编码和目标序列相加生成q和k向量,最开始的tgt的元素全部为0
  3. tgt2 ,torch.Size([100, 2, 256]),将Q、K、V进行自注意力的计算得到输出tgt2 ,其中QK是相同的在tgt基础上加入了位置编码,而V和tgt完全相同
  4. tgt ,torch.Size([100, 2, 256]),自注意力的输出经过dropout后和原始tgt 相加,实现残差连接
  5. tgt ,torch.Size([100, 2, 256]),残差连接后的输出经过层归一化
  6. tgt2 ,torch.Size([100, 2, 256]),交叉注意力操作:
    • Q:目标序列(tgt)和对应的位置编码相加得到Q向量
    • K,编码器输出(memory,torch.Size([725, 2, 256]))和对应的位置编码torch.Size([725, 2, 256])相加得到K向量
    • V:编码器输出(memory)
    • Q、K、V经过交叉注意力后的取第一个输出为tgt2
  7. tgt ,torch.Size([100, 2, 256]),交叉注意力的输出经过dropout再加上上一次的层归一化的输出,实现残差连接
  8. tgt ,torch.Size([100, 2, 256]),经过一层层归一化
  9. tgt2 ,torch.Size([100, 2, 256]),此次相当于经过一个MLP,先后经过第1层全连接、激活函数、dropout、第2层全连接
  10. tgt ,torch.Size([100, 2, 256]),再实现一次和前面相同形式的残差连接
  11. tgt ,torch.Size([100, 2, 256]),经过一层层归一化
  12. return

with_pos_embed函数:
如果位置编码pos存在,则加上位置编码

12.4 forward_pre函数

    def forward_pre(self, tgt, memory,
                    tgt_mask: Optional[Tensor] = None,
                    memory_mask: Optional[Tensor] = None,
                    tgt_key_padding_mask: Optional[Tensor] = None,
                    memory_key_padding_mask: Optional[Tensor] = None,
                    pos: Optional[Tensor] = None,
                    query_pos: Optional[Tensor] = None):
        tgt2 = self.norm1(tgt)
        q = k = self.with_pos_embed(tgt2, query_pos)
        tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask,
                              key_padding_mask=tgt_key_padding_mask)[0]
        tgt = tgt + self.dropout1(tgt2)
        tgt2 = self.norm2(tgt)
        tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt2, query_pos),
                                   key=self.with_pos_embed(memory, pos),
                                   value=memory, attn_mask=memory_mask,
                                   key_padding_mask=memory_key_padding_mask)[0]
        tgt = tgt + self.dropout2(tgt2)
        tgt2 = self.norm3(tgt)
        tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2))))
        tgt = tgt + self.dropout3(tgt2)
        return tgt

和forward_post函数基本相同,不同的地方在于层归一化的顺序不同,而且这两个函数每次也只会选择一个执行

13、TransformerDecoder类

位置:models/transformer.py/TransformerDecoder类

该类用来构建整体的解码器,主要是进行解码器层的堆叠,单个的解码器由前面的TransformerDecoderLayer类实现。

13.1 构造函数

class TransformerDecoder(nn.Module):
    def __init__(self, decoder_layer, num_layers, norm=None, return_intermediate=False):
        super().__init__()
        self.layers = _get_clones(decoder_layer, num_layers)
        self.num_layers = num_layers
        self.norm = norm
        self.return_intermediate = return_intermediate
  1. 继承PyTorch的nn.Module
  2. 构造函数,传入4个参数:
    • decoder_layer:单个解码器层的实例
    • num_layers:解码器层堆叠的数量
    • norm:所有解码器层堆叠后执行的层归一化,可选
    • return_intermediate:是否返回每一层解码器的输出,如果返回的每一层解码器的输出都会去做损失,这样做可以得到更好的结果
  3. 初始化
  4. layers ,使用辅助函数_get_clones结合单个解码器层的实例和解码器层堆叠的数量,生成一个包含这些克隆层的列表,并将其赋值给self.layers。这样,解码器就由多个相同配置的解码器层组成
  5. num_layers
  6. norm
  7. return_intermediate

13.2 前向传播

    def forward(self, tgt, memory,
                tgt_mask: Optional[Tensor] = None,
                memory_mask: Optional[Tensor] = None,
                tgt_key_padding_mask: Optional[Tensor] = None,
                memory_key_padding_mask: Optional[Tensor] = None,
                pos: Optional[Tensor] = None,
                query_pos: Optional[Tensor] = None):
        output = tgt
        intermediate = []
        for layer in self.layers:
            output = layer(output, memory, tgt_mask=tgt_mask,
                           memory_mask=memory_mask,
                           tgt_key_padding_mask=tgt_key_padding_mask,
                           memory_key_padding_mask=memory_key_padding_mask,
                           pos=pos, query_pos=query_pos)
            if self.return_intermediate:
                intermediate.append(self.norm(output))
        if self.norm is not None:
            output = self.norm(output)
            if self.return_intermediate:
                intermediate.pop()
                intermediate.append(output)
        if self.return_intermediate:
            return torch.stack(intermediate)
        return output.unsqueeze(0)
  1. 前向传播函数,传入8参数,这些参数的含义在12、TransformerDecoderLayer类都已解释
  2. output ,torch.Size([100, 2, 256])
  3. intermediate ,存储每个单层解码器层的输出
  4. 循环遍历所有解码器层
  5. output ,torch.Size([100, 2, 256]),执行一次单层解码器层得到输出,每一层的输出都会被用作下一层的输入
  6. return_intermediate,如果为True,即启用了中间输出收集
  7. 将当前层的输出output经过归一化后添加到intermediate列表中
  8. 最后的一层解码器层的输出如果有归一化模块:
  9. 对最后的一层解码器层的输出执行层归一化
  10. 如果启用了中间输出收集
  11. 移除最后一个未经归一化处理的输出
  12. 添加经过归一化的最终输出
  13. 如果启用了中间输出收集
  14. 将intermediate列表中的所有Tensor沿着新的维度堆叠起来,形成一个新的Tensor,新的维度可以保留每一层输出的独立性
  15. 如果没有,返回最终的输出output并增加一层新的维度,这是为了不管是否启用了中间输出收集,最后返回的格式都保持一致

13.3 _get_clones函数

def _get_clones(module, N):
    return nn.ModuleList([copy.deepcopy(module) for i in range(N)])

这个辅助函数_get_clones创建给定模块(TransformerEncoderLayer、TransformerDecoderLayer)的N个深度拷贝,并将这些拷贝组织为一个nn.ModuleListnn.ModuleList是PyTorch中的一个容器模块,用于存储一系列子模块

因此DETR总的架构为,TransformerEncoderLayer、TransformerDecoderLayer分别堆叠形成了TransformerEncoder和TransformerDecoder,这两个构成了整个的Transformer,Transformer再结合之前的backbone即项目的总体模型DETR

DETR 算法解读
DETR 源码解读1(项目配置/CocoDetection类/ConvertCocoPolysToMask类)
DETR 源码解读2(DETR类)
DETR 源码解读3(位置编码:Joiner类/PositionEmbeddingSine类)
DETR 源码解读4(BackboneBase类/Backbone类)
DETR 源码解读5(Transformer类)
DETR 源码解读6(编码器:TransformerEncoder类/TransformerEncoderLayer类)
DETR 源码解读7(解码器:TransformerDecoder类/TransformerDecoderLayer类)
DETR 源码解读8(训练函数/损失函数)

你可能感兴趣的:(Transformer实战,transformer,深度学习,计算机视觉,DETR,人工智能,物体检测)