1 /++
2 Likelihood maximization algorithms for normal variance mean mixture with unknown scale parameter `beta`.
3 ------
4 F(x) = ∫_0^∞ Φ((x-αu_i)√u) dG(u) ≈ Σ_i p_i*Φ((x-βu_i)/sqrt(u))
5 β - beta (unknown)
6 Φ - standard normal distribution
7 G - mixture distribution
8 p - approximation of G, mixture weights (unknown)
9 ------
10 
11 Example:
12 --------
13 import atmosphere;
14 
15 double[] myGrid, mySample, myNewSample;
16 //... initialize myGrid and mySample.
17 
18 auto optimizer = new NvmmLikelihoodAscentEMCoordinate!double(myGrid, mySample.length+1000);
19 
20 optimizer.sample = mySample;
21 optimizer.optimize(
22 	(double betaPrev, double beta, double likelihoodPrev, double likelihood)
23 		=> likelihood - likelihoodPrev <= 1e-3);
24 
25 double beta = optimizer.beta;
26 double[] mixtureWeights = optimizer.weights.dup;
27 
28 
29 //remove first 50 elements in sample. 
30 optimizer.popFrontN(50);
31 
32 //... initialize myNewSample.
33 //check length <= 1050
34 assert(myNewSample.length <= 1050);
35 
36 // add new sample
37 optimizer.sample = optimizer.sample~myNewSample;
38 optimizer.optimize(
39 	(double betaPrev, double beta, double likelihoodPrev, double likelihood)
40 		=> likelihood - likelihoodPrev <= 1e-3);
41 
42 double beta2 = optimizer.beta;
43 double[] mixtureWeights2 = optimizer.weights.dup;
44 --------
45 +/
46 /**
47 Authors: [Ilya Yaroshenko](http://9il.github.io)
48 
49 Copyright: © 2014-2015 [Ilya Yaroshenko](http://9il.github.io)
50 
51 License: MIT
52 */
53 module atmosphere.estimate.nvmm;
54 
55 import core.stdc.tgmath;
56 
57 import atmosphere.mixture;
58 import atmosphere.internal;
59 import std.algorithm;
60 import std.range;
61 import std.numeric;
62 import std.traits;
63 import std.algorithm;
64 
65 import std.math : isFinite;
66 
67 /++
68 Normal variance-mean mixture optimizer
69 +/
70 abstract class NvmmLikelihoodAscentEM(T) : MixtureOptimizer!T, LikelihoodAscent!T
71 	if(isFloatingPoint!T)
72 {
73 
74 	override void update()
75 	{
76 		_likelihood = _likelihood_(mixture);
77 		updateBeta;
78 	}
79 
80 	package T[] _sample;
81 	package const T[] _grid;
82 
83 	package T _mean;
84 	package T _beta;
85 	package T _likelihood;
86 
87 	mixin LikelihoodAscentTemplate!T;
88 	
89 
90 	/++
91 	Constructor
92 	Params:
93 		_grid = Array of parameters u. [u_1, ..., u_k]
94 		maxLength = maximal length of sample
95 	+/
96 	this(in T[] _grid, size_t maxLength)
97 	in
98 	{
99 		assert(_grid.length);
100 		assert(maxLength);
101 	}
102 	body
103 	{
104 		super(_grid.length, maxLength);
105 		this._grid = _grid.dup;
106 		this._sample = new T[maxLength];
107 		if (!isFeaturesCorrect)
108 			throw new FeaturesException;
109 	}
110 
111 final:
112 
113 	/++
114 	Performs optimization.
115 	Params:
116 		tolerance = Defines an early termination condition. 
117 			Receives the current and previous versions of various parameters. 
118 			The delegate must return true when parameters are acceptable. 
119 		findRootTolerance = Tolerance for inner optimization.
120 	Throws: [FeaturesException](atmosphere/mixture/FeaturesException.html) if [isFeaturesCorrect](atmosphere/mixture/LikelihoodAscent.isFeaturesCorrect.html) is false.
121 	See_Also: $(STDREF numeric, findRoot)
122 	+/
123 	void optimize(
124 			scope bool delegate (
125 				T betaPrev, 
126 				T beta, 
127 				T likelihoodValuePrev, 
128 				T likelihoodValue, 
129 				in T[] weightsPrev, 
130 				in T[] weights,
131 			) 
132 			tolerance,
133 			scope bool delegate(T a, T b) @nogc nothrow findRootTolerance = null,
134 		)
135 	{
136 		if (!isFeaturesCorrect)
137 			throw new FeaturesException;
138 		T likelihoodPrev, betaPrev;
139 		scope T[] weightsPrev = new T[weights.length];
140 		do
141 		{
142 			likelihoodPrev = _likelihood;
143 			betaPrev = _beta;
144 			assert(weights.length == weightsPrev.length);
145 			weightsPrev[] = weights[];
146 			eval(findRootTolerance);
147 		}
148 		while(!tolerance(betaPrev, _beta, likelihoodPrev, _likelihood, weightsPrev, weights));
149 	}
150 
151 
152 	///ditto
153 	void optimize(
154 			scope bool delegate (
155 				T betaPrev, 
156 				T beta, 
157 				T likelihoodValuePrev, 
158 				T likelihoodValue, 
159 			) 
160 			tolerance,
161 			scope bool delegate(T a, T b) @nogc nothrow findRootTolerance = null,
162 		)
163 	{
164 		if (!isFeaturesCorrect)
165 			throw new FeaturesException;
166 		T likelihoodPrev, betaPrev;
167 		do
168 		{
169 			likelihoodPrev = _likelihood;
170 			betaPrev = _beta;
171 			eval(findRootTolerance);
172 		}
173 		while(!tolerance(betaPrev, _beta, likelihoodPrev, _likelihood));
174 	}
175 
176 	///ditto
177 	void optimize(
178 			scope bool delegate (
179 				T betaPrev, 
180 				T beta, 
181 				in T[] weightsPrev, 
182 				in T[] weights,
183 			) 
184 			tolerance,
185 			scope bool delegate(T a, T b) @nogc nothrow findRootTolerance = null,
186 		)
187 	{
188 		if (!isFeaturesCorrect)
189 			throw new FeaturesException;
190 		T betaPrev;
191 		scope T[] weightsPrev = new T[weights.length];
192 		do
193 		{
194 			betaPrev = _beta;
195 			assert(weights.length == weightsPrev.length);
196 			weightsPrev[] = weights[];
197 			eval(findRootTolerance);
198 		}
199 		while(!tolerance(betaPrev, _beta, weightsPrev, weights));
200 	}
201 
202 
203 	/++
204 	Sets sample and recalculates beta and mixture.
205 	Params:
206 		_sample = new sample with length less or equal `maxLength`
207 	Throws: [FeaturesException](atmosphere/mixture/FeaturesException.html) if [isFeaturesCorrect](atmosphere/mixture/LikelihoodAscent.isFeaturesCorrect.html) is false.
208 	+/
209 	void sample(in T[] _sample) @property
210 	in
211 	{
212 		assert(_sample.length <= this._sample.length);
213 		foreach(s; _sample)
214 		{
215 			assert(std.math.isFinite(s));
216 		}
217 		assert(_featuresT.matrix.shift >= _sample.length);
218 	}
219 	body
220 	{
221 		reset;
222 		_featuresT.reserveBackN(_sample.length);
223 		this._sample[0.._sample.length] = _sample[];
224 		_mean = sample.sum/sample.length;
225 		updateBeta;
226 		assert(_featuresT.matrix.width == _sample.length);
227 		updateComponents;
228 		if (!isFeaturesCorrect)
229 			throw new FeaturesException;
230 	}
231 
232 	/++
233 	Returns: Const slice of the internal sample representation.
234 	+/
235 	const(T)[] sample() @property const
236 	{
237 		return _sample[0..mixture.length];
238 	}
239 
240 	/++
241 	Returns: sample mean
242 	+/
243 	T mean() @property const
244 	{
245 		return _mean;
246 	}
247 
248 
249 	T likelihood() @property const
250 	{
251 		return _likelihood;
252 	}
253 
254 	/++
255 	Returns: beta
256 	+/
257 	T beta() @property const
258 	{
259 		return _beta;
260 	}
261 
262 
263 	/++
264 	Returns: Const slice of the internal grid representation.
265 	+/
266 	const(T)[] grid() @property const
267 	{
268 		return _grid;
269 	}
270 
271 	package void updateBeta()
272 	in
273 	{
274 		assert(weights.length == _grid.length);
275 	}
276 	body
277 	{
278 		_beta =  _mean / dotProduct(weights, _grid);
279 	}
280 
281 
282 	package void updateComponents()
283 	{
284 		auto m = _featuresT.matrix;
285 		assert(m.width == sample.length);
286 		foreach(pdf; _grid.map!(z => CorePDF(beta, z)))
287 		{
288 			auto r = m.front;
289 			m.popFront;
290 			foreach(x; sample)
291 			{
292 				r.front = pdf(x);
293 				r.popFront;
294 			}
295 		}
296 		updateMixture;
297 	}
298 
299 	///
300 	static struct CorePDF
301 	{
302 		import atmosphere.pdf;
303 		NormalSPDF!T pdf;
304 		alias pdf this;
305 
306 		/++
307 		Params:
308 			beta = beta
309 			z = z
310 		+/
311 		this(T beta, T z)
312 		{
313 			assert(z > 0);
314 			assert(beta.isFinite);
315 			pdf = NormalSPDF!T(beta*z, z);
316 		}
317 	}
318 }
319 
320 
321 /++
322 Expectation–maximization algorithm
323 +/
324 final class NvmmLikelihoodAscentEMEM(T) : NvmmLikelihoodAscentEM!T
325 	if(isFloatingPoint!T)
326 {
327 	private T[] pi;
328 	private T[] c;
329 
330 	/++
331 	Constructor
332 	Params:
333 		_grid = Array of parameters u. [u_1, ..., u_k]
334 		maxLength = maximal length of sample
335 	+/
336 	this(in T[] _grid, size_t maxLength)
337 	in
338 	{
339 		assert(maxLength);
340 	}
341 	body
342 	{
343 		super(_grid, maxLength);
344 		pi = new T[_sample.length];
345 		c = new T[_grid.length];
346 	}
347 
348 	override void eval(scope bool delegate(T a, T b) @nogc nothrow findRootTolerance = null)
349 	{
350 		EMIteration!
351 			((a, b) {foreach(i, ai; a) b[i] = 1/ai;}, T)
352 			(features, _weights, mixture, pi[0..length], c);
353 		updateComponents;
354 	}
355 }
356 
357 
358 /++
359 Expectation–maximization algorithm with inner gradient descend optimization.
360 +/
361 final class NvmmLikelihoodAscentEMGradient(T) : NvmmLikelihoodAscentEM!T
362 	if(isFloatingPoint!T)
363 {
364 	private T[] pi;
365 	private T[] xi;
366 	private T[] gamma;
367 	private T[] c;
368 
369 	/++
370 	Constructor
371 	Params:
372 		_grid = Array of parameters u. [u_1, ..., u_k]
373 		maxLength = maximal length of sample
374 	+/
375 	this(in T[] _grid, size_t maxLength)
376 	{
377 		super(_grid, maxLength);
378 		pi = new T[_sample.length];
379 		xi = new T[_sample.length];
380 		gamma = new T[_sample.length];
381 		c = new T[_grid.length];
382 	}
383 
384 	override void eval(scope bool delegate(T a, T b) @nogc nothrow findRootTolerance = null)
385 	{
386 		gradientDescentIteration!
387 			((a, b) {foreach(i, ai; a) b[i] = -1/ai;}, T)
388 			(features, _weights, mixture, pi[0..length], xi[0..length], gamma[0..length], c, findRootTolerance is null ? (a, b) => false : findRootTolerance);
389 		updateComponents;
390 	}
391 }
392 
393 
394 /++
395 Expectation–maximization algorithm with inner coordinate descend optimization.
396 Speed depends on permutation of elements of `grid`.
397 +/
398 final class NvmmLikelihoodAscentEMCoordinate(T) : NvmmLikelihoodAscentEM!T
399 	if(isFloatingPoint!T)
400 {
401 	private T[] pi;
402 
403 	/++
404 	Constructor
405 	Params:
406 		_grid = Array of parameters u. [u_1, ..., u_k]
407 		maxLength = maximal length of sample
408 	+/
409 	this(in T[] _grid, size_t maxLength)
410 	{
411 		super(_grid, maxLength);
412 		pi = new T[_sample.length];
413 	}
414 
415 	override void eval(scope bool delegate(T a, T b) @nogc nothrow findRootTolerance = null)
416 	{
417 		coordinateDescentIterationPartial!("-1/a", T)
418 			(features, _weights, _mixture[0..mixture.length], pi[0..length], findRootTolerance is null ? (a, b) => false : findRootTolerance);
419 		updateComponents;
420 	}
421 }
422 
423 
424 unittest {
425 	alias C0 = NvmmLikelihoodAscentEMEM!double;
426 	alias C1 = NvmmLikelihoodAscentEMCoordinate!double;
427 	alias C2 = NvmmLikelihoodAscentEMGradient!double;
428 }