Числовий приклад для розуміння Очікування-Максимізація


117

Я намагаюся зрозуміти алгоритм ЕМ, щоб мати можливість його реалізувати та використовувати. Я провів цілий день, читаючи теорію та документ, де ЕМ використовується для відстеження літака, використовуючи інформацію про положення, що надходить від радарів. Чесно кажучи, я не думаю, що я повністю розумію основну ідею. Чи може хтось вказати мені на числовий приклад, що показує декілька ітерацій (3-4) ЕМ для більш простої задачі (як, наприклад, визначення параметрів гауссового розподілу або послідовності синусоїдального ряду чи підгонки лінії).

Навіть якщо хтось може вказати мені на фрагмент коду (із синтетичними даними), я можу спробувати перейти через код.


1
k-означає дуже em, але з постійною дисперсією, і відносно простий.
EngrStudent

2
@ arjsgh21 чи можете ви опублікувати згаданий документ про літальний апарат? Звучить дуже цікаво. Дякую
Wakan Tanka

1
В Інтернеті є навчальний посібник, який стверджує, що дає дуже чітке математичне розуміння алгоритму Ем "Демістифікований ЕМ: Підручник з максимізації очікування". Однак приклад настільки поганий, що він межує з незрозумілим.
Експерт Шамісен

Відповіді:


98

Це рецепт для вивчення ЕМ на практичному та (на мою думку) дуже інтуїтивно зрозумілому прикладі «монети-кидання»:

  1. Прочитайте цей короткий документ із електронних посібників від Do і Batzoglou. Це схема, де пояснюється приклад кидання монети:

    введіть тут опис зображення

  2. Можливо, у вас в голові знаки запитань, особливо щодо того, звідки беруться ймовірності кроку очікування. Перегляньте пояснення на цій сторінці обміну стеками математики .

  3. Подивіться на / запустіть цей код, який я написав у Python, який імітує рішення проблеми монети, кинутого в документі з підручника 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.
Жубарб

1
@Zhubarb: Це стандартний підхід для обчислення конвергенції в ЕМ, чи це щось, що ви придумали? Якщо це стандартний підхід, чи можете ви навести посилання?
stackoverflowuser2010

Ось посилання на конвергенцію ЕМ. Я написав код десь тому, тому не можу надто добре запам'ятати. Я вважаю, що те, що ви бачите в коді, є моїм критерієм конвергенції для цього конкретного випадку. Ідея полягає в тому, щоб зупинити ітерації, коли максимум поліпшень для A і B менше, ніж delta.
Жубарб

1
Чудово, немає нічого доброго коду, щоб уточнити, які абзаци тексту не можуть
jon_simon

63

Здається, що ваше запитання має дві частини: основна ідея та конкретний приклад. Почну з основної ідеї, а потім посилаюсь на приклад внизу.


EM корисно в Catch-22 ситуацій , коли здається , що ви повинні знати , перш ніж можна обчислити , і ви повинні знати , перш ніж можна обчислити .Б В АABBA

Найбільш поширений випадок, з яким люди стикаються - це, мабуть, розподіл сумішей. Для нашого прикладу розглянемо просту модель гауссової суміші:

У вас є два різних універсальних гауссових розподілу з різними засобами та дисперсією одиниць.

У вас є маса точок даних, але ви не впевнені, які точки прийшли з якого розподілу, і ви також не впевнені в засобах двох розподілів.

А тепер ти застряг:

  • Якби ви знали справжні засоби, ви могли б зрозуміти, які точки даних походять від якого Гаусса. Наприклад, якщо точка даних мала дуже високе значення, вона, ймовірно, походить від розподілу з більш високим середнім значенням. Але ви не знаєте, що це за засоби, тож це не спрацює.

  • Якщо ви знали, з якого розподілу походить кожна точка, то ви могли б оцінити засоби двох розподілів, використовуючи вибіркові засоби відповідних точок. Але ви насправді не знаєте, які точки присвоювати дистрибутиву, тому це також не спрацює.

Тож жоден підхід не здається, що він працює: вам слід знати відповідь, перш ніж ви зможете знайти відповідь, і ви застрягли.

Що дозволяє вам зробити, це чергувати ці два простежувані кроки, а не братися за весь процес відразу.

