Da questa risposta possiamo vedere che il numero più piccolo in Python (prendilo per esempio) è 5e-324
dovuto all'IEEE754 , e la causa dell'hardware si applica anche ad altre lingue.
In [2]: np.nextafter(0, 1)
Out[2]: 5e-324
E qualsiasi galleggiante più piccolo di quello porterebbe a 0.
In [3]: np.nextafter(0, 1)/2
Out[3]: 0.0
E vediamo la funzione di Naive Bayes with discrete features and two classes
come richiesto:
p(S=1|w1,...wn)=p(S=1)∏ni=1p(wi|S=1) ∑s={0,1}p(S=s)∏ni=1p(wi|S=s)
Consentitemi di creare un'istanza di tale funzione tramite un semplice muggito di attività NLP.
Decidiamo di rilevare se l'email in arrivo è spam ( ) o no spam ( ) e abbiamo un vocabolario di parole di 5.000 dimensioni ( ) e l'unica preoccupazione è se si verifica una parola ( ) ( ) nell'e-mail o meno ( ) per semplicità ( Bernoulli naive 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)
Possiamo vedere che sarebbe molto piccolo a causa delle probabilità (sia che sarebbe tra 0 e 1) in , e quindi siamo sicuri che il prodotto sarebbe più piccolo di e otterremo solo .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
Quindi sorge il problema: come possiamo calcolare la probabilità che l'e-mail sia uno spam ? O come possiamo calcolare il numeratore e il denominatore?p(S=1|w1,...wn)
Possiamo vedere l'implementazione ufficiale in 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
Per il numeratore ha convertito il prodotto delle probabilità nella somma della probabilità di log e per il denominatore ha usato il logsumexp in scipy che è:
out = log(sum(exp(a - a_max), axis=0))
out += a_max
Perché non possiamo aggiungere due probabilità congiunte aggiungendo la sua probabilità di registro comune e dovremmo uscire dallo spazio del registro allo spazio delle probabilità. Ma non possiamo aggiungere le due vere probabilità perché sono troppo piccole e dovremmo ridimensionarle e fare l'aggiunta: e rimettere il risultato nello spazio log quindi ridimensionalo nuovamente: nello spazio di registro aggiungendo .∑s={0,1}ejlls−max_jlllog∑s={0,1}ejlls−max_jllmax_jll+log∑s={0,1}ejlls−max_jllmax_jll
Ed ecco la derivazione:
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
dove è la nel codice.max_jlla_max
Una volta che abbiamo sia il numeratore che il denominatore nello spazio del registro, possiamo ottenere la probabilità condizionata del registro ( ) sottraendo il denominatore dal numeratore : logp(S=1|w1,...wn)
return jll - np.atleast_2d(log_prob_x).T
Spero possa aiutare.
Riferimento:
1. Classificazione di Bernoulli Naive Bayes
2. Filtro antispam con Naive Bayes - Quali Naive Bayes?