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 }