2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2013 W. Trevor King <wking@drexel.edu>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this program. If not, see
18 # <http://www.gnu.org/licenses/>.
20 """Understanding expectation maximization
22 You have a bag of red, green, and blue balls, from which you draw N
23 times with replacement and get n1 red, n2 green, and n3 blue balls.
24 The probability of any one combination of n1, n2, and n3 is given by
25 the multinomial distribution:
27 p(n1, n2, n3) = N! / (n1! n2! n3!) p1^n1 p2^n2 p3^n3
29 From some outside information, we can parameterize this model in terms
30 of a single hidden variable p:
36 If we are red/green colorblind, we only measure
41 What is p (the hidden variable)? What were n1 and n2?
44 import numpy as _numpy
47 class BallBag (object):
48 """Color-blind ball drawings
50 def __init__(self, p=1):
52 self.pvals = [0.25, 0.25 + p/4., 0.5 - p/4.]
55 """Draw `n` balls from the bag with replacement
57 Return (m1, m2), where m1 is the number of red or green balls
58 and m2 is the number of blue balls.
60 nvals = _numpy.random.multinomial(n=n, pvals=self.pvals)
61 m1 = sum(nvals[:2]) # red and green
66 class Analyzer (object):
67 def __init__(self, m1, m2):
70 self.p = self.E_n1 = self.E_n2 = None
75 def print_results(self):
76 print('Results for {}:'.format(type(self).__name__))
79 ('E[n1|m1,m2]', 'E_n1'),
80 ('E[n2|m1,m2]', 'E_n2'),
82 print(' {}: {}'.format(name, getattr(self, attr)))
85 class Naive (Analyzer):
88 With a large enough sample, the measured m1 and m2 give good
89 estimates for (p1 + p2) and p3. You can use either of these to
90 solve for the unknown p, and then solve for E[n1] and E[n2].
92 While this is an easy approach for the colored ball example, it
93 doesn't generalize well to more complicated models ;).
98 p3 = self.m2 / float(N)
105 class MaximumLikelihood (Analyzer):
106 """Analytical ML estimation
108 The likelihood of a general model θ looks like:
110 L(x^N,θ) = sum_{n=1}^N log[p(x_n|θ)] + log[p(θ)]
112 dropping the p(θ) term and applying to this situation, we have:
114 L([m1,m2], p) = N! / (m1! m2!) (p1 + p2)^m1 p3^m2
115 = N! / (m1! m2!) (1/2 + p/4)^m1 (1/2 - p/4)^m2
117 which comes from recognizing that to a color-blind experimenter the
118 three ball colors are effectively two ball colors, so they'll have a
119 binomial distribution. Maximizing the log-likelihood:
121 log[L(m1,m2)] = log[N!…] + m1 log[1/2 + p/4] + m2 log[1/2 - p/4]
122 d/dp log[L] = m1/(1/2 + p/4)/4 + m2/(1/2 - p/4)/(-4) = 0
123 m1 (2 - p) = m2 (2 + p)
124 2 m1 - m1 p = 2 m2 + m2 p
125 (m1 + m2) p = 2 (m1 - m2)
126 p = 2 (m1 - m2) / (m1 + m2)
128 Given this value of p, the the expected values of n1 and n2 are:
130 E[n1|m1,m2] = p1 / (p1 + p2) * m1 # from the relative probabilities
131 = (1/4) / (1/2 + p/4) * m1
133 = m1 / [2 + 2 (m1 - m2) / (m1 + m2)]
134 = m1/2 * (m1 + m2) / (m1 + m2 + m1 - m2)
135 = m1 * (m1 + m2) / (4 m1)
137 E[n2|m1,m2] = p2 / (p1 + p2) * m1
138 = (1/4 + p/4) / (1/2 + p/4) * m1
139 = m1 (1 + p) / (2 + p)
140 = m1 [1 + 2 ((m1 - m2) / (m1 + m2)] /
141 [2 + 2 (m1 - m2) / (m1 + m2)]
142 = m1/2 * (m1 + m2 + 2m1 - 2m2) / (m1 + m2 + m1 - m2)
143 = m1 * (3m1 - m2) / (4 m1)
146 So with a draw of m1 = 61 and m2 = 39, the ML estimates are:
153 N = self.m1 + self.m2
154 self.p = 2 * (self.m1 - self.m2) / float(N)
156 self.E_n2 = (3*self.m1 - self.m2) / 4.
159 class ExpectationMaximizer (Analyzer):
160 """Expectation maximization
162 Sometimes analytical ML is hard, so instead we iteratively
165 Q(θ_t|θ_{t-1}) = E[log[p(x,y,θ_t)]|x,θ_{t-1}]
167 Applying to this situation, we have:
169 Q(p_t|p_{t-1}) = E[log[p([m1,m2],[n1,n2,n3],p_t)]|[m1,m2],p_{t-1}]
173 p(m1,m2,n1,n2,n3,p) = δ(m1-n1-n2)δ(m2-n3)p(n1,n2,n3)
175 Plugging in and expanding the log:
178 = E[log[δ(m1…)] + log[δ(m2…)] + log[p(n1,n2,n3,p_t)]
180 ≈ E[log[p(n1,n2,n3,p_t)]|m1,m2,p_{t-1}] # drop boring δ terms
181 ≈ E[log[N!…] + n1 log[1/4] + n2 log[1/4 + p_t/4] + n3 log[1/2 - p_t/4]
183 ≈ E[n2 log[1/4 + p_t/4] + n3 log[1/2 - p_t/4]
184 |m1,m2,p_{t-1}] # drop non-p_t terms
185 ≈ E[n2|m1,m2,p_{t-1}] log[1/4 + p_t/4] + m2 log[1/2 - p_t/4]
187 Maximizing (the M step):
189 d/dp_t Q(p_t|p_{t-1})
190 ≈ E[n2|m1,m2,p_{t-1}] / (1/4 + p_t/4)/4 + m2 / (1/2 - p_t/4)/(-4)
192 E[n2|m1,m2,p_{t-1}] / (1 + p_t) = m2 / (2 - p_t)
193 E[n2|m1,m2,p_{t-1}] (2 - p_t) = m2 (1 + p_t)
194 p_t (E[n2|m1,m2,p_{t-1}] + m2) = 2 E[n2|m1,m2,p_{t-1}] - m2
195 p_t = (2 E[n2|m1,m2,p_{t-1}] - m2) / (E[n2|m1,m2,p_{t-1}] + m2)
197 To get a value for p_t, we need to evaluate those expectations
198 (the E step). Using a subset of the ML analysis:
200 E[n2|m1,m2,p_{t-1}] = m1 (1 + p_{t-1}) / (2 + p_{t-1})
202 def __init__(self, p=0, **kwargs):
203 super(ExpectationMaximizer, self).__init__(**kwargs)
204 self.p = 0 # prior belief
207 """Caculate E[ni|m1,m2,p_{t-1}] given the prior parameter p_{t-1}
210 'E_n1': m1 / (2. + self.p),
211 'E_n2': m1 * (1. + self.p) / (2. + self.p),
215 def _M_step(self, E_n1, E_n2, E_n3):
216 "Maximize Q(p_t|p_{t-1}) over p_t"
217 self.p = (2.*E_n2 - self.m2) / (E_n2 + self.m2)
219 def __call__(self, n=10):
221 print(' estimated p{}: {}'.format(i, self.p))
224 for key,value in self._E_step().items():
225 setattr(self, key, value)
228 if __name__ == '__main__':
231 m1,m2 = bag.draw(n=100)
232 print('estimate p using m1 = {} and m2 = {}'.format(m1, m2))
234 ExpectationMaximizer(m1=m1, m2=m2),
235 MaximumLikelihood(m1=m1, m2=m2),
239 analyzer.print_results()