神经架构搜索

我们在不同的搜索空间中支持不同的神经架构搜索算法。 神经架构搜索通常由三个模块构成:搜索空间、搜索策略和评估策略。

搜索空间描述了所有可能要搜索的架构。空间主要由两部分组成:操作(如GCNconv、GATconv)和输入输出关系。 大空间可能有更好的最优架构,但需要更多的算力来探索。 人类知识可以帮助设计合理的搜索空间,减少搜索策略的开销。

搜索策略则控制着如何来探索搜索空间。 它包含了经典的探索-利用困境。 一方面,人们希望快速找到性能良好的架构, 而另一方面,也需要避免过早收敛到次优架构区域。

评估策略在探索时给出架构的性能。 最简单的方法是对数据执行标准的架构训练和验证。 但由于在搜索过程中有很多架构需要评估,因此需要非常有效的评估策略来节省计算资源。

../../_images/nas1.svg

为了更加灵活易用,我们将神经架构搜索过程分解为三部分:算法、空间和评估器,分别对应于三个模块:搜索空间、搜索策略和评估策略。 不同部分的不同模型可以在满足一定条件的时候组合起来。 如果您想设计自己的神经架构搜索流程,您可以根据需要更改其中的任意部分。

用法

您可以通过将算法、空间与评估器直接传递给求解器来在节点分类任务上启用架构搜索。 下面是一个例子:

# 在cora数据集上使用图神经架构搜索
from autogl.datasets import build_dataset_from_name
from autogl.solver import AutoNodeClassifier

solver = AutoNodeClassifier(
    feature = 'PYGNormalizeFeatures',
    graph_models = (),
    hpo = 'tpe',
    ensemble = None,
    nas_algorithms=['rl'],
    nas_spaces='graphnasmacro',
    nas_estimators=['scratch']
)

cora = build_dataset_from_name('cora')
solver.fit(cora)

上面的代码将先用 rl 搜索算法在空间 GraphnaSmaro 中找到最优架构。 然后通过超参数优化方法 tpe 进一步优化搜索到的架构。

Note

graph_models 参数与神经架构搜索模块不冲突。您可以将 graph_models 设置为 神经架构搜索发现的模型外的,其他手工设计的模型。而神经架构搜索模块派生出来的架构 的作用也与直接通过图模型模块传递的手工设计的模型相同。

搜索空间

空间定义基于开源工具包NNI中使用的可变方式,定义为继承基础空间的模型。 定义搜索空间的方法主要有两种,一种支持单样本方法,而另一种则不支持。 目前,我们支持如下搜索空间:

您也可以定义自己的神经架构搜索空间。 如果需要支持单样本方式,可以使用函数 setLayerChoicesetInputChoice 来构建超网络。 下面是一个例子。

