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..992a473b5384 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SigmoidActivation.java @@ -0,0 +1,59 @@ +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 class 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][x[0].length]; + for(int i = 0; i < x.length; i++) { + for (int j = 0; j < x[0].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][y[0].length]; + for(int i = 0; i < y.length; i++) { + for (int j = 0; j < y[0].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..61a8119188ab --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SigmoidActivationTest.java @@ -0,0 +1,61 @@ +package com.thealgorithms.maths; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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"); + } +}