Neural Networks

Filed in Python Leave a comment

I’ve recently been working with neural networks and currently have a neural network class and a simple program to evolve the network weights.

The class and a few methods are defined in NeuralFunctions.py. The function createRandomWeights is currently set up to create a 9 neuron network when test = True and a completely random network when test = False.

Neural Network

Neural Network

The weights are defined as:

Neuron Weights

Neuron Weights

# Neural Network Class Functions
# NeuralFunctions.py
# Written in Python.  See http://www.python.org/
# Placed in the public domain.
# Chad Bonner 1.31.2015
# Based on Back-Propagation Neural Networks by
# Neil Schemenauer <nas@arctrix.com>

print "NeuralNet v1.0"
import math
import random
import numpy as np

weightLimit = 5 #Somewhat arbitrary choice

#-------------Support Functions----------------
random.seed()

def createRandomWeights(n, weightLimit, test):
    # n = number of neurons
    # create matrix for weights
    if test == True:
        weights = np.array([[0.0 for i in range(n)] for j in range(n)])
        weights[2,0] = random.uniform(-weightLimit,weightLimit)
        weights[2,1] = random.uniform(-weightLimit,weightLimit)        
        weights[3,0] = random.uniform(-weightLimit,weightLimit)        
        weights[3,1] = random.uniform(-weightLimit,weightLimit)        
        weights[4,2] = random.uniform(-weightLimit,weightLimit)
        weights[4,3] = random.uniform(-weightLimit,weightLimit)        
    else:
        weights = np.array([[random.uniform(-weightLimit,weightLimit)
        for i in range(n)] 
            for j in range(n)])
    return weights

def createNets(numNets, sizeNet, numOut):
    # Create population of neural nets
    # Create a set of network connection weights with random weights
    # Instantiate nets and load with weights
    # Set test = True to use predefined weights
    test = True
    nets = [] #create empty variable to hold neural nets
    for id in range(numNets):
        #create weights for neuron connections
        weights = createRandomWeights(sizeNet, weightLimit, test)
        #instantiate a neural net
        # id=0, number of neurons, number of outputs, dna
        nets.append(NeuralNet(id, sizeNet, numOut, weights))
    return nets

# our sigmoid function, tanh is a little nicer than the standard 1/(1+e^-x)
def sigmoid(x):
    # init output variable
    y = np.array([0.0]*len(x))        
    for i in range(len(x)):
        y[i] = math.tanh(x[i])*2    
    return y


#------------ Neural Net Class Definition ------------------------------
class NeuralNet:
    def __init__(self, id, n, no, weights):
        # n = number of neurons
        # no = number of outputs
        self.id = id
        self.n = n
        self.no = no
        self.weights = weights
        # activations for nodes
        self.ai = np.array([0]*self.n) #make all inputs neutral
        self.an = np.array([0]*self.n) #neuron outputs
        self.summ = np.array([0]*self.n) #init neuron activation levels to 0
    def identifyNet(self):
        print 'Net ', self.id
    def update(self, neuronInputs):
        # init neuron activation levels to 0
        self.summ = np.array([0]*self.n)
        # Apply inputs to input neurons
        self.ai[:len(neuronInputs)] = neuronInputs      
        # Add neuron inputs to activation level
        self.summ = self.ai + np.dot(self.weights, self.an)
        # set neuron outputs
        self.an = sigmoid(self.summ)
        # net output is the output from the last no neurons
        out = (self.an[self.n-self.no:])
        #print "out", out

        return out

 

# Evolution.py
# 
# Written in Python.  See http://www.python.org/
# Placed in the public domain.
# Chad Bonner 1.31.2015

from __future__ import division
import NeuralFunctions as nf
import weightedRandomChoice as wrc
import random
import numpy as np
import csv
import operator
from collections import defaultdict


# Setup
numGenerations = 50     #number of generations
numIterations = 4   #number of times to run each net
numNets =2000 #Size of the population
sizeNet = 5 #Number of neurons in each net
numOut = 1 #Number of outputs for each net
nets = [] #create empty variable to hold neural nets
nextGenNets = [] #create empty variable to hold neural nets
netFitness = {} #dictionary to hold net index and fitness
indexParents = [] #index of nets to be parents of next gen
parentDNA = [] #dna of parents
childDNA = [] #dna of children in the next gen

inputs = np.array([[0,0],[1,0],[0,1],[1,1]])
#============================================================================
#inputs = np.array([[0,0,0,0],[0,0,0,1],[0,0,1,0],[0,0,1,1],
#          [0,1,0,0],[0,1,0,1],[0,1,1,0],[0,1,1,1],
#          [1,0,0,0],[1,0,0,1],[1,0,1,0],[1,0,1,1],
#          [1,1,0,0],[1,1,0,1],[1,1,1,0],[1,1,1,1]])
#============================================================================

def netEvaluate(nets, inputs):
    #Apply input to each net and evaluate fitness
    fitness = {} #dictionary to hold net index and fitness    
    for index, net in enumerate(nets):
        #let signals progagate through net for iterations
        #then look at output
        fitness[index] = 0
        for i in range(len(inputs)):
            #print "Input:", i
            for c in range(numIterations): #allow for propgation to output
                out = net.update(inputs[i])
            out = net.update(inputs[i])    
#============================================================================        #Modify fitness here to suit application
    #Arbitrary choice of matching for input pattern 0101. A correct match
    #will have an output of 1 for an input of 0101 and an output of -1 for
    #all other input patterns. 
            #Set out = [1,0] for even input
