8000 Add implementation of the multi-layer perceptron classifier from scratch by duuan · Pull Request #12756 · TheAlgorithms/Python · GitHub
[go: up one dir, main page]

Skip to content

Add implementation of the multi-layer perceptron classifier from scratch #12756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
  • Loading branch information
pre-commit-ci[bot] committed May 14, 2025
commit d02e27b5128c21a94ae70782c01d698dbdee5e4c
136 changes: 68 additions & 68 deletions machine_learning/multilayer_perceptron_classifier_from_scratch.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ def __init__(self, features: list[list[float]], labels: list[int]) -> None:
self.y = np.array(labels)
self.class_weights = {0: 1.0, 1: 1.0} # Example class weights, adjust as needed

def get_train_test_data(self) -> tuple[list[np.ndarray],
list[np.ndarray], list[np.ndarray], list[np.ndarray]]:
def get_train_test_data(
self,
) -> tuple[list[np.ndarray], list[np.ndarray], list[np.ndarray], list[np.ndarray]]:
"""
Splits the data into training and testing sets.
Here, we manually split the data.
Expand All @@ -52,16 +53,18 @@ def get_train_test_data(self) -> tuple[list[np.ndarray],
- Test labels
"""
train_data = np.array([self.X[0], self.X[1], self.X[2]])
train_labels = [np.array([self.y[0]]), np.array([self.y[1]]),
np.array([self.y[2]])]
train_labels = [
np.array([self.y[0]]),
np.array([self.y[1]]),
np.array([self.y[2]]),
]
test_data = np.array([self.X[3]])
test_labels = [np.array([self.y[3]])]
return train_data, train_labels, test_data, test_labels

def shuffle_data(
self,
paired_data: list[tuple[np.ndarray, int]]
) -> list[tuple[np.ndarray, int]]:
self, paired_data: list[tuple[np.ndarray, int]]
) -> list[tuple[np.ndarray, int]]:
"""
Shuffles the data randomly.

Expand Down Expand Up @@ -99,41 +102,43 @@ def one_hot_encode(labels: list[int], num_classes: int) -> np.ndarray:

class MLP:
"""
A custom MLP class for implementing a simple multi-layer perceptron with
forward propagation, backpropagation.

Attributes:
learning_rate (float): Learning rate for gradient descent.
gamma (float): Parameter to control learning rate adjustment.
epoch (int): Number of epochs for training.
hidden_dim (int): Dimension of the hidden layer.
batch_size (int): Number of samples per mini-batch.
train_loss (List[float]): List to store training loss for each fold.
train_accuracy (List[float]): List to store training accuracy for each fold.
test_loss (List[float]): List to store test loss for each fold.
test_accuracy (List[float]): List to store test accuracy for each fold.
dataloader (Dataloader): DataLoader object for handling training data.
inter_variable (dict): Dictionary to store intermediate variables
for backpropagation.
weights1_list (List[Tuple[np.ndarray, np.ndarray]]):
List of weights for each fold.

Methods:
get_inout_dim:obtain input dimension and output dimension.
relu: Apply the ReLU activation function.
relu_derivative: Compute the derivative of the ReLU function.
forward: Perform a forward pass through the network.
back_prop: Perform backpropagation to compute gradients.
update_weights: Update the weights using gradients.
update_learning_rate: Adjust the learning rate based on test accuracy.
accuracy: Compute accuracy of the model.
loss: Compute weighted MSE loss.
train: Train the MLP over multiple folds with early stopping.


"""
def __init__(self, dataloader, epoch: int, learning_rate: float,
gamma=1, hidden_dim=2):
A custom MLP class for implementing a simple multi-layer perceptron with
forward propagation, backpropagation.

Attributes:
learning_rate (float): Learning rate for gradient descent.
gamma (float): Parameter to control learning rate adjustment.
epoch (int): Number of epochs for training.
hidden_dim (int): Dimension of the hidden layer.
batch_size (int): Number of samples per mini-batch.
train_loss (List[float]): List to store training loss for each fold.
train_accuracy (List[float]): List to store training accuracy for each fold.
test_loss (List[float]): List to store test loss for each fold.
test_accuracy (List[float]): List to store test accuracy for each fold.
dataloader (Dataloader): DataLoader object for handling training data.
inter_variable (dict): Dictionary to store intermediate variables
for backpropagation.
weights1_list (List[Tuple[np.ndarray, np.ndarray]]):
List of weights for each fold.

Methods:
get_inout_dim:obtain input dimension and output dimension.
relu: Apply the ReLU activation function.
relu_derivative: Compute the derivative of the ReLU function.
forward: Perform a forward pass through the network.
back_prop: Perform backpropagation to compute gradients.
update_weights: Update the weights using gradients.
update_learning_rate: Adjust the learning rate based on test accuracy.
accuracy: Compute accuracy of the model.
loss: Compute weighted MSE loss.
train: Train the MLP over multiple folds with early stopping.


"""

