'My Genetic Neural Network doesn't improve

I am trying my hands at Neural Networks and Genetic Algorithm by following the Nature of Code series from CodingTrain (https://www.youtube.com/playlist?list=PLRqwX-V7Uu6aCibgK1PTWWu9by6XFdCfh and https://www.youtube.com/playlist?list=PLRqwX-V7Uu6Yd3975YwxrR0x40XGJ_KGO). However, I use Python and numpy to handle all the matrix computations.

I am trying to evolve the NN to resolve the XOR problem (for now). It was previously working, but since then I added functionalities to my NN and now it doesn't improve anymore, and I can't figure out why. So I was hopping that a new pair of eyes could help me see the issue.

Here's the code for the NN:

import random
import numpy
import numpy.matlib

class NeuralNetwork:

#--------------------------------------------------------- Constructor ---------------------------------------------------------

    def __init__(self, input, hidden_list, output):
        #Memorize Size of Neural Network
        self.input = input
        self.output = output
        self.hidden = []
        for hidden in hidden_list:
            self.hidden.append(hidden)


        #Generate weights
        #Input weights
        self.input_weights = numpy.matlib.rand(self.hidden[0], self.input)
        self.input_weights = self.input_weights * 2 - 1

        #Hidden weights
        self.hidden_weights = []
        for i in range(len(self.hidden) - 1):
            hidden_weight = numpy.matlib.rand(self.hidden[i+1], self.hidden[i])
            hidden_weight = hidden_weight * 2 - 1

            self.hidden_weights.append(hidden_weight)

        #Output weights
        self.output_weights = numpy.matlib.rand(self.output, self.hidden[-1])
        self.output_weights = self.output_weights *2 - 1


        #Generate bias
        #Hidden bias
        self.hidden_bias = []
        for i in range(len(self.hidden)):
            hidden_bias = numpy.matlib.rand(self.hidden[i], 1)
            hidden_bias = hidden_bias * 2 - 1

            self.hidden_bias.append(hidden_bias)

        #Output bias
        self.output_bias = numpy.matlib.rand(self.output, 1)
        self.output_bias = self.output_bias * 2 - 1

#--------------------------------------------------------- Neural Network ---------------------------------------------------------

    #Activation Function
    def activation(self, x):
        return 1/(1 + numpy.exp(-x))
        #return x


    #Feed Forward and return result
    def feedForward(self, inputs):
        #Input
        inputs_matrix = numpy.matrix(inputs).transpose()

        inputs_result = numpy.dot(self.input_weights, inputs_matrix)
        inputs_result = numpy.add(inputs_result, self.hidden_bias[0])

        #inputs_result = self.activation(inputs_result)
        inputs_result = numpy.vectorize(self.activation)(inputs_result)


        #Hidden
        hidden_result = inputs_result
        for i in range(1, len(self.hidden)):
            hidden_result = numpy.dot(self.hidden_weights[i-1], hidden_result)
            hidden_result = numpy.add(hidden_result, self.hidden_bias[i])

            #hidden_result = self.activation(hidden_result)
            hidden_result = numpy.vectorize(self.activation)(hidden_result)


        #Output
        output_result = numpy.dot(self.output_weights, hidden_result)
        output_result = numpy.add(output_result, self.output_bias)

        #output_result = self.activation(output_result)
        output_result = numpy.vectorize(self.activation)(output_result)

        return output_result

#--------------------------------------------------------- Evolution ---------------------------------------------------------
    
    def copy(self):
        #Create a new Neural Network of same size
        new_NN = NeuralNetwork(self.input, self.hidden, self.output)

        #Copy weights
        new_NN.input_weights = self.input_weights.copy()
        new_NN.hidden_weights = self.hidden_weights.copy()
        new_NN.output_weights = self.output_weights.copy()

        #Copy bias
        new_NN.hidden_bias = self.hidden_bias.copy()
        new_NN.output_bias = self.output_bias.copy()

        #Return new Neural Network
        return new_NN


    #Mutate the Neural Network
    def mutate(self, mutation_rate, mutation_scale):
        #Mutate weights
        self.input_weights = numpy.vectorize(mutate_value)(self.input_weights, mutation_rate, mutation_scale)
        for i in range(len(self.hidden_weights)):
            self.hidden_weights[i] = numpy.vectorize(mutate_value)(self.hidden_weights[i], mutation_rate, mutation_scale)
        self.output_weights = numpy.vectorize(mutate_value)(self.output_weights, mutation_rate, mutation_scale)

        #Mutate bias
        for i in range(len(self.hidden_weights)):
            self.hidden_bias[i] = numpy.vectorize(mutate_value)(self.hidden_bias[i], mutation_rate, mutation_scale)
        self.output_bias = numpy.vectorize(mutate_value)(self.output_bias, mutation_rate, mutation_scale)


    #Reproduce the Neural Network
    def reproduce(self, mutation_rate = 0.01, mutation_scale = 0.1):
        #Copy this Neural Network
        child = self.copy()

        #Mutate new Neural Network
        child.mutate(mutation_rate, mutation_scale)

        #Return new Neural Network
        return child
    

    #Crossover the Neural Network with another Neural Network
    def crossover(self, other, selection_rate = 0.5, mutation_rate = 0.01, mutation_scale = 0.1):
        #Create a new Neural Network of same size
        child = NeuralNetwork(self.input, self.hidden, self.output)

        #Copy weights from one of both parents randomly
        child.input_weights = numpy.vectorize(random_selection)(self.input_weights, other.input_weights, selection_rate)
        for i in range(len(self.hidden_weights)):
            child.hidden_weights[i] = numpy.vectorize(random_selection)(self.hidden_weights[i], other.hidden_weights[i], selection_rate)
        child.output_weights = numpy.vectorize(random_selection)(self.output_weights, other.output_weights, selection_rate)

        #Copy bias from one of both parents randomly
        for i in range(len(self.hidden_weights)):
            child.hidden_bias[i] = numpy.vectorize(random_selection)(self.hidden_bias[i], other.hidden_bias[i], selection_rate)
        child.output_bias = numpy.vectorize(random_selection)(self.output_bias, other.output_bias, selection_rate)

        #Mutate new Neural Network
        child.mutate(mutation_rate, mutation_scale)

        #Return new Neural Network
        return child

#--------------------------------------------------------- Util ---------------------------------------------------------

#Randomly decide and mutate given value
def mutate_value(value, mutation_rate, mutation_scale):
    #Randomly decide
    if random.random() < mutation_rate:
        #Randomly mutate
        mutation =  numpy.random.uniform(-mutation_scale, mutation_scale)
        return value + mutation
    else:
        #Return original value
        return value


#Randomly select which of the two given values will be returned
def random_selection(value, other_value, selection_rate):
    #Randomly decide
    if random.random() < selection_rate:
        #Return original value
        return value
    else:
        #Return other value
        return other_value

And here's the code for the XOR problem:

import math
import random
import numpy
import numpy.matlib

from NeuralNetwork import NeuralNetwork

#--------------------------------------------------------- Create Population ---------------------------------------------------------

population_size = 2000
population = []
fitness = []

for i in range(population_size):
    population.append(NeuralNetwork(2, [2], 1))
    fitness.append(0)

#--------------------------------------------------------- Create Dataset ---------------------------------------------------------

datas = [[0, 0], [0, 1], [1, 0], [1, 1]]
answers = [0, 1, 1, 0]



for generation in range(100):

    #Init Top 10 List
    top = []
    for i in range(10):
        top.append([])
        top[i].append(None)
        top[i].append(-math.inf)

    #--------------------------------------------------------- Evaluate Fitness ---------------------------------------------------------
    
    for i in range(population_size):
        nb_tests = 100

        for j in range(nb_tests):
            index = random.choice([0, 1, 2, 3])
            result = population[i].feedForward(datas[index]).getA1()
            fitness[i] += (1 - numpy.absolute(answers[index] - result[0]))

        fitness[i] = (fitness[i] / nb_tests) * 100


        #Get TOP 10
        for j in range(len(top)):
            if fitness[i] > top[j][1]:
                top[j][0] = population[i]
                top[j][1] = fitness[i]
                break


    print("---------------------------- Generation " + str(generation + 1) + " ----------------------------")
    for entry in top:
        print(entry[1])

    #--------------------------------------------------------- Make new Population ---------------------------------------------------------

    population = []
    fitness = []

    #For each TOP 10
    for i in range(len(top)):
        #Add itself
        population.append(top[i][0].copy())
        fitness.append(0)

        #Add 10 mutated version of itself
        for j in range(100):
            population.append(top[i][0].reproduce())
            fitness.append(0)
        
        #For each other TOP 10
        for j in range(i+1, len(top)):
            #Add 20 Crossovers
            for k in range(20):
                population.append(top[i][0].crossover(top[j][0]))
                fitness.append(0)

    #Fill remaining with new Neural Networks
    for i in range(population_size - len(population)):
        population.append(NeuralNetwork(2, [2], 1))
        fitness.append(0)

    population_size = len(population)

print(top[0].feedForward(datas[0]))
print(top[0].feedForward(datas[1]))
print(top[0].feedForward(datas[2]))
print(top[0].feedForward(datas[3]))

I don't know why, but I previously had the TOP 10 NN attain 100% fitness in 15 generations or so. Now, even after 100 generation, it doesn't go higher than 60%...

Please overlook any malpractice and/or Python aberrations, I am learning the language on the go while developing this project.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source