Repository: google/jax
Pull request #20241 (2024-03-13 22:24:37)

Add sparseplus activation to jax.nn.
Add sparseplus activation to jax.nn.



Patch:
diff --git a/docs/jax.nn.rst b/docs/jax.nn.rst
index ae2c6f24a..4cb669540 100644
--- a/docs/jax.nn.rst
+++ b/docs/jax.nn.rst
@@ -22,6 +22,7 @@ Activation functions
     relu6
     sigmoid
     softplus
+    sparse_plus
     soft_sign
     silu
     swish
diff --git a/jax/_src/nn/functions.py b/jax/_src/nn/functions.py
index 239d1b8d2..63b0ccac9 100644
--- a/jax/_src/nn/functions.py
+++ b/jax/_src/nn/functions.py
@@ -110,6 +110,31 @@ def softplus(x: ArrayLike) -> Array:
   """
   return jnp.logaddexp(x, 0)
 
+@jax.jit
+def sparse_plus(x: ArrayLike) -> Array:
+  r"""Sparse plus function.
+
+  Computes the function:
+
+  .. math::
+
+    \mathrm{sparse\_plus}(x) = \begin{cases}
+      0, & x \leq -1\\
+      \frac{1}{4}(x+1)^2, & -1 < x < 1 \\
+      x, & 1 \leq x
+    \end{cases}
+
+  This is the twin function of the softplus activation ensuring a zero output
+  for inputs less than -1 and a linear output for inputs greater than 1,
+  while remaining smooth, convex, monotonic by an adequate definition between
+  -1 and 1.
+
+  Args:
+    x: input (float)
+  """
+  x = jnp.asarray(x)
+  return jnp.where(x <= -1.0, 0.0, jnp.where(x >= 1.0, x, (x + 1.0)**2/4))
+
 @jax.jit
 def soft_sign(x: ArrayLike) -> Array:
   r"""Soft-sign activation function.
diff --git a/jax/nn/__init__.py b/jax/nn/__init__.py
index 0f008b30e..0f7e42560 100644
--- a/jax/nn/__init__.py
+++ b/jax/nn/__init__.py
@@ -41,6 +41,7 @@ from jax._src.nn.functions import (
   soft_sign as soft_sign,
   softmax as softmax,
   softplus as softplus,
+  sparse_plus as sparse_plus,
   silu as silu,
   swish as swish,
   squareplus as squareplus,


Test patch:
diff --git a/tests/nn_test.py b/tests/nn_test.py
index 1a89389a9..67890db9e 100644
--- a/tests/nn_test.py
+++ b/tests/nn_test.py
@@ -63,6 +63,14 @@ class NNFunctionsTest(jtu.JaxTestCase):
   def testSoftplusZero(self, dtype):
     self.assertEqual(jnp.log(dtype(2)), nn.softplus(dtype(0)))
 
+  def testSparseplusGradZero(self):
+    check_grads(nn.sparse_plus, (-2.,), order=1,
+                rtol=1e-2 if jtu.test_device_matches(["tpu"]) else None)
+
+  def testSparseplusGrad(self):
+    check_grads(nn.sparse_plus, (0.,), order=1,
+                rtol=1e-2 if jtu.test_device_matches(["tpu"]) else None)
+
   def testSquareplusGrad(self):
     check_grads(nn.squareplus, (1e-8,), order=4,
                 rtol=1e-2 if jtu.test_device_matches(["tpu"]) else None)
@@ -101,6 +109,10 @@ class NNFunctionsTest(jtu.JaxTestCase):
     val = nn.softplus(89.)
     self.assertAllClose(val, 89., check_dtypes=False)
 
+  def testSparseplusValue(self):
+    val = nn.sparse_plus(89.)
+    self.assertAllClose(val, 89., check_dtypes=False)
+
   def testSquareplusValue(self):
     val = nn.squareplus(1e3)
     self.assertAllClose(val, 1e3, check_dtypes=False, atol=1e-3)
@@ -137,7 +149,7 @@ class NNFunctionsTest(jtu.JaxTestCase):
       (jnp.float32, jnp.bfloat16, jnp.float16),
       (partial(nn.gelu, approximate=False),
        partial(nn.gelu, approximate=True),
-       nn.relu, nn.softplus, nn.sigmoid, nn.squareplus)))
+       nn.relu, nn.softplus, nn.sparse_plus, nn.sigmoid, nn.squareplus)))
   def testDtypeMatchesInput(self, dtype, fn):
     x = jnp.zeros((), dtype=dtype)
     out = fn(x)