+/*
+ * clueless-kmean is a variant of k-mean which enforces balanced
+ * distribution of classes in every cluster
+ *
+ * Copyright (c) 2013 Idiap Research Institute, http://www.idiap.ch/
+ * Written by Francois Fleuret <francois.fleuret@idiap.ch>
+ *
+ * This file is part of clueless-kmean.
+ *
+ * clueless-kmean 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.
+ *
+ * clueless-kmean 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 selector. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "clusterer.h"
+#include <glpk.h>
+
+Clusterer::Clusterer() {
+ _cluster_means = 0;
+ _cluster_var = 0;
+}
+
+Clusterer::~Clusterer() {
+ deallocate_array<scalar_t>(_cluster_means);
+ deallocate_array<scalar_t>(_cluster_var);
+}
+
+scalar_t Clusterer::baseline_cluster_association(int nb_points, scalar_t **points,
+ int nb_classes, int *labels,
+ scalar_t **gamma) {
+ int *associated_clusters = new int[nb_points];
+ scalar_t total_dist = 0;
+
+ for(int n = 0; n < nb_points; n++) {
+ scalar_t lowest_dist = 0;
+ for(int k = 0; k < _nb_clusters; k++) {
+ scalar_t dist = 0;
+
+ for(int d = 0; d < _dim; d++) {
+ dist += sq(_cluster_means[k][d] - points[n][d]) / (2 * _cluster_var[k][d]);
+ dist += 0.5 * log(_cluster_var[k][d]);
+ ASSERT(!isnan(dist) && !isinf(dist));
+ }
+
+ if(k == 0 || dist <= lowest_dist) {
+ lowest_dist = dist;
+ associated_clusters[n] = k;
+ }
+ }
+
+ total_dist += lowest_dist;
+ }
+
+ for(int n = 0; n < nb_points; n++) {
+ for(int k = 0; k < _nb_clusters; k++) {
+ gamma[n][k] = 0.0;
+ }
+ gamma[n][associated_clusters[n]] = 1.0;
+ }
+
+ delete[] associated_clusters;
+
+ return total_dist;
+}
+
+scalar_t Clusterer::baseline_lp_cluster_association(int nb_points, scalar_t **points,
+ int nb_classes, int *labels,
+ scalar_t **gamma) {
+ glp_prob *lp;
+
+ int *ia = new int[nb_points * _nb_clusters + 1];
+ int *ja = new int[nb_points * _nb_clusters + 1];
+ scalar_t *ar = new scalar_t[nb_points * _nb_clusters + 1];
+
+ lp = glp_create_prob();
+
+ glp_set_prob_name(lp, "baseline_lp_cluster_association");
+ glp_set_obj_dir(lp, GLP_MIN);
+
+ glp_add_rows(lp, nb_points);
+
+ for(int n = 1; n <= nb_points; n++) {
+ glp_set_row_bnds(lp, n, GLP_FX, 1.0, 1.0);
+ }
+
+ glp_add_cols(lp, nb_points * _nb_clusters);
+ for(int k = 1; k <= _nb_clusters; k++) {
+ for(int n = 1; n <= nb_points; n++) {
+ int i = n + nb_points * (k - 1);
+
+ scalar_t dist = 0;
+
+ for(int d = 0; d < _dim; d++) {
+ dist += sq(_cluster_means[k-1][d] - points[n-1][d]) / (2 * _cluster_var[k-1][d]);
+ dist += 0.5 * log(_cluster_var[k-1][d]);
+ }
+
+ glp_set_obj_coef(lp, i, dist);
+ glp_set_col_bnds(lp, i, GLP_DB, 0.0, 1.0);
+ }
+ }
+
+ int l = 1;
+
+ for(int n = 1; n <= nb_points; n++) {
+ for(int k = 1; k <= _nb_clusters; k++) {
+ ia[l] = n;
+ ja[l] = n + nb_points * (k - 1);
+ ar[l] = 1.0;
+ l++;
+ }
+ }
+
+ glp_load_matrix(lp, nb_points * _nb_clusters, ia, ja, ar);
+
+ glp_simplex(lp, NULL);
+
+ scalar_t total_dist = glp_get_obj_val(lp);
+
+ for(int k = 1; k <= _nb_clusters; k++) {
+ for(int n = 1; n <= nb_points; n++) {
+ int i = n + nb_points * (k - 1);
+ gamma[n-1][k-1] = glp_get_col_prim(lp, i);
+ }
+ }
+
+ delete[] ia;
+ delete[] ja;
+ delete[] ar;
+
+ glp_delete_prob(lp);
+
+ return total_dist;
+}
+
+scalar_t Clusterer::uninformative_lp_cluster_association(int nb_points, scalar_t **points,
+ int nb_classes, int *labels,
+ scalar_t **gamma) {
+ glp_prob *lp;
+
+ int nb_coeffs = nb_points * _nb_clusters + nb_points * _nb_clusters;
+
+ int *ia = new int[nb_coeffs + 1];
+ int *ja = new int[nb_coeffs + 1];
+ scalar_t *ar = new scalar_t[nb_coeffs + 1];
+
+ scalar_t *nb_samples_per_class = new scalar_t[nb_classes];
+ for(int c = 0; c < nb_classes; c++) {
+ nb_samples_per_class[c] = 0.0;
+ }
+
+ for(int n = 0; n < nb_points; n++) {
+ nb_samples_per_class[labels[n]] += 1.0;
+ }
+
+ lp = glp_create_prob();
+
+ glp_set_prob_name(lp, "uninformative_lp_cluster_association");
+ glp_set_obj_dir(lp, GLP_MIN);
+
+ glp_add_rows(lp, nb_points + _nb_clusters * nb_classes);
+
+ for(int n = 1; n <= nb_points; n++) {
+ glp_set_row_bnds(lp, n, GLP_FX, 1.0, 1.0);
+ }
+
+ for(int k = 1; k <= _nb_clusters; k++) {
+ for(int c = 1; c <= nb_classes; c++) {
+ int row = nb_points + (k - 1) * nb_classes + c;
+ scalar_t tau = nb_samples_per_class[c-1] / scalar_t(_nb_clusters);
+ // cout << "tau " << k << " " << c << " " << tau << endl;
+ glp_set_row_bnds(lp, row, GLP_FX, tau, tau);
+ }
+ }
+
+ glp_add_cols(lp, nb_points * _nb_clusters);
+
+ for(int k = 1; k <= _nb_clusters; k++) {
+ for(int n = 1; n <= nb_points; n++) {
+ int r = n + nb_points * (k - 1);
+
+ scalar_t dist = 0;
+
+ for(int d = 0; d < _dim; d++) {
+ dist += sq(_cluster_means[k-1][d] - points[n-1][d]) / (2 * _cluster_var[k-1][d]);
+ dist += 0.5 * log(_cluster_var[k-1][d]);
+ }
+
+ glp_set_obj_coef(lp, r, dist);
+ glp_set_col_bnds(lp, r, GLP_DB, 0.0, 1.0);
+ }
+ }
+
+ int l = 1;
+
+ for(int n = 1; n <= nb_points; n++) {
+ for(int k = 1; k <= _nb_clusters; k++) {
+ int row = n;
+ ia[l] = row;
+ ja[l] = nb_points * (k - 1) + n;
+ ar[l] = 1.0;
+ l++;
+ }
+ }
+
+ for(int k = 1; k <= _nb_clusters; k++) {
+ for(int c = 1; c <= nb_classes; c++) {
+ int row = nb_points + (k - 1) * nb_classes + c;
+ for(int n = 1; n <= nb_points; n++) {
+ if(labels[n-1] == c - 1) {
+ ia[l] = row;
+ ja[l] = (k-1) * nb_points + n;
+ ar[l] = 1.0;
+ l++;
+ }
+ }
+ }
+ }
+
+ ASSERT(l == nb_coeffs + 1);
+
+ glp_load_matrix(lp, nb_coeffs, ia, ja, ar);
+
+ glp_simplex(lp, NULL);
+
+ scalar_t total_dist = glp_get_obj_val(lp);
+
+ for(int k = 1; k <= _nb_clusters; k++) {
+ for(int n = 1; n <= nb_points; n++) {
+ int i = n + nb_points * (k - 1);
+ gamma[n-1][k-1] = glp_get_col_prim(lp, i);
+ }
+ }
+
+ // { // ******************************* START ***************************
+// #warning Test code added on 2013 Feb 07 20:32:05
+ // // for(int n = 0; n < nb_points; n++) {
+ // // scalar_t sum = 0;
+ // // for(int k = 0; k < _nb_clusters; k++) {
+ // // ASSERT(gamma[n][k] >= 0 && gamma[n][k] <= 1);
+ // // sum += gamma[n][k];
+ // // }
+ // // cout << sum << endl;
+ // // }
+
+ // scalar_t *sum_gamma = new scalar_t[nb_classes];
+
+ // for(int k = 0; k < _nb_clusters; k++) {
+ // for(int c = 0; c < nb_classes; c++) { sum_gamma[c] = 0.0; }
+ // for(int n = 0; n < nb_points; n++) {
+ // sum_gamma[labels[n]] += gamma[n][k];
+ // }
+ // cout << "CLUSTER" << k;
+ // for(int c = 0; c < nb_classes; c++) {
+ // cout << " " << sum_gamma[c];
+ // }
+ // cout << endl;
+ // }
+
+ // delete sum_gamma;
+
+ // } // ******************************** END ****************************
+
+ delete[] nb_samples_per_class;
+ delete[] ia;
+ delete[] ja;
+ delete[] ar;
+ glp_delete_prob(lp);
+
+ return total_dist;
+}
+
+void Clusterer::baseline_update_clusters(int nb_points, scalar_t **points, scalar_t **gamma) {
+ for(int k = 0; k < _nb_clusters; k++) {
+
+ for(int d = 0; d < _dim; d++) {
+ _cluster_means[k][d] = 0.0;
+ _cluster_var[k][d] = 0.0;
+ }
+
+ scalar_t sum_gamma = 0;
+ for(int n = 0; n < nb_points; n++) {
+ sum_gamma += gamma[n][k];
+ for(int d = 0; d < _dim; d++) {
+ _cluster_means[k][d] += gamma[n][k] * points[n][d];
+ _cluster_var[k][d] += gamma[n][k] * sq(points[n][d]);
+ }
+ }
+
+ ASSERT(sum_gamma >= 1);
+
+ for(int d = 0; d < _dim; d++) {
+ if(sum_gamma >= 2) {
+ _cluster_var[k][d] = (_cluster_var[k][d] - sq(_cluster_means[k][d]) / sum_gamma) / (sum_gamma - 1);
+ } else {
+ _cluster_var[k][d] = 1;
+ }
+ _cluster_var[k][d] = max(0.01, _cluster_var[k][d]);
+ _cluster_means[k][d] /= sum_gamma;
+ }
+ }
+}
+
+void Clusterer::initialize_clusters(int nb_points, scalar_t **points) {
+ int *used = new int[nb_points];
+ for(int k = 0; k < nb_points; k++) { used[k] = 0; }
+ for(int k = 0; k < _nb_clusters; k++) {
+ int l;
+ do { l = int(drand48() * nb_points); } while(used[l]);
+ for(int d = 0; d < _dim; d++) {
+ _cluster_means[k][d] = points[l][d];
+ _cluster_var[k][d] = 1.0;
+ }
+ used[l] = 1;
+ }
+ delete[] used;
+}
+
+void Clusterer::train(int nb_clusters, int dim,
+ int nb_points, scalar_t **points,
+ int nb_classes, int *labels,
+ int *cluster_associations) {
+ deallocate_array<scalar_t>(_cluster_means);
+ deallocate_array<scalar_t>(_cluster_var);
+ _nb_clusters = nb_clusters;
+ _dim = dim;
+ _cluster_means = allocate_array<scalar_t>(_nb_clusters, _dim);
+ _cluster_var = allocate_array<scalar_t>(_nb_clusters, _dim);
+
+ scalar_t **gammas = allocate_array<scalar_t>(nb_points, _nb_clusters);
+
+ if(nb_clusters > nb_points) abort();
+
+ initialize_clusters(nb_points, points);
+
+ scalar_t pred_total_distance, total_distance = FLT_MAX;
+ int nb_rounds = 0;
+
+ do {
+ pred_total_distance = total_distance;
+ total_distance =
+ // baseline_cluster_association(nb_points, points, nb_classes, labels, gammas);
+ // baseline_lp_cluster_association(nb_points, points, nb_classes, labels, gammas);
+ uninformative_lp_cluster_association(nb_points, points, nb_classes, labels, gammas);
+ cout << "TRAIN " << nb_rounds << " " << total_distance << endl;
+ baseline_update_clusters(nb_points, points, gammas);
+ nb_rounds++;
+ } while(total_distance < min_iteration_improvement * pred_total_distance &&
+ nb_rounds < max_nb_iterations);
+
+ {
+ cout << "TOTAL_NB_SAMPLES";
+ for(int c = 0; c < nb_classes; c++) {
+ int nb_samples = 0;
+ for(int n = 0; n < nb_points; n++) {
+ if(labels[n] == c) {
+ nb_samples++;
+ }
+ }
+ cout << " " << nb_samples;
+ }
+ cout << endl;
+
+ for(int k = 0; k < _nb_clusters; k++) {
+ cout << "CLUSTER_GAMMA_SUM " << k << " :";
+ for(int c = 0; c < nb_classes; c++) {
+ scalar_t sum = 0.0;
+ for(int n = 0; n < nb_points; n++) {
+ if(labels[n] == c) {
+ sum += gammas[n][k];
+ }
+ }
+ cout << " " << sum;
+ }
+ cout << endl;
+ }
+ }
+
+ for(int n = 0; n < nb_points; n++) {
+ for(int k = 0; k < _nb_clusters; k++) {
+ if(k == 0 || gammas[n][k] > gammas[n][cluster_associations[n]]) {
+ cluster_associations[n] = k;
+ }
+ }
+ }
+
+ deallocate_array<scalar_t>(gammas);
+}
+
+int Clusterer::cluster(scalar_t *point) {
+ scalar_t lowest_dist = 0;
+ int associated_cluster = -1;
+
+ for(int k = 0; k < _nb_clusters; k++) {
+ scalar_t dist = 0;
+
+ for(int d = 0; d < _dim; d++) {
+ dist += sq(_cluster_means[k][d] - point[d]) / (2 * _cluster_var[k][d]);
+ dist += 0.5 * log(_cluster_var[k][d]);
+ ASSERT(!isnan(dist) && !isinf(dist));
+ }
+
+ if(k == 0 || dist <= lowest_dist) {
+ lowest_dist = dist;
+ associated_cluster = k;
+ }
+ }
+
+ ASSERT(associated_cluster >= 0);
+
+ return associated_cluster;
+}