Come calcolare una funzione sigmoide logistica in Python?


146

Questa è una funzione sigmoidea logistica:

inserisci qui la descrizione dell'immagine

Conosco x. Come posso calcolare F (x) in Python ora?

Diciamo x = 0.458.

F (x) =?

Risposte:


219

Questo dovrebbe farlo:

import math

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

E ora puoi provarlo chiamando:

>>> sigmoid(0.458)
0.61253961344091512

Aggiornamento : si noti che quanto sopra era principalmente inteso come una traduzione diretta one-to-one dell'espressione data in codice Python. È non testato o caratterizza per essere una corretta attuazione numericamente. Se sai di aver bisogno di un'implementazione molto solida, sono sicuro che ce ne sono altri in cui le persone hanno effettivamente pensato a questo problema.


7
Solo perché ne ho bisogno così spesso per provare piccole cose:sigmoid = lambda x: 1 / (1 + math.exp(-x))
Martin Thoma,

2
Questo non funziona per valori estremamente negativi di x. Stavo usando questa sfortunata implementazione fino a quando ho notato che stava creando NaNs.
Neil G,

3
Se lo sostituisci math.expcon np.exp, non riceverai NaN, anche se riceverai avvisi di runtime.
Richard Rast,

2
Uso math.expcon matrice NumPy può produrre alcuni errori, come: TypeError: only length-1 arrays can be converted to Python scalars. Per evitarlo dovresti usare numpy.exp.
ViniciusArruda,

L'instabilità numerica può essere mitigata semplicemente aggiungendo x = max(-709,x)prima dell'espressione?
Elias Hasle,

201

È disponibile anche in 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

che è solo un costoso wrapper (perché ti consente di ridimensionare e tradurre la funzione logistica) di un'altra funzione scipy:

In [3]: from scipy.special import expit

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

Se sei preoccupato per le esibizioni continua a leggere, altrimenti usa solo expit.

Alcuni benchmark:

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

