Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@

## Machine Learning
* [Gradient Boosting](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/gradient_boosting.r)
* [K-NN](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/k-NN.r)
* [K-Medoids](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/k_medoids.r)
* [LSTM Time Series](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/lstm_time_series.r)
* [Naive Bayes](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/naive_bayes.r)

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says DIRECTORY.md is updated to add the Perceptron entry, but this diff also adds entries for several other existing ML algorithms (K-NN, K-Medoids, LSTM Time Series, Naive Bayes). If this is intentional, please mention it in the PR description; otherwise, consider limiting the DIRECTORY.md change to the Perceptron entry.

Copilot uses AI. Check for mistakes.
* [Perceptron](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/perceptron.r)


## Mathematics
Expand Down
25 changes: 25 additions & 0 deletions documentation/perceptron.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Perceptron

A simple linear classifier using the perceptron learning rule. This implementation supports binary and multiclass classification using one-vs-rest updates.

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc claims multiclass support uses one-vs-rest updates, but the implementation in machine_learning/perceptron.r performs a multiclass perceptron update (adjusting the true class and the argmax predicted class). Please correct the documentation to match the implementation (or update the code to true one-vs-rest).

Suggested change
A simple linear classifier using the perceptron learning rule. This implementation supports binary and multiclass classification using one-vs-rest updates.
A simple linear classifier using the perceptron learning rule. This implementation supports binary classification and multiclass classification with direct multiclass perceptron updates.

Copilot uses AI. Check for mistakes.

``` r
library(R6)
source("../machine_learning/perceptron.r")

# example data for binary classification
X <- matrix(c(
0.1, 0.3,
0.2, 0.1,
0.9, 0.8,
0.7, 0.9
), ncol = 2, byrow = TRUE)

y <- factor(c("class1", "class1", "class2", "class2"))

model <- Perceptron$new(learning_rate = 0.1, n_epochs = 20, shuffle = FALSE, random_state = 42)
model$fit(X, y)

predictions <- model$predict(X)
print(predictions)
print(model$score(X, y))
```
167 changes: 167 additions & 0 deletions machine_learning/perceptron.r
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# perceptron.r
# Perceptron classifier implementation in R
# A simple linear classifier using the perceptron learning rule.
# Supports binary and multiclass classification via one-vs-rest updates.

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment says multiclass is implemented via one-vs-rest updates, but the training loop updates only the true class and the single argmax predicted class (multiclass perceptron), not separate one-vs-rest classifiers. Please align the documentation/PR description with the actual algorithm, or adjust the implementation to true one-vs-rest if that’s the intent.

Suggested change
# Supports binary and multiclass classification via one-vs-rest updates.
# Supports binary classification and multiclass classification
# using a standard multiclass perceptron with class score comparisons.

Copilot uses AI. Check for mistakes.
# Time Complexity: O(n_epochs * n_samples * n_features)
# Space Complexity: O(n_classes * n_features)

library(R6)

