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:

entrez la description de l'image ici

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?

160
demandé sur Martin Thoma 2016-01-23 23:52:50

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.

81
répondu Trevor Merrifield 2018-04-11 15:24:14

(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).

69
répondu desertnaut 2016-07-07 16:55:58

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.

30
répondu Shagun Sodhani 2016-02-08 18:13:54

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)
30
répondu ChuckFive 2016-09-19 13:35:08

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]]) 
17
répondu Roman Orac 2017-07-28 07:25:54

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 à

entrez la description de l'image ici

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 de x 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 nourrir x = np.array([1000, 5]) dans vos deux fonctions. L'un retournera la probabilité correcte, le second débordera avec nan

  • 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)

9
répondu Salvador Dali 2018-07-04 11:07:19

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."

7
répondu Sadegh Salehi 2016-06-29 20:09:44

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.

6
répondu Nolan Conaway 2017-06-02 01:11:56

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))
4
répondu PikalaxALT 2018-02-15 19:38:28

Une version plus concise est:

def softmax(x):
    return np.exp(x) / np.exp(x).sum(axis=0)
3
répondu Pimin Konstantin Kefaloukos 2016-09-06 20:08:40

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

1
répondu Ravish Kumar Sharma 2018-08-31 19:24:17

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
0
répondu Rahul Ahuja 2016-11-06 15:52:37

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:

  1. pour obtenir max, essayez de le faire le long de l'axe des x, vous obtiendrez un tableau 1D.
  2. remodelez votre tableau max à la forme originale.
  3. Avez-np.exp obtenir exponentielle valeur.
  4. Avez-np.somme le long de l'axe.
  5. 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.

0
répondu Hao Xu 2017-07-16 02:00:38

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))
0
répondu Debashish 2017-12-15 10:04:54

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)
0
répondu 2018-04-27 09:18:45

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)
0
répondu kingspp 2018-10-03 15:22:33