2017-08-04 1 views
1

Je suis en train de suivre cet exemple, mais ne peut pas sembler adapter à travailler avec mon jeu de données puisque j'ai besoin Normales tronquées: https://stackoverflow.com/questions/35990467/fit-two-gaussians-to-a-histogram-from-one-set-of-data-python#=Ajustement du modèle de mélange (Bimodal?) Dans SciPy en utilisant des normales tronquées. Python 3

J'ai un ensemble de données qui est certainement un mélange de 2 normales tronquées. La valeur minimale dans le domaine est 0 et le maximum est 1. Je veux créer un objet que je peux adapter pour optimiser les paramètres et obtenir la probabilité qu'une séquence de nombres soit tirée de cette distribution. Une option pourrait consister à simplement utiliser le modèle KDE et à utiliser le pdf pour avoir la probabilité. Cependant, je veux la moyenne exacte et les écarts-types des 2 distributions. Je suppose que je pourrais, diviser les données en deux et ensuite modéliser les 2 normales séparément mais je veux aussi apprendre à utiliser optimize en SciPy. Je commence juste à expérimenter avec ce type d'analyse statistique donc mes excuses si cela semble naïf.

Je ne suis pas sûr comment obtenir un pdf de cette façon qui peut intégrer à 1 et un domaine limité entre 0 et 1.

import requests 
from ast import literal_eval 
from scipy import optimize, stats 
import matplotlib.pyplot as plt 
import seaborn as sns 
import numpy as np 


# Actual Data 
u = np.asarray(literal_eval(requests.get("https://pastebin.com/raw/hP5VJ9vr").text)) 
# u.size ==> 6000 
u.min(), u.max() 
# (1.3628525454666037e-08, 0.99973136607553781) 

# Distribution 
with plt.style.context("seaborn-white"): 
    fig, ax = plt.subplots() 
    sns.kdeplot(u, color="black", ax=ax) 
    ax.axvline(0, linestyle=":", color="red") 
    ax.axvline(1, linestyle=":", color="red") 
kde = stats.gaussian_kde(u) 

enter image description here

# KDE Model 
def truncated_gaussian_lower(x,mu,sigma,A): 
    return np.clip(A*np.exp(-(x-mu)**2/2/sigma**2), a_min=0, a_max=None) 
def truncated_gaussian_upper(x,mu,sigma,A): 
    return np.clip(A*np.exp(-(x-mu)**2/2/sigma**2), a_min=None, a_max=1) 
def mixture_model(x,mu1,sigma1,A1,mu2,sigma2,A2): 
    return truncated_gaussian_lower(x,mu1,sigma1,A1) + truncated_gaussian_upper(x,mu2,sigma2,A2) 
kde = stats.gaussian_kde(u) 

# Estimates: mu sigma A 
estimates= [0.1, 1, 3, 
      0.9, 1, 1] 
params,cov= optimize.curve_fit(mixture_model,u,kde.pdf(u),estimates) 

# --------------------------------------------------------------------------- 
# RuntimeError        Traceback (most recent call last) 
# <ipython-input-265-b2efb2ca0e0a> in <module>() 
#  32 estimates= [0.1, 1, 3, 
#  33    0.9, 1, 1] 
# ---> 34 params,cov= optimize.curve_fit(mixture_model,u,kde.pdf(u),estimates) 

# /Users/mu/anaconda/lib/python3.6/site-packages/scipy/optimize/minpack.py in curve_fit(f, xdata, ydata, p0, sigma, absolute_sigma, check_finite, bounds, method, jac, **kwargs) 
#  738   cost = np.sum(infodict['fvec'] ** 2) 
#  739   if ier not in [1, 2, 3, 4]: 
# --> 740    raise RuntimeError("Optimal parameters not found: " + errmsg) 
#  741  else: 
#  742   # Rename maxfev (leastsq) to max_nfev (least_squares), if specified. 

# RuntimeError: Optimal parameters not found: Number of calls to function has reached maxfev = 1400. 

En réponse à @ L'explication très utile d'Uvar ci-dessous. J'essaie de tester l'intégrale de 0 - 1 pour voir si elle est égale à 1 mais j'obtiens 0.3. Je pense que je manque une étape cruciale dans la logique:

# KDE Model 
def truncated_gaussian(x,mu,sigma,A): 
    return A*np.exp(-(x-mu)**2/2/sigma**2) 

