22from tqdm import tqdm
33from numpy .random import default_rng
44from numpy .random import seed
5+
56seed (42 )
7+
8+
69class Dataloader :
710 """
811 DataLoader class for handling dataset, including data shuffling, one-hot encoding, and train-test splitting.
@@ -45,7 +48,9 @@ def __init__(self, features: list[list[float]], labels: list[int]) -> None:
4548 self .y = np .array (labels )
4649 self .class_weights = {0 : 1.0 , 1 : 1.0 } # Example class weights, adjust as needed
4750
48- def get_Train_test_data (self ) -> tuple [list [np .ndarray ], list [np .ndarray ], list [np .ndarray ], list [np .ndarray ]]:
51+ def get_Train_test_data (
52+ self ,
53+ ) -> tuple [list [np .ndarray ], list [np .ndarray ], list [np .ndarray ], list [np .ndarray ]]:
4954 """
5055 Splits the data into training and testing sets. Here, we manually split the data.
5156
@@ -56,13 +61,21 @@ def get_Train_test_data(self) -> tuple[list[np.ndarray], list[np.ndarray], list[
5661 - Test data
5762 - Test labels
5863 """
59- train_data = np .array ([self .X [0 ], self .X [1 ], self .X [2 ]]) # First 3 samples for training
60- train_labels = [np .array ([self .y [0 ]]), np .array ([self .y [1 ]]), np .array ([self .y [2 ]])] # Labels as np.ndarray
64+ train_data = np .array (
65+ [self .X [0 ], self .X [1 ], self .X [2 ]]
66+ ) # First 3 samples for training
67+ train_labels = [
68+ np .array ([self .y [0 ]]),
69+ np .array ([self .y [1 ]]),
70+ np .array ([self .y [2 ]]),
71+ ] # Labels as np.ndarray
6172 test_data = np .array ([self .X [3 ]]) # Last sample for testing
6273 test_labels = [np .array ([self .y [3 ]])] # Labels as np.ndarray
6374 return train_data , train_labels , test_data , test_labels
6475
65- def shuffle_data (self , paired_data : list [tuple [np .ndarray , int ]]) -> list [tuple [np .ndarray , int ]]:
76+ def shuffle_data (
77+ self , paired_data : list [tuple [np .ndarray , int ]]
78+ ) -> list [tuple [np .ndarray , int ]]:
6679 """
6780 Shuffles the data randomly.
6881
@@ -99,40 +112,43 @@ def one_hot_encode(labels: list[int], num_classes: int) -> np.ndarray:
99112 return one_hot
100113
101114
102- class MLP ():
115+ class MLP :
116+ """
117+ A custom MLP class for implementing a simple multi-layer perceptron with
118+ forward propagation, backpropagation.
119+
120+ Attributes:
121+ learning_rate (float): Learning rate for gradient descent.
122+ gamma (float): Parameter to control learning rate adjustment.
123+ epoch (int): Number of epochs for training.
124+ hidden_dim (int): Dimension of the hidden layer.
125+ batch_size (int): Number of samples per mini-batch.
126+ train_loss (List[float]): List to store training loss for each fold.
127+ train_accuracy (List[float]): List to store training accuracy for each fold.
128+ test_loss (List[float]): List to store test loss for each fold.
129+ test_accuracy (List[float]): List to store test accuracy for each fold.
130+ dataloader (Dataloader): DataLoader object for handling training data.
131+ inter_variable (dict): Dictionary to store intermediate variables for backpropagation.
132+ weights1_list (List[Tuple[np.ndarray, np.ndarray]]): List of weights for each fold.
133+
134+ Methods:
135+ get_inout_dim:obtain input dimension and output dimension.
136+ relu: Apply the ReLU activation function.
137+ relu_derivative: Compute the derivative of the ReLU function.
138+ forward: Perform a forward pass through the network.
139+ back_prop: Perform backpropagation to compute gradients.
140+ update_weights: Update the weights using gradients.
141+ update_learning_rate: Adjust the learning rate based on test accuracy.
142+ accuracy: Compute accuracy of the model.
143+ loss: Compute weighted MSE loss.
144+ train: Train the MLP over multiple folds with early stopping.
145+
146+
103147 """
104- A custom MLP class for implementing a simple multi-layer perceptron with
105- forward propagation, backpropagation.
106-
107- Attributes:
108- learning_rate (float): Learning rate for gradient descent.
109- gamma (float): Parameter to control learning rate adjustment.
110- epoch (int): Number of epochs for training.
111- hidden_dim (int): Dimension of the hidden layer.
112- batch_size (int): Number of samples per mini-batch.
113- train_loss (List[float]): List to store training loss for each fold.
114- train_accuracy (List[float]): List to store training accuracy for each fold.
115- test_loss (List[float]): List to store test loss for each fold.
116- test_accuracy (List[float]): List to store test accuracy for each fold.
117- dataloader (Dataloader): DataLoader object for handling training data.
118- inter_variable (dict): Dictionary to store intermediate variables for backpropagation.
119- weights1_list (List[Tuple[np.ndarray, np.ndarray]]): List of weights for each fold.
120-
121- Methods:
122- get_inout_dim:obtain input dimension and output dimension.
123- relu: Apply the ReLU activation function.
124- relu_derivative: Compute the derivative of the ReLU function.
125- forward: Perform a forward pass through the network.
126- back_prop: Perform backpropagation to compute gradients.
127- update_weights: Update the weights using gradients.
128- update_learning_rate: Adjust the learning rate based on test accuracy.
129- accuracy: Compute accuracy of the model.
130- loss: Compute weighted MSE loss.
131- train: Train the MLP over multiple folds with early stopping.
132-
133-
134- """
135- def __init__ (self , dataloader , epoch : int , learning_rate : float , gamma = 1 , hidden_dim = 2 ):
148+
149+ def __init__ (
150+ self , dataloader , epoch : int , learning_rate : float , gamma = 1 , hidden_dim = 2
151+ ):
136152 self .learning_rate = learning_rate #
137153 self .gamma = gamma # learning_rate decay hyperparameter gamma
138154 self .epoch = epoch
@@ -215,13 +231,12 @@ def relu_derivative(self, input_array: np.ndarray) -> np.ndarray:
215231 """
216232 return (input_array > 0 ).astype (float )
217233
218-
219234 def forward (
220- self ,
221- input_data : np .ndarray ,
222- W1 : np .ndarray ,
223- W2 : np .ndarray ,
224- no_gradient : bool = False
235+ self ,
236+ input_data : np .ndarray ,
237+ W1 : np .ndarray ,
238+ W2 : np .ndarray ,
239+ no_gradient : bool = False ,
225240 ) -> np .ndarray :
226241 """
227242 Performs a forward pass through the neural network with one hidden layer.
@@ -261,11 +276,11 @@ def forward(
261276 return a2
262277
263278 def back_prop (
264- self ,
265- input_data : np .ndarray ,
266- true_labels : np .ndarray ,
267- W1 : np .ndarray ,
268- W2 : np .ndarray
279+ self ,
280+ input_data : np .ndarray ,
281+ true_labels : np .ndarray ,
282+ W1 : np .ndarray ,
283+ W2 : np .ndarray ,
269284 ) -> tuple [np .ndarray , np .ndarray ]:
270285 """
271286 Performs backpropagation to compute gradients for the weights.
@@ -307,20 +322,22 @@ def back_prop(
307322 grad_w2 = (
308323 np .dot (a1 .T , delta_k ) / batch_size
309324 ) # (hidden, batch).dot(batch, output) = (hidden, output)
310- input_data_flat = input_data .reshape (input_data .shape [0 ], - 1 ) # (batch_size, input_dim)
325+ input_data_flat = input_data .reshape (
326+ input_data .shape [0 ], - 1
327+ ) # (batch_size, input_dim)
311328 grad_w1 = (
312329 np .dot (input_data_flat .T , delta_j ) / batch_size
313330 ) # (input_dim, batch_size).dot(batch, hidden) = (input, hidden)
314331
315332 return grad_w1 , grad_w2
316333
317334 def update_weights (
318- self ,
319- w1 : np .ndarray ,
320- w2 : np .ndarray ,
321- grad_w1 : np .ndarray ,
322- grad_w2 : np .ndarray ,
323- learning_rate : float
335+ self ,
336+ w1 : np .ndarray ,
337+ w2 : np .ndarray ,
338+ grad_w1 : np .ndarray ,
339+ grad_w2 : np .ndarray ,
340+ learning_rate : float ,
324341 ) -> tuple [np .ndarray , np .ndarray ]:
325342 """
326343 Updates the weight matrices using the computed gradients and learning rate.
@@ -355,7 +372,6 @@ def update_weights(
355372 w2 -= learning_rate * grad_w2
356373 return w1 , w2
357374
358-
359375 def update_learning_rate (self , learning_rate : float ) -> float :
360376 """
361377 Updates the learning rate by applying the decay factor gamma.
@@ -451,12 +467,13 @@ def train(self) -> None:
451467 """
452468
453469 learning_rate = self .learning_rate
454- train_data , train_labels , test_data , test_labels = self .dataloader .get_Train_test_data ()
470+ train_data , train_labels , test_data , test_labels = (
471+ self .dataloader .get_Train_test_data ()
472+ )
455473
456474 train_data = np .c_ [train_data , np .ones (train_data .shape [0 ])]
457475 test_data = np .c_ [test_data , np .ones (test_data .shape [0 ])]
458476
459-
460477 _ , total_label_num = self .dataloader .get_inout_dim ()
461478
462479 train_labels = self .dataloader .one_hot_encode (train_labels , total_label_num )
@@ -471,13 +488,16 @@ def train(self) -> None:
471488
472489 for j in tqdm (range (self .epoch )):
473490 for k in range (0 , train_data .shape [0 ], batch_size ): # retrieve every image
491+ batch_imgs = train_data [k : k + batch_size ]
492+ batch_labels = train_labels [k : k + batch_size ]
474493
475- batch_imgs = train_data [k : k + batch_size ]
476- batch_labels = train_labels [k : k + batch_size ]
477-
478- output = self .forward (input_data = batch_imgs , W1 = W1 , W2 = W2 , no_gradient = False )
494+ output = self .forward (
495+ input_data = batch_imgs , W1 = W1 , W2 = W2 , no_gradient = False
496+ )
479497
480- grad_W1 , grad_W2 = self .back_prop (input_data = batch_imgs , true_labels = batch_labels , W1 = W1 , W2 = W2 )
498+ grad_W1 , grad_W2 = self .back_prop (
499+ input_data = batch_imgs , true_labels = batch_labels , W1 = W1 , W2 = W2
500+ )
481501
482502 W1 , W2 = self .update_weights (W1 , W2 , grad_W1 , grad_W2 , learning_rate )
483503
@@ -492,10 +512,10 @@ def train(self) -> None:
492512
493513 self .test_accuracy = test_accuracy_list
494514 self .test_loss = test_loss_list
495- print (f"Test accuracy:" , sum (test_accuracy_list )/ len (test_accuracy_list ))
515+ print (f"Test accuracy:" , sum (test_accuracy_list ) / len (test_accuracy_list ))
496516
497517
498518if __name__ == "__main__" :
499519 import doctest
500520
501- doctest .testmod ()
521+ doctest .testmod ()
0 commit comments