Ví dụ số để hiểu tối đa hóa kỳ vọng


117

Tôi đang cố gắng nắm bắt tốt thuật toán EM, để có thể thực hiện và sử dụng nó. Tôi đã dành cả ngày để đọc lý thuyết và một bài báo trong đó EM được sử dụng để theo dõi một chiếc máy bay sử dụng thông tin vị trí đến từ radar. Thành thật mà nói, tôi không nghĩ rằng tôi hoàn toàn hiểu ý tưởng cơ bản. Ai đó có thể chỉ cho tôi một ví dụ bằng số hiển thị một vài lần lặp (3-4) của EM cho một vấn đề đơn giản hơn (như ước tính các tham số của phân phối Gaussian hoặc một chuỗi các chuỗi hình sin hoặc khớp một dòng).

Ngay cả khi ai đó có thể chỉ cho tôi một đoạn mã (có dữ liệu tổng hợp), tôi vẫn có thể thử bước qua mã.


1
k-nghĩa là rất em, nhưng với phương sai không đổi, và tương đối đơn giản.
EngrStudent 8/2/2016

2
@ arjsgh21 bạn có thể xin vui lòng gửi giấy đề cập về máy bay? Âm thanh rất thú vị. Cảm ơn bạn
Wakan Tanka

1
Có một hướng dẫn trực tuyến tuyên bố cung cấp một sự hiểu biết toán học rất rõ ràng về thuật toán Em "EM Demystified: An Expectation-Maximization Tutorial" Tuy nhiên, ví dụ này rất tệ đến mức không thể hiểu được.
Chuyên gia Shamisen

Câu trả lời:


98

Đây là một công thức để học EM với một ví dụ thực tế và (theo ý kiến ​​của tôi) rất trực quan 'Ví dụ về Coin-Toss':

  1. Đọc bài hướng dẫn EM ngắn này của Do và Batzoglou. Đây là lược đồ nơi ví dụ tung đồng xu được giải thích:

    nhập mô tả hình ảnh ở đây

  2. Bạn có thể có dấu chấm hỏi trong đầu, đặc biệt là về xác suất trong bước Kỳ vọng đến từ đâu. Xin vui lòng xem các giải thích trên trang trao đổi ngăn xếp toán học này .

  3. Nhìn vào / chạy mã này mà tôi đã viết bằng Python mô phỏng giải pháp cho vấn đề tung đồng xu trong tài liệu hướng dẫn EM của mục 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: bạn có thể vui lòng giải thích điều kiện kết thúc vòng lặp (tức là để xác định khi thuật toán hội tụ)? Biến "cải tiến" tính toán gì?
stackoverflowuser2010

@ stackoverflowuser2010, cải thiện nhìn vào hai vùng đồng bằng: 1) sự thay đổi giữa pA_heads[j+1]pA_heads[j]và 2) sự thay đổi giữa pB_heads[j+1]pB_heads[j]. Và nó mất tối đa của hai thay đổi. Ví dụ, nếu Delta_A=0.001Delta_B=0.02, cải thiện từ bước jtới j+1sẽ được 0.02.
Zhubarb

1
@Zhubarb: Đó có phải là một cách tiếp cận tiêu chuẩn để hội tụ điện toán trong EM, hay đó là thứ bạn nghĩ ra? Nếu đó là một cách tiếp cận tiêu chuẩn, bạn có thể vui lòng trích dẫn một tài liệu tham khảo?
stackoverflowuser2010

Dưới đây là một tài liệu tham khảo về sự hội tụ của EM. Tôi đã viết mã cách đây một thời gian để không thể nhớ quá rõ. Tôi tin rằng những gì bạn nhìn thấy trong mã là tiêu chí hội tụ của tôi cho trường hợp cụ thể này. Ý tưởng là dừng lặp lại khi tối đa các cải tiến cho A và B nhỏ hơn delta.
Zhubarb

1
Tuyệt vời, không có gì giống như một số mã tốt để làm rõ những đoạn văn bản nào không thể
jon_simon

63

