Simulating and Fitting a Gaussian Mixture Model

This tutorial demonstrates how to use StateSpaceDynamics.jl to create a Probabilistic PCA model and fit it using the EM algorithm.

Load Packages

using StateSpaceDynamics
using LinearAlgebra
using Random
using Plots
using StatsPlots
using StableRNGs
using Distributions
rng = StableRNG(1234);

Create a State-Space Model

D = 2
k = 2
2

define true parameters for the model to sample from

W_true = [
   -1.64   0.2;
    0.9  -2.8
]

σ²_true = 0.5
μ_true = [1.65, -1.3]

ppca = ProbabilisticPCA(W_true, σ²_true, μ_true)
ProbabilisticPCA{Float64, Matrix{Float64}, Vector{Float64}}([-1.64 0.2; 0.9 -2.8], 0.5, [1.65, -1.3], 2, 2, Matrix{Float64}(undef, 2, 0))

Sample data from the model

num_obs = 500
X, z = rand(rng, ppca, num_obs)
([-0.16292996949597693 4.4667986169103875 … -1.664030995267812 2.256120297611268; -2.8032655940478572 0.8620051665088987 … 3.015699033993531 1.8530191339070186], [0.5194945485037596 -1.6756611579095566 … 1.2133013606558642 -0.42285308575916947; 0.9051397268611271 -1.2947334444127405 … -1.1944870517261137 -1.4742335297619584])

Plot sampled data from the model

x1 = X[1, :]
x2 = X[2, :]
labels = map(i -> (abs(z[1,i]) > abs(z[2,i]) ? 1 : 2), 1:size(z,2))

p = plot()

scatter!(
    p, x1, x2;
    group      = labels,
    xlabel     = "X₁",
    ylabel     = "X₂",
    title      = "Samples grouped by dominant latent factor",
    label= ["Latent 1" "Latent 2"],
    legend     = :topright,
    markersize = 5,
)

p
Example block output

Paramter recovery: Initialize a new model with default parameters and fit to the data using EM.

#define default parameters
D = 2
k = 2
W = randn(rng, D, k)
σ² = 0.5
μ_vector = randn(rng, 2)

fit_ppca = ProbabilisticPCA(W, σ², μ_vector)
ProbabilisticPCA{Float64, Matrix{Float64}, Vector{Float64}}([-0.6286783412298981 1.6477984375260266; -1.9544959850153094 2.20883182977146], 0.5, [-1.2857779269609082, 0.6481990189960374], 2, 2, Matrix{Float64}(undef, 2, 0))

Fit model using EM Algorithm

lls = fit!(fit_ppca, X)
26-element Vector{Float64}:
 -2942.9846914566488
 -2649.021975079838
 -2596.3727771298704
 -2578.2703501279584
 -2571.2629060042264
 -2568.437824572584
 -2567.265115905659
 -2566.7600331093895
 -2566.5343236584004
 -2566.430444632034
     ⋮
 -2566.337136047855
 -2566.3369917179375
 -2566.3369215564903
 -2566.336887441408
 -2566.336870850639
 -2566.3368627813325
 -2566.3368588563276
 -2566.3368569470554
 -2566.336856018277

Confirm model convergence using log likelihoods

ll_plot = plot(
    lls;
    xlabel="Iteration",
    ylabel="Log-Likelihood",
    title="EM Convergence (PPCA)",
    marker=:circle,
    label="log_likelihood",
    reuse=false,
)

ll_plot
Example block output

Plot the learned lower dimension latent space over the Data

x1, x2 = X[1, :], X[2, :]
μ1, μ2  = fit_ppca.μ
W_fit = fit_ppca.W
w1      = W_fit[:, 1]
w2      = W_fit[:, 2]

P = plot()

scatter!(
    P, x1, x2;
    xlabel     = "X₁",
    ylabel     = "X₂",
    title      = "Data with PPCA loading directions",
    label      = "Data",
    alpha      = 0.5,
    markersize = 4,
)

k = size(W_fit, 2)
bases_x = repeat([μ1], k)
bases_y = repeat([μ2], k)
2-element Vector{Float64}:
 0.6481990189960374
 0.6481990189960374

Add the component arrows in both directions

quiver!(
  P, [μ1], [μ2];
  quiver      = ([ w1[1]], [ w1[2]]),
  arrow       = :arrow, lw=3,
  color       = :red,
  label       = "W₁"
)
quiver!(
  P, [μ1], [μ2];
  quiver      = ([-w1[1]], [-w1[2]]),
  arrow       = :arrow, lw=3,
  color       = :red,
  label       = ""
)

quiver!(
  P, [μ1], [μ2];
  quiver      = ([ w2[1]], [ w2[2]]),
  arrow       = :arrow, lw=3,
  color       = :green,
  label       = "W₂"
)
quiver!(
  P, [μ1], [μ2];
  quiver      = ([-w2[1]], [-w2[2]]),
  arrow       = :arrow, lw=3,
  color       = :green,
  label       = ""
)

P
Example block output

This page was generated using Literate.jl.