ตัวอย่างเชิงตัวเลขเพื่อทำความเข้าใจเกี่ยวกับความคาดหวังสูงสุด


117

ฉันพยายามเข้าใจอัลกอริธึม EM อย่างดีเพื่อให้สามารถใช้งานและใช้งานได้ ฉันใช้เวลาทั้งวันในการอ่านทฤษฎีและกระดาษที่ EM ใช้ในการติดตามอากาศยานโดยใช้ข้อมูลตำแหน่งที่มาจากเรดาร์ สุจริตฉันไม่คิดว่าฉันเข้าใจความคิดพื้นฐานทั้งหมด ใครบางคนสามารถชี้ให้ฉันเป็นตัวอย่างของตัวเลขที่แสดงการวนซ้ำสองสาม (3-4) ของ EM สำหรับปัญหาที่ง่ายกว่า (เช่นการประมาณค่าพารามิเตอร์ของการแจกแจงแบบเกาส์หรือลำดับของชุดไซน์หรือปรับเส้นตรง)

แม้ว่าใครบางคนสามารถชี้ให้ฉันเห็นชิ้นส่วนของรหัส (ด้วยข้อมูลสังเคราะห์) ฉันสามารถลองผ่านรหัสได้


1
k-หมายถึง em มาก แต่มีความแปรปรวนคงที่และค่อนข้างง่าย
EngrStudent

2
@ arjsgh21 คุณช่วยกรุณาโพสต์กระดาษที่กล่าวถึงเกี่ยวกับเครื่องบิน? ฟังดูน่าสนใจมาก ขอบคุณ
Wakan Tanka

1
มีแบบฝึกหัดออนไลน์ที่อ้างว่าให้ความเข้าใจทางคณิตศาสตร์อย่างชัดเจนเกี่ยวกับอัลกอริทึม Em "EM Demystified: บทช่วยสอนการเพิ่มความคาดหวัง" อย่างไรก็ตามตัวอย่างนั้นแย่มากที่มันทำให้ไม่สามารถเข้าใจได้
ชามิเซนผู้เชี่ยวชาญ

คำตอบ:


98

นี่เป็นสูตรการเรียนรู้ EM ด้วยตัวอย่างที่ปฏิบัติได้จริงและในความคิดของฉัน: 'Coin-Toss' ที่ใช้งานง่ายมาก:

  1. อ่านบทความสอนสั้น ๆ ของEMโดย Do and Batzoglou นี่คือสคีมาที่อธิบายตัวอย่างการโยนเหรียญ:

    ป้อนคำอธิบายรูปภาพที่นี่

  2. คุณอาจมีเครื่องหมายคำถามในหัวของคุณโดยเฉพาะเกี่ยวกับความน่าจะเป็นที่มาจากขั้นตอนการคาดหวัง โปรดดูที่คำอธิบายในการแลกเปลี่ยนสแต็คณิตศาสตร์นี้หน้า

  3. ดู / เรียกใช้โค้ดนี้ที่ฉันเขียนใน Python ซึ่งจำลองวิธีแก้ปัญหาของ coin-toss ในเอกสารการสอน EM ของรายการ 1:

    import numpy as np
    import math
    import matplotlib.pyplot as plt
    
    ## E-M Coin Toss Example as given in the EM tutorial paper by Do and Batzoglou* ##
    
    def get_binomial_log_likelihood(obs,probs):
        """ Return the (log)likelihood of obs, given the probs"""
        # Binomial Distribution Log PDF
        # ln (pdf)      = Binomial Coeff * product of probabilities
        # ln[f(x|n, p)] =   comb(N,k)    * num_heads*ln(pH) + (N-num_heads) * ln(1-pH)
    
        N = sum(obs);#number of trials  
        k = obs[0] # number of heads
        binomial_coeff = math.factorial(N) / (math.factorial(N-k) * math.factorial(k))
        prod_probs = obs[0]*math.log(probs[0]) + obs[1]*math.log(1-probs[0])
        log_lik = binomial_coeff + prod_probs
    
        return log_lik
    
    # 1st:  Coin B, {HTTTHHTHTH}, 5H,5T
    # 2nd:  Coin A, {HHHHTHHHHH}, 9H,1T
    # 3rd:  Coin A, {HTHHHHHTHH}, 8H,2T
    # 4th:  Coin B, {HTHTTTHHTT}, 4H,6T
    # 5th:  Coin A, {THHHTHHHTH}, 7H,3T
    # so, from MLE: pA(heads) = 0.80 and pB(heads)=0.45
    
    # represent the experiments
    head_counts = np.array([5,9,8,4,7])
    tail_counts = 10-head_counts
    experiments = zip(head_counts,tail_counts)
    
    # initialise the pA(heads) and pB(heads)
    pA_heads = np.zeros(100); pA_heads[0] = 0.60
    pB_heads = np.zeros(100); pB_heads[0] = 0.50
    
    # E-M begins!
    delta = 0.001  
    j = 0 # iteration counter
    improvement = float('inf')
    while (improvement>delta):
        expectation_A = np.zeros((len(experiments),2), dtype=float) 
        expectation_B = np.zeros((len(experiments),2), dtype=float)
        for i in range(0,len(experiments)):
            e = experiments[i] # i'th experiment
              # loglikelihood of e given coin A:
            ll_A = get_binomial_log_likelihood(e,np.array([pA_heads[j],1-pA_heads[j]])) 
              # loglikelihood of e given coin B
            ll_B = get_binomial_log_likelihood(e,np.array([pB_heads[j],1-pB_heads[j]])) 
    
              # corresponding weight of A proportional to likelihood of A 
            weightA = math.exp(ll_A) / ( math.exp(ll_A) + math.exp(ll_B) ) 
    
              # corresponding weight of B proportional to likelihood of B
            weightB = math.exp(ll_B) / ( math.exp(ll_A) + math.exp(ll_B) ) 
    
            expectation_A[i] = np.dot(weightA, e) 
            expectation_B[i] = np.dot(weightB, e)
    
        pA_heads[j+1] = sum(expectation_A)[0] / sum(sum(expectation_A)); 
        pB_heads[j+1] = sum(expectation_B)[0] / sum(sum(expectation_B)); 
    
        improvement = ( max( abs(np.array([pA_heads[j+1],pB_heads[j+1]]) - 
                        np.array([pA_heads[j],pB_heads[j]]) )) )
        j = j+1
    
    plt.figure();
    plt.plot(range(0,j),pA_heads[0:j], 'r--')
    plt.plot(range(0,j),pB_heads[0:j])
    plt.show()
    

