Created by gh-md-toc
相比较 ENAS 中使用增强算法,通过验证集的 reward 进行优化,darts 使用梯度下降优化验证集的 loss,
1. Darts Paper Reader
相对于 NAS 或者 ENAS 这种离散化的黑盒优化问题,Darts 将离散集合松弛到连续空间,通过梯度下降在验证集上验证
- 每个 Cell 固定 N 个node
- 对于卷积 N 的前两个 node 是输入节点,最后为输出节点、循环神经网络的输入是当前step 和之前的 step 计算所得
- 为什么到最后有的 node 间连接,有的不连接? 依赖于 zero operator
类似 enas 中的 micro 功能但是不是基于 RNN 选择(也就是没有控制器),首先选择 Cell 后按照规则 Stack, 有额外的 zero operator ,代表两个 node 无关联
continuous relaxation(连续松弛操作):对 softmax 的选择单个操作做松弛,松弛到全部可能
$\begin{array}{ll}{\min {\alpha}} & {\mathcal{L}{v a l}\left(w^{}(\alpha), \alpha\right)} \ {\text { s.t. }} & {w^{}(\alpha)=\operatorname{argmin}{w} \mathcal{L}{t r a i n}(w, \alpha)}\end{array}$ 最小化验证集 loss获取 α 参数,其中最小化训练集 loss 获取 w 参数,这里是共同学习,两者随之变动的计算量很大。这里的α类似于一个超参数但是相对于学习率这些超参数维度更大优化更难。
第一步更新:$\begin{aligned} & \nabla_{\alpha} \mathcal{L}{v a l}\left(w^{*}(\alpha), \alpha\right) \ \approx & \nabla{\alpha} \mathcal{L}{v a l}\left(w-\xi \nabla{w} \mathcal{L}_{t r a i n}(w, \alpha), \alpha\right) \end{aligned}$
这一步是一个 trick: 没有使用 inner 优化(因为计算量很大)(这里如果是局部最优 $\begin{aligned} & \nabla_{\alpha} \mathcal{L}{v a l}\left(w^{*}(\alpha), \alpha\right) \ = & \nabla{\alpha} \mathcal{L}{v a l}\left(w, \alpha\right) \end{aligned}$,则梯度$\nabla{w} \mathcal{L}_{t r a i n}(w, \alpha)=0$,通过调节learning rate
由上式可得: $\nabla_{\alpha} \mathcal{L}{v a l}\left(w^{\prime}, \alpha\right)-\xi \nabla{\alpha, w}^{2} \mathcal{L}{t r a i n}(w, \alpha) \nabla{w^{\prime}} \mathcal{L}{v a l}\left(w^{\prime}, \alpha\right)$ ,其中 $w^{\prime}=w-\xi \nabla{w} \mathcal{L}{\operatorname{train}}(w, \alpha)$ 是一步 forward 权重,第二部分的计算还是很昂贵;当 $\xi$ 为0 时为one-order approximation 一阶近似,大于0 的时候为 二阶近似,$\xi$ (一般确定使用 $\epsilon=0.01 /\left|\nabla{w^{\prime}} \mathcal{L}{v a l}\left(w^{\prime}, \alpha\right)\right|{2}$
$w^{ \pm}=w \pm \epsilon \nabla_{w^{\prime}} \mathcal{L}{v a l}\left(w^{\prime}, \alpha\right)$ 得到 $\nabla{\alpha, w}^{2} \mathcal{L}{t r a i n}(w, \alpha) \nabla{w^{\prime}} \mathcal{L}{v a l}\left(w^{\prime}, \alpha\right) \approx \frac{\nabla{\alpha} \mathcal{L}{t r a i n}\left(w^{+}, \alpha\right)-\nabla{\alpha} \mathcal{L}_{t r a i n}\left(w^{-}, \alpha\right)}{2 \epsilon}$ (finite difference approximation 有限差分近似) 两次向前传播,后两次向后传播得到 α,复杂度由 O(α*w) 到 O(α+w)
确定固定的操作集合(arch)
while not converaged do
更新α参数根据 Val的 loss 计算梯度并进行更新 #bilevel optimization problem Approximation :近似双层优化
更新w参数根据 train 的 loss 计算梯度并更新
通过学习 α 得到最终搜索到的框架
- 首先进行 Cell 选择,validate 确定最佳 Cell
- 用这些 Cell 构建 ARCH , train 后 validate 最佳 (注意这里的 arc 是固定的)
- 验证 The best cell 的迁移能力
搜索出的 genotype 注意是两两一组合(这里是不是有局限性或者更多的可能性),最后 concat 操作
-
首先初始化 alphas(是一个参数矩阵 tensor ,注意个数是和 node 节点个数相同,这里的 node 数目是不包含输入的两个节点以及最后 concat 的节点), 合格 alphas 会在之后计算 mixOp 的时候作为加权参数
-
调用 SearchCNN 类生成 net,这是按照一定的规则做的(默认8层,8/3和8*2/3 层间是执行 reduce 操作,同时 channel * 2 ),调用 SearchCell 搜索 Cell
-
SearchCell 根据传入的前一个 cell 是否 reduce ,是的话 c_{k-1} 节点将进行 FactorizedReduce 操作,c{k-2} 进行 StdConv,构建 dag 的时候需要考虑到两个输入节点,通过 遍历得到一个 dag 注意这个 dag 是类似于paper 中的全部连接并且全部节点上有 MixedOp 构建的所有的待选 op,构建出整个图
for i in range(self.n_nodes): self.dag.append(nn.ModuleList()) for j in range(2+i): # include 2 input nodes # reduction should be used only for input node stride = 2 if reduction and j < 2 else 1 op = ops.MixedOp(C, stride) self.dag[i].append(op) for primitive in gt.PRIMITIVES: op = OPS[primitive](C, stride, affine=False) self._ops.append(op
-
对于参数 权重w 和 框架α 分别使用 Momentum 和 Adam 进行优化
-
train 方法中
# architect 已经加载 model, 这里计算展开 loss 并进行梯度下降 architect.unrolled_backward(train_image, train_label, val_image, val_label, w_optim) # unrolled_backward 执行以下步骤 # 1. 主要是更新 w 对应论文中的 等式4 的条件 self.virtual_step(train_image, train_label, w_optim) # 2. 用 val 计算新的 unroll loss,对应论文 等式3 使用验证集 loss = self.v_net.loss(val_image, val_label) # 计算梯度, 这里体现论文中的一句话, both loss is not only determined by architect α but also weight w, 计算联合下降梯度 v_alphas = tuple(self.v_net.alphas()) v_weights = tuple(self.v_net.weights()) v_grads = torch.autograd.grad(loss, v_alphas + v_weights) dalpha = v_grads[:len(v_alphas)] dw = v_grads[len(v_alphas):] # 对应论文的等式8,其中 eps 的求解对应论文中的注释2 hessian = self.compute_hessian(dw, train_image, train_label) # 最后更新梯度,对应等式 7 update final gradient = dalpha - xi*hessian with torch.no_grad(): for alpha, da, h in zip(self.net.alphas(), dalpha, hessian): alpha.grad = da - xi*h
-
得到 child model 后进行梯度下降
-
进行梯度 clip,优化计算准确率
-
解析最后生成的 Cell
genotypes.py 中的 parse 方法
-
AugmentCNN
#注意传入搜索出的 genotype, 默认层数为 20 (注意在 20/3 和 40/3 这两层 channel*2 执行 reduce 操作), 默认使用 aux model = AugmentCNN(input_size, input_channels, config.init_channels, n_classes, config.layers, use_aux, config.genotype) # 注意最后使用 AdaptiveAvgPool2d
-
AugmentCell
# cell = AugmentCell(genotype, C_pp, C_p, C_cur, reduction_p, reduction) # AugmentCell 做以下事情 #1. 首先两个 node 作为输入, 注意两个 Node 一个时之前的 Cell 的前 node 以及 前前 node,如果有 reduce 操作会在第一个 Node 做 if reduction_p: self.preproc0 = ops.FactorizedReduce(C_pp, C) else: self.preproc0 = ops.StdConv(C_pp, C, 1, 1, 0) self.preproc1 = ops.StdConv(C_p, C, 1, 1, 0) # generate dag if reduction: gene = genotype.reduce self.concat = genotype.reduce_concat else: gene = genotype.normal self.concat = genotype.normal_concat self.dag = gt.to_dag(C, gene, reduction)
-
生成 DAG
def to_dag(C_in, gene, reduction): """ generate discrete ops from gene """ dag = nn.ModuleList() for edges in gene: row = nn.ModuleList() for op_name, s_idx in edges: # reduction cell & from input nodes => stride = 2 stride = 2 if reduction and s_idx < 2 else 1 op = ops.OPS[op_name](C_in, stride, True) if not isinstance(op, ops.Identity): # Identity does not use drop path op = nn.Sequential( op, ops.DropPath_() ) op.s_idx = s_idx row.append(op) dag.append(row) return dag
-
生成DAG 按照正常的 Model 训练
-
为什么使用 aux_head? Auxiliary head in 2/3 place of network to let the gradient flow well