Esempio di come funziona il trucco log-sum-exp in Naive Bayes


14

Ho letto del trucco log-sum-exp in molti posti (ad esempio qui e qui ) ma non ho mai visto un esempio di come viene applicato in modo specifico al classificatore Naive Bayes (ad esempio con funzionalità discrete e due classi)

Come si eviterebbe esattamente il problema del underflow numerico usando questo trucco?


2
Ci sono diversi esempi del suo uso qui, anche se non necessariamente esplicitamente per ingenui Bayes. Tuttavia, ciò non importa, dato che l'idea del trucco è abbastanza semplice e facilmente adattabile.
Glen_b

È più probabile che il problema sia underflow che overflow.
Henry,

Ti suggerirei di provare una ricerca su underflow e quindi di aggiornare la tua domanda per affrontare in modo più specifico tutto ciò che non è già coperto.
Glen_b

Potresti anche chiarire: questa è l'ingenua Bayes modello Bernoulli? qualcos'altro forse?
Glen_b

Vedi l'esempio qui , proprio in fondo (appena prima di 'Vedi anche' dove prendono i log; esponenziale su entrambi i lati ma lasciando RHS "così com'è" (come l'esp di una somma di log) sarebbe un esempio del log -sum-exp trucco. Ti dà informazioni sufficienti relative al suo utilizzo in Naive Bayes per porre una domanda più specifica?
Glen_b -Reinstate Monica

Risposte:


26

In

p(Y=C|x)=p(x|Y=C)p(Y=C) k=1|C|p(x|Y=Ck)p(Y=Ck)

sia il denominatore che il numeratore possono diventare molto piccoli, in genere perché può essere vicino a 0 e moltipliciamo molti di loro l'uno con l'altro. Per evitare underflow, si può semplicemente prendere il registro del numeratore, ma è necessario utilizzare il trucco log-sum-exp per il denominatore.p(xi|Ck)


Più specificamente, al fine di prevenire underflow:

  • Se ci interessa solo sapere a quale classe l'input molto probabilmente appartiene alla regola di decisione massima a posteriori (MAP), non lo facciamo dobbiamo applicare il trucco log-sum-exp, dal momento che non dobbiamo calcolare il denominatore in quel caso. Per il numeratore si può semplicemente prendere il registro per evitare underflow: . Più specificamente:( x = x 1 , ... , x n ) l o g ( p ( x | Y = C ) p ( Y = C ) )(y^)(x=x1,,xn)log(p(x|Y=C)p(Y=C))

    y^=argmaxk{1,,|C|}p(Ck|x1,,xn)=argmaxk{1,,|C|} p(Ck)i=1np(xi|Ck)

    che diventa dopo aver preso il registro:

y^=argmaxk{1,,|C|}log(p(Ck|x1,,xn))=argmaxk{1,,|C|}log( p(Ck)i=1np(xi|Ck))=argmaxk{1,,|C|}(log(p(Ck))+ i=1nlog(p(xi|Ck)))
  • Se vogliamo calcolare la probabilità di classe , dovremo calcolare il denominatore:p(Y=C|x)

    log(p(Y=C|x))=log(p(x|Y=C)p(Y=C) k=1|C|p(x|Y=Ck)p(Y=Ck))=log(p(x|Y=C)p(Y=C)numerator)log( k=1|C|p(x|Y=Ck)p(Y=Ck)denominator)

    L'elemento potrebbe essere underflow perché può essere molto piccolo: è lo stesso problema del numeratore, ma questa volta abbiamo una somma all'interno del logaritmo, che ci impedisce di trasformare (può essere vicino a 0) in (negativo e non più vicino a 0, poiché ). Per aggirare questo problema, possiamo usare il fatto che per ottenere:log( k=1|C|p(x|Y=Ck)p(Y=Ck))p(xi|Ck)p(xi|Ck)log(p(xi|Ck))0p(xi|Ck)1p(xi|Ck)=exp(log(p(xi|Ck)))

    log( k=1|C|p(x|Y=Ck)p(Y=Ck))=log( k=1|C|exp(log(p(x|Y=Ck)p(Y=Ck))))

    A quel punto, sorge un nuovo problema: potrebbe essere abbastanza negativo, il che implica che può diventare molto vicino a 0, ovvero underflow. Qui è dove usiamo il trucco log-sum-exp :log(p(x|Y=Ck)p(Y=Ck))exp(log(p(x|Y=Ck)p(Y=Ck)))

    logkeak=logkeakeAA=A+logkeakA

    con:

    • ak=log(p(x|Y=Ck)p(Y=Ck)) ,
    • A=maxk{1,,|C|}ak.

    Possiamo vedere che l'introduzione della variabile evita i underflow. Ad esempio con , abbiamo:Ak=2,a1=245,a2=255

    • exp(a1)=exp(245)=3.96143×10107
    • exp(a2)=exp(255)=1.798486×10111

    Usando il trucco log-sum-exp evitiamo il underflow, con : A=max(245,255)=245logkeak=logkeakeAA=A+logkeakA=245+logkeak+245=245+log(e245+245+e255+245)=245+log(e0+e10)

    Abbiamo evitato il underflow poiché è molto più lontano da 0 di o .e103.96143×101071.798486×10111


2

Supponiamo di voler identificare da quale dei due database è più probabile che abbia generato una frase (ad esempio, da quale romanzo è più probabile che questa frase provenga). Potremmo assumere l'indipendenza delle parole condizionate dal database (ipotesi Naive Bayes).

Ora cerca il secondo link che hai pubblicato. Là rappresenterebbe la probabilità congiunta di osservare la frase data un database e gli s rappresenterebbero la probabilità di osservare ciascuna delle parole nella frase.aebt


1

Da questa risposta possiamo vedere che il numero più piccolo in Python (prendilo per esempio) è 5e-324dovuto 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 classescome richiesto:

p(S=1|w1,...wn)=p(S=1)i=1np(wi|S=1) s={0,1}p(S=s)i=1np(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)1p(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)i=1np(wi|S=s)p(wi|S=1)1p(wi|S=1)i50005e3240/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}ejllsmax_jlllogs={0,1}ejllsmax_jllmax_jll+logs={0,1}ejllsmax_jllmax_jll

Ed ecco la derivazione:

logs={0,1}ejlls=logs={0,1}ejllsemax_jllmax_jll=logemax_jll+logs={0,1}ejllsmax_jll=max_jll+logs={0,1}ejllsmax_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?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.