web-dev-qa-db-fra.com

Comment implémenter la fonction Softmax en Python

Dans la classe d'apprentissage en profondeur de Udacity , la softmax de y_i est simplement l'exponentielle divisée par la somme de l'exponentielle du vecteur Y entier:

 enter image description here

S(y_i) est la fonction softmax de y_i et e est l'exponentielle et j est le no. des 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 retourne:

[ 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 résultat que la première implémentation, même si la première implémentation prend explicitement la différence de chaque colonne et le maximum, puis se divise par la somme.

_ {Quelqu'un peut-il montrer mathématiquement pourquoi? Un est-il correct et l'autre est-il faux?

_ {Les implémentations sont-elles similaires en termes de code et de complexité temporelle? Laquelle est la plus efficace?

180
alvas

Les deux sont corrects, mais votre préférence est préférée du point de vue de la stabilité numérique.

Vous commencez avec

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 l’annuler.

99
Trevor Merrifield

(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 non équivalentes; ils arrivent pour n'être équivalents que dans le cas particulier des tableaux de scores 1-D. Vous l'auriez découvert si vous aviez également essayé le tableau de scores 2-D dans l'exemple de test Udacity.

En termes de résultats, la seule différence entre les deux solutions est l'argument axis=0. Pour 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 scores 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 scores 2D donnés dans le questionnaire Udacity à titre d'exemple:

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 - la seconde est en effet identique à celle attendue dans le quiz Udacity, où toutes les colonnes totalisent bien 1, ce qui n’est pas le cas du premier résultat (erroné).

Donc, tout le problème était en fait pour un détail de l'implémentation - l'argument axis. Selon la documentation de numpy.sum :

La valeur par défaut, axis = None, fera la somme de tous les éléments du tableau en entrée.

alors que nous voulons faire la somme par rangée, 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ù les résultats identiques dans ce cas ...

Le problème axis mis à part, votre implémentation (c’est-à-dire votre choix de soustraire le max en premier) est en réalité meilleure que la solution proposée! En fait, c'est la méthode recommandée pour implémenter la fonction softmax - voir ici pour la justification (stabilité numérique, également soulignée par certaines réponses ci-dessus).

76
desertnaut

C'est donc vraiment un commentaire sur la réponse de desertnaut, mais je ne peux pas encore le commenter à cause de ma réputation. Comme il l’a souligné, votre version n’est correcte que si votre saisie consiste en un seul échantillon. Si votre entrée consiste en 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 dimension, il prend ensuite une entrée à 2 dimensions. Laisse-moi te montrer ça.

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 par exemple les desserts désertiques:

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 de desernauts échouerait dans cette situation. (Cela ne serait pas le cas si l'entrée était simplement une dimension comme np.array ([1, 2, 3, 6]).

Utilisons maintenant 3 échantillons car c’est la raison pour laquelle nous utilisons une entrée en 2 dimensions. Le x2 suivant n'est pas le même que celui de l'exemple de 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 consiste en un lot de 3 échantillons. Mais les échantillons un et trois sont essentiellement les mêmes. Nous attendons maintenant 3 lignes d'activation de softmax, la première étant identique à la troisième et identique à l'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 voyez que ce n'est que le cas de 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)
44
ChuckFive

Je dirais que si les deux sont corrects du point de vue mathématique, du point de vue de la mise en œuvre, le premier est préférable. Lors du calcul de softmax, les valeurs intermédiaires peuvent devenir très grandes. La division de deux grands nombres peut être numériquement instable. Ces notes (de Stanford) mentionnent une astuce de normalisation qui est essentiellement ce que vous faites. 

31
Shagun Sodhani

sklearn propose également la mise en œuvre 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]]) 
20
Roman Orac

D'un point de vue mathématique, les deux côtés sont égaux. 

Et vous pouvez facilement le prouver. Let's m=max(x). Maintenant, votre fonction softmax renvoie un vecteur dont la i-ème coordonnée est égale à

 enter image description here

remarquez 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 s'exécutent tous les deux dans O(n) time, où n est la taille d'un vecteur. 

  • À partir de stabilité numérique point de vue, la première solution est préférable, car e^x croît très rapidement et même pour de très petites valeurs de x, il débordera. La soustraction de la valeur maximale permet de supprimer ce dépassement. Pour expérimenter concrètement les choses dont je parlais, essayez d’intégrer x = np.array([1000, 5]) dans vos deux fonctions. L'un renverra une probabilité correcte, le second débordera de nan

  • pas en rapport avec la question, mais votre solution ne fonctionne que pour les vecteurs (Udacity quiz vous demande de la calculer également pour les matrices). Pour résoudre ce problème, vous devez utiliser sum(axis=0)

10
Salvador Dali

Ici vous pouvez savoir pourquoi ils ont utilisé - max

De là:

"Lorsque vous écrivez du code pour le calcul de la fonction Softmax, les termes intermédiaires peuvent être très volumineux en raison des exponentielles. La division de grands nombres peut être numériquement instable, il est donc important d'utiliser une astuce de normalisation."

9
Sadegh Salehi

MODIFIER. Depuis la version 1.2.0, scipy inclut softmax en tant que fonction spéciale: 

https://scipy.github.io/devdocs/generated/scipy.special.softmax.html

J'ai écrit une fonction qui applique 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 maximum, comme d'autres utilisateurs l'ont décrit, est une bonne pratique. J'ai écrit un article détaillé à ce sujet ici .

7
Nolan Conaway

Une version plus concise est:

def softmax(x):
    return np.exp(x) / np.exp(x).sum(axis=0)

Pour proposer une autre solution, considérons les cas où vos arguments sont extrêmement volumineux, de sorte que exp(x) pourrait déborder (dans le cas négatif) ou trop (dans le cas positif). Ici, vous voulez rester dans l'espace journal le plus longtemps possible, en exponentiant uniquement à la fin où vous pouvez être sûr que le résultat se comportera correctement.

import scipy.special as sc
import numpy as np

def softmax(x: np.ndarray) -> np.ndarray:
    return np.exp(x - sc.logsumexp(x))
4
PikalaxALT

Je suggérerais 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 le traitement stochastique que pour le lot . Pour plus de détails, voir https://medium.com/@ravish1729/analysis-of-softmax-function-ad058d6a564d

1
Ravish Kumar Sharma

Voici une solution généralisée utilisant numpy et la comparaison pour une correction avec tensorflow et scipy:

Préparation des données:

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)

Sortie:

logits_np.shape (1, 3, 2)
logits_np:
[[[0.9034822  0.3930805 ]
  [0.62397    0.6378774 ]
  [0.88049906 0.299172  ]]]

Softmax utilisant 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))

