Podemos ver en esta respuesta que el número más pequeño en Python (solo tómalo , por ejemplo) se 5e-324
debe al IEEE754 , y la causa del hardware también se aplica a otros idiomas.
In [2]: np.nextafter(0, 1)
Out[2]: 5e-324
Y cualquier flotador más pequeño que eso llevaría a 0.
In [3]: np.nextafter(0, 1)/2
Out[3]: 0.0
Y veamos la función de Naive Bayes with discrete features and two classes
como lo requirió:
p(S=1|w1,...wn)=p(S=1)∏ni=1p(wi|S=1) ∑s={0,1}p(S=s)∏ni=1p(wi|S=s)
Permítanme crear una instancia de esa función con una simple tarea de PNL a continuación.
Decidimos detectar si el correo electrónico entrante es spam ( ) o no spam ( ) y tenemos un vocabulario de 5.000 palabras ( ) y la única preocupación es si aparece una palabra ( ) ( ) en el correo electrónico o no ( ) por simplicidad ( Bernoulli ingenuo Bayes ).S=1S=0n=5,000wip(wi|S=1)1−p(wi|S=1)
In [1]: import numpy as np
In [2]: from sklearn.naive_bayes import BernoulliNB
# let's train our model with 200 samples
In [3]: X = np.random.randint(2, size=(200, 5000))
In [4]: y = np.random.randint(2, size=(200, 1)).ravel()
In [5]: clf = BernoulliNB()
In [6]: model = clf.fit(X, y)
Podemos ver que sería muy pequeño debido a las probabilidades (tanto como estaría entre 0 y 1) en , y por lo tanto, estamos seguros de que el producto será menor que y solo obtenemos .p(S=s)∏ni=1p(wi|S=s)p(wi|S=1)1−p(wi|S=1)∏5000i5e−3240/0
In [7]: (np.nextafter(0, 1)*2) / (np.nextafter(0, 1)*2)
Out[7]: 1.0
In [8]: (np.nextafter(0, 1)/2) / (np.nextafter(0, 1)/2)
/home/lerner/anaconda3/bin/ipython3:1: RuntimeWarning: invalid value encountered in double_scalars
#!/home/lerner/anaconda3/bin/python
Out[8]: nan
In [9]: l_cpt = model.feature_log_prob_
In [10]: x = np.random.randint(2, size=(1, 5000))
In [11]: cls_lp = model.class_log_prior_
In [12]: probs = np.where(x, np.exp(l_cpt[1]), 1-np.exp(l_cpt[1]))
In [13]: np.exp(cls_lp[1]) * np.prod(probs)
Out[14]: 0.0
Entonces surge el problema: ¿cómo podemos calcular la probabilidad de que el correo electrónico sea un spam ? ¿O cómo podemos calcular el numerador y el denominador?p(S=1|w1,...wn)
Podemos ver la implementación oficial en sklearn :
jll = self._joint_log_likelihood(X)
# normalize by P(x) = P(f_1, ..., f_n)
log_prob_x = logsumexp(jll, axis=1)
return jll - np.atleast_2d(log_prob_x).T
Para el numerador, convirtió el producto de probabilidades en la suma de la probabilidad logarítmica y para el denominador usó logsumexp en scipy, que es:
out = log(sum(exp(a - a_max), axis=0))
out += a_max
Debido a que no podemos agregar dos probabilidades conjuntas agregando su probabilidad de registro conjunto, debemos salir del espacio logarítmico al espacio de probabilidad. Pero no podemos agregar las dos probabilidades verdaderas porque son demasiado pequeñas y debemos escalarlas y hacer la suma: y poner el resultado de nuevo en el espacio de registro luego vuelva a : en el espacio de registro agregando .∑s={0,1}ejlls−max_jlllog∑s={0,1}ejlls−max_jllmax_jll+log∑s={0,1}ejlls−max_jllmax_jll
Y aquí está la derivación:
log∑s={0,1}ejlls=log∑s={0,1}ejllsemax_jll−max_jll=logemax_jll+log∑s={0,1}ejlls−max_jll=max_jll+log∑s={0,1}ejlls−max_jll
donde es el en el código.max_jlla_max
Una vez que obtenemos tanto el numerador como el denominador en el espacio logarítmico, podemos obtener la probabilidad condicional logarítmica ( ) restando el denominador del numerador : logp(S=1|w1,...wn)
return jll - np.atleast_2d(log_prob_x).T
Espero que ayude.
Referencia:
1. Bernoulli Naive Bayes Classifier
2. Filtrado de spam con Naive Bayes - ¿Qué Naive Bayes?