1414from numpy .exceptions import AxisError
1515
1616
17- def softmax (vector : np .ndarray , axis : int = - 1 ) -> np .ndarray :
17+ def softmax (vector : np .ndarray , axis : int | None = - 1 ) -> np .ndarray :
1818 """
19- Implements the softmax function .
19+ Compute the softmax of ``vector`` along ``axis`` in a numerically-stable way .
2020
2121 Parameters:
22- vector (np.ndarray | list | tuple): A numpy array of shape (1, n)
23- consisting of real values or a similar list/tuple.
24- axis (int, optional): Axis along which to compute softmax.
25- Default is -1.
22+ vector (np.ndarray | list | tuple): Input data (vector, matrix or
23+ higher-rank tensor). It is converted to a float ``np.ndarray``,
24+ so lists, tuples and integers are accepted too.
25+ axis (int | None, optional): Axis along which softmax is computed so
26+ that the probabilities sum to 1 along that axis. If ``None``, the
27+ softmax is computed over the flattened array (a single
28+ distribution). Default is ``-1`` (the last axis).
2629
2730 Returns:
28- np.ndarray: The input numpy array after applying softmax.
31+ np.ndarray: An array with the same shape as ``vector`` whose values
32+ along ``axis`` (or over the whole array when ``axis is None``) form a
33+ probability distribution that sums to 1.
34+
35+ Raises:
36+ ValueError: If ``vector`` is empty or cannot be converted to a numeric
37+ float array (for example a string or a dict).
38+ numpy.exceptions.AxisError: If ``axis`` is out of bounds for the input.
39+
40+ Note:
41+ If the input contains ``NaN`` or ``inf`` the result will contain
42+ ``NaN`` along the affected axis; softmax is only meaningful for finite
43+ real inputs.
2944
3045 The softmax vector adds up to one. We need to ceil to mitigate precision.
3146
3247 >>> float(np.ceil(np.sum(softmax([1, 2, 3, 4]))))
3348 1.0
3449
35- >>> vec = np.array([5, 5])
36- >>> softmax(vec)
50+ Identical logits map to a uniform distribution:
51+
52+ >>> softmax(np.array([5, 5]))
3753 array([0.5, 0.5])
3854
55+ A single element always maps to 1:
56+
3957 >>> softmax([0])
4058 array([1.])
59+
60+ It is numerically stable for large logits (no overflow):
61+
62+ >>> softmax([1000.0, 1001.0, 1002.0])
63+ array([0.09003057, 0.24472847, 0.66524096])
64+
65+ For a 2-D array the ``axis`` selects where probabilities sum to 1:
66+
67+ >>> mat = np.array([[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]])
68+ >>> np.round(softmax(mat, axis=-1), 3)
69+ array([[0.09 , 0.245, 0.665],
70+ [0.09 , 0.245, 0.665]])
71+ >>> np.round(softmax(mat, axis=0), 3)
72+ array([[0.5, 0.5, 0.5],
73+ [0.5, 0.5, 0.5]])
74+
75+ With ``axis=None`` the whole array becomes one distribution that sums to 1:
76+
77+ >>> float(np.round(np.sum(softmax(mat, axis=None)), 6))
78+ 1.0
79+
80+ Empty, non-numeric and out-of-bounds inputs raise clear errors:
81+
82+ >>> softmax([])
83+ Traceback (most recent call last):
84+ ...
85+ ValueError: softmax input must be non-empty
86+ >>> softmax("not a number")
87+ Traceback (most recent call last):
88+ ...
89+ ValueError: softmax input must be numeric, got str
90+ >>> softmax([1, 2, 3], axis=3)
91+ Traceback (most recent call last):
92+ ...
93+ numpy.exceptions.AxisError: axis 3 is out of bounds for array of dimension 1
4194 """
42- # Convert input to numpy array of floats
43- vector = np .asarray (vector , dtype = float )
95+ # Convert input to a float numpy array, turning numpy's terse conversion
96+ # errors into a clear message about the unsupported input type.
97+ try :
98+ vector = np .asarray (vector , dtype = float )
99+ except (ValueError , TypeError ) as exc :
100+ error_message = f"softmax input must be numeric, got { type (vector ).__name__ } "
101+ raise ValueError (error_message ) from exc
44102
45103 # Handle empty input
46104 if vector .size == 0 :
47105 raise ValueError ("softmax input must be non-empty" )
48106
49- # Validate axis
50- ndim = vector .ndim
51- if axis >= ndim or axis < - ndim :
52- error_message = f"axis { axis } is out of bounds for array of dimension { ndim } "
53- raise AxisError (error_message )
107+ # Validate axis (None means "treat the whole array as one distribution")
108+ if axis is not None :
109+ ndim = vector .ndim
110+ if axis >= ndim or axis < - ndim :
111+ error_message = (
112+ f"axis { axis } is out of bounds for array of dimension { ndim } "
113+ )
114+ raise AxisError (error_message )
115+
54116 # Subtract max for numerical stability
55117 vector_max = np .max (vector , axis = axis , keepdims = True )
56118 exponent_vector = np .exp (vector - vector_max )
@@ -73,3 +135,5 @@ def softmax(vector: np.ndarray, axis: int = -1) -> np.ndarray:
73135 print ("Softmax along last axis:\n " , softmax (mat ))
74136 # Matrix along axis 0
75137 print ("Softmax along axis 0:\n " , softmax (mat , axis = 0 ))
138+ # Whole-matrix distribution
139+ print ("Softmax over the whole matrix:\n " , softmax (mat , axis = None ))
0 commit comments