遗传算法综合指南(以及如何Python编码)

xiekch 2018-08-28

遗传算法综合指南(以及如何Python编码)

在19世纪中期,查尔斯达尔文提出了进化论以及它如何在使生物体通过自然选择适应环境方面发挥关键作用 - 适者生存的过程在一个特定的种群中,适者生存的时间足够长,可以将他们的特征和特征传递给后代以确保他们的生存。

目前发生了什么?

目前,与90年代末和2000年代初基于规则的机器相比,机器学习(ML)开创了一个智能机器能够做出更好决策的新时代。

利用我们现在拥有的大量计算能力,机器学习(ML)算法,特别是深度神经网络,利用我们的大量数据库(包括结构化和非结构化),以高精度提供洞察力,线索,预测等等。

遗传算法综合指南(以及如何Python编码)

Inception V4:最先进的计算机视觉模型,广泛用于分类和迁移学习

最先进的ML模型现在可以通过几小时/几天的训练对数据进行分类,归类和生成数据。深度神经网络现在已经扩展到多个领域,现在能够处理从图像到音频的不同数据格式,许多这样的网络在所述区域中具有超过人类水平的能力。

强化学习

最近,像OpenAI和DeepMind这样的研究机构一直在涉及一个名为强化学习的机器学习领域。这是一个系统,代理通过犯错误和为各自的状态收集相应的奖励(正面或负面),在与环境互动的同时学习和改进。

遗传算法综合指南(以及如何Python编码)

遗传算法在哪里适用?

遗传算法构成了智能代理生态系统的一小部分,其中应用半蛮力方法来创建能够“生存”(保持在最顶层)的“适合”代理。为什么我说半蛮力?这是因为代理的参数是随机生成的,因此没有用于代理的预定义测试值集。

遗传算法(GA)工作于基本的进化原则,因为它是自然选择和各种自然发生的子过程的元启发。这涉及到整合进化的四个基本步骤:

  • 适应度
  • 选择
  • 交叉
  • 突变

当在一组代理上执行时,这四个过程一起产生用于正在执行的任务的最适合的代理。

从这一点开始,当我提到“幸存”这个词或该词的任何变体时,我的意思是说,代理人仍然是能够进入下一代的可行性最少的代理商的一部分。

模型适应度

这是指代理在完成手头任务时的强度。它量化了代理的能力,并且这增加了它与群体中的另一个代理交叉的可能性,从而可能创建具有两个父模型特征的更强的后代模型。

对于不同的任务,适应度函数稍作修改。然而,从本质上讲,它的主要功能是在群体中发挥区分作用,将较强的学习者与较弱的学习者分开。

模型选择

这个过程是不言自明的,因为最少的几个模型被允许留在群体中,而剩下的较弱的模型被丢弃,因为它们没有用处。

这让我们在群体中留下了空位,为剩下的母代理人提供了新的更强的后代的空间。

模型交叉

这个过程使两个强壮的父母能够创造出具有父母双方特征增加生存机会的后代。这是一个两全其美的场景。

在交叉期间,其中一个代理的所有属性被分解或分解为更基本的单元,其中一半与另一个代理的交换。从生物学角度来说,来自一个亲本的一部分基因与来自另一个基因的另一部分基因相连,以产生具有两组基因片段的后代,从而提高其在适应性方面的存活机会。

可视化交叉过程的图表,其中部分DNA(二进制数字的组合)被分开并与另一个亲本的DNA的剩余部分交换以产生具有两个不同DNA片段的后代。

模型突变

与进化过程类似,突变在将一些随机性和随机性引入群体中起着关键作用。通过这样做,具有这种突变的更好的存活(更高的健康评分)的代理能够将这种存活突变特性传递给后代以确保它们的存活。

与Fitness函数类似,突变函数也需要事先确定。该突变的范围可以是在基因序列(DNA)中的一个特定位置或多个位置发生的点突变。

无论哪种方式,突变是进化过程中不可或缺的组成部分,因为我们的模型在没有它的情况下无法存活很长时间。

停止条件

当代理实现手头的目标或目标时会发生什么?为了迎合这种情况,使用了停止条件。此函数检查是否有任何一个代理已超过某个阈值(超参数)或一般与完成任务有关的标准。

