ตัวอย่างการหลอกลวง log-sum-exp ใน Naive Bayes


14

ฉันได้อ่านเกี่ยวกับเคล็ดลับการบันทึกผลรวมในหลายสถานที่ (เช่นที่นี่และที่นี่ ) แต่ไม่เคยเห็นตัวอย่างของวิธีการใช้งานเฉพาะกับตัวจําแนก Naive Bayes (เช่นด้วยคุณสมบัติแยกและสองคลาส)

เราจะหลีกเลี่ยงปัญหาอันเดอร์โฟลว์ที่เป็นตัวเลขโดยใช้เคล็ดลับนี้ได้อย่างไร?


2
มีหลายตัวอย่างของการใช้งานที่นี่ แต่ไม่จำเป็นต้องชัดเจนสำหรับเบย์ไร้เดียงสา อย่างไรก็ตามนั่นแทบจะไม่สำคัญเนื่องจากความคิดเรื่องกลอุบายนั้นค่อนข้างตรงไปตรงมาและสามารถปรับเปลี่ยนได้อย่างง่ายดาย
Glen_b -Reinstate Monica

ปัญหามีแนวโน้มที่จะ underflow มากกว่าล้น
เฮนรี่

ฉันขอแนะนำให้คุณลองค้นหาunderflowจากนั้นอัปเดตคำถามของคุณเพื่อระบุสิ่งที่ไม่ได้กล่าวถึงแล้วโดยเฉพาะ
Glen_b -Reinstate Monica

คุณช่วยอธิบายได้ไหม - นี่คือเบอนูลี่ - เบย์ไร้เดียงสานางแบบ? อย่างอื่นอาจจะ?
Glen_b -Reinstate Monica

ดูตัวอย่างที่นี่ที่ด้านล่าง (ก่อนหน้า 'เห็นด้วย' โดยที่พวกเขาใช้บันทึกการยกกำลังทั้งสองด้าน แต่การทิ้ง RHS "ตาม - คือ" (เนื่องจาก exp ของผลรวมของบันทึก) จะเป็นตัวอย่างของบันทึก เคล็ดลับ -sum-exp ที่ให้ข้อมูลที่เพียงพอเกี่ยวกับการใช้งานใน Naive Bayes เพื่อถามคำถามที่เฉพาะเจาะจงมากกว่านี้หรือไม่
Glen_b -Reinstate Monica

คำตอบ:


26

ใน

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

ทั้งตัวส่วนและตัวเศษอาจเล็กมากโดยทั่วไปแล้วเนื่องจากสามารถใกล้เคียงกับ 0 และเราคูณพวกมันเข้าด้วยกัน เพื่อป้องกันไม่ให้อันเดอร์โฟล์วสามารถใช้บันทึกของตัวเศษได้ แต่ต้องใช้เคล็ดลับการบันทึกผลรวมสำหรับตัวส่วนp(xi|Ck)


โดยเฉพาะอย่างยิ่งเพื่อป้องกันไม่ให้มีอันเดอร์โฟลว์:

  • ถ้าเราจะดูแลเกี่ยวกับการรู้ซึ่งระดับอินพุต( x = x 1 , ... , x n )ส่วนใหญ่จะเป็นของที่มีค่าสูงสุดที่กฎการตัดสินใจ posteriori (MAP) เราจะได้ไม่ต้องใช้ log- เคล็ดลับผลรวมเนื่องจากเราไม่ต้องคำนวณตัวส่วนในกรณีนั้น สำหรับเศษหนึ่งก็สามารถใช้เข้าสู่ระบบเพื่อป้องกันไม่ให้ underflows: L o กรัม( 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)

    ซึ่งกลายเป็นหลังจากการบันทึก:

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)))
  • ถ้าเราต้องการคำนวณความน่าจะเป็นของคลาสเราจะต้องคำนวณตัวส่วน: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)

    บันทึกองค์ประกอบ( | C | k = 1log( k=1|C|p(x|Y=Ck)p(Y=Ck))อาจ underflow เนื่องจากอาจมีขนาดเล็กมาก: มันเป็นปัญหาเดียวกับตัวเศษ แต่คราวนี้เรามีผลบวกภายในลอการิทึมซึ่งทำให้เราไม่สามารถเปลี่ยน p ( x i | C k ได้ ) (สามารถใกล้ถึง 0) ในบันทึก( p ( x ( i | C k ) )) (ลบและไม่ใกล้กับ 0 อีกต่อไปตั้งแต่0 p ( x i | C k ) 1p(xi|Ck)p(xi|Ck)log(p(xi|Ck))0p(xi|Ck)1) ในการหลีกเลี่ยงปัญหานี้เราสามารถใช้ข้อเท็จจริงที่ว่าเพื่อรับ:p(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))))

    ณ จุดนั้นมีปัญหาใหม่เกิดขึ้น: อาจเป็นค่าลบซึ่งหมายถึงexp ( log ( p ( x | Y = C k ) p ) ( Y = C k ) ) )อาจใกล้เคียงกับ 0 มากเช่นอันเดอร์โฟล์ นี่คือที่เราใช้เคล็ดลับการบันทึกผลรวม :log(p(x|Y=Ck)p(Y=Ck))exp(log(p(x|Y=Ck)p(Y=Ck)))

    logkeak=logkeakeAA=A+logkeakA

    ด้วย:

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

    เราจะเห็นได้ว่าการแนะนำตัวแปรหลีกเลี่ยงอันเดอร์โฟลว์ เช่นกับเรามี:k = 2 , a 1 = - 245 , a 2 = - 255Ak=2,a1=245,a2=255

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

    การใช้เคล็ดลับ log-sum-exp เราหลีกเลี่ยง underflow ด้วย : A=max(245,255)=245logkeak=logkeakeAA=A+logkeakA=245+logkeak+245=245+log(e245+245+e255+245)=245+log(e0+e10)

    เราหลีกเลี่ยงอันเดอร์โฟล์เนื่องจาก อยู่ห่างจาก 0 มากกว่าหรือมากe103.96143×101071.798486×10111


