[YOLOv7/YOLOv5系列算法改进NO.21]CNN+Transformer——主干网络替换为又快又强的轻量化主干EfficientFormer

 ​前 言:作为当前先进的深度学习目标检测算法YOLOv5,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列文章,将重点对YOLOv5的如何改进进行详细的介绍,目的是为了给那些搞科研的同学需要创新点或者搞工程项目的朋友需要达到更好的效果提供自己的微薄帮助和参考。

解决问题:YOLOv5主干特征提取网络采用C3结构,带来较大的参数量,检测速度较慢,应用受限,在某些真实的应用场景如移动或者嵌入式设备,如此大而复杂的模型时难以被应用的。首先是模型过于庞大,面临着内存不足的问题,其次这些场景要求低延迟,或者说响应速度要快,想象一下自动驾驶汽车的行人检测系统如果速度很慢会发生什么可怕的事情。所以,研究小而高效的CNN模型在这些场景至关重要,至少目前是这样,尽管未来硬件也会越来越快。另外,YOLOv5为卷积神经网络缺乏全局建模的能力,Transformer具有获取全局信息的能力,前面已经引入多头注意力机制进行改进。参考改进之十七。在移动设备等资源受限的硬件上,这种改进不便于轻量化部署。本文尝试引入最新Transformer网络EfficientFormer,既能提升速度又能克服全卷积网络的缺陷。

YOLOv5改进之十七:CNN+Transformer——融合Bottleneck Transformers_人工智能算法研究院的博客-CSDN博客

YOLOv5改进之十六:主干网络C3替换为轻量化网络PP-LCNet_人工智能算法研究院的博客-CSDN博客
YOLOv5改进之十一:主干网络C3替换为轻量化网络MobileNetV3_人工智能算法工程师0301的博客-CSDN博客https://blog.csdn.net/m0_70388905/article/details/125593267

原理:

EfficientFormer:  Vision Transformers at MobileNet Speed

论文:https://arxiv.org/abs/2206.01191

代码:https://github.com/hkzhang91/EdgeFormer

   Vision Transformers (ViT) 在计算机视觉任务中取得了快速进展,在各种基准测试中取得了可喜的成果。然而,由于大量的参数和模型设计,例如注意力机制,基于 ViT 的模型通常比轻量级卷积网络慢几倍。因此,应用部署 ViT 具有很大的挑战性,尤其是在移动设备等资源受限的硬件上。

   最近的很多工作都试图通过网络架构搜索或与 MobileNet Block 的混合设计来降低 ViT 的计算复杂度,但推理速度仍然不能令人满意。这就引出了一个重要的问题:Transformer 能否在获得高性能的同时运行得像 MobileNet 一样快?

    为了回答这个问题,首先重新审视基于 ViT 的模型中使用的网络架构和 ViT 算子,并确定其低效的设计。然后引入了一个维度一致的纯 Transformer (没有 MobileNet Block)作为设计范式。最后,执行延迟驱动的瘦身以获得一系列最终模型,称为 EfficientFormer

大量实验表明 EfficientFormer 在移动设备上的性能和速度方面具有优势。EfficientFormer-L1 在 ImageNet-1K 上实现了 79.2% 的 Top-1 准确率,在 iPhone 12(使用 CoreML 编译)上只有 1.6 ms 的推理延迟,这甚至比 MobileNetV2(1.7 ms,71.8% Top-1),EfficientFormer-L7 获得了 83.3% 的准确率,延迟仅为 7.0 ms。EfficientFormer证明,正确设计的 Transformer 可以在移动设备上达到极低的延迟,同时保持高性能。

项目部分代码如下:

import torch
from torch import nn, Tensor
from typing import Tuple, Optional
from sys import platform

from .base_layer import BaseLayer
from .linear_layer import LinearLayer
from .dropout import Dropout
from ..misc.profiler import module_profile