Come previsto logistic.cdfè (molto) più lento di expit. expitè ancora più lento della sigmoidfunzione python quando viene chiamato con un singolo valore perché è una funzione universale scritta in C ( http://docs.scipy.org/doc/numpy/reference/ufuncs.html ) e quindi ha un overhead di chiamata. Questo sovraccarico è maggiore della velocità di calcolo expitdata dalla sua natura compilata quando viene chiamato con un singolo valore. Ma diventa trascurabile quando si tratta di grandi array:

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

(Noterai il piccolo cambiamento da math.expa np.exp(il primo non supporta gli array, ma è molto più veloce se hai un solo valore da calcolare))

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

Ma quando hai davvero bisogno di prestazioni, una pratica comune è avere una tabella pre-calcolata della funzione sigmoid che si trova nella RAM e scambiare precisione e memoria per una certa velocità (ad esempio: http://radimrehurek.com/2013/09 / word2vec-in-python-part-two-optimizing / )

Inoltre, si noti che l' expitimplementazione è numericamente stabile dalla versione 0.14.0: https://github.com/scipy/scipy/issues/3385


4
Usando float (1.) invece di ints (1) nella tua funzione
sigmoid ridurrai il

Non sono sicuro di aver capito cosa intendi (i float sono usati negli esempi), ma in ogni caso raramente si calcola un sigmoide sugli interger.
Théo T

2
Ciò che kd88 voleva dire era che i letterali numerici che hai usato nella tua funzione (1) sono analizzati come numeri interi e devono essere lanciati in fase di esecuzione su float. Otterresti prestazioni migliori usando valori letterali in virgola mobile (1.0).
krs013,

È sempre possibile vettorializzare la funzione in modo che supporti gli array.
agcala,

vuoi parlare di un involucro costoso? % timeit -r 1 expit (0.458)% timeit -r 1 1 / (1 + np.exp (0.458))
Andrew Louw,

42

Ecco come implementare il sigmoid logistico in modo numericamente stabile (come descritto qui ):

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)

O forse questo è più preciso:

import numpy as np

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

Internamente, implementa le stesse condizioni di cui sopra, ma poi utilizza log1p.

In generale, il sigmoide logistico multinomiale è:

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

(Tuttavia, logaddexp.reducepotrebbe essere più preciso.)


riferendosi al sigmoide logistico multinomiale (softmax), se volevo anche un parametro di temperatura per l'apprendimento del rinforzo , è sufficiente dividere max_qe rebased_qper tau? perché l'ho provato e non ho probabilità che si
sommino

@CiprianTomoiaga Se vuoi avere una temperatura, dividi semplicemente le tue prove ( q) per la tua temperatura. rebased_q può essere qualsiasi cosa: non cambia la risposta; migliora la stabilità numerica.
Neil G,

sei sicuro che nat_to_expequivale a softmax (come hai già detto nella tua altra risposta)? Copia-incolla ne restituisce probabilità che non si sommano a 1
Ciprian Tomoiagă

@CiprianTomoiaga La risposta breve è che ho omesso il componente finale dell'input e dell'output, quindi dovrai calcolarlo se lo desideri come uno meno la somma del resto. La spiegazione più statistica è che la distribuzione categoriale ha n-1 parametri naturali o n-1 parametri di aspettativa.
Neil G,

ha un senso, in un certo senso. Ti interessa ellaborare sulla mia domanda ?
Ciprian Tomoiagă,

7

un altro modo

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

1
Qual è la differenza tra questa e la funzione di svolgimento? Math.e ** - x è meglio di math.exp (-x)?
Richard Knop,

Non vi è alcuna differenza in termini di risultato di output. Se vuoi conoscere la differenza in termini di velocità, puoi usare timeit per programmare la loro esecuzione. Ma questo non è davvero importante.
ghostdog74,

9
powviene spesso implementato in termini di expe log, quindi l'utilizzo expdiretto è quasi sicuramente migliore.
japreiss,

2
Questo soffre di overflow quando xè molto negativo.
Neil G,

7

Un altro modo trasformando la tanhfunzione:

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

@NeilG Matematicamente, sigmoide (x) == (1 + tanh (x / 2)) / 2. Quindi questa è una soluzione valida, sebbene i metodi a stabilizzazione numerica siano superiori.
scottclowe,

6

Penso che molti potrebbero essere interessati ai parametri gratuiti per alterare la forma della funzione sigmoide. Secondo per molte applicazioni, si desidera utilizzare una funzione sigmoid con mirroring. In terzo luogo, potresti voler eseguire una semplice normalizzazione, ad esempio i valori di output sono compresi tra 0 e 1.

Provare:

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

E per disegnare e confrontare:

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

Finalmente:

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

Grafico delle funzioni sigmoide


6

Utilizzare il pacchetto numpy per consentire alla funzione sigmoid di analizzare i vettori.

In conformità con Deeplearning, utilizzo il seguente codice:

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

2

Buona risposta da @unwind. Tuttavia, non può gestire un numero estremamente negativo (lanciando OverflowError).

Il mio miglioramento:

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

Questo è meglio, ma stai ancora soffrendo di problemi di percussione numerici con valori negativi.
Neil G,


2

Una versione numericamente stabile della funzione sigmoidea logistica.

    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
se x è positivo stiamo semplicemente usando 1 / (1 + np.exp (-x)) ma quando x è negativo stiamo usando la funzione np.exp (x) / (1 + np.exp (x)) invece di usando 1 / (1 + np.exp (-x)) perché quando x è negativo -x sarà positivo, quindi np.exp (-x) può esplodere a causa del grande valore di -x.
Yash Khare,

2

Una fodera ...

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

Metodo vettoriale quando si utilizza pandas DataFrame/Serieso numpy array:

Le risposte migliori sono metodi ottimizzati per il calcolo a punto singolo, ma quando si desidera applicare questi metodi a una serie di panda o ad un array numpy, è necessario apply , che è fondamentalmente per il ciclo in background e itererà su ogni riga e applicherà il metodo. Questo è abbastanza inefficiente.

Per accelerare il nostro codice, possiamo utilizzare la vettorializzazione e la trasmissione 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

O con un pandas Series:

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

1

puoi calcolarlo come:

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

o concettuale, più profondo e senza alcuna importazione:

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

oppure puoi usare numpy per le matrici:

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)

Il codice sopra è la funzione sigmoid logistica in python. Se io so che x = 0.467, la funzione sigmoide, F(x) = 0.385. Puoi provare a sostituire qualsiasi valore di x che conosci nel codice sopra e otterrai un valore diverso di F(x).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.