2
@Zhubarb: คุณช่วยอธิบายเงื่อนไขการเลิกลูป (เช่นเพื่อพิจารณาว่าอัลกอริทึมลู่เข้าได้ไหม)? ตัวแปร "การปรับปรุง" คำนวณอะไรบ้าง
stackoverflowuser2010

@ stackoverflowuser2010 ปรับปรุงลักษณะที่สองสันดอน: 1) การเปลี่ยนแปลงระหว่างpA_heads[j+1]และpA_heads[j]และ 2) การเปลี่ยนแปลงระหว่างและpB_heads[j+1] pB_heads[j]และต้องใช้การเปลี่ยนแปลงทั้งสองอย่างสูงสุด ตัวอย่างเช่นถ้าDelta_A=0.001และDelta_B=0.02ปรับปรุงจากขั้นตอนjที่จะจะj+1 0.02
Zhubarb

1
@Zhubarb: นั่นเป็นวิธีมาตรฐานสำหรับการคำนวณการลู่เข้าใน EM หรือว่าเป็นสิ่งที่คุณคิดขึ้นมาด้วย? หากเป็นวิธีมาตรฐานคุณช่วยอ้างอิงการอ้างอิงได้ไหม
stackoverflowuser2010

นี่คือการอ้างอิงเกี่ยวกับการลู่เข้าของ EM ฉันเขียนรหัสเมื่อไม่นานมานี้ดังนั้นจำไม่ได้ด้วย ฉันเชื่อว่าสิ่งที่คุณเห็นในรหัสคือเกณฑ์การบรรจบกันของฉันสำหรับกรณีนี้โดยเฉพาะ ความคิดที่จะหยุดการทำซ้ำเมื่อสูงสุดของการปรับปรุงสำหรับ A และ B deltaจะน้อยกว่า
Zhubarb

1
สุดยอดไม่มีอะไรที่เหมือนกับรหัสที่ดีที่จะอธิบายว่าย่อหน้าใดที่ไม่สามารถแปลได้
jon_simon

63

ดูเหมือนว่าคำถามของคุณมีสองส่วน: แนวคิดพื้นฐานและตัวอย่างที่เป็นรูปธรรม ฉันจะเริ่มต้นด้วยแนวคิดพื้นฐานแล้วเชื่อมโยงไปยังตัวอย่างที่ด้านล่าง


ABBA

กรณีที่พบบ่อยที่สุดที่คนจัดการคือการกระจายตัวแบบผสม สำหรับตัวอย่างเรามาดูรูปแบบผสมแบบเกาส์ง่าย ๆ :

คุณมีการแจกแจงเกาส์เซียนแบบ univariate ที่ต่างกันสองแบบด้วยค่าเฉลี่ยและความแปรปรวนของหน่วย

คุณมีจุดข้อมูลจำนวนมาก แต่คุณไม่แน่ใจว่าจุดใดมาจากการกระจายและคุณไม่แน่ใจเกี่ยวกับความหมายของการแจกแจงสองแบบ

และตอนนี้คุณติดอยู่:

  • หากคุณรู้วิธีการที่แท้จริงคุณสามารถคิดได้ว่าจุดข้อมูลใดมาจากไหนเสียน ตัวอย่างเช่นหากจุดข้อมูลมีค่าสูงมากอาจมาจากการกระจายที่มีค่าเฉลี่ยสูงกว่า แต่คุณไม่รู้ว่าหมายถึงอะไรดังนั้นสิ่งนี้จะไม่ทำงาน

  • หากคุณรู้ว่าการกระจายแต่ละจุดมาจากไหนคุณสามารถประมาณค่าการแจกแจงสองค่าได้โดยใช้ค่าเฉลี่ยตัวอย่างของคะแนนที่เกี่ยวข้อง แต่คุณไม่ทราบว่าจะให้คะแนนใดกับการแจกจ่ายดังนั้นสิ่งนี้จะไม่ได้ผล

