这篇博客将演化一个简单 MLP 的权重解决“异或”问题 (XOR)。 众所周知,MLP 中需要一个隐藏层来解决这个问题,因为它不是线性可分的。 XOR 问题接受两个二进制输入,如果其中一个是 1,则输出 1,但不是两个都是
Python有专门用于生成神经网络的软件包,例如 Tensorflow 和 Pytorch。 然而,为了简单起见,我们将实现我们自己的基本 MLP,只有一个隐藏层。
定义神经网络
首先,先定义MLP函数
import numpy as np
import math
class MLP(object):
def __init__(self, numInput, numHidden, numOutput):
self.fitness = 0
self.numInput = numInput + 1 # 添加bias节点到数入
self.numHidden = numHidden
self.numOutput = numOutput
self.wh = np.random.randn(self.numHidden, self.numInput)
self.wo = np.random.randn(self.numOutput, self.numHidden)
self.ReLU = lambda x : max(0,x)
def sigmoid(self,x):
try:
ans = (1 / (1 + math.exp(-x)))
except OverflowError:
ans = float('inf')
return ans
接下来我们定义网络的前馈函数。 为此,我们只需将输入数组的点积与该输入的权重计算到下一层节点。 然后我们通过隐藏层中的 ReLU 函数和最后一层中的 sigmoid 运行这些加权和。 这使得它类似于非线性回归问题。
class MLP(MLP):
def feedForward(self, inputs):
inputsBias = inputs[:]
inputsBias.insert(len(inputs),1) # 添加bias
h1 = np.dot(self.wh, inputsBias) # 传输至隐藏层
h1 = [self.ReLU(x) for x in h1] # 激活隐藏层
output = np.dot(self.wo, h1) # 传输至输出层
output = [self.sigmoid(x) for x in output] # 激活输出层
return output
接下来我们定义允许遗传算法获取和设置权重的函数作为一个简单的一维列表。 这意味着我们可以只使用内置运算符,而不必担心定义我们自己的运算符来处理多维数组。
class MLP(MLP):
def getWeightsLinear(self):
flat_wh = list(self.wh.flatten())
flat_wo = list(self.wo.flatten())
return( flat_wh + flat_wo )
def setWeightsLinear(self, Wgenome):
numWeights_IH = self.numHidden * (self.numInput)
self.wh = np.array(Wgenome[:numWeights_IH])
self.wh = self.wh.reshape((self.numHidden, self.numInput))
self.wo = np.array(Wgenome[numWeights_IH:])
self.wo = self.wo.reshape((self.numOutput, self.numHidden))
这里我们将创建一个具有 2 个输入、3 个隐藏节点(此处为单个隐藏层)和 1 个输出的多层感知器。
myNet = MLP(2,3,1)
a = myNet.getWeightsLinear()
它接受一个大小为 2 的列表,并给出一个列表作为输出,列表中的每个元素都是输出节点(这里我们只有 1 个)。
inputs = [0,1]
outcome = myNet.feedForward(inputs)
由于 sigmoid 函数,结果将介于 0 和 1 之间。 为了制作这个二进制文件,我们可以添加一个阶跃函数。
print(outcome)
# [0.5431873319595386]
int(outcome[0] > 0.5)
遗传算法
一种方法可能是创建大量神经网络。 但是,这可能会很慢(并且涉及使用 copy.deepcopy)并且不允许使用 DEAP 的内置运算符。
相反,我们将演化出一组平面列表,代表神经网络权重。 我们只需要实例化一个神经网络来评估这些权重的适合度。
import random
from deap import base
from deap import creator
from deap import tools
numInputNodes = 2 + 1
numHiddenNodes = 3
numOutputNodes = 1
IND_SIZE = (numInputNodes * numHiddenNodes) + (numHiddenNodes * numOutputNodes)
# 以下代码解释可以看第一篇文章
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)
toolbox = base.Toolbox()
toolbox.register("attr_float", random.uniform, -1, 1)
toolbox.register("individual", tools.initRepeat, creator.Individual,
toolbox.attr_float, n=IND_SIZE)
def evaluate(indiv, myNet):
fitness = 0
myNet.setWeightsLinear(indiv)
output = myNet.feedForward([0,0])
fitness += abs( 0 - output[0] )
output = myNet.feedForward([0,1])
fitness += abs( 1 - output[0] )
output = myNet.feedForward([1,0])
fitness += abs( 1 - output[0] )
output = myNet.feedForward([1,1])
fitness += abs( 0 - output[0] )
return fitness,
toolbox.register("evaluate", evaluate)
toolbox.register("select", tools.selTournament, tournsize=3)
toolbox.register("mutate", tools.mutGaussian, mu=0.0, sigma=0.5, indpb=0.1)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
stats = tools.Statistics(key=lambda ind: ind.fitness.values)
stats.register("avg", np.mean)
stats.register("std", np.std)
stats.register("min", np.min)
stats.register("max", np.max)
logbook = tools.Logbook()
pop = toolbox.population(n=500)
fitnesses = [toolbox.evaluate(indiv, myNet) for indiv in pop]
for ind, fit in zip(pop, fitnesses):
ind.fitness.values = fit
NGEN, CXPROB = 200, 0.0
for g in range(NGEN):
print("-- Generation %i --" % g)
offspring = toolbox.select(pop, len(pop))
offspring = list(map(toolbox.clone, offspring))
for mutant in offspring:
toolbox.mutate(mutant)
del mutant.fitness.values
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = [toolbox.evaluate(indiv, myNet) for indiv in invalid_ind]
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit
pop[:] = offspring
record = stats.compile(pop)
logbook.record(gen=g, **record)
快速检查算法
logbook.header = "gen", "avg", "evals", "std", "min", "max"
import matplotlib.pyplot as plt
%matplotlib inline
gen = logbook.select("gen")
avgs = logbook.select("avg")
stds = logbook.select("std")
plt.rc('axes', labelsize=14)
plt.rc('xtick', labelsize=14)
plt.rc('ytick', labelsize=14)
plt.rc('legend', fontsize=14)
fig, ax1 = plt.subplots()
#line1 = ax1.plot(gen, avgs)
line1 = ax1.errorbar(gen, avgs, yerr=stds, errorevery=2)
ax1.set_xlabel("Generation")
ax1.set_ylabel("Mean Fitness")
测试结果
indiv1 = tools.selBest(pop, 1)[0]
myNet.setWeightsLinear(indiv1)
outcome = myNet.feedForward([0,0])
int(outcome[0] > 0.5)
# 0
outcome = myNet.feedForward([0,1])
int(outcome[0] > 0.5)
# 1
outcome = myNet.feedForward([1,0])
int(outcome[0] > 0.5)
# 1
outcome = myNet.feedForward([1,1])
int(outcome[0] > 0.5)
# 0