Có vẻ như câu hỏi của bạn có hai phần: ý tưởng cơ bản và một ví dụ cụ thể. Tôi sẽ bắt đầu với ý tưởng cơ bản, sau đó liên kết đến một ví dụ ở phía dưới.


MộtBBMột

Trường hợp phổ biến nhất mà mọi người đối phó có lẽ là phân phối hỗn hợp. Ví dụ của chúng ta, hãy xem xét một mô hình hỗn hợp Gaussian đơn giản:

Bạn có hai phân phối Gaussian đơn biến khác nhau với các phương tiện và phương sai đơn vị khác nhau.

Bạn có một loạt các điểm dữ liệu, nhưng bạn không chắc chắn điểm nào đến từ phân phối nào và bạn cũng không chắc chắn về phương tiện của hai phân phối.

Và bây giờ bạn đang bị mắc kẹt:

  • Nếu bạn biết phương tiện thực sự, bạn có thể tìm ra điểm dữ liệu đến từ Gaussian nào. Ví dụ: nếu một điểm dữ liệu có giá trị rất cao, thì có lẽ nó đến từ phân phối với giá trị trung bình cao hơn. Nhưng bạn không biết phương tiện là gì, vì vậy điều này sẽ không hoạt động.

  • Nếu bạn biết phân phối mỗi điểm đến từ đâu, thì bạn có thể ước tính hai phương tiện phân phối bằng cách sử dụng phương tiện mẫu của các điểm có liên quan. Nhưng bạn thực sự không biết điểm nào để gán cho phân phối nào, vì vậy điều này cũng không hoạt động.

Vì vậy, không có cách tiếp cận nào có vẻ như nó hoạt động: bạn cần biết câu trả lời trước khi bạn có thể tìm thấy câu trả lời và bạn bị mắc kẹt.

Những gì EM cho phép bạn làm là xen kẽ giữa hai bước có thể thay đổi này thay vì giải quyết toàn bộ quá trình cùng một lúc.

Bạn sẽ cần bắt đầu với một dự đoán về hai phương tiện (mặc dù dự đoán của bạn không nhất thiết phải rất chính xác, bạn cần phải bắt đầu ở đâu đó).

Nếu dự đoán của bạn về phương tiện là chính xác, thì bạn sẽ có đủ thông tin để thực hiện bước trong điểm đầu tiên của tôi ở trên và bạn có thể (xác suất) gán từng điểm dữ liệu cho một trong hai Gaussian. Mặc dù chúng tôi biết dự đoán của mình là sai, nhưng hãy thử điều này bằng mọi cách. Và sau đó, với các phân phối được chỉ định của mỗi điểm, bạn có thể nhận được các ước tính mới cho phương tiện bằng cách sử dụng dấu đầu dòng thứ hai. Nó chỉ ra rằng, mỗi khi bạn thực hiện vòng lặp qua hai bước này, bạn sẽ cải thiện giới hạn thấp hơn về khả năng của mô hình.

Điều đó thật tuyệt vời: mặc dù hai gợi ý trong các gạch đầu dòng ở trên dường như không hoạt động riêng lẻ, bạn vẫn có thể sử dụng chúng cùng nhau để cải thiện mô hình. Điều kỳ diệu thực sự của EM là, sau khi lặp đủ, giới hạn dưới sẽ cao đến mức sẽ không có khoảng trống giữa nó và cực đại cục bộ. Kết quả là, và bạn đã tối ưu hóa khả năng của địa phương.

Vì vậy, bạn chưa cải tiến mô hình, bạn đã tìm thấy mô hình tốt nhất có thể tìm thấy với các bản cập nhật gia tăng.


Đây trang từ Wikipedia cho thấy một ví dụ hơi phức tạp hơn (hai chiều Gaussian và chưa biết hiệp phương sai), nhưng ý tưởng cơ bản là như nhau. Nó cũng bao gồm Rmã nhận xét tốt để thực hiện ví dụ.