ดังนั้นวิธีการทั้งสองดูเหมือนจะไม่ได้ผล: คุณต้องรู้คำตอบก่อนจึงจะสามารถหาคำตอบได้

สิ่งที่ EM ให้คุณทำคือสลับระหว่างสองขั้นตอนที่สามารถจัดการได้แทนการจัดการกระบวนการทั้งหมดในคราวเดียว

คุณจะต้องเริ่มต้นด้วยการเดาเกี่ยวกับสองวิธี (แม้ว่าการคาดเดาของคุณไม่จำเป็นต้องแม่นยำมาก แต่คุณต้องเริ่มต้นที่ไหนสักแห่ง)

หากการเดาของคุณเกี่ยวกับค่าเฉลี่ยนั้นถูกต้องคุณก็จะมีข้อมูลเพียงพอที่จะทำตามขั้นตอนในหัวข้อย่อยแรกของฉันและคุณสามารถกำหนดจุดข้อมูลแต่ละจุดให้กับหนึ่งในสอง Gaussians แม้ว่าเราจะรู้ว่าการคาดเดาของเราผิดไปลองทำสิ่งนี้กัน จากนั้นเมื่อกำหนดการแจกแจงที่กำหนดของแต่ละจุดคุณสามารถรับค่าประมาณใหม่สำหรับวิธีการใช้สัญลักษณ์แสดงหัวข้อย่อยที่สอง ปรากฎว่าทุกครั้งที่คุณวนลูปผ่านสองขั้นตอนนี้คุณจะพัฒนาขอบเขตล่างของความเป็นไปได้ของโมเดล

มันค่อนข้างเจ๋งแล้ว: แม้ว่าคำแนะนำสองข้อในหัวข้อย่อยด้านบนดูเหมือนจะไม่ได้ทำงานเป็นรายบุคคล แต่คุณยังสามารถใช้ร่วมกันเพื่อปรับปรุงแบบจำลองได้ จริงมหัศจรรย์ของ EM คือว่าหลังจากที่ซ้ำพอขอบเขตที่ต่ำจะสูงเพื่อที่จะไม่มีพื้นที่ใดระหว่างมันและสูงสุดในท้องถิ่น เป็นผลให้คุณได้รับโอกาสที่ดีที่สุดในท้องถิ่น

ดังนั้นคุณยังไม่ได้ปรับปรุงโมเดลคุณพบว่าเป็นรุ่นที่ดีที่สุดเท่าที่จะหาได้ด้วยการอัพเดตที่เพิ่มขึ้น


หน้านี้จาก Wikipedia แสดงตัวอย่างที่ซับซ้อนขึ้นเล็กน้อย (Gaussians สองมิติและความแปรปรวนร่วมที่ไม่รู้จัก) แต่แนวคิดพื้นฐานเหมือนกัน นอกจากนี้ยังมีRรหัสที่มีความคิดเห็นดีสำหรับการใช้งานตัวอย่าง

ในรหัสขั้นตอน "ความคาดหวัง" (E-step) สอดคล้องกับสัญลักษณ์แสดงหัวข้อแรกของฉัน: การหาว่าเกาส์เซียนใดรับผิดชอบข้อมูลจุดแต่ละจุดโดยกำหนดพารามิเตอร์ปัจจุบันสำหรับแต่ละเกาส์เซียน ขั้นตอน "การเพิ่มประสิทธิภาพ" (ขั้นตอน M) อัปเดตค่าเฉลี่ยและความแปรปรวนร่วมที่ได้รับมอบหมายเหล่านี้เช่นเดียวกับในหัวข้อย่อยที่สองของฉัน

อย่างที่คุณเห็นในแอนิเมชันการอัปเดตเหล่านี้จะช่วยให้อัลกอริทึมเปลี่ยนจากชุดการประเมินที่แย่มากไปสู่ชุดที่ดีมาก ๆ : ดูเหมือนว่าจะมีกลุ่มเมฆสองจุดอยู่ตรงกลางของการแจกแจงแบบเกาส์ทั้งสอง


13

นี่คือตัวอย่างของการคาดหวังสูงสุด (EM) ที่ใช้ในการประเมินค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐาน รหัสอยู่ใน Python แต่ควรง่ายต่อการติดตามแม้ว่าคุณจะไม่คุ้นเคยกับภาษา

แรงจูงใจสำหรับ EM

จุดสีแดงและสีน้ำเงินที่แสดงด้านล่างนั้นมาจากการแจกแจงแบบปกติสองแบบที่ต่างกันโดยแต่ละค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐาน:

ป้อนคำอธิบายรูปภาพที่นี่

