首页 > 代码库 > NeuralFinder:集成人工生命和遗传算法自动发现神经网络最优结构

NeuralFinder:集成人工生命和遗传算法自动发现神经网络最优结构

/* 版权声明:可以任意转载,转载时请标明文章原始出处和作者信息 .*/

                          张俊林 黄通文 马柏樟  薛会萍      

              

一.为什么要做神经网络结构自动发现

16年年中开始,我们开始思考最优的深度神经网络结构自动发现的问题,并在业余时间开始逐步做些探索性的实验。当时的出发点其实很简单:对于解决某个机器学习任务,目前的常规做法是通过算法研发人员分析问题特性,并不断设计修改试探深度神经网络的结构,找到最适合解决手头问题的网络结构,然后通过不断调参来获得解决问题的最优网络结构及其对应的参数。很明显,这里需要耗费研发人员大量的精力,大家知道,AI在各个行业逐步辅助和替代人是个大的发展趋势,那么自然就产生一个问题:在探索解决手头问题的深度神经网络结构方面既然耗费了大量的精力,那么这个工作能不能让机器自动完成呢?也就是这项工作的初衷是代替深度学习研发工程师的日常工作。我觉得这个思路是可行的,而且将来也一定是这个行业的发展趋势,而且初步调研了一下当时还没有发现有人发表相关的工作,有一定的前沿性,并且如果真的可行,在实际工作中也可以用起来节省人力研发成本,于是就开始想些办法探索这个方向。

我觉得将来深度学习研发的形态不会是目前这个样子,而应该是如下的一种场景:假设我们手头有一个技术成熟的神经网络自动探索最优结构的系统(比如它的名字叫NeuralFinder)的话,那么整个研发过程不需要深度学习工程师的介入,只需要给定某个问题的训练数据,然后启动NeuralFinder,它会自动找到解决这个问题最好的网络结构及其对应的参数。那么目前身价不菲的深度学习工程师岂不是没事可干?换句话说会面临失业的风险?这个问题也对也不对,虽然可能没有参数可调,甚至神经网络架构也不需要你去设计了,但是你可以投入精力去研发这种神经网络自动探索系统啊,俗话说的好:上帝给你关上了一扇窗,他还会给你打开一扇门,是吧?当然,上面说的是未来的理想状况,要真达到这种状态一方面相关技术才刚刚冒头起步,另外也需要计算资源成本得大幅下降才行。

这个方向我觉得是个值得投入精力去摸索的好的探索方向,也是个深度学习工程师自己革自己命的方向,反正你不做迟早有一天别人也会这么做。这是当时驱动我去开始摸索这个方向的初始动力。

最开始我的思路是想用遗传算法去做这个问题,因为遗传方法看上去解决这个问题匹配度比较高而且也比较直观:可以把一个具体的网络结构看做一个生命,而构成神经网络的基本层比如Conv卷积层,Pooling层或者BN或者非线性函数的选择都当做构成生命的基本基因(不同的参数也可以看做不同的基因),不同的基因组合形成了不同的网络结构,然后通过大量群体之间遗传变异等操作以及优胜劣汰的基本指导思想,把解决手头问题性能比较差的网络结构淘汰掉,性能比较好的网络结构逐步胜出,然后直到找到最优的网络结构为止。

上述思路看着简单直观,但是有个现实的问题,因为遗传算法要生成大量种群,尤其是再加上各种遗传变异操作,所以同一时间需要的计算量是超级大的,但是我们手头只有一台GPU服务器,所以跑一轮估计要太久时间,这个时间成本基本无法承受。所以对这个思路进行了一些改造,然后糅合了人工生命和遗传算法的思路来做,它的好处是运算规模基本可以通过简单控制参数来进行调整控制,而且不像遗传算法只有算出一批种群才能继续往后进行下一轮,每一步只需要计算5-10个模型就可以继续往后进行。具体思路后面内容会介绍。