2

สมมติว่าเราต้องการระบุว่าฐานข้อมูลใดในสองฐานข้อมูลมีแนวโน้มที่จะสร้างวลี (ตัวอย่างเช่นนวนิยายชนิดใดที่เป็นวลีนี้มีแนวโน้มที่จะมาจาก) เราสามารถสมมติความเป็นอิสระของคำที่มีเงื่อนไขบนฐานข้อมูล (สมมติฐานของ Naive Bayes)

ตอนนี้ค้นหาลิงค์ที่สองที่คุณโพสต์ มีจะเป็นตัวแทนของความน่าจะเป็นร่วมกันของการสังเกตประโยคที่กำหนดฐานข้อมูลและ s จะเป็นตัวแทนของความน่าจะเป็นในการสังเกตแต่ละคำในประโยคaebt


1

เราสามารถเห็นได้จากคำตอบนี้ว่าจำนวนที่น้อยที่สุดใน 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

และมาดูฟังก์ชั่นของ Naive Bayes with discrete features and two classesตามที่คุณต้องการ:

p(S=1|w1,...wn)=p(S=1)i=1np(wi|S=1) s={0,1}p(S=s)i=1np(wi|S=s)

ให้ฉันยกตัวอย่างฟังก์ชั่นนั้นโดยงานร้อง NLP ง่าย

เราตัดสินใจที่จะตรวจสอบว่าอีเมลที่เข้ามาเป็นสแปม ( ) หรือไม่เป็นสแปม ( ) และเรามีคำศัพท์ที่มีขนาด 5,000 คำ ( ) และข้อกังวลเดียวคือถ้ามีคำ ( ) เกิดขึ้น ( ) ในอีเมลหรือไม่ ( ) เพื่อความเรียบง่าย ( 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)

เราจะเห็นว่าจะมีขนาดเล็กมากเนื่องจากความน่าจะเป็น (ทั้งและจะอยู่ระหว่าง 0 และ 1) ในและด้วยเหตุนี้เรามั่นใจว่าสินค้าจะมีขนาดเล็กกว่าและเราก็จะได้รับ0/0p(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

แล้วปัญหาที่เกิดขึ้น: วิธีการที่เราสามารถคำนวณความน่าจะเป็นของอีเมลที่เป็นสแปม ? หรือเราจะคำนวณตัวเศษและส่วนได้อย่างไร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

สำหรับตัวเศษมันจะแปลงผลคูณของความน่าจะเป็นผลรวมของความน่าจะเป็นของบันทึกและสำหรับตัวส่วนที่มันใช้งานlogumexp ใน scipyซึ่งก็คือ:

out = log(sum(exp(a - a_max), axis=0))
out += a_max

เนื่องจากเราไม่สามารถเพิ่มความน่าจะเป็นร่วมสองข้อด้วยการเพิ่มโอกาสในการบันทึกร่วมและเราควรออกจากพื้นที่บันทึกไปยังพื้นที่ความน่าจะเป็น แต่เราไม่สามารถเพิ่มความน่าจะเป็นที่แท้จริงทั้งสองได้เนื่องจากมันมีขนาดเล็กเกินไปและเราควรปรับขนาดและทำการเพิ่ม: และนำผลลัพธ์กลับมา ในพื้นที่บันทึกจากนั้นให้ขายอีกครั้ง:ในพื้นที่การบันทึกโดยการเพิ่มสูงสุดs={0,1}ejllsmax_jlllogs={0,1}ejllsmax_jllmax_jll+logs={0,1}ejllsmax_jllmax_jll

และนี่คือที่มา:

logs={0,1}ejlls=logs={0,1}ejllsemax_jllmax_jll=logemax_jll+logs={0,1}ejllsmax_jll=max_jll+logs={0,1}ejllsmax_jll

โดยที่คือในรหัสmax_jlla_max

เมื่อเราได้รับทั้งเศษและส่วนในพื้นที่บันทึกเราจะได้รับความน่าจะเป็นตามเงื่อนไขของบันทึก ( ) โดยการลบตัวส่วนจากเศษ : logp(S=1|w1,...wn)

return jll - np.atleast_2d(log_prob_x).T

หวังว่าจะช่วย

การอ้างอิง:
1. ตัวแยกประเภท Bernoulli Naive Bayes
2. การกรองสแปมด้วย Naive Bayes - Naive Bayes ใด?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.