|
| 1 | + |
| 2 | +#%% packages |
| 3 | +import numpy as np |
| 4 | +import pandas as pd |
| 5 | +import seaborn as sns |
| 6 | +from sklearn.preprocessing import StandardScaler |
| 7 | +from sklearn.model_selection import train_test_split |
| 8 | +from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay |
| 9 | + |
| 10 | +#%% data prep |
| 11 | +# source: https://www.kaggle.com/datasets/rashikrahmanpritom/heart-attack-analysis-prediction-dataset |
| 12 | +df = pd.read_csv('heart.csv') |
| 13 | +df.head() |
| 14 | + |
| 15 | +#%% separate independent / dependent features |
| 16 | +X = np.array(df.loc[ :, df.columns != 'output']) |
| 17 | +y = np.array(df['output']) |
| 18 | + |
| 19 | +print(f"X: {X.shape}, y: {y.shape}") |
| 20 | + |
| 21 | +#%% Train / Test Split |
| 22 | +X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=12) |
| 23 | + |
| 24 | +#%% scale the data |
| 25 | +scaler = StandardScaler() |
| 26 | +X_train_scale = scaler.fit_transform(X_train) |
| 27 | +X_test_scale = scaler.transform(X_test) |
| 28 | + |
| 29 | +#%% network class |
| 30 | +class CustomNeuralNetwork: |
| 31 | + def __init__(self, learning_rate, X_train, y_train, X_test, y_test): |
| 32 | + self.weights = np.random.randn(X_train.shape[1]) |
| 33 | + self.biases = np.random.randn() |
| 34 | + self.learning_rate = learning_rate |
| 35 | + self.X_train = X_train |
| 36 | + self.y_train = y_train |
| 37 | + self.X_test = X_test |
| 38 | + self.y_test = y_test |
| 39 | + self.learning_losses_train = [] |
| 40 | + self.learning_losses_test = [] |
| 41 | + |
| 42 | + def activationFunction(self, x): |
| 43 | + # sigmoid |
| 44 | + return 1 / (1 + np.exp(-x)) |
| 45 | + |
| 46 | + def activationDerivative(self, x): |
| 47 | + # sigmoid derivative |
| 48 | + return self.activationFunction(x) * (1 - self.activationFunction(x)) |
| 49 | + |
| 50 | + def forwardPass(self, derivative_x): |
| 51 | + hidden_layer_1 = np.dot(derivative_x, self.weights) + self.biases |
| 52 | + activate_layer = self.activationFunction(hidden_layer_1) |
| 53 | + |
| 54 | + return activate_layer |
| 55 | + |
| 56 | + def backwardPass(self, independent_features, y_true_values): |
| 57 | + # calculcate gradients1 |
| 58 | + hidden_layer_1 = np.dot(independent_features, self.weights) + self.biases |
| 59 | + y_predictions = self.forwardPass(independent_features) |
| 60 | + |
| 61 | + derivative_of_losses_to_predictions = 2 * (y_predictions - y_true_values) |
| 62 | + derivative_of_predictions_to_hidden_layer = self.activationDerivative(hidden_layer_1) |
| 63 | + derivative_of_hidden_layer_biases = 1 |
| 64 | + derivative_of_hidden_layer_weights = independent_features |
| 65 | + |
| 66 | + derivatives_biases = derivative_of_losses_to_predictions * derivative_of_predictions_to_hidden_layer * derivative_of_hidden_layer_biases |
| 67 | + derivatives_weights = derivative_of_losses_to_predictions * derivative_of_predictions_to_hidden_layer * derivative_of_hidden_layer_weights |
| 68 | + |
| 69 | + return derivatives_biases, derivatives_weights |
| 70 | + |
| 71 | + def optimiser(self, derivatives_biases, derivatives_weights): |
| 72 | + # update weights |
| 73 | + self.biases = self.biases - derivatives_biases * self.learning_rate |
| 74 | + self.weights = self.weights - derivatives_weights * self.learning_rate |
| 75 | + |
| 76 | + def train(self, iterations): |
| 77 | + for iteration in range(iterations): |
| 78 | + # get random position |
| 79 | + random_position = np.random.randint(len(self.X_train)) |
| 80 | + |
| 81 | + # forward pass |
| 82 | + y_train_true = self.y_train[random_position] |
| 83 | + y_train_predictictions = self.forwardPass(self.X_train[random_position]) |
| 84 | + |
| 85 | + # calculate training losses |
| 86 | + losses = np.sum(np.square(y_train_predictictions - y_train_true)) |
| 87 | + self.learning_losses_train.append(losses) |
| 88 | + |
| 89 | + # calculate gradients |
| 90 | + derivatives_biases, derivatives_weights = self.backwardPass(self.X_train[random_position], self.y_train[random_position]) |
| 91 | + |
| 92 | + # update rates |
| 93 | + self.optimiser(derivatives_biases, derivatives_weights) |
| 94 | + |
| 95 | + # calculate errors for test data |
| 96 | + losses_sum = 0 |
| 97 | + for i in range(len(self.X_test)): |
| 98 | + y_true_value = self.y_test[i] |
| 99 | + y_predictions = self.forwardPass(self.X_test[i]) |
| 100 | + |
| 101 | + losses_sum += np.square(y_predictions - y_true_value) |
| 102 | + self.learning_losses_test.append(losses_sum) |
| 103 | + return 'training done' |
| 104 | + |
| 105 | +#%% Hyper parameters |
| 106 | +learning_rate = 0.2 |
| 107 | +iterations = 1000 |
| 108 | + |
| 109 | +#%% model instance and training |
| 110 | +neural_network = CustomNeuralNetwork(learning_rate, X_train_scale, y_train, X_test_scale, y_test) |
| 111 | + |
| 112 | +neural_network.train(iterations) |
| 113 | + |
| 114 | +# %% check losses |
| 115 | +sns.lineplot(x=list(range(len(neural_network.learning_losses_test))), y=neural_network.learning_losses_test) |
| 116 | + |
| 117 | +# %% iterate over test data |
| 118 | +total_observations = X_test_scale.shape[0] |
| 119 | +correct_predictions = 0 |
| 120 | +y_preditctions = [] |
| 121 | +for i in range(total_observations): |
| 122 | + y_true = y_test[i] |
| 123 | + y_preditction = np.round(neural_network.forwardPass(X_test_scale[i])) |
| 124 | + y_preditctions.append(y_preditction) |
| 125 | + correct_predictions += 1 if y_true == y_preditction else 0 |
| 126 | + |
| 127 | +# %% Calculate Accuracy |
| 128 | +correct_predictions / total_observations |
| 129 | + |
| 130 | +# %% Baseline Classifier |
| 131 | +from collections import Counter |
| 132 | +Counter(y_test) |
| 133 | + |
| 134 | +# %% Confusion Matrix |
| 135 | +cm = confusion_matrix(y_true=y_test, y_pred=y_preditctions) |
| 136 | + |
| 137 | + |
| 138 | +# %% |
| 139 | +import matplotlib.pyplot as plt |
| 140 | +disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['true', 'false']) |
| 141 | + |
| 142 | +disp.plot() |
| 143 | +plt.show() |
| 144 | + |
| 145 | +# %% |
0 commit comments