0
$\begingroup$

I am learning about VI and am implementing a GMM model for clustering using variational inference. However, my implementation is not fitting the data at all, even when initializing the cluster means to their true values.

I have generated the following data using two mixture components, with means N(1, 1) and N(7, 2), 100 points each, plotted with jitter.

enter image description here

I have the following code for dealing with variational parameters

class VParamNormal:
    def __init__(self, size, m=None, log_s=None):
        if m is None: m = torch.randn(size) * 0.01
        if log_s is None: log_s = torch.randn(size) * 0.01
        self.vp = torch.stack([m, log_s])
        self.vp.requires_grad = True
        self.size = size

    def dist(self):
        return torch.distributions.Normal(self.vp[0], self.vp[1].exp())

    def rsample(self, n=torch.Size([])):
        return self.dist().rsample(n)

    def log_q(self, real):
        return self.dist().log_prob(real).sum()

and have done the same for both the Gamma and Dirichlet distribution, using exp(param) for alpha and beta in the Gamma distribution and the exp for the concentration parameters in the Dirichlet.

To calculate the ELBO and likelihood, I use the following. I have skipped priors here because I wanted to keep things as simple as possible, and adding them doesn't make it work either.

def log_priors(real_params, params):
    return 0

def log_q(model_params, real_params):
    out = 0
    for key in model_params:
        out += model_params[key].log_q(real_params[key])
    return out

def elbo(x, model_params):
    params = {}
    for key in model_params:
        params[key] = model_params[key].rsample()
        
    clust_assignments = (
        torch.log(params["w"]) +
        torch.distributions.Normal(params["means"], params["vars"]).log_prob(x[:, None].repeat(1, 2))
    )

    out = clust_assignments.sum()
    out += log_priors(params, params) 
    out -= log_q(model_params, params)

    return out

Because VI can't handle discrete variables, I marginalize out the categorical, drawn from the Dirichlet. Found in clust_assignments. Then, if I wanted to get the actual point clusters, I'd have to softmax these values (if my understanding is correct).

Then, optimization is simple, using standard techniques

model_params = {
    "means": VParamNormal(size=(2,), m=torch.tensor([1., 7.])),
    "vars": VParamGamma(size=(2,)),
    "w": VParamDirichlet([1., 1.])
}

optimizer = torch.optim.Adam([model_params[key].vp for key in model_params], lr=0.1)
elbo_hist = []

max_iter = 3000
for t in iters:
    loss = -elbo(x, model_params)
    elbo_hist.append(-loss.item())
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

However, when I fit this, the means do find their true values. Or in this case, I initialized them to the true means, and they drift together to something that might be the mean of all the points, as indicated by the red crosses.

I am a bit baffled by this and can't really see where I went wrong. Any help would be appreciated.

enter image description here

$\endgroup$

1 Answer 1

1
$\begingroup$

So, I've figured out the problem. When calculating the likelihood of the model, I was calculating

out = clust_assignments.sum()

however, this is not correct. I needed to calculate the log-sum-exp of for the cluster assignments, then sum those up. Changing this line to

out = torch.logsumexp(clust_assignments, dim=1).sum()

fixed the problem.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.