# 创建一个神经架构搜索空间的例子
from autogl.module.nas.space.base import BaseSpace
from autogl.module.nas.space.operation import gnn_map
class YourOneShotSpace(BaseSpace):
    # 在初始化时获取基本参数
    def __init__(self, input_dim = None, output_dim = None):
        super().__init__()
        # 必须在空间中设定输入维度与输出维度,也可以在函数 `instantiate` 中初始化这两个参数`
        self.input_dim = input_dim
        self.output_dim = output_dim

    # 实例化超网络
    def instantiate(self, input_dim = None, output_dim = None):
        # 必须在函数中调用父类的实例化
        super().instantiate()
        self.input_dim = input_dim or self.input_dim
        self.output_dim = output_dim or self.output_dim
        # 按照顺序定义两层网络
        setattr(self, 'layer0', self.setLayerChoice(0, [gnn_map(op,self.input_dim,self.output_dim)for op in ['gcn', 'gat']], key = 'layer0')
        setattr(self, 'layer1', self.setLayerChoice(1, [gnn_map(op,self.input_dim,self.output_dim)for op in ['gcn', 'gat']], key = 'layer1')
        # 定义一个从两层的结果中选择的输入选项
        setattr(self, 'input_layer', self.setInputChoice(2, choose_from = ['layer0', 'layer1'], n_chosen = 1, returen_mask = False, key = 'input_layer'))
        self._initialized = True

    # 定义前向传播过程
    def forward(self, data):
        x, edges = data.x, data.edge_index
        x_0 = self.layer0(x, edges)
        x_1 = self.layer1(x, edges)
        y = self.input_layer([x_0, x_1])
        y = F.log_fostmax(y, dim = 1)
        return y

    # 对于单样本范式,您可以使用如 ``parse_model`` 函数中的方法
    def parse_model(self, selection, device) -> BaseModel:
        return self.wrap().fix(selection)

您也可以使用不支持单样本范式的方式。 这样的话,您可以直接复制模型,并进行少量更改。 但相应的,您也只能使用基于样本的搜索策略。

# 创建一个神经架构搜索空间的例子
from autogl.module.nas.space.base import BaseSpace, map_nn
from autogl.module.nas.space.operation import gnn_map
# 在这里,我们以 `head` 作为参数,在三种图卷积上进行搜索
# 在搜索 `heads` 时,我们在搜索图卷积
from torch_geometric.nn import GATConv, FeaStConv, TransformerConv
class YourNonOneShotSpace(BaseSpace):
    # 在初始化时获取基本参数
    def __init__(self, input_dim = None, output_dim = None):
        super().__init__()
        # 必须在空间中设定输入维度与输出维度,也可以在函数 `instantiate` 中初始化这两个参数`
        self.input_dim = input_dim
        self.output_dim = output_dim

    # 实例化超网络
    def instantiate(self, input_dim, output_dim):
        # 必须在函数中调用父类的实例化
        super().instantiate()
        self.input_dim = input_dim or self.input_dim
        self.output_dim = output_dim or self.output_dim
        # 设置你每一层的选择
        self.choice0 = self.setLayerChoice(0, map_nn(["gat", "feast", "transformer"]), key="conv")
        self.choice1 = self.setLayerChoice(1, map_nn([1, 2, 4, 8]), key="head")

    # 不要忘记在这里定义前向传播过程
    # 对于非单样本范式,您可以直接返回选择下的模型
    # ``YourModel`` 也就是您的模型必须继承基础空间
    def parse_model(self, selection, device) -> BaseModel:
        model = YourModel(selection, self.input_dim, self.output_dim).wrap()
        return model
# ``YourModel`` 也就是您的模型定义如下
class YourModel(BaseSpace):
    def __init__(self, selection, input_dim, output_dim):
        self.input_dim = input_dim
        self.output_dim = output_dim
        if selection["conv"] == "gat":
            conv = GATConv
        elif selection["conv"] == "feast":
            conv = FeaStConv
        elif selection["conv"] == "transformer":
            conv = TransformerConv
        self.layer = conv(input_dim, output_dim, selection["head"])

    def forward(self, data):
        x, edges = data.x, data.edge_index
        y = self.layer(x, edges)
        return y

性能评估器

性能评估器用于评估一个架构的优劣. 目前我们支持如下一些评估器:

您也可以自己定义一个评估器. 下面是一个无需训练即可评估架构效果的评估器的例子 (通常应用于one-shot space).

# 例如,您也可以自己定义一个estimator
from autogl.module.nas.estimator.base import BaseEstimator
class YourOneShotEstimator(BaseEstimator):
    # 您所需要做的只是定义``infer``这个方法
    def infer(self, model: BaseSpace, dataset, mask="train"):
        device = next(model.parameters()).device
        dset = dataset[0].to(device)
        # 对架构直接进行前向传播
        pred = model(dset)[getattr(dset, f"{mask}_mask")]
        y = dset.y[getattr(dset, f'{mask}_mask')]
        # 使用默认的损失函数和评价指标来评估架构效果,当然,在这里您也可以选择其他的损失函数和评价指标
        loss = getattr(F, self.loss_f)(pred, y)
        probs = F.softmax(pred, dim = 1)
        metrics = [eva.evaluate(probs, y) for eva in self.evaluation]
        return metrics, loss

搜索策略

搜索策略定义了如何去搜索一个好的子架构. 目前我们支持如下一些搜索策略:

基于采样的非共享权重的搜索策略在实现上更加简单 接下来,我们将向您展示如何自定义一个基于DFS的搜索策略来作为一个例子 如果您想要自定义更多复杂的搜索策略,您可以去参考NNI中Darts、Enas或者其他搜索策略的实现

from autogl.module.nas.algorithm.base import BaseNAS
class RandomSearch(BaseNAS):
    # 接收需要采样的数量作为初始化
    def __init__(self, n_sample):
        super().__init__()
        self.n_sample = n_sample

    # NAS算法流程中的关键步骤,这个方法会根据给定的search space、dataset和estimator去搜索一个合理的架构
    def search(self, space: BaseSpace, dset, estimator):
        self.estimator=estimator
        self.dataset=dset
        self.space=space

        self.nas_modules = []
        k2o = get_module_order(self.space)
        # 寻找并存储search space中所有的mutables,这些mutables就是您在search space中定义的可搜索的部分
        replace_layer_choice(self.space, PathSamplingLayerChoice, self.nas_modules)
        replace_input_choice(self.space, PathSamplingInputChoice, self.nas_modules)
        # 根据给定的orders对mutables进行排序
        self.nas_modules = sort_replaced_module(k2o, self.nas_modules)
        # 得到包含所有可能选择的一个字典
        selection_range={}
        for k,v in self.nas_modules:
            selection_range[k]=len(v)
        self.selection_dict=selection_range

        arch_perfs=[]
        # 定义DFS的流程
        self.selection = {}
        last_k = list(self.selection_dict.keys())[-1]
        def dfs():
            for k,v in self.selection_dict.items():
                if not k in self.selection:
                    for i in range(v):
                        self.selection[k] = i
                        if k == last_k:
                            # 评估一个架构的效果
                            self.arch=space.parse_model(self.selection,self.device)
                            metric,loss=self._infer(mask='val')
                            arch_perfs.append([metric, self.selection.copy()])
                        else:
                            dfs()
                    del self.selection[k]
                    break
        dfs()

        # 得到在搜索过程中拥有最好效果的架构
        selection=arch_perfs[np.argmax([x[0] for x in arch_perfs])][1]
        arch=space.parse_model(selection,self.device)
        return arch

不同的搜索策略需要与特定的搜索空间与评估器搭配使用 这与它们的实现相关,如非one-shot的搜索空间不能与one-shot的搜索策略搭配使用 下面的表格中给出了我们目前所支持的搭配组合

Space single path GraphNAS[1] GraphNAS-macro[1]
Random
RL
GraphNAS [1]
ENAS [2]    
DARTS [3]    
Estimator one-shot Train
Random  
RL  
GraphNAS [1]  
ENAS [2]  
DARTS [3]  
[1](1, 2) Gao, Yang, et al. “Graph neural architecture search.” IJCAI. Vol. 20. 2020.
[2](1, 2) Pham, Hieu, et al. “Efficient neural architecture search via parameters sharing.” International Conference on Machine Learning. PMLR, 2018.
[3](1, 2) Liu, Hanxiao, Karen Simonyan, and Yiming Yang. “DARTS: Differentiable Architecture Search.” International Conference on Learning Representations. 2018.
[4]Guo, Zichao, et al. “Single Path One-Shot Neural Architecture Search with Uniform Sampling.” European Conference on Computer Vision, 2019, pp. 544–560.