Widzimy z tej odpowiedzi, że najmniejsza liczba w Pythonie (tylko weźmy na przykład) 5e-324
wynika z IEEE754 , a przyczyna sprzętowa dotyczy również innych języków.
In [2]: np.nextafter(0, 1)
Out[2]: 5e-324
A każda liczba zmienna mniejsza niż ta prowadziłaby do 0.
In [3]: np.nextafter(0, 1)/2
Out[3]: 0.0
Zobaczmy funkcję Naive Bayes with discrete features and two classes
zgodnie z wymaganiami:
p(S=1|w1,...wn)=p(S=1)∏ni=1p(wi|S=1) ∑s={0,1}p(S=s)∏ni=1p(wi|S=s)
Pozwólcie, że utworzę tę funkcję przez proste zadanie poniżej NLP.
Postanawiamy wykryć, czy nadchodzący e-mail jest spamem ( ), czy nie spamem ( ), i dysponujemy słownikiem słów o wielkości 5000 ( ), a jedynym problemem jest to, czy pojawi się słowo ( ) ( ) w e-mailu lub nie ( ) dla uproszczenia ( Bernoulli naiwny 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)
Widzimy, że byłoby bardzo małe ze względu na prawdopodobieństwo (oba i będzie między 0 a 1) w , a zatem jesteśmy pewni, że produkt będzie mniejszy niż i otrzymamy po prostu .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
Potem pojawia się problem: jak obliczyć prawdopodobieństwo, że wiadomość e-mail jest spamem ? Lub jak obliczyć licznik i mianownik?p(S=1|w1,...wn)
Oficjalne wdrożenie możemy zobaczyć w 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
Dla licznika przekształcił iloczyn prawdopodobieństwa w sumę prawdopodobieństwa logarytmicznego, a dla mianownika użył logsumexp w scipy, który jest:
out = log(sum(exp(a - a_max), axis=0))
out += a_max
Ponieważ nie możemy dodać dwóch wspólnych prawdopodobieństw, dodając prawdopodobieństwo wspólnego dziennika, i powinniśmy wyjść z przestrzeni dziennika do przestrzeni prawdopodobieństwa. Ale nie możemy dodać dwóch prawdziwych prawdopodobieństw, ponieważ są one za małe i powinniśmy je przeskalować i dodać: i odłożyć wynik z powrotem do przestrzeni logów a następnie przeskaluj ponownie: w przestrzeni dziennika, dodając .∑s={0,1}ejlls−max_jlllog∑s={0,1}ejlls−max_jllmax_jll+log∑s={0,1}ejlls−max_jllmax_jll
A oto pochodna:
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
gdzie jest w kodzie.max_jlla_max
Gdy otrzymamy zarówno licznik, jak i mianownik w przestrzeni dziennika, możemy uzyskać prawdopodobieństwo warunkowe dziennika ( ), odejmując mianownik od licznika : logp(S=1|w1,...wn)
return jll - np.atleast_2d(log_prob_x).T
Mam nadzieję, że to pomaga.
Odniesienie:
1. Klasyfikator Bernoulliego naiwnego Bayesa
2. Filtrowanie spamu za pomocą Naive Bayesa - Który Naive Bayesa?