Comment calculer une fonction sigmoïde logistique en Python?

146

Il s'agit d'une fonction sigmoïde logistique:

entrez la description de l'image ici

Je sais x. Comment puis-je calculer F (x) en Python maintenant?

Disons x = 0,458.

F (x) =?

Richard Knop
la source

Réponses:

219

Cela devrait le faire:

import math

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

Et maintenant, vous pouvez le tester en appelant:

>>> sigmoid(0.458)
0.61253961344091512

Mise à jour : Notez que ce qui précède était principalement conçu comme une traduction directe un à un de l'expression donnée en code Python. Il n'est pas testé ou connu pour être une mise en œuvre numériquement solide. Si vous savez que vous avez besoin d'une implémentation très robuste, je suis sûr qu'il y en a d'autres où les gens ont réfléchi à ce problème.

se détendre
la source
7
Juste parce que j'en ai besoin si souvent pour essayer de petites choses:sigmoid = lambda x: 1 / (1 + math.exp(-x))
Martin Thoma
2
Cela ne fonctionne pas pour les valeurs négatives extrêmes de x. J'utilisais cette implémentation malheureuse jusqu'à ce que je remarque qu'elle créait des NaN.
Neil G
3
Si vous remplacez math.exppar, np.expvous n'obtiendrez pas de NaN, bien que vous obteniez des avertissements d'exécution.
Richard Rast
2
L' utilisation math.expavec tableau numpy peut donner des erreurs, comme: TypeError: only length-1 arrays can be converted to Python scalars. Pour éviter cela, vous devez utiliser numpy.exp.
ViniciusArruda
L'instabilité numérique peut-elle être atténuée simplement en ajoutant x = max(-709,x)avant l'expression?
Elias Hasle
201

Il est également disponible dans 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