def __init__(
self, dataloader, epoch: int, learning_rate: float, gamma=1, hidden_dim=2
):
self.learning_rate = learning_rate
self.gamma = gamma # learning_rate decay hyperparameter gamma
self.epoch = epoch
Expand Down Expand Up @@ -216,13 +221,12 @@ def relu_derivative(self, input_array: np.ndarray) -> np.ndarray:
"""
return (input_array > 0).astype(float)


def forward(
self,
input_data: np.ndarray,
w1: np.ndarray,
w2: np.ndarray,
no_gradient: bool = False
self,
input_data: np.ndarray,
w1: np.ndarray,
w2: np.ndarray,
no_gradient: bool = False,
) -> np.ndarray:
"""
Performs a forward pass through the neural network with one hidden layer.
Expand Down Expand Up @@ -264,10 +268,7 @@ def forward(
return a2

def back_prop(
self,
input_data: np.ndarray,
true_labels: np.ndarray,
w2: np.ndarray
self, input_data: np.ndarray, true_labels: np.ndarray, w2: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
"""
Performs backpropagation to compute gradients for the weights.
Expand Down Expand Up @@ -315,12 +316,12 @@ def back_prop(
return grad_w1, grad_w2

def update_weights(
self,
w1: np.ndarray,
w2: np.ndarray,
grad_w1: np.ndarray,
grad_w2: np.ndarray,
learning_rate: float
self,
w1: np.ndarray,
w2: np.ndarray,
grad_w1: np.ndarray,
grad_w2: np.ndarray,
learning_rate: float,
) -> tuple[np.ndarray, np.ndarray]:
"""
Updates the weight matrices using the computed gradients and learning rate.
Expand Down Expand Up @@ -359,7 +360,6 @@ def update_weights(
w2 -= learning_rate * grad_w2
return w1, w2


def update_learning_rate(self, learning_rate: float) -> float:
"""
Updates the learning rate by applying the decay factor gamma.
Expand Down Expand Up @@ -456,13 +456,13 @@ def train(self) -> None:
"""

learning_rate = self.learning_rate
train_data, train_labels, test_data, test_labels = \
self.dataloader.get_train_test_data()
train_data, train_labels, test_data, test_labels = (
self.dataloader.get_train_test_data()
)

train_data = np.c_[train_data, np.ones(train_data.shape[0])]
test_data = np.c_[test_data, np.ones(test_data.shape[0])]


_, total_label_num = self.dataloader.get_inout_dim()

train_labels = self.dataloader.one_hot_encode(train_labels, total_label_num)
Expand All @@ -476,14 +476,14 @@ def train(self) -> None:

for _j in tqdm(range(self.epoch)):
for k in range(0, train_data.shape[0], batch_size): # retrieve every image

batch_imgs = train_data[k: k + batch_size]
batch_labels = train_labels[k: k + batch_size]
batch_imgs = train_data[k : k + batch_size]
batch_labels = train_labels[k : k + batch_size]

self.forward(input_data=batch_imgs, w1=w1, w2=w2, no_gradient=False)

grad_w1, grad_w2 = self.back_prop(input_data=batch_imgs, \
true_labels=batch_labels, w2=w2)
grad_w1, grad_w2 = self.back_prop(
input_data=batch_imgs, true_labels=batch_labels, w2=w2
)

w1, w2 = self.update_weights(w1, w2, grad_w1, grad_w2, learning_rate)

Expand All @@ -498,7 +498,7 @@ def train(self) -> None:

self.test_accuracy = test_accuracy_list
self.test_loss = test_loss_list
print("Test accuracy:", sum(test_accuracy_list)/len(test_accuracy_list))
print("Test accuracy:", sum(test_accuracy_list) / len(test_accuracy_list))


if __name__ == "__main__":
Expand Down
Loading
0