ในการคำนวณการประมาณค่าที่เหมาะสมของพารามิเตอร์ "จริง" และค่าเบี่ยงเบนมาตรฐานสำหรับการแจกแจงสีแดงเราสามารถดูจุดสีแดงและบันทึกตำแหน่งของแต่ละคนได้อย่างง่ายดายจากนั้นใช้สูตรที่คุ้นเคย (และคล้ายกันสำหรับกลุ่มสีน้ำเงิน) .

ตอนนี้ให้พิจารณากรณีที่เรารู้ว่ามีจุดสองกลุ่ม แต่เราไม่สามารถเห็นได้ว่าจุดใดเป็นของกลุ่มใด กล่าวอีกนัยหนึ่งสีถูกซ่อนอยู่:

ป้อนคำอธิบายรูปภาพที่นี่

ไม่ชัดเจนเลยว่าจะแบ่งคะแนนออกเป็นสองกลุ่มอย่างไร ขณะนี้เราไม่สามารถดูตำแหน่งและคำนวณการประมาณค่าพารามิเตอร์ของการแจกแจงสีแดงหรือการกระจายสีฟ้าได้

นี่คือสิ่งที่ EM สามารถนำมาใช้เพื่อแก้ไขปัญหา

ใช้ EM เพื่อประมาณค่าพารามิเตอร์

นี่คือรหัสที่ใช้ในการสร้างคะแนนที่แสดงด้านบน คุณสามารถดูค่าเฉลี่ยจริงและส่วนเบี่ยงเบนมาตรฐานของการแจกแจงปกติที่ได้คะแนนมา ตัวแปรredและblueดำรงตำแหน่งของแต่ละจุดในกลุ่มสีแดงและสีน้ำเงินตามลำดับ:

import numpy as np
from scipy import stats

np.random.seed(110) # for reproducible random results

# set parameters
red_mean = 3
red_std = 0.8

blue_mean = 7
blue_std = 2

# draw 20 samples from normal distributions with red/blue parameters
red = np.random.normal(red_mean, red_std, size=20)
blue = np.random.normal(blue_mean, blue_std, size=20)

both_colours = np.sort(np.concatenate((red, blue)))

ถ้าเราสามารถมองเห็นสีของแต่ละจุดที่เราจะพยายามกู้คืนวิธีการและค่าเบี่ยงเบนมาตรฐานโดยใช้ฟังก์ชั่นห้องสมุด:

>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195

แต่เนื่องจากสีถูกซ่อนไว้จากเราเราจะเริ่มกระบวนการ EM ...

อันดับแรกเราแค่เดาค่าสำหรับพารามิเตอร์ของแต่ละกลุ่ม ( ขั้นตอนที่ 1 ) การเดาเหล่านี้ไม่จำเป็นต้องดี:

# estimates for the mean
red_mean_guess = 1.1
blue_mean_guess = 9

# estimates for the standard deviation
red_std_guess = 2
blue_std_guess = 1.7

ป้อนคำอธิบายรูปภาพที่นี่

การคาดเดาที่ไม่ดีพอสมควร - วิธีการดูเหมือนพวกเขาอยู่ไกลจาก "จุดกึ่งกลาง" ของกลุ่มคะแนนใด ๆ

เพื่อดำเนินการกับ EM และปรับปรุงการคาดเดาเหล่านี้เราคำนวณความน่าจะเป็นของแต่ละจุดข้อมูล (โดยไม่คำนึงถึงสีที่เป็นความลับ) ที่ปรากฏภายใต้การเดาเหล่านี้สำหรับค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐาน ( ขั้นตอนที่ 2 )

ตัวแปรboth_coloursเก็บจุดข้อมูลแต่ละจุด ฟังก์ชันstats.normคำนวณความน่าจะเป็นของจุดภายใต้การแจกแจงแบบปกติด้วยพารามิเตอร์ที่กำหนด:

likelihood_of_red = stats.norm(red_mean_guess, red_std_guess).pdf(both_colours)
likelihood_of_blue = stats.norm(blue_mean_guess, blue_std_guess).pdf(both_colours)

ตัวอย่างเช่นสิ่งนี้บอกเราว่าเมื่อเราเดาจุดข้อมูลที่ 1.761 มีแนวโน้มที่จะเป็นสีแดง (0.189) มากกว่าสีน้ำเงิน (0.00003)

เราสามารถเปลี่ยนค่าความน่าจะเป็นทั้งสองนี้ให้เป็นน้ำหนัก ( ขั้นตอนที่ 3 ) เพื่อให้ค่ารวมเป็น 1 ดังนี้:

likelihood_total = likelihood_of_red + likelihood_of_blue

red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total

ด้วยประมาณการปัจจุบันของเราและน้ำหนักที่คำนวณใหม่ของเราตอนนี้เราสามารถคำนวณใหม่อาจจะดีกว่าประมาณการสำหรับพารามิเตอร์ ( ขั้นตอนที่ 4 ) เราต้องการฟังก์ชันสำหรับค่าเฉลี่ยและฟังก์ชันสำหรับส่วนเบี่ยงเบนมาตรฐาน:

def estimate_mean(data, weight):
    return np.sum(data * weight) / np.sum(weight)

def estimate_std(data, weight, mean):
    variance = np.sum(weight * (data - mean)**2) / np.sum(weight)
    return np.sqrt(variance)

