本文介绍NNIPyTorch版实现神经网络过程搜索过程中的几个重要的类,比如LayerChoice和InputChoice,对这两个类有了初步认识以后,就可以设计自己的搜索空间。
1.Mutable类笔者画的类图(后续会扩充)上图是NNI的有关NAS的部分类图,Mutable类表示的意思是可变的,这也是实现NAS中的核心,操作是可变动的,具体选择什么操作需要优化器,也就是tuner来决定。
Mutable被设计成一个普通层,具有所有操作的权重。
Mutator中应该包含网络架构的状态和权重,而不是层本身。
Mutable对象有一个key,用于标记mutable对象的身份。用户可以根据key来进行共享不同mutable对象之间的决定。
在Mutator的实现中,Mutator应该使用key区分不同的mutable对象。如果两个mutable对象的key是相同的,说明并不需要对其进行区分,即这两个mutable对象是相似的。
当前key的默认作用域是全局的。默认情况下,key使用counter从1开始计数,来自动生成uniqueid
Mutable类属于模型级别的设置,counter是程序级别的。
classMutable(nn.Module):def__init__(self,key=None):super().__init__()ifkeyisnotNone:ifnotisinstance(key,str):key=str(key)logger.warning("Warning:key\"%s\"isnotstring,convertedtostring.",key)self._key=keyelse:self._key=self.__class__.__name__+str(global_mutable_counting())self.init_hook=self.forward_hook=None
在初始化的时候,需要接收key,如果没有特别设置key,那就通过global_mutable_counting()方法返回全局变量counter数量。
2.MutableScopeMutableScope代码实现非常短,如下:
classMutableScope(Mutable):def__init__(self,key):super().__init__(key=key)def__call__(self,*args,**kwargs):try:self._check_built()self.mutator.enter_mutable_scope(self)returnsuper().__call__(*args,**kwargs)finally:self.mutator.exit_mutable_scope(self)
MutableScope是继承了Mutable对象,也有一个key,他是比操作更高层次的抽象。类似的概念有子图,子模块,可以看作一系列操作的集合。
MutableScope可以更好的帮助Mutator做决策,将其看作略高层次的抽象。如果没有标注为mutablescope,那么搜索空间将会展开为一个列表。如果一个模块是在mutablescope中定义,那么将被视为sub-search-space,子搜索空间,并且这些mutablescope之间也可以相互嵌套。Mutator有两种方法使用mutablescope:一种是初始化的时候,通过树的形式进行初始化搜索空间。另一种是实现enter_mutable_scope和exit_mutable_scope两个方法MutableScope也是一种Mutable对象,只不过其比较特殊,包含的内容不是普通的操作opration,而是Mutable对象。MutableScope也会在搜索空间中被枚举出来,但是不应该出现在选项的字典中。3.LayerChoiceLayerChoice类的核心功能是从候选操作中挑选一个,将该操作施加到输入得到输出结果。在特殊情况下,可以选择zero或者选择多个操作。LayerChoice不允许嵌套。主要有以下几个参数:
op_candidates:候选操作,可以是nn.Module列表或字典reduction:可以从mean,concat,sum,none几种选择。return_mask:决定返回结果是否包含maskkey:input_choice的keyclassLayerChoice(Mutable):def__init__(self,op_candidates,reduction="sum",return_mask=False,key=None):super().__init__(key=key)self.names=[]ifisinstance(op_candidates,OrderedDict):forname,moduleinop_candidates.items():assertnamenotin["length","reduction","return_mask","_key","key","names"],\"Pleasedontuseareservedname{}foryourmodule.".format(name)self.add_module(name,module)#添加模块进来self.names.append(name)elifisinstance(op_candidates,list):fori,moduleinenumerate(op_candidates):self.add_module(str(i),module)self.names.append(str(i))#list的画就手动添加nameelse:raiseTypeError("Unsupportedop_candidatestype:{}".format(type(op_candidates)))self.reduction=reductionself.return_mask=return_mask#是否同时returnmask和tensor
可以看出LayerChoice就是一个类似于列表的类,其中包含了候选的操作,可以通过add_module的方式将候选操作添加到LayerChoice这个类中。
defforward(self,*args,**kwargs):"""Returns-------tupleoftensorsOutputandselectionmask.If``return_mask``is``False``,onlyoutputisreturned."""out,mask=self.mutator.on_forward_layer_choice(self,*args,**kwargs)ifself.return_mask:returnout,maskreturnout
前向传播的时候,是mutator的on_forward_layer_choice函数进行控制具体的操作,return_mask控制是否同时输出mask和tensor。
一个调用的例子:
self.op_choice=LayerChoice(OrderedDict([("conv3x3",nn.Conv2d(3,16,)),("conv5x5",nn.Conv2d(5,16,)),("conv7x7",nn.Conv2d(7,16,))]))4.InputChoice
InputChoice是用来解决网络层与层之间连接的问题,有以下几个参数:
n_candidates:是一个数,选择多少个作为inputchoose_from:是一个装满key的列表,都是过去已经生成的mutable对象的key。也可以是InputChoice.NO_KEY代表n_chosen:选择的输入的个数,如果不设置,那就可以选择任何数量的组合。reduction:规约方式有mean,concat,sum,none。return_maskkey同上。综合来说,InputChoice就是从choose_from对应key中选择n_chosen个输入,其中n_candidates决定了forward函数中,候选选项中选择的个数。
举个例子:
classCell(MutableScope):passclassNet(nn.Module):def__init__(self):self.cell1=Cell("cell1")self.cell2=Cell("cell2")self.op=LayerChoice([conv3x3(),conv5x5()],key="op")self.input_choice=InputChoice(choose_from=["cell1","cell2","op",InputChoice.NO_KEY])defforward(self,x):x1=max_pooling(self.cell1(x))x2=self.cell2(x)x3=self.op(x)x4=torch.zeros_like(x)returnself.input_choice([x1,x2,x3,x4])
InputChoice的源码实现:
classInputChoice(Mutable):NO_KEY=""def__init__(self,n_candidates=None,choose_from=None,n_chosen=None,reduction="sum",return_mask=False,key=None):super().__init__(key=key)#preconditioncheckassertn_candidatesisnotNoneorchoose_fromisnotNone,"Atleastoneof`n_candidates`and`choose_from`"\"mustbenotNone."ifchoose_fromisnotNoneandn_candidatesisNone:n_candidates=len(choose_from)#choose_from不为None,n_candidate就是其长度elifchoose_fromisNoneandn_candidatesisnotNone:choose_from=[self.NO_KEY]*n_candidates#将空白字符串作为keyassertn_candidates==len(choose_from),"Numberofcandidatesmustbeequaltothelengthof`choose_from`."assertn_candidates0,"Numberofcandidatesmustbegreaterthan0."assertn_chosenisNoneor0=n_chosen=n_candidates,"ExpectedselectednumbermustbeNoneornomore"\"thannumberofcandidates."self.n_candidates=n_candidatesself.choose_from=choose_from.copy()self.n_chosen=n_chosenself.reduction=reductionself.return_mask=return_maskdefforward(self,optional_inputs):#optional_inputs是一个列表,里边是所有可选的输入张量optional_input_list=optional_inputsifisinstance(optional_inputs,dict):optional_input_list=[optional_inputs[tag]fortaginself.choose_from]assertisinstance(optional_input_list,list),\"Optionalinputlistmustbealist,nota{}.".format(type(optional_input_list))assertlen(optional_inputs)==self.n_candidates,\"Lengthoftheinputlistmustbeequaltonumberofcandidates."out,mask=self.mutator.on_forward_input_choice(self,optional_input_list)ifself.return_mask:returnout,maskreturnout
前向传播的选择还是通过调用mutator的on_forward_input_choice函数来决定选择哪条路径连接。
本文主要介绍了nni中搜索空间指定最核心的几个类,通过使用这些类就可以做到构建自己的搜索空间。最近nni更新了2.1版本retiarii等新的功能特性,允许用户以高度的灵活性表达各种搜索空间,重用许多前沿搜索算法,更加易用,准备踩坑。
-END-
预览时标签不可点收录于话题#个上一篇下一篇