Advertisement
here2share

# pure_backpropagation.py

Dec 3rd, 2020
744
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.99 KB | None | 0 0
  1. # pure_backpropagation.py
  2.  
  3. import random
  4. import math
  5.  
  6. class Neuron():
  7.     '''
  8.        A conceptual Neuron hat can be trained using a
  9.        fit and predict methodology, without any library
  10.    '''
  11.    
  12.     def __init__(self, position_in_layer, is_output_neuron=False):
  13.         self.weights = []
  14.         self.inputs = []
  15.         self.output = None
  16.        
  17.         # This is used for the backpropagation update
  18.         self.updated_weights = []
  19.         # This is used to know how to update the weights
  20.         self.is_output_neuron = is_output_neuron
  21.         # This delta is used for the update at the backpropagation
  22.         self.delta = None
  23.         # This is used for the backpropagation update
  24.         self.position_in_layer = position_in_layer
  25.        
  26.     def attach_to_output(self, neurons):
  27.         '''
  28.            Helper function to store the reference of the other neurons
  29.            To this particular neuron (used for backpropagation)
  30.        '''
  31.        
  32.         self.output_neurons = neurons
  33.    
  34.     def sigmoid(self, x):
  35.         '''
  36.            simple sigmoid function (logistic) used for the activation
  37.        '''
  38.         return 1 / (1 + math.exp(-x))
  39.    
  40.     def init_weights(self, num_input):
  41.         '''
  42.            This is used to setup the weights when we know how many inputs there is for
  43.            a given neuron
  44.        '''
  45.        
  46.         # Randomly initalize the weights
  47.         for i in range(num_input+1):
  48.             self.weights.append(random.uniform(0,1))
  49.        
  50.     def predict(self, row):
  51.         '''
  52.            Given a row of data it will predict what the output should be for
  53.            this given neuron. We can have many input, but only one output for a neuron
  54.        '''
  55.        
  56.         # Reset the inputs
  57.         self.inputs = []
  58.        
  59.         # We iterate over the weights and the features in the given row
  60.         activation = 0
  61.         for weight, feature in zip(self.weights, row):
  62.             self.inputs.append(feature)
  63.             activation = activation + weight*feature
  64.            
  65.        
  66.         self.output = self.sigmoid(activation)
  67.         return self.output
  68.    
  69.        
  70.            
  71.     def update_neuron(self):
  72.         '''
  73.            Will update a given neuron weights by replacing the current weights
  74.            with those used during the backpropagation. This need to be done at the end of the
  75.            backpropagation
  76.        '''
  77.        
  78.         self.weights = []
  79.         for new_weight in self.updated_weights:
  80.             self.weights.append(new_weight)
  81.    
  82.     def calculate_update(self, learning_rate, target):
  83.         '''
  84.            This function will calculate the updated weights for this neuron. It will first calculate
  85.            the right delta (depending if this neuron is a ouput or a hidden neuron), then it will
  86.            calculate the right updated_weights. It will not overwrite the weights yet as they are needed
  87.            for other update in the backpropagation algorithm.
  88.        '''
  89.        
  90.         if self.is_output_neuron:
  91.             # Calculate the delta for the output
  92.             self.delta = (self.output - target)*self.output*(1-self.output)
  93.         else:
  94.             # Calculate the delta
  95.             delta_sum = 0
  96.             # this is to know which weights this neuron is contributing in the output layer
  97.             cur_weight_index = self.position_in_layer
  98.             for output_neuron in self.output_neurons:
  99.                 delta_sum = delta_sum + (output_neuron.delta * output_neuron.weights[cur_weight_index])
  100.  
  101.             # Update this neuron delta
  102.             self.delta = delta_sum*self.output*(1-self.output)
  103.            
  104.            
  105.         # Reset the update weights
  106.         self.updated_weights = []
  107.        
  108.         # Iterate over each weight and update them
  109.         for cur_weight, cur_input in zip(self.weights, self.inputs):
  110.             gradient = self.delta*cur_input
  111.             new_weight = cur_weight - learning_rate*gradient
  112.             self.updated_weights.append(new_weight)
  113.  
  114. class Layer():
  115.     '''
  116.        Layer is modelizing a layer in the fully-connected-feedforward neural network architecture.
  117.        It will play the role of connecting everything together inside and will be doing the backpropagation
  118.        update.
  119.    '''
  120.    
  121.     def __init__(self, num_neuron, is_output_layer = False):
  122.        
  123.         # Will create that much neurons in this layer
  124.         self.is_output_layer = is_output_layer
  125.         self.neurons = []
  126.         for i in range(num_neuron):
  127.             # Create neuron
  128.             neuron = Neuron(i,  is_output_neuron=is_output_layer)
  129.             self.neurons.append(neuron)
  130.    
  131.     def attach(self, layer):
  132.         '''
  133.            This function attach the neurons from this layer to another one
  134.            This is needed for the backpropagation algorithm
  135.        '''
  136.         # Iterate over the neurons in the current layer and attach
  137.         # them to the next layer
  138.         for in_neuron in self.neurons:
  139.             in_neuron.attach_to_output(layer.neurons)
  140.            
  141.     def init_layer(self, num_input):
  142.         '''
  143.            This will initialize the weights of each neuron in the layer.
  144.            By giving the right num_input it will spawn the right number of weights
  145.        '''
  146.        
  147.         # Iterate over each of the neuron and initialize
  148.         # the weights that connect with the previous layer
  149.         for neuron in self.neurons:
  150.             neuron.init_weights(num_input)
  151.    
  152.     def predict(self, row):
  153.         '''
  154.            This will calcualte the activations for the full layer given the row of data
  155.            streaming in.
  156.        '''
  157.         row.append(1) # need to add the bias
  158.         activations = [neuron.predict(row) for neuron in self.neurons]
  159.         return activations
  160.  
  161. class MultiLayerPerceptron():
  162.     '''
  163.        We will be creating the multi-layer perceptron with only two layer:
  164.        an input layer, a perceptrons layer and a one neuron output layer which does binary classification
  165.    '''
  166.     def __init__(self, learning_rate = 0.01, num_iteration = 100):
  167.        
  168.         # Layers
  169.         self.layers = []
  170.                
  171.         # Training parameters
  172.         self.learning_rate = learning_rate
  173.         self.num_iteration = num_iteration
  174.        
  175.        
  176.     def add_output_layer(self, num_neuron):
  177.         '''
  178.            This helper function will create a new output layer and add it to the architecture
  179.        '''
  180.         self.layers.insert(0, Layer(num_neuron, is_output_layer = True))
  181.    
  182.     def add_hidden_layer(self, num_neuron):
  183.         '''
  184.            This helper function will create a new hidden layer, add it to the architecture
  185.            and finally attach it to the front of the architecture
  186.        '''
  187.         # Create an hidden layer
  188.         hidden_layer = Layer(num_neuron)
  189.         # Attach the last added layer to this new layer
  190.         hidden_layer.attach(self.layers[0])
  191.         # Add this layers to the architecture
  192.         self.layers.insert(0, hidden_layer)
  193.        
  194.     def update_layers(self, target):
  195.         '''
  196.            Will update all the layers by calculating the updated weights and then updating
  197.            the weights all at once when the new weights are found.
  198.        '''
  199.         # Iterate over each of the layer in reverse order
  200.         # to calculate the updated weights
  201.         for layer in reversed(self.layers):
  202.                            
  203.             # Calculate update the hidden layer
  204.             for neuron in layer.neurons:
  205.                 neuron.calculate_update(self.learning_rate, target)  
  206.        
  207.         # Iterate over each of the layer in normal order
  208.         # to update the weights
  209.         for layer in self.layers:
  210.             for neuron in layer.neurons:
  211.                 neuron.update_neuron()
  212.    
  213.     def fit(self, X, y):
  214.         '''
  215.            Main training function of the neural network algorithm. This will make use of backpropagation.
  216.            It will use stochastic gradient descent by selecting one row at random from the dataset and
  217.            use predict to calculate the error. The error will then be backpropagated and new weights calculated.
  218.            Once all the new weights are calculated, the whole network weights will be updated
  219.        '''
  220.         num_row = len(X)
  221.         num_feature = len(X[0]) # Here we assume that we have a rectangular matrix
  222.        
  223.         # Init the weights throughout each of the layer
  224.         self.layers[0].init_layer(num_feature)
  225.        
  226.         for i in range(1, len(self.layers)):
  227.             num_input = len(self.layers[i-1].neurons)
  228.             self.layers[i].init_layer(num_input)
  229.  
  230.         # Launch the training algorithm
  231.         for i in range(self.num_iteration):
  232.            
  233.             # Stochastic Gradient Descent
  234.             r_i = random.randint(0,num_row-1)
  235.             row = X[r_i] # take the random sample from the dataset
  236.             yhat = self.predict(row)
  237.             target = y[r_i]
  238.            
  239.             # Update the layers using backpropagation  
  240.             self.update_layers(target)
  241.            
  242.             # At every 100 iteration we calculate the error
  243.             # on the whole training set
  244.             if i % 1000 == 0:
  245.                 total_error = 0
  246.                 for r_i in range(num_row):
  247.                     row = X[r_i]
  248.                     yhat = self.predict(row)
  249.                     error = (y[r_i] - yhat)
  250.                     total_error = total_error + error**2
  251.                 mean_error = total_error/num_row
  252.                 print(f"Iteration {i} with error = {mean_error}")
  253.        
  254.    
  255.     def predict(self, row):
  256.         '''
  257.            Prediction function that will take a row of input and give back the output
  258.            of the whole neural network.
  259.        '''
  260.        
  261.         # Gather all the activation in the hidden layer
  262.        
  263.         activations = self.layers[0].predict(row)
  264.         for i in range(1, len(self.layers)):
  265.             activations = self.layers[i].predict(activations)
  266.  
  267.         outputs = []
  268.         for activation in activations:                        
  269.             # Decide if we output a 1 or 0
  270.             if activation >= 0.5:
  271.                 outputs.append(1.0)
  272.             else:
  273.                 outputs.append(0.0)
  274.                            
  275.         # We currently have only One output allowed
  276.         return outputs[0]
  277.  
  278. # XOR function (one or the other but not both)
  279. X = [[0,0], [0,1], [1,0], [1,1]]
  280. y = [0, 1, 1, 0]
  281.  
  282. # Init the parameters for the network
  283. clf = MultiLayerPerceptron(learning_rate = 0.1, num_iteration = 100000)
  284. # Create the architecture backward
  285. clf.add_output_layer(num_neuron = 1)
  286. clf.add_hidden_layer(num_neuron = 3)
  287. clf.add_hidden_layer(num_neuron = 2)
  288. # Train the network
  289. clf.fit(X,y)
  290.  
  291. print("Expected 0.0, got: ",clf.predict([0,0]))
  292. print("Expected 1.0, got: ",clf.predict([0,1]))
  293. print("Expected 1.0, got: ",clf.predict([1,0]))
  294. print("Expected 0.0, got: ",clf.predict([1,1]))
  295.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement