diff --git a/src/main/java/com/thealgorithms/maths/SigmoidActivation.java b/src/main/java/com/thealgorithms/maths/SigmoidActivation.java new file mode 100644 index 000000000000..aa12886e171b --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SigmoidActivation.java @@ -0,0 +1,74 @@ +package com.thealgorithms.maths; + +/** + * Implementation of the Sigmoid Activation function. + * Sigmoid function is used as an activation function in machine learning and neural networks + * for modeling binary classification problems, smoothing outputs, and introducing non-linearity + * into models. + * + * @author Alikhan Turugeldiyev + */ + +public final class SigmoidActivation { + + private SigmoidActivation() { + } + + /** + * @summary Maps any real-valued number into a value between 0 and 1 + * @param x In machine learning, x could be a weighted sum of inputs in a neural network neuron or a raw score in logistic regression. + * @return The output (range) of the sigmoid function is always strictly between 0 and 1. + */ + public static double activate(double x) { + // If the number x is NaN then, returning NaN to saving from unexpected output. + if (Double.isNaN(x)) { + return Double.NaN; + } + // Saving from unnecessary and heavy calculations. + // lim x->-inf sigmoid(x) will return number very close to 0 + if (x < -745) { + return 0.0; + } + // lim x->inf sigmoid(x) will return number very close to 1 + if (x > 745) { + return 1.0; + } + // sigmoid function's formula + return 1.0 / (1 + Math.exp((-1) * x)); + } + + public static double[][] activate(double[][] x) { + // apply calculation to every value in batch. + double[][] activatedNumbers = new double[x.length][]; + for (int i = 0; i < x.length; i++) { + activatedNumbers[i] = new double[x[i].length]; + for (int j = 0; j < x[i].length; j++) { + activatedNumbers[i][j] = activate(x[i][j]); + } + } + return activatedNumbers; + } + + /** + * @summary Calculates gradients for mapped values. By the chain rule, you can calculate error. + * @param y Activated by sigmoid function value. + * @return The output is a gradient of the activated value. + */ + public static double grad(double y) { + // sigmoid function derivative is reducing to this value. + // sigmoid'(x) = sigmoid(x) * (1-sigmoid(x)) + return y * (1 - y); + } + + public static double[][] grad(double[][] y) { + // apply calculation to every value in batch. + double[][] grads = new double[y.length][]; + for (int i = 0; i < y.length; i++) { + grads[i] = new double[y[i].length]; + for (int j = 0; j < y[i].length; j++) { + grads[i][j] = grad(y[i][j]); + } + } + return grads; + } +} diff --git a/src/test/java/com/thealgorithms/maths/SigmoidActivationTest.java b/src/test/java/com/thealgorithms/maths/SigmoidActivationTest.java new file mode 100644 index 000000000000..aadf2c7ddc12 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SigmoidActivationTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class SigmoidActivationTest { + + @Test + public void calculationTest() { + assertEquals(0.5, SigmoidActivation.activate(0), 0.01, "1 case correct"); + assertEquals(0.73, SigmoidActivation.activate(1), 0.01, "2 case correct"); + assertEquals(0.26, SigmoidActivation.activate(-1), 0.01, "3 case correct"); + assertEquals(0.88, SigmoidActivation.activate(2), 0.01, "4 case correct"); + assertEquals(0.11, SigmoidActivation.activate(-2), 0.01, "5 case correct"); + + double[][] xBatch = new double[4][3]; + double[][] expectedX = new double[4][3]; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 3; j++) { + xBatch[i][j] = 0; + } + } + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 3; j++) { + expectedX[i][j] = 0.5; + } + } + + assertTrue(Arrays.deepEquals(expectedX, SigmoidActivation.activate(xBatch)), "batch case correct"); + + assertEquals(0.25, SigmoidActivation.grad(0.5), 0.01, "grad calculation correct"); + + double[][] yBatch = new double[4][3]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 3; j++) { + yBatch[i][j] = 0.5; + } + } + + double[][] expectedY = new double[4][3]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 3; j++) { + expectedY[i][j] = 0.25; + } + } + assertTrue(Arrays.deepEquals(expectedY, SigmoidActivation.grad(yBatch)), "grad batch case correct"); + } + + @Test + public void willReturnNaN() { + double x = Double.NaN; + + assertTrue(Double.isNaN(SigmoidActivation.activate(x)), "returned NaN"); + } + + @Test + public void extremumNumbersOnActivate() { + double x = 777; + + assertEquals(1.0, SigmoidActivation.activate(x), 0.01, "big number case correct"); + assertEquals(0.0, SigmoidActivation.activate((-1) * x), 0.01, "small number case correct"); + } +}