Jak zaimplementować funkcję Softmax w Pythonie

Z klasy głębokiego uczenia Udacity , softmax y_i jest po prostu wykładnikiem podzielonym przez sumę wykładniczą całego wektora Y:

Tutaj wpisz opis obrazka

Gdzie S(y_i) jest funkcją softmax y_i i {[5] } jest wykładniczą i j jest nie. kolumny w wektorze wejściowym Y.

Próbowałem:

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

Które zwraca:

[ 0.8360188   0.11314284  0.05083836]

Ale sugerowanym rozwiązaniem było:

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    return np.exp(x) / np.sum(np.exp(x), axis=0)

Który wytwarza to samo wyjście co pierwsza implementacja, mimo że pierwsza implementacja wyraźnie pobiera różnicę każdej kolumny i maksa, a następnie dzieli przez sumę.

Czy ktoś może pokazać matematycznie dlaczego? Czy jeden ma rację, a drugi się myli?

Czy implementacja jest podobna pod względem kodu i złożoności czasowej? Co jest bardziej efektywne?

Author: Martin Thoma, 2016-01-23

16 answers

Oba są poprawne, ale twój jest preferowany z punktu widzenia stabilności liczbowej.

Zaczynasz od

e ^ (x - max(x)) / sum(e^(x - max(x))

Używając faktu, że a^(b - c) = (a^b)/(a^c) mamy

= e ^ x / (e ^ max(x) * sum(e ^ x / e ^ max(x)))

= e ^ x / sum(e ^ x)

Czyli to, co mówi druga odpowiedź. Można zastąpić max (x) dowolną zmienną i to anulować.

 81
Author: Trevor Merrifield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-11 15:24:14

(dobrze... wiele zamieszania tutaj, zarówno w pytaniu, jak i w odpowiedziach...)

Na początek, dwa rozwiązania (tj. twoje i sugerowane) są Nie równoważne; zdarzają się być równoważne tylko dla specjalnego przypadku tablic wyników 1-D. Odkryłbyś ją, gdybyś wypróbował również tablicę wyników 2-D w przykładzie quizu Udacity.

Jeśli chodzi o wyniki, jedyną rzeczywistą różnicą między tymi dwoma rozwiązaniami jest argument axis=0. Zobaczyć jeśli tak jest, wypróbujmy Twoje rozwiązanie (your_softmax) i takie, w którym jedyną różnicą jest 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

Jak powiedziałem, dla tablicy wyników 1-D wyniki są rzeczywiście identyczne:

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)

Niemniej jednak, oto wyniki dla tablicy wyników 2-D podanej w quizie Udacity jako przykład testu:

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

Wyniki są różne - drugi jest rzeczywiście identyczny z oczekiwanym w quizie Udacity, gdzie wszystkie kolumny rzeczywiście sumują się do 1, co nie jest przypadek z pierwszym (błędnym) wynikiem.

Więc całe zamieszanie dotyczyło szczegółów implementacji-argumentu axis. Według numpy.suma dokumentacji :

Wartość domyślna, axis = None, sumuje wszystkie elementy tablicy wejściowej

Podczas gdy tutaj chcemy sumować wiersze, stąd axis=0. Dla tablicy 1-D suma (tylko) wiersza i suma wszystkich elementów jest identyczna, stąd identyczność wynika z tego, że case...

Pomijając kwestię axis, twoja implementacja (tj. wybór, aby najpierw odjąć max) jest w rzeczywistości lepsza niż sugerowane rozwiązanie! W rzeczywistości jest to zalecany sposób implementacji funkcji softmax-patrz tutaj dla uzasadnienia(stabilność liczbowa, również wskazana przez niektóre odpowiedzi powyżej).

 69
Author: desertnaut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-07 16:55:58

Powiedziałbym, że chociaż oba są poprawne matematycznie, to pierwszy jest lepszy. Przy obliczaniu softmax wartości pośrednie mogą stać się bardzo duże. Dzielenie dwóch dużych liczb może być numerycznie niestabilne. te notatki (ze Stanford) wspominają o sztuczce normalizacyjnej, która jest zasadniczo tym, co robisz.

 30
Author: Shagun Sodhani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-08 18:13:54

