Jak obliczyć logistyczną funkcję sigmoidalną w Pythonie?


145

To jest logistyczna funkcja sigmoidalna:

wprowadź opis obrazu tutaj

Wiem x. Jak mogę teraz obliczyć F (x) w Pythonie?

Powiedzmy, że x = 0,458.

F (x) =?

Odpowiedzi:


218

To powinno wystarczyć:

import math

def sigmoid(x):
  return 1 / (1 + math.exp(-x))

A teraz możesz to przetestować dzwoniąc pod numer:

>>> sigmoid(0.458)
0.61253961344091512

Aktualizacja : Zwróć uwagę, że powyższe było głównie przeznaczone jako bezpośrednie tłumaczenie danego wyrażenia na kod Pythona. Nie została przetestowana ani nie jest znana jako poprawna numerycznie implementacja. Jeśli wiesz, że potrzebujesz bardzo solidnej implementacji, jestem pewien, że są inne osoby, w których ludzie zastanawiali się nad tym problemem.


7
Tylko dlatego, że potrzebuję go tak często, aby spróbować małych rzeczy:sigmoid = lambda x: 1 / (1 + math.exp(-x))
Martin Thoma

2
To nie działa dla skrajnie ujemnych wartości x. Używałem tej niefortunnej implementacji, dopóki nie zauważyłem, że tworzy ona NaN.
Neil G

3
Jeśli zastąpić math.expz np.expwas nie dostanie Nans, chociaż dostaniesz ostrzeżenia uruchomieniowe.
Richard Rast

2
Korzystanie math.expz numpy tablicy mogą dawać błędy, takie jak: TypeError: only length-1 arrays can be converted to Python scalars. Aby tego uniknąć, powinieneś użyć numpy.exp.
ViniciusArruda

Czy niestabilność liczbową można złagodzić, dodając po prostu x = max(-709,x)przed wyrażeniem?
Elias Hasle

201

Jest również dostępny w scipy: http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.logistic.html

In [1]: from scipy.stats import logistic

In [2]: logistic.cdf(0.458)
Out[2]: 0.61253961344091512

który jest tylko kosztownym opakowaniem (ponieważ pozwala skalować i tłumaczyć funkcję logistyczną) innej funkcji scipy:

In [3]: from scipy.special import expit

In [4]: expit(0.458)
Out[4]: 0.61253961344091512

Jeśli obawiasz się występów, kontynuuj czytanie, w przeciwnym razie po prostu użyj expit.

Niektóre testy porównawcze:

In [5]: def sigmoid(x):
  ....:     return 1 / (1 + math.exp(-x))
  ....: 

In [6]: %timeit -r 1 sigmoid(0.458)
1000000 loops, best of 1: 371 ns per loop


In [7]: %timeit -r 1 logistic.cdf(0.458)
10000 loops, best of 1: 72.2 µs per loop

In [8]: %timeit -r 1 expit(0.458)
100000 loops, best of 1: 2.98 µs per loop

