/*
* RapidMiner
*
* Copyright (C) 2001-2008 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.operator.learner.clustering.clusterer;
import java.util.List;
import com.rapidminer.example.Attribute;
import com.rapidminer.example.Example;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.learner.clustering.ClusterModel;
import com.rapidminer.operator.learner.clustering.FlatClusterModel;
import com.rapidminer.operator.learner.clustering.FlatCrispClusterModel;
import com.rapidminer.operator.learner.clustering.IdUtils;
import com.rapidminer.operator.similarity.bregmandivergences.*;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeCategory;
import com.rapidminer.parameter.ParameterTypeInt;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.tools.IterationArrayList;
/**
* This operator represents an implementation of the Bregman Hard Clustering.
*
* @author Regina Fritsch
* @version $Id: BregmanHardClustering.java,v 1.5 2008/09/12 10:31:43 tobiasmalbrecht Exp $
*/
public class BregmanHardClustering extends AbstractKMethod {
/** The parameter name for "Bregman Divergence" */
public static final String PARAMETER_BREGMAN_DIVERGENCE = "bregman_divergence";
public static final String[] DISTANCES = { "Squared Euclidean distance",
"KL-divergence", "Generalized I-divergence",
"Itakura-Saito distance", "Logistic loss", "Mahalanobis distance",
"Squared loss", "Logarithmic loss" };
public static final int SQUARED_EUCLIDEAN_DISTANCE = 0;
public static final int KL_DIVERGENCE = 1;
public static final int GENERALIZED_I_DIVERGENCE = 2;
public static final int ITAKURA_SAITO_DISTANCE = 3;
public static final int LOGISTIC_LOSS = 4;
public static final int MAHALANOBIS_DISTANCE = 5;
public static final int SQUARED_LOSS = 6;
public static final int LOGARITHMIC_LOSS = 7;
/**
* The parameter name for "Use the given random seed instead of global
* random numbers (-1: use global)."
*/
public static final String PARAMETER_LOCAL_RANDOM_SEED = "local_random_seed";
private double[][] centroids;
private ExampleSet exampleSet;
private int numberOfAttr;
private int k;
private BregmanDivergence div;
public BregmanHardClustering(OperatorDescription description) {
super(description);
}
public ClusterModel createClusterModel(ExampleSet exampleSet) throws OperatorException {
this.exampleSet = exampleSet;
k = getParameterAsInt(PARAMETER_K);
int maxOptimizationSteps = getParameterAsInt(PARAMETER_MAX_OPTIMIZATION_STEPS);
int maxRuns = getParameterAsInt(PARAMETER_MAX_RUNS);
// create model
FlatClusterModel model = kmethod(exampleSet, k, maxOptimizationSteps,
maxRuns);
return model;
}
// initialize cluster centroids randomly
protected void initKMethod(List<String> ids, int k) throws OperatorException {
numberOfAttr = this.exampleSet.getAttributes().size();
centroids = new double[k][numberOfAttr];
List<String> randomList = IdUtils.getRandomIdList(ids, k, getParameterAsInt(PARAMETER_LOCAL_RANDOM_SEED));
for (int i = 0; i < k; i++) {
String id = randomList.get(i);
Example ex = IdUtils.getExampleFromId(exampleSet, id);
int j = 0;
for (Attribute attribute : ex.getAttributes()) {
centroids[i][j] = ex.getValue(attribute);
j++;
}
}
// which bregman divergence is to use:
try {
int divergence = getParameterAsInt(PARAMETER_BREGMAN_DIVERGENCE);
try {
switch (divergence) {
case 0:
div = new SquaredEuclideanDistance(exampleSet);
break;
case 1:
div = new KLDivergence(exampleSet);
break;
case 2:
div = new GeneralizedIDivergence(exampleSet);
break;
case 3:
div = new ItakuraSaitoDistance(exampleSet);
break;
case 4:
div = new LogisticLoss(exampleSet);
break;
case 5:
div = new MahalanobisDistance(exampleSet);
break;
case 6:
div = new SquaredLoss(exampleSet);
break;
case 7:
div = new LogarithmicLoss(exampleSet);
break;
default:
div = new SquaredEuclideanDistance(exampleSet);
break;
}
} catch (InstantiationException ie) {
try {
div = new SquaredEuclideanDistance(exampleSet);
setParameter(PARAMETER_BREGMAN_DIVERGENCE, "Squared Euclidean distance");
logWarning(ie.getMessage());
} catch (Exception e) {
}
}
} catch (UndefinedParameterError e) {
}
}
// the best cluster for the example
protected int bestIndex(String id, FlatCrispClusterModel cm, FlatCrispClusterModel oldCm) {
Example toAllocate = IdUtils.getExampleFromId(exampleSet, id);
int bestIndex = -1;
double shortestDistance = Double.POSITIVE_INFINITY;
for (int i = 0; i < k; i++) {
double[] centroid = centroids[i];
double divergence = div.computeDistance(toAllocate, centroid);
if (shortestDistance > divergence) {
shortestDistance = divergence;
bestIndex = i;
}
}
return bestIndex;
}
// compute new cluster centroids
protected void recalculateCentroids(FlatCrispClusterModel cl) {
// if no weights available, initialise weights
if (exampleSet.getAttributes().getWeight() == null) {
com.rapidminer.example.Tools.createWeightAttribute(exampleSet);
}
// clear centroids
centroids = new double[k][numberOfAttr];
// compute new centroids for every cluster i
for (int i = 0; i < k; i++) {
// over all objects in cluster i
List<String> iter = new IterationArrayList<String>(cl.getClusterAt(i).getObjects());
double probMeasureSum = 0;
for (int j = 0; j < iter.size(); j++) {
Example ex = IdUtils.getExampleFromId(exampleSet, iter.get(j));
probMeasureSum += ex.getWeight();
int a = 0;
for (Attribute attribute : ex.getAttributes()) {
centroids[i][a] += (ex.getWeight() * ex.getValue(attribute));
a++;
}
}
for (int x = 0; x < centroids[i].length; x++) {
centroids[i][x] = centroids[i][x] / probMeasureSum;
}
}
}
protected double evaluateClusterModel(FlatCrispClusterModel cl) {
// according to KMeans
double sum = 0;
int count = 0;
for (int i = 0; i < cl.getNumberOfClusters(); i++) {
List<String> objectsIter = new IterationArrayList<String>(cl.getClusterAt(i).getObjects());
for (int j = 0; j < objectsIter.size(); j++) {
Example ex = IdUtils.getExampleFromId(exampleSet, objectsIter.get(j));
double z = (div.computeDistance(ex, centroids[i]));
sum += (z * z);
count++;
}
}
return -(sum / count);
}
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
types.add(new ParameterTypeCategory(PARAMETER_BREGMAN_DIVERGENCE, "The Bregman Divergence", DISTANCES, SQUARED_EUCLIDEAN_DISTANCE));
types.add(new ParameterTypeInt(PARAMETER_LOCAL_RANDOM_SEED, "The local random seed (-1: use global random seed)", -1, Integer.MAX_VALUE, -1));
return types;
}
}