上面是原初的想法,后面我们不断微调算法反复试验,到了16年年底的(对于缺乏GPU设备来说,即使初始模型规模控制在比较小,跑一轮时间确实还是比较久,基本是以周为单位的)时候,系统基本能够较好的自动运转,也能在CIFAR10图像分类数据集合和文本分类数据集合上获得和目前手工设计的较好网络结构相当的水准(后面会给出一些实验数据以及自动找到的好的网络结构)。本来是打算在初步想法得到验证后,引入复杂算法进一步实验看是否能发表,结果16年年底17年年初已经发现陆续开始出现这方面的论文(比如Google的这篇投往ICLR 2017 的用增强学习做的文章:NEURAL ARCHITECTURE SEARCH WITH REINFORCEMENT LEARNING MIT发布的论文DESIGNING NEURAL NETWORK ARCHITECTURES USING REINFORCEMENT LEARNING;以及今年3月份发出来的Google的同一批人用遗传算法来做的论文:Large-Scale Evolution of Image Classifiers),考虑到我们采用的方法比较简单,所以看到增强学习的思路后基本放弃了发表的念头,但是另外考虑到在这上面还是花了精力,而且方法和目前已经出现的方法有区别,另外我自己工作很快会发生变动,可能会放弃这个方向的进一步探索,所以最终还是考虑以这种方式发出来,供有兴趣继续进行探索的同学参考。这里我再强调一点,我认为这是一个值得关注的好的探索方向,代表了未来的趋势而且刚刚冒头还有大量问题需要解决,其它原因上面说过了。


二.集成人工生命和遗传算法思想的最优网络结构发现


上文简单说明了基本思路,虽然很直观,但是如果仔细考虑,你会发现需要探索的网络结构组合空间的大小还是很恐怖的。

 

2.1 探索空间的巨大性及简化工作


对比目前人工设计的卷积神经网络,诸如LeNet网络,ImageNet,VGG16,VGG19,All-CNNs,NIN,ResNet,GoogleNet,FractalNet,DenseNet等结构,很容易发现这些卷积神经网络的结构都是由一些不同类型的层构成基础的结构单元,然后基础结构单元通过不断复制组成了深层神经网络结构,一般而言卷积神经网络的基本结构由以下部分组成:

  1. 卷积层: 卷积层含有的属性包括卷积核个数nb_filter,卷积核的大小(长和高的大小),初始化方式,卷积移动stride步数,border_mode模式(same或者valid)。

  2. 激活函数层: 激活函数层的类型可以取值为tanh,sigmoid,relu任意一种及其各种变体,目前最常用的一般是Relu及其变体;

  3. Pooling层: Pooling层的属性包括大小即length取值长度,移动的步数stride,padding数目,还有Pooling类型比如MaxPooling,AveragePooling等。

  4. 全连接层: 主要的变化点是隐层单元的个数以及激活函数的类型;

  5. 分类层: 同全连接一样,只不过隐层单元个数为类标签的个数;

  6. Dropout层: 正则化系数,主要指的是丢弃神经元的比例;

  7. BatchNormatization层: 主要是每批训练的每个样本减去批内数据的均值,除以批内数据的方差,再乘以alpha加上beta系数,主要变化点为alpha和beta系数。

  8. Skip     Connection: 主要指的是CNN网络结构上的层间跳跃,在低层与高层之间建立“捷径”,从ResNet以及后续许多带shortcuts结构的模型可以知道,带有这种结构的网络结构效果会明显好于传统结构。

如果完全利用以上基础的层结构进行组合形成深层CNN网络结构,很明显其参数空间是巨大无比的,是一个NP难问题。 假设一个d层神经网络,层的类型个数为s,每层含有k个属性,每个属性的取值为t个,那么其搜索空间的范围为:(skt)d , 这个数字是相当大的,比如针对10层的神经网络,5种类型层,每层含有3个属性,每个属性取值5个,那么结构的个数为7510 =1017.8。这个空间的搜索范围是很恐怖的。

