softmax python
Cómo implementar la función Softmax en Python (20)
(Bueno ... mucha confusión aquí, tanto en la pregunta como en las respuestas ...)
Para empezar, las dos soluciones (es decir, la suya y la sugerida) no son equivalentes; resultan equivalentes solo para el caso especial de matrices de puntaje 1-D. Lo habrías descubierto si hubieras probado también la matriz de puntuación en 2-D en el ejemplo de prueba de Udacity proporcionado.
En cuanto a los resultados, la única diferencia real entre las dos soluciones es el argumento
axis=0
.
Para ver que este es el caso, intentemos su solución (
your_softmax
) y una en la que la única diferencia sea el argumento del
axis
:
import numpy as np
# your solution:
def your_softmax(x):
"""Compute softmax values for each sets of scores in x."""
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
# correct solution:
def softmax(x):
"""Compute softmax values for each sets of scores in x."""
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum(axis=0) # only difference
Como dije, para una matriz de puntaje 1-D, los resultados son de hecho idénticos:
scores = [3.0, 1.0, 0.2]
print(your_softmax(scores))
# [ 0.8360188 0.11314284 0.05083836]
print(softmax(scores))
# [ 0.8360188 0.11314284 0.05083836]
your_softmax(scores) == softmax(scores)
# array([ True, True, True], dtype=bool)
Sin embargo, aquí están los resultados para la matriz de puntaje 2-D dada en el cuestionario Udacity como ejemplo de prueba:
scores2D = np.array([[1, 2, 3, 6],
[2, 4, 5, 6],
[3, 8, 7, 6]])
print(your_softmax(scores2D))
# [[ 4.89907947e-04 1.33170787e-03 3.61995731e-03 7.27087861e-02]
# [ 1.33170787e-03 9.84006416e-03 2.67480676e-02 7.27087861e-02]
# [ 3.61995731e-03 5.37249300e-01 1.97642972e-01 7.27087861e-02]]
print(softmax(scores2D))
# [[ 0.09003057 0.00242826 0.01587624 0.33333333]
# [ 0.24472847 0.01794253 0.11731043 0.33333333]
# [ 0.66524096 0.97962921 0.86681333 0.33333333]]
Los resultados son diferentes: el segundo es idéntico al esperado en la prueba de Udacity, donde todas las columnas suman 1, que no es el caso con el primer resultado (incorrecto).
Entonces, todo el alboroto era en realidad un detalle de implementación: el argumento del
axis
.
De acuerdo con la
documentación de numpy.sum
:
El valor predeterminado, axis = None, sumará todos los elementos de la matriz de entrada
mientras que aquí queremos sumar en fila, por lo tanto,
axis=0
.
Para una matriz 1-D, la suma de la (única) fila y la suma de todos los elementos resultan ser idénticos, por lo tanto, sus resultados idénticos en ese caso ...
Dejando a un lado el problema del
axis
, su implementación (es decir, su elección de restar el máximo primero) es realmente
mejor
que la solución sugerida.
De hecho, es la forma recomendada de implementar la función softmax: consulte
here
la justificación (estabilidad numérica, también señalada por algunas respuestas anteriores).
De la clase de aprendizaje profundo de Udacity , el softmax de y_i es simplemente el exponencial dividido por la suma del exponencial de todo el vector Y:
Donde
S(y_i)
es la función softmax de
y_i
y
e
es la exponencial y
j
es el no.
de columnas en el vector de entrada Y.
He intentado lo siguiente:
import numpy as np
def softmax(x):
"""Compute softmax values for each sets of scores in x."""
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
scores = [3.0, 1.0, 0.2]
print(softmax(scores))
que devuelve:
[ 0.8360188 0.11314284 0.05083836]
Pero la solución sugerida fue:
def softmax(x):
"""Compute softmax values for each sets of scores in x."""
return np.exp(x) / np.sum(np.exp(x), axis=0)
que produce el mismo resultado que la primera implementación , aunque la primera implementación explícitamente toma la diferencia de cada columna y el máximo y luego se divide por la suma.
¿Alguien puede mostrar matemáticamente por qué? ¿Es uno correcto y el otro incorrecto?
¿La implementación es similar en términos de código y complejidad de tiempo? ¿Cuál es más eficiente?
Ambos son correctos, pero el suyo es el preferido desde el punto de vista de la estabilidad numérica.
Empiezas con
e ^ (x - max(x)) / sum(e^(x - max(x))
Al usar el hecho de que a ^ (b - c) = (a ^ b) / (a ^ c) tenemos
= e ^ x / (e ^ max(x) * sum(e ^ x / e ^ max(x)))
= e ^ x / sum(e ^ x)
Que es lo que dice la otra respuesta. Puede reemplazar max (x) con cualquier variable y se cancelaría.
Aquí hay una solución generalizada que utiliza numpy y comparación para la corrección con tensorflow y scipy:
Preparación de datos:
import numpy as np
np.random.seed(2019)
batch_size = 1
n_items = 3
n_classes = 2
logits_np = np.random.rand(batch_size,n_items,n_classes).astype(np.float32)
print(''logits_np.shape'', logits_np.shape)
print(''logits_np:'')
print(logits_np)
Salida:
logits_np.shape (1, 3, 2)
logits_np:
[[[0.9034822 0.3930805 ]
[0.62397 0.6378774 ]
[0.88049906 0.299172 ]]]
Softmax usando tensorflow:
import tensorflow as tf
logits_tf = tf.convert_to_tensor(logits_np, np.float32)
scores_tf = tf.nn.softmax(logits_np, axis=-1)
print(''logits_tf.shape'', logits_tf.shape)
print(''scores_tf.shape'', scores_tf.shape)
with tf.Session() as sess:
scores_np = sess.run(scores_tf)
print(''scores_np.shape'', scores_np.shape)
print(''scores_np:'')
print(scores_np)
print(''np.sum(scores_np, axis=-1).shape'', np.sum(scores_np,axis=-1).shape)
print(''np.sum(scores_np, axis=-1):'')
print(np.sum(scores_np, axis=-1))
Salida:
logits_tf.shape (1, 3, 2)
scores_tf.shape (1, 3, 2)
scores_np.shape (1, 3, 2)
scores_np:
[[[0.62490064 0.37509936]
[0.4965232 0.5034768 ]
[0.64137274 0.3586273 ]]]
np.sum(scores_np, axis=-1).shape (1, 3)
np.sum(scores_np, axis=-1):
[[1. 1. 1.]]
Softmax usando scipy:
from scipy.special import softmax
scores_np = softmax(logits_np, axis=-1)
print(''scores_np.shape'', scores_np.shape)
print(''scores_np:'')
print(scores_np)
print(''np.sum(scores_np, axis=-1).shape'', np.sum(scores_np, axis=-1).shape)
print(''np.sum(scores_np, axis=-1):'')
print(np.sum(scores_np, axis=-1))
Salida:
scores_np.shape (1, 3, 2)
scores_np:
[[[0.62490064 0.37509936]
[0.4965232 0.5034768 ]
[0.6413727 0.35862732]]]
np.sum(scores_np, axis=-1).shape (1, 3)
np.sum(scores_np, axis=-1):
[[1. 1. 1.]]
Softmax usando numpy ( here ):
def softmax(X, theta = 1.0, axis = None):
"""
Compute the softmax of each element along an axis of X.
Parameters
----------
X: ND-Array. Probably should be floats.
theta (optional): float parameter, used as a multiplier
prior to exponentiation. Default = 1.0
axis (optional): axis to compute values along. Default is the
first non-singleton axis.
Returns an array the same size as X. The result will sum to 1
along the specified axis.
"""
# make X at least 2d
y = np.atleast_2d(X)
# find axis
if axis is None:
axis = next(j[0] for j in enumerate(y.shape) if j[1] > 1)
# multiply y against the theta parameter,
y = y * float(theta)
# subtract the max for numerical stability
y = y - np.expand_dims(np.max(y, axis = axis), axis)
# exponentiate y
y = np.exp(y)
# take the sum along the specified axis
ax_sum = np.expand_dims(np.sum(y, axis = axis), axis)
# finally: divide elementwise
p = y / ax_sum
# flatten if X was 1D
if len(X.shape) == 1: p = p.flatten()
return p
scores_np = softmax(logits_np, axis=-1)
print(''scores_np.shape'', scores_np.shape)
print(''scores_np:'')
print(scores_np)
print(''np.sum(scores_np, axis=-1).shape'', np.sum(scores_np, axis=-1).shape)
print(''np.sum(scores_np, axis=-1):'')
print(np.sum(scores_np, axis=-1))
Salida:
scores_np.shape (1, 3, 2)
scores_np:
[[[0.62490064 0.37509936]
[0.49652317 0.5034768 ]
[0.64137274 0.3586273 ]]]
np.sum(scores_np, axis=-1).shape (1, 3)
np.sum(scores_np, axis=-1):
[[1. 1. 1.]]
Desde el punto de vista matemático, ambos lados son iguales.
Y puedes probar esto fácilmente.
Vamos a
m=max(x)
.
Ahora su función
softmax
devuelve un vector, cuya i-ésima coordenada es igual a
tenga en cuenta que esto funciona para cualquier
m
, porque para todos los números (incluso complejos)
e^m != 0
-
desde el punto de vista de la complejidad computacional, también son equivalentes y ambos se ejecutan en tiempo
O(n)
, donden
es el tamaño de un vector. -
desde el punto de vista de la estabilidad numérica , se prefiere la primera solución, porque
e^x
crece muy rápido e incluso para valores bastante pequeños dex
se desbordará. Restar el valor máximo permite deshacerse de este desbordamiento. Para experimentar prácticamente las cosas de las que estaba hablando, intente alimentarx = np.array([1000, 5])
en ambas funciones. Uno devolverá la probabilidad correcta, el segundo se desbordará connan
-
su solución funciona solo para vectores (el cuestionario Udacity quiere que también lo calcule para matrices). Para solucionarlo, debe usar
sum(axis=0)
Diría que si bien ambos son matemáticamente correctos, en cuanto a la implementación, el primero es mejor. Al calcular softmax, los valores intermedios pueden llegar a ser muy grandes. Dividir dos números grandes puede ser numéricamente inestable. here (de Stanford) mencionan un truco de normalización que es esencialmente lo que estás haciendo.
El objetivo era lograr resultados similares usando Numpy y Tensorflow.
El único cambio de la respuesta original es el parámetro de
axis
para
np.sum
api.
Enfoque inicial
:
axis=0
- Sin embargo, esto no proporciona los resultados previstos cuando las dimensiones son N.
Enfoque modificado
:
axis=len(e_x.shape)-1
- Suma siempre en la última dimensión.
Esto proporciona resultados similares a la función softmax de tensorflow.
def softmax_fn(input_array):
"""
| **@author**: Prathyush SP
|
| Calculate Softmax for a given array
:param input_array: Input Array
:return: Softmax Score
"""
e_x = np.exp(input_array - np.max(input_array))
return e_x / e_x.sum(axis=len(e_x.shape)-1)
El propósito de la función softmax es preservar la proporción de los vectores en lugar de aplastar los puntos finales con un sigmoide a medida que los valores se saturan (es decir, tienden a +/- 1 (tanh) o de 0 a 1 (logístico)). Esto se debe a que conserva más información sobre la tasa de cambio en los puntos finales y, por lo tanto, es más aplicable a las redes neuronales con codificación de salida 1-de-N (es decir, si aplastamos los puntos finales, sería más difícil diferenciar el 1 -of-N clase de salida porque no podemos decir cuál es el "más grande" o el "más pequeño" porque fueron aplastados); también hace que la producción total sume a 1, y el ganador claro estará más cerca de 1, mientras que otros números que están cerca uno del otro sumarán 1 / p, donde p es el número de neuronas de salida con valores similares.
El propósito de restar el valor máximo del vector es que cuando haces e ^ y exponentes puedes obtener un valor muy alto que recorta el flotador en el valor máximo que conduce a un empate, lo cual no es el caso en este ejemplo. Esto se convierte en un GRAN problema si resta el valor máximo para hacer un número negativo, entonces tiene un exponente negativo que reduce rápidamente los valores que alteran la relación, que es lo que ocurrió en la pregunta del póster y arrojó la respuesta incorrecta.
La respuesta proporcionada por Udacity es HORRIBLEMENTE ineficiente. Lo primero que debemos hacer es calcular e ^ y_j para todos los componentes del vector, MANTENER ESOS VALORES, luego sumarlos y dividirlos. Donde Udacity se equivocó, calcularon e ^ y_j ¡¡DOS VECES !!! Aquí está la respuesta correcta:
def softmax(y):
e_to_the_y_j = np.exp(y)
return e_to_the_y_j / np.sum(e_to_the_y_j, axis=0)
Entonces, este es realmente un comentario a la respuesta de desertnaut, pero aún no puedo comentarlo debido a mi reputación. Como señaló, su versión solo es correcta si su entrada consiste en una sola muestra. Si su entrada consta de varias muestras, está mal. Sin embargo, la solución de desertnaut también está mal. El problema es que una vez que toma una entrada unidimensional y luego toma una entrada bidimensional. Déjame mostrarte esto.
import numpy as np
# your solution:
def your_softmax(x):
"""Compute softmax values for each sets of scores in x."""
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
# desertnaut solution (copied from his answer):
def desertnaut_softmax(x):
"""Compute softmax values for each sets of scores in x."""
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum(axis=0) # only difference
# my (correct) solution:
def softmax(z):
assert len(z.shape) == 2
s = np.max(z, axis=1)
s = s[:, np.newaxis] # necessary step to do broadcasting
e_x = np.exp(z - s)
div = np.sum(e_x, axis=1)
div = div[:, np.newaxis] # dito
return e_x / div
Tomemos ejemplo de desertnauts:
x1 = np.array([[1, 2, 3, 6]]) # notice that we put the data into 2 dimensions(!)
Esta es la salida:
your_softmax(x1)
array([[ 0.00626879, 0.01704033, 0.04632042, 0.93037047]])
desertnaut_softmax(x1)
array([[ 1., 1., 1., 1.]])
softmax(x1)
array([[ 0.00626879, 0.01704033, 0.04632042, 0.93037047]])
Puedes ver que la versión de desernauts fallaría en esta situación. (No lo haría si la entrada fuera solo unidimensional como np.array ([1, 2, 3, 6]).
Ahora usemos 3 muestras ya que esa es la razón por la que usamos una entrada bidimensional. El siguiente x2 no es el mismo que el del ejemplo de desernauts.
x2 = np.array([[1, 2, 3, 6], # sample 1
[2, 4, 5, 6], # sample 2
[1, 2, 3, 6]]) # sample 1 again(!)
Esta entrada consiste en un lote con 3 muestras. Pero la muestra uno y tres son esencialmente lo mismo. ¡Ahora esperamos 3 filas de activaciones de softmax donde la primera debería ser la misma que la tercera y también la misma que nuestra activación de x1!
your_softmax(x2)
array([[ 0.00183535, 0.00498899, 0.01356148, 0.27238963],
[ 0.00498899, 0.03686393, 0.10020655, 0.27238963],
[ 0.00183535, 0.00498899, 0.01356148, 0.27238963]])
desertnaut_softmax(x2)
array([[ 0.21194156, 0.10650698, 0.10650698, 0.33333333],
[ 0.57611688, 0.78698604, 0.78698604, 0.33333333],
[ 0.21194156, 0.10650698, 0.10650698, 0.33333333]])
softmax(x2)
array([[ 0.00626879, 0.01704033, 0.04632042, 0.93037047],
[ 0.01203764, 0.08894682, 0.24178252, 0.65723302],
[ 0.00626879, 0.01704033, 0.04632042, 0.93037047]])
Espero que puedan ver que este es solo el caso con mi solución.
softmax(x1) == softmax(x2)[0]
array([[ True, True, True, True]], dtype=bool)
softmax(x1) == softmax(x2)[2]
array([[ True, True, True, True]], dtype=bool)
Además, aquí están los resultados de la implementación de TensorFlows softmax:
import tensorflow as tf
import numpy as np
batch = np.asarray([[1,2,3,6],[2,4,5,6],[1,2,3,6]])
x = tf.placeholder(tf.float32, shape=[None, 4])
y = tf.nn.softmax(x)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(y, feed_dict={x: batch})
Y el resultado:
array([[ 0.00626879, 0.01704033, 0.04632042, 0.93037045],
[ 0.01203764, 0.08894681, 0.24178252, 0.657233 ],
[ 0.00626879, 0.01704033, 0.04632042, 0.93037045]], dtype=float32)
Me gustaría complementar un poco más de comprensión del problema. Aquí es correcto restar max de la matriz. Pero si ejecuta el código en la otra publicación, descubrirá que no le da la respuesta correcta cuando la matriz tiene dimensiones 2D o superiores.
Aquí te doy algunas sugerencias:
- Para obtener el máximo, intente hacerlo a lo largo del eje x, obtendrá una matriz 1D.
- Cambie la forma de su matriz máxima a la forma original.
- ¿Np.exp obtiene un valor exponencial?
- Hacer np.sum a lo largo del eje.
- Obtenga los resultados finales.
Siga el resultado, obtendrá la respuesta correcta haciendo la vectorización. Como está relacionado con la tarea de la universidad, no puedo publicar el código exacto aquí, pero me gustaría darle más sugerencias si no lo comprende.
Necesitaba algo compatible con la salida de una capa densa de Tensorflow .
La solución de @desertnaut no funciona en este caso porque tengo lotes de datos. Por lo tanto, vine con otra solución que debería funcionar en ambos casos:
def softmax(x, axis=-1):
e_x = np.exp(x - np.max(x)) # same code
return e_x / e_x.sum(axis=axis, keepdims=True)
Resultados:
logits = np.asarray([
[-0.0052024, -0.00770216, 0.01360943, -0.008921], # 1
[-0.0052024, -0.00770216, 0.01360943, -0.008921] # 2
])
print(softmax(logits))
#[[0.2492037 0.24858153 0.25393605 0.24827873]
# [0.2492037 0.24858153 0.25393605 0.24827873]]
Ref: Tensorflow softmax
Para mantener la estabilidad numérica, se debe restar max (x). El siguiente es el código para la función softmax;
def softmax (x):
if len(x.shape) > 1:
tmp = np.max(x, axis = 1)
x -= tmp.reshape((x.shape[0], 1))
x = np.exp(x)
tmp = np.sum(x, axis = 1)
x /= tmp.reshape((x.shape[0], 1))
else:
tmp = np.max(x)
x -= tmp
x = np.exp(x)
tmp = np.sum(x)
x /= tmp
return x
Para ofrecer una solución alternativa, considere los casos en que sus argumentos son extremadamente grandes en magnitud, de modo que
exp(x)
se desbordaría (en el caso negativo) o se desbordaría (en el caso positivo).
Aquí desea permanecer en el espacio logarítmico el mayor tiempo posible, exponiendo solo al final donde puede confiar en que el resultado se comportará bien.
import scipy.special as sc
import numpy as np
def softmax(x: np.ndarray) -> np.ndarray:
return np.exp(x - sc.logsumexp(x))
Sugeriría esto:
def softmax(z):
z_norm=np.exp(z-np.max(z,axis=0,keepdims=True))
return(np.divide(z_norm,np.sum(z_norm,axis=0,keepdims=True)))
Funcionará tanto para el estocástico como para el lote.
Para más detalles ver:
https://medium.com/@ravish1729/analysis-of-softmax-function-ad058d6a564d
Todos parecen publicar su solución, así que yo publicaré la mía:
def softmax(x):
e_x = np.exp(x.T - np.max(x, axis = -1))
return (e_x / e_x.sum(axis=0)).T
Obtengo exactamente los mismos resultados que los importados de sklearn:
from sklearn.utils.extmath import softmax
Una versión más concisa es:
def softmax(x):
return np.exp(x) / np.exp(x).sum(axis=0)
Ya respondí con mucho detalle en las respuestas anteriores.
max
se resta para evitar el desbordamiento.
Estoy agregando aquí una implementación más en python3.
import numpy as np
def softmax(x):
mx = np.amax(x,axis=1,keepdims = True)
x_exp = np.exp(x - mx)
x_sum = np.sum(x_exp, axis = 1, keepdims = True)
res = x_exp / x_sum
return res
x = np.array([[3,2,4],[4,5,6]])
print(softmax(x))
sklearn también ofrece implementación de softmax
from sklearn.utils.extmath import softmax
import numpy as np
x = np.array([[ 0.50839931, 0.49767588, 0.51260159]])
softmax(x)
# output
array([[ 0.3340521 , 0.33048906, 0.33545884]])
here
puede averiguar por qué lo usaron
- max
.
Desde allí:
"Cuando se escribe código para calcular la función Softmax en la práctica, los términos intermedios pueden ser muy grandes debido a los exponenciales. Dividir números grandes puede ser numéricamente inestable, por lo que es importante utilizar un truco de normalización".
EDITAR A partir de la versión 1.2.0, scipy incluye softmax como una función especial:
https://scipy.github.io/devdocs/generated/scipy.special.softmax.html
Escribí una función aplicando el softmax sobre cualquier eje:
def softmax(X, theta = 1.0, axis = None):
"""
Compute the softmax of each element along an axis of X.
Parameters
----------
X: ND-Array. Probably should be floats.
theta (optional): float parameter, used as a multiplier
prior to exponentiation. Default = 1.0
axis (optional): axis to compute values along. Default is the
first non-singleton axis.
Returns an array the same size as X. The result will sum to 1
along the specified axis.
"""
# make X at least 2d
y = np.atleast_2d(X)
# find axis
if axis is None:
axis = next(j[0] for j in enumerate(y.shape) if j[1] > 1)
# multiply y against the theta parameter,
y = y * float(theta)
# subtract the max for numerical stability
y = y - np.expand_dims(np.max(y, axis = axis), axis)
# exponentiate y
y = np.exp(y)
# take the sum along the specified axis
ax_sum = np.expand_dims(np.sum(y, axis = axis), axis)
# finally: divide elementwise
p = y / ax_sum
# flatten if X was 1D
if len(X.shape) == 1: p = p.flatten()
return p
Restar el máximo, como lo describieron otros usuarios, es una buena práctica. Escribí una publicación detallada al respecto here .
import tensorflow as tf
import numpy as np
def softmax(x):
return (np.exp(x).T / np.exp(x).sum(axis=-1)).T
logits = np.array([[1, 2, 3], [3, 10, 1], [1, 2, 5], [4, 6.5, 1.2], [3, 6, 1]])
sess = tf.Session()
print(softmax(logits))
print(sess.run(tf.nn.softmax(logits)))
sess.close()