17
$\begingroup$

I am currently trying to compute the BIC for my toy data set (ofc iris (: ). I want to reproduce the results as shown here (Fig. 5). That paper is also my source for the BIC formulas.

I have 2 problems with this:

  • Notation:
    • $n_i$ = number of elements in cluster $i$
    • $C_i$ = center coordinates of cluster $i$
    • $x_j$ = data points assigned to cluster $i$
    • $m$ = number of clusters

1) The variance as defined in Eq. (2):

$$ \sum_i = \frac{1}{n_i-m}\sum_{j=1}^{n_i}\Vert x_j - C_i \Vert^2 $$

As far as I can see it is problematic and not covered that the variance can be negative when there are more clusters $m$ than elements in the cluster. Is this correct?

2) I just cannot make my code work to compute the correct BIC. Hopefully there is no error, but it would be highly appreciated if someone could check. The whole equation can be found in Eq. (5) in the paper. I am using scikit learn for everything right now (to justify the keyword :P ).

from sklearn import cluster
from scipy.spatial import distance
import sklearn.datasets
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np

def compute_bic(kmeans,X):
    """
    Computes the BIC metric for a given clusters

    Parameters:
    -----------------------------------------
    kmeans:  List of clustering object from scikit learn

    X     :  multidimension np array of data points

    Returns:
    -----------------------------------------
    BIC value
    """
    # assign centers and labels
    centers = [kmeans.cluster_centers_]
    labels  = kmeans.labels_
    #number of clusters
    m = kmeans.n_clusters
    # size of the clusters
    n = np.bincount(labels)
    #size of data set
    N, d = X.shape

    #compute variance for all clusters beforehand
    cl_var = [(1.0 / (n[i] - m)) * sum(distance.cdist(X[np.where(labels == i)], [centers[0][i]], 'euclidean')**2)  for i in xrange(m)]

    const_term = 0.5 * m * np.log10(N)

    BIC = np.sum([n[i] * np.log10(n[i]) -
           n[i] * np.log10(N) -
         ((n[i] * d) / 2) * np.log10(2*np.pi) -
          (n[i] / 2) * np.log10(cl_var[i]) -
         ((n[i] - m) / 2) for i in xrange(m)]) - const_term

    return(BIC)



# IRIS DATA
iris = sklearn.datasets.load_iris()
X = iris.data[:, :4]  # extract only the features
#Xs = StandardScaler().fit_transform(X)
Y = iris.target

ks = range(1,10)

# run 9 times kmeans and save each result in the KMeans object
KMeans = [cluster.KMeans(n_clusters = i, init="k-means++").fit(X) for i in ks]

# now run for each cluster the BIC computation
BIC = [compute_bic(kmeansi,X) for kmeansi in KMeans]

plt.plot(ks,BIC,'r-o')
plt.title("iris data  (cluster vs BIC)")
plt.xlabel("# clusters")
plt.ylabel("# BIC")

My results for the BIC look like this:

Which is not even close to what I have expected and also makes no sense... I looked at the equations now for some time and am not getting any further locating my mistake ):

$\endgroup$
6
  • $\begingroup$ You may find computation of BIC for clustering here. It is how SPSS does it. Not necessarily exactly the same way as you show. $\endgroup$
    – ttnphns
    Commented Mar 20, 2014 at 21:26
  • $\begingroup$ Thank you ttnphns. I saw your answer before. But that has no reference on how the steps are derived and thus not what I was looking for. Moreover this SPSS output or whatever the syntax is is not very readable. Thank you anyway. Due to the lack of interest in this questions I'll look for the reference and use another estimate for the variance. $\endgroup$
    – Kam Sen
    Commented Mar 24, 2014 at 13:11
  • $\begingroup$ I know this does not answer your question (thus I leave it as a comment), but the R package mclust fits finite mixture models (a parametric clustering method) and automatically optimizes the number, shape, size, orientation, and heterogeneity of clusters. I understand you're using sklearn but just wanted to throw that out there. $\endgroup$ Commented Apr 15, 2015 at 17:57
  • 1
    $\begingroup$ Brash, sklearn also has GMM $\endgroup$ Commented Apr 16, 2015 at 8:00
  • $\begingroup$ @KamSen can you please help me here? :- stats.stackexchange.com/questions/342258/… $\endgroup$
    – Batman22
    Commented Apr 23, 2018 at 13:45

4 Answers 4

17
$\begingroup$

It seems you have a few errors in your formulas, as determined by comparing to:

1.

np.sum([n[i] * np.log(n[i]) -
               n[i] * np.log(N) -
             ((n[i] * d) / 2) * np.log(2*np.pi) -
              (n[i] / 2) * np.log(cl_var[i]) -
             ((n[i] - m) / 2) for i in range(m)]) - const_term

Here there are three errors in the paper, fourth and fifth lines are missing a factor of d, the last line substitute m for 1. It should be:

np.sum([n[i] * np.log(n[i]) -
               n[i] * np.log(N) -
             ((n[i] * d) / 2) * np.log(2*np.pi*cl_var) -
             ((n[i] - 1) * d/ 2) for i in range(m)]) - const_term

2.

The const_term:

const_term = 0.5 * m * np.log(N)

should be:

const_term = 0.5 * m * np.log(N) * (d+1)

3.

The variance formula:

cl_var = [(1.0 / (n[i] - m)) * sum(distance.cdist(p[np.where(label_ == i)], [centers[0][i]], 'euclidean')**2)  for i in range(m)]

should be a scalar:

cl_var = (1.0 / (N - m) / d) * sum([sum(distance.cdist(p[np.where(labels == i)], [centers[0][i]], 'euclidean')**2) for i in range(m)])

4.

