「这是我参加11月更文挑战的第1天,活动详情检查:2021最终一次更文挑战」

前言

近年来,深度学习模型功能取得了腾跃,能够在单个网络中运用很多躲藏层。练习深度学习模型可能会占用很多核算资源,而且一般在图形处理单元(GPU)上进行,一起为了获得最优的模型功能,可能需求网络架构和超参数的重复修改和调整,一般此过程取决于实践问题和网络架构规划人员的经历,而运用遗传算法能够将此过程自动化,一起能够在可接受的时间开销内找到更好的网络架构。专门的深度学习库,例如TensorFlow,能够运用根据GPU的核算平台,本文运用MNIST数据集和Tensorflow构建简略的全连接网络,运用遗传算法优化躲藏层数和每层的节点数。虽然全连接网络是非常根底简略的网络,可是,运用的原理相同适用于更复杂的网络和数据集。 以下是所用库:

  1. tensorflow2.x
  2. deap
  3. matplotlib

优化深度学习分类器的架构

在创立神经网络模型能够执行给定的机器学习使命时,一项要害作业是规划网络体系结构的装备。对于多层感知器,输入层和输出层中的节点数取决于当前问题的特征。因而,要做出的挑选是关于躲藏层——有多少层以及每层有多少个节点。能够采用一些经历进行测验,可是在多数情况下,确定最佳架构可能需求重复试验。

处理网络体系结构参数的一种办法是将它们视为模型的超参数,运用这些超参数构建网络,并将练习后网络的功能作为习惯度进行评价。接下来,将运用遗传算法找到躲藏层的最佳组合。

深度学习与遗传算法的碰撞——利用遗传算法优化深度学习网络结构(详解与实现)

躲藏层装备的染色体表明

因为MLP的体系结构由躲藏层装备决议,在tensorflow.keras中可通过改动Dense层的units参数获得节点数不同的全连接躲藏层:

Dense(units, activation=None,...)

一起,能够通过for来构建所需层数,例如,如果要为MLP装备三个有20个节点的躲藏层,则能够通过以下方式:

self.model = Sequential()
for l in range(3): 
	self.model.add(layers.Dense(20,activation='relu'))

因而,咱们需求提出既能够表明层数又能够表明每一层节点数的染色体。 一起,为了能够运用标准遗传算子,运用固定长度的染色体表明方式。运用这种办法时,预先确定最大层数,但为了层数可变,能够在染色体中设置无效位(也能够称为停止参数),使模型构建提前停止。例如,将网络限制为四个躲藏层,则染色体将如下所示:

[n1,n2,n3,n4][n_1, n_2, n_3, n_4]

其间,nin_i表明ii层中的节点数。

为了控制网络中躲藏层的实践数量,其间一些值可能为零或负数。该值意味着之后不会再有其他层添加到网络:

  1. 染色体[10, 20, -5, 15]表明元组(10, 20),因为-5是无效位。
  2. 染色体[10, 0, -5, 15]表明元组(10, ),因为0是无效位。
  3. 染色体[10, 20, 5, -15]表明元组(10, 20, 5),因为-15是无效位。
  4. 染色体[10, 20, 5, 15]表明元组(10, 20, 5, 15)。

为了保证至少有一个躲藏层,能够强制第一个参数一直大于零。其他层参数能够在零附近散布,以便能够控制它们成为停止参数。

另外,因为染色体中值有限制区间,能够挑选运用浮点数列表构建染色体。运用浮点数列表使咱们能够运用现有的遗传算子。为了构建网络能够运用round()函数能够将浮点数转换回整数:

  1. 染色体[9.35, 10.71, -2.51, 17.99]能够转化为元组(9, 11)
  2. 染色体[9.35, 10.71, 2.51, -17.99]能够转化为元组(9, 11, 3) 要评价构建的网络结构,需求创立完成这些层的MLP分类器,对其进行练习并进行评价。

评价个别的习惯度得分

MLPLayers类封装了MNIST数据集的MLP分类器的构建以及模型准确率的评价。

MLPLayers类主要包括以下办法:

  1. preprocess(self,x,y)用于构建练习数据集的预处理
  2. initDataset(self)用于构建练习数据集
  3. convertParams(self,params)将params的列表转换为能够有用构建模型的元组
  4. getAccuracy(self,params)构建模型,练习,并回来最终一个epoch的验证准确率,用于习惯度评价。
  5. testLayer(self),运用经历值构建的分类模型,用于和优化得到的网络进行对比
  6. formatParams(self, params)用于格式化输出染色体
