Bike Sharing Project Answers
Table of Contents
Introduction
The Bike Sharing Project uses a neural network to predict daily ridership for a bike sharing service. The code is split into two parts - a jupyter notebook that you work with and a python file (my_answers.py
) where you put the parts of the code that isn't provided. This creates the my_answer.py
file.
Imports
import numpy
The Neural Network
class NeuralNetwork(object):
"""Implementation of a neural network with one hidden layer
Args:
input_nodes: number of input nodes
hidden_nodes: number of hidden nodes
output_nodes: number of output_nodes
learning_rate: rate at which to update the weights
"""
def __init__(self, input_nodes: int, hidden_nodes: int, output_nodes:int,
learning_rate: float) -> None:
# Set number of nodes in input, hidden and output layers.
self.input_nodes = input_nodes
self.hidden_nodes = hidden_nodes
self.output_nodes = output_nodes
self.learning_rate = learning_rate
# Initialize weights
self._weights_input_to_hidden = None
self._weights_hidden_to_output = None
return
Input To Hidden Weights
@property
def weights_input_to_hidden(self) -> numpy.ndarray:
"""Array of weights from input layer to the hidden layer"""
if self._weights_input_to_hidden is None:
self._weights_input_to_hidden = numpy.random.normal(
0.0, self.input_nodes**-0.5,
(self.input_nodes, self.hidden_nodes))
return self._weights_input_to_hidden
The unit-test tries to set the weights so we need a setter.
@weights_input_to_hidden.setter
def weights_input_to_hidden(self, weights: numpy.ndarray) -> None:
"""Sets the weights"""
self._weights_input_to_hidden = weights
return
Hidden To Output Weights
@property
def weights_hidden_to_output(self):
"""Array of weights for edges from hidden layer to output"""
if self._weights_hidden_to_output is None:
self._weights_hidden_to_output = numpy.random.normal(
0.0,
self.hidden_nodes**-0.5,
(self.hidden_nodes, self.output_nodes))
return self._weights_hidden_to_output
Once again, this is for the unit-testing.
@weights_hidden_to_output.setter
def weights_hidden_to_output(self, weights: numpy.ndarray) -> None:
"""sets the weights for edges from hidden layer to output"""
self._weights_hidden_to_output = weights
return
Activation Function
def activation_function(self, value):
"""A pass-through to the sigmoid"""
return self.sigmoid(value)
Sigmoid
def sigmoid(self, value):
"""Calculates the sigmoid of the value"""
return 1/(1 + numpy.exp(-value))
Train
def train(self, features, targets):
''' Train the network on batch of features and targets.
Arguments
---------
features: 2D array, each row is one data record, each column is a feature
targets: 1D array of target values
'''
n_records = features.shape[0]
delta_weights_i_h = numpy.zeros(self.weights_input_to_hidden.shape)
delta_weights_h_o = numpy.zeros(self.weights_hidden_to_output.shape)
for X, y in zip(features, targets):
final_outputs, hidden_outputs = self.forward_pass_train(X)
# Implement the backpropagation function below
delta_weights_i_h, delta_weights_h_o = self.backpropagation(
final_outputs, hidden_outputs, X, y,
delta_weights_i_h, delta_weights_h_o)
self.update_weights(delta_weights_i_h, delta_weights_h_o, n_records)
Forward Pass Train
def forward_pass_train(self, X):
''' Implement forward pass here
Arguments
---------
X: features batch
'''
hidden_inputs = numpy.matmul(X, self.weights_input_to_hidden)
hidden_outputs = self.activation_function(hidden_inputs)
final_inputs = numpy.matmul(hidden_outputs, self.weights_hidden_to_output)
final_outputs = final_inputs
return final_outputs, hidden_outputs
Back Propagation
def backpropagation(self, final_outputs, hidden_outputs, X, y, delta_weights_i_h, delta_weights_h_o):
''' Implement backpropagation
Arguments
---------
final_outputs: output from forward pass
y: target (i.e. label) batch
delta_weights_i_h: change in weights from input to hidden layers
delta_weights_h_o: change in weights from hidden to output layers
'''
error = final_outputs - y
hidden_error = numpy.matmul(self.weights_hidden_to_output, error)
output_error_term = error
hidden_error_term = hidden_error * hidden_outputs * (1 - hidden_outputs)
delta_weights_i_h += -hidden_error_term * X[:, None]
delta_weights_h_o += -output_error_term * hidden_outputs[:,None]
return delta_weights_i_h, delta_weights_h_o
Update Weights
def update_weights(self, delta_weights_i_h, delta_weights_h_o, n_records):
''' Update weights on gradient descent step
Arguments
---------
delta_weights_i_h: change in weights from input to hidden layers
delta_weights_h_o: change in weights from hidden to output layers
n_records: number of records
'''
self.weights_hidden_to_output += self.learning_rate * (delta_weights_h_o/n_records)
self.weights_input_to_hidden += self.learning_rate * (delta_weights_i_h/n_records)
return
Run
Warning: The MSE function defined in the jupyter notebook won't work if you use numpy.dot instead of numpy.matmul. You can make it work by passing in axis=1
to numpy.mean but I don't think you're allowed to change the things in the jupyter notebook.
def run(self, features):
''' Run a forward pass through the network with input features
Arguments
---------
features: 1D array of feature values
'''
hidden_inputs = numpy.matmul(features, self.weights_input_to_hidden)
hidden_outputs = self.activation_function(hidden_inputs)
final_inputs = numpy.matmul(hidden_outputs, self.weights_hidden_to_output)
final_outputs = final_inputs
return final_outputs
The Hyper Parameters
iterations = 7500
learning_rate = 0.4
hidden_nodes = 28
output_nodes = 1