สิ่งเหล่านี้มีลักษณะคล้ายกับฟังก์ชันปกติของค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานของข้อมูล ความแตกต่างคือการใช้weightพารามิเตอร์ที่กำหนดน้ำหนักให้กับแต่ละจุดข้อมูล

น้ำหนักนี้เป็นกุญแจสำคัญใน EM ยิ่งน้ำหนักของสีบนจุดข้อมูลมากขึ้นเท่าไหร่จุดข้อมูลก็ยิ่งมีผลต่อการประมาณค่าถัดไปสำหรับพารามิเตอร์ของสีนั้น ท้ายที่สุดสิ่งนี้มีผลในการดึงแต่ละพารามิเตอร์ในทิศทางที่ถูกต้อง

การคาดเดาใหม่คำนวณด้วยฟังก์ชั่นเหล่านี้:

# new estimates for standard deviation
blue_std_guess = estimate_std(both_colours, blue_weight, blue_mean_guess)
red_std_guess = estimate_std(both_colours, red_weight, red_mean_guess)

# new estimates for mean
red_mean_guess = estimate_mean(both_colours, red_weight)
blue_mean_guess = estimate_mean(both_colours, blue_weight)

จากนั้นกระบวนการ EM จะถูกทำซ้ำด้วยการคาดเดาใหม่จากขั้นตอนที่ 2 เป็นต้นไป เราสามารถทำซ้ำขั้นตอนสำหรับการวนซ้ำตามจำนวนที่กำหนด (พูด 20) หรือจนกว่าเราจะเห็นพารามิเตอร์มาบรรจบกัน

หลังจากการทำซ้ำห้าครั้งเราจะเห็นการคาดเดาที่ไม่ดีเริ่มต้นของเราเริ่มดีขึ้น:

ป้อนคำอธิบายรูปภาพที่นี่

หลังจากการวนซ้ำ 20 ครั้งกระบวนการ EM มีการรวมกันมากหรือน้อย:

ป้อนคำอธิบายรูปภาพที่นี่

สำหรับการเปรียบเทียบนี่คือผลลัพธ์ของกระบวนการ EM เมื่อเปรียบเทียบกับค่าที่คำนวณโดยที่ข้อมูลสีไม่ได้ถูกซ่อนอยู่:

          | EM guess | Actual 
----------+----------+--------
Red mean  |    2.910 |   2.802
Red std   |    0.854 |   0.871
Blue mean |    6.838 |   6.932
Blue std  |    2.227 |   2.195

หมายเหตุ: คำตอบนี้ดัดแปลงมาจากคำตอบของฉันในกองมากเกินที่นี่


10

ตามคำตอบของ Zhubarb ฉันได้ใช้เหรียญ Do and Batzoglou "การโยน" ตัวอย่าง EM ใน GNU R โปรดทราบว่าฉันใช้mleฟังก์ชันของstats4แพ็คเกจ - สิ่งนี้ช่วยให้ฉันเข้าใจชัดเจนยิ่งขึ้นว่า EM และ MLE เกี่ยวข้องกันอย่างไร

require("stats4");

## sample data from Do and Batzoglou
ds<-data.frame(heads=c(5,9,8,4,7),n=c(10,10,10,10,10),
    coin=c("B","A","A","B","A"),weight_A=1:5*0)

## "baby likelihood" for a single observation
llf <- function(heads, n, theta) {
  comb <- function(n, x) { #nCr function
    return(factorial(n) / (factorial(x) * factorial(n-x)))
  }
  if (theta<0 || theta >1) { # probabilities should be in [0,1]
    return(-Inf);
  }
  z<-comb(n,heads)* theta^heads * (1-theta)^(n-heads);
  return (log(z))
}

## the "E-M" likelihood function
em <- function(theta_A,theta_B) {
  # expectation step: given current parameters, what is the likelihood
  # an observation is the result of tossing coin A (vs coin B)?
  ds$weight_A <<- by(ds, 1:nrow(ds), function(row) {
    llf_A <- llf(row$heads,row$n, theta_A);
    llf_B <- llf(row$heads,row$n, theta_B);

    return(exp(llf_A)/(exp(llf_A)+exp(llf_B)));
  })

  # maximisation step: given params and weights, calculate likelihood of the sample
  return(- sum(by(ds, 1:nrow(ds), function(row) {
    llf_A <- llf(row$heads,row$n, theta_A);
    llf_B <- llf(row$heads,row$n, theta_B);

    return(row$weight_A*llf_A + (1-row$weight_A)*llf_B);
  })))
}

est<-mle(em,start = list(theta_A=0.6,theta_B=0.5), nobs=NROW(ds))

1
@ user3096626 คุณช่วยอธิบายได้ไหมว่าทำไมในขั้นตอนการเพิ่มประสิทธิภาพสูงสุดที่คุณคูณความน่าจะเป็นของเหรียญ A (แถว $ weight_A) ด้วยความน่าจะเป็นบันทึก (llf_A) มีกฎหรือเหตุผลพิเศษที่เราทำหรือไม่? ฉันหมายถึงว่าคนเราจะเพิ่มความน่าจะเป็นหรือเพิ่มความน่าสนใจ แต่ไม่ได้ปะปนกัน ฉันยังเปิดหัวข้อ
อลีนา