qui n'est qu'un wrapper coûteux (car il vous permet de mettre à l'échelle et de traduire la fonction logistique) d'une autre fonction scipy:

In [3]: from scipy.special import expit

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

Si vous êtes préoccupé par les performances, continuez à lire, sinon utilisez simplement expit.

Quelques benchmarking:

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

Comme prévu, logistic.cdfc'est (beaucoup) plus lent que expit. expitest toujours plus lente que la sigmoidfonction python lorsqu'elle est appelée avec une seule valeur car il s'agit d'une fonction universelle écrite en C ( http://docs.scipy.org/doc/numpy/reference/ufuncs.html ) et a donc une surcharge d'appel. Cette surcharge est plus grande que la vitesse de calcul expitdonnée par sa nature compilée lorsqu'elle est appelée avec une seule valeur. Mais cela devient négligeable lorsqu'il s'agit de grands tableaux:

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

(Vous remarquerez le petit changement de math.expà np.exp(le premier ne prend pas en charge les tableaux, mais est beaucoup plus rapide si vous n'avez qu'une seule valeur à calculer))

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

Mais lorsque vous avez vraiment besoin de performances, une pratique courante consiste à avoir un tableau précalculé de la fonction sigmoïde qui contient de la RAM, et à échanger une certaine précision et mémoire contre une certaine vitesse (par exemple: http://radimrehurek.com/2013/09 / word2vec-in-python-part-two-optimizing / )

Notez également que l' expitimplémentation est numériquement stable depuis la version 0.14.0: https://github.com/scipy/scipy/issues/3385

Théo T
la source
4
En utilisant floats (1.) au lieu de ints (1) dans votre fonction sigmoïde, vous
réduirez le
Je ne suis pas sûr de comprendre ce que vous voulez dire (les flottants sont utilisés dans les exemples), mais dans tous les cas on calcule rarement un sigmoïde sur des intergers.
Théo T
2
Ce que kd88 voulait dire, c'est que les littéraux numériques que vous avez utilisés dans votre fonction (1) sont analysés comme des entiers et doivent être convertis en flottants lors de l'exécution. Vous obtiendrez de meilleures performances en utilisant des littéraux à virgule flottante (1.0).
krs013
Vous pouvez toujours vectoriser la fonction pour qu'elle prenne en charge les tableaux.
agcala
vous voulez parler d'un emballage coûteux? % timeit -r 1 expit (0.458)% timeit -r 1 1 / (1 + np.exp (0.458))
Andrew Louw
42

Voici comment implémenter le sigmoïde logistique de manière numériquement stable (comme décrit ici ):

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)

Ou peut-être est-ce plus précis:

import numpy as np

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

En interne, il implémente la même condition que ci-dessus, mais utilise ensuite log1p.

En général, le sigmoïde logistique multinomial est:

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

(Cependant, cela logaddexp.reducepourrait être plus précis.)

Neil G
la source
se référant au sigmoïde logistique multinomial (softmax), si je voulais aussi un paramètre de température pour l'apprentissage par renforcement , suffit-il de diviser max_qet rebased_qpar tau? parce que j'ai essayé cela et je n'obtiens pas de probabilités qui
totalisent
@CiprianTomoiaga Si vous voulez avoir une température, divisez simplement votre preuve ( q) par votre température. rebased_q peut être n'importe quoi: cela ne change pas la réponse; il améliore la stabilité numérique.
Neil G
êtes-vous sûr qu'il nat_to_expest équivalent à softmax (comme vous l'avez mentionné dans votre autre réponse)? Copier-coller de celui-ci renvoie des probabilités qui ne
totalisent
@CiprianTomoiaga La réponse courte est que j'omets le dernier composant de l'entrée et de la sortie, vous devrez donc le calculer si vous le voulez comme un moins la somme du reste. L'explication la plus statistique est que la distribution catégorielle a n-1 paramètres naturels ou n-1 paramètres d'espérance.
Neil G
a du sens, en quelque sorte. Voulez-vous expliquer ma question ?
Ciprian Tomoiagă
7

autrement

>>> def sigmoid(x):
...     return 1 /(1+(math.e**-x))
...
>>> sigmoid(0.458)
ghostdog74
la source
1
Quelle est la différence entre ceci et la fonction de déroulement? Math.e ** - x est-il meilleur que math.exp (-x)?
Richard Knop
Il n'y a pas de différence en termes de résultat de sortie. Si vous voulez connaître la différence en termes de vitesse, vous pouvez utiliser timeit pour chronométrer leur exécution. Mais ce n'est vraiment pas important.
ghostdog74
9
powest souvent implémenté en termes de expet log, donc l'utilisation expdirecte est presque certainement meilleure.
japreiss
2
Celui-ci souffre de débordements lorsqu'il xest très négatif.
Neil G
7

Une autre façon en transformant la tanhfonction:

sigmoid = lambda x: .5 * (math.tanh(.5 * x) + 1)
dontloo
la source
@NeilG Mathématiquement, sigmoïde (x) == (1 + tanh (x / 2)) / 2. C'est donc une solution valable, bien que les méthodes numériquement stabilisées soient supérieures.
scottclowe
6

Je pense que beaucoup pourraient être intéressés par des paramètres libres pour modifier la forme de la fonction sigmoïde. Deuxièmement, pour de nombreuses applications, vous souhaitez utiliser une fonction sigmoïde en miroir. Troisièmement, vous voudrez peut-être faire une normalisation simple, par exemple, les valeurs de sortie sont comprises entre 0 et 1.

Essayer:

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

Et pour dessiner et comparer:

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

Finalement:

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

Graphique des fonctions sigmoïdes

Philipp Schwarz
la source
6

Utilisez le package numpy pour permettre à votre fonction sigmoïde d'analyser les vecteurs.

Conformément à Deeplearning, j'utilise le code suivant:

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

Bonne réponse de @unwind. Il ne peut cependant pas gérer un nombre négatif extrême (lançant OverflowError).

Mon amélioration:

def sigmoid(x):
    try:
        res = 1 / (1 + math.exp(-x))
    except OverflowError:
        res = 0.0
    return res
czxttkl
la source
C'est mieux, mais vous souffrez toujours de problèmes de percussion numérique avec des valeurs négatives.
Neil G
2

Une version numériquement stable de la fonction sigmoïde logistique.

    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)
Yash Khare
la source
1
si x est positif, nous utilisons simplement 1 / (1 + np.exp (-x)) mais lorsque x est négatif, nous utilisons la fonction np.exp (x) / (1 + np.exp (x)) au lieu de en utilisant 1 / (1 + np.exp (-x)) car lorsque x est négatif -x sera positif donc np.exp (-x) peut exploser en raison de la grande valeur de -x.
Yash Khare
2

Une seule doublure ...

In[1]: import numpy as np

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

In[3]: sigmoid(3)
Out[3]: 0.9525741268224334
cristiandatum
la source
1

Méthode vectorisée lors de l'utilisation de pandas DataFrame/Seriesou numpy array:

Les principales réponses sont des méthodes optimisées pour le calcul d'un point unique, mais lorsque vous souhaitez appliquer ces méthodes à une série pandas ou à un tableau numpy, cela nécessite apply, ce qui est essentiellement une boucle pour en arrière-plan et itérera sur chaque ligne et appliquera la méthode. C'est assez inefficace.

Pour accélérer notre code, nous pouvons utiliser la vectorisation et la diffusion 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

Ou avec un pandas Series:

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

vous pouvez le calculer comme:

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

ou conceptuel, plus profond et sans aucune importation:

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

ou vous pouvez utiliser numpy pour les matrices:

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

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

result = sigmoid(0.467)
print(result)

Le code ci-dessus est la fonction sigmoïde logistique en python. Si je sais que x = 0.467, la fonction sigmoïde, F(x) = 0.385. Vous pouvez essayer de remplacer n'importe quelle valeur de x que vous connaissez dans le code ci-dessus, et vous obtiendrez une valeur différente de F(x).

Dr Hazael Brown
la source