Из этого ответа мы можем видеть, что наименьшее число в Python (просто возьмем его, например) 5e-324
связано с IEEE754 , а аппаратная причина относится и к другим языкам.
In [2]: np.nextafter(0, 1)
Out[2]: 5e-324
И любое число с плавающей запятой меньше этого приведет к 0.
In [3]: np.nextafter(0, 1)/2
Out[3]: 0.0
И давайте посмотрим на функцию наивного Байеса, with discrete features and two classes
как вам нужно:
p(S=1|w1,...wn)=p(S=1)∏ni=1p(wi|S=1) ∑s={0,1}p(S=s)∏ni=1p(wi|S=s)
Позвольте мне описать эту функцию простой задачей НЛП ниже.
Мы решили определить, является ли приходящее письмо спамом ( ) или не спамом ( ), и у нас есть словарный запас размером 5000 слов ( ), и единственное беспокойство вызывает то, что слово ( ) встречается ( ) в электронном письме или нет ( ) для простоты ( Бернулли наивный Байес ).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)
Мы можем видеть, что будет очень маленьким из-за вероятностей (оба и будет между 0 и 1) в , и, следовательно, мы уверены, что произведение будет меньше и мы просто получим .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
Тогда возникает проблема: как мы можем вычислить вероятность того, что письмо является спамом ? Или как мы можем вычислить числитель и знаменатель?p(S=1|w1,...wn)
Мы можем увидеть официальную реализацию в 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
Для числителя он преобразовал произведение вероятностей в сумму логарифмического правдоподобия, а для знаменателя он использовал logsumexp в scipy, который:
out = log(sum(exp(a - a_max), axis=0))
out += a_max
Потому что мы не можем добавить две совместные вероятности, добавив их совместную логарифмическую вероятность, и мы должны выйти из логарифмического пространства в вероятностное пространство. Но мы не можем добавить две истинные вероятности, потому что они слишком малы, и мы должны масштабировать их и сделать сложение: и вернуть результат обратно в пространство журнала затем измените его масштаб: в пространстве журнала, добавив .∑s={0,1}ejlls−max_jlllog∑s={0,1}ejlls−max_jllmax_jll+log∑s={0,1}ejlls−max_jllmax_jll
А вот и вывод:
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
где - это в коде.max_jlla_max
Как только мы получим как числитель, так и знаменатель в лог-пространстве, мы можем получить условную вероятность ( ), вычитая знаменатель из числителя : logp(S=1|w1,...wn)
return jll - np.atleast_2d(log_prob_x).T
Надеюсь, это поможет.
Ссылка:
1. Наивный байесовский классификатор Бернулли
2. Фильтрация спама наивным байесовским фильтром - какие наивные байесовские?