Więc to jest naprawdę komentarz do odpowiedzi desertnaut, ale nie mogę go jeszcze skomentować ze względu na moją reputację. Jak zaznaczył, Twoja wersja jest poprawna tylko wtedy, gdy twoje wejście składa się z pojedynczej próbki. Jeśli Dane wejściowe składają się z kilku próbek, jest to błędne. Jednak rozwiązanie desertnaut również jest błędne. problem polega na tym, że raz bierze 1-wymiarowe wejście, a następnie bierze 2-wymiarowe wejście. Pokażę Ci to.

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

Lets take desertnauts przykład:

x1 = np.array([[1, 2, 3, 6]]) # notice that we put the data into 2 dimensions(!)

To jest wyjście:

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

Widać, że wersja desernauts zawiedzie w tej sytuacji. (Nie byłoby, gdyby wejście było tylko jednowymiarowe jak np.array([1, 2, 3, 6]).

Pozwala teraz używać 3 próbek, ponieważ to jest powód, dla którego używamy dwuwymiarowego wejścia. Poniższy x2 nie jest taki sam jak ten z przykładu desernauts.

x2 = np.array([[1, 2, 3, 6],  # sample 1
               [2, 4, 5, 6],  # sample 2
               [1, 2, 3, 6]]) # sample 1 again(!)

To wejście składa się z partii z 3 próbkami. Ale próbka pierwsza i trzecia są zasadniczo takie same. My teraz spodziewaj się 3 rzędów aktywacji softmax, gdzie pierwsza powinna być taka sama jak trzecia, a także taka sama jak nasza aktywacja 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]])

Mam nadzieję, że widzisz, że tak jest tylko w przypadku mojego rozwiązania.

softmax(x1) == softmax(x2)[0]
array([[ True,  True,  True,  True]], dtype=bool)

softmax(x1) == softmax(x2)[2]
array([[ True,  True,  True,  True]], dtype=bool)

Dodatkowo, oto wyniki implementacji 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})

I wynik:

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
Author: ChuckFive,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-19 13:35:08

Sklep oferuje również implementację 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
Author: Roman Orac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 07:25:54

Z matematycznego punktu widzenia obie strony są równe.

I możesz to łatwo udowodnić. Let ' s m=max(x). Teraz Funkcja softmax zwraca wektor, którego i-ta współrzędna jest równa

Tutaj wpisz opis obrazka

Zauważ, że działa to dla dowolnych m, ponieważ dla wszystkich (parzystych złożonych) liczb e^m != 0

  • Z punktu widzenia złożoności obliczeniowej są one również równoważne i oba biegną w czasie O(n), Gdzie n jest wielkością wektora.

  • Z punktu widzenia stabilności numerycznej preferowane jest pierwsze rozwiązanie, ponieważ e^x rośnie bardzo szybko i nawet dla dość małych wartości x przepełni się. Odjęcie maksymalnej wartości pozwala pozbyć się tego przepełnienia. Aby praktycznie doświadczyć rzeczy, o których mówiłem, spróbuj wprowadzić x = np.array([1000, 5]) w obie swoje funkcje. Jeden zwróci prawidłowe prawdopodobieństwo, drugi przepełni się nan

  • Nie związane z pytaniem, ale Twoje rozwiązanie działa tylko dla wektorów(Udacity quiz chce, abyś obliczył je również dla macierzy). Aby to naprawić, musisz użyć sum(axis=0)

 9
Author: Salvador Dali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-04 11:07:19

Tutaj możesz dowiedzieć się, dlaczego użyli - max.

Stamtąd:

" kiedy piszesz kod do obliczania funkcji Softmax w praktyce, terminy pośrednie mogą być bardzo duże ze względu na wykładniki. Dzielenie dużych liczb może być numerycznie niestabilne, dlatego ważne jest użycie sztuczki normalizacyjnej."

 7
Author: Sadegh Salehi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-29 20:09:44

Napisałem funkcję stosującą softmax na dowolnej osi:

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

Odejmowanie maksa, jak opisali inni użytkownicy, jest dobrą praktyką. Napisałem o tym szczegółowy post tutaj .

 6
Author: Nolan Conaway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-02 01:11:56

Aby zaoferować alternatywne rozwiązanie, rozważ przypadki, w których twoje argumenty są bardzo duże, takie, że exp(x) będzie underflow (w przypadku negatywnym) lub overflow (w przypadku pozytywnym). Tutaj chcesz pozostać w przestrzeni logów tak długo, jak to możliwe, wykładnicze tylko na końcu, gdzie można ufać, że wynik będzie dobrze zachowywał.

import scipy.special as sc
import numpy as np