在大多数情况下,它是代理需要交叉以完成活动或任务的单个最小阈值。在其他情况下,可能存在一系列标准或一组阈值,模型需要跨越每一代被称为适者群体/群体中最适合的群体之一。

遗传算法综合指南(以及如何Python编码)

遗传算法循环从评估当前一代中所有代理的适合度得分开始,选择所有模型的最高百分比,随机地交叉模型并最终突变它们以向群体中引入随机性。循环从此时开始重置,如果满足停止标准则停止。

总之,这4个过程可以生产出能够在环境中生存的新的,更强大,更可行的后代。

通过应用程序学习

现在我们已经完成了遗传算法的复习,编码项目是应用所学知识的最佳方式,并加强您对GA所用概念的理解。

在研究GA时,我遇到了许多有趣的项目,从着名的旅行商问题到背包问题。我甚至发现自己发现了能够为事件创建最佳时间表的时间表构建代理。

我创建了一个网络优化算法,该算法采用现有的神经网络架构超参数,并在测试集上以更高的精度增强它以更好地执行。

项目介绍

主要目标是采用现有的神经网络架构,并在一定数量的代中随机发展它以改进它。在这里,我使用“进化”这个词,因为在现实中,遗传算法基本上执行完全随机的动作并祈祷它改进了某些东西。我已经将Keras机器学习库用于该项目。我已经将准确度阈值(停止标准)设置为99.5%,并且从60K训练和测试集MNIST中获得了1000个实例(如此可预测)。

现在,从更大的角度来看,我们有一个网络代理,它提供了一堆随机超参数。在Keras中有一个内置的函数model.predict(),它计算每个网络代理在测试集上的平均适应度得分,并将该得分分配为二级代理的准确性。

随着时间的推移,具有更好的超参数的代理会出现在顶部,并被识别为具有最佳模型超参数集的代理。

需要导入的Python库及基本设置

import numpy as np

import random

from keras.models import Sequential

from keras.layers import Dense, Activation

from keras.datasets import mnist

from keras.utils import to_categorical

(x_train, y_train), (x_test, y_test) = mnist.load_data()

x_train = x_train.reshape(60000, 784).astype('float32')[:1000] / 255

x_test = x_test.reshape(10000, 784).astype('float32')[:1000] / 255

y_train = to_categorical(y_train, 10)[:1000]

y_test = to_categorical(y_test, 10)[:1000]

classes = 10

batch_size = 64

population = 20

generations = 100

threshold = 0.995

def serve_model(epochs, units1, act1, units2, act2, classes, act3, loss, opt, xtrain, ytrain, summary=False):

model = Sequential()

model.add(Dense(units1, input_shape=[784]))

model.add(Activation(act1))

model.add(Dense(units2))

model.add(Activation(act2))

model.add(Dense(classes))

model.add(Activation(act3))

model.compile(loss=loss, optimizer=opt, metrics=['acc'])

if summary:

model.summary()

model.fit(xtrain, ytrain, batch_size=batch_size, epochs=epochs, verbose=0)

return model

def init_networks(population):

return [Network() for _ in range(population)]

涉及的函数和对象

首先,我们从Network代理开始。它的主要功能是随机生成一组超参数,稍后将其插入到模型设计中并在MNIST的子集上进行测试。在这里,我选择随机化的超参数是非常基本的而不是太复杂(例如学习率),但请记住它可以完成!Python代码如下:

class Network():

def __init__(self):

self._epochs = np.random.randint(1, 15)

self._units1 = np.random.randint(1, 500)

self._units2 = np.random.randint(1, 500)

self._act1 = random.choice(['sigmoid', 'relu', 'softmax', 'tanh', 'elu', 'selu', 'linear'])

self._act2 = random.choice(['sigmoid', 'relu', 'softmax', 'tanh', 'elu', 'selu', 'linear'])

self._act3 = random.choice(['sigmoid', 'relu', 'softmax', 'tanh', 'elu', 'selu', 'linear'])

self._loss = random.choice([

'categorical_crossentropy',

'binary_crossentropy',

'mean_squared_error',

'mean_absolute_error',

'sparse_categorical_crossentropy'

])

self._opt = random.choice(['sgd', 'rmsprop', 'adagrad', 'adadelta', 'adam', 'adamax', 'nadam'])

