论文作者 Hanxiao Liu∗ Karen Simonyan Yiming Yang代码链接 https://github.com/quark0/darts环境需求Python 3.5.5, PyTorch 0.3.1, torchvision 0.2.0代码结构 darts/ └── cnn/ ├── architect.py ├── genotypes.py ├── model.py ├── model_search.py ├── operations.py ├── test.py ├── test_imagenet.py ├── train.py ├── train_imagenet.py ├── train_search.py ├── utils.py └── visualize.pyCNN分析顺序为genotypes.py候选操作的搜索空间和搜索结果存储格式operations.py候选操作的算法model_search.py搜索阶段的网络architect.py负责架构参数α的优化train_search.py启动搜索的主文件model.py搜索结束后性能评估使用的网络train.py / train_imagenet.py训练性能评估使用的网络utils.py工具函数visualize.py根据genotype画出Cell的有向图test.py / test_imagenet.py性能评估01 genotypes.py存储候选操作的清单定义候选操作的搜索空间和搜索结果的存储格式还存储着已经搜索好的网络在性能评估时调用包括NASNet、AmoebaNet、DARTS_V1、DARTS_V2.候选操作的清单特殊的容器类型搜索结果的存储格式。所存储的网络示例NASNet网络。所存储网络均有一个普通单元和一个降采样单元均为两个输入节点每两条边对应一个中间节点每个元组是表示一条边第一个元素是操作名称第二个元素是该边连接的前一个节点concat指定哪些中间节点的通道拼接得到单元的输出。02 operations.py存储实现候选操作的算法实现候选操作的算法sep_conv_7x7和conv_7x1_1x7并不在候选名单中搜索阶段并不会被选中。算法由lambda函数封装形参为C通道stride步长和affine控制是否启用BN层的仿射参数。仅选择几个代表进行分析零操作将输入张量逐元素乘以浮点数0输出的全零张量在后续计算中无影响。分为两种情况如果在普通单元中则步长为1原尺寸输出如果是在降采样单元中则步长为2逐步长降采样使零操作输出的尺寸和其他操作的输出尺寸相同。跳跃连接恒等映射如果在普通单元中则跳跃连接为恒等映射将输入张量原样输出。分解式降采样如果在降采样单元中则跳跃连接为分解式降采样使用两个1×1卷积核分别采集张量位于偶数、奇数坐标点的特征输出两个尺寸减半的张量再将两个张量沿通道维度拼接归一化后输出。深度可分离卷积两层堆叠的3×3深度可分离卷积。先进行空间提取再进行通道融合。第一层深度卷积一个卷积核只负责一个通道并可完成降采样第一层逐点卷积跨通道融合信息第二层深度卷积步长为1不再降采样第二层逐点卷积将通道映射到目标输出通道。空洞深度可分离卷积dilation扩张系数为 i 时采样点间隔为 i-1单层空洞深度可分离卷积。采样点间隔为1在不增加卷积参数量前提下扩大感受野。与深度可分离卷积相同一层深度卷积一个卷积核只负责一个通道并可完成降采样一层逐点卷积跨通道融合信息。03 model_search.py定义搜索阶段的网络混合操作.ModuleList()模块列表专门用来存放网络层的列表容器定义混合算子。建立模块列表_ops存放一条边候选操作并为池化层增加归一化。前向阶段为混合操作的计算对所有操作输出加权求和作为混合操作的输出。x为输入张量weights为架构权重即αwisoftmax(α)iMixedOp(x,α)∑i1Nwi⋅opi(x) \begin{aligned} w_i \mathrm{softmax}(\boldsymbol{\alpha})_i \\ \mathrm{MixedOp}(x,\boldsymbol{\alpha}) \sum_{i1}^{N} w_i \cdot \mathrm{op}_i(x) \end{aligned}wiMixedOp(x,α)softmax(α)ii1∑Nwi⋅opi(x)单元初始化代码steps单元内需要生成几个中间节点multiplier最终从后往前依次选择中间节点的数量将其输出拼接为单元的输出c_prev_prev前前一个单元的通道数c_prev前一个单元的通道数reduction该单元是否为降采样单元reduction_prev前一个单元是否为降采样单元预处理函数的定义如果与单元相连的前一个单元是降采样单元则将前前一个单元的输入张量经过分解式降采样与单元的通道对齐如果为普通单元则经过ReLUConvBN函数与单元的通道对齐。对于前一个单元的输入经过ReLUConvBN函数与单元的通道对齐。ops存储所有边对应的混合操作bns存储部分操作后配置的归一化操作旧写法新写法中在混合操作定义时已经添加了归一化操作所以这里的bns没有被使用。for循环第 i 个中间节点有 1i 条待搜索的边边在ops中逐个储存每条边对应一个混合操作。如果该单元为降采样单元则当中间节点的边与两个输入节点相连时步长为2进行降采样。前向计算s0为第一个输入节点的输入s1为第二输入节点的输入weights为架构参数即αoffset为游标j为节点在states里的下标h为节点的特征张量首先对两个输入张量进行预处理。因为ops中的边是逐个存储的所以第 i 个中间节点的第 1 条边在ops中的位置为(i)?-1。分析for循环可知offset (i)?-1即为第 i 个中间节点的第 1 条边在ops中的位置对于第 i 个中间节点一共有 1i 个states分别对应 1i 条边的输入经过每条边的混合操作输出求和后即为节点的输出 s 。s(j)∑ijo(i,j)(s(i)) s^{(j)}\sum_{ij}o^{(i,j)}(s^{(i)})s(j)ij∑o(i,j)(s(i))最终从后往前选择multiplier个节点的结果沿通道维度拼接为单元的输出。网络网络初始化C通道数num_classes分类的类别数量layers网络堆叠的层数criterion损失函数steps中间节点数量multiplier拼接节点的个数stem_multiplier输入主干的放大倍数。定义输入主干将原始图片转换为单元可以接受的特征图。for循环将单元堆叠为完整的卷积网络网络三分之一和三分之二处的单元为降采样单元。卷积网络输出的特征图经过全局自适应池化被压缩成 1×1 的特征图消除图像长宽的影响传递给全连接层转换成类别打分完成最终分类。self._initialize_alphas()初始化架构参数α新建网络的副本新建一个一模一样结构的网络副本取副本的原因在architect的分析中解释。前向计算将输入图像经过输入主干转化为单元可以接受的特征图。s0为前前一个单元的输出s1为前一个单元的输出。for循环通过逐个单元计算得到最后的特征图经过全连接层转换成类别打分。损失函数input输入图像target图像类别将图像传入全连接层获得类别打分与图像类别经过损失函数计算得到损失。初始化架构参数αk一个单元内部所有候选边的数量num_ops候选操作的数量初始化架构参数并声明参数可以通过梯度进行优化最后将架构参数打包成列表。取出架构参数的函数保存网络结构定义gene连接的记录weights架构参数α计算得到的操作强度x参数中边对应的位置k一条边中操作对应的位置edges元素数量为第 i 个中间节点的 2 条边第 i 个中间节点有 1i 条边排除无操作的边按照一条边最大的操作强度的大小排序取出最大的两条边即为edges在for循环中排除无操作得到操作强度最大的操作并将操作和其对应的边添加入gene中。定义concat最终从后往前依次的multiplier个中间节点得到普通节点的gene和降采样节点的gene普通节点的concat和降采样节点的concat按照正确的格式存入genotype中保存网络结构04 architect.py核心目的优化架构参数α拼接函数将模型的网络权重展平成一维的长向量便于对网络权重进行线性运算和二阶计算前取副本。用循环可以实现相同的作用但是代码会更啰嗦。Architect初始化代码为α准备优化器。二阶梯度计算前置步骤首先计算损失求出权重梯度取权重副本计算更新后的网络权重副本模拟一次更新。代码中的theta对应公式中的weta对应ξdtheta对应w的梯度权重衰减系数对应λ。w′w−ξ(moment∇wLtrain(w,α)λw) w w - \xi \big(\text{moment} \nabla_w \mathcal L_{\text{train}}(w,\alpha) \lambda w\big)w′w−ξ(moment∇wLtrain(w,α)λw)最后的_construct_model_from_theta将网络权重从一维长向量还原。在这里得到近似最优网络权重w’。取副本的原因理想情况pytorch自动微分计算的限制倘若本地修改网络权重计算图的依赖链路断开无法正确算出α的梯度因此需要取网络副本和权重副本计算权重副本得到近似最优网络权重w’将其传入副本模型在后面还原一维长向量处进行计算得到验证损失进行反向传播避免本地修改网络权重。使用有限近似差分在后面海森项的计算处进行前面相同在反向传播时使用近似最优网络权重w’的梯度vector计算方向向量扰动w可以类似理想情况的计算链路图理解。计算的依赖链路图示逐步进行训练分为二阶梯度计算unrolled和一阶近似梯度计算rolled。二阶梯度计算得到架构参数α的梯度然后使用优化器进行优化。一阶近似梯度计算∇αLval(w′,α) \nabla_{\alpha}\mathcal{L}_{val}(w, \alpha)∇αLval(w′,α)5.** 二阶梯度计算**由近似网络最优权重w’计算得到α的梯度对应代码中的dalphavector为w’的梯度后进入海森项计算模块得到海森项计算结果第一个for循环将α的梯度和海森项相减得到公式中的形式eta对应ξ第二个for循环将计算完成的架构梯度传回模型的架构参数α。∇αLval(w′,α)−ξ∇α,w2Ltrain(w,α)∇w′Lval(w′,α) \nabla_{\alpha}\mathcal{L}_{val}(w, \alpha) -\xi \nabla_{\alpha,w}^2 \mathcal{L}_{train}(w,\alpha) \nabla_{w}\mathcal{L}_{val}(w, \alpha)∇αLval(w′,α)−ξ∇α,w2Ltrain(w,α)∇w′Lval(w′,α)在这里得到架构参数α的梯度。.parameters()取网络权重的张量.arch_parameters()取架构权重的张量.named_parameters()取网络权重的参数名称和张量还原一维长向量model_new得到原模型的复制model_dict是储存原模型参数名称和数值的字典params是储存新模型参数名称和数值的字典offset是游标。for循环逐个取出原模型中的参数计算v_lenth得到原模型中参数在一维长向量中对应的长度以offset为起点取出对应向量再变形为原参数形状即为更新后的参数。断言offset与一维长向量长度相同确定获的参数元素总数等于一维长向量的长度。最后更新模型的参数。海森项的有限近似差分将架构参数的梯度拼接为一维长向量并求范数计算得到归一化后的扰动步长R。第一个for循环得到 wR·v计算得到正向扰动的梯度第二个for循环(wR·v)-2R·v 得到 w-R·v计算得到负向扰动的梯度第三个for循环(w-R·v)R·v 得到 w将模型参数还原。最后返回值为公式的计算结果原论文中ε为r工程中改为R变为动态缩放系数使得数值稳定用以应对梯度过大或梯度过小的问题∇α,w2Ltrain(w,α)∇w′Lval(w′,α)≈∇αLtrain(w,α)−∇αLtrain(w−,α)2ϵ \nabla_{\alpha,w}^2\mathcal{L}_{train}(w,\alpha)\nabla_{w}\mathcal{L}_{val}(w,\alpha) \approx \frac{\nabla_{\alpha}\mathcal{L}_{train}(w^,\alpha)-\nabla_{\alpha}\mathcal{L}_{train}(w^-,\alpha)}{2\epsilon}∇α,w2Ltrain(w,α)∇w′Lval(w′,α)≈2ϵ∇αLtrain(w,α)−∇αLtrain(w−,α)在这里得到海森项的计算结果
阅读完成 · 觉得有帮助?