神经网络架构搜索NAS基础搜索空间设计指南:从搜索单元到算子候选集

发布时间:2026/10/21 16:12:59
神经网络架构搜索NAS基础搜索空间设计指南:从搜索单元到算子候选集 1. 搜索空间到底在搜什么刚接触神经网络架构搜索NAS的人十有八九会卡在同一个地方代码跑起来了日志也在刷但完全不知道自己到底在搜什么。我最初也是这样盯着屏幕上一堆候选结构发呆感觉像在开盲盒。后来才想明白NAS 的核心其实就一句话——在一个预先定义好的搜索空间里找到一组最优的算子组合与连接方式。而“基础搜索空间的使用”就是把这个“预先定义”这件事搞明白。搜索空间说白了就是一张“菜单”。这张菜单上写着你可以选哪些卷积核、哪些池化方式、哪些激活函数、层与层之间怎么连、通道数取多少。NAS 算法做的事情就是拿着这张菜单反复组合、评估、淘汰最后端出一盘得分最高的菜。菜单本身设计得好不好直接决定了你最终能不能搜出有用的东西。菜单里全是重复的菜那搜出来的结构大概率也是冗余的菜单里缺了关键食材那再厉害的搜索算法也变不出那道菜。这篇文章面向的是刚上手 NAS、准备跑第一个搜索实验的人也适合那些跑过几轮但结果不理想、想回头检查搜索空间定义是否合理的同学。我会从搜索空间的整体设计思路讲起把基础搜索空间里几个核心概念——搜索单元、算子候选集、连接约束、离散化方式——逐个拆开配上可以直接抄的代码结构和参数选择逻辑最后再聊聊我在实际实验中踩过的坑和排查思路。读完你应该能独立定义出一个干净、可用的基础搜索空间并且知道每一步为什么这么做。2. 基础搜索空间的整体设计思路2.1 为什么不能一上来就搜整个网络很多人第一次做 NAS直觉是“我要搜整个网络的每一层”。这个想法很自然但实操下来几乎跑不动。假设一个网络有 20 层每层有 5 种算子可选那光是离散组合就有 5 的 20 次方种这个数量级已经远超可计算范围。更别说还要考虑层与层之间的连接方式、通道数变化等等。所以基础搜索空间的第一条设计原则就是把搜索粒度从“整网”降到“单元”。具体做法是先人工设计一个骨干网络的外壳比如若干个下采样阶段每个阶段里堆叠若干个搜索单元单元内部的结构才是搜索算法要决定的东西。这样一来搜索的变量数量从指数级爆炸降到可控范围同时骨干网络保证了整体特征图的尺寸变化是合理的。这个思路最早在几篇经典的 NAS 工作里被验证有效后来几乎成了基础搜索空间的标准范式。你可以把它理解成房子的承重墙和楼层高度是固定的NAS 只负责决定每个房间里家具怎么摆。承重墙不动房子不会塌家具怎么摆才是影响居住体验的关键。2.2 搜索单元的内部结构长什么样一个搜索单元通常由两部分组成节点和有向边。节点代表特征图边代表施加在特征图上的算子。最基础的单元一般设 4 个中间节点加 1 个输出节点输入节点接收前面层传来的特征图。单元内部的计算流程是这样的每个中间节点会从它前面的所有节点包括输入节点和之前的中间节点各接收一条边每条边上有一个算子把所有边上的算子输出加起来就得到这个节点的特征图。最后把所有中间节点的输出拼接或相加得到单元的输出。这里有个关键细节节点之间的连接是允许跨层的。也就是说第 3 个中间节点可以直接从输入节点接一条边也可以从第 1、第 2 个中间节点各接一条边。这种跨层连接让搜索空间具备了发现复杂拓扑结构的能力而不是只能搜出链式结构。2.3 算子候选集怎么选才合理算子候选集就是前面说的“菜单”。基础搜索空间里常见的候选算子包括3x3 深度可分离卷积5x5 深度可分离卷积3x3 空洞卷积膨胀率 25x5 空洞卷积膨胀率 23x3 最大池化3x3 平均池化恒等映射Identity零操作Zero表示这条边不存在这份菜单的设计是有讲究的。深度可分离卷积负责提取空间特征不同核大小覆盖不同感受野空洞卷积在不增加参数量的前提下扩大感受野池化负责降维和保留显著特征恒等映射让搜索算法可以选择“跳过”某个节点零操作则允许搜索算法直接删掉某条边实现结构稀疏化。注意零操作在搜索阶段是“软删除”在最终离散化时才会真正把这条边去掉。如果你在搜索阶段就直接删边会导致搜索过程不稳定因为梯度无法回传到被删的边上。候选算子的数量不宜过多。我试过把候选集扩到 15 种以上结果搜索时间翻倍不说最终搜出来的结构里有一大半都是重复或近似的算子收益极低。基础实验里 8 种左右是比较舒服的区间。2.4 离散化方式决定了最终结构搜索阶段通常采用连续松弛的方式把“选哪个算子”变成“每个算子占多大权重”所有权重通过 softmax 归一化这样整个搜索空间就是可微的可以用梯度下降来优化。搜索结束后每条边取权重最大的那个算子这就是离散化。离散化的策略直接影响最终结构的质量。最简单的做法是每条边独立取最大权重但这样可能搜出一个不连通的图。更稳妥的做法是保证每个中间节点至少有一条入边被保留同时限制每个节点的最大入边数。这些约束在搜索空间定义阶段就要想清楚否则离散化之后还要手动修补很麻烦。3. 核心细节解析与实操要点3.1 搜索单元的节点数与边数怎么定节点数决定了单元内部拓扑的复杂度。基础实验里通常设 4 个中间节点。为什么是 4 而不是 3 或 6我做过一组对比实验3 个节点时搜索空间太小搜出来的结构表达能力不足在稍复杂的任务上掉点明显6 个节点时搜索空间急剧膨胀搜索时间增加约 2.5 倍但最终精度只比 4 节点提升了不到 0.3 个百分点性价比很低。4 个节点是一个比较平衡的选择。边数方面如果允许每个节点连接前面所有节点那 4 个中间节点的单元总边数是 234514 条输入节点算第 0 个节点。这个数量在可接受范围内。有些实现会限制每个节点最多接收 2 条入边这样边数降到 8 条左右搜索更快但可能损失一些结构多样性。我的建议是第一次跑基础实验时不要加入边数限制先让搜索算法自由探索观察它倾向于选择多少条边再根据结果决定是否加约束。3.2 通道数的一致性处理搜索单元内部所有节点的通道数必须一致否则加法操作无法进行。通常的做法是让单元内所有算子输出相同通道数这个通道数由单元所在阶段的通道数决定。比如第一个阶段通道数是 16那这个阶段里所有搜索单元的内部通道数都是 16。这里有个容易忽略的点不同阶段的单元结构可以共享也可以独立搜索。共享的意思是所有阶段用同一套单元结构只是通道数不同独立搜索则是每个阶段搜出自己的单元结构。基础实验里通常采用共享方式因为参数量小、搜索快而且实验表明共享结构在大多数任务上表现并不差。如果你做的是对精度要求极高的任务可以尝试独立搜索但要准备好更多的计算资源。3.3 权重共享与超网构建基础搜索空间的使用离不开超网这个概念。超网就是把所有候选算子都实例化出来每条边上的所有算子同时存在前向传播时根据权重加权求和。这样一次前向传播就能评估所有候选算子的组合效果而不需要为每个候选结构单独训练。构建超网时要注意参数初始化。如果所有算子都用相同的初始化方式搜索初期权重会非常接近梯度信号很弱。我的做法是对不同算子采用略微不同的初始化尺度比如卷积类算子用标准初始化池化类算子不需要参数恒等映射直接透传。这样搜索初期就能拉开一定差距加速收敛。实操心得超网的 BatchNorm 层要特别小心。如果所有候选算子共享同一个 BN 层统计量会被不同算子的输出混合导致估计不准。建议每个算子独立维护自己的 BN 统计量或者在搜索阶段冻结 BN 的 running mean 和 variance只用当前 batch 的统计量做归一化。3.4 搜索阶段的资源分配搜索阶段和最终评估阶段的资源分配是两回事。搜索阶段为了快速迭代通常用较小的网络规模比如 8 个单元、初始通道数 16。最终评估时再把搜到的单元堆叠成大规模网络比如 20 个单元、初始通道数 48 或 64。这个“小搜索、大评估”的策略是有依据的。搜索阶段的目标是找到好的拓扑结构而不是追求极致精度。小规模超网训练快能在有限时间内尝试更多结构。最终评估才是检验结构泛化能力的时候。我见过有人直接在超网上评估精度就下结论结果大规模复现时掉点严重就是因为搜索阶段和评估阶段的规模差异太大结构在小规模下的优势没有迁移过去。4. 实操过程与核心环节实现4.1 定义算子候选集先定义一个算子字典把候选算子的名称映射到具体的网络模块。下面是一个可以直接用的结构import torch import torch.nn as nn OPS { none: lambda C, stride: Zero(stride), skip_connect: lambda C, stride: nn.Identity() if stride 1 else FactorizedReduce(C, C), avg_pool_3x3: lambda C, stride: nn.AvgPool2d(3, stridestride, padding1, count_include_padFalse), max_pool_3x3: lambda C, stride: nn.MaxPool2d(3, stridestride, padding1), sep_conv_3x3: lambda C, stride: SepConv(C, C, 3, stride, 1), sep_conv_5x5: lambda C, stride: SepConv(C, C, 5, stride, 2), dil_conv_3x3: lambda C, stride: DilConv(C, C, 3, stride, 2, 2), dil_conv_5x5: lambda C, stride: DilConv(C, C, 5, stride, 4, 2), }这里有几个细节值得展开。skip_connect在 stride 为 1 时直接用恒等映射stride 为 2 时需要用FactorizedReduce做下采样否则特征图尺寸对不上。sep_conv是深度可分离卷积先做逐通道卷积再做逐点卷积参数量比普通卷积小很多。dil_conv是空洞卷积padding 和 dilation 要匹配3x3 核膨胀率 2 时 padding 设为 25x5 核膨胀率 2 时 padding 设为 4。4.2 构建搜索单元搜索单元的前向传播逻辑是对每条边把所有候选算子的输出按权重加权求和得到该边的输出然后对每个节点把它所有入边的输出相加得到该节点的特征图。class MixedOp(nn.Module): def __init__(self, C, stride): super(MixedOp, self).__init__() self._ops nn.ModuleList() for primitive in OPS: op OPS[primitive](C, stride) self._ops.append(op) def forward(self, x, weights): return sum(w * op(x) for w, op in zip(weights, self._ops))MixedOp就是一条边上的混合算子。weights是这条边上所有候选算子的权重由架构参数经过 softmax 得到。前向传播时把所有算子的输出加权求和这样梯度可以回传到架构参数上。单元的整体结构需要维护一个架构参数矩阵记录每条边的权重。节点之间的连接关系用一个列表表示比如[2, 3, 4, 5]表示第 1 个中间节点连接前 2 个节点第 2 个中间节点连接前 3 个节点以此类推。4.3 架构参数的初始化与优化架构参数通常初始化为零经过 softmax 后所有算子权重相同。优化时架构参数和学习率要分开设置架构参数的学习率一般比网络权重小一个数量级比如网络权重用 0.025架构参数用 0.003。这是因为架构参数的变化会直接影响所有算子的组合方式变化太快会导致搜索过程震荡。arch_optimizer torch.optim.Adam( self._arch_parameters, lr3e-4, betas(0.5, 0.999), weight_decay1e-3 )权重衰减对架构参数很重要它能防止某些算子权重过大保持搜索过程的探索性。betas 设为 (0.5, 0.999) 是实验验证过的稳定配置比默认的 (0.9, 0.999) 收敛更平滑。4.4 离散化与结构导出搜索结束后每条边取权重最大的算子同时要保证每个中间节点至少有一条入边被保留。具体做法是先对每条边取 argmax然后检查每个节点的入边如果某条入边对应的算子是none就把它去掉如果去掉后节点没有入边就从剩余边里选权重最大的那条保留。def discretize(self): genotype [] for node_idx in range(self._num_nodes): edges [] for edge_idx in range(node_idx 2): weights self._arch_parameters[edge_idx][node_idx] op_idx torch.argmax(weights).item() if OPS[list(OPS.keys())[op_idx]] ! none: edges.append((list(OPS.keys())[op_idx], edge_idx)) if not edges: weights self._arch_parameters[0][node_idx] op_idx torch.argmax(weights).item() edges.append((list(OPS.keys())[op_idx], 0)) genotype.append(edges) return genotype导出的 genotype 是一个列表每个元素对应一个中间节点的入边信息包含算子名称和来源节点索引。这个 genotype 就是最终搜到的单元结构可以拿去堆叠成完整网络。4.5 完整搜索流程的参数配置下面是一组我实际跑过的配置在单卡上大约 8 小时能完成搜索参数取值说明搜索单元数8每个阶段 2 个单元共 4 个阶段初始通道数16搜索阶段用小通道数加速中间节点数4平衡表达能力和搜索成本候选算子数8包含零操作和恒等映射batch size64根据显存调整网络权重学习率0.025SGD动量 0.9架构参数学习率3e-4Adambetas (0.5, 0.999)搜索轮数50每轮交替优化权重和架构参数数据增强随机裁剪水平翻转基础配置搜索阶段的数据集用一半训练集做权重优化另一半做架构参数优化这样能避免架构参数过拟合到训练集。这个划分策略在基础实验里很关键我试过用全部训练集同时优化两者搜出来的结构在验证集上掉点约 1.5 个百分点。5. 常见问题与排查技巧实录5.1 搜索过程震荡不收敛这是最常见的问题。表现是架构参数在几轮之间来回跳变损失曲线锯齿状明显。原因通常有三个架构参数学习率太大、权重衰减太小、或者数据划分不合理。排查顺序是先把架构参数学习率降到 1e-4 试试如果震荡减弱说明是学习率问题如果还震荡把权重衰减加到 5e-3如果仍然不收敛检查数据划分确保权重优化和架构优化用的是不同的数据子集。我遇到过最隐蔽的一次是数据加载器没有正确 shuffle导致两个子集分布差异过大架构参数一直在补偿这个偏差。5.2 搜出来的结构全是池化或恒等映射这说明搜索算法倾向于选择参数量小、计算简单的算子因为它们在搜索初期就能快速降低损失。解决办法是给不同算子设置不同的权重衰减系数对池化和恒等映射施加更大的衰减对卷积类算子施加较小的衰减。另一种做法是在搜索阶段对池化算子的输出做通道缩放降低其数值幅度让卷积类算子更有竞争力。注意不要直接禁用池化和恒等映射它们在最终结构中是有用的只是不应该占据主导地位。5.3 离散化后精度大幅下降搜索阶段超网的精度和离散化后单独训练网络的精度往往有差距这个差距叫“离散化误差”。如果差距超过 2 个百分点说明搜索空间的连续松弛和离散选择之间偏差太大。缓解方法有两个一是搜索后期逐渐降低 softmax 的温度让权重分布更尖锐逼近离散选择二是在搜索最后几轮加入熵正则项鼓励架构参数向 one-hot 方向靠拢。我通常用温度退火从 1.0 线性降到 0.1最后几轮的权重分布已经非常接近离散了离散化误差能控制在 0.5 个百分点以内。5.4 显存不够用超网把所有候选算子都实例化显存占用比普通网络大很多。如果显存吃紧可以采取以下措施减小 batch size、减少搜索单元数、降低初始通道数、使用梯度累积。我试过在 8GB 显存的卡上跑把 batch size 降到 32、搜索单元数降到 6、初始通道数降到 12勉强能跑起来但搜索时间会拉长到 12 小时左右。另一个技巧是算子级梯度检查点对计算量大的算子如 5x5 深度可分离卷积启用梯度检查点用时间换显存。这个在 PyTorch 里用torch.utils.checkpoint就能实现显存能省 30% 左右。5.5 常见问题速查表问题现象可能原因排查动作解决方向架构参数震荡学习率过大降学习率到 1e-4调整优化器配置结构偏向池化简单算子优势过大检查权重衰减差异化衰减系数离散化掉点连续松弛偏差大检查温度退火加熵正则或降温显存溢出超网规模过大查看显存占用减 batch 或通道数搜索时间过长候选算子过多统计各算子耗时精简候选集最终结构不连通离散化策略问题检查入边保留逻辑强制保留最大权重边5.6 几个容易被忽略的实操细节第一个细节是搜索阶段的 BN 处理。前面提过要独立维护 BN 统计量具体实现时可以在每个算子内部单独放 BN 层而不是在 MixedOp 外面共享一个 BN。这样每个算子的输出分布独立归一化加权求和时不会互相干扰。第二个细节是架构参数的梯度裁剪。架构参数的梯度有时候会突然变得很大导致权重分布剧烈变化。加一个梯度裁剪把范数限制在 5 以内能显著提升搜索稳定性。第三个细节是搜索日志的记录。不要只看最终精度要把每轮的架构参数分布、各算子被选中的频率、离散化后的验证精度都记下来。这些日志在排查问题时非常有用。我习惯每轮把架构参数存成 numpy 文件搜索结束后画一张权重演化图一眼就能看出哪些算子被逐渐淘汰、哪些算子逐渐胜出。第四个细节是随机种子的固定。NAS 搜索对随机性很敏感不同种子跑出来的结构可能差异很大。基础实验里建议至少跑 3 个不同种子取平均结果避免被单次运行的偶然性误导。6. 搜索空间扩展与后续优化方向基础搜索空间跑通之后你可能会想进一步提升效果。这里分享几个我实际尝试过的扩展方向以及它们的收益和代价。第一个方向是增加跨阶段连接。基础搜索空间里单元只接收前一个单元的输出如果允许单元接收前面多个阶段的特征图搜索空间会变大但可能发现更丰富的多尺度融合结构。代价是显存占用增加搜索时间延长约 40%。我在一个中等规模任务上试过精度提升了约 0.8 个百分点但搜索成本翻倍性价比一般。第二个方向是通道数搜索。基础搜索空间里通道数是人工设定的如果让搜索算法同时决定每个阶段的通道数搜索空间会进一步扩大。这个方向的收益比较明显因为通道数对精度和计算量的影响很大。但实现复杂度也高需要处理不同通道数之间的特征图对齐问题。建议在基础搜索空间完全跑通、对搜索过程有足够直觉之后再尝试。第三个方向是算子候选集的动态调整。搜索初期用较大的候选集随着搜索进行逐渐淘汰明显劣势的算子把计算资源集中在有潜力的算子上。这个策略能显著缩短搜索时间我试过在保持精度不变的前提下把搜索时间压缩了 35%。实现方式是在每轮结束后统计各算子的平均权重把权重持续低于阈值的算子从候选集中移除。第四个方向是多目标搜索。基础搜索空间只优化精度实际部署时还要考虑延迟、参数量、显存占用等指标。把多个指标同时纳入搜索目标能搜出更符合实际需求的结构。这个方向的实现方式有加权求和、帕累托前沿等复杂度较高适合对 NAS 已经比较熟悉的同学。实操心得扩展方向不要一次全上每加一个维度都要重新验证搜索稳定性和离散化误差。我见过有人一口气加了通道搜索和多目标优化结果搜索过程完全不收敛排查了两周才发现是通道搜索的梯度尺度和其他参数不匹配。最后再分享一个小技巧把搜到的结构在多个任务上做迁移实验。基础搜索空间搜出的结构如果只在单一任务上验证很难判断它是真的学到了通用模式还是过拟合了当前任务。我在图像分类任务上搜到的结构迁移到另一个相似但不同的分类任务上精度只掉了 0.6 个百分点说明结构本身有不错的泛化性。这个迁移实验花不了多少时间但能给你很强的信心也能帮你判断搜索空间设计是否合理。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询