Trong mã, bước "Kỳ vọng" (Bước E) tương ứng với điểm đầu tiên của tôi: tìm ra Gaussian nào chịu trách nhiệm cho từng điểm dữ liệu, đưa ra các tham số hiện tại cho mỗi Gaussian. Bước "Tối đa hóa" (bước M) cập nhật các phương tiện và hiệp phương sai, được đưa ra các nhiệm vụ này, như trong điểm đạn thứ hai của tôi.

Như bạn có thể thấy trong hình ảnh động, các cập nhật này nhanh chóng cho phép thuật toán chuyển từ một tập các ước tính khủng khiếp sang một tập hợp rất tốt: dường như thực sự có hai đám mây điểm tập trung vào hai phân phối Gaussian mà EM tìm thấy.


13

Dưới đây là một ví dụ về Tối đa hóa kỳ vọng (EM) được sử dụng để ước tính độ lệch trung bình và độ lệch chuẩn. Mã này bằng Python, nhưng nó rất dễ theo dõi ngay cả khi bạn không quen với ngôn ngữ này.

Động lực cho EM

Các điểm màu đỏ và màu xanh hiển thị bên dưới được rút ra từ hai phân phối bình thường khác nhau, mỗi điểm có một độ lệch chuẩn và trung bình cụ thể:

nhập mô tả hình ảnh ở đây

Để tính các xấp xỉ hợp lý của các tham số độ lệch chuẩn và trung bình "đúng" cho phân bố màu đỏ, chúng ta có thể dễ dàng nhìn vào các điểm đỏ và ghi lại vị trí của từng điểm, sau đó sử dụng các công thức quen thuộc (và tương tự cho nhóm màu xanh) .

Bây giờ hãy xem xét trường hợp chúng ta biết rằng có hai nhóm điểm, nhưng chúng ta không thể thấy điểm nào thuộc về nhóm nào. Nói cách khác, các màu được ẩn đi:

nhập mô tả hình ảnh ở đây

Không rõ ràng làm thế nào để chia điểm thành hai nhóm. Bây giờ chúng ta không thể chỉ nhìn vào các vị trí và tính toán các ước tính cho các tham số của phân phối màu đỏ hoặc phân phối màu xanh.

Đây là nơi EM có thể được sử dụng để giải quyết vấn đề.

Sử dụng EM để ước tính các tham số

Đây là mã được sử dụng để tạo các điểm được hiển thị ở trên. Bạn có thể thấy các phương tiện thực tế và độ lệch chuẩn của các bản phân phối bình thường mà các điểm được rút ra từ đó. Các biến redbluegiữ vị trí của từng điểm trong các nhóm màu đỏ và màu xanh tương ứng:

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)))

Nếu chúng ta có thể thấy màu của từng điểm, chúng ta sẽ thử và phục hồi các phương tiện và độ lệch chuẩn bằng các hàm thư viện:

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

Nhưng vì màu sắc bị ẩn khỏi chúng ta, chúng ta sẽ bắt đầu quá trình EM ...

Đầu tiên, chúng tôi chỉ đoán các giá trị cho các tham số của mỗi nhóm ( bước 1 ). Những dự đoán này không phải là tốt:

# 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

nhập mô tả hình ảnh ở đây

Dự đoán khá tệ - phương tiện trông giống như chúng là một chặng đường dài từ bất kỳ "giữa" nào của một nhóm điểm.

Để tiếp tục với EM và cải thiện những dự đoán này, chúng tôi tính toán khả năng của từng điểm dữ liệu (bất kể màu bí mật của nó) xuất hiện dưới những dự đoán này cho độ lệch trung bình và độ lệch chuẩn ( bước 2 ).

Biến both_coloursgiữ từng điểm dữ liệu. Hàm stats.normtính toán xác suất của điểm dưới một phân phối bình thường với các tham số đã cho:

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)

Điều này cho chúng ta, ví dụ, với dự đoán hiện tại của chúng tôi, điểm dữ liệu tại 1.761 có nhiều khả năng là màu đỏ (0.189) hơn màu xanh lam (0,00003).

Chúng ta có thể biến hai giá trị khả năng này thành trọng số ( bước 3 ) để chúng tổng hợp thành 1 như sau:

likelihood_total = likelihood_of_red + likelihood_of_blue

