forked from TASBE/TASBEFlowAnalytics
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgmdistribution.m
344 lines (318 loc) · 12 KB
/
gmdistribution.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
%% Copyright (C) 2015 Lachlan Andrew <[email protected]>
%%
%% This program is free software; you can redistribute it and/or modify it under
%% the terms of the GNU General Public License as published by the Free Software
%% Foundation; either version 3 of the License, or (at your option) any later
%% version.
%%
%% This program is distributed in the hope that it will be useful, but WITHOUT
%% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
%% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
%% details.
%%
%% You should have received a copy of the GNU General Public License along with
%% this program; if not, see <http://www.gnu.org/licenses/>.
%%
%% -*- texinfo -*-
%% @deftypefn {Function File} {@var{GMdist} =} gmdistribution (@var{mu}, @var{Sigma})
%% @deftypefnx {Function File} {@var{GMdist} =} gmdistribution (@var{mu}, @var{Sigma}, @var{p})
%% @deftypefnx {Function File} {@var{GMdist} =} gmdistribution (@var{mu}, @var{Sigma}, @var{p}, @var{extra})
%% Create an object of the gmdistribution class which represents a Gaussian
%% mixture model with k components of n-dimensional Gaussians.
%%
%% Input @var{mu} is a k-by-n matrix specifying the n-dimensional mean of each
%% of the k components of the distribution.
%%
%% Input @var{Sigma} is an array that specifies the variances of the
%% distributions, in one of four forms depending on its dimension.
%% @itemize
%% @item n-by-n-by-k: Slice @var{Sigma}(:,:,i) is the variance of the
%% i'th component
%% @item 1-by-n-by-k: Slice diag(@var{Sigma}(1,:,i)) is the variance of the
%% i'th component
%% @item n-by-n: @var{Sigma} is the variance of every component
%% @item 1-by-n-by-k: Slice diag(@var{Sigma}) is the variance of every
%% component
%% @end itemize
%%
%% If @var{p} is specified, it is a vector of length k specifying the proportion
%% of each component. If it is omitted or empty, each component has an equal
%% proportion.
%%
%% Input @var{extra} is used by fitgmdist to indicate the parameters of the
%% fitting process.
%% @seealso{fitgmdist}
%% @end deftypefn
classdef gmdistribution
properties
mu %% means
Sigma %% covariances
ComponentProportion %% mixing proportions
DistributionName %% 'gaussian mixture distribution'
NumComponents %% Number of mixture components
NumVariables %% Dimension d of each Gaussian component
CovarianceType %% 'diagonal' if DiagonalCovariance, 'full' othw
SharedCovariance %% true if all components have equal covariance
%% Set by a call to gmdistribution.fit or fitgmdist
AIC %% Akaike Information Criterion
BIC %% Bayes Information Criterion
Converged %% true if algorithm converged by MaxIter
NegativeLogLikelihood %% Negative of log-likelihood
NlogL %% Negative of log-likelihood
NumIterations %% Number of iterations
RegularizationValue %% const added to diag of cov to make +ve def
end
properties (Access = private)
DiagonalCovariance %% bool summary of 'CovarianceType'
end
methods
% Constructor
function obj = gmdistribution (mu,sigma,p,extra)
p = [];
extra = [];
obj.DistributionName = 'gaussian mixture distribution';
obj.mu = mu;
obj.Sigma = sigma;
[row_n col_n] = size(mu);
obj.NumComponents = row_n; %rows (mu);
obj.NumVariables = columns (mu);
if (isempty (p))
obj.ComponentProportion = ones (1,obj.NumComponents) / obj.NumComponents;
else
if any (p < 0)
error ('gmmdistribution: component weights must be non-negative');
end
s = sum(p);
if (s == 0)
error ('gmmdistribution: component weights must not be all zero');
elseif (s ~= 1)
p = p / s;
end
obj.ComponentProportion = p(:)';
end
if (length (size (sigma)) == 3)
obj.SharedCovariance = false;
else
obj.SharedCovariance = true;
end
[r_sig c_sig] = size(sigma);
[r_mu c_mu] = size(mu);
if (r_sig == 1 && c_mu > 1) %(rows (sigma) == 1 && columns (mu) > 1)
obj.DiagonalCovariance = true;
obj.CovarianceType = 'diagonal';
else
obj.DiagonalCovariance = false; %% full
obj.CovarianceType = 'full';
end
if (~isempty (extra))
obj.AIC = extra.AIC;
obj.BIC = extra.BIC;
obj.Converged = extra.Converged;
obj.NegativeLogLikelihood = extra.NegativeLogLikelihood;
obj.NlogL = extra.NegativeLogLikelihood;
obj.NumIterations = extra.NumIterations;
obj.RegularizationValue = extra.RegularizationValue;
end
end
% Cumulative distribution function for Gaussian mixture distribution
function c = cdf (obj, X)
X = checkX (obj, X, 'cdf');
p_x_l = zeros (rows (X), obj.NumComponents);
if (obj.SharedCovariance)
if (obj.DiagonalCovariance)
sig = diag (obj.Sigma);
else
sig = obj.Sigma;
end
end
for i = 1:obj.NumComponents
if (~obj.SharedCovariance)
if (obj.DiagonalCovariance)
sig = diag (obj.Sigma(:,:,i));
else
sig = obj.Sigma(:,:,i);
end
end
p_x_l(:,i) = mvncdf (X,obj.mu(i,:),sig)*obj.ComponentProportion(i);
end
c = sum (p_x_l, 2);
end
% Construct clusters from Gaussian mixture distribution
function [idx,nlogl,P,logpdf,M] = cluster (obj,X)
X = checkX (obj, X, 'cluster');
[p_x_l, M] = componentProb (obj, X);
[~, idx] = max (p_x_l, [], 2);
if (nargout >= 2)
PDF = sum (p_x_l, 2);
logpdf = log (PDF);
nlogl = -sum (logpdf);
if (nargout >= 3)
P = bsxfun (@rdivide, p_x_l, PDF);
end
end
end
% Display Gaussian mixture distribution object
function c = disp (obj)
fprintf('Gaussian mixture distribution with %d components in %d dimension(s)\n', obj.NumComponents, columns (obj.mu));
for i = 1:obj.NumComponents
fprintf('Clust %d: weight %d\n\tMean: ', i, obj.ComponentProportion(i));
fprintf('%g ', obj.mu(i,:));
fprintf('\n');
if (~obj.SharedCovariance)
fprintf('\tVariance:');
if (~obj.DiagonalCovariance)
if columns (obj.mu) > 1
fprintf('\n');
end
disp(squeeze(obj.Sigma(:,:,i)))
else
fprintf(' diag(');
fprintf('%g ', obj.Sigma(:,:,i));
fprintf(')\n');
end
end
end
if (obj.SharedCovariance)
fprintf('Shared variance\n');
if (~obj.DiagonalCovariance)
obj.Sigma
else
fprintf(' diag(');
fprintf('%g ', obj.Sigma);
fprintf(')\n');
end
end
if (~isempty (obj.AIC))
fprintf('AIC=%g BIC=%g NLogL=%g Iter=%d Cged=%d Reg=%g\n', ...
obj.AIC, obj.BIC, obj.NegativeLogLikelihood, ...
obj.NumIterations, obj.Converged, obj.RegularizationValue);
end
end
% Display Gaussian mixture distribution object
function c = display (obj)
disp(obj);
end
% Mahalanobis distance to component means
function D = mahal (obj,X)
X = checkX (obj, X, 'mahal');
[~, D] = componentProb (obj,X);
end
% Probability density function for Gaussian mixture distribution
function c = pdf (obj,X)
X = checkX (obj, X, 'pdf');
p_x_l = componentProb (obj, X);
c = sum (p_x_l, 2);
end
% Posterior probabilities of components
function c = posterior (obj,X)
X = checkX (obj, X, 'posterior');
p_x_l = componentProb (obj, X);
c = bsxfun(@rdivide, p_x_l, sum (p_x_l, 2));
end
% Random numbers from Gaussian mixture distribution
function c = random (obj,n)
c = zeros (n, obj.NumVariables);
classes = randsample (obj.NumVariables, n, true, obj.ComponentProportion);
if (obj.SharedCovariance)
if (obj.DiagonalCovariance)
sig = diag (obj.Sigma);
else
sig = obj.Sigma;
end
end
for i = 1:obj.NumComponents
idx = (classes == i);
k = sum(idx);
if k > 0
if (~obj.SharedCovariance)
if (obj.DiagonalCovariance)
sig = diag (obj.Sigma(:,:,i));
else
sig = obj.Sigma(:,:,i);
end
end
% [sig] forces [sig] not to have class 'diagonal',
% since mvnrnd uses automatic broadcast,
% which fails on structured matrices
c(idx,:) = mvnrnd ([obj.mu(i,:)], [sig], k);
end
end
end
end
%
methods (Static)
%Gaussian mixture parameter estimates
function c = fit (X,k,varargin)
c = fitgmdist (X,k,varargin);
end
end
%
methods (Access = private)
% Probability density of (row of) X *and* component l
% Second argument is an array of the Mahalonis distances
function [p_x_l, M] = componentProb (obj, X)
[r_X c_X] = size(X); %num of rows and cols in X
M = zeros (r_X, obj.NumComponents);
dets = zeros (1, obj.NumComponents); % sqrt(determinant)
if (obj.SharedCovariance)
if (obj.DiagonalCovariance)
r = diag (sqrt(obj.Sigma));
else
r = chol (obj.Sigma);
end
end
for i = 1:obj.NumComponents
dev = bsxfun (@minus, X, obj.mu(i,:));
if (~obj.SharedCovariance)
if (obj.DiagonalCovariance)
r = diag (sqrt (obj.Sigma(:,:,i)));
else
r = chol (obj.Sigma(:,:,i));
end
end
M(:,i) = sumsq (dev / r, 2);
dets(i) = prod (diag (r));
end
p_x_l = exp (-M/2);
coeff = obj.ComponentProportion ./ ((2*pi)^(obj.NumVariables/2).*dets);
p_x_l = bsxfun (@times, p_x_l, coeff);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Check format of argument X
function X = checkX (obj, X, name)
if (columns (X) ~= obj.NumVariables)
if (columns (X) == 1 && rows (X) == obj.NumVariables)
X = X';
else
error ('gmdistribution.%s: X has %d columns instead of %d\n', ...
name, columns (X), obj.NumVariables);
end
end
end
end
end
%~ mu = eye(2);
%~ Sigma = eye(2);
%~ GM = gmdistribution (mu, Sigma);
%~ density = GM.pdf ([0 0; 1 1]);
%~ assert (density(1) - density(2), 0, 1e-6);
%~
%~ [idx, nlogl, P, logpdf,M] = cluster (GM, eye(2));
%~ assert (idx, [1; 2]);
%~ [idx2,nlogl2,P2,logpdf2] = GM.cluster (eye(2));
%~ assert (nlogl - nlogl2, 0, 1e-6);
%~ [idx3,nlogl3,P3] = cluster (GM, eye(2));
%~ assert (P - P3, zeros (2), 1e-6);
%~ [idx4,nlogl4] = cluster (GM, eye(2));
%~ assert (size (nlogl4), [1 1]);
%~ idx5 = cluster (GM, eye(2));
%~ assert (idx - idx5, zeros (2,1));
%~
%~ D = GM.mahal ([1;0]);
%~ assert (D - M(1,:), zeros (1,2), 1e-6);
%~
%~ P = GM.posterior ([0 1]);
%~ assert (P - P2(2,:), zeros (1,2), 1e-6);
%~
%~ R = GM.random(20);
%~ assert (size(R), [20, 2]);