-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNeuralNetworkClassifier.py
285 lines (237 loc) · 9.86 KB
/
NeuralNetworkClassifier.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# Dependencies
import numpy as np
import difflib
class NeuralNetworkClassifier(object):
def __init__(self, X, y):
"""Initialise NeuralNetworkClassifier Object
Keyword Arguments:
X -- data array
y -- target array
"""
# Store data and target matrices
self.X = self.prepare_data(X)
self.y = y
self.classes = self.get_classes(self.y)
# target is converted from categorical string values into boolean vectors
self.target = self.normalise_categorical_data(self.y)
# Calculate input and output layer sizes
self.input_layer_size = len(self.X[0])
# the network's hidden layer size is initialised to 0
self.hidden_layer_size = 0
# The network's output layer has one unit for each class in the target data
self.output_layer_size = len(self.classes)
# Network weight matrices
self.W1 = []
self.W2 = []
# learning rate for updating network values
self.epsilon = 0.01
def build_network(self, hidden_layer_size, iterations, learning_rate):
"""Builds and trains a Neural Network Classifier
Keyword Arguments:
hidden_layer_size -- number of nodes in the hidden layer
iterations -- number of times training data is propagated through the network
"""
# Assign value of hidden layer size
self.hidden_layer_size = hidden_layer_size
self.epsilon = learning_rate
# Initialise Weight matrices to random values.
np.random.seed(0)
self.W1 = np.random.randn(self.input_layer_size, self.hidden_layer_size)
self.W2 = np.random.randn(self.hidden_layer_size, self.output_layer_size)
count = 0
# Repeat for specified number of iterations
for i in range(0, iterations, 1):
# Forward Propagation
z2 = self.X.dot(self.W1)
a2 = self.sigmoid(z2)
z3 = a2.dot(self.W2)
y_hat = self.sigmoid(z3)
# Print accuracy of training prediction every 500 iterations
# if i % 500 == 0:
# training_accuracy = self.calculate_training_accuracy(y_hat)
# print "Iteration Number = " + str(i)
# print "Accuracy = " + str(training_accuracy)
# Back Propagation
delta_3 = y_hat - self.target
delta_3 = np.multiply(delta_3, self.sigmoid_prime(z3))
delta_2 = np.dot(delta_3, self.W2.T)
delta_2 = np.multiply(delta_2, self.sigmoid_prime(z2))
# Update Weights
dJdW2 = np.dot(a2.T, delta_3)
dJdW1 = np.dot(self.X.T, delta_2)
self.W2 -= self.epsilon * dJdW2
self.W1 -= self.epsilon * dJdW1
count += 1
# Perform classification
def classify_set(self, X):
"""
Classify a set of testing data
:param X: Set of testing data to be classified
:return: Numpy array of results
"""
data = self.prepare_data(X)
classified = []
# Input data is propagated forward through the artificial neural network
z2 = data.dot(self.W1)
a2 = self.sigmoid(z2)
z3 = a2.dot(self.W2)
y_hat = self.sigmoid(z3)
# For each result in y_hat, the index with the highest value corresponds to the class to be added to the array.
for result in y_hat:
normalised_result = self.normalise_numerical_data(result)
max_probability = 0
prediction = "inconclusive"
for index in range(0, len(result), 1):
if normalised_result[index] > max_probability:
max_probability = normalised_result[index]
prediction = self.classes[index]
classified.append(prediction)
return np.array(classified)
# Utility Methods
def prepare_data(self, input_array):
"""
Prepares the input data for the network.
Categorical data is converted into boolean vector form
Numerical data is normalised
:param input_array: the array of training examples to be prepared
:return: Array of correctly formatted input data
"""
# Create array of arrays, each contains one feature of the dataset.
separated_features = np.array(map(list, zip(*input_array)))
# For each array:
temp_array = []
for feature_values in separated_features:
# Check if data is categorical or not
if self.target_is_categorical(feature_values):
# If the data is categorical, convert it to a boolean vectors
feature_values = self.normalise_categorical_data(feature_values)
else:
# Otherwise, the data is numerical and will be normalised
feature_values = self.normalise_numerical_data(feature_values)
temp_array.append(np.array(feature_values))
reconstructed_data = []
# Reconstruct the feature array with the new data
for index in range(0, len(temp_array[0]), 1):
reconstructed_entry = []
for entry in temp_array:
# If the entry is a boolean vector, append each entry in turn rather than the vector itself
if isinstance(entry[index], np.ndarray):
for element in entry[index]:
reconstructed_entry.append(element)
else:
reconstructed_entry.append(entry[index])
reconstructed_data.append(np.array(reconstructed_entry))
reconstructed_data = np.array(reconstructed_data)
return reconstructed_data
def normalise_categorical_data(self, input_array):
"""
Returns an array of boolean vectors corresponding to the class of each training example in the form of a
numpy array
:param input_array: the array of categorical elements to be converted into boolean vectors
:return: array of boolean vectors
"""
# Initialise target list
target = []
classes = self.get_classes(input_array)
# Convert each class into a boolean vector
for entry in input_array:
# Initialise a list of zeros the same length as the classes list
target_entry = [0] * len(classes)
for value in classes:
# Set the index in target_entry corresponding to the training example to 1
if entry == value:
target_entry[classes.index(value)] = 1.0
# Append target_entry to target
target.append(np.array(target_entry))
return np.array(target)
@staticmethod
def normalise_numerical_data(input_array):
"""
Normalises a list of floats
:param input_array: List of floats
:return: Normalised list of floats
"""
""""""
float_list = []
for entry in input_array:
float_list.append(float(entry))
float_array = np.array(float_list)
max_value = np.amax(float_array)
index = 0
return_list = []
for entry in float_array:
return_list.append(entry/max_value)
index += 1
return_array = np.array(return_list)
return return_array
@staticmethod
def get_classes(input_array):
"""
Stores each class in an input array in a list
:param input_array: array of categorical data to have lists checked
:return: Array containing on instance of each class in input_array
"""
classes = []
for entry in input_array:
if entry not in classes:
classes.append(entry)
return classes
@staticmethod
def sigmoid(z):
"""
Applies a sigmoid activation function to the input
:rtype: np.ndarray
:param z: Matrix of floats
:return: Matrix of floats
"""
result = 1 / (1 + np.exp(-z))
# Handle overflow errors
result[np.isnan(result)] = 0
return result
@staticmethod
def sigmoid_prime(z):
"""
Applies the derivative of the sigmoid activation function to the input
:param z: Matrix of floats
:return: Matrix of floats
"""
result = np.exp(-z) / ((1 + np.exp(-z)) ** 2)
# Handle overflow errors
result[np.isnan(result)] = 0
return result
@staticmethod
def target_is_categorical(target):
"""
Returns boolean value indicating whether or not the data is categorical
:param target: list of
:return: True if data in target is categorical, False otherwise
"""
result = True
try:
float(target[0])
result = False
except ValueError:
pass
return result
def calculate_training_accuracy(self, y_hat):
"""
Returns the proportion similarity between the input list and the training target data
:param y_hat: Array of values to be compared to training target
:return: Float value of the accuracy of the prediction
"""
classified = []
# For each result in y_hat, the index with the highest value corresponds to the class to be added to the array.
for result in y_hat:
normalised_result = self.normalise_numerical_data(result)
max_probability = 0.0
prediction = "inconclusive"
for index in xrange(len(normalised_result)):
probability = float(normalised_result[index])
if probability > max_probability:
max_probability = result[index]
prediction = self.classes[index]
classified.append(prediction)
# compare the two lists
sequence_matcher = difflib.SequenceMatcher(None, self.y, classified)
accuracy = sequence_matcher.ratio()
return accuracy