9

ทั้งหมดข้างต้นดูเหมือนแหล่งข้อมูลที่ดี แต่ฉันต้องเชื่อมโยงไปยังตัวอย่างที่ยอดเยี่ยมนี้ มันนำเสนอคำอธิบายที่ง่ายมากสำหรับการค้นหาพารามิเตอร์สำหรับสองบรรทัดของชุดคะแนน บทแนะนำคือโดย Yair Weiss ขณะอยู่ที่ MIT

http://www.cs.huji.ac.il/~yweiss/emTutorial.pdf
http://www.cs.huji.ac.il/~yweiss/tutorials.html


5

คำตอบที่ได้รับจาก Zhubarb นั้นยอดเยี่ยม แต่น่าเสียดายที่มันอยู่ใน Python ด้านล่างนี้เป็นการใช้งาน Java ของอัลกอริทึม EM ที่ดำเนินการในปัญหาเดียวกัน (โพสต์ในบทความโดย Do and Batzoglou, 2008) ฉันได้เพิ่ม printf's ในเอาต์พุตมาตรฐานเพื่อดูว่าพารามิเตอร์มาบรรจบกันได้อย่างไร

thetaA = 0.71301, thetaB = 0.58134
thetaA = 0.74529, thetaB = 0.56926
thetaA = 0.76810, thetaB = 0.54954
thetaA = 0.78316, thetaB = 0.53462
thetaA = 0.79106, thetaB = 0.52628
thetaA = 0.79453, thetaB = 0.52239
thetaA = 0.79593, thetaB = 0.52073
thetaA = 0.79647, thetaB = 0.52005
thetaA = 0.79667, thetaB = 0.51977
thetaA = 0.79674, thetaB = 0.51966
thetaA = 0.79677, thetaB = 0.51961
thetaA = 0.79678, thetaB = 0.51960
thetaA = 0.79679, thetaB = 0.51959
Final result:
thetaA = 0.79678, thetaB = 0.51960

โค้ด Java ด้านล่าง:

import java.util.*;

/*****************************************************************************
This class encapsulates the parameters of the problem. For this problem posed
in the article by (Do and Batzoglou, 2008), the parameters are thetaA and
thetaB, the probability of a coin coming up heads for the two coins A and B.
*****************************************************************************/
class Parameters
{
    double _thetaA = 0.0; // Probability of heads for coin A.
    double _thetaB = 0.0; // Probability of heads for coin B.

    double _delta = 0.00001;

    public Parameters(double thetaA, double thetaB)
    {
        _thetaA = thetaA;
        _thetaB = thetaB;
    }

    /*************************************************************************
    Returns true if this parameter is close enough to another parameter
    (typically the estimated parameter coming from the maximization step).
    *************************************************************************/
    public boolean converged(Parameters other)
    {
        if (Math.abs(_thetaA - other._thetaA) < _delta &&
            Math.abs(_thetaB - other._thetaB) < _delta)
        {
            return true;
        }

        return false;
    }

    public double getThetaA()
    {
        return _thetaA;
    }

    public double getThetaB()
    {
        return _thetaB;
    }

    public String toString()
    {
        return String.format("thetaA = %.5f, thetaB = %.5f", _thetaA, _thetaB);
    }

}


/*****************************************************************************
This class encapsulates an observation, that is the number of heads
and tails in a trial. The observation can be either (1) one of the
observed observations, or (2) an estimated observation resulting from
the expectation step.
*****************************************************************************/
class Observation
{
    double _numHeads = 0;
    double _numTails = 0;

    public Observation(String s)
    {
        for (int i = 0; i < s.length(); i++)
        {
            char c = s.charAt(i);

            if (c == 'H')
            {
                _numHeads++;
            }
            else if (c == 'T')
            {
                _numTails++;
            }
            else
            {
                throw new RuntimeException("Unknown character: " + c);
            }
        }
    }

    public Observation(double numHeads, double numTails)
    {
        _numHeads = numHeads;
        _numTails = numTails;
    }

    public double getNumHeads()
    {
        return _numHeads;
    }

    public double getNumTails()
    {
        return _numTails;
    }

    public String toString()
    {
        return String.format("heads: %.1f, tails: %.1f", _numHeads, _numTails);
    }

}

/*****************************************************************************
This class runs expectation-maximization for the problem posed by the article
from (Do and Batzoglou, 2008).
*****************************************************************************/
public class EM
{
    // Current estimated parameters.
    private Parameters _parameters;

    // Observations from the trials. These observations are set once.
    private final List<Observation> _observations;

    // Estimated observations per coin. These observations are the output
    // of the expectation step.
    private List<Observation> _expectedObservationsForCoinA;
    private List<Observation> _expectedObservationsForCoinB;

    private static java.io.PrintStream o = System.out;

    /*************************************************************************
    Principal constructor.
    @param observations The observations from the trial.
    @param parameters The initial guessed parameters.
    *************************************************************************/
    public EM(List<Observation> observations, Parameters parameters)
    {
        _observations = observations;
        _parameters = parameters;
    }

