GMM 的EM 实现
算法逻辑在这里:
http://www.cnblogs.com/Azhu/p/4131733.html
贴之前先说下,本来呢是打算自己写一个的,在matlab 上,不过,实在是写不出来那么高效和健壮的,网上有很多实现的代码,例如上面参考里面的,那个代码明显有问题阿,然后因为那里面的代码与逻辑分析是一致的,那在其基础上修改看看,结果发现代码健壮性实在太差了,我的数据集是 70-by-2000 的矩阵,70个样本2000维,结果协方差的逆根本算不出来,全部是inf,那去前50维,还是算不出来,这个虽然逻辑是对的,但是这软件的局限阿。
那只能用其他方法了,有一个写的很好的,下面会贴出来,不过都是矩阵运算,看是能看懂的,不过数学计算实在写不出来,按这么来的也只是跟着其敲了一遍,敲之前还看了半天才懂其的数学计算,matlab 的内置函数也不算熟,这里就顺便写下来好了。
主函数:
- 12-26 行是初始化类标号和其他参数,12行调用了初始标号的参数,实际上初始化的是R。
- R 是一个n-by-k 矩阵,每项表示一个i-th 样本在 j-th GM 中的概率值,就是p(x|k)。
- 因为是初始化,所以14行获取了当前类标号label。
- 27 - 40 是迭代部分,通过判断是否收敛和迭代次数循环
- 29 是m-step, 这跟我写的算法逻辑有点不同,不过不影响。
- 29 m-step是假设知道了标号,训练GMM 模型参数,获得的是model。
- 30 是 e-step,假设训练好了GMM ,计算样本的分配情况,其中loglikehood 是在e-step 中计算了。
- 剩下的是收敛判断
1 function [label, model, llh] = emgm(X, init) 2 % Perform EM algorithm for fitting the Gaussian mixture model. 3 % X: d x n data matrix 4 % init: k (1 x 1) or label (1 x n, 1<=label(i)<=k) or center (d x k) 5 % Written by Michael Chen (sth4nth@gmail.com). 6 %% initialization 7 % fprintf('EM for Gaussian mixture: running ... \n'); 8 % load('final_initlize'); 9 % X = dataset(1).x'; 10 % init = dataset(1).y'; 11 % R n-by-k 矩阵,表示i-th 样本属于j-th 个类的概率,初始化时候为1、0,迭代后变是权重化了。 12 R = initialization(X,init); 13 % label 表示n 个样本的类标号。 14 [~,label(1,:)] = max(R,[],2); 15 % 这句是为了处理类标号不连续的情况 16 R = R(:,unique(label)); 17 18 %pect = zeros(size(label)); 19 % tol 是阀值控制 20 tol = 1e-10; 21 maxiter = 500; 22 % loglikehood 23 llh = -inf(1,maxiter); 24 converged = false; 25 % 当前迭代的标号 26 t = 1; 27 while ~converged && t < maxiter 28 t = t+1; 29 model = maximization(X,R); 30 [R, llh(t)] = expectation(X,model); 31 32 [~,label(:)] = max(R,[],2); 33 u = unique(label); % non-empty components 34 if size(R,2) ~= size(u,2) 35 R = R(:,u); % remove empty components 36 else 37 converged = llh(t)-llh(t-1) < tol*abs(llh(t)); 38 end 39 40 end 41 llh = llh(2:t); 42 % if converged 43 % fprintf('Converged in %d steps.\n',t-1); 44 % llh = t-1; 45 % else 46 % fprintf('Not converged in %d steps.\n',maxiter); 47 % llh = maxiter; 48 % end
初始化函数:
这个函数很简单,没什么好解释的。
1 %% init 2 function R = initialization(X, init) 3 % 初始化一共用4中方式,一种是给定GMM 模型的参数初始值,一种是给定k 的个数,一种是给各sample 的标号,一种是给出类的中心点 4 [d,n] = size(X); 5 if isstruct(init) % initialize with a model 6 R = expectation(X,init); 7 elseif length(init) == 1 % random initialization 8 k = init; 9 idx = randsample(n,k); 10 m = X(:,idx); 11 [~,label] = max(bsxfun(@minus,m'*X,dot(m,m,1)'/2),[],1); 12 [u,~,label] = unique(label); 13 while k ~= length(u) 14 idx = randsample(n,k); 15 m = X(:,idx); 16 [~,label] = max(bsxfun(@minus,m'*X,dot(m,m,1)'/2),[],1); 17 [u,~,label] = unique(label); 18 end 19 R = full(sparse(1:n,label,1,n,k,n)); 20 elseif size(init,1) == 1 && size(init,2) == n % initialize with labels 21 label = init; 22 k = max(label); 23 R = full(sparse(1:n,label,1,n,k,n)); 24 elseif size(init,1) == d %initialize with only centers 25 k = size(init,2); 26 m = init; 27 [~,label] = max(bsxfun(@minus,m'*X,dot(m,m,1)'/2),[],1); 28 R = full(sparse(1:n,label,1,n,k,n)); 29 else 30 error('ERROR: init is not valid.'); 31 end
m-step函数:
- 输入参数 R解释参考上面。
- 7 计算各类的sample 个数和,一个1-by-k matrix。
- 8 7中的值除以样本总数就是 GM 的权重,同样是1-by-k matrix。
- 9 计算GM 样本均值,mu 是个d-by-k matrix,每列表示 k-th GM 的样本均值。
- 19 计算sqrtR 是为了 15-17行的计算中 结果刚好是R。
- 15-17 sigma
- 18行 应该是为了避免sigma 不能逆。
1 %% m-step 2 function model = maximization(X, R) 3 [d,n] = size(X); 4 % k 为类个数 5 k = size(R,2); 6 % 各类的sample个数 7 nk = sum(R,1); 8 w = nk/n; 9 mu = bsxfun(@times, X*R, 1./nk); 10 11 Sigma = zeros(d,d,k); 12 % 这个值是为了下面计算时候得到R, 13 sqrtR = sqrt(R); 14 for i = 1:k 15 Xo = bsxfun(@minus,X,mu(:,i)); 16 Xo = bsxfun(@times,Xo,sqrtR(:,i)'); 17 Sigma(:,:,i) = Xo*Xo'/nk(i); 18 Sigma(:,:,i) = Sigma(:,:,i)+eye(d)*(1e-6); % add a prior for numerical stability 19 end 20 21 model.mu = mu; 22 model.Sigma = Sigma; 23 model.weight = w;
e-step:
- e step 需要解释很多阿。
- 9 logRho,首先我们知道R 是每项表示一个i-th 样本在 j-th GM 中的概率值,计算公式如下,公式中x是d-by-1 的sample,也就是gamma 中的N()
-
%Gaussian posterior probability
%N(x|pMiu,pSigma) = 1/((2pi)^(D/2))*(1/(abs(sigma))^0.5)*exp(-1/2*(x-pMiu)'pSigma^(-1)*(x-pMiu)) - 问题是上面公式不一定能按步求出来阿,例如 sigma^-1,不一定解得出来阿,所以对上面得N()log 一下,后计算,同时避开计算sigma^-1,这个矩阵就是logRho
- 20-31 便是12 行的函数调用,其中涉及了一堆矩阵转换,验证过没有错,计算的就是log 后的 N()
- 14 上面公式 gamma 的分子部分。
- 15-16 是计算当前的loglikehood。
- 17 计算R 矩阵的log 形式。
- 18 反计算R。
1 %% e-step 2 function [R, llh] = expectation(X, model) 3 mu = model.mu; 4 Sigma = model.Sigma; 5 w = model.weight; 6 7 n = size(X,2); 8 k = size(mu,2); 9 logRho = zeros(n,k); 10 11 for i = 1:k 12 logRho(:,i) = loggausspdf(X,mu(:,i),Sigma(:,:,i)); 13 end 14 logRho = bsxfun(@plus,logRho,log(w)); 15 T = logsumexp(logRho,2); 16 llh = sum(T)/n; % loglikelihood 17 logR = bsxfun(@minus,logRho,T); 18 R = exp(logR); 19 %% log pdf 20 function y = loggausspdf(X, mu, Sigma) 21 22 d = size(X,1); 23 X = bsxfun(@minus,X,mu); 24 [U,p]= chol(Sigma); 25 if p ~= 0 26 error('ERROR: Sigma is not PD.'); 27 end 28 Q = U'\X; 29 q = dot(Q,Q,1); % quadratic term (M distance) 30 c = d*log(2*pi)+2*sum(log(diag(U))); % normalization constant 31 y = -(c+q)/2;