class MultiHeadAttention(BaseLayer):
    '''
            This layer applies a multi-head attention as described in "Attention is all you need" paper
            https://arxiv.org/abs/1706.03762
    '''
    def __init__(self, embed_dim: int, num_heads: int, attn_dropout: Optional[float] =0.0, 
                 bias: Optional[bool] = True,
                 *args, **kwargs):
        """
        :param embed_dim: Embedding dimension
        :param num_heads: Number of attention heads
        :param attn_dropout: Attention dropout
        :param bias: Bias
        """
        super(MultiHeadAttention, self).__init__()
        assert embed_dim % num_heads == 0, "Got: embed_dim={} and num_heads={}".format(embed_dim, num_heads)

        self.qkv_proj = LinearLayer(in_features=embed_dim, out_features=3*embed_dim, bias=bias)

        self.attn_dropout = Dropout(p=attn_dropout)
        self.out_proj = LinearLayer(in_features=embed_dim, out_features=embed_dim, bias=bias)

        self.head_dim = embed_dim // num_heads
        self.scaling = self.head_dim ** -0.5
        self.softmax = nn.Softmax(dim=-1)
        self.num_heads = num_heads
        self.embed_dim = embed_dim

        self.mac_device = False
        if platform == "darwin":
            self.mac_device = True

    def forward_mac_device(self, x: Tensor) -> Tensor:
        # [B x N x C]
        qkv = self.qkv_proj(x)

        query, key, value = torch.chunk(qkv, chunks=3, dim=-1)

        query = query * self.scaling

        # [B x N x C] --> [B x N x c] x h
        query = torch.chunk(query, chunks=self.num_heads, dim=-1)
        value = torch.chunk(value, chunks=self.num_heads, dim=-1)
        key = torch.chunk(key, chunks=self.num_heads, dim=-1)

        wt_out = []
        for h in range(self.num_heads):
            attn_h = torch.bmm(query[h], key[h].transpose(1, 2))
            attn_h = self.softmax(attn_h)
            attn_h = self.attn_dropout(attn_h)
            out_h = torch.bmm(attn_h, value[h])
            wt_out.append(out_h)

        wt_out = torch.cat(wt_out, dim=-1)
        wt_out = self.out_proj(wt_out)
        return wt_out

    def forward_other(self, x: Tensor) -> Tensor:
        # [B x N x C]
        b_sz, n_patches, in_channels = x.shape

        # [B x N x C] --> [B x N x 3 x h x C]
        qkv = (
            self.qkv_proj(x)
                .reshape(b_sz, n_patches, 3, self.num_heads, -1)
        )
        # [B x N x 3 x h x C] --> [B x h x 3 x N x C]
        qkv = qkv.transpose(1, 3)

        # [B x h x 3 x N x C] --> [B x h x N x C] x 3
        query, key, value = qkv[:, :, 0], qkv[:, :, 1], qkv[:, :, 2]

        query = query * self.scaling

        # [B x h x N x C] --> [B x h x c x N]
        key = key.transpose(2, 3)

        # QK^T
        # [B x h x N x c] x [B x h x c x N] --> [B x h x N x N]
        attn = torch.matmul(query, key)
        attn = self.softmax(attn)
        attn = self.attn_dropout(attn)

        # weighted sum
        # [B x h x N x N] x [B x h x N x c] --> [B x h x N x c]
        out = torch.matmul(attn, value)

        # [B x h x N x c] --> [B x N x h x c] --> [B x N x C=ch]
        out = out.transpose(1, 2).reshape(b_sz, n_patches, -1)
        out = self.out_proj(out)

        return out

    def forward(self, x: Tensor) -> Tensor:
        if self.mac_device:
            return self.forward_mac_device(x)
        else:
            return self.forward_other(x)

    def profile_module(self, input) -> (Tensor, float, float):
        b_sz, seq_len, in_channels = input.shape
        params = macs = 0.0

        qkv, p, m = module_profile(module=self.qkv_proj, x=input)
        params += p
        macs += (m * seq_len * b_sz)

        # number of operations in QK^T
        m_qk = (seq_len * in_channels * in_channels) * b_sz
        macs += m_qk

        # number of operations in computing weighted sum
        m_wt = (seq_len * in_channels * in_channels) * b_sz
        macs += m_wt

        out_p, p, m = module_profile(module=self.out_proj, x=input)
        params += p
        macs += (m * seq_len * b_sz)

        return input, params, macs

结 果:本人在多个数据集上做了大量实验,针对不同的数据集效果不同,map值有所下降,但是权值模型大小降低,参数量下降。

预告一下:下一篇内容将继续分享深度学习算法相关改进方法。有兴趣的朋友可以关注一下我,有问题可以留言或者私聊我哦

PS:主干网络的替换不仅仅是适用改进YOLOv5,也可以改进其他的YOLO网络以及目标检测网络,比如YOLOv4、v3等。平台回复不及时欢迎添加微信公众号:人工智能AI算法工程师 。  

最后,希望能互粉一下,做个朋友,一起学习交流。


版权声明:本文为m0_70388905原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。