self._accuracy = 0

def init_hyperparams(self):

hyperparams = {

'epochs': self._epochs,

'units1': self._units1,

'act1': self._act1,

'units2': self._units2,

'act2': self._act2,

'act3': self._act3,

'loss': self._loss,

'optimizer': self._opt

}

return hyperparams

转到主事件循环,我们有必要的函数,如上面讨论的fitness()、selection()、crossover()、mutate()。fitness()使用随机超参数计算模型的准确性。由于维度问题或其他错误,一些模型体系结构无法工作并破坏代码,这就是为什么我们将培训和评估功能放在try-except块中。selection()函数非常不言自明,也很容易理解——前20%的模型可以转移到下一代模型。Python实现如下:

def fitness(networks):

for network in networks:

hyperparams = network.init_hyperparams()

epochs = hyperparams['epochs']

units1 = hyperparams['units1']

act1 = hyperparams['act1']

units2 = hyperparams['units2']

act2 = hyperparams['act2']

act3 = hyperparams['act3']

loss = hyperparams['loss']

opt = hyperparams['optimizer']

try:

model = serve_model(epochs, units1, act1, units2, act2, classes, act3, loss, opt, x_train, y_train)

accuracy = model.evaluate(x_test, y_test, verbose=0)[1]

network._accuracy = accuracy

print ('Accuracy: {}'.format(network._accuracy))

except:

network._accuracy = 0

print ('Failed build. Moving to next model.')

return networks

def selection(networks):

networks = sorted(networks, key=lambda network: network._accuracy, reverse=True)

networks = networks[:int(0.2 * len(networks))]

return networks

需要注意的重要函数是crossover()和mutation()功能。后代代理的质量取决于这些函数的质量,即它创造了几乎与父母完全不同的后代,并为基因库和群体增加了随机性和新颖性。

对于该crossover()函数,父母的超参数在创建的两个子代理之间分开。一部分隐藏单元被分配给子代理。Python代码如下:

def crossover(networks):

offspring = []

for _ in range(int((population - len(networks)) / 2)):

parent1 = random.choice(networks)

parent2 = random.choice(networks)

child1 = Network()

child2 = Network()

# Crossing over parent hyper-params

child1._epochs = int(parent1._epochs/4) + int(parent2._epochs/2)

child2._epochs = int(parent1._epochs/2) + int(parent2._epochs/4)

child1._units1 = int(parent1._units1/4) + int(parent2._units1/2)

child2._units1 = int(parent1._units1/2) + int(parent2._units1/4)

child1._units2 = int(parent1._units2/4) + int(parent2._units2/2)

child2._units2 = int(parent1._units2/2) + int(parent2._units2/4)

child1._act1 = parent2._act2

child2._act1 = parent1._act2

child1._act2 = parent2._act1

child2._act2 = parent1._act1

child1._act3 = parent2._act2

child2._act3 = parent1._act2

offspring.append(child1)

offspring.append(child2)

networks.extend(offspring)

return networks

对于Mutation函数,我将原始隐藏单元与0到100之间的随机整数相加(正如我所说,我想不出有趣的函数)。发生突变的几率应小于0.1。这将随机性控制在很小但有一定程度。Python实现如下:

def mutate(networks):

for network in networks:

if np.random.uniform(0, 1) <= 0.1:

network._epochs += np.random.randint(0,100)

network._units1 += np.random.randint(0,100)

network._units2 += np.random.randint(0,100)

return networks

在下面,该main()函数将代理生成到环境中。每一代,上述功能都是一个接一个地运行。最后,超过99.5%准确率的模型被认为是最好的

def main():

networks = init_networks(population)

for gen in range(generations):

print ('Generation {}'.format(gen+1))

networks = fitness(networks)

networks = selection(networks)

networks = crossover(networks)

networks = mutate(networks)

for network in networks:

if network._accuracy > threshold:

print ('Threshold met')

print (network.init_hyperparams())

print ('Best accuracy: {}'.format(network._accuracy))

exit(0)

if __name__ == '__main__':

main()

上面的代码片段只是整个事情的一部分。总之,不同的组件协同工作,以生成最适合的模型,以实现手头的任务(在这种情况下,达到99.5%及以上的准确度)。

相关推荐