class MLPLayers:
    def __init__(self):
        self.initDataset()
    def preprocess(self,x,y):
        x = tf.reshape(x, [-1]) 
        return x,y
    def initDataset(self):
        (self.X_train,self.y_train),(self.X_test,self.y_test) = datasets.mnist.load_data()
        self.X_train = tf.convert_to_tensor(self.X_train,dtype=tf.float32) / 255.
        self.X_test = tf.convert_to_tensor(self.X_test,dtype=tf.float32) / 255.
        self.y_train = tf.convert_to_tensor(self.y_train,dtype=tf.int32)
        self.y_test = tf.convert_to_tensor(self.y_test,dtype=tf.int32)
        self.y_train = tf.one_hot(self.y_train,depth=10)
        self.y_test = tf.one_hot(self.y_test,depth=10)
        self.train_db = tf.data.Dataset.from_tensor_slices((self.X_train,self.y_train))
        self.validation_db = tf.data.Dataset.from_tensor_slices((self.X_test,self.y_test))
        self.train_db = self.train_db.shuffle(1000).map(self.preprocess).batch(128)
        self.validation_db = self.validation_db.shuffle(1000).map(self.preprocess).batch(128)
    def convertParams(self,params):
        if round(params[1]) <= 0:
            hiddenLayerSizes = round(params[0]),
        elif round(params[2]) <= 0:
            hiddenLayerSizes = (round(params[0]), round(params[1]))
        elif round(params[3]) <= 0:
            hiddenLayerSizes = (round(params[0]), round(params[1]), round(params[2]))
        else:
            hiddenLayerSizes = (round(params[0]), round(params[1]), round(params[2]), round(params[3]))
        return hiddenLayerSizes
    def getAccuracy(self,params):
    	#将染色体转化为能够有用构建网络的元组
        hiddenLayerSizes = self.convertParams(params)
        self.model = Sequential()
        #构建网络
        for l in hiddenLayerSizes:
            self.model.add(layers.Dense(l,activation='relu'))
        self.model.add(layers.Dense(10,activation='relu'))
        self.model.build(input_shape=(4,28*28))
        self.model.summary()
        self.model.compile(optimizer=optimizers.Adam(lr=0.01),
                loss=losses.CategoricalCrossentropy(from_logits=True),
                metrics=['accuracy'])
        # 指定练习集为db,验证集为val_db,练习5个epochs,每1个epoch验证一次
        history = self.model.fit(self.train_db, epochs=5, validation_data=self.validation_db, validation_freq=1,verbose=2)
        #回来最终一个epoch练习后的验证准确率,用于习惯度评价
        return history.history['val_accuracy'][-1]
    def testLayer(self):
        # 创立5层的全连接层网络
        network = Sequential([layers.Dense(256, activation='relu'),
                            layers.Dense(128, activation='relu'),
                            layers.Dense(64, activation='relu'),
                            layers.Dense(32, activation='relu'),
                            layers.Dense(10)])
        network.build(input_shape=(4, 28*28))
        network.summary()
        # 采用Adam优化器,学习率为0.01;采用穿插熵丢失函数,包括Softmax
        network.compile(optimizer=optimizers.Adam(lr=0.01),
                loss=losses.CategoricalCrossentropy(from_logits=True),
                metrics=['accuracy'] # 设置丈量指标为准确率
        )
        # 指定练习集为db,验证集为val_db,练习5个epochs,每1个epoch验证一次
        history = network.fit(self.train_db, epochs=5, validation_data=self.validation_db, validation_freq=1,verbose=2)
        #打印成果
        print(history.history['val_accuracy'][-1])
    def formatParams(self, params):
        return "'hidden_layer_sizes'={}".format(self.convertParams(params))

运用遗传算法优化MLP架构

现在,咱们现已有了MLP的体系结构装备,以及确定每种装备的MLP准确率的办法,接下来,创立根据遗传算法的优化程序以对装备进行查找——躲藏层的数量以及每层中的节点数量——发生最佳分类准确率。

详细的步骤在注释中进行介绍:

#创立MlpLayersTest类的实例,用于测试躲藏层架构的各种组合
test = MLPLayers()
# 首先为代表躲藏层的每个float值设置上下边界。第一个躲藏层的规模为[100,300],而其余的层则从负值开始,增加停止层数的时机:
BOUNDS_LOW = [100,-25,-50,-75]
BOUNDS_HIGH = [300,200,100,50]
NUM_OF_PARAMS = len(BOUNDS_LOW)
#超参数:
POPULATION_SIZE = 50
P_CROSSOVER = 0.9
P_MUTATION = 0.5
MAX_GENERATIONS = 20
HALL_OF_FAME_SIZE = 5
CROWDING_FACTOR = 10.0
toolbox = base.Toolbox()
#定义最大化适费用战略:
creator.create("FitnessMax",base.Fitness,weights=(1.0,))
#根据列表创立个别类:
creator.create("Individual",list,fitness=creator.FitnessMax)
#因为解由一系列不同区间的浮点值表明,因而咱们运用以下循环并为每个区间创立一个独自的toolbox运算符(layer_size_attribute),用于在适当规模内生成随机浮点值:
for i in range(NUM_OF_PARAMS):
    #"layer_size_attribute_0","layer_size_attribute_1"...
    toolbox.register("layer_size_attribute_"+str(i),
            random.uniform,
            BOUNDS_LOW[i],
            BOUNDS_HIGH[i])