当时为了验证思路的可行性,所以考虑一切从简,先验证思路可行再考虑引入更多类型的层及参数变化并引入更为复杂的模型,所以当时尽可能地步减少搜索空间范围,初步的实验我们做了如下的简化处理和一些约定:

   1. 卷积层中由于属性较多,stride固定设置为1, border_mode模式设置为same即经过卷积不改变大小,卷积层的权值初始化方式为随机取值。

   2. 激活函数的使用过程中默认和卷积层绑定,不作为独立一层,理想的方式应该将激活函数当做单独的一层处理,这样可以在其中插入BN操作,同时将BN当做另外的独立层,这样产生的网络结构会更灵活,为了简化问题,我们约定激活函数和卷积层绑定,而且激活函数的类型固定住,在卷积层使用relu,分类层使用softmax激活函数。

   3. Dropout用于全连接层,设定固定值0.5。

   4. Pooling层设大小统一置为2,strides的大小等于pooling的大小,使用的是MaxPooling,不考虑其它类型,这也是目前最常见的类型。

   5. 不考虑Skip-Connnection和BatchNormalization操作,简少搜索空间。

   6. 全连接层默认固定为512个隐层神经单元。

   7. 分类层上加一个正则化约束因子L2,防止过拟合。

   8. 训练时迭代次数设置100次,加入早停机制,即如果10次性能不提高,终止训练,这是为了加快单次探索的速度,所以只采用一种粗粒度的训练和性能评估。

综上所述,经过上述大量简化,模型探索过程的主要变化点来自于卷积层的filter个数大小和MaxPooling层的个数和在CNN中的不同位置。为了进一步简化问题,这里我们将CNN卷积层filter个数设置为取16,32,48,96,128,196,256,512中的某个值。

尽管如此,假设单独考虑10层模型,并且不考虑其间插入pooling层的情况下,上面8种filter的搜索空间的个数为:810 ,相对而言训练的模型的个数还是比较多的。


2.2人工生命和遗传算法思想

 

2.2.1基础思想

 

    人工生命结合遗传算法的基础思想其实非常简单我们以下面两个图形来说明。

技术分享

                        1.人工生命的4*4二维生存世界


   我们首先模仿类似人工生命的思路,假设人工生命的生存世界是如图1所示的4*4大小的二维格子,每个格子里面居住一个人工生命,在我们的问题里,每个人工生命就是某个深度神经网络的网络结构,每个人工生命网络结构都不同,而通过训练数据训练后使用验证集可以测试出每个生命的性能,这个性能代表其在生存世界的生存能力,性能越高代表自身存活并繁衍后代的可能性就越大,通过在人工生命的生存世界里不断的优胜劣汰并逐步进化的过程,能够在几乎无限大小的网络结构空间里面逐步向性能好的网络结构进化,最终性能好的生命会淘汰掉性能差的生命并充斥整个生存世界。

技术分享

2.遗传算法进化


   上述是其基本思想,图2展示了具体的一轮进化过程,一般在人工生命的生存世界中随机选择一个格子的生命作为“主生命”,另外在主生命的邻居范围随机选择一个“辅生命”,然后以这两个生命作为后续进化基础,通过遗传算法产生新的网络结构,进化出更好的生命,淘汰掉局部范围内性能差的生命,这样就完成了一轮进化操作。通过进化操作使得性能好的网络结构增加在生存世界存活的概率,同时逐步淘汰掉性能差的网络结构。

以上就是结合人工生命和遗传算法自动最优探索网络结构的基本思想。因为我们去年做这个事情的初步的实验目的是验证模型的可行性,所以会尽量减少进化过程中基础遗传操作的类型,原先的考虑是验证模型可行再引入更复杂的基础操作步骤。

 

我们定义了最基本的遗传操作如下:


