diff --git a/demos/demo_Riemannian_SPD_GMM_augmSigma01.m b/demos/demo_Riemannian_SPD_GMM_augmSigma01.m new file mode 100644 index 0000000000000000000000000000000000000000..f3f5a95471241ca40b6d119a701befb4e88c9857 --- /dev/null +++ b/demos/demo_Riemannian_SPD_GMM_augmSigma01.m @@ -0,0 +1,316 @@ +function demo_Riemannian_SPD_GMM_augmSigma01 +% GMM to encode ellipsoid datapoints (centers and covariance matrices) by relying on +% augmented covariance embeddings and Riemannian manifold +% +% If this code is useful for your research, please cite the related publication: +% @article{Calinon20RAM, +% author="Calinon, S.", +% title="Gaussians on {R}iemannian Manifolds: Applications for Robot Learning and Adaptive Control", +% journal="{IEEE} Robotics and Automation Magazine ({RAM})", +% year="2020", +% month="June", +% volume="27", +% number="2", +% pages="33--45", +% doi="10.1109/MRA.2020.2980548" +% } +% +% Copyright (c) 2019 Idiap Research Institute, https://idiap.ch/ +% Written by NoƩmie Jaquier and Sylvain Calinon +% +% This file is part of PbDlib, https://www.idiap.ch/software/pbdlib/ +% +% PbDlib is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License version 3 as +% published by the Free Software Foundation. +% +% PbDlib 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 PbDlib. If not, see <https://www.gnu.org/licenses/>. + +addpath('./m_fcts/'); + + +%% Parameters +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +nbData = 30; %Number of datapoints +nbSamples = 1; %Number of demonstrations +nbIter = 5; %Number of iteration for the Gauss Newton algorithm +nbIterEM = 5; %Number of iteration for the EM algorithm + +model.nbStates = 3; %Number of states in the GMM +model.nbVar = 3; %Dimension of the tangent space +model.nbVarVec = model.nbVar + model.nbVar*(model.nbVar-1)/2; % Dimension in vector form +model.params_diagRegFact = 1E1; %Regularization of covariance + + +% %% Generate augmented covariance data +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% for i=1:model.nbStates +% S(:,:,i) = cov(randn(3,model.nbVar-1)); +% end +% m = randn(model.nbVar-1, model.nbStates) * 1E0; +% x = zeros(model.nbVar, model.nbVar, nbData*nbSamples); +% xMu = zeros(model.nbVar-1, nbData*nbSamples); +% xSigma = zeros(model.nbVar-1, model.nbVar-1, nbData*nbSamples); +% idList = repmat(kron(1:model.nbStates,ones(1,ceil(nbData/model.nbStates))),1,nbSamples); +% for t=1:nbData*nbSamples +% xn = randn(model.nbVar-1,5) * 3E-1; +% xMu(:,t) = m(:,idList(t)) + randn(model.nbVar-1,1) * 3E-1; +% xSigma(:,:,t) = S(:,:,idList(t)) + cov(xn'); +% x(:,:,t) = [xSigma(:,:,t) + xMu(:,t)*xMu(:,t)', xMu(:,t); xMu(:,t)', 1]; +% end +% xvec = reshape(x, [model.nbVar^2, nbData*nbSamples]); +% uvec = logmap_vec(xvec, reshape(e0,[model.nbVar^2,1])); + + +%% Generate augmented covariance datapoints from handwriting data +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +demos=[]; +load('data/2Dletters/S.mat'); +Data=[]; +for n=1:nbSamples + s(n).Data = spline(1:size(demos{n}.pos,2), demos{n}.pos, linspace(1,size(demos{n}.pos,2),nbData)); %Resampling + Data = [Data [s(n).Data]]; +end +for t=1:nbData*nbSamples + xMu(:,t) = Data(:,t); + xSigma(:,:,t) = eye(model.nbVar-1) * 1E1; + %xSigma(:,:,t) = Data(:,t) * Data(:,t)' + eye(model.nbVar-1) * 1E0; + X(:,:,t) = [xSigma(:,:,t) + xMu(:,t)*xMu(:,t)', xMu(:,t); xMu(:,t)', 1]; +% X(:,:,t) = [xSigma(:,:,t) + xMu(:,t)*xMu(:,t)', xMu(:,t); xMu(:,t)', 1] .* (det(xSigma(:,:,t)).^(-1./(model.nbVar+1))); +end +x = symMat2vec(X); + + +%% GMM parameters estimation (Mandel notation) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +model = spd_init_GMM_kbins(x, model, nbSamples); +model.Mu = zeros(size(model.MuMan)); + +L = zeros(model.nbStates, nbData*nbSamples); +xts = zeros(model.nbVarVec, nbData*nbSamples, model.nbStates); +for nb=1:nbIterEM + % E-step + for i=1:model.nbStates + xts(:,:,i) = logmap_vec(x, model.MuMan(:,i)); + L(i,:) = model.Priors(i) * gaussPDF(xts(:,:,i), model.Mu(:,i), model.Sigma(:,:,i)); + + end + GAMMA = L ./ repmat(sum(L,1)+realmin, model.nbStates, 1); + H = GAMMA ./ repmat(sum(GAMMA,2)+realmin, 1, nbData*nbSamples); + + % M-step + for i=1:model.nbStates + % Update Priors + model.Priors(i) = sum(GAMMA(i,:)) / (nbData*nbSamples); + + % Update MuMan + for n=1:nbIter + uTmp = logmap_vec(x, model.MuMan(:,i)); + uTmpTot = sum(uTmp.*repmat(H(i,:),model.nbVarVec,1),2); + + model.MuMan(:,i) = expmap_vec(uTmpTot, model.MuMan(:,i)); + end + + % Update Sigma + model.Sigma(:,:,i) = uTmp * diag(H(i,:)) * uTmp' + eye(model.nbVarVec) .* model.params_diagRegFact; + end +end + +%Convert back augmented covariances to Gaussians +Mu = zeros(model.nbVar-1, model.nbStates); +Sigma = zeros(model.nbVar-1, model.nbVar-1, model.nbStates); +MuMan = vec2symMat(model.MuMan); +for i=1:model.nbStates + %Mu(:,i) = MuMan(1:end-1,end,i); + %Sigma(:,:,i) = MuMan(1:end-1,1:end-1,i) - Mu(:,i) * Mu(:,i)'; + beta = MuMan(end,end,i); + Mu(:,i) = MuMan(end,1:end-1,i) ./ beta; + Sigma(:,:,i) = MuMan(1:end-1,1:end-1,i) - beta * Mu(:,i) * Mu(:,i)'; +end + + +%% Plots +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +figure('PaperPosition',[0 0 8 8],'position',[10,10,650,650]); hold on; axis off; +clrmap = lines(model.nbStates); + +plotGMM(xMu, xSigma, [.6 .6 .6], .05); +for i=1:model.nbStates + plotGMM(Mu(:,i), Sigma(:,:,i)*.8, clrmap(i,:), .3); +end +axis equal; +%print('-dpng','graphs/demo_Riemannian_cov_GMM_augmSigma01.png'); + +%Plot activation function +figure; hold on; +clrmap = lines(model.nbStates); +for i=1:model.nbStates + plot(1:nbData, GAMMA(i,:),'linewidth',2,'color',clrmap(i,:)); +end +axis([1, nbData, 0, 1.02]); +set(gca,'xtick',[],'ytick',[]); +xlabel('t'); ylabel('h_i'); + +end + + +%% Functions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function X = expmap(U,S) +% Exponential map (SPD manifold) +N = size(U,3); +for n = 1:N +% X(:,:,n) = S^.5 * expm(S^-.5 * U(:,:,n) * S^-.5) * S^.5; + [v,d] = eig(S\U(:,:,n)); + X(:,:,n) = S * v*diag(exp(diag(d)))*v^-1; +end +end + +function x = expmap_vec(u,s) +% Exponential map (SPD manifold) +U = vec2symMat(u); +S = vec2symMat(s); +X = expmap(U,S); +x = symMat2vec(X); +end + +function U = logmap(X,S) +% Logarithm map +N = size(X,3); +for n = 1:N +% U(:,:,n) = S^.5 * logm(S^-.5 * X(:,:,n) * S^-.5) * S^.5; +% U(:,:,n) = S * logm(S\X(:,:,n)); + [v,d] = eig(S\X(:,:,n)); + U(:,:,n) = S * v*diag(log(diag(d)))*v^-1; +end +end + +function u = logmap_vec(x,s) +% Exponential map (SPD manifold) +X = vec2symMat(x); +S = vec2symMat(s); +U = logmap(X,S); +u = symMat2vec(U); +end + +function Ac = transp(S1,S2) +% Parallel transport (SPD manifold) +% t = 1; +% U = logmap(S2,S1); +% Ac = S1^.5 * expm(0.5 .* t .* S1^-.5 * U * S1^-.5) * S1^-.5; +Ac = (S2/S1)^.5; +end + +function M = spdMean(setS, nbIt) +% Mean of SPD matrices on the manifold +if nargin == 1 + nbIt = 10; +end +M = setS(:,:,1); + +for i=1:nbIt + L = zeros(size(setS,1),size(setS,2)); + for n = 1:size(setS,3) + L = L + logm(M^-.5 * setS(:,:,n)* M^-.5); + end + M = M^.5 * expm(L./size(setS,3)) * M^.5; +end + +end + +function model = spd_init_GMM_kbins(Data, model, nbSamples, spdDataId) +% K-Bins initialisation by relying on SPD manifold +nbData = size(Data,2) / nbSamples; +if ~isfield(model,'params_diagRegFact') + model.params_diagRegFact = 1E-4; %Optional regularization term to avoid numerical instability +end + +% Delimit the cluster bins for the first demonstration +tSep = round(linspace(0, nbData, model.nbStates+1)); + +% Compute statistics for each bin +for i=1:model.nbStates + id=[]; + for n=1:nbSamples + id = [id (n-1)*nbData+[tSep(i)+1:tSep(i+1)]]; + end + model.Priors(i) = length(id); + + % Mean computed on SPD manifold for parts of the data belonging to the + % manifold + if nargin < 4 + model.MuMan(:,i) = symMat2vec(spdMean(vec2symMat(Data(:,id)))); + else + model.MuMan(:,i) = mean(Data(:,id),2); + if iscell(spdDataId) + for c = 1:length(spdDataId) + model.MuMan(spdDataId{c},i) = symMat2vec(spdMean(vec2symMat(Data(spdDataId{c},id)),3)); + end + else + model.MuMan(spdDataId,i) = symMat2vec(spdMean(vec2symMat(Data(spdDataId,id)),3)); + end + end + + % Parts of data belonging to SPD manifold projected to tangent space at + % the mean to compute the covariance tensor in the tangent space + DataTgt = zeros(size(Data(:,id))); + if nargin < 4 + DataTgt = logmap_vec(Data(:,id),model.MuMan(:,i)); + else + DataTgt = Data(:,id); + if iscell(spdDataId) + for c = 1:length(spdDataId) + DataTgt(spdDataId{c},:) = logmap_vec(Data(spdDataId{c},id),model.MuMan(spdDataId{c},i)); + end + else + DataTgt(spdDataId,:) = logmap_vec(Data(spdDataId,id),model.MuMan(spdDataId,i)); + end + end + + model.Sigma(:,:,i) = cov(DataTgt') + eye(model.nbVarVec).*model.params_diagRegFact; + +end +model.Priors = model.Priors / sum(model.Priors); +end + +function V = symMat2vec(S) +% Vectorization of a tensor of symmetric matrix + +[D, ~, N] = size(S); + +V = []; +for n = 1:N + v = []; + v = diag(S(:,:,n)); + for d = 1:D-1 + v = [v; sqrt(2).*diag(S(:,:,n),d)]; % Mandel notation + % v = [v; diag(M,n)]; % Voigt notation + end + V = [V v]; +end + +end + +function S = vec2symMat(V) +% Transforms matrix of vectors to tensor of symmetric matrices + +[d, N] = size(V); +D = (-1 + sqrt(1 + 8*d))/2; +for n = 1:N + v = V(:,n); + M = diag(v(1:D)); + id = cumsum(fliplr(1:D)); + + for i = 1:D-1 + M = M + diag(v(id(i)+1:id(i+1)),i)./sqrt(2) + diag(v(id(i)+1:id(i+1)),-i)./sqrt(2); % Mandel notation + % M = M + diag(v(id(i+1)+1:id(i+1)),i) + diag(v(id(i+1)+1:id(i+1)),-i); % Voigt notation + end + S(:,:,n) = M; +end +end