Podemos ver nesta resposta que o menor número em Python (apenas por exemplo) se 5e-324
deve ao IEEE754 , e a causa do hardware se aplica a outros idiomas também.
In [2]: np.nextafter(0, 1)
Out[2]: 5e-324
E qualquer flutuação menor que isso levaria a 0.
In [3]: np.nextafter(0, 1)/2
Out[3]: 0.0
E vamos ver a função do Naive Bayes with discrete features and two classes
conforme necessário:
p(S=1|w1,...wn)=p(S=1)∏ni=1p(wi|S=1) ∑s={0,1}p(S=s)∏ni=1p(wi|S=s)
Permita-me instanciar essa função com uma simples tarefa de PNL abaixo.
Decidimos detectar se o e-mail a chegar é spam ( ) ou não ( ) e temos um vocabulário de palavras de 5.000 em tamanho ( ) e a única preocupação é se ocorrer uma palavra ( ) ( ) no e-mail ou não ( ) por simplicidade ( Bernoulli ingênuo 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 seria muito pequeno devido às probabilidades (ambos e estaria entre 0 e 1) em e, portanto, temos certeza de que o produto seria menor que e obtemos .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
Em seguida, surge o problema: como podemos calcular a probabilidade do email ser um spam ? Ou como podemos calcular o numerador e o denominador?p(S=1|w1,...wn)
Podemos ver a implementação oficial no 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 o numerador, converteu o produto de probabilidades na soma da probabilidade de log e, para o denominador, usou o logsumexp em scipy, que é:
out = log(sum(exp(a - a_max), axis=0))
out += a_max
Como não podemos adicionar duas probabilidades conjuntas adicionando a probabilidade de log conjunto, devemos sair do espaço de log para o espaço de probabilidade. Mas não podemos adicionar as duas probabilidades verdadeiras porque elas são muito pequenas e devemos escalá-las e fazer a adição: e retornar o resultado no espaço do log seguida, redimensione-o novamente: no espaço de log adicionando o .∑s={0,1}ejlls−max_jlllog∑s={0,1}ejlls−max_jllmax_jll+log∑s={0,1}ejlls−max_jllmax_jll
E aqui está a derivação:
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
onde é o no código.max_jlla_max
Depois de obter o numerador e o denominador no espaço do log, podemos obter a probabilidade condicional do log ( ) subtraindo o denominador do numerador : logp(S=1|w1,...wn)
return jll - np.atleast_2d(log_prob_x).T
Espero que ajude.
Referência:
1. Classificador Bernoulli Naive Bayes
2. Filtragem de Spam com Naive Bayes - Que Naive Bayes?