ShuffleNet 轻量化网络全解析:从通道混洗到内存访问代价的 V1/V2 演进之路
发布时间:2026/10/2 1:59:21来源:尧图网络
文档教程人工智能【免费下载链接】AISystemAISystem 主要是指AI系统包括AI芯片、AI编译器、AI推理和训练框架等AI全栈底层技术项目地址https://gitcode.com/GitHub_Trending/ai/AISystem点击查看免费下载导读本文以 04Inference/02Mobilenet/022Shufflenet.md 为主体系统讲解轻量化卷积网络 ShuffleNet 系列的两代核心设计V1 通过逐点分组卷积Pointwise Group Convolution与通道混洗Channel Shuffle在降低计算量的同时保持表征能力V2 则跳出 FLOPs 指标的局限从设备实际运行速度出发提出四大设计原则用通道分割Channel Split进一步逼近移动端推理的最优形态。读完本文你将掌握 ShuffleNet 单元与 Block 的完整 PyTorch 实现、参数/计算量的推导过程以及把内存访问代价纳入网络设计决策的实战方法论可直接应用于移动端视觉模型的轻量化选型与自研主干网络设计。ShuffleNet 系列概述ShuffleNet 系列属于 CNN 模型小型化的代表性工作与其同章节的 SqueezeNet、MobileNet、ESPNet、FBNet、EfficientNet、GhostNet 等一同构成推理引擎模型小型化知识体系的重要一环详见 02CNN.md 的章节组织。该系列的核心命题是如何在深度网络中降低计算量同时尽量不损失精度。两个版本各有关注点ShuffleNet V1贡献在于使用 Point-Wise 分组卷积和 Channel Shuffle 两个操作在降低计算量的同时保持准确率。网络使用更多通道帮助编码阶段提取更多信息并针对小网络提出了 ShuffleNet Unit。ShuffleNet V2框架结构与 V1 基本相同唯一区别是 V2 比 V1 多了一个 $1\times1$ Conv5。V2 的最大贡献在于看到 GPU 访存带宽内存访问代价 MAC对模型推理时间的影响而不仅仅是 FLOPs 与参数量对推理时间的影响并由此提出 4 个轻量级网络设计原则和一个新颖的卷积 Block 架构。V1 给出的收益数据原文引用供选型参考相比 MobileNetShuffleNet V1 在 ImageNet 分类任务上 top-1 误差更低绝对 7.8%在基于 ARM 的移动设备上ShuffleNet 比 AlexNet 实现了约 13 倍的实际加速同时保持相当的精度。ShuffleNet V1分组卷积 通道混洗设计思路逐点卷积成为新的瓶颈ShuffleNet V1 网络结构沿袭了稀疏连接的设计理念。作者通过分析 Xception 和 ResNeXt 发现这两种结构通过卷积核拆分虽然使计算复杂度较原始卷积有所下降但拆分所产生的逐点卷积1×1 卷积计算量却相当可观成为新的瓶颈。例如对于 ResNeXt 模型逐点卷积占据了93.4%的运算复杂度。可见要进一步提升模型速度就必须寻求更高效的结构来取代逐点卷积。受 ResNeXt 启发作者提出使用**分组逐点卷积Group Pointwise Convolution来代替原来的结构通过将卷积运算的输入限制在每个组内模型计算量显著下降。但多层逐点卷积堆叠时模型信息流被分割在各个组内组与组之间没有信息交换这会损害模型的表征能力和识别精度。因此在使用分组逐点卷积的同时必须引入组间信息交换机制对于第二层卷积每个卷积核需要同时接收各组的特征作为输入。作者指出通过引入通道重排Channel Shuffle**可以很方便地实现这一机制且通道重排操作是可导的因此可以嵌入网络结构中实现端到端学习。上图 (a) 展示普通分组卷积的缺陷GConv2 的每个输出通道只来自输入的一小部分同组通道集中排列组间无联系(b) 展示理想状态下各组的通道应交叉参与下一层卷积(c) 即通道混洗操作将按组集中的通道重新交替排列使下一层卷积核天然覆盖所有组的特征。逐点分组卷积对 1×1 卷积做分组逐点分组卷积pointwise group convolution本质就是带分组的、卷积核为 $1\times1$ 的卷积。在 ResNeXt 中分组主要作用于 $3\times3$ 卷积但 ShuffleNet 作者认为 $1\times1$ 卷积的计算量不可忽视因此把分组操作应用到 $1\times1$ 卷积上。分组将输入与输出的通道分成若干组。例如输入输出通道都是 4 个且分成 2 组则第 1、2 通道的输出只使用第 1、2 通道的输入第 3、4 通道的输出也只使用第 1、2 通道的输入组内完成全连接组间完全隔离。不同组的输出与输入不再有关系联系减少使计算量下降但同时也导致信息丢失。当分成 $g$ 组后一层参数量由$$ C_{out}\times(C_{in}\times K_h\times K_w) $$变成$$ C_{out}\times(C_{in}\times K_h\times K_w/g) $$特征图大小不变但每一组只使用原来 $1/g$ 的参数量。循环 $g$ 次后参数量与计算量如下$$ ParamsC_{out}/g\times(C_{in}/g\times K_h\times K_w/g)\times(C_{out}/g)\cdots C_{out}/g\times C_{in}/g\times K_h\times K_w/g\times C_{out} $$$$ ComputationC_{out}/g\times H_{out}\times W_{out}\times(C_{in}/g\times K_h\times K_w/g) $$与 01Introduction.md 中给出的通用公式对照Group 卷积的 FLOPs 为 $k_h\times k_w\times c_{in}\times c_{out}\times H\times W / g$两者一致说明分组逐点卷积正是对 $1\times1$ 卷积施加分组在轻量化上的直接应用。通道重排打破组间信息壁垒不同组之间没有任何联系得到的特征图只和对应组别的输入有关学习到的特征非常有限容易导致信息丢失。通道重排Channel Shuffle解决这一问题。一般卷积操作中输入特征图数量为 $N$卷积核数量为 $M$则 $M$ 个卷积核中的每一个都要与 $N$ 个特征图的某个区域做卷积后相加得到 1 个卷积结果。引入分组操作后组数为 $g$$N$ 个输入特征图被分成 $g$ 个组$M$ 个卷积核也被分成 $g$ 个组。卷积时第一组的 $M/g$ 个卷积核中的每一个只与第一组的 $N/g$ 个输入特征图做卷积第二组同理直到最后一组。这种操作大大减少计算量但多个组操作叠加会产生边界效应某个输出通道仅仅来自输入通道的一小部分使网络学习到的特征非常局限。通道重排的解法是在第二层分组卷积GConv2之前对其输入特征图做一个分配——每个组再分成几个次组然后将不同组的次组重新组合为 GConv2 的每个组的输入使 GConv2 的每一个组都能卷积输入所有组的特征图。PyTorch 实现如下# 通道混洗 def shuffle_channels(x, groups): # 先得到输入特征图的 shapeb:batch sizeh,w:一张图的 sizec:通道数 batch_size, channels, height, width x.size() assert channels % groups 0 channels_per_group channels // groups # 在通道维度上将特征图 reshape 为 groups 行 channels_per_group 列的矩阵 x x.view(batch_size, groups, channels_per_group, height, width) # 矩阵转置 x x.transpose(1, 2).contiguous() x x.view(batch_size, channels, height, width) # 返回通道维度交叉排序后的 tensor return x核心只有三步view把通道维度拆成(groups, channels_per_group)两维 →transpose(1, 2)交换这两维等价于把每组内的次组变成每个次组内的组→ 再次view还原为原始形状完成通道的交叉排序。整个过程只有张量重塑与转置无额外参数、可导因而可嵌入网络任意位置。ShuffleNet 单元stride1 与 stride2 两种残差块ShuffleNet Unit 基于残差块residual block与通道重排channel shuffle设计主要由深度卷积、逐点分组卷积组合而成。stride1 的基本单元对应上图 b输入输出通道相等通过残差Add连接瓶颈通道数为输出通道的 1/4# ShuffleNet 中 stride1 的基本单元 class ShuffleNetUnitA(nn.Module): ShuffleNet unit for stride1 def __init__(self, in_channels, out_channels, groups3): super(ShuffleNetUnitA, self).__init__() assert in_channels out_channels assert out_channels % 4 0 bottleneck_channels out_channels // 4 self.groups groups # 1*1 分组卷积降维 self.group_conv1 nn.Conv2d(in_channels, bottleneck_channels, 1, groupsgroups, stride1) self.bn2 nn.BatchNorm2d(bottleneck_channels) self.depthwise_conv3 nn.Conv2d(bottleneck_channels, bottleneck_channels, 3, padding1, stride1, groupsbottleneck_channels) self.bn4 nn.BatchNorm2d(bottleneck_channels) # 1*1 分组卷积升维 self.group_conv5 nn.Conv2d(bottleneck_channels, out_channels, 1, stride1, groupsgroups) self.bn6 nn.BatchNorm2d(out_channels) def forward(self, x): out self.group_conv1(x) out F.relu(self.bn2(out)) out shuffle_channels(out, groupsself.groups) out self.depthwise_conv3(out) out self.bn4(out) out self.group_conv5(out) out self.bn6(out) out F.relu(x out) return out数据流为1×1 分组卷积(降维) → BNReLU → Channel Shuffle → 3×3 深度卷积 → BN → 1×1 分组卷积(升维) → BN → Add 残差 → ReLU。stride2 的下采样单元对应上图 c与残差块的关键区别是右分支通道数加上左分支3×3 平均池化的通道数才等于输出通道数两条分支使用Concat 拼接而非 Add# ShuffleNet 中 stride2 的基本单元下采样模块concat class ShuffleNetUnitB(nn.Module): ShuffleNet unit for stride2 def __init__(self, in_channels, out_channels, groups3): super(ShuffleNetUnitB, self).__init__() # 右分支的通道数和左分支的通道数叠加 输出特征图的通道数 out_channel out_channels - in_channels assert out_channels % 4 0 bottleneck_channels out_channels // 4 self.groups groups self.group_conv1 nn.Conv2d(in_channels, bottleneck_channels, 1, groupsgroups, stride1) self.bn2 nn.BatchNorm2d(bottleneck_channels) self.depthwise_conv3 nn.Conv2d(bottleneck_channels, bottleneck_channels, 3, padding1, stride2, groupsbottleneck_channels) self.bn4 nn.BatchNorm2d(bottleneck_channels) self.group_conv5 nn.Conv2d(bottleneck_channels, out_channels, 1, stride1, groupsgroups) self.bn6 nn.BatchNorm2d(out_channels) def forward(self, x): # 右分支 out self.group_conv1(x) out F.relu(self.bn2(out)) ## Channel Shuffle out shuffle_channels(out, groupsself.groups) out self.depthwise_conv3(out) out self.bn4(out) out self.group_conv5(out) out self.bn6(out) # 左分支:3*3 AVG Pool,stride2 x F.avg_pool2d(x, 3, stride2, padding1) out F.relu(torch.cat([x, out], dim1)) return out注意两处细节out_channels - in_channels先扣掉左分支保留的输入通道数深度卷积stride2负责空间下采样配合左分支 3×3 平均池化实现分辨率减半、通道数翻倍的经典下采样模式。网络结构与实现ShuffleNet V1 架构主要由一组 ShuffleNet 单元组成分为三个阶段Stage 2/3/4。设计要点每个阶段的第一个构建块应用 stride2即下采样单元ShuffleNetUnitB阶段内其他超参数保持不变下一个阶段的输出通道加倍每个 ShuffleNet 单元的瓶颈通道数设置为输出通道的1/4Stage 2 的第一个 block 不用分组卷积改用普通 $1\times1$ 卷积因为此时输入通道只有 24太少、分组意义不大每个 Stage 中第一个 block 的 stride2下采样模块其余 block 的 stride1基本模块。# ShuffleNet 网络 class ShuffleNet(nn.Module): ShuffleNet for groups3 def __init__(self, groups3, in_channels3, num_classes1000): super(ShuffleNet, self).__init__() self.conv1 nn.Conv2d(in_channels, 24, 3, stride2, padding1) stage2_seq [ShuffleNetUnitB(24, 240, groups3)] \ [ShuffleNetUnitA(240, 240, groups3) for i in range(3)] self.stage2 nn.Sequential(*stage2_seq) stage3_seq [ShuffleNetUnitB(240, 480, groups3)] \ [ShuffleNetUnitA(480, 480, groups3) for i in range(7)] self.stage3 nn.Sequential(*stage3_seq) stage4_seq [ShuffleNetUnitB(480, 960, groups3)] \ [ShuffleNetUnitA(960, 960, groups3) for i in range(3)] self.stage4 nn.Sequential(*stage4_seq) self.fc nn.Linear(960, num_classes) def forward(self, x): net self.conv1(x) net F.max_pool2d(net, 3, stride2, padding1) net self.stage2(net) net self.stage3(net) net self.stage4(net) net F.avg_pool2d(net, 7) net net.view(net.size(0), -1) net self.fc(net) logits F.softmax(net) return logits整体结构为Conv1(24 通道, stride2) → MaxPool → Stage2(24→240) → Stage3(240→480) → Stage4(480→960) → 7×7 全局平均池化 → FC(960→1000)。Stage 内单元数依次为 4、8、4 个通道按 240 → 480 → 960 翻倍递进。ShuffleNet V2从 FLOPs 到实际运行速度设计思路直接度量速度而非间接度量 FLOPs作者分析了 ShuffleNet V1 和 MobileNet V2 两个经典结构在不同设备上的运行时间分布提出一个关键观点之前的轻量级网络都通过浮点运算量FLOPs这个间接度量来描述快慢从不直接考虑运行速度。在移动设备中运行速度不仅取决于 FLOPs还受**内存访问成本Memory Access CostMAC和平台特性Platform Characteristics**影响。因此 ShuffleNet V2 通过控制不同环境来测试网络在设备上实际运行速度的快慢而不是以 FLOPs 判断性能并提出两条设计准则应该使用直接度量如速度而不是间接度量如 FLOPs这些指标应该在目标平台上进行评估。在 01Introduction.md 中MAC 被定义为输入单个样本时模型/卷积层完成一次前向传播所发生的内存交换总量单位 Byte而内存带宽决定了数据从内存移动到计算核心的速度——这正是 V2 把访存纳入设计考量的理论背景。基于此论文提出四条可落地的网络设计原则G1相同的通道宽度最小化内存访问成本MACG2过多的分组卷积增加 MACG3网络碎片fragmentation降低了并行度G4元素操作Elementwise如 Add、ReLU的代价不可忽略。对应到 02CNN.md 中的归纳G1 指输入输出通道相同时 MAC 最小、模型最快G2 指保持 FLOPs 不变时分组数增大会使 MAC 增大应针对硬件与需求设计分组数而非盲目增加G3 指碎片化程度越高速度越慢G4 指不要过度使用逐点逐元素运算。通道分割V2 Block 的核心算子在 ShuffleNet V1 Block 基础上V2 Block 引入**通道分割Channel Split**这一简单算子来实现上述目标。在每个单元开始时将输入特征图的 $c$ 个通道切分成两个分支$c-c$ 个通道和 $c$ 个通道。# 特征图 C 个通道进行切分 def split(x, groups): out x.chunk(groups, dim1) return out按照四条原则逐一落实G3减少碎片其中一个分支保持不变shortcut connection 恒等映射另一个分支包含三个通道数一样的卷积分支数量少、结构规整G1MAC 最小与 V1 不同V2 Block 的两个 $1\times1$ 卷积不再使用分组卷积一部分原因是满足 G2另一部分原因是通道分割split操作本身已经完成了分组效果分支卷积保持输入输出通道数一致都等于 $c$使 MAC 最小信息交互对两个分支的结果拼接Concat后仍使用通道混洗Channel Shuffle保证两个分支信息交互混洗后的输出即下一个单元的输入G4减少逐元素操作V1 的Add操作不再使用ReLU 和 DW 卷积等逐元素操作只存在于右分支同时将三个连续的逐元素算子——拼接Concat、通道混洗Channel Shuffle、通道拆分Channel Split——合并成一个逐元素算子减少逐元素操作的总体开销。针对需要空间下采样的 Block通道切分算子被移除Block 的输出通道数变为两倍详细信息见上图 (d)。PyTorch 实现如下# Shuffle V2 block class ShuffleUnit(nn.Module): def __init__(self, input_c: int, output_c: int, stride: int): super(ShuffleUnit, self).__init__() # 步长必须在 1 和 2 之间 if stride not in [1, 2]: raise ValueError(illegal stride value.) self.stride stride # 输出通道必须能被 2 等分 assert output_c % 2 0 branch_features output_c // 2 # 当 stride 为 1 时input_channel 是 branch_features 的两倍 # 是位运算可理解为计算×2 的快速方法 assert (self.stride ! 1) or (input_c branch_features 1) # 捷径分支 if self.stride 2: # 进行下采样:3×3 深度卷积1×1 卷积 self.branch1 nn.Sequential( self.depthwise_conv(input_c, input_c, kernel_s3, strideself.stride, padding1), nn.BatchNorm2d(input_c), nn.Conv2d(input_c, branch_features, kernel_size1, stride1, padding0, biasFalse), nn.BatchNorm2d(branch_features), nn.ReLU(inplaceTrue) ) else: # 不进行下采样:保持原状 self.branch1 nn.Sequential() # 主干分支 self.branch2 nn.Sequential( # 1×1 卷积3×3 深度卷积1×1 卷积 nn.Conv2d(input_c if self.stride 1 else branch_features, branch_features, kernel_size1, stride1, padding0, biasFalse), nn.BatchNorm2d(branch_features), nn.ReLU(inplaceTrue), self.depthwise_conv(branch_features, branch_features, kernel_s3, strideself.stride, padding1), nn.BatchNorm2d(branch_features), nn.Conv2d(branch_features, branch_features, kernel_size1, stride1, padding0, biasFalse), nn.BatchNorm2d(branch_features), nn.ReLU(inplaceTrue) ) # 深度卷积 staticmethod def depthwise_conv(input_c, output_c, kernel_s, stride, padding, biasFalse): return nn.Conv2d(in_channelsinput_c, out_channelsoutput_c, kernel_sizekernel_s, stridestride, paddingpadding, biasbias, groupsinput_c) def forward(self, x): if self.stride 1: # 通道切分 x1, x2 split(x, 2) # 主干分支和捷径分支拼接 out torch.cat((x1, self.branch2(x2)), dim1) else: # 通道切分被移除 # 主干分支和捷径分支拼接 out torch.cat((self.branch1(x), self.branch2(x)), dim1) # 通道混洗 out channel_shuffle(out, 2) return out实现要点解读branch_features output_c // 2且 stride1 时要求input_c branch_features 1即输入通道恰好是分支通道的两倍——这正是通道分割成两个相等分支的前提stride1 时branch1为空恒等映射split(x, 2)将输入切成两半一半直接走捷径一半经branch21×1 卷积 → 3×3 深度卷积 → 1×1 卷积三段通道数均为branch_features满足 G1stride2 时移除通道切分两条分支均对全通道输入做处理branch1用 3×3 深度卷积 1×1 卷积下采样branch2用 1×1 3×3 深度卷积stride2 1×1 下采样最后拼接使输出通道翻倍两种分支的 1×1 卷积都是普通卷积未设groups且biasFalseBN 层已包含偏置项节省参数量与计算量拼接后统一执行channel_shuffle(out, 2)保证两分支信息交互与 V1 的混洗思想一脉相承。网络结构实现将上图 (c)(d) 的 Block 叠加即得到 ShuffleNet V2 模型堆叠后的网络结构与 V1 类似。v1 与 v2 Block 的区别在于v2 在全局平均池化Global Average Pooling之前添加了一个 $1\times1$ 卷积Conv5来混合特征mix up features而 v1 没有。与 V1 一样V2 的 Block 通道数按 0.5×、1× 等比例缩放生成不同复杂度的网络标记为 ShuffleNet V2 0.5×、ShuffleNet V2 1× 等模型。# V2 网络结构 class ShuffleNetV2(nn.Module): def __init__(self, stages_repeats, stages_out_channels, num_classes1000, ShuffleUnitShuffleUnit): super(ShuffleNetV2, self).__init__() if len(stages_repeats) ! 3: raise ValueError(expected stages_repeats as list of 3 positive ints) if len(stages_out_channels) ! 5: raise ValueError(expected stages_out_channels as list of 5 positive ints) self._stage_out_channels stages_out_channels # 输入通道 input_channels 3 output_channels self._stage_out_channels[0] self.conv1 nn.Sequential( nn.Conv2d(input_channels, output_channels, kernel_size3, stride2, padding1, biasFalse), nn.BatchNorm2d(output_channels), nn.ReLU(inplaceTrue) ) input_channels output_channels self.maxpool nn.MaxPool2d(kernel_size3, stride2, padding1) # 三个基本单元组层 self.stage2: nn.Sequential self.stage3: nn.Sequential self.stage4: nn.Sequential stage_names [stage{}.format(i) for i in [2, 3, 4]] for name, repeats, output_channels in zip(stage_names, stages_repeats, self._stage_out_channels[1:]): # 每个 Stage 的首个基础单元都需要进行下采样其他单元不需要 seq [ShuffleUnit(input_channels, output_channels, 2)] for i in range(repeats - 1): seq.append(ShuffleUnit(output_channels, output_channels, 1)) setattr(self, name, nn.Sequential(*seq)) input_channels output_channels output_channels self._stage_out_channels[-1] self.conv5 nn.Sequential( nn.Conv2d(input_channels, output_channels, kernel_size1, stride1, padding0, biasFalse), nn.BatchNorm2d(output_channels), nn.ReLU(inplaceTrue) ) # 全局平均池化 self.global_pool nn.AdaptiveAvgPool2d((1, 1)) # 全连接层 self.fc nn.Linear(output_channels, num_classes) # 权重初始化 self.init_params() def init_params(self): for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, modefan_out) if m.bias is not None: nn.init.zeros_(m.bias) elif isinstance(m, nn.BatchNorm2d): nn.init.ones_(m.weight) nn.init.zeros_(m.bias) elif isinstance(m, nn.Linear): nn.init.normal_(m.weight, 0, 0.01) nn.init.zeros_(m.bias) def forward(self, x): x self.conv1(x) x self.maxpool(x) x self.stage2(x) x self.stage3(x) x self.stage4(x) x self.conv5(x) x self.global_pool(x) x x.view(x.size(0), -1) x self.fc(x) return x def shufflenet_v2_x0_5(num_classes1000): weight: https://download.pytorch.org/models/shufflenetv2_x0.5-f707e7126e.pth model ShuffleNetV2(stages_repeats[4, 8, 4], stages_out_channels[24, 48, 96, 192, 1024], num_classesnum_classes) return model实现要点网络结构为Conv1 → MaxPool → Stage2/3/4 → Conv5 → GlobalPool → FC其中conv5即 V2 相对 V1 多出的 $1\times1$ 卷积在全局池化前混合特征每个 Stage 首个单元ShuffleUnit(..., 2)做下采样后续repeats-1个单元 stride1以shufflenet_v2_x0_5为例stages_repeats[4, 8, 4]对应三个 Stage 分别含 4、8、4 个单元stages_out_channels[24, 48, 96, 192, 1024]中前 4 个是 Stage 的输入/输出通道最后一个 1024 是conv5的输出通道init_params使用 kaiming_normalfan_out 模式初始化卷积、全连接权重BN 权重初始化为 1、偏置为 0是保证小网络训练收敛的常见工程实践。按比例缩放时将stages_out_channels整体乘以缩放系数如 0.5 对应[24, 48, 96, 192, 1024]即可生成不同计算量档位的模型shufflenet_v2_x0_5已给出标准配置1.0× 等更大档位只需对应放大通道数。小结与思考ShuffleNet 系列专注于模型结构的轻量化设计通过引入逐点分组卷积Pointwise Group Convolution和通道混洗Channel Shuffle两种新运算有效降低深度网络的计算量ShuffleNet V1 利用分组逐点卷积减少计算量并通过通道重排交换组间信息、保持模型表征能力V1 在 ImageNet 分类任务上达到较低的 top-1 误差并在移动设备上实现显著加速ShuffleNet V2 进一步考虑设备运算速度提出 4 个轻量级网络设计原则G1 最小化 MAC、G2 慎用分组卷积、G3 减少网络碎片、G4 控制逐元素操作并通过通道分割Channel Split和改进的卷积 Block 架构最小化内存访问成本提高网络在目标平台上的运行速度。两代模型的演进揭示了一个重要方法论轻量化设计不能只盯着 FLOPs 与参数量还要把内存访问代价、平台算力特性和逐元素算子开销纳入设计目标并最终用目标设备上的实测速度来验证。这为在移动端、边缘端部署视觉模型时的主干网络选型提供了直接、可复现的参考路径。更多同系列的轻量化结构SqueezeNet、MobileNet、ESPNet、FBNet、EfficientNet、GhostNet 等可继续参阅 02CNN.md复杂度与硬件指标基础可参阅 01Introduction.md。赞分享文档教程人工智能【免费下载链接】AISystemAISystem 主要是指AI系统包括AI芯片、AI编译器、AI推理和训练框架等AI全栈底层技术项目地址https://gitcode.com/GitHub_Trending/ai/AISystem点击查看免费下载相关推荐ShuffleNet系列轻量化网络设计终极指南从通道混洗到移动端优化ShuffleNet系列轻量化网络设计终极指南从通道混洗到移动端优化 在深度学习模型部署到移动设备的实际应用中ShuffleNet系列轻量化网络设计已成为A文档教程人工智能小米手表表盘设计神器Mi-Create零基础打造个性化智能表盘小米手表表盘设计神器Mi Create零基础打造个性化智能表盘 想要为你的小米智能手表设计专属表盘吗Mi Create作为一款完全免费开源的专业级表盘创作工桌面应用开发工具从v1到v2Audiobookshelf功能演进全解析从v1到v2Audiobookshelf功能演进全解析 Audiobookshelf作为一款自托管的有声书和播客服务器从v1版本到v2版本经历了显著的功能升后端音视频前端上一篇StegOnline终极指南5分钟掌握免费在线图像隐写分析工具下一篇如何用OpenKore实现RO游戏自动化从入门到精通的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网