22from tqdm import tqdm
33from numpy .random import default_rng
44
5+
56class Dataloader :
67 """
78 DataLoader class for handling dataset operations. Supports:
@@ -48,7 +49,9 @@ def __init__(self, features: list[list[float]], labels: list[int]) -> None:
4849 self .y = np .array (labels )
4950 self .class_weights = {0 : 1.0 , 1 : 1.0 } # Example class weights, adjust as needed
5051
51- def get_train_test_data (self ) -> tuple [list [np .ndarray ], list [np .ndarray ], list [np .ndarray ], list [np .ndarray ]]:
52+ def get_train_test_data (
53+ self ,
54+ ) -> tuple [list [np .ndarray ], list [np .ndarray ], list [np .ndarray ], list [np .ndarray ]]:
5255 """
5356 Splits the data into training and testing sets.
5457 Here, we manually split the data.
@@ -60,13 +63,21 @@ def get_train_test_data(self) -> tuple[list[np.ndarray], list[np.ndarray], list[
6063 - Test data
6164 - Test labels
6265 """
63- train_data = np .array ([self .X [0 ], self .X [1 ], self .X [2 ]]) # First 3 samples for training
64- train_labels = [np .array ([self .y [0 ]]), np .array ([self .y [1 ]]), np .array ([self .y [2 ]])] # Labels as np.ndarray
66+ train_data = np .array (
67+ [self .X [0 ], self .X [1 ], self .X [2 ]]
68+ ) # First 3 samples for training
69+ train_labels = [
70+ np .array ([self .y [0 ]]),
71+ np .array ([self .y [1 ]]),
72+ np .array ([self .y [2 ]]),
73+ ] # Labels as np.ndarray
6574 test_data = np .array ([self .X [3 ]]) # Last sample for testing
6675 test_labels = [np .array ([self .y [3 ]])] # Labels as np.ndarray
6776 return train_data , train_labels , test_data , test_labels
6877
69- def shuffle_data (self , paired_data : list [tuple [np .ndarray , int ]]) -> list [tuple [np .ndarray , int ]]:
78+ def shuffle_data (
79+ self , paired_data : list [tuple [np .ndarray , int ]]
80+ ) -> list [tuple [np .ndarray , int ]]:
7081 """
7182 Shuffles the data randomly.
7283
@@ -103,42 +114,45 @@ def one_hot_encode(labels: list[int], num_classes: int) -> np.ndarray:
103114 return one_hot
104115
105116
106- class MLP () :
117+ class MLP :
107118 """
108- A custom MLP class for implementing a simple multi-layer perceptron with
109- forward propagation, backpropagation.
110-
111- Attributes:
112- learning_rate (float): Learning rate for gradient descent.
113- gamma (float): Parameter to control learning rate adjustment.
114- epoch (int): Number of epochs for training.
115- hidden_dim (int): Dimension of the hidden layer.
116- batch_size (int): Number of samples per mini-batch.
117- train_loss (List[float]): List to store training loss for each fold.
118- train_accuracy (List[float]): List to store training accuracy for each fold.
119- test_loss (List[float]): List to store test loss for each fold.
120- test_accuracy (List[float]): List to store test accuracy for each fold.
121- dataloader (Dataloader): DataLoader object for handling training data.
122- inter_variable (dict):
123- Dictionary to store intermediate variables for backpropagation.
124- weights1_list (List[Tuple[np.ndarray, np.ndarray]]):
125- List of weights for each fold.
126-
127- Methods:
128- get_inout_dim:obtain input dimension and output dimension.
129- relu: Apply the ReLU activation function.
130- relu_derivative: Compute the derivative of the ReLU function.
131- forward: Perform a forward pass through the network.
132- back_prop: Perform backpropagation to compute gradients.
133- update_weights: Update the weights using gradients.
134- update_learning_rate: Adjust the learning rate based on test accuracy.
135- accuracy: Compute accuracy of the model.
136- loss: Compute weighted MSE loss.
137- train: Train the MLP over multiple folds with early stopping.
138-
139-
140- """
141- def __init__ (self , dataloader , epoch : int , learning_rate : float , gamma = 1 , hidden_dim = 2 ):
119+ A custom MLP class for implementing a simple multi-layer perceptron with
120+ forward propagation, backpropagation.
121+
122+ Attributes:
123+ learning_rate (float): Learning rate for gradient descent.
124+ gamma (float): Parameter to control learning rate adjustment.
125+ epoch (int): Number of epochs for training.
126+ hidden_dim (int): Dimension of the hidden layer.
127+ batch_size (int): Number of samples per mini-batch.
128+ train_loss (List[float]): List to store training loss for each fold.
129+ train_accuracy (List[float]): List to store training accuracy for each fold.
130+ test_loss (List[float]): List to store test loss for each fold.
131+ test_accuracy (List[float]): List to store test accuracy for each fold.
132+ dataloader (Dataloader): DataLoader object for handling training data.
133+ inter_variable (dict):
134+ Dictionary to store intermediate variables for backpropagation.
135+ weights1_list (List[Tuple[np.ndarray, np.ndarray]]):
136+ List of weights for each fold.
137+
138+ Methods:
139+ get_inout_dim:obtain input dimension and output dimension.
140+ relu: Apply the ReLU activation function.
141+ relu_derivative: Compute the derivative of the ReLU function.
142+ forward: Perform a forward pass through the network.
143+ back_prop: Perform backpropagation to compute gradients.
144+ update_weights: Update the weights using gradients.
145+ update_learning_rate: Adjust the learning rate based on test accuracy.
146+ accuracy: Compute accuracy of the model.
147+ loss: Compute weighted MSE loss.
148+ train: Train the MLP over multiple folds with early stopping.
149+
150+
151+ """
152+
153+ def __init__ (
154+ self , dataloader , epoch : int , learning_rate : float , gamma = 1 , hidden_dim = 2
155+ ):
142156 self .learning_rate = learning_rate
143157 self .gamma = gamma # learning_rate decay hyperparameter gamma
144158 self .epoch = epoch
@@ -221,13 +235,12 @@ def relu_derivative(self, input_array: np.ndarray) -> np.ndarray:
221235 """
222236 return (input_array > 0 ).astype (float )
223237
224-
225238 def forward (
226- self ,
227- input_data : np .ndarray ,
228- W1 : np .ndarray ,
229- W2 : np .ndarray ,
230- no_gradient : bool = False
239+ self ,
240+ input_data : np .ndarray ,
241+ W1 : np .ndarray ,
242+ W2 : np .ndarray ,
243+ no_gradient : bool = False ,
231244 ) -> np .ndarray :
232245 """
233246 Performs a forward pass through the neural network with one hidden layer.
@@ -267,11 +280,11 @@ def forward(
267280 return a2
268281
269282 def back_prop (
270- self ,
271- input_data : np .ndarray ,
272- true_labels : np .ndarray ,
273- W1 : np .ndarray ,
274- W2 : np .ndarray
283+ self ,
284+ input_data : np .ndarray ,
285+ true_labels : np .ndarray ,
286+ W1 : np .ndarray ,
287+ W2 : np .ndarray ,
275288 ) -> tuple [np .ndarray , np .ndarray ]:
276289 """
277290 Performs backpropagation to compute gradients for the weights.
@@ -313,20 +326,22 @@ def back_prop(
313326 grad_w2 = (
314327 np .dot (a1 .T , delta_k ) / batch_size
315328 ) # (hidden, batch).dot(batch, output) = (hidden, output)
316- input_data_flat = input_data .reshape (input_data .shape [0 ], - 1 ) # (batch_size, input_dim)
329+ input_data_flat = input_data .reshape (
330+ input_data .shape [0 ], - 1
331+ ) # (batch_size, input_dim)
317332 grad_w1 = (
318333 np .dot (input_data_flat .T , delta_j ) / batch_size
319334 ) # (input_dim, batch_size).dot(batch, hidden) = (input, hidden)
320335
321336 return grad_w1 , grad_w2
322337
323338 def update_weights (
324- self ,
325- w1 : np .ndarray ,
326- w2 : np .ndarray ,
327- grad_w1 : np .ndarray ,
328- grad_w2 : np .ndarray ,
329- learning_rate : float
339+ self ,
340+ w1 : np .ndarray ,
341+ w2 : np .ndarray ,
342+ grad_w1 : np .ndarray ,
343+ grad_w2 : np .ndarray ,
344+ learning_rate : float ,
330345 ) -> tuple [np .ndarray , np .ndarray ]:
331346 """
332347 Updates the weight matrices using the computed gradients and learning rate.
@@ -361,7 +376,6 @@ def update_weights(
361376 w2 -= learning_rate * grad_w2
362377 return w1 , w2
363378
364-
365379 def update_learning_rate (self , learning_rate : float ) -> float :
366380 """
367381 Updates the learning rate by applying the decay factor gamma.
@@ -457,12 +471,13 @@ def train(self) -> None:
457471 """
458472
459473 learning_rate = self .learning_rate
460- train_data , train_labels , test_data , test_labels = self .dataloader .get_train_test_data ()
474+ train_data , train_labels , test_data , test_labels = (
475+ self .dataloader .get_train_test_data ()
476+ )
461477
462478 train_data = np .c_ [train_data , np .ones (train_data .shape [0 ])]
463479 test_data = np .c_ [test_data , np .ones (test_data .shape [0 ])]
464480
465-
466481 _ , total_label_num = self .dataloader .get_inout_dim ()
467482
468483 train_labels = self .dataloader .one_hot_encode (train_labels , total_label_num )
@@ -477,13 +492,16 @@ def train(self) -> None:
477492
478493 for j in tqdm (range (self .epoch )):
479494 for k in range (0 , train_data .shape [0 ], batch_size ): # retrieve every image
495+ batch_imgs = train_data [k : k + batch_size ]
496+ batch_labels = train_labels [k : k + batch_size ]
480497
481- batch_imgs = train_data [k : k + batch_size ]
482- batch_labels = train_labels [k : k + batch_size ]
483-
484- output = self .forward (input_data = batch_imgs , W1 = W1 , W2 = W2 , no_gradient = False )
498+ output = self .forward (
499+ input_data = batch_imgs , W1 = W1 , W2 = W2 , no_gradient = False
500+ )
485501
486- grad_W1 , grad_W2 = self .back_prop (input_data = batch_imgs , true_labels = batch_labels , W1 = W1 , W2 = W2 )
502+ grad_W1 , grad_W2 = self .back_prop (
503+ input_data = batch_imgs , true_labels = batch_labels , W1 = W1 , W2 = W2
504+ )
487505
488506 W1 , W2 = self .update_weights (W1 , W2 , grad_W1 , grad_W2 , learning_rate )
489507
@@ -498,7 +516,7 @@ def train(self) -> None:
498516
499517 self .test_accuracy = test_accuracy_list
500518 self .test_loss = test_loss_list
501- print (f"Test accuracy:" , sum (test_accuracy_list )/ len (test_accuracy_list ))
519+ print (f"Test accuracy:" , sum (test_accuracy_list ) / len (test_accuracy_list ))
502520
503521
504522if __name__ == "__main__" :
0 commit comments