def softmax(x: np.ndarray) -> np.ndarray:
    return np.exp(x - sc.logsumexp(x))
 4
Author: PikalaxALT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-15 19:38:28

Bardziej zwięzła wersja to:

def softmax(x):
    return np.exp(x) / np.exp(x).sum(axis=0)
 3
Author: Pimin Konstantin Kefaloukos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-06 20:08:40

Proponuję to -

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

Będzie działać zarówno dla stochastic, jak i partii. Więcej szczegółów można znaleźć w https://medium.com/@ravish1729/analysis-of-softmax-function-ad058d6a564d

 1
Author: Ravish Kumar Sharma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-31 19:24:17

Aby utrzymać stabilność liczbową, należy odjąć max(x). Poniżej znajduje się kod funkcji 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
Author: Rahul Ahuja,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-06 15:52:37

Chciałbym uzupełnić nieco więcej zrozumienia problemu. Tutaj jest poprawne odejmowanie max tablicy. Ale jeśli uruchomisz kod w innym poście, okaże się, że nie daje prawidłowej odpowiedzi, gdy tablica ma wymiary 2D lub wyższe.

Oto kilka sugestii:

  1. aby uzyskać max, spróbuj zrobić to wzdłuż osi x, otrzymasz tablicę 1D.
  2. Przekształć swoją tablicę max do oryginalnego kształtu.
  3. Do np.exp get exponential wartość.
  4. Do np.suma wzdłuż osi.
  5. uzyskaj ostateczne Wyniki.

Postępuj zgodnie z wynikiem otrzymasz poprawną odpowiedź wykonując wektoryzację. Ponieważ jest to związane z pracą domową na uczelni, nie mogę opublikować dokładnego kodu tutaj, ale chciałbym podać więcej sugestii, jeśli nie rozumiesz.

 0
Author: Hao Xu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-16 02:00:38

Już odpowiedział bardzo szczegółowo w powyższych odpowiedziach. max jest odejmowane, aby uniknąć przepełnienia. Dodaję tu jeszcze jedną implementację w 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
Author: Debashish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-15 10:04:54

Celem funkcji softmax jest zachowanie stosunku wektorów w przeciwieństwie do zgniatania punktów końcowych sigmoidem, gdy wartości są nasycone (tzn. mają tendencję do + / -1 (tanh) lub od 0 do 1 (0)). Dzieje się tak dlatego, że zachowuje więcej informacji o szybkości zmian w punktach końcowych, a tym samym jest bardziej odpowiedni dla sieci neuronowych z kodowaniem wyjścia 1-of-N (tj. który z nich jest "największy" lub "najmniejszy", ponieważ zostały zgniecione.); również sprawia, że całkowita suma wyjściowa wynosi 1, A jasny zwycięzca będzie bliżej 1, podczas gdy inne liczby, które są blisko siebie, będą sumować się do 1 / p, gdzie p jest liczbą neuronów wyjściowych o podobnych wartościach.

Celem odejmowania wartości maksymalnej od wektora jest to, że gdy wykonujesz wykładniki E^y, możesz uzyskać bardzo wysoką wartość, która zaciska float na wartości maksymalnej, prowadząc do remisu, co nie jest w tym przypadku przykład. Staje się to dużym problemem, jeśli odejmujesz wartość maksymalną, aby uzyskać liczbę ujemną, wtedy masz wykładnik ujemny, który szybko zmniejsza wartości zmieniające stosunek, co stało się w pytaniu postera i dało błędną odpowiedź.

ODPOWIEDŹ dostarczona przez Udacity jest strasznie nieefektywna. Pierwszą rzeczą, którą musimy zrobić, to obliczyć e^y_j dla wszystkich składników wektorowych, zachować te wartości, następnie zsumować je i podzielić. Gdzie Udacity to obliczają e^y_j Dwa razy!!! Oto prawidłowa odpowiedź:

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
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-27 09:18:45

Celem było osiągnięcie podobnych wyników przy użyciu Numpy i Tensorflow. Jedyną zmianą w stosunku do odpowiedzi oryginalnej jest parametr axis dla api np.sum.

Podejście początkowe : axis=0 - nie daje to jednak zamierzonych rezultatów, gdy wymiary wynoszą N.

Zmodyfikowane podejście: axis=len(e_x.shape)-1 - zawsze suma na ostatnim wymiarze. Daje to podobne wyniki jak funkcja softmax 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
Author: kingspp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 15:22:33