Comment implémenter la fonction Softmax en Python
De la classe D'apprentissage profond D'Udacity , le softmax de y_i est simplement l'exponentielle divisée par la somme de l'exponentielle du vecteur Y entier:
Où S(y_i)
est la fonction softmax de y_i
et e
est l'exponentielle et j
est le pas. de colonnes dans le vecteur D'entrée Y.
J'ai essayé ce qui suit:
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))
Qui renvoie:
[ 0.8360188 0.11314284 0.05083836]
Mais la solution suggérée était:
def softmax(x):
"""Compute softmax values for each sets of scores in x."""
return np.exp(x) / np.sum(np.exp(x), axis=0)
Qui produit le même sortie que la première implémentation , même si la première implémentation prend explicitement la différence de chaque colonne et le max, puis se divise par la somme.
Quelqu'un Peut montrer mathématiquement pourquoi? Est-ce que l'un est correct et l'autre faux?
L'implémentation est-elle similaire en termes de code et de complexité temporelle? Ce qui est plus efficace?
16 réponses
Ils sont tous les deux corrects, mais le vôtre est préféré du point de vue de la stabilité numérique.
Vous commencez par
e ^ (x - max(x)) / sum(e^(x - max(x))
En utilisant le fait que a^(b - c) = (a^b)/(a^c), nous avons
= e ^ x / (e ^ max(x) * sum(e ^ x / e ^ max(x)))
= e ^ x / sum(e ^ x)
C'est ce que dit l'autre réponse. Vous pouvez remplacer max (x) par n'importe quelle variable et cela s'annulerait.
(bien... beaucoup de confusion ici, à la fois dans la question et dans les réponses...)
Pour commencer, les deux solutions (c'est-à-dire la vôtre et celle suggérée) sont Pas équivalentes; elles arrivent à être équivalentes uniquement pour le cas particulier des tableaux de score 1-D. Vous l'auriez découvert si vous aviez également essayé le tableau de score 2-D dans L'exemple fourni par Udacity quiz.
En termes de résultats, la seule différence réelle entre les deux solutions est l'argument axis=0
. Voir que c'est le cas, essayons votre solution (your_softmax
) et celle où la seule différence est l'argument 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
Comme je l'ai dit, pour un tableau de score 1-D, les résultats sont en effet identiques:
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)
Néanmoins, voici les résultats pour le tableau de score 2-D donné dans le Quiz Udacity comme exemple de test:
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]]
Les résultats sont différents - le second est en effet identique à celui attendu dans le Quiz Udacity, où toutes les colonnes totalisent en effet 1, ce qui n'est pas le cas avec le premier (mauvais) résultat.
Donc, tout le bruit était en fait pour un détail d'implémentation - l'argument axis
. Selon le numpy.documentation de somme :
La valeur par défaut, axis=None, additionnera tous les éléments du tableau d'entrée
Alors qu'ici nous voulons additionner en ligne, d'où axis=0
. Pour un tableau 1-D, la somme de la (seule) ligne et la somme de tous les éléments sont identiques, d'où vos résultats identiques cas...
Le problème axis
mis à part, votre implémentation (c'est-à-dire votre choix de soustraire le maximum en premier) est en fait meilleur que la solution suggérée! En fait, c'est la manière recommandée d'implémenter la fonction softmax - voir ici pour la justification (stabilité numérique, également soulignée par certaines réponses ci-dessus).
Je dirais que bien que les deux soient corrects mathématiquement, en termes d'implémentation, le premier est meilleur. Lors du calcul de softmax, les valeurs intermédiaires peuvent devenir très grandes. Diviser deux grands nombres peut être numériquement instable. Ces notes (de Stanford) mentionnent une astuce de normalisation qui est essentiellement ce que vous faites.
Donc, c'est vraiment un commentaire à la réponse de desertnaut mais je ne peux pas encore commenter à cause de ma réputation. Comme il l'a souligné, votre version n'est correcte que si votre entrée consiste en un seul échantillon. Si votre entrée se compose de plusieurs échantillons, c'est faux. cependant, la solution de desertnaut est également fausse. le problème est qu'une fois qu'il prend une entrée 1-dimensionnelle, puis il prend une entrée 2-dimensionnelle. Laissez-moi vous montrer ce à vous.
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
Prenons desertnauts exemple:
x1 = np.array([[1, 2, 3, 6]]) # notice that we put the data into 2 dimensions(!)
C'est la sortie:
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]])
Vous pouvez voir que la version desernauts échouerait dans cette situation. (Ce ne serait pas si l'entrée était juste unidimensionnelle comme np.tableau([1, 2, 3, 6]).
Permet maintenant d'utiliser 3 échantillons puisque c'est la raison pour laquelle nous utilisons une entrée 2 dimensions. Le x2 suivant n'est pas le même que celui de l'exemple desernauts.
x2 = np.array([[1, 2, 3, 6], # sample 1
[2, 4, 5, 6], # sample 2
[1, 2, 3, 6]]) # sample 1 again(!)
Cette entrée est constituée d'un lot de 3 échantillons. Mais les échantillons un et trois sont essentiellement les mêmes. Nous attendez-vous maintenant à 3 lignes d'activations softmax où la première devrait être la même que la troisième et aussi la même que notre activation 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]])
J'espère que vous pouvez voir que ce n'est que le cas avec ma solution.
softmax(x1) == softmax(x2)[0]
array([[ True, True, True, True]], dtype=bool)
softmax(x1) == softmax(x2)[2]
array([[ True, True, True, True]], dtype=bool)
De plus, voici les résultats de L'implémentation 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})
Et le résultat:
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)
Sklearn propose également l'implémentation 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]])
Du point de vue mathématique les deux côtés sont égaux.
Et vous pouvez facilement le prouver. Allons m=max(x)
. Maintenant, votre Fonction softmax
renvoie un vecteur, dont la ième coordonnée est égale à
Notez que cela fonctionne pour tout m
, car pour tous les nombres (même complexes) e^m != 0
Du point de vue de la complexité de calcul, ils sont également équivalents et les deux s'exécutent dans
O(n)
temps, où {[5] } est la taille d'un vecteur.Du point de vue de stabilité numérique , la première solution est préférée, car
e^x
croît très vite et même pour des valeurs assez petites dex
elle débordera. Soustraire la valeur maximale permet de se débarrasser de ce débordement. Pour expérimenter pratiquement les choses dont je parlais, essayez de nourrirx = np.array([1000, 5])
dans vos deux fonctions. L'un retournera la probabilité correcte, le second débordera avecnan
Pas lié à la question, mais votre solution ne fonctionne que pour les vecteurs (Udacity quiz veut que vous le calculiez aussi pour les matrices). Pour corriger cela, vous devez utiliser
sum(axis=0)
Ici, vous pouvez savoir pourquoi ils ont utilisé - max
.
À Partir de là:
" Lorsque vous écrivez du code pour calculer la fonction Softmax en pratique, les Termes intermédiaires peuvent être très grands en raison des exponentielles. Diviser de grands nombres peut être numériquement instable, il est donc important d'utiliser une astuce de normalisation."
J'ai écrit une fonction appliquant le softmax sur n'importe quel axe:
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
Soustraire le max, comme d'autres utilisateurs l'ont décrit, est une bonne pratique. J'ai écrit un poste détaillée à ce sujet ici.
Pour offrir une solution alternative, considérez les cas où vos arguments sont extrêmement importants tels que exp(x)
déborderait (dans le cas négatif) ou déborderait (dans le cas positif). Ici, vous voulez rester dans l'espace du journal aussi longtemps que possible, exponentiating seulement à la fin où vous pouvez faire confiance le résultat sera bien comportés.
import scipy.special as sc
import numpy as np
def softmax(x: np.ndarray) -> np.ndarray:
return np.exp(x - sc.logsumexp(x))
Une version plus concise est:
def softmax(x):
return np.exp(x) / np.exp(x).sum(axis=0)
Je suggère ceci -
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)))
Cela fonctionnera aussi bien pour stochastique que pour le lot. Pour plus de détail, voir l' https://medium.com/@ravish1729/analysis-of-softmax-function-ad058d6a564d
Afin de maintenir la stabilité numérique, max (x) doit être soustrait. Ce qui suit est le code pour la fonction 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
Je voudrais compléter un peu plus la compréhension du problème. Ici, il est correct de soustraire max du tableau. Mais si vous exécutez le code dans l'autre post, vous constaterez qu'il ne vous donne pas la bonne réponse lorsque le tableau est de dimensions 2D ou supérieures.
Ici, je vous donne quelques suggestions:
- pour obtenir max, essayez de le faire le long de l'axe des x, vous obtiendrez un tableau 1D.
- remodelez votre tableau max à la forme originale.
- Avez-np.exp obtenir exponentielle valeur.
- Avez-np.somme le long de l'axe.
- Obtenez les résultats finaux.
Suivez le résultat, vous obtiendrez la bonne réponse en faisant la vectorisation. Comme il est lié aux devoirs du collège, Je ne peux pas poster le code exact ici, mais je voudrais donner plus de suggestions si vous ne comprenez pas.
Déjà répondu en détail dans les réponses ci-dessus. max
est soustrait pour éviter le débordement. J'ajoute ici une implémentation de plus 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))
Le but de la fonction softmax est de préserver le rapport des vecteurs par opposition à écraser les points d'extrémité avec un sigmoïde lorsque les valeurs saturent (c'est-à - dire tendent à + / - 1 (tanh) ou de 0 à 1 (logistique)). C'est parce qu'il conserve plus d'informations sur le taux de changement aux points d'extrémité et est donc plus applicable aux réseaux neuronaux avec un codage de sortie 1-of-N (c'est-à-dire que si nous écrasons les points d'extrémité, il serait plus difficile de différencier la classe de sortie 1-of-N parce que nous ne qui est le "plus grand" ou "plus petit" parce qu'ils ont été écrasée.); en outre, il fait la somme totale de sortie à 1, et le gagnant clair sera plus proche de 1 tandis que les autres nombres qui sont proches les uns des autres seront additionnés à 1/p, où p est le nombre de neurones de sortie avec des valeurs similaires.
Le but de soustraire la valeur max du vecteur est que lorsque vous faites des exposants e^y, Vous pouvez obtenir une valeur très élevée qui clipse le flottant à la valeur max menant à une cravate, ce qui n'est pas le cas dans ce cas exemple. Cela devient un gros problème si vous soustrayez la valeur max pour faire un nombre négatif, alors vous avez un exposant négatif qui rétrécit rapidement les valeurs modifiant le rapport, ce qui s'est produit dans la question de poster et a donné la réponse incorrecte.
La réponse fournie par Udacity est horriblement inefficace. La première chose que nous devons faire est de calculer e^y_j pour toutes les composantes vectorielles, de conserver ces valeurs, puis de les additionner et de les diviser. Où Udacity foiré est-ce qu'ils calculent e^y_j Deux fois!!! Voici la bonne réponse:
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)
L'objectif était d'obtenir des résultats similaires en utilisant Numpy et Tensorflow. Le seul changement par rapport à la réponse originale est le paramètre axis
pour l'api np.sum
.
Approche initiale : axis=0
- Cependant, cela ne fournit pas les résultats escomptés lorsque les dimensions sont N.
Approche Modifiée: axis=len(e_x.shape)-1
- Toujours en montant sur la dernière dimension. Cela fournit des résultats similaires à la fonction 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)