    /*************************************************************************
    Run EM until parameters converge.
    *************************************************************************/
    public Parameters run()
    {

        while (true)
        {
            expectation();

            Parameters estimatedParameters = maximization();

            o.printf("%s\n", estimatedParameters);

            if (_parameters.converged(estimatedParameters)) {
                break;
            }

            _parameters = estimatedParameters;
        }

        return _parameters;

    }

    /*************************************************************************
    Given the observations and current estimated parameters, compute new
    estimated completions (distribution over the classes) and observations.
    *************************************************************************/
    private void expectation()
    {

        _expectedObservationsForCoinA = new ArrayList<Observation>();
        _expectedObservationsForCoinB = new ArrayList<Observation>();

        for (Observation observation : _observations)
        {
            int numHeads = (int)observation.getNumHeads();
            int numTails = (int)observation.getNumTails();

            double probabilityOfObservationForCoinA=
                binomialProbability(10, numHeads, _parameters.getThetaA());

            double probabilityOfObservationForCoinB=
                binomialProbability(10, numHeads, _parameters.getThetaB());

            double normalizer = probabilityOfObservationForCoinA +
                                probabilityOfObservationForCoinB;

            // Compute the completions for coin A and B (i.e. the probability
            // distribution of the two classes, summed to 1.0).

            double completionCoinA = probabilityOfObservationForCoinA /
                                     normalizer;
            double completionCoinB = probabilityOfObservationForCoinB /
                                     normalizer;

            // Compute new expected observations for the two coins.

            Observation expectedObservationForCoinA =
                new Observation(numHeads * completionCoinA,
                                numTails * completionCoinA);

            Observation expectedObservationForCoinB =
                new Observation(numHeads * completionCoinB,
                                numTails * completionCoinB);

            _expectedObservationsForCoinA.add(expectedObservationForCoinA);
            _expectedObservationsForCoinB.add(expectedObservationForCoinB);
        }
    }

    /*************************************************************************
    Given new estimated observations, compute new estimated parameters.
    *************************************************************************/
    private Parameters maximization()
    {

        double sumCoinAHeads = 0.0;
        double sumCoinATails = 0.0;
        double sumCoinBHeads = 0.0;
        double sumCoinBTails = 0.0;

        for (Observation observation : _expectedObservationsForCoinA)
        {
            sumCoinAHeads += observation.getNumHeads();
            sumCoinATails += observation.getNumTails();
        }

        for (Observation observation : _expectedObservationsForCoinB)
        {
            sumCoinBHeads += observation.getNumHeads();
            sumCoinBTails += observation.getNumTails();
        }

        return new Parameters(sumCoinAHeads / (sumCoinAHeads + sumCoinATails),
                              sumCoinBHeads / (sumCoinBHeads + sumCoinBTails));

        //o.printf("parameters: %s\n", _parameters);

    }

    /*************************************************************************
    Since the coin-toss experiment posed in this article is a Bernoulli trial,
    use a binomial probability Pr(X=k; n,p) = (n choose k) * p^k * (1-p)^(n-k).
    *************************************************************************/
    private static double binomialProbability(int n, int k, double p)
    {
        double q = 1.0 - p;
        return nChooseK(n, k) * Math.pow(p, k) * Math.pow(q, n-k);
    }

    private static long nChooseK(int n, int k)
    {
        long numerator = 1;

        for (int i = 0; i < k; i++)
        {
            numerator = numerator * n;
            n--;
        }

        long denominator = factorial(k);

        return (long)(numerator / denominator);
    }

    private static long factorial(int n)
    {
        long result = 1;
        for (; n >0; n--)
        {
            result = result * n;
        }

        return result;
    }

    /*************************************************************************
    Entry point into the program.
    *************************************************************************/
    public static void main(String argv[])
    {
        // Create the observations and initial parameter guess
        // from the (Do and Batzoglou, 2008) article.

        List<Observation> observations = new ArrayList<Observation>();
        observations.add(new Observation("HTTTHHTHTH"));
        observations.add(new Observation("HHHHTHHHHH"));
        observations.add(new Observation("HTHHHHHTHH"));
        observations.add(new Observation("HTHTTTHHTT"));
        observations.add(new Observation("THHHTHHHTH"));

        Parameters initialParameters = new Parameters(0.6, 0.5);

        EM em = new EM(observations, initialParameters);

        Parameters finalParameters = em.run();

        o.printf("Final result:\n%s\n", finalParameters);
    }
}

5
% Implementation of the EM (Expectation-Maximization)algorithm example exposed on:
% Motion Segmentation using EM - a short tutorial, Yair Weiss, %http://www.cs.huji.ac.il/~yweiss/emTutorial.pdf
% Juan Andrade, jandrader@yahoo.com

clear all
clc

%% Setup parameters
m1 = 2;                 % slope line 1
m2 = 6;                 % slope line 2
b1 = 3;                 % vertical crossing line 1
b2 = -2;                % vertical crossing line 2
x = [-1:0.1:5];         % x axis values
sigma1 = 1;             % Standard Deviation of Noise added to line 1
sigma2 = 2;             % Standard Deviation of Noise added to line 2