Use natural logs, instead of your base10 logs.

5.

Finally, and most importantly, the BIC you are computing has an inverse sign from the regular definition. so you are looking to maximize instead of minimize

$\endgroup$
5
13
$\begingroup$

This is basically eyaler's solution, with a few notes.. I just typed it out if someone wanted a quick copy/paste:

Notes:

  1. eyalers 4th comment is incorrect np.log is already a natural log, no change needed

  2. eyalers 5th comment about inverse is correct. In the code below, you are looking for the MAXIMUM - keep in mind that the example has negative BIC numbers

Code is as follows (again, all credit to eyaler):

from sklearn import cluster
from scipy.spatial import distance
import sklearn.datasets
from sklearn.preprocessing import StandardScaler
import numpy as np

def compute_bic(kmeans,X):
    """
    Computes the BIC metric for a given clusters

    Parameters:
    -----------------------------------------
    kmeans:  List of clustering object from scikit learn

    X     :  multidimension np array of data points

    Returns:
    -----------------------------------------
    BIC value
    """
    # assign centers and labels
    centers = [kmeans.cluster_centers_]
    labels  = kmeans.labels_
    #number of clusters
    m = kmeans.n_clusters
    # size of the clusters
    n = np.bincount(labels)
    #size of data set
    N, d = X.shape

    #compute variance for all clusters beforehand
    cl_var = (1.0 / (N - m) / d) * sum([sum(distance.cdist(X[np.where(labels == i)], [centers[0][i]], 
             'euclidean')**2) for i in range(m)])

    const_term = 0.5 * m * np.log(N) * (d+1)

    BIC = np.sum([n[i] * np.log(n[i]) -
               n[i] * np.log(N) -
             ((n[i] * d) / 2) * np.log(2*np.pi*cl_var) -
             ((n[i] - 1) * d/ 2) for i in range(m)]) - const_term

    return(BIC)



# IRIS DATA
iris = sklearn.datasets.load_iris()
X = iris.data[:, :4]  # extract only the features
#Xs = StandardScaler().fit_transform(X)
Y = iris.target

ks = range(1,10)

# run 9 times kmeans and save each result in the KMeans object
KMeans = [cluster.KMeans(n_clusters = i, init="k-means++").fit(X) for i in ks]

# now run for each cluster the BIC computation
BIC = [compute_bic(kmeansi,X) for kmeansi in KMeans]

print BIC
$\endgroup$
3
  • $\begingroup$ Looking at github.com/bobhancock/goxmeans/blob/master/doc/BIC_notes.pdf could you explain how this BIC formula is optimising for the MAXIMUM ? Could you show for the Minimum and explain what it does in verbal language ? finding difficult to interpret the formula $\endgroup$
    – user305883
    Commented Jul 13, 2018 at 18:58
  • $\begingroup$ Pl see my related question here: stats.stackexchange.com/questions/374002/… $\endgroup$
    – rnso
    Commented Oct 27, 2018 at 15:37
  • 1
    $\begingroup$ there seems to be a bug in the formula. Has anyone managed to fix it? $\endgroup$
    – STiGMa
    Commented Jun 24, 2019 at 17:34
0
$\begingroup$

With my environments Prabhath's answer does not work because np.where() clause is unhashable for telling which records of X to be referred to calculate cl_var. Let me fix that error and re-post the code as I don't have enough reputation to even add a comment.

# Almost all credits to elayer and Prabhath
from sklearn import cluster

from scipy.spatial import distance
import sklearn.datasets
from sklearn.preprocessing import StandardScaler
import numpy as np

def compute_bic(kmeans,X):
    """
    Computes the BIC metric for a given clusters

    Parameters:
    -----------------------------------------
    kmeans:  List of clustering object from scikit learn

    X     :  multidimension np array of data points

    Returns:
    -----------------------------------------
    BIC value
    """
    # assign centers and labels
    centers = [kmeans.cluster_centers_]
    labels  = kmeans.labels_
    #number of clusters
    m = kmeans.n_clusters
    # size of the clusters
    n = np.bincount(labels)
    #size of data set
    N, d = X.shape

    #compute variance for all clusters beforehand
    #fixed
    cl_var = (1.0 / (N - m) / d) * sum([sum(distance.cdist(X.iloc[labels == i], [centers[0][i]], 
             'euclidean')**2) for i in range(m)])

    const_term = 0.5 * m * np.log(N) * (d+1)

    BIC = np.sum([n[i] * np.log(n[i]) -
               n[i] * np.log(N) -
             ((n[i] * d) / 2) * np.log(2*np.pi*cl_var) -
             ((n[i] - 1) * d/ 2) for i in range(m)]) - const_term

    return(BIC)



# IRIS DATA
iris = sklearn.datasets.load_iris()
X = iris.data[:, :4]  # extract only the features
#Xs = StandardScaler().fit_transform(X)
Y = iris.target

ks = range(1,10)

# run 9 times kmeans and save each result in the KMeans object
KMeans = [cluster.KMeans(n_clusters = i, init="k-means++").fit(X) for i in ks]

# now run for each cluster the BIC computation
BIC = [compute_bic(kmeansi,X) for kmeansi in KMeans]

print BIC
$\endgroup$
1
  • $\begingroup$ AttributeError: 'numpy.ndarray' object has no attribute 'iloc'. It is failing for me :) $\endgroup$
    – ssbarbee
    Commented Nov 12, 2021 at 22:11
0
$\begingroup$

Yosher's modification is for the case when X is a pd.DataFrame. The initial solution for np.arrays. There seems to be multiple formulas around. In case you need to compare the score to the built-in version of Consensus K-Means I refer to this to the bic_kmeans function as found here: pyckmeans.

$\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.