Files
2026-07-13 13:30:25 +08:00

178 lines
5.8 KiB
Python
Executable File

import numpy as np
from scipy.misc import logsumexp
from scipy.special import digamma, gamma
from prml.rv.rv import RandomVariable
class VariationalGaussianMixture(RandomVariable):
def __init__(self, n_components=1, alpha0=None, m0=None, W0=1., dof0=None, beta0=1.):
"""
construct variational gaussian mixture model
Parameters
----------
n_components : int
maximum numnber of gaussian components
alpha0 : float
parameter of prior dirichlet distribution
m0 : float
mean parameter of prior gaussian distribution
W0 : float
mean of the prior Wishart distribution
dof0 : float
number of degrees of freedom of the prior Wishart distribution
beta0 : float
prior on the precision distribution
"""
super().__init__()
self.n_components = n_components
if alpha0 is None:
self.alpha0 = 1 / n_components
else:
self.alpha0 = alpha0
self.m0 = m0
self.W0 = W0
self.dof0 = dof0
self.beta0 = beta0
def _init_params(self, X):
sample_size, self.ndim = X.shape
self.alpha0 = np.ones(self.n_components) * self.alpha0
if self.m0 is None:
self.m0 = np.mean(X, axis=0)
else:
self.m0 = np.zeros(self.ndim) + self.m0
self.W0 = np.eye(self.ndim) * self.W0
if self.dof0 is None:
self.dof0 = self.ndim
self.component_size = sample_size / self.n_components + np.zeros(self.n_components)
self.alpha = self.alpha0 + self.component_size
self.beta = self.beta0 + self.component_size
indices = np.random.choice(sample_size, self.n_components, replace=False)
self.mu = X[indices]
self.W = np.tile(self.W0, (self.n_components, 1, 1))
self.dof = self.dof0 + self.component_size
@property
def alpha(self):
return self.parameter["alpha"]
@alpha.setter
def alpha(self, alpha):
self.parameter["alpha"] = alpha
@property
def beta(self):
return self.parameter["beta"]
@beta.setter
def beta(self, beta):
self.parameter["beta"] = beta
@property
def mu(self):
return self.parameter["mu"]
@mu.setter
def mu(self, mu):
self.parameter["mu"] = mu
@property
def W(self):
return self.parameter["W"]
@W.setter
def W(self, W):
self.parameter["W"] = W
@property
def dof(self):
return self.parameter["dof"]
@dof.setter
def dof(self, dof):
self.parameter["dof"] = dof
def get_params(self):
return self.alpha, self.beta, self.mu, self.W, self.dof
def _fit(self, X, iter_max=100):
self._init_params(X)
for _ in range(iter_max):
params = np.hstack([p.flatten() for p in self.get_params()])
r = self._variational_expectation(X)
self._variational_maximization(X, r)
if np.allclose(params, np.hstack([p.flatten() for p in self.get_params()])):
break
def _variational_expectation(self, X):
d = X[:, None, :] - self.mu
maha_sq = -0.5 * (
self.ndim / self.beta
+ self.dof * np.sum(
np.einsum("kij,nkj->nki", self.W, d) * d, axis=-1))
ln_pi = digamma(self.alpha) - digamma(self.alpha.sum())
ln_Lambda = digamma(0.5 * (self.dof - np.arange(self.ndim)[:, None])).sum(axis=0) + self.ndim * np.log(2) + np.linalg.slogdet(self.W)[1]
ln_r = ln_pi + 0.5 * ln_Lambda + maha_sq
ln_r -= logsumexp(ln_r, axis=-1)[:, None]
r = np.exp(ln_r)
return r
def _variational_maximization(self, X, r):
self.component_size = r.sum(axis=0)
Xm = (X.T.dot(r) / self.component_size).T
d = X[:, None, :] - Xm
S = np.einsum('nki,nkj->kij', d, r[:, :, None] * d) / self.component_size[:, None, None]
self.alpha = self.alpha0 + self.component_size
self.beta = self.beta0 + self.component_size
self.mu = (self.beta0 * self.m0 + self.component_size[:, None] * Xm) / self.beta[:, None]
d = Xm - self.m0
self.W = np.linalg.inv(
np.linalg.inv(self.W0)
+ (self.component_size * S.T).T
+ (self.beta0 * self.component_size * np.einsum('ki,kj->kij', d, d).T / (self.beta0 + self.component_size)).T)
self.dof = self.dof0 + self.component_size
def classify(self, X):
"""
index of highest posterior of the latent variable
Parameters
----------
X : (sample_size, ndim) ndarray
input
Returns
-------
output : (sample_size, n_components) ndarray
index of maximum posterior of the latent variable
"""
return np.argmax(self._variational_expectation(X), 1)
def classify_proba(self, X):
"""
compute posterior of the latent variable
Parameters
----------
X : (sample_size, ndim) ndarray
input
Returns
-------
output : (sample_size, n_components) ndarray
posterior of the latent variable
"""
return self._variational_expectation(X)
def student_t(self, X):
nu = self.dof + 1 - self.ndim
L = (nu * self.beta * self.W.T / (1 + self.beta)).T
d = X[:, None, :] - self.mu
maha_sq = np.sum(np.einsum('nki,kij->nkj', d, L) * d, axis=-1)
return (
gamma(0.5 * (nu + self.ndim))
* np.sqrt(np.linalg.det(L))
* (1 + maha_sq / nu) ** (-0.5 * (nu + self.ndim))
/ (gamma(0.5 * nu) * (nu * np.pi) ** (0.5 * self.ndim)))
def _pdf(self, X):
return (self.alpha * self.student_t(X)).sum(axis=-1) / self.alpha.sum()