Perceptron <- R6Class("Perceptron",
public = list(
learning_rate = NULL,
n_epochs = NULL,
shuffle = NULL,
fit_intercept = NULL,
random_state = NULL,
classes = NULL,
weights = NULL,
bias = NULL,
is_multiclass = NULL,

initialize = function(learning_rate = 0.1,
n_epochs = 100,
shuffle = TRUE,
fit_intercept = TRUE,
random_state = NULL) {
self$learning_rate <- learning_rate
self$n_epochs <- n_epochs
self$shuffle <- shuffle
self$fit_intercept <- fit_intercept
self$random_state <- random_state
},

fit = function(X, y) {
if (is.data.frame(X)) X <- as.matrix(X)
if (!is.matrix(X)) stop("X must be a numeric matrix or data.frame.")
if (!is.numeric(X)) stop("X must contain numeric features.")
if (any(is.na(X))) stop("X must not contain missing values.")

if (is.character(y)) y <- factor(y)
if (is.factor(y)) {
self$classes <- levels(y)
} else {
self$classes <- sort(unique(y))
}

if (length(y) != nrow(X)) stop("Length of y must match rows of X.")
if (length(self$classes) < 2) stop("Perceptron requires at least two classes.")

X <- as.matrix(X)
n_samples <- nrow(X)
n_features <- ncol(X)
if (self$fit_intercept) {
X <- cbind(1, X)
n_features <- n_features + 1
}
Comment on lines +53 to +56

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In binary mode, the model both adds an intercept column to X (when fit_intercept=TRUE) and also keeps a separate bias term that is added/updated during training/prediction. This effectively introduces two intercepts and will shift the decision boundary unexpectedly. Consider using either (a) an intercept weight via the added column with no separate bias, or (b) a separate bias with no added intercept column, and keep the approach consistent across fit/predict.

Copilot uses AI. Check for mistakes.

if (length(self$classes) == 2) {
self$is_multiclass <- FALSE
self$weights <- rep(0, n_features)
self$bias <- 0
} else {
self$is_multiclass <- TRUE
self$weights <- matrix(0, nrow = length(self$classes), ncol = n_features)
self$bias <- rep(0, length(self$classes))

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For multiclass training/prediction, self$bias is initialized but never used in score computation or updates (scores are computed only from weights %% x_i / X_new %% t(weights)). Either incorporate bias into the multiclass score/update, or remove the unused bias field to avoid confusion.

Suggested change
self$bias <- rep(0, length(self$classes))
self$bias <- NULL

Copilot uses AI. Check for mistakes.
}

if (!is.null(self$random_state)) {
set.seed(self$random_state)
}

y_encoded <- self$encode_labels(y)

for (epoch in seq_len(self$n_epochs)) {
indices <- seq_len(n_samples)
if (self$shuffle) {
indices <- sample(indices)
}

for (i in indices) {
x_i <- X[i, ]
y_i <- y_encoded[i]

if (self$is_multiclass) {
scores <- self$weights %*% x_i
predicted <- which.max(scores)
if (predicted != y_i) {
self$weights[y_i, ] <- self$weights[y_i, ] + self$learning_rate * x_i
self$weights[predicted, ] <- self$weights[predicted, ] - self$learning_rate * x_i
}
} else {
score <- sum(self$weights * x_i) + self$bias
if (y_i * score <= 0) {
self$weights <- self$weights + self$learning_rate * y_i * x_i
self$bias <- self$bias + self$learning_rate * y_i
}
}
}
}

invisible(self)
},

predict = function(X_new) {
if (is.data.frame(X_new)) X_new <- as.matrix(X_new)
if (is.vector(X_new)) X_new <- matrix(X_new, nrow = 1)
if (!is.matrix(X_new)) stop("X_new must be a numeric matrix, data.frame, or vector.")
if (!is.numeric(X_new)) stop("X_new must contain numeric features.")
if (any(is.na(X_new))) stop("X_new must not contain missing values.")

if (self$fit_intercept) {
X_new <- cbind(1, X_new)
}

if (self$is_multiclass) {
scores <- X_new %*% t(self$weights)
predicted_idx <- apply(scores, 1, which.max)
return(self$classes[predicted_idx])
}

raw_scores <- as.numeric(X_new %*% self$weights + self$bias)
if (is.factor(self$classes)) {
labels <- c(self$classes[1], self$classes[2])
} else {
labels <- self$classes
}

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self$classes is always stored as a vector (levels(y) returns a character vector, and encode_labels() overwrites it with character/numeric), so checking is.factor(self$classes) will never be TRUE. This branch is effectively dead code; consider removing it and simplifying label handling.

Suggested change
if (is.factor(self$classes)) {
labels <- c(self$classes[1], self$classes[2])
} else {
labels <- self$classes
}
labels <- self$classes

Copilot uses AI. Check for mistakes.
predictions <- ifelse(raw_scores >= 0, labels[2], labels[1])
return(predictions)
},

score = function(X, y) {
predictions <- self$predict(X)
if (is.factor(y) || is.character(y)) {
y <- as.character(y)
predictions <- as.character(predictions)
}
mean(predictions == y)
},

encode_labels = function(y) {
if (self$is_multiclass) {
if (is.factor(y)) {
return(as.integer(y))
}
return(match(y, self$classes))
}

if (is.factor(y)) {
y <- as.character(y)
}
labels <- sort(unique(y))
if (length(labels) != 2) stop("Binary perceptron requires exactly two classes.")
self$classes <- labels

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In binary mode, encode_labels() recomputes and sorts unique(y) and overwrites self$classes, even though fit() already set self$classes (e.g., from factor levels). This can change the label ordering and therefore flip which class is treated as +1 vs -1. Consider using the existing self$classes (preserving factor level order) instead of re-sorting unique labels here.

Suggested change
labels <- sort(unique(y))
if (length(labels) != 2) stop("Binary perceptron requires exactly two classes.")
self$classes <- labels
labels <- self$classes
if (is.null(labels) || length(labels) == 0) {
labels <- unique(y)
if (length(labels) != 2) stop("Binary perceptron requires exactly two classes.")
self$classes <- labels
} else {
if (is.factor(labels)) {
labels <- as.character(labels)
}
if (length(labels) != 2) stop("Binary perceptron requires exactly two classes.")
}
if (any(!y %in% labels)) {
stop("Binary perceptron received labels not present in self$classes.")
}

Copilot uses AI. Check for mistakes.
y_bin <- ifelse(y == labels[2], 1, -1)
return(y_bin)
}
)
)

# Example usage:
# data(iris)
# X <- as.matrix(iris[, 1:4])
# y <- iris$Species
# model <- Perceptron$new(learning_rate = 0.1, n_epochs = 50, shuffle = TRUE)
# model$fit(X, y)
# preds <- model$predict(X)
# cat('Training accuracy:', model$score(X, y), '\n')
Loading