def mixture_model(x,mu1,sigma1,A1,mu2,sigma2,A2): 
    if type(x) == np.ndarray: 
     norm_probas = truncated_gaussian(x,mu1,sigma1,A1) + truncated_gaussian(x,mu2,sigma2,A2) 
     mask_lower = x < 0 
     mask_upper = x > 1 
     mask_floor = (mask_lower.astype(int) + mask_upper.astype(int)) > 1 
     norm_probas[mask_floor] = 0 
     return norm_probas 
    else: 
     if (x < 0) or (x > 1): 
      return 0 
     return truncated_gaussian_lower(x,mu1,sigma1,A1) + truncated_gaussian_upper(x,mu2,sigma2,A2) 

kde = stats.gaussian_kde(u, bw_method=2e-2) 

# # Estimates: mu sigma A 
estimates= [0.1, 1, 3, 
      0.9, 1, 1] 
params,cov= optimize.curve_fit(mixture_model,u,kde.pdf(u)/integrate.quad(kde, 0 , 1)[0],estimates ,maxfev=5000) 
# params 
# array([ 9.89751700e-01, 1.92831695e-02, 7.84324114e+00, 
#   3.73623345e-03, 1.07754038e-02, 3.79238972e+01]) 

# Test the integral from 0 - 1 
x = np.linspace(0,1,1000) 
with plt.style.context("seaborn-white"): 
    fig, ax = plt.subplots() 
    ax.plot(x, kde(x), color="black", label="Data") 
    ax.plot(x, mixture_model(x, *params), color="red", label="Model") 
    ax.legend() 
# Integrating from 0 to 1 
integrate.quad(lambda x: mixture_model(x, *params), 0,1)[0] 
# 0.3026863969781809 

enter image description here

+0

Je cherche un moyen d'obtenir une distribution personnalisée scipy où le pdf s'intègre à 1 et est lié entre 0 et 1 ayant 2 pics; un autour de 0 (avec une densité plus élevée) et un autre autour de 1 (avec une densité plus faible). –

Répondre

2

Il semble que vous êtes la procédure d'ajustement erreurs de spécification. Vous essayez d'ajuster le kde.pdf(u) tout en limitant les demi-limites. Comme vous pouvez le voir, la fonction de densité de probabilité de u n'est pas contrainte à [0,1]. En tant que tel, la suppression de l'action d'écrêtage entraînera un ajustement exact.

def truncated_gaussian_lower(x,mu,sigma,A): 
    return A*np.exp((-(x-mu)**2)/(2*sigma**2)) 
def truncated_gaussian_upper(x,mu,sigma,A): 
    return A * np.exp((-(x-mu)**2)/(2*sigma**2)) 
def mixture_model(x,mu1,sigma1,A1,mu2,sigma2,A2): 
    return truncated_gaussian_lower(x,mu1,sigma1,A1) + truncated_gaussian_upper(x,mu2,sigma2,A2) 

estimates= [0.15, 1, 3, 
      0.95, 1, 1] 
params,cov= optimize.curve_fit(f=mixture_model, xdata=u, ydata=kde.pdf(u), p0=estimates) 

params 
Out[327]: 
array([ 0.00672248, 0.07462657, 4.01188383, 0.98006841, 0.07654998, 
     1.30569665]) 

y3 = mixture_model(u, params[0], params[1], params[2], params[3], params[4], params[5]) 

plt.plot(kde.pdf(u)+0.1) #add offset for visual inspection purpose 

plt.plot(y3) 

Perfect overlap, made visible by the +0.1 offset

Alors, disons que maintenant je change ce que je suis comploter pour:

plt.figure(); plt.plot(u,y3,'.') 

Which does look like what you are trying to achieve

Parce que, en effet:

np.allclose(y3, kde(u), atol=1e-2) 
>>True 

Vous pouvez modifier le modèle de mélange un peu à 0 en dehors du domaine [0, 1]:

def mixture_model(x,mu1,sigma1,A1,mu2,sigma2,A2): 
    if (x < 0) or (x > 1): 
     return 0 
    return truncated_gaussian_lower(x,mu1,sigma1,A1) + truncated_gaussian_upper(x,mu2,sigma2,A2) 

Cela, cependant, perdre l'option d'évaluer instantanément la fonction sur un tableau de x .. Donc, pour le bien d'argument, je vais le laisser dehors pour l'instant. Quoi qu'il en soit, nous voulons que notre intégrale résume jusqu'à 1 dans le domaine [0, 1], et une façon de le faire (n'hésitez pas à jouer avec l'estimateur de la bande passante en stats.gaussian_kde ainsi ..) est de diviser l'estimation de densité de probabilité par son intégrale sur le domaine.Veillez à ce que optimize.curve_fit ne prenne que 1400 itérations dans cette implémentation, donc les estimations de paramètres initiaux sont importantes.

