Instructions
Requirements and Specifications
Source Code
import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import matplotlib.pyplot as plt
class NeuralNetwork:
def __init__(self, layers, X=None, y=None,
activation=lambda x: 1 / (1 + np.exp(-x)),
deriv_activation=lambda x: np.multiply(x, 1 - x), learning_rate=0.15):
self.X = X
self.y = y
self.weights = list()
self.layers = layers
self.X_train = None
self.y_train = None
self.X_test = None
self.y_test = None
self.act = activation
self.dact = deriv_activation
self.lr = learning_rate
self.losses = list()
self.accuracies = list()
def train_test_split(self, test_size = 0.3):
if self.X is None:
raise ValueError('No dataset has been loaded into the Neural Network model')
# Compute the number of training samples
n_train = len(self.X)-int(test_size * len(self.X))
# Split
self.X_train = self.X[:n_train]
self.y_train = self.y[:n_train]
self.X_test = self.X[n_train:]
self.y_test = self.y[n_train:]
# print info
print(f"There are a total of {len(self.X)} samples and {len(self.y[0])} features in the dataset.")
print(f"There are {len(self.X_train)} samples in the train dataset.")
print(f"There are {len(self.X_test)} samples in the test dataset.")
def initialize_weights(self, layers):
# Initialize weights for hidden layers (not taking into account the input and output layers)
n_layers = len(layers)
for i in range(1, n_layers):
w = [
[np.random.uniform(-1, 1) for k in range(layers[i - 1] + 1)] for j in range(layers[i])
]
self.weights.append(np.matrix(w))
def fit(self, epochs=10):
# Initialize weights for hidden layers (not taking into account the input and output layers)
n_layers = len(self.layers) - 1
self.initialize_weights(self.layers)
# Update weights and print acc for each epoch
print('-' * 40)
print('{:<10s}{:>10s}{:>12s}'.format('Epoch', 'Accuracy (%)', 'Loss'))
print('{:<10s}{:>10s}{:>12s}'.format('-----', '------------', '----'))
for epoch in range(epochs):
# Update weights
err = self.train()
# Compute current accuracy
accuracy = self.accuracy(self.X_train, self.y_train)
print('{:<10d}{:<20.2f}{:<20.6f}'.format(epoch + 1, accuracy * 100.0, abs(err.mean())))
self.losses.append(abs(err.mean()))
self.accuracies.append(accuracy)
# After all training, print test accuracy
print('-' * 40)
val_acc = self.accuracy(self.X_test, self.y_test)
print('Model accuracy: {:.2f}%'.format(val_acc * 100.0))
print('-' * 40)
def accuracy(self, X, y):
n_good = 0
for i in range(len(X)):
y_pred = self.predict(X[i])
if list(y[i]) == y_pred:
n_good += 1
return n_good / len(X)
def predict(self, X):
X = np.append(1, X)
# Compute the activations
result = self.forward_prop(X)
output = result[-1].A1
index = self.find_max_activation(output)
y = [0 for i in range(len(output))]
y[index] = 1
return y
def find_max_activation(self, output):
m, index = output[0], 0
for i in range(1, len(output)):
if output[i] > m:
m, index = output[i], i
return index
def forward_prop(self, x):
activations = [x]
layer_input = x
for i in range(len(self.weights)):
activation = self.act(np.dot(layer_input, self.weights[i].T))
activations.append(activation)
layer_input = np.append(1, activation) # Augment with bias
return activations
def backward_prop(self, y, activations):
outputFinal = activations[-1]
error = np.matrix(y - outputFinal) # Error at output
; finalErr = None
n_layers = len(self.weights)
for j in range(n_layers, 0, -1):
currActivation = activations[j]
if j > 1:
# Augment previous activation
prevActivation = np.append(1, activations[j - 1])
else:
# First hidden layer, prevActivation is input (without bias)
prevActivation = activations[0]
delta = np.multiply(error, self.dact(currActivation))
self.weights[j - 1] += self.lr * np.multiply(delta.T, prevActivation)
# Remove bias from weights to calculate error
w = np.delete(self.weights[j - 1], [0], axis=1)
error = np.dot(delta, w) # Error at current layer
if j == 1:
finalErr = error
return finalErr
def train(self):
X = self.X_train
Y = self.y_train
n_layers = len(self.weights)
for i in range(len(X)):
x, y = X[i], Y[i]
x = np.matrix(np.append(1, x)) # Augment feature vector
activations = self.forward_prop(x)
err = self.backward_prop(y, activations)
return err
if __name__ == '__main__':
# Load data
df = pd.read_csv('ANN - Iris data.txt', header=None, names=['sepal_length', 'sepal_width',
'petal_length', 'petal_width', 'label'])
# Extract dependant and independent variables and convert to numpy arrays
y = df['label'].to_numpy()
X = df.drop(columns=['label']).to_numpy()
# Encode the dependent variable y
y = y.reshape(-1, 1)
encoder = OneHotEncoder(sparse=False)
y = encoder.fit_transform(y)
layers = [X.shape[1], 8, 16, y.shape[1]]
epochs = 100
nn = NeuralNetwork(layers, X, y)
nn.train_test_split(0.2)
nn.fit(epochs)
# Plot accuracy and loss
fig, axes = plt.subplots(nrows=1, ncols=2)
axes[0].plot(range(epochs), nn.accuracies)
axes[0].set_xlabel('Epoch')
axes[0].set_ylabel('Accuracy (%)')
axes[0].grid(True)
axes[1].plot(range(epochs), nn.losses)
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Loss')
axes[1].grid(True)
plt.show()