#            if np.any(np.all(np.equal(inputs[i], 
#            [[0,0,0,0],[0,0,1,0],[0,1,0,0],[0,1,1,0],[1,0,0,0],
#             [1,0,1,0],[1,1,0,0],[1,1,1,0]]), 1)):
#                if out[0] >= 1 and out[1] < 0.2:
#                    fitness[index] = fitness[index] + 1 #award points
#            else:
#                if out[0] < 0.2 and out[1] >= 1:
#                    fitness[index] = fitness[index] + 1 #award
#============================================================================
#============================================================================
#         #Modify fitness here to suit application
            if np.any(np.all(np.equal(inputs[i], [[0,1],[1,0]]), 1)):
                #print "Should be 1: Out =", out
                if out >= [0.9]:
                    fitness[index] = fitness[index] + 1 #award points
                    #print "Rewarded for +1 output"
            else:
                #print "Should be 0: Out =", out
                if out < [0.2]:
                    fitness[index] = fitness[index] + 1 #award
                    #print "Rewarded for 0 output"
        if fitness[index] == 4:
            fitness[index] = 15 #award a lot of points for correct answer
#============================================================================
    return fitness

def writeFile(f, generation, fitness, fitIndex, nets, inputs, writeAll):
    # Write data to CSV file
    outputs = [] #create empty variable to hold net outputs
    writer = csv.writer(f, delimiter=',')
    if writeAll == True:    
        for net in nets:        
            writer.writerow(['Generation', 'Fitness', 'ID', 'SizeNet',
            'NumOut', 'Weights'])
            writer.writerow([generation, fitness, net.id, net.n, net.no])
            for j in range(net.n):
                writer.writerow(net.weights[j])
    else:
        writer.writerow(['Generation', 'Fitness', 'ID', 'SizeNet',
        'NumOut', 'Weights'])
        writer.writerow([generation, fitness, nets[fitIndex].id,
        nets[fitIndex].n,            
            nets[fitIndex].no])
        for i in range(len(inputs)):
            for c in range(numIterations):
                nets[fitIndex].update(inputs[i])
            outputs.append(nets[fitIndex].update(inputs[i]))
        writer.writerow(outputs)        
        print outputs
        for j in range(nets[fitIndex].n):
            writer.writerow(nets[fitIndex].weights[j])
    return
    
def breed(netFitness, nets):
    # Create new population by combining 
    # parents's dna (sexual reproduction).
    # Each net can combine with any other net, even itself.
    #print netFitness
    pairs = zip(netFitness.values(),netFitness.keys())
    pairs.sort()
    pairs.reverse()
    nextGenNets = [] # clear next gen variable for use
    for c, net in enumerate(nets):
        mom = wrc.weighted_random_choice(pairs)        
        #print "mom", mom
        dad = wrc.weighted_random_choice(pairs)        
        #print "dad", dad        
        childDNA = np.array([[0.0 for i in range(net.n)]
            for j in range(net.n)])
        for neuron in range(net.n):
            #take one neuron from each parent and combine to form child        
            childDNA[neuron,:] = nets[random.choice((mom,dad))].weights[neuron,:]
        # now create new net using child dna
        # instantiate a neural net
        nextGenNets.append(nf.NeuralNet(c, net.n, net.no, childDNA))
    return nextGenNets

# ------------------------- Main Routine ------------------------------
#def main():
#Open file to save data
csvfile = open('data_out.csv', 'wb')

#Create and evaluate initial population of neural nets
print "Genesis!"
nets = nf.createNets(numNets, sizeNet, numOut)
# Apply input to each net and evaluate fitness
netFitness = netEvaluate(nets, inputs)
fitHistogram = defaultdict(int) #variable to hold count of fitness values    
#Count frequency of fitness values
for value in netFitness.values(): fitHistogram[value] += 1
#print fitHistogram
print "Generation Fitness:", "%.3f"%((sum(netFitness.values())/numNets))
# Identify net with maximum fitness and save to log file
mostFit = max(netFitness.iteritems(), key=operator.itemgetter(1))[0]
mostFitness = netFitness[mostFit]
print "Most Fit:", mostFit
writeFile(csvfile, 0, mostFitness, mostFit, nets, inputs, False)

# Go forth and multiply
for generation in range(1, numGenerations):
    #Breed next generation
    nets = breed(netFitness, nets)
    print " "        
    print "Generation:", generation, "born"    
    # Apply input to each net and evaluate fitness
    netFitness = netEvaluate(nets, inputs)
    fitHistogram = defaultdict(int) #variable to hold count of fitness values    
    #Count frequency of fitness values
    for value in netFitness.values(): fitHistogram[value] += 1
    print fitHistogram
    print "Generation Fitness:", "%.3f"%((sum(netFitness.values())/numNets))
    # Identify net with maximum fitness and save to log file
    mostFit = max(netFitness.iteritems(), key=operator.itemgetter(1))[0]
    mostFitness = netFitness[mostFit]
    print "Most Fit:", mostFit
    writeFile(csvfile, generation, mostFitness, mostFit, nets, inputs, False)
    #print "Best net saved"
    #print "Iteration", generation, "complete!"
csvfile.close()
print "Done!"

 

Currently it takes about 12 seconds to run a population of 50 nets of 500 neurons each for 50 cycles. I’m using numpy to speed things up. I’m sure there are still opportunities for improvement. Creating the nets actually takes longer than running them as shown below (click to enlarge).

Neural Network Run Time

Neural Network Run Time

TOP