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在哪
比如:
每两个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。
根据这样的编码方式就可以得到网络的节点以及拓扑方式,如下是一些例子
random产生新个体
作者在继承problem这个类的时候,定义了low boundry和up boundry这两个变量,如下图,这两个变量在random搜索的时候回用到,即random产生新个体的时候会在其中用到在上下界之间随机选取数值。
交叉和变异
作者没有显示的写出来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过程中的输出
X为选取的两个上一代的样本,_X为产生的子代,M为mask,表示是否交换基因片段。
变异
变异作者调用的是PolynomialMutation函数,即多项式变异,多项式变异该函数的执行过程如下
其中\(\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中存储的是刚刚说到的基因编码的上下界, 如下图
选取需要变异的基因位如下图,每个基因平均选一位进行变异,
原来的基因如下
变异之后的结果如下
可以看到进行上面操作之后,基因还是和原来几乎一样,变化的不是特别的大。
posted on 2021-05-26 14:12 YongjieShi 阅读(242) 评论(2) 编辑 收藏 举报