red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total

Với các ước tính hiện tại của chúng tôi và các trọng số mới được tính toán của chúng tôi, giờ đây chúng tôi có thể tính toán các ước tính mới, có thể tốt hơn cho các tham số ( bước 4 ). Chúng ta cần một hàm cho giá trị trung bình và một hàm cho độ lệch chuẩn:

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)

Chúng trông rất giống với các hàm thông thường với độ lệch trung bình và độ lệch chuẩn của dữ liệu. Sự khác biệt là việc sử dụng weighttham số gán trọng số cho từng điểm dữ liệu.

Trọng số này là chìa khóa cho EM. Trọng lượng của màu trên điểm dữ liệu càng lớn, điểm dữ liệu càng ảnh hưởng đến các ước tính tiếp theo cho các tham số của màu đó. Cuối cùng, điều này có tác dụng kéo từng tham số theo đúng hướng.

Các dự đoán mới được tính toán với các chức năng này:

# 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)

Quá trình EM sau đó được lặp lại với những dự đoán mới từ bước 2 trở đi. Chúng ta có thể lặp lại các bước cho một số lần lặp nhất định (giả sử 20) hoặc cho đến khi chúng ta thấy các tham số hội tụ.

Sau năm lần lặp lại, chúng ta thấy những dự đoán xấu ban đầu của mình bắt đầu tốt hơn:

nhập mô tả hình ảnh ở đây

Sau 20 lần lặp, quy trình EM đã hội tụ ít nhiều:

nhập mô tả hình ảnh ở đây

Để so sánh, đây là kết quả của quá trình EM so với các giá trị được tính toán trong đó thông tin màu không bị ẩn:

          | 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

Lưu ý: câu trả lời này đã được điều chỉnh từ câu trả lời của tôi trên Stack Overflow tại đây .


10

Theo câu trả lời của Zhubarb, tôi đã triển khai ví dụ EM và Batzoglou "tung đồng xu" trong GNU R. Lưu ý rằng tôi sử dụng mlechức năng của stats4gói - điều này giúp tôi hiểu rõ hơn về EM và MLE có liên quan như thế nào.

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 Bạn có thể giải thích tại sao trong bước tối đa hóa, bạn có thể nhân khả năng của một đồng xu A (hàng $ weight_A) với xác suất đăng nhập (llf_A) không? Có một quy tắc đặc biệt hoặc lý do chúng ta làm điều đó? Tôi có nghĩa là người ta sẽ nhân các khả năng hoặc loglikehoods nhưng không trộn lẫn với nhau. Tôi cũng đã mở một chủ đề
Alina


5

Câu trả lời được đưa ra bởi Zhubarb là rất tốt, nhưng thật không may, đó là bằng Python. Dưới đây là một triển khai Java của thuật toán EM được thực hiện trên cùng một vấn đề (được đặt ra trong bài viết của Do và Batzoglou, 2008). Tôi đã thêm một số printf vào đầu ra tiêu chuẩn để xem cách các tham số hội tụ.

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

Mã Java như sau:

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
Bạn có thể thêm một số thảo luận hoặc giải thích cho mã thô? Nó sẽ hữu ích cho nhiều độc giả ít nhất là đề cập đến ngôn ngữ bạn đang viết.
Glen_b

1
@Glen_b - đây là MatLab. Tôi tự hỏi làm thế nào lịch sự nó được coi là chú thích rộng rãi hơn mã của ai đó trong câu trả lời của họ.
EngrStudent 8/2/2016

4

Chà, tôi sẽ đề nghị bạn xem qua một cuốn sách về R của Maria L Rizzo. Một trong những chương chứa việc sử dụng thuật toán EM với một ví dụ bằng số. Tôi nhớ đi qua mã để hiểu rõ hơn.

Ngoài ra, hãy thử xem nó từ một quan điểm phân cụm trong đầu. Làm việc bằng tay, một vấn đề phân cụm trong đó 10 quan sát được lấy từ hai mật độ bình thường khác nhau. Điều này sẽ giúp. Hãy giúp đỡ từ R :)


2

θMột= =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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.