%% Clean lines
l1 = m1*x+b1;           % line 1
l2 = m2*x+b2;           % line 2

%% Adding noise to lines
p1 = l1 + sigma1*randn(size(l1));
p2 = l2 + sigma2*randn(size(l2));

%% showing ideal and noise values
figure,plot(x,l1,'r'),hold,plot(x,l2,'b'), plot(x,p1,'r.'),plot(x,p2,'b.'),grid

%% initial guess
m11(1) = -1;            % slope line 1
m22(1) = 1;             % slope line 2
b11(1) = 2;             % vertical crossing line 1
b22(1) = 2;             % vertical crossing line 2

%% EM algorithm loop
iterations = 10;        % number of iterations (a stop based on a threshold may used too)

for i=1:iterations

    %% expectation step (equations 2 and 3)
    res1 = m11(i)*x + b11(i) - p1;
    res2 = m22(i)*x + b22(i) - p2;
    % line 1
    w1 = (exp((-res1.^2)./sigma1))./((exp((-res1.^2)./sigma1)) + (exp((-res2.^2)./sigma2)));

    % line 2
    w2 = (exp((-res2.^2)./sigma2))./((exp((-res1.^2)./sigma1)) + (exp((-res2.^2)./sigma2)));

    %% maximization step  (equation 4)
    % line 1
    A(1,1) = sum(w1.*(x.^2));
    A(1,2) = sum(w1.*x);
    A(2,1) = sum(w1.*x);
    A(2,2) = sum(w1);
    bb = [sum(w1.*x.*p1) ; sum(w1.*p1)];
    temp = A\bb;
    m11(i+1) = temp(1);
    b11(i+1) = temp(2);

    % line 2
    A(1,1) = sum(w2.*(x.^2));
    A(1,2) = sum(w2.*x);
    A(2,1) = sum(w2.*x);
    A(2,2) = sum(w2);
    bb = [sum(w2.*x.*p2) ; sum(w2.*p2)];
    temp = A\bb;
    m22(i+1) = temp(1);
    b22(i+1) = temp(2);

    %% plotting evolution of results
    l1temp = m11(i+1)*x+b11(i+1);
    l2temp = m22(i+1)*x+b22(i+1);
    figure,plot(x,l1temp,'r'),hold,plot(x,l2temp,'b'), plot(x,p1,'r.'),plot(x,p2,'b.'),grid
end

4
คุณสามารถเพิ่มการสนทนาหรือคำอธิบายลงในรหัสดิบได้หรือไม่ มันจะมีประโยชน์สำหรับผู้อ่านหลายคนอย่างน้อยพูดถึงภาษาที่คุณกำลังเขียน
Glen_b

1
@Glen_b - นี่คือ MatLab ฉันสงสัยว่ามันสุภาพอย่างไรที่จะใช้คำอธิบายประกอบรหัส someones อย่างละเอียดมากขึ้นในคำตอบของพวกเขา
EngrStudent

4

ฉันขอแนะนำให้คุณอ่านหนังสือเกี่ยวกับ R โดย Maria L Rizzo หนึ่งในบทที่มีการใช้อัลกอริทึม EM กับตัวอย่างที่เป็นตัวเลข ฉันจำได้ว่าต้องใช้รหัสเพื่อความเข้าใจที่ดีขึ้น

นอกจากนี้ลองดูจากจุดการจัดกลุ่มในจุดเริ่มต้น ออกกำลังกายด้วยมือปัญหาการจัดกลุ่มที่การสังเกต 10 ครั้งนำมาจากความหนาแน่นปกติสองแบบที่แตกต่างกัน สิ่งนี้จะช่วยได้รับความช่วยเหลือจาก R :)


2

θA=0.6θB=0.5

# gem install distribution
require 'distribution'

# error bound
EPS = 10**-6

# number of coin tosses
N = 10

# observations
X = [5, 9, 8, 4, 7]

# randomly initialized thetas
theta_a, theta_b = 0.6, 0.5

p [theta_a, theta_b]

loop do
  expectation = X.map do |h|
    like_a = Distribution::Binomial.pdf(h, N, theta_a)
    like_b = Distribution::Binomial.pdf(h, N, theta_b)

    norm_a = like_a / (like_a + like_b)
    norm_b = like_b / (like_a + like_b)

    [norm_a, norm_b, h]
  end

  maximization = expectation.each_with_object([0.0, 0.0, 0.0, 0.0]) do |(norm_a, norm_b, h), r|
    r[0] += norm_a * h; r[1] += norm_a * (N - h)
    r[2] += norm_b * h; r[3] += norm_b * (N - h)
  end

  theta_a_hat = maximization[0] / (maximization[0] + maximization[1])
  theta_b_hat = maximization[2] / (maximization[2] + maximization[3])

  error_a = (theta_a_hat - theta_a).abs / theta_a
  error_b = (theta_b_hat - theta_b).abs / theta_b

  theta_a, theta_b = theta_a_hat, theta_b_hat

  p [theta_a, theta_b]

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