一、本文介绍
本文给大家带来的最新改进机制是Rewrite the Stars ,其 探讨了“星操作”(即元素级的乘法)在网络设计中的潜力。文章提出,星操作能够将输入映射到高维的非线性特征空间中,而不需要增加网络的宽度。这个过程类似于 机器学习 中的核技巧,但通过保持网络的紧凑性和低延迟实现了 高效的 计算。本文将其添加到YOLOv11中并且根据yolov11的N、S、M、L、X进行缩放和扩张, yolov11全系列可实现轻量化。
二、原理介绍
官方论文地址: 官方论文地址点击此处即可跳转
官方代码地址: 官方代码地址点击此处即可跳转
以下是文章《Rewrite the Stars》的详细每一部分的内容总结:
1. 引言 (Introduction)
- 学习方法的演变 :文章首先简要回顾了深度学习方法的演变。从AlexNet开始,深度学习模型经历了快速的发展,尤其是在计算机视觉和自然语言处理领域。尽管这些网络的设计各具特色,但大多数模型仍然基于线性投影(如卷积和线性层)和非线性激活的结合。
- 自注意力机制的引入 :文章提到,近年来自注意力机制(如Transformer)逐渐成为主流,先是在自然语言处理领域取得突破,随后在计算机视觉中也得到了广泛应用。
- 提出星操作 :在这一背景下,文章引入了“星操作”这一概念,即通过元素级乘法将特征进行融合,达到提高网络表示能力的目的。星操作的特点是能通过简单的操作在低维空间中实现高维的非线性映射。
2. 星操作 (Star Operation)
-
概述 :星操作是指将两个输入特征通过元素级的乘法(即逐元素相乘)进行融合,从而在低维输入空间中实现高维的非线性特征映射。文章解释了星操作在本质上与传统机器学习中的核技巧相似,但它在 神经网络 中表现得更加高效。
-
数学表达式 :文章介绍了如何通过数学公式定义星操作,并通过公式推导星操作如何实现特征空间的扩展。具体地,在单层网络中,星操作可以将两个输入信号融合成一个新的特征表示,从而在不增加额外计算复杂度的情况下实现特征维度的提升。
-
高维特征空间 :星操作的核心优势是它能够实现高维、非线性的特征映射,同时避免了传统方法中需要显著增加网络层数或计算量的局限性。文章通过实验验证了这种操作能够大幅提升网络表示能力。
3. StarNet 模型
-
StarNet 介绍 :StarNet是文章提出的一个简单但强大的神经网络原型模型。它将星操作应用到多个网络层中,从而提升网络的表达能力,同时保持网络结构的紧凑性和低延迟。StarNet的设计目标是展示星操作如何在低成本的预算下实现高效的性能。
-
网络结构 :StarNet的网络结构设计相对简单,使用了标准的卷积层和激活函数。通过在这些层中集成星操作,StarNet能够在维度扩展的同时保持计算效率,并且在多个标准数据集上表现出色。
-
实验结果 :文章通过大量实验验证了StarNet的有效性。实验结果表明,StarNet能够在低延迟和小型网络结构下提供优异的性能,远超一些传统的深度网络设计。
4. 多层星操作 (Multi-layer Star Operation)
-
多层应用 :文章进一步探讨了将星操作应用到多层网络中的情况。通过堆叠多个网络层,星操作能够在每一层显著提升特征的维度,从而实现接近于无限维度的特征表示。文章通过数学推导证明了随着层数的增加,星操作能够极大地提高网络的表达能力。
-
递归性质 :文章指出,星操作在多个层次堆叠后具有递归性质,每增加一层就能大幅度增加特征的隐式维度。例如,在一个10层的网络中,星操作能够将特征空间扩展到接近无限维度。
-
性能提升 :通过多层堆叠,StarNet模型能够在每一层提升特征空间的维度,进而提高整个网络的分类准确度和性能。文章通过实验展示了多层星操作带来的显著性能提升。
5. 特殊情况 (Special Cases)
-
非线性分支 :文章讨论了星操作在一些特殊情况下的表现。例如,某些网络(如VAN和SENet)可能会在其中某些分支中使用身份分支或不进行可学习的转换。在这些情况下,星操作仍然能够保持良好的性能。
-
非线性变换 :在一些实际应用中,星操作的变换函数可能会加入非线性激活函数,如ReLU或GELU。这些变化不会显著改变星操作在多个层次中的维度扩展效果,文章对这些特殊情况进行了分析。
6. 实验验证 (Empirical Validation)
-
实验证明 :文章通过一系列实验验证了星操作在不同网络架构中的优势。实验表明,在多个标准任务上,StarNet模型都能够在较低的计算复杂度下达到更高的准确度,展示了星操作在高效网络设计中的巨大潜力。
-
性能对比 :文章与传统方法进行了对比实验,展示了使用星操作的网络相比于传统方法在计算效率和模型性能方面的显著优势。例如,在ImageNet分类任务中,StarNet能够在较小的模型体积下实现更高的准确率。
7. 结论 (Conclusion)
-
文章总结了星操作的独特优势,并呼吁在更多的 深度学习 任务中探索这一操作的应用。星操作通过低维度的输入、高效的计算方式、以及非线性的特征映射,提供了一种全新的网络设计思路。未来,星操作有望在多个领域中发挥更大的作用,尤其是在资源有限或计算要求较高的任务中。
三、核心代码
核心代码的使用方式看章节四!
- """
- Implementation of Prof-of-Concept Network: StarNet.
- We make StarNet as simple as possible [to show the key contribution of element-wise multiplication]:
- - like NO layer-scale in network design,
- - and NO EMA during training,
- - which would improve the performance further.
- Created by: Xu Ma (Email: ma.xu1@northeastern.edu)
- Modified Date: Mar/29/2024
- """
- import torch
- import torch.nn as nn
- from timm.models.layers import DropPath, trunc_normal_
- __all__ = ['starnet_s1', 'starnet_s2', 'starnet_s3', 'starnet_s4', 'starnet_s050', 'starnet_s100', 'starnet_s150']
- model_urls = {
- "starnet_s1": "https://github.com/ma-xu/Rewrite-the-Stars/releases/download/checkpoints_v1/starnet_s1.pth.tar",
- "starnet_s2": "https://github.com/ma-xu/Rewrite-the-Stars/releases/download/checkpoints_v1/starnet_s2.pth.tar",
- "starnet_s3": "https://github.com/ma-xu/Rewrite-the-Stars/releases/download/checkpoints_v1/starnet_s3.pth.tar",
- "starnet_s4": "https://github.com/ma-xu/Rewrite-the-Stars/releases/download/checkpoints_v1/starnet_s4.pth.tar",
- }
- class ConvBN(torch.nn.Sequential):
- def __init__(self, in_planes, out_planes, kernel_size=1, stride=1, padding=0, dilation=1, groups=1, with_bn=True):
- super().__init__()
- self.add_module('conv', torch.nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, dilation, groups))
- if with_bn:
- self.add_module('bn', torch.nn.BatchNorm2d(out_planes))
- torch.nn.init.constant_(self.bn.weight, 1)
- torch.nn.init.constant_(self.bn.bias, 0)
- class Block(nn.Module):
- def __init__(self, dim, mlp_ratio=3, drop_path=0.):
- super().__init__()
- self.dwconv = ConvBN(dim, dim, 7, 1, (7 - 1) // 2, groups=dim, with_bn=True)
- self.f1 = ConvBN(dim, mlp_ratio * dim, 1, with_bn=False)
- self.f2 = ConvBN(dim, mlp_ratio * dim, 1, with_bn=False)
- self.g = ConvBN(mlp_ratio * dim, dim, 1, with_bn=True)
- self.dwconv2 = ConvBN(dim, dim, 7, 1, (7 - 1) // 2, groups=dim, with_bn=False)
- self.act = nn.ReLU6()
- self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
- def forward(self, x):
- input = x
- x = self.dwconv(x)
- x1, x2 = self.f1(x), self.f2(x)
- x = self.act(x1) * x2
- x = self.dwconv2(self.g(x))
- x = input + self.drop_path(x)
- return x
- def _make_divisible(v, divisor, min_value=None):
- """
- This function is taken from the original tf repo.
- It ensures that all layers have a channel number that is divisible by 8
- It can be seen here:
- https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
- :param v:
- :param divisor:
- :param min_value:
- :return:
- """
- if min_value is None:
- min_value = divisor
- new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
- # Make sure that round down does not go down by more than 10%.
- if new_v < 0.9 * v:
- new_v += divisor
- return new_v
- class StarNet(nn.Module):
- def __init__(self, depth=0.25, width=0.5, base_dim=32, depths=[3, 3, 12, 5], mlp_ratio=4, drop_path_rate=0.0, num_classes=1000, **kwargs):
- super().__init__()
- self.num_classes = num_classes
- base_dim = _make_divisible(int(base_dim * width), 8)
- self.in_channel = _make_divisible(int(32 * width), 8)
- # self.in_channel = 32
- # stem layer
- self.stem = nn.Sequential(ConvBN(3, self.in_channel, kernel_size=3, stride=2, padding=1), nn.ReLU6())
- dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] # stochastic depth
- # build stages
- self.stages = nn.ModuleList()
- cur = 0
- for i_layer in range(len(depths)):
- embed_dim = base_dim * 2 ** i_layer
- down_sampler = ConvBN(self.in_channel, embed_dim, 3, 2, 1)
- self.in_channel = embed_dim
- blocks = [Block(self.in_channel, mlp_ratio, dpr[cur + i]) for i in range(depths[i_layer])]
- cur += depths[i_layer]
- self.stages.append(nn.Sequential(down_sampler, *blocks))
- # head
- # self.norm = nn.BatchNorm2d(self.in_channel)
- # self.avgpool = nn.AdaptiveAvgPool2d(1)
- # self.head = nn.Linear(self.in_channel, num_classes)
- self.apply(self._init_weights)
- self.width_list = [i.size(1) for i in self.forward(torch.randn(1, 3, 640, 640))]
- def _init_weights(self, m):
- if isinstance(m, nn.Linear or nn.Conv2d):
- trunc_normal_(m.weight, std=.02)
- if isinstance(m, nn.Linear) and m.bias is not None:
- nn.init.constant_(m.bias, 0)
- elif isinstance(m, nn.LayerNorm or nn.BatchNorm2d):
- nn.init.constant_(m.bias, 0)
- nn.init.constant_(m.weight, 1.0)
- def forward(self, x):
- x = self.stem(x)
- unique_tensors = {}
- for stage in self.stages:
- x = stage(x)
- width, height = x.shape[2], x.shape[3]
- unique_tensors[(width, height)] = x
- result_list = list(unique_tensors.values())[-4:]
- return result_list
- # x = torch.flatten(self.avgpool(self.norm(x)), 1)
- # return self.head(x)
- def starnet_s1(depth=0.5, width=0.25, pretrained=False, **kwargs):
- model = StarNet(depth=depth, width=width, base_dim=24, depths=[2, 2, 8, 3], **kwargs)
- if pretrained:
- url = model_urls['starnet_s1']
- checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu")
- model.load_state_dict(checkpoint["state_dict"])
- return model
- def starnet_s2(depth=0.5, width=0.25, pretrained=False, **kwargs):
- model = StarNet(depth=depth, width=width, base_dim=32, depths=[1, 2, 6, 2], **kwargs)
- if pretrained:
- url = model_urls['starnet_s2']
- checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu")
- model.load_state_dict(checkpoint["state_dict"])
- return model
- def starnet_s3(depth=0.5, width=0.25, pretrained=False, **kwargs):
- model = StarNet(depth=depth, width=width, base_dim=32, depths=[2, 2, 8, 4], **kwargs)
- if pretrained:
- url = model_urls['starnet_s3']
- checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu")
- model.load_state_dict(checkpoint["state_dict"])
- return model
- def starnet_s4(depth=0.5, width=0.25, pretrained=False, **kwargs):
- model = StarNet(depth=depth, width=width, base_dim=32, depths=[3, 3, 12, 5], **kwargs)
- if pretrained:
- url = model_urls['starnet_s4']
- checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu")
- model.load_state_dict(checkpoint["state_dict"])
- return model
- def starnet_s050(depth=0.5, width=0.25, pretrained=False, **kwargs):
- return StarNet(depth=depth, width=width, base_dim=16, depths=[1, 1, 3, 1], mlp_ratio=3, **kwargs)
- def starnet_s100(depth=0.5, width=0.25, pretrained=False, **kwargs):
- return StarNet(depth=depth, width=width, base_dim=20, depths=[1, 2, 4, 1], mlp_ratio=4, **kwargs)
- def starnet_s150(depth=0.5, width=0.25, pretrained=False, **kwargs):
- return StarNet(depth=depth, width=width, base_dim=24, depths=[1, 2, 4, 2], mlp_ratio=3, **kwargs)
- if __name__ == "__main__":
- model = starnet_s1()
- inputs = torch.randn((1, 3, 640, 640))
- for i in model(inputs):
- print(i.size())
四、本文机制添加教程
4.1 修改一
第一步还是建立文件,我们找到如下ultralytics/nn文件夹下建立一个目录名字呢就是'Addmodules'文件夹( 用群内的文件的话已经有了无需新建) !然后在其内部建立一个新的py文件将核心代码复制粘贴进去即可
4.2 修改二
第二步我们在该目录下创建一个新的py文件名字为'__init__.py'( 用群内的文件的话已经有了无需新建) ,然后在其内部导入我们的检测头如下图所示。
4.3 修改三
第三步我门中到如下文件'ultralytics/nn/tasks.py'进行导入和注册我们的模块( 用群内的文件的话已经有了无需重新导入直接开始第四步即可) !
从今天开始以后的教程就都统一成这个样子了,因为我默认大家用了我群内的文件来进行修改!!
4.4 修改四
添加如下两行代码!!!
4.5 修改五
找到七百多行大概把具体看图片,按照图片来修改就行,添加红框内的部分,注意没有()只是函数名。
- elif m in {自行添加对应的模型即可,下面都是一样的}:
- m = m(*args)
- c2 = m.width_list # 返回通道列表
- backbone = True
4.6 修改六
下面的两个红框内都是需要改动的。
- if isinstance(c2, list):
- m_ = m
- m_.backbone = True
- else:
- m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
- t = str(m)[8:-2].replace('__main__.', '') # module type
- m.np = sum(x.numel() for x in m_.parameters()) # number params
- m_.i, m_.f, m_.type = i + 4 if backbone else i, f, t # attach index, 'from' index, type
4.7 修改七
如下的也需要修改,全部按照我的来。
代码如下把原先的代码替换了即可。
- if verbose:
- LOGGER.info(f'{i:>3}{str(f):>20}{n_:>3}{m.np:10.0f} {t:<45}{str(args):<30}') # print
- save.extend(x % (i + 4 if backbone else i) for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
- layers.append(m_)
- if i == 0:
- ch = []
- if isinstance(c2, list):
- ch.extend(c2)
- if len(c2) != 5:
- ch.insert(0, 0)
- else:
- ch.append(c2)
4.8 修改八
修改八和前面的都不太一样,需要修改前向传播中的一个部分, 已经离开了parse_model方法了。
可以在图片中开代码行数,没有离开task.py文件都是同一个文件。 同时这个部分有好几个前向传播都很相似,大家不要看错了, 是70多行左右的!!!,同时我后面提供了代码,大家直接复制粘贴即可,有时间我针对这里会出一个视频。
代码如下->
- def _predict_once(self, x, profile=False, visualize=False, embed=None):
- """
- Perform a forward pass through the network.
- Args:
- x (torch.Tensor): The input tensor to the model.
- profile (bool): Print the computation time of each layer if True, defaults to False.
- visualize (bool): Save the feature maps of the model if True, defaults to False.
- embed (list, optional): A list of feature vectors/embeddings to return.
- Returns:
- (torch.Tensor): The last output of the model.
- """
- y, dt, embeddings = [], [], [] # outputs
- for m in self.model:
- if m.f != -1: # if not from previous layer
- x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
- if profile:
- self._profile_one_layer(m, x, dt)
- if hasattr(m, 'backbone'):
- x = m(x)
- if len(x) != 5: # 0 - 5
- x.insert(0, None)
- for index, i in enumerate(x):
- if index in self.save:
- y.append(i)
- else:
- y.append(None)
- x = x[-1] # 最后一个输出传给下一层
- else:
- x = m(x) # run
- y.append(x if m.i in self.save else None) # save output
- if visualize:
- feature_visualization(x, m.type, m.i, save_dir=visualize)
- if embed and m.i in embed:
- embeddings.append(nn.functional.adaptive_avg_pool2d(x, (1, 1)).squeeze(-1).squeeze(-1)) # flatten
- if m.i == max(embed):
- return torch.unbind(torch.cat(embeddings, 1), dim=0)
- return x
到这里就完成了修改部分,但是这里面细节很多,大家千万要注意不要替换多余的代码,导致报错,也不要拉下任何一部,都会导致运行失败,而且报错很难排查!!!很难排查!!!
4.9 修改九
关注我的其实都知道,我大部分的修改都是一样的,这个网络需要额外的修改一步,就是s一个参数,将下面的s改为640!!!即可完美运行!!
4.10 修改十
我们找到如下文件'ultralytics/utils/torch_utils.py'按照如下的图片进行修改,否则容易打印不出来计算量。
五、StarNet的yaml文件
5.1 UniRepLknet 的yaml文件版本1
此版本训练信息:YOLO11-StarNet summary: 456 layers, 1,826,058 parameters, 1,826,042 gradients, 4.4 GFLOPs
使用说明:# 下面 [-1, 1, starnet_s1, [0.5,0.25]] 参数位置的0.25是通道放缩的系数, YOLOv11N是0.25 YOLOv11S是0.5 YOLOv11M是1. YOLOv11l是1 YOLOv11是1.5大家根据自己训练的YOLO版本设定即可.# 0.5对应的是模型的深度系数
# 本文支持版本有starnet_s1, starnet_s2, starnet_s3, starnet_s4, starnet_s050, starnet_s100, starnet_s150
- # Ultralytics YOLO 🚀, AGPL-3.0 license
- # YOLO11 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
- # Parameters
- nc: 80 # number of classes
- scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
- # [depth, width, max_channels]
- n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
- s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
- m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
- l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
- x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs
- # 下面 [-1, 1, starnet_s1, [0.5,0.25]] 参数位置的0.25是通道放缩的系数, YOLOv11N是0.25 YOLOv11S是0.5 YOLOv11M是1. YOLOv11l是1 YOLOv11是1.5大家根据自己训练的YOLO版本设定即可.
- # 0.5对应的是模型的深度系数
- # 支持的版本starnet_s1, starnet_s2, starnet_s3, starnet_s4, starnet_s050, starnet_s100, starnet_s150
- # YOLO11n backbone
- backbone:
- # [from, repeats, module, args]
- - [-1, 1, starnet_s1, [0.5, 0.25]] # 0-4 P1/2
- - [-1, 1, SPPF, [1024, 5]] # 5
- - [-1, 2, C2PSA, [1024]] # 6
- # YOLO11n head
- head:
- - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- - [[-1, 3], 1, Concat, [1]] # cat backbone P4
- - [-1, 2, C3k2, [512, False]] # 9
- - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- - [[-1, 2], 1, Concat, [1]] # cat backbone P3
- - [-1, 2, C3k2, [256, False]] # 12 (P3/8-small)
- - [-1, 1, Conv, [256, 3, 2]]
- - [[-1, 9], 1, Concat, [1]] # cat head P4
- - [-1, 2, C3k2, [512, False]] # 15 (P4/16-medium)
- - [-1, 1, Conv, [512, 3, 2]]
- - [[-1, 6], 1, Concat, [1]] # cat head P5
- - [-1, 2, C3k2, [1024, True]] # 18 (P5/32-large)
- - [[12, 15, 18], 1, Detect, [nc]] # Detect(P3, P4, P5)
5.2 训练文件
- import warnings
- warnings.filterwarnings('ignore')
- from ultralytics import YOLO
- if __name__ == '__main__':
- model = YOLO('替换你的模型yaml文件地址')
- # model.load('yolov8n.pt') # loading pretrain weights
- model.train(data=r'替换数据集yaml文件地址',
- # 如果大家任务是其它的'ultralytics/cfg/default.yaml'找到这里修改task可以改成detect, segment, classify, pose
- cache=False,
- imgsz=640,
- epochs=150,
- single_cls=False, # 是否是单类别检测
- batch=4,
- close_mosaic=10,
- workers=0,
- device='0',
- optimizer='SGD', # using SGD
- # resume='', # 如过想续训就设置last.pt的地址
- amp=False, # 如果出现训练损失为Nan可以关闭amp
- project='runs/train',
- name='exp',
- )
六、成功运行记录
下面是成功运行的截图,已经完成了有1个epochs的训练,图片太大截不全第2个epochs,这里改完之后打印出了点问题,但是不影响任何功能,后期我找时间修复一下这个问题。
七、本文总结
到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv11改进有效涨点专栏,本专栏目前为新开的平均质量分98分,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的改进机制进行补充, 目前本专栏免费阅读(暂时,大家尽早关注不迷路~) ,如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~