Zgodnie z oczekiwaniami logistic.cdfjest (znacznie) wolniejszy niż expit. expitjest nadal wolniejszy niż sigmoidfunkcja Pythona, gdy jest wywoływana z pojedynczą wartością, ponieważ jest to uniwersalna funkcja napisana w języku C ( http://docs.scipy.org/doc/numpy/reference/ufuncs.html ) i dlatego ma narzut wywołania. Ten narzut jest większy niż przyspieszenie obliczeńexpit z jego skompilowanej natury, gdy jest wywoływane z jedną wartością. Ale staje się pomijalny, jeśli chodzi o duże tablice:

In [9]: import numpy as np

In [10]: x = np.random.random(1000000)

In [11]: def sigmoid_array(x):                                        
   ....:    return 1 / (1 + np.exp(-x))
   ....: 

(Zauważysz niewielką zmianę od math.expdo np.exp(pierwsza nie obsługuje tablic, ale jest znacznie szybsza, jeśli masz tylko jedną wartość do obliczenia))

In [12]: %timeit -r 1 -n 100 sigmoid_array(x)
100 loops, best of 1: 34.3 ms per loop

In [13]: %timeit -r 1 -n 100 expit(x)
100 loops, best of 1: 31 ms per loop

Ale kiedy naprawdę potrzebujesz wydajności, powszechną praktyką jest posiadanie wstępnie obliczonej tabeli funkcji sigmoidalnej, która jest przechowywana w pamięci RAM i zamiana precyzji i pamięci na pewną prędkość (na przykład: http://radimrehurek.com/2013/09 / word2vec-in-python-part-two-optimizing / )

Zwróć również uwagę, że expitimplementacja jest stabilna numerycznie od wersji 0.14.0: https://github.com/scipy/scipy/issues/3385


4
Używając pływaków (1.) zamiast ints (1) w swojej funkcji sigmoidalnej,
skróciłbyś

Nie jestem pewien, czy rozumiem, co masz na myśli (w przykładach są używane liczby zmiennoprzecinkowe), ale w każdym razie rzadko oblicza się sigmoidę na intergerach.
Théo T

2
Kd88 chciał powiedzieć, że literały numeryczne używane w funkcji (1) są analizowane jako liczby całkowite i muszą być rzutowane w czasie wykonywania na liczby zmiennoprzecinkowe. Lepszą wydajność uzyskasz używając literałów zmiennoprzecinkowych (1.0).
krs013

Zawsze możesz wektoryzować funkcję, aby obsługiwała tablice.
agcala

chcesz porozmawiać o drogim opakowaniu? % timeit -r 1 expit (0.458)% timeit -r 1 1 / (1 + np.exp (0.458))
Andrew Louw

42

Oto jak zaimplementowałbyś logistyczną sigmoidę w stabilny numerycznie sposób (jak opisano tutaj ):

def sigmoid(x):
    "Numerically-stable sigmoid function."
    if x >= 0:
        z = exp(-x)
        return 1 / (1 + z)
    else:
        z = exp(x)
        return z / (1 + z)

A może jest to dokładniejsze:

import numpy as np

def sigmoid(x):  
    return math.exp(-np.logaddexp(0, -x))

Wewnętrznie implementuje ten sam warunek co powyżej, ale potem używa log1p.

Ogólnie rzecz biorąc, wielomianowa sigmoida logistyczna to:

def nat_to_exp(q):
    max_q = max(0.0, np.max(q))
    rebased_q = q - max_q
    return np.exp(rebased_q - np.logaddexp(-max_q, np.logaddexp.reduce(rebased_q)))

(Jednak logaddexp.reducemogłoby być dokładniejsze).


odnosząc się do wielomianowej sigmoidy logistycznej (softmax), czy gdybym również chciał mieć parametr temperatury do uczenia się zbrojenia , to wystarczy podzielić max_qi rebased_qprzez tau? ponieważ próbowałem tego i nie dostaję prawdopodobieństwa, że ​​suma
wynosi

@CiprianTomoiaga Jeśli chcesz mieć temperaturę, po prostu podziel dowody ( q) przez swoją temperaturę. rebased_q może być wszystkim: nie zmienia odpowiedzi; poprawia stabilność numeryczną.
Neil G

czy na pewno nat_to_expjest odpowiednikiem softmax (jak wspomniałeś w swojej drugiej odpowiedzi)? Kopiuj-wklej zwraca prawdopodobieństwa, które nie sumują się do 1
Ciprian Tomoiagă

@CiprianTomoiaga Krótka odpowiedź jest taka, że ​​pomijam ostatni składnik danych wejściowych i wyjściowych, więc będziesz musiał to obliczyć, jeśli chcesz, aby był to jeden minus suma reszty. Bardziej statystycznym wyjaśnieniem jest to, że rozkład jakościowy ma n-1 parametrów naturalnych lub n-1 parametrów oczekiwanych.
Neil G

ma sens, w pewnym sensie. Chcesz rozwinąć moje pytanie ?
Ciprian Tomoiagă

7

Inny sposób

>>> def sigmoid(x):
...     return 1 /(1+(math.e**-x))
...
>>> sigmoid(0.458)

1
Jaka jest różnica między funkcją this i relax? Czy funkcja math.e ** - x jest lepsza niż math.exp (-x)?
Richard Knop,

Nie ma różnicy pod względem wyniku wyjściowego. Jeśli chcesz poznać różnicę w szybkości, możesz poświęcić trochę czasu na ich wykonanie. Ale to naprawdę nie jest ważne.
ghostdog74

9
powjest często implementowany w kategoriach expi log, więc używanie expbezpośrednio jest prawie na pewno lepsze.
japreiss

2
Cierpi na przepełnienia, gdy xjest bardzo ujemne.
Neil G

7

Inny sposób poprzez przekształcenie tanhfunkcji:

sigmoid = lambda x: .5 * (math.tanh(.5 * x) + 1)

@NeilG Matematycznie, sigmoida (x) == (1 + tanh (x / 2)) / 2. Jest to więc poprawne rozwiązanie, chociaż metody stabilizowane numerycznie są lepsze.
scottclowe

6

Wydaje mi się, że wielu mogłoby być zainteresowanych swobodnymi parametrami zmieniającymi kształt funkcji sigmoidalnej. Po drugie dla wielu aplikacji, w których chcesz użyć funkcji lustrzanej sigmoidy. Po trzecie, możesz chcieć wykonać prostą normalizację, na przykład wartości wyjściowe mieszczą się w zakresie od 0 do 1.

Próbować:

def normalized_sigmoid_fkt(a, b, x):
   '''
   Returns array of a horizontal mirrored normalized sigmoid function
   output between 0 and 1
   Function parameters a = center; b = width
   '''
   s= 1/(1+np.exp(b*(x-a)))
   return 1*(s-min(s))/(max(s)-min(s)) # normalize function to 0-1

I narysować i porównać:

def draw_function_on_2x2_grid(x): 
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
    plt.subplots_adjust(wspace=.5)
    plt.subplots_adjust(hspace=.5)

    ax1.plot(x, normalized_sigmoid_fkt( .5, 18, x))
    ax1.set_title('1')

    ax2.plot(x, normalized_sigmoid_fkt(0.518, 10.549, x))
    ax2.set_title('2')

    ax3.plot(x, normalized_sigmoid_fkt( .7, 11, x))
    ax3.set_title('3')

    ax4.plot(x, normalized_sigmoid_fkt( .2, 14, x))
    ax4.set_title('4')
    plt.suptitle('Different normalized (sigmoid) function',size=10 )

    return fig

Wreszcie:

x = np.linspace(0,1,100)
Travel_function = draw_function_on_2x2_grid(x)

Wykres funkcji sygmoidalnych


6

Użyj pakietu numpy, aby pozwolić funkcji sigmoid na parsowanie wektorów.

Zgodnie z Deeplearningiem używam następującego kodu:

import numpy as np
def sigmoid(x):
    s = 1/(1+np.exp(-x))
    return s

2

Dobra odpowiedź od @unwind. Nie radzi sobie jednak z ekstremalną liczbą ujemną (rzucając OverflowError).

Moja poprawa:

def sigmoid(x):
    try:
        res = 1 / (1 + math.exp(-x))
    except OverflowError:
        res = 0.0
    return res

Tak jest lepiej, ale nadal cierpisz na numeryczne problemy z perkusją z ujemnymi wartościami.
Neil G,


2

Numerycznie stabilna wersja logistycznej funkcji sigmoidalnej.

    def sigmoid(x):
        pos_mask = (x >= 0)
        neg_mask = (x < 0)
        z = np.zeros_like(x,dtype=float)
        z[pos_mask] = np.exp(-x[pos_mask])
        z[neg_mask] = np.exp(x[neg_mask])
        top = np.ones_like(x,dtype=float)
        top[neg_mask] = z[neg_mask]
        return top / (1 + z)

1
jeśli x jest dodatnie, używamy po prostu 1 / (1 + np.exp (-x)), ale gdy x jest ujemne, używamy funkcji np.exp (x) / (1 + np.exp (x)) zamiast używając 1 / (1 + np.exp (-x)), ponieważ gdy x jest ujemne -x będzie dodatnie, więc np.exp (-x) może eksplodować z powodu dużej wartości -x.
Yash Khare

2

Jedna wkładka ...

In[1]: import numpy as np

In[2]: sigmoid=lambda x: 1 / (1 + np.exp(-x))

In[3]: sigmoid(3)
Out[3]: 0.9525741268224334

1

Metoda wektoryzacji przy użyciu pandas DataFrame/Serieslub numpy array:

Najlepsze odpowiedzi to zoptymalizowane metody obliczania pojedynczego punktu, ale jeśli chcesz zastosować te metody do serii pand lub tablicy numpy, wymaga to apply , co w zasadzie dotyczy pętli w tle i będzie iterować po każdym wierszu i zastosować metodę. Jest to dość nieefektywne.

Aby przyspieszyć nasz kod, możemy wykorzystać wektoryzację i emisję numpy:

x = np.arange(-5,5)
np.divide(1, 1+np.exp(-x))

0    0.006693
1    0.017986
2    0.047426
3    0.119203
4    0.268941
5    0.500000
6    0.731059
7    0.880797
8    0.952574
9    0.982014
dtype: float64

Lub z pandas Series:

x = pd.Series(np.arange(-5,5))
np.divide(1, 1+np.exp(-x))

1

możesz to obliczyć jako:

import math
def sigmoid(x):
  return 1 / (1 + math.exp(-x))

lub koncepcyjne, głębsze i bez importu:

def sigmoid(x):
  return 1 / (1 + 2.718281828 ** -x)

lub możesz użyć numpy dla macierzy:

import numpy as np #make sure numpy is already installed
def sigmoid(x):
  return 1 / (1 + np.exp(-x))

0
import numpy as np

def sigmoid(x):
    s = 1 / (1 + np.exp(-x))
    return s

result = sigmoid(0.467)
print(result)

Powyższy kod jest logistyczną funkcją sigmoidalną w Pythonie. Jeśli wiem, że x = 0.467funkcja esicy, F(x) = 0.385. Możesz spróbować podstawić dowolną wartość x, którą znasz w powyższym kodzie, a otrzymasz inną wartość F(x).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.