Card 1 / 5
The logistic sigmoid squashes any real number into , applied elementwise over an array:
Computed literally, overflows for large negative (try ). For use the algebraically equal form , so the exponent is never positive.
z |
result |
|---|---|
[0] |
[0.5] |
[-2, 2] |
[0.1192, 0.8808] |
[[-1000, 1000]] |
[[0, 1]] (no overflow) |
The two forms are the same number: multiply numerator and denominator of the first by . One branch for , one for , and the output keeps the shape of . Two identities the tests check: and .
Both branches come from one safe quantity : for and for . In NumPy, np.exp(-np.abs(z)) then np.where(z >= 0, a, b) picks a or b per element; it evaluates both, which is fine here because neither overflows. In Eigen, Z.unaryExpr([](double z) { ... }) applies a lambda to every coefficient and yields a matrix of the same shape.
def sigmoid(z: np.ndarray) -> np.ndarray:
"""Logistic sigmoid, elementwise and numerically stable.
z: any shape, e.g. [B, H]
Returns an array of the same shape with sigma(z) = 1 / (1 + e^{-z}) in (0, 1).
For z < 0 use the equal form e^{z} / (1 + e^{z}) so nothing overflows,
even at z = -1000.
"""
e = np.exp(-np.abs(z)) # in (0, 1], never overflows
return np.where(z >= 0, 1.0 / (1.0 + e), e / (1.0 + e))Card 2 / 5
The rectified linear unit keeps positive inputs and zeroes the rest, elementwise; its derivative is a 0/1 mask. Implement both.