LycorisNet之超参数自动搜寻策略
在设计Lycoris的伊始,就没有考虑过为用户留下太多的接口来调整超参数,毕竟对现代框架调参的厌烦也是独立开发这个库的动力之一。于是,自动搜寻最佳超参数的策略便成为了一个相当重要的需求。
但凡提到自动调整超参数,“网格搜索”、“随机搜索”、“贝叶斯优化”等等便习惯性地被提及。Grid Search是一种调参手段,也称为穷举搜索:在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果,其原理就像是在数组里找最大值;随机搜索顾名思义就是随机的搜索,没有特别的要说的,举个例子:有十亿个数字,我想搜索十万次就找出一个比较理想的最小(大)数。这就是一个简单的应用随机搜索的一个场景。这时候我们需要设计一个随机取样的函数,然后在十亿个数里取出十万个数进行比较获取最小(大)的数。虽然后最的结果不精确,但是如果并不需要知道确切的最优值的时候,这还是非常棒的提高程序效率的算法;贝叶斯优化其实就是在函数方程不知的情况下根据已有的采样点预估函数最大值的一个算法,贝叶斯优化会选取未知函数的中数个已知点,作为先验(prior),假设这些点是GP中的一部分,即他们服从多变量高斯分布。根据多变量高斯分布的一些性质,可以计算出这些点中每一个点的均值(mean)和方差(variance) 。
在Lycoris中这三种优化方案均不能直接用于策略中,主要原因还是增强拓扑的过程带来的不稳定性,这种情况下,项目组采用了一种近似随机搜索思想的优化方案,对NEAT的超参数实现自动化调整,这一部分的代码如下:
void Lycoris::autoParameter() {
if (args->checkFlag) {
auto length = uint32_t(args->gapList->size());
auto lastValue = args->gapList->back();
uint32_t count = 0;
for (auto iter = args->gapList->begin(); iter != args->gapList->end(); ++iter) {
if (lastValue > (*iter)) {
count++;
}
}
if (count < (length / 2 + 1)) {
args->miss++;
args->hit = 0;
if (args->miss == 2) {
if (args->tock > 1) {
args->tock /= 2;
}
args->miss = 1;
}
args->p1 = args->p1B;
args->p2 = args->p2B;
args->p3 = args->p3B;
args->p4 = args->p4B;
args->p5 = args->p5B;
args->p6 = args->p6B;
args->mateOdds = args->mateOddsB;
args->mutateOdds = args->mutateOddsB;
args->mutateTime = args->mutateTimeB;
} else {
args->hit++;
args->miss = 0;
if (args->hit == 2) {
if (args->tock < args->maxTock) {
args->tock *= 2;
}
args->hit = 1;
}
}
args->checkFlag = false;
}
if (args->tick == args->tock) {
emergeArgs();
args->checkFlag = true;
args->tick = 0;
} else {
args->tick += 1;
}
}
从代码可以看出还借鉴了少许的“tick-tock”思想。