NSGA-NET中的micro内部细节搜索

NSGA-NET中的micro内部细节搜索

micro做了啥

这篇文章的micro采用的是nasnet的搜索目标以及编码方式,只不过nasnet采用的是强化学习(如下图所示),作者这里将其更改为ea相关的算法来进行优化。搜索的目标主要是一个normal cell和一个reduction cell,构建网络的时候两个cell交替排列。用ea进行优化的时候,目标也是acc和flops。

名称

优化的问题描述继承了pymop这个库中的Problem这个类,优化的方法调用了pymoo的minimize函数,不如vega中的算法step比较明确

micro的编码方式

每个cell单元都包含5个block,每个block有两个节点,描述当前基本单元的op类型以及输入的index在哪

比如:

image

每两个array构成一个基本的单元,NAGA-NET作者将他们的输出都进行相加。比如[array([7, 0]), array([6, 1])],表示这个block的两个operation分别为7和6,输入的index分别为0, 1。然后当前的输出的index作为2,方便后续的节点使用这个输出。一共有5个这样的block,所以对于一个normal cell或者reduction cell而言,编码长度为4*5=20,当然作者这里是将normal cell和reduction cell一起搜索的,所以群体中个体的基因编码的长度是40。

根据这样的编码方式就可以得到网络的节点以及拓扑方式,如下是一些例子

image

random产生新个体

作者在继承problem这个类的时候,定义了low boundry和up boundry这两个变量,如下图,这两个变量在random搜索的时候回用到,即random产生新个体的时候会在其中用到在上下界之间随机选取数值。

image

交叉和变异

作者没有显示的写出来crossover和mutation的方式,但是调用了pymoo中的两个函数分别是PointCrossover和PolynomialMutation。

def nsganet(
        pop_size=100,
        sampling=RandomSampling(var_type=np.int),
        selection=TournamentSelection(func_comp=binary_tournament),
        crossover=PointCrossover(n_points=2),  # 一种crossover的方式,双点crossover
        mutation=PolynomialMutation(eta=3, var_type=np.int),  # 一种mutation的方式
        eliminate_duplicates=True,
        n_offsprings=None,
        **kwargs):

交叉

pointcrossover函数如下

class PointCrossover(Crossover):

    def __init__(self, n_points):  # 作者在这里设置的是两点交叉
        super().__init__(2, 2)
        self.n_points = n_points

    def _do(self, problem, pop, parents, **kwargs):

        # get the X of parents and count the matings
        X = pop.get("X")[parents.T]  # X为选取的两个parents
        _, n_matings, n_var = X.shape  # n mating 为1,获取基因的长度

        # start point of crossover
        r = np.row_stack([random.perm(n_var-1) + 1 for _ in range(n_matings)])[:, :self.n_points]  # 选取index
        r.sort(axis=1)
        r = np.column_stack([r, np.full(n_matings, n_var)])

        # the mask do to the crossover
        M = np.full((n_matings, n_var), False)

        # create for each individual the crossover range
        for i in range(n_matings):

            j = 0
            while j < r.shape[1] - 1:
                a, b = r[i, j], r[i, j + 1]
                M[i, a:b] = True  # 选取一个片段,进行交换
                j += 2

        _X = crossover_mask(X, M)  # 根据mask,对X进行变异,_X为变异之后的基因片段
        return pop.new("X", _X)

其中X为选取的parent,包含两个基因,通过random选取r,即为两个index,最后交换着两个基因片段内的基因。以下为程序debug过程中的输出

image

X为选取的两个上一代的样本,_X为产生的子代,M为mask,表示是否交换基因片段。

变异

变异作者调用的是PolynomialMutation函数,即多项式变异,多项式变异该函数的执行过程如下

\[v_k' = v_k +\delta\times(u_k-l_k) \]

\[\delta=\left\{ \begin{aligned} &[2\times u+(1-2\times u)(1-\delta_1)^{\eta_m+1}]^{\frac{1}{\eta_m+1}}-1 && if \ u\leq0.5\\ &1-[2\times(1-u)+2\times(u-0.5)(1-\delta_2)^{\eta_m+1}]^{\frac{1}{\eta_m+1}} && if\ u>0.5 \end{aligned} \right. \]

其中\(\delta_1 = (v_k-l_k)/(u_k-l_k)\), \(\delta_2 = (u_k-v_k)/(u_k-l_k)\)\(u\)是一个[0,1]区间内的随机数。\(\eta_m\)是由用户选取的分布指数。\(u_k\)\(l_k\)分别是对应基因位的上下界,\(v_k\)是所选取的基因位, \(u\)为随机生成的数

代码实现如下

    def _do(self, problem, pop, **kwargs):
        pdb.set_trace()
        X = pop.get("X").astype(np.double)  # 选取两个样本,而不是一个
        Y = np.full(X.shape, np.inf)

        if self.prob is None:
            self.prob = 1.0 / problem.n_var  # 1/40.

        do_mutation = random.random(X.shape) < self.prob  # 长度越大,做mutation的概率越小,平均是选取一位来做mutation

        Y[:, :] = X
        # 以下,根据选取的基因位,选择其上下boundary
        xl = np.repeat(problem.xl[None, :], X.shape[0], axis=0)[do_mutation]
        xu = np.repeat(problem.xu[None, :], X.shape[0], axis=0)[do_mutation]

        if self.var_type == np.int:  # true
            xl -= 0.5  # 下界由0减少0.5,
            xu += (0.5 - 1e-16)  # 上界+0.5

        X = X[do_mutation]  # 选取需要变异的基因位

        delta1 = (X - xl) / (xu - xl)  # 计算delta1
        delta2 = (xu - X) / (xu - xl)  # 计算delta2

        mut_pow = 1.0 / (self.eta + 1.0)

        rand = random.random(X.shape)
        mask = rand <= 0.5
        mask_not = np.logical_not(mask)  # 逻辑not

        deltaq = np.zeros(X.shape)
        # 计算如下的value,并且将结果赋给delta
        xy = 1.0 - delta1
        val = 2.0 * rand + (1.0 - 2.0 * rand) * (np.power(xy, (self.eta + 1.0)))
        d = np.power(val, mut_pow) - 1.0
        deltaq[mask] = d[mask]

        xy = 1.0 - delta2
        val = 2.0 * (1.0 - rand) + 2.0 * (rand - 0.5) * (np.power(xy, (self.eta + 1.0)))
        d = 1.0 - (np.power(val, mut_pow))
        deltaq[mask_not] = d[mask_not]

        # mutated values
        _Y = X + deltaq * (xu - xl)  # 得到的结果为浮点数

        # back in bounds if necessary (floating point issues)
        _Y[_Y < xl] = xl[_Y < xl]
        _Y[_Y > xu] = xu[_Y > xu]

        # set the values for output
        Y[do_mutation] = _Y  # 将变异得到的结果赋给下一代

        if self.var_type == np.int:
            Y = np.rint(Y).astype(np.int)

        off = OutOfBoundsRepair().do(problem, pop.new("X", Y))
        return off

其中problem中存储的是刚刚说到的基因编码的上下界, 如下图

image

选取需要变异的基因位如下图,每个基因平均选一位进行变异,

image

原来的基因如下

image

变异之后的结果如下

image

可以看到进行上面操作之后,基因还是和原来几乎一样,变化的不是特别的大。

posted on 2021-05-26 14:12  YongjieShi  阅读(242)  评论(2编辑  收藏  举报

导航