#创立layer_size_attributes元组,其间包括咱们刚刚为每个躲藏层创立的独自的浮点数生成器:
layer_size_attributes = ()
for i in range(NUM_OF_PARAMS):
    layer_size_attributes = layer_size_attributes + (toolbox.__getattribute__("layer_size_attribute_"+str(i)),)
#将此layer_size_attributes元组与DEAP的内置initCycle()运算符结合运用,以创立一个新的individualCreator运算符,该运算符将随机生成的躲藏层值组合起来填充单个实例
toolbox.register("individualCreator",tools.initCycle,creator.Individual,layer_size_attributes,n=1)
#定义种群创立运算符:
toolbox.register("populationCreator",tools.initRepeat,list,toolbox.individualCreator)
#运用类的getAccuracy()办法进行习惯度评价
def classificationAccuracy(individual):
    return test.getAccuracy(individual),
toolbox.register("evaluate",classificationAccuracy)
#遗传算子定义:对于挑选运算符,运用锦标赛巨细为2的锦标赛挑选,运用专门用于有界起浮列表染色体的穿插和变异运算符,并为它们提供定义的上下限:
toolbox.register("select",tools.selTournament,tournsize=2)
toolbox.register("mate",tools.cxSimulatedBinaryBounded,low=BOUNDS_LOW,up=BOUNDS_HIGH,eta=CROWDING_FACTOR)
toolbox.register("mutate",tools.mutPolynomialBounded,low=BOUNDS_LOW,up=BOUNDS_HIGH,eta=CROWDING_FACTOR,indpb=1.0/NUM_OF_PARAMS)

带精英主义战略的遗传流程函数

运用名人堂能够用来保存进化过程中种群中曾经存在的最佳个别,并不会因为挑选,穿插或变异而失去了它们,HallOfFame类在tools模块中完成。

将Halloffame目标用于完成精英主义。 Halloffame目标中包括的个别被直接注入下一代,而且不受挑选,穿插和突变的遗传算子的影响。

遗传流程

def main():
    #创立初始种群:
    population = toolbox.populationCreator(n=POPULATION_SIZE)
    #注册要监听的计算数据:
    stats = tools.Statistics(lambda ind:ind.fitness.values)
    stats.register("max",np.max)
    stats.register("avg",np.mean)
    #定义名人堂目标:
    hof = tools.HallOfFame(HALL_OF_FAME_SIZE)
    #运用精英主义战略执行遗传流程:
    population,logbook = eaSimpleWithElitism(population,toolbox,
            cxpb=P_CROSSOVER,mutpb=P_MUTATION,
            ngen=MAX_GENERATIONS,
            stats=stats,halloffame=hof,verbose=True)
    # 打印找到的最佳解:
    print("- Best solution is: ",test.formatParams(hof.items[0]),", accuracy = ",hof.items[0].fitness.values[0])
    # 获取计算数据:
    maxFitnessValues, meanFitnessValues = logbook.select("max", "avg")
if __name__ == "__main__":
    main()

成果分析

检查找到的最佳解

- Best solution is:  'hidden_layer_sizes'=(135,) , accuracy =  0.9731000065803528

能够到,仅运用一层具有135个节点的躲藏层,准确率就到达了97.31. 算法运转过程中计算成果如下:

深度学习与遗传算法的碰撞——利用遗传算法优化深度学习网络结构(详解与实现)

而依托经历规划的网络结构及其准确率如下

Layer (type)                 Output Shape              Param #
=================================================================
dense_2812 (Dense)           (4, 256)                  200960    
_________________________________________________________________
dense_2813 (Dense)           (4, 128)                  32896     
_________________________________________________________________
dense_2814 (Dense)           (4, 64)                   8256      
_________________________________________________________________
dense_2815 (Dense)           (4, 32)                   2080      
_________________________________________________________________
dense_2816 (Dense)           (4, 10)                   330       
=================================================================
Total params: 244,522
Trainable params: 244,522
Non-trainable params: 0
...
469/469 - 1s - loss: 0.0911 - accuracy: 0.9754 - val_loss: 0.1547 - val_accuracy: 0.9653

能够看出,比较于精心规划的网络结构,遗传算法得到的网络结构,在MNIST数据集上有更高的准确率,虽然提升并不非常明显,可是考虑到:MNIST数据集较简略,以及比较精心规划的网络的参数量(244522),遗传算法找到的最佳解的参数量仅为107335(28∗28∗135+135∗10+135+1028*28*135+135*10+135+10),参数量削减一倍以上,能够说遗传算法的优化现已到达预期。能够通过将更多超参数加入遗传算法优化的列表中,检查不同效果。