2.2.2基础遗传操作定义


  1. 基因片段的自我复制.

      这个操作的引入主要是借鉴类似于VGG或者ResNet等结构,这些网络结构都是由人工定义的基础Block反复叠加产生的,为了能够产生类似结构,所以引入了网络子结构的自我复制操作。


  2. 基因片段的交叉组合。

      基因交叉是遗传算法中的典型操作。以某个神经网络结构个体序列S1=a1_a2_a3_a4和个体序列S2=b1_b2_b3_b4为例,它们进行交叉组合的种类是比较多的,这里采用的策略是:随机分别在个体序列S1和S2之间选择交叉点p1,p2.假设p1=a2,p2=b3,那么它们产生的交叉个体为:

       a1_a2_b3_b4,

        a1_a2_b1_b2,

       b1_b2_a1_a2,

       b1_b2_a3_a4

       这四个交叉组合个体。


  3. 变异操作。

     基因变异也是遗传算法典型操作。假设个体的序列为a1_a2_a3_a4,那么随机产生一个变异点,比如说变异点发生在a2位置,这里有两种情况:

      如果a2是一个MaxPooling层,那么变异操作空操作,即原始基因保持不变;

      如果a2是conv卷积层,其属性卷积核filter个数是f1,那么在之前的filter可选集合S中去掉f1随机选择,即{S-f1}中任意的一个元素作为变异操作的结果,也就是说将卷积层的卷积核个数作为变异内容。


2.2.3自动网络结构寻优的关键步骤


采取如下步骤来完成人工生命+遗传算法的进化过程:


step1. 人工生命初始化步

人工生命生存世界二维格子大小可以自由设定大小比如L×L,最初的生命基因片段只包含单层的卷积层或单层的MaxPooling层,可以按照一定比例配置两者的个数配比,比如70%是Conv层,30%是MaxPooling层,但是考虑到输入层如果是MaxPooling性能都比较差,所以在MaxPooling层前可以先套上随机个数卷积核的卷积层。初始化过程随机将这些简单基因片段组成的神经网络生命分配到L×L大小的二维格子中,填充整个L×L个空间的单元格,针对这L×L个个体分别进行训练,使用验证集得到对应网络结构性能得分。如此就完成了人工生命的初始化步骤。


step2. 人工生命进化步

 1. 随机选择一个个体s1作为“主生命”(这一步是可以优化成给予目前性能较好的生命体更高的被选中概率,这样会加快进化速度),以s1为中心点选择一个半径为d的局部区域(一般选择二维格中相邻格子中的个体),在除去s1的d*d-1个局部区域中随机选择另外一个个体s2,s2被称为“辅生命”。之所以有主辅之分,从其命名也可以看出,其中一个是进化的核心,另外一个是辅助进化的生命个体。在这里,我们假设“主生命”s1的模型层次序列为a1_a2_a3_a4, “辅生命”s2的序列为b1_b2_b3_b4;


 2. “主生命”的自身复制

    以“主生命”s1进行自身基因复制操作,产生一个新的个体s3=a1_a2_a3_a4_a1_a2_a3_a4,进行训练后通过验证集获得其性能得分,假设其性能为p3.


 3. 主辅生命基因交叉组合

     如上面提到的基本操作部分,对主辅生命两个基因进行随机的交叉组合产生4个个体,训练后分别通过验证集获得其性能得分,假设为c1,c2,c3,c4.


 4. 选择性能表现较好的组合或者自身复制出的新个体,记最好的生命性能得分为max_c,即max_c=max{p3,c1,c2,c3,c4}。

   这时可能出现如下三种情况:

    a. 如果max_c比“主生命”s1的性能要差,说明目前选中的“主生命”性能比较好,那么本轮进化不做任何操作,直接跳到step2,进行下一轮进化操作;

    b. 如果max_c比“主生命”s1的性能要好,但比“辅生命”s2的性能要差,这时max_c的网络结构替换掉s1的网络结构,意味着性能较差的“主生命”被新生命淘汰掉,然后进行第5步的基因变异操作;

    c. 如果max_c比“主生命”s1的性能要好,也比“辅生命”s2的性能要好,这时max_c的网络结构替换掉“主生命”s1的结构,同时也替换掉“辅生命”s2的结构,意味着性能较差的“主生命”和“辅生命”同时被新生命淘汰掉,然后进行第5步的基因变异步操作;


 5. 基因变异操作

在s1附近的局部种群中,除去“主生命”和“辅生命”s1,s2外,随机选择出局部区域中两个性能最差的个体L1,L2,使用max_c的网络结构替换掉L1和L2的网络结构后进行变异操作,就是把卷积层的卷积核个数随机变化掉。这步的意思是加快优秀基因个体在种群中的传播,通过增加优秀基因个数并淘汰局部范围内较差基因的方式来达成这个目的。同时,为了能够探索更多的网络结构空间,将目前找到的优秀基因进行变异。

 

