隐马尔科夫(HMM)的Matlab实现

说明:

1.      本文实现了PRML一书第13章的隐马尔科夫(HMM)算法,并与K-means聚类、GMM模型聚类进行了对比。当然,HMM的用处远不止是聚类;

2.      非职业码农,代码质量不高,变量命名也不规范,凑合着看吧,不好意思。


隐马尔科夫模型(HMM)是我一直想弄清楚的一种模型。可能是上学时随机过程只考了60分,心有不甘。


模型和算法本身不赘述了,详见PRML第13章。直接看结果,把隐变量z作为类别编号,HMM可用于聚类。这里可以看到,由于HMM利用了序列跳转信息(也就是马尔科夫特性),可以获得比混合高斯模型(GMM,http://blog.csdn.net/foreseerwang/article/details/75222522)更为准确的结果。


Matlab输出: 

****************************************************************

****************************************************************

K-means聚类计算,结果将作为HMM初始值......

K-means聚类算法结束

****************************************************************

GMM聚类计算中......

GMM聚类算法结束

****************************************************************

HMM EM算法迭代计算中......

loop 1, A error: 7.19e-01

loop 2, A error: 4.20e-02

loop 3, A error: 1.36e-02

loop 4, A error: 5.59e-03

loop 5, A error: 2.96e-03

loop 6, A error: 1.61e-03

loop 7, A error: 8.77e-04

loop 8, A error: 4.87e-04

loop 9, A error: 2.81e-04

loop 10, A error: 1.70e-04

loop 11, A error: 1.08e-04

loop 12, A error: 7.28e-05

HMM EM算法结束

****************************************************************

真实质心为      :[1.000,2.000], [-4.000, 2.000], [7.000, 1.000]

Kmeans聚类质心为:[0.647, 1.799],[-4.714, 2.394], [6.713, 0.870]

GMM聚类质心为   :[0.977, 2.009],[-4.179, 2.010], [6.945, 0.973]

HMM拟合质心为   :[1.011, 1.994],[-4.091, 2.005], [6.984, 1.001]

****************************************************************

Kmeans聚类错误率:10.517%

GMM  聚类错误率: 3.360%

HMM  拟合错误率: 1.673%

****************************************************************

真实转移概率矩阵A:

   0.8000    0.1000    0.1000

   0.2000    0.7000    0.1000

        0    0.5000    0.5000

HMM拟合转移概率矩阵A:

   0.7978    0.0981    0.1041

   0.2038    0.6963    0.0999

   0.0006    0.4987    0.5007

****************************************************************

聚类结果的图示:



代码:

%% by foreseer wang
% web: http://blog.csdn.net/foreseerwang
% QQ: 50834
%% 新买了个机械键盘,茶轴,码字很爽

clear all;
close all;
rng('default');
fprintf('****************************************************************\n');
fprintf('****************************************************************\n');

%% 初始化并生成测试数据
dim=[2,30000];   % k*N,k为数据维度,N为数据数目
Nclst=3;         % 簇的数量,也就是隐变量z可能取值的个数
k=dim(1);
N=dim(2);
Aerr=1e-4;       % 用于迭代终止

A0real = [0.2, 0.3, 0.5];                   % z1点分布概率
Areal = [0.8, 0.1, 0.1;...
         0.2, 0.7, 0.1;...
         0.0, 0.5, 0.5];                    % z序列转移概率矩阵

mureal = [1 2; -4 2; 7 1]';                 % p(x|z)的均值
sigreal=zeros(k,k,Nclst);                   % p(x|z)的方差
sigreal(:,:,1)=[2 -1.5; -1.5 2];
sigreal(:,:,2)=[5 -2.; -2. 3];
sigreal(:,:,3)=[1 0.1; 0.1 2];

zreal=zeros(1,dim(2));                      % 隐变量z
zreal(1)=randsrc(1,1,[1:Nclst;A0real]);
x=zeros(dim);                               % 可观测到的变量x
x(:,1)=funGaussSample(mureal(:,zreal(1)),squeeze(sigreal(:,:,zreal(1))),1);
for ii=2:dim(2),
    zreal(ii)=randsrc(1,1,[1:Nclst;Areal(zreal(ii-1),:)]);
    x(:,ii)=funGaussSample(mureal(:,zreal(ii)),squeeze(sigreal(:,:,zreal(ii))),1);
end;

%% 利用EM算法求解HMM问题
% 只有序列x是已知量
% 通过x求解mu(p(x|z)的均值)、sig(p(x|z)的协方差)、
%   A0(z1的分布概率)、A(z序列的状态跳转概率矩阵)

% 其中涉及的中间变量包括(详见PRML第13章):
%   alpha(zn) = p(x1,..., xn, zn)(未用到)
%   alp_caret(zn) = p(zn|x1,..., xn) = alpha(zn)/p(x1, ..., xn)
%   beta(zn) = p(x(n+1), ..., xN|zn)(未用到)
%   bet_caret(zn) = p(x(n+1), ..., xN|zn)/p(x(n+1), ..., xN|x1,...xn)
%               = beta(zn)/p(x(n+1), ..., xN|x1,...xn)
%   c(n) = p(xn|x1, ..., x(n-1))

%   gamma(zn) = p(zn|X)=alpha(zn)*beta(zn)/p(X) = alp_caret(zn)*bet_caret(zn)
%   xi(z(n-1), zn) = alpha(z(n-1))*p(xn|zn)*p(zn|z(n-1))*beta(zn)/p(X)
%                  = alp_caret(z(n-1))*p(xn|zn)*p(zn|z(n-1))*bet_caret(zn)/c(n)

% 算法步骤:
% 1. 变量随机初始化,包括:mu(k*Nclst)、sig(k*k*Nclst)、A0(1*Nclst)、
%    A(k*k)、z(Nclst*N)
% 2. E步和M步轮换迭代:
%    2.1 使用forward-backward算法alp_caret和bet_caret,进而得出gama和xi
%    2.2 M步:重新计算mu、sig、A0、A
% 3. 使用Viterbi算法求得最可能的z序列

% 1. 初始化 ---------------------------------------------------------------
mu = zeros(k,Nclst);
sig = zeros(k,k,Nclst);
z = zeros(Nclst,N);

% K-means聚类,结果作为HMM的初始值
fprintf('K-means聚类计算,结果将作为HMM初始值......\n');
k_idx=kmeans(x',Nclst);         % k_idx即为聚类后的z
tmp_k_idx1=zeros(1,Nclst);
mu_kmeans=zeros(k, Nclst);
for ii=1:Nclst,
    idx=(k_idx==ii);
    tmp_mu=mean(x(:,idx),2);    
    % 旋转操作,确保类别编号与真实数据一致
    dist=sum((repmat(tmp_mu,1,Nclst)-mureal).^2);
    [dummy,tmp_idx]=min(dist);
    mu_kmeans(:,tmp_idx)=tmp_mu;
    mu(:,tmp_idx)=tmp_mu;
    sig(:,:,tmp_idx)=cov(x(:,idx)');
    tmp_k_idx1(ii)=tmp_idx;
end;

tmp_k_idx2=k_idx;
for ii=1:Nclst,
    k_idx(tmp_k_idx2==ii)=tmp_k_idx1(ii);
end;
errrate_Clst=sum(abs(k_idx'-zreal)>0.5)*1.0/N; % kmeans聚类错误率
fprintf('K-means聚类算法结束\n');
fprintf('****************************************************************\n');

% GMM聚类 -----------------------------------------------------------------
% 此处仅为对比,相对独立。删除此处横线间的代码及最后的相关打印代码,对结果无影响
fprintf('GMM聚类计算中......\n');
k_idx_GMM=funGMM(x',Nclst);     % k_idx_GMM为GMM聚类后的z
mu_GMM = zeros(k,Nclst);
sig_GMM = zeros(k,k,Nclst);
for ii=1:Nclst,
    idx=(k_idx_GMM==ii);
    tmp_mu=mean(x(:,idx),2);    % 旋转
    dist=sum((repmat(tmp_mu,1,Nclst)-mureal).^2);
    [dummy,tmp_idx]=min(dist);
    mu_GMM(:,tmp_idx)=tmp_mu;
    sig_GMM(:,:,tmp_idx)=cov(x(:,idx)');
    tmp_k_idx1(ii)=tmp_idx;
end;

tmp_k_idx2=k_idx_GMM;
for ii=1:Nclst,
    k_idx_GMM(tmp_k_idx2==ii)=tmp_k_idx1(ii);
end;

errrate_GMM=sum(abs(k_idx_GMM'-zreal)>0.5)*1.0/N; % GMM聚类错误率
fprintf('GMM聚类算法结束\n');
fprintf('****************************************************************\n');
% GMM聚类结束 --------------------------------------------------------------

% K-means聚类结果赋给隐变量z
for ii=1:N,
    z(k_idx(ii),ii)=1;
end;

% 随机初始化A0和A
tmp = rand(1,Nclst);
A0 = tmp/sum(tmp);
tmp = rand(Nclst, Nclst);
A = tmp./repmat(sum(tmp,2),1,Nclst);

% 2. EM迭代
alp_caret = zeros(Nclst, N);
c=zeros(1,N);
Pzx=zeros(Nclst,N);         % p(x|z)

bet_caret = zeros(Nclst, N);
bet_caret(:,N) = ones(Nclst, 1);

gama = zeros(Nclst,N);
xi = zeros(Nclst,Nclst,N-1);
Aold=ones(size(A))/Nclst;
fprintf('HMM EM算法迭代计算中......\n');

for ii=1:100,
    for jj=1:Nclst,
        Pzx(jj,:)=mvnpdf(x',mu(:,jj)',squeeze(sig(:,:,jj)))';
    end;
    
    % 2.1 E step ----------------------------------------------------------
    tmp1=A0'.*Pzx(:,1);    % 实际上是alpha(z1)
    c(1)=sum(tmp1);
    alp_caret(:,1)=tmp1/c(1);
    % 迭代计算PRML式13.59
    for kk=2:N,
        tmp1=alp_caret(:,kk-1)'*A;
        tmp2=tmp1'.*Pzx(:,kk);
        c(kk)=sum(tmp2);
        alp_caret(:,kk)=tmp2/c(kk);
    end;
    % end of forward. alpha_caret got
    
    % 迭代计算PRML式13.62
    for kk=N:-1:2,
        tmp2=A*(Pzx(:,kk).*bet_caret(:,kk));
        bet_caret(:,kk-1)=tmp2/c(kk);
    end;
    % end of backward. beta_caret got
    
    gama = alp_caret.*bet_caret;
    for jj=1:N-1,
        xi(:,:,jj)=alp_caret(:,jj)/c(jj+1)*...
            (Pzx(:,jj+1).*bet_caret(:,jj+1))'.*A;
    end;
    % gama and xi got
    
    % 2.2 M step ----------------------------------------------------------
    A0=gama(:,1)'/sum(gama(:,1));
    A=sum(xi,3)./repmat(sum(sum(xi,3),2),1,Nclst);
    mu=x*gama'./repmat(transpose(sum(gama,2)),k,1);
    for kk=1:Nclst,
        sig(:,:,kk)=(repmat(gama(kk,:),k,1).*(x-repmat(mu(:,kk),1,N)))*...
            (x-repmat(mu(:,kk),1,N))'/sum(gama(kk,:));
    end;
    % M step completed
    
    iter_err=sum(sum(abs(A-Aold)))/Nclst;
    Aold=A;
    fprintf('loop %2d, A error: %4.2e \n', ii, iter_err);
    if iter_err<Aerr,
        break;
    end;
end;
fprintf('HMM EM算法结束\n');
fprintf('****************************************************************\n');

for jj=1:Nclst,
    Pzx(jj,:)=mvnpdf(x',mu(:,jj)',squeeze(sig(:,:,jj)))';
end;

% 3. Viterbi算法确定最可能的序列 -------------------------------------------
w = zeros(Nclst,N);
psi = zeros(Nclst, N-1);
zpred=zeros(1,N);

w(:,1)=log(A0')+log(Pzx(:,1));
% 迭代计算PRML式13.70
for ii=1:N-1,
    tmp_1=log(A)+repmat(w(:,ii),1,Nclst);
    [tmp_max, tmp_idx]=max(tmp_1, [], 1);
    w(:,ii+1)=log(Pzx(:,ii+1))+tmp_max';
    psi(:,ii)=tmp_idx';
end;

[dummy, tmp_idx]=max(w(:,N));
% 反向计算PRML式13.71
zpred(N)=tmp_idx;
for ii=N-1:-1:1,
    zpred(ii)=psi(zpred(ii+1),ii);
end;

errrate_HMM=sum(abs(zpred-zreal)>0.5)*1.0/N;
for ii=1:Nclst,
    mu(:,ii)=mean(x(:,zpred==ii),2);
end;

%% 结果打印
% 打印聚类质心
fprintf('真实质心为      :[%5.3f, %5.3f], [%5.3f, %5.3f], [%5.3f, %5.3f]\n',...
    mureal(1,1),mureal(2,1),mureal(1,2),mureal(2,2),mureal(1,3),mureal(2,3));
fprintf('Kmeans聚类质心为:[%5.3f, %5.3f], [%5.3f, %5.3f], [%5.3f, %5.3f]\n',...
    mu_kmeans(1,1),mu_kmeans(2,1),mu_kmeans(1,2),...
    mu_kmeans(2,2),mu_kmeans(1,3),mu_kmeans(2,3));
fprintf('GMM聚类质心为   :[%5.3f, %5.3f], [%5.3f, %5.3f], [%5.3f, %5.3f]\n',...
    mu_GMM(1,1),mu_GMM(2,1),mu_GMM(1,2),mu_GMM(2,2),mu_GMM(1,3),mu_GMM(2,3));
fprintf('HMM拟合质心为   :[%5.3f, %5.3f], [%5.3f, %5.3f], [%5.3f, %5.3f]\n',...
    mu(1,1),mu(2,1),mu(1,2),mu(2,2),mu(1,3),mu(2,3));

% 打印散点图,直观观看效果
% 可以看到,HMM算法中用到了序列信息,结果更为准确
figure(1); 
subplot(2,2,1); hold on;
scatter(x(1,zreal==1),x(2,zreal==1),'ko');
scatter(x(1,zreal==2),x(2,zreal==2),'go');
scatter(x(1,zreal==3),x(2,zreal==3),'bo');
xlabel('x1');
ylabel('x2');
axis([-15,15,-5,10]);
title('1. 原始数据', 'FontSize', 20);

subplot(2,2,2); hold on;
scatter(x(1,k_idx==1),x(2,k_idx==1),'ko');
scatter(x(1,k_idx==2),x(2,k_idx==2),'go');
scatter(x(1,k_idx==3),x(2,k_idx==3),'bo');
xlabel('x1');
ylabel('x2');
axis([-15,15,-5,10]);
title('2. K-means聚类结果', 'FontSize', 20);

subplot(2,2,3); hold on;
scatter(x(1,k_idx_GMM==1),x(2,k_idx_GMM==1),'ko');
scatter(x(1,k_idx_GMM==2),x(2,k_idx_GMM==2),'go');
scatter(x(1,k_idx_GMM==3),x(2,k_idx_GMM==3),'bo');
xlabel('x1');
ylabel('x2');
axis([-15,15,-5,10]);
title('3. GMM聚类结果', 'FontSize', 20);

subplot(2,2,4); hold on;
scatter(x(1,zpred==1),x(2,zpred==1),'ko');
scatter(x(1,zpred==2),x(2,zpred==2),'go');
scatter(x(1,zpred==3),x(2,zpred==3),'bo');
xlabel('x1');
ylabel('x2');
axis([-15,15,-5,10]);
title('4. HMM结果', 'FontSize', 20);

% 打印聚类错误率,其实就是隐变量z的错误率
fprintf('****************************************************************\n');
fprintf('Kmeans聚类错误率:%6.3f%%\n', errrate_Clst*100);
fprintf('GMM   聚类错误率:%6.3f%%\n', errrate_GMM*100);
fprintf('HMM   拟合错误率:%6.3f%%\n', errrate_HMM*100);
fprintf('****************************************************************\n');

% 打印z序列转移概率矩阵
fprintf('真实转移概率矩阵A:\n');
disp(Areal);
fprintf('HMM拟合转移概率矩阵A:\n');
disp(A);
fprintf('****************************************************************\n');


子程序1(就一句话,好像有点多此一举):

function z = funGaussSample( mu, sigma, dim )
%GAUSSAMPLE Summary of this function goes here
%   Detailed explanation goes here

%R = chol(sigma);
%z = repmat(mu,dim(1),1) + randn(dim)*R;
z = mvnrnd(mu, sigma, dim(1));

end

子程序2(混合高斯模型聚类算法):

function clst_idx = funGMM( z, Nclst )
%% by foreseer wang
% web: http://blog.csdn.net/foreseerwang
% QQ: 50834

[len, k]=size(z);

k_idx=kmeans(z,Nclst);     % 用K-means聚类结果做GMM聚类的初始值
mu=zeros(Nclst,k);
sigma=zeros(k,k,Nclst);
for ii=1:Nclst,
    mu(ii,:)=mean(z(k_idx==ii,:));
    sigma(:,:,ii)=cov(z(k_idx==ii,:));
end;
Pw=ones(Nclst,1)*1.0/Nclst;

px=zeros(len,Nclst);
r=zeros(len,Nclst);
for jj=1:1000, % 简单起见,直接循环,不做结束判断
    for ii=1:Nclst,
        px(:,ii)=mvnpdf(z,mu(ii,:),squeeze(sigma(:,:,ii)));
    end;
    
    % E step
    temp=px.*repmat(Pw',len,1);
    r=temp./repmat(sum(temp,2),1,Nclst);

    % M step
    rk=sum(r);
    pw=rk/len;
    mu=r'*z./repmat(rk',1,k);
    for ii=1:Nclst
        sigma(:,:,ii)=z'*(repmat(r(:,ii),1,k).*z)/rk(ii)-mu(ii,:)'*mu(ii,:);
    end;
end;

% display
[dummy,clst_idx]=max(px,[],2);

end




猜你喜欢

转载自blog.csdn.net/foreseerwang/article/details/77773327