Sortie:

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

Sortie:

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 using numpy ( https://nolanbconaway.github.io/blog/2017/softmax-numpy ):

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

Sortie:

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.]]
0
mrgloom

L'objectif était d'obtenir des résultats similaires avec Numpy et Tensorflow. Le seul changement par rapport à la réponse originale est le paramètre axis pour np.sum api.

Approche initiale : axis=0 - Cela ne donne toutefois pas les résultats escomptés lorsque les dimensions sont N.

Approche modifiée : axis=len(e_x.shape)-1 - Faites toujours la somme sur la dernière dimension. Ceci fournit des résultats similaires à ceux de 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
kingspp

J'avais besoin de quelque chose de compatible avec la sortie d'une couche dense de Tensorflow

La solution de @desertnaut ne fonctionne pas dans ce cas car j'ai des lots de données. Par conséquent, je suis venu avec une autre solution qui devrait fonctionner dans les deux cas:

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)

Résultats:

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

0
Lucas Casagrande

Tout le monde semble poster sa solution alors je vais poster le mien:

def softmax(x):
    e_x = np.exp(x.T - np.max(x, axis = -1))
    return (e_x / e_x.sum(axis=0)).T

Je reçois exactement les mêmes résultats que ceux importés de sklearn:

from sklearn.utils.extmath import softmax
0
Julian

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 message, vous constaterez qu'il ne vous donne pas la bonne réponse lorsque le tableau est en 2D ou à des dimensions supérieures.

Ici je vous donne quelques suggestions:

  1. Pour obtenir le maximum, essayez de le faire en abscisse, vous obtiendrez un tableau 1D.
  2. Remodelez votre tableau max à la forme originale.
  3. Ne np.exp obtenir une valeur exponentielle.
  4. Do np.sum le long de l'axe.
  5. Obtenez les résultats finaux.

Suivez le résultat, vous obtiendrez la bonne réponse en faisant de la vectorisation. Comme il est lié aux devoirs du collège, je ne peux pas poster le code exact ici, mais j'aimerais donner plus de suggestions si vous ne comprenez pas.

0
Hao Xu

Déjà répondu en détail dans les réponses ci-dessus. max est soustrait pour éviter tout débordement. J'ajoute ici une autre implémentation dans 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
Debashish

Le but de la fonction softmax est de préserver le rapport des vecteurs au lieu d’écraser les points finaux avec un sigmoïde lorsque les valeurs saturent (c’est-à-dire qu’elles ont tendance à +/- 1 (tanh) ou de 0 à 1 (logistique)). En effet, cela préserve plus d’informations sur le taux de changement aux points finaux et s’applique donc davantage aux réseaux neuronaux avec un codage de sortie 1-sur-N (c’est-à-dire que si nous réduisions les points finaux, il serait plus difficile de différencier le 1 -of-N output class car on ne sait pas laquelle est la "plus grande" ou la "plus petite", car elles ont été écrasées.); de plus, la somme de sortie totale est égale à 1 et le gagnant clair sera plus proche de 1, tandis que les autres nombres proches les uns des autres totalisent 1/p, où p est le nombre de neurones de sortie ayant des valeurs similaires.

Le but de la soustraction de la valeur maximale du vecteur est que, lorsque vous créez des exposants, vous pouvez obtenir une valeur très élevée qui coupe le flottant à la valeur maximale menant à une égalité, ce qui n'est pas le cas dans cet exemple. Cela devient un gros problème si vous soustrayez la valeur maximale pour obtenir un nombre négatif, vous avez alors un exposant négatif qui réduit rapidement les valeurs en modifiant le rapport, ce qui est arrivé à la question de l'affiche et a donné la réponse incorrecte.

La réponse fournie par Udacity est horriblement inefficace. La première chose à faire est de calculer e ^ y_j pour toutes les composantes du vecteur, GARDER CES VALEURS, puis de les résumer et de les diviser. Où Udacity est foiré, ils calculent 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
user2356685
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()
0
King

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
Rahul Ahuja