以上就是人工生命结合遗传算法进行进化操作的步骤,从上面的流程描述可以看出,这种结合人工生命和遗传算法的方法好处是一次进化步骤只需要产生10个以内的新结构,然后可以进入下一次进化过程,当然,理论上完全可以把上述过程并行化来加快进化过程,比如多GPU的情况下,可以同时随机选择K个“主生命”同时计算加快速度。但是上文说过,之所以选择这种方式正是因为我们只有一个GPU服务器,所以为了保证算法能够跑起来被迫采取这种方式,就是把遗传算法的种群这种大量并行计算给消解掉了使得其能够串行运行。


   另外需要说明的是:上述过程中存在优秀生命个体淘汰落后生命个体的过程,淘汰标准是对这个网络结构在验证集上的性能比较来进行的,性能好的生命个体就是优秀生命个体。这里存在一个性能比较标准问题,比如一个网络性能是0.8253,另外一个是0.8252,其实两者性能差异并大,可以认为相当,不足以说明哪个性能更好,所以使用模型性能的相对比率进行度量和比较,即两个模型的得分为s1,s2,如果s1>s2 并且abs(s1-s2)/s2>r, 则认为s1的性能比s2好。


三.实验结果及分析


3.1实验配置


·  数据集

CIFAR10数据集,50000训练和10000测试,10个类别。


·  单次训练的参数统一设置

  - 训练算法的选择:

    带动量的随机梯度下降算法,使用nesterov进行初始化,初始学习率设置为0.01,动量因子0.9,衰减因子设置为1e-6。

  - 误差函数

    误差函数选择交叉熵,度量指标为准确率。      

  - 结构参数:

    训练次数设置100次,设置早停,10次性能不提高终止训练。

    固定模型的全连接层的隐层单元为512个,dropout=0.5,分类层标签数为10。


·  进化过程参数设置

  - 基础算子的选择:

    卷积层的filter选择了16,32,64,96,128,196,224,256,512.

    pooling层,默认设为2,stride设置也为2

  - 进化次数设置150次

  - 性能比较比率设置为0.02

- 二维格子大小设定为7*7或9*9大小


·  数据处理说明

  - 数据增强:

    进行旋转操作,水平和竖直方向进行平移操作;

  - 数据处理:

    零均值规范化处理;

 

3.2自动找出的TOP 性能网络结构


    因为条件所限以及目的主要是探索思路可行性,所以我们设定的二维格都比较小一般采取7*7或者9*9大小,最终跑出来大约1000多个各种基因组成的CNN结构,很明显这个数据量相对可能组合出的CNN结构来比可说是沧海一粟,但是经过优胜劣汰,性能得分前列的模型表现还是不错的,基本可以进入目前人工设计出的最好的神经网络结构之列,其中效果最好的模型在CIFAR10数据集上性能达到了92.70%的准确率,自动找出的性能TOP 20的网络结构准确率都超过了91%(本文附件列出了性能TOP 15的神经网络模型结构)。其中,性能最好的模型长这个样子:

   (‘init-1‘, ‘0.927000‘, [(‘conv‘, [(‘nb_filter‘, 32)]), (‘conv‘,[(‘nb_filter‘, 192)]), (‘conv‘, [(‘nb_filter‘, 192)]), (‘maxp‘, [(‘pool_size‘, 2)]),(‘conv‘, [(‘nb_filter‘, 128)]), (‘conv‘, [(‘nb_filter‘, 192)]), (‘maxp‘,[(‘pool_size‘, 2)]), (‘conv‘, [(‘nb_filter‘, 128)]), (‘conv‘, [(‘nb_filter‘,192)]), (‘conv‘, [(‘nb_filter‘, 192)]), (‘maxp‘, [(‘pool_size‘, 2)]), (‘conv‘,[(‘nb_filter‘, 128)]), (‘conv‘, [(‘nb_filter‘, 192)]), (‘maxp‘, [(‘pool_size‘,2)])])