Вам потрібно буде почати здогадку про два засоби (хоча ваші здогадки не обов'язково повинні бути дуже точними, вам потрібно десь почати).

Якщо ваші здогадки про засоби були точними, то у вас буде достатньо інформації, щоб здійснити крок у моїй першій точці кулі вище, і ви могли (імовірно) присвоїти кожну точку даних одному з двох гаусів. Хоча ми знаємо, що наша здогадка неправильна, давайте все-таки спробуємо це. І тоді, з огляду на призначені розподіли кожної точки, ви можете отримати нові оцінки для засобів, використовуючи другу точку кулі. Виявляється, щоразу, коли ви робите цикл через ці два кроки, ви вдосконалюєте нижню межу щодо ймовірності моделі.

Це вже досить круто: хоч обидві пропозиції в точках кулі вище не здавалися, що вони працюватимуть окремо, ви все одно можете використовувати їх разом для вдосконалення моделі. Справжня магія ЕСТ є те, що після досить ітерацій, нижня межа буде настільки висока , що не буде ніякого простору між ним і локальним максимумом. Як результат, і ви локально оптимізували ймовірність.

Таким чином, ви не просто вдосконалили модель, ви знайшли найкращу можливу модель, яку можна знайти за допомогою поступових оновлень.


Ця сторінка з Вікіпедії показує дещо складніший приклад (двовимірний гаусс і невідома коваріація), але основна ідея та ж. Він також включає добре коментований Rкод для реалізації прикладу.

У коді крок "Очікування" (Е-крок) відповідає моїй першій точці відмітки: з'ясування того, який Гаусс отримує відповідальність за кожну точку даних, враховуючи поточні параметри для кожного Гаусса. Крок "Максимізація" (М-крок) оновлює засоби та коваріації, враховуючи ці завдання, як у моїй другій точці.

Як ви бачите в анімації, ці оновлення швидко дозволяють алгоритму переходити від набору жахливих оцінок до набору дуже хороших: насправді, схоже, є дві хмари точок, зосереджені на двох розподілах Гаусса, які знайде ЕМ.


13

Ось приклад максимізації очікування (EM), який використовується для оцінки середнього та стандартного відхилень. Код є в Python, але його слід легко дотримуватися, навіть якщо ви не знайомі з мовою.

Мотивація ЕМ

Червоні та сині точки, показані нижче, намальовані з двох різних нормальних розподілів, кожен з певним середнім та стандартним відхиленням:

введіть тут опис зображення

Для обчислення розумних наближень до "справжнього" середнього та стандартного параметрів відхилення для розподілу червоного кольору ми могли б дуже легко подивитися на червоні точки та записати положення кожного з них, а потім використати знайомі формули (і аналогічно для синьої групи) .

Тепер розглянемо випадок, коли ми знаємо, що існує дві групи точок, але ми не можемо побачити, яка точка належить до якої групи. Іншими словами, кольори приховані:

введіть тут опис зображення

Зовсім не очевидно, як розділити бали на дві групи. Зараз ми не можемо просто подивитися на позиції та обчислити параметри параметрів червоного чи синього розподілу.

Саме тут ЕМ можна використовувати для вирішення проблеми.

Використання ЕМ для оцінки параметрів

Ось код, який використовується для створення точок, показаних вище. Ви можете бачити фактичні середні та стандартні відхилення нормальних розподілів, з яких були проведені точки. Змінні 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

Але оскільки кольори приховані від нас, ми розпочнемо процес ЕМ ...

Спочатку ми просто вгадуємо значення для параметрів кожної групи ( крок 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

введіть тут опис зображення

Досить погані здогадки - засоби виглядають так, що вони далекі від будь-якої "середини" групи очок.

Для продовження ЕМ та вдосконалення цих здогадок, ми обчислюємо ймовірність появи кожної точки даних (незалежно від її секретного кольору) серед цих здогадок для середнього та стандартного відхилень ( крок 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параметра, який присвоює вагу кожній точці даних.

Це зважування є ключем до ЕМ. Чим більша вага кольору в точці даних, тим більше точка даних впливає на наступні оцінки параметрів цього кольору. Зрештою, це призводить до того, що кожен параметр потягне в правильному напрямку.

Нові здогадки обчислюються цими функціями:

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

Потім процес ЕМ повторюється з цими новими здогадами з кроку 2. Ми можемо повторити кроки для заданої кількості ітерацій (скажімо, 20) або поки не побачимо, що параметри збігаються.

Після п'яти повторень ми бачимо, що наші початкові погані здогадки починають покращуватися:

введіть тут опис зображення

Після 20 ітерацій процес ЕМ більш-менш сходився:

введіть тут опис зображення

Для порівняння, ось результати 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

Після відповіді Жубарба я застосував приклад 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

Все вищезазначене виглядає як чудові ресурси, але я мушу посилатись на цей чудовий приклад. У ньому представлено дуже просте пояснення для пошуку параметрів для двох рядків набору точок. Підручник - Яїр Вайс, перебуваючи в MIT.

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


5

Відповідь, яку дав Жубарб, чудова, але, на жаль, вона є в Python. Нижче представлена ​​Java реалізація алгоритму ЕМ, виконаного за тією ж проблемою (викладено у статті Do та Batzoglou, 2008). Я додав деякі типи printf до стандартного виводу, щоб побачити, як параметри зближуються.

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. Цікаво, наскільки ввічливим вважається більш широко анотувати код когось у своїй відповіді.
EngrStudent

4

Ну, я б запропонував вам перечитати книгу про R Марії Л Ріццо. В одній з глав міститься використання алгоритму ЕМ з числовим прикладом. Я пам’ятаю, що переглядав код для кращого розуміння.

Також спробуйте переглянути його з точки зору кластеризації на початку. Розробка вручну, проблема кластеризації, де 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.