from scipy import integrate 
sum_prob = integrate.quad(kde, 0 , 1)[0] 
y = kde(u)/sum_prob 
# Estimates: mu sigma A 
estimates= [0.15, 1, 5, 
      0.95, 0.5, 3] 
params,cov= optimize.curve_fit(f=mixture_model, xdata=u, ydata=y, p0=estimates) 
>>array([ 6.72247814e-03, 7.46265651e-02, 7.23699661e+00, 
    9.80068414e-01, 7.65499825e-02, 2.35533297e+00]) 

y3 = mixture_model(np.arange(0,1,0.001), params[0], params[1], params[2], 
    params[3], params[4], params[5]) 

with plt.style.context("seaborn-white"): 
    fig, ax = plt.subplots() 
    sns.kdeplot(u, color="black", ax=ax) 
    ax.axvline(0, linestyle=":", color="red") 
    ax.axvline(1, linestyle=":", color="red") 
    plt.plot(np.arange(0,1,0.001), y3) #The red line is now your custom pdf with area-under-curve = 0.998 in the domain.. 

Total plot

Pour vérifier la zone sous la courbe, je cette solution hacky de redéfinir mixture_model ..:

def mixture_model(x): 
    mu1=params[0]; sigma1=params[1]; A1=params[2]; mu2=params[3]; sigma2=params[4]; A2=params[5] 
    return truncated_gaussian_lower(x,mu1,sigma1,A1) + truncated_gaussian_upper(x,mu2,sigma2,A2) 

from scipy import integrate 
integrated_value, error = integrate.quad(mixture_model, 0, 1) #0 lower bound, 1 upper bound 
>>(0.9978588016186962, 5.222293368393178e-14) 

Ou faire l'intégrale d'une deuxième façon:

import sympy 
x = sympy.symbols('x', real=True, nonnegative=True) 
foo = sympy.integrate(params[2]*sympy.exp((-(x-params[0])**2)/(2*params[1]**2))+params[5]*sympy.exp((-(x-params[3])**2)/(2*params[4]**2)),(x,0,1), manual=True) 
foo.doit() 

>>0.562981541724715*sqrt(pi) #this evaluates to 0.9978588016186956 

Et en fait, à votre façon, comme décrit dans votre question éditée:

def mixture_model(x,mu1,sigma1,A1,mu2,sigma2,A2): 
    return truncated_gaussian_lower(x,mu1,sigma1,A1) + truncated_gaussian_upper(x,mu2,sigma2,A2) 
integrate.quad(lambda x: mixture_model(x, *params), 0,1)[0] 
>>0.9978588016186962 

Si je mets ma bande passante à votre niveau (2e-2), en effet l'évaluation se résume à 0,92, ce qui est un résultat pire que le 0,998 nous avions plus tôt, mais qui est encore significativement différent de 0,3 vous signalez ce que je ne peux pas recréer, même en copiant vos extraits de code. Peut-être redéfinissez-vous accidentellement des fonctions/variables quelque part?

+0

Existe-t-il un moyen de modéliser le mélange de normales tronquées? Peut-être que les clips n'étaient pas la meilleure façon, mais il semble que les moyens soient corrects. Si j'ai un vecteur de valeurs, je me demande comment je pourrais obtenir la probabilité tout en considérant le domaine de ce pdf étant dans la gamme de 0 - 1 –

+0

Je ne comprends pas votre question ici. Comme vous pouvez le voir, les valeurs de u sont dans l'intervalle [0, 1]; cependant, les valeurs de pdf n'ont pas besoin d'être dans cette gamme, c'est la probabilité DENSITÉ, pas la probabilité. L'intégrale va évaluer à environ 1, puisque la probabilité sur l'ensemble du domaine devrait être 1. Et, à partir de l'optimisation des paramètres, vous avez vos 2 gaussiennes en forme. Que reste-t-il d'autre? Est-ce que vous manquez le décalage que j'ai ajouté pour visualiser que les formes correspondent? Si je supprime le décalage, vous verrez 1 ligne, avec l'autre caché en dessous .. – Uvar

+0

Merci pour cela et l'explication! Laissez-moi digérer cela et revenir à vous. Une chose qui me dérange est qu'un pdf devrait intégrer à 1 w/dans son domaine juste? Si je calcule le 'kde.pdf (1.1)' je reçois '0.37351788'. Cette valeur ne devrait-elle pas être 0? –