这是模型的主体,最后再拼接上512个隐层神经单元的全连接层和10分类的分类层即可。 其中(‘conv‘, [(‘nb_filter‘, 32)])代表第一层是卷积层,卷积核个数为32,而(‘maxp‘, [(‘pool_size‘, 2)])代表MaxPooling层,大小为2。

   

3.3相关实验性能比较


     目前人工设计的比较常见的神经网络有包括VGG,NIN,ALL-CNN,ResNet,DenseNet等等,其性能情况参考如下两个表格:

技术分享

       表1:目前CIFAR10上最常见的深度神经网络性能及Google使用增强学习自动探索网络结构性能(From paper: NEURAL ARCHITECTURE SEARCH WITH REINFORCEMENT LEARNING)

技术分享

 

2:目前CIFAR10上最常见的深度神经网络性能及MITMetaQNN自动探索网络结构性能(From paper: DESIGNING NEURAL NETWORK ARCHITECTURES USING REINFORCEMENT LEARNING)


从数据上看,人工生命结合遗传算法的实验中找出的性能最好的网络结构对应的误差率为7.30(考虑到我们资源有限,只跑了1000多组不同网络结构,如果在更充分的计算资源支持条件下应该能够找到性能更好的网络结构)。

首先,和人工设计的神经网络对比,一个特点是其结果要比highway,NIN,maxout,FitNet等人工设计的结构要好,与VGG和All-CNNs相比性能相当,但是比ResNet,Wide ResNet及DenseNet这些带有Skip Connection的网络结构相比仍然有差距。

     另外,如果和MIT的MetaQNN和Google的RL方法比的话,会发现比MetaQNN找出的最优结构性能(误差率6.92)稍差,但是MetaQNN找出的性能最优的TOP 10结构中,除了最好的结构外,其它结构性能都比较差,参考如下结果:

技术分享

表3:MIT的MetaQNN性能TOP 5的网络结构性能


所以人工生命+神经网络方法TOP性能结构整体性能是要优于MetaQNN的,就是说能够找到更多平均性能更好的网络结构。

而Google的RL方法找出的最优网络结构性能明显要好(不同方法找出的最优结构性能误差率在3.84到6.01之间),与其另一篇采用遗传算法论文找出的最优结构性能(最优结构误差率5.4)比也有较大差距。Google的这两个方法在构建网络结构的时候都引入了Skip Connection 结构,而我们的方法及MIT的MetaQNN的方法都没有引入Skip Connection,有理由相信如果在网络结构自动生成过程中引入Skip Connection会明显增加网络性能。

 另外,如果看一下自动找出的神经网络结构和人工设计的网络结构的差异来说,也有很明显的区别。从附录的TOP 15网络结构和常见的人工设计的网络结构对比,可以看出有以下几个特点:

首先,自动生成的性能较好的网络结构和人工设计的结构差异比较大,存在conv_conv_conv_maxp,conv_conv_maxp,conv_conv_conv_conv_maxp等不规则BLOCK结构交叉组合出现的情况,而且其具体的卷积核filter个数参数不规则,人工设计网络结构往往会重复相同配置的BLOCK结构,很难去设计这种不规则的结构。但是从另外一个角度也说明了,也许这种不规则的组合能更容易找到更好的结构,这一点人工设计很难做到。

其次,从生成网络结构的深度也就是层数看,VGG结构最少是16层,ResNet常规的会有50多到100层,其它人工设计的网络结构一般卷积层都比较深,而自动发现的性能较好的网络结构一般都不太深,大部分介于5到10个卷积层,层数要明显少。

结合上面两个区别可以看出:自动生成网络结构更容易找出那些网络结构不规则但是层数较浅的性能较好的网络结构,而人工设计的网络结构因为结构的规则性,所以可能必须层数更深才能达到类似的性能。这是两者最主要的区别。


附录:利用上述方法找到的CIFAR10图像分类问题的TOP-15网络结构

技术分享技术分享技术分享技术分享技术分享

NeuralFinder:集成人工生命和遗传算法自动发现神经网络最优结构