/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* WeightedDotP.java
* Copyright (C) 2001 Mikhail Bilenko
*
*/
package weka.core.metrics;
import weka.core.*;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.Vector;
import java.util.Arrays;
/**
* WeightedDotP class
*
* Implements the weighted dot product distance metric
*
* @author Mikhail Bilenko (mbilenko@cs.utexas.edu)
* @version $Revision: 1.21 $
*/
public class WeightedDotP extends LearnableMetric implements OptionHandler {
/** Should cosine similarity be normalized by the length of two instance vectors? */
protected boolean m_lengthNormalized = true;
/** We can have different ways of converting from similarity to distance */
public static final int CONVERSION_LAPLACIAN = 1;
public static final int CONVERSION_UNIT = 2;
public static final int CONVERSION_EXPONENTIAL = 4;
public static final Tag[] TAGS_CONVERSION = {
new Tag(CONVERSION_UNIT, "distance = 1-similarity"),
new Tag(CONVERSION_LAPLACIAN, "distance=1/(1+similarity)"),
new Tag(CONVERSION_EXPONENTIAL, "distance=exp(-similarity)")
};
/** The method of converting, by default laplacian */
protected int m_conversionType = CONVERSION_LAPLACIAN;
/** A metric learner responsible for training the parameters of the metric */
// protected MetricLearner m_metricLearner = new MatlabMetricLearner();
// protected MetricLearner m_metricLearner = new GDMetricLearner();
protected MetricLearner m_metricLearner = new ClassifierMetricLearner();
/** The metric may return its own maximum distance */
public double getMaxDistance() {
return 1;
}
/** Creates an empty metric class */
public WeightedDotP() {
m_normalizeData = true;
m_fixedMaxDistance = true;
m_maxDistance = 1;
}
/**
* Creates a new metric.
* @param numAttributes the number of attributes that the metric will work on
*/
public WeightedDotP(int numAttributes) throws Exception {
super();
m_normalizeData = true;
buildMetric(numAttributes);
}
/**
* Creates a new metric which takes specified attributes.
*
* @param _attrIdxs An array containing attribute indeces that will
* be used in the metric
*/
public WeightedDotP(int[] _attrIdxs) throws Exception {
super();
m_normalizeData = true;
setAttrIdxs(_attrIdxs);
buildMetric(_attrIdxs.length);
}
/**
* Generates a new Metric. Has to initialize all fields of the metric
* with default values.
*
* @param numAttributes the number of attributes that the metric will work on
* @exception Exception if the distance metric has not been
* generated successfully.
*/
public void buildMetric(int numAttributes) throws Exception {
m_numAttributes = numAttributes;
m_attrWeights = new double[numAttributes];
m_attrIdxs = new int[numAttributes];
for (int i = 0; i < numAttributes; i++) {
m_attrWeights[i] = 1;
m_attrIdxs[i] = i;
}
}
/**
* Generates a new Metric. Has to initialize all fields of the metric
* with default values
*
* @param options an array of options suitable for passing to setOptions.
* May be null.
* @exception Exception if the distance metric has not been
* generated successfully.
*/
public void buildMetric(int numAttributes, String[] options) throws Exception {
buildMetric(numAttributes);
};
/**
* Create a new metric for operating on specified instances
* @param data instances that the metric will be used on
*/
public void buildMetric(Instances data) throws Exception {
m_classIndex = data.classIndex();
m_numAttributes = data.numAttributes();
// account for the class attribute if it is present
if (m_classIndex != m_numAttributes-1 && m_classIndex != -1) {
throw new Exception("Class attribute (" + m_classIndex + ") should be the last attribute!!!");
}
if (m_classIndex != -1) {
m_numAttributes--;
}
System.out.println("About to build metric with " + m_numAttributes + " attributes, classIdx=" + m_classIndex);
buildMetric(m_numAttributes);
if (m_trainable) {
learnMetric(data);
}
}
/**
* Returns a dot product similarity value between two instances.
* @param instance1 First instance.
* @param instance2 Second instance.
* @exception Exception if similarity could not be estimated.
*/
public double similarity(Instance instance1, Instance instance2) throws Exception {
// either pass the computation to the external classifier, or do the work yourself
if (m_trainable && m_external && m_trained) {
return m_metricLearner.getSimilarity(instance1, instance2);
} else {
return similarityInternal(instance1, instance2);
}
}
/** Return the penalty contribution - distance*distance */
public double penalty(Instance instance1,
Instance instance2) throws Exception {
double sim = similarity(instance1, instance2);
return 1 - sim;
}
/** Return the penalty contribution - distance*distance */
public double penaltySymmetric(Instance instance1,
Instance instance2) throws Exception {
double sim = similarity(instance1, instance2);
return 1 - sim;
}
/**
* Returns a dot product similarity value between two instances without using the weights.
* @param instance1 First instance.
* @param instance2 Second instance.
* @exception Exception if similarity could not be estimated.
*/
public double similarityNonWeighted(Instance instance1, Instance instance2) throws Exception {
if (instance1 instanceof SparseInstance && instance2 instanceof SparseInstance) {
return similaritySparseNonWeighted((SparseInstance)instance1, (SparseInstance)instance2);
} else if (instance1 instanceof SparseInstance) {
return similaritySparseNonSparseNonWeighted((SparseInstance)instance1, instance2);
} else if (instance2 instanceof SparseInstance) {
return similaritySparseNonSparseNonWeighted((SparseInstance)instance2, instance1);
} else {
return similarityNonSparseNonWeighted(instance1, instance2);
}
}
/**
* Returns a dot product similarity value between two instances.
* @param instance1 First instance.
* @param instance2 Second instance.
* @exception Exception if similarity could not be estimated.
*/
public double similarityInternal(Instance instance1, Instance instance2) throws Exception {
if (instance1 instanceof SparseInstance && instance2 instanceof SparseInstance) {
return similaritySparse((SparseInstance)instance1, (SparseInstance)instance2);
} else if (instance1 instanceof SparseInstance) {
return similaritySparseNonSparse((SparseInstance)instance1, instance2);
} else if (instance2 instanceof SparseInstance) {
return similaritySparseNonSparse((SparseInstance)instance2, instance1);
} else {
return similarityNonSparse(instance1, instance2);
}
}
/**
* Returns a dot product similarity value between two sparse instances.
* @param instance1 First sparse instance.
* @param instance2 Second sparse instance.
* @exception Exception if similarity could not be estimated.
*/
public double similaritySparse(SparseInstance instance1, SparseInstance instance2) throws Exception {
double sim = 0;
double length1 = 0, length2 = 0;
// iterate through the attributes that are present in the first instance
for (int i = 0; i < instance1.numValues(); i++) {
Attribute attribute = instance1.attributeSparse(i);
int attrIdx = attribute.index();
// Skip the class index
if (attrIdx != m_classIndex) {
// get the corresponding value of the second instance
int idx2 = instance2.locateIndex(attrIdx);
if (idx2 >=0 && attrIdx == instance2.index(idx2)) {
double val1 = instance1.value(attrIdx);
double val2 = instance2.value(attrIdx);
sim += m_attrWeights[attrIdx] * val1 * val2;
}
}
}
if (m_lengthNormalized) {
length1 = lengthWeighted(instance1);
length2 = lengthWeighted(instance2);
if (length1 != 0 && length2 != 0) {
sim /= length1 * length2;
}
}
return sim;
};
/**
* Returns a dot product similarity value between a two non-sparse instances
* @param instance1 First non-sparse instance.
* @param instance2 Second non-sparse instance.
* @exception Exception if similarity could not be estimated.
*/
public double similarityNonSparse(Instance instance1, Instance instance2) throws Exception {
double sim = 0;
double [] values1 = instance1.toDoubleArray();
double [] values2 = instance2.toDoubleArray();
double length1 = 0, length2 = 0;
// iterate through the attributes
for (int i = 0; i < values1.length; i++) {
// Skip the class index
if (i != m_classIndex) {
sim += m_attrWeights[i] * values1[i] * values2[i];
}
}
if (m_lengthNormalized) {
length1 = lengthWeighted(instance1);
length2 = lengthWeighted(instance2);
if (length1 != 0 && length2 != 0) {
sim /= length1 * length2;
}
}
return sim;
}
/**
* Returns a dot product similarity value between a non-sparse instance and a sparse instance
* @param instance1 First sparse instance.
* @param instance2 Second non-sparse instance.
* @exception Exception if similarity could not be estimated.
*/
public double similaritySparseNonSparse(SparseInstance instance1, Instance instance2) throws Exception {
double sim = 0;
// iterate through the attributes that are present in the first instance
for (int i = 0; i < instance1.numValues(); i++) {
Attribute attribute = instance1.attributeSparse(i);
int attrIdx = attribute.index();
// Skip the class index
if (attrIdx != m_classIndex) {
double val1 = instance1.value(attrIdx);
double val2 = instance2.value(attrIdx);
sim += m_attrWeights[attrIdx] * val1 * val2;
}
}
// Bad news: need to iterate through all attributes of the non-sparse instance
// to compute its length for normalization
if (m_lengthNormalized) {
double length1 = lengthWeighted(instance1);
double length2 = lengthWeighted(instance2);
if (length1 != 0 && length2 != 0) {
sim /= length1 * length2;
}
}
return sim;
}
/**
* Returns a dot product similarity value between two sparse instances.
* @param instance1 First sparse instance.
* @param instance2 Second sparse instance.
* @exception Exception if similarity could not be estimated.
*/
public double similaritySparseNonWeighted(SparseInstance instance1, SparseInstance instance2) throws Exception {
double sim = 0;
double length1 = 0, length2 = 0;
// iterate through the attributes that are present in the first instance
for (int i = 0; i < instance1.numValues(); i++) {
Attribute attribute = instance1.attributeSparse(i);
int attrIdx = attribute.index();
// Skip the class index
if (attrIdx != m_classIndex) {
// get the corresponding value of the second instance
int idx2 = instance2.locateIndex(attrIdx);
if (idx2 >=0 && attrIdx == instance2.index(idx2)) {
double val1 = instance1.value(attrIdx);
double val2 = instance2.value(attrIdx);
sim += val1 * val2;
}
}
}
if (m_lengthNormalized) {
length1 = length(instance1);
length2 = length(instance2);
if (length1 != 0 && length2 != 0) {
sim /= length1 * length2;
}
}
return sim;
};
/**
* Returns a dot product similarity value between a two non-sparse instances
* @param instance1 First non-sparse instance.
* @param instance2 Second non-sparse instance.
* @exception Exception if similarity could not be estimated.
*/
public double similarityNonSparseNonWeighted(Instance instance1, Instance instance2) throws Exception {
double sim = 0;
double [] values1 = instance1.toDoubleArray();
double [] values2 = instance2.toDoubleArray();
double length1 = 0, length2 = 0;
// iterate through the attributes
for (int i = 0; i < values1.length; i++) {
// Skip the class index
if (i != m_classIndex) {
sim += values1[i] * values2[i];
if (m_lengthNormalized) {
length1 += values1[i] * values1[i];
length2 += values2[i] * values2[i];
}
}
}
if (m_lengthNormalized && length1!=0 && length2!=0) {
sim /= Math.sqrt(length1) * Math.sqrt(length2);
}
return sim;
}
/**
* Returns a dot product similarity value between a non-sparse instance and a sparse instance
* @param instance1 First sparse instance.
* @param instance2 Second non-sparse instance.
* @exception Exception if similarity could not be estimated.
*/
public double similaritySparseNonSparseNonWeighted(SparseInstance instance1, Instance instance2) throws Exception {
double sim = 0;
// iterate through the attributes that are present in the first instance
for (int i = 0; i < instance1.numValues(); i++) {
Attribute attribute = instance1.attributeSparse(i);
int attrIdx = attribute.index();
// Skip the class index
if (attrIdx != m_classIndex) {
double val1 = instance1.value(attrIdx);
double val2 = instance2.value(attrIdx);
sim += val1 * val2;
}
}
// Bad news: need to iterate through all attributes of the non-sparse instance
// to compute its length for normalization
if (m_lengthNormalized) {
double length1 = length(instance1);
double length2 = length(instance2);
if (length1 != 0 && length2 != 0) {
sim /= length1 * length2;
}
}
return sim;
}
/** Create an Instance with features corresponding to internal "features":
* for x'y returns an instance with the following features: [x1*y1, x2*y2, ..., xn*yn]
* @param instance1 first instance
* @param instance2 second instance
* @returns an instance containing the dot-product components
*/
public Instance createDiffInstance(Instance instance1, Instance instance2) {
if (instance1 instanceof SparseInstance && instance2 instanceof SparseInstance) {
return createDiffInstanceSparse((SparseInstance)instance1, (SparseInstance)instance2);
} else if (instance1 instanceof SparseInstance) {
return createDiffInstanceSparseNonSparse((SparseInstance)instance1, instance2);
} else {
return createDiffInstanceNonSparse(instance1, instance2);
}
}
/** Create a SparseInstance with features corresponding to internal "features":
* for x'y returns an instance with the following features: [x1*y1, x2*y2, ..., xn*yn]
* @param instance1 first sparse instance
* @param instance2 second sparse instance
* @returns a sparse instance containing the dot-product components
*/
public SparseInstance createDiffInstanceSparse(SparseInstance instance1, SparseInstance instance2) {
SparseInstance diffInstance = new SparseInstance(instance1);
diffInstance.setDataset(instance1.dataset());
double sim = 0;
try {
boolean saveLengthNormalized = m_lengthNormalized;
m_lengthNormalized = false;
sim = similaritySparse(instance1, instance2);
m_lengthNormalized = saveLengthNormalized;
} catch (Exception e) { e.printStackTrace();}
double length1 = lengthWeighted(instance1);
double length2 = lengthWeighted(instance2);
double denom = length1 * length1 * length2 * length2;
if (denom == 0) {
for (int i = 0; i < diffInstance.numValues(); i++) {
diffInstance.setValueSparse(i, 0);
}
return diffInstance;
}
// iterate through the attributes that are present in the first instance
for (int i = 0; i < diffInstance.numValues(); i++) {
int attrIdx = diffInstance.index(i);
// Skip the class index
if (attrIdx != m_classIndex) {
double val1 = diffInstance.valueSparse(i);
// get the corresponding value of the second instance
double val2 = instance2.value(attrIdx);
double val = (val1 * val2 * length1 * length2
- sim * (val1 * val1 * length2 / (2 * length1) +
val2 * val2 * length1 / (2 * length2))) / denom;
diffInstance.setValue(attrIdx, val);
} else { // second instance doesn't have this value (=0), kill it
diffInstance.setValue(attrIdx, 0);
}
}
// pick up values only present in instance2
for (int i = 0; i < instance2.numValues(); i++) {
int attrIdx = instance2.index(i);
if (attrIdx != m_classIndex) {
int idx1 = instance1.locateIndex(attrIdx);
if (idx1 < 0 || attrIdx != instance1.index(idx1)) {
double val2 = instance2.valueSparse(i);
double val = -sim * val2 * val2 * length1 / (2 * length2) / denom;
diffInstance.setValue(attrIdx, val);
}
}
}
return diffInstance;
};
/** Create an Instance with features corresponding to internal "features":
* for x'y returns an instance with the following features: [x1*y1, x2*y2, ..., xn*yn]
* @param instance1 first instance
* @param instance2 second instance
* @returns an instance containing the dot-product components
*/
public Instance createDiffInstanceNonSparse(Instance instance1, Instance instance2) {
double length = -1;
Instance diffInstance = new Instance(instance2);
diffInstance.setDataset(instance2.dataset());
double sim = 0;
try {
boolean saveLengthNormalized = m_lengthNormalized;
m_lengthNormalized = false;
sim = similarityNonSparse(instance1, instance2);
m_lengthNormalized = saveLengthNormalized;
} catch (Exception e) { e.printStackTrace();}
double length1 = lengthWeighted(instance1);
double length2 = lengthWeighted(instance2);
double denom = length1 * length1 * length2 * length2;
if (denom == 0) { // don't need to calculate - all zeros
return diffInstance;
}
// iterate through all attributes
for (int i = 0; i < diffInstance.numValues(); i++) {
if (i != m_classIndex) {
double val1 = instance1.value(i);
double val2 = instance2.value(i);
double val = (val1 * val2 * length1 * length2
- sim * (val1 * val1 * length2 / (2 * length1) +
val2 * val2 * length1 / (2 * length2))) / denom;
diffInstance.setValue(i, val);
}
}
return diffInstance;
}
/** Create a SparseInstance with features corresponding to internal "features":
* for x'y returns an instance with the following features: [x1*y1, x2*y2, ..., xn*yn]
* @param instance1 first sparse instance
* @param instance2 second instance
* @returns a sparse instance containing the dot-product components
*/
public Instance createDiffInstanceSparseNonSparse(SparseInstance instance1,
Instance instance2) {
double length = -1;
Instance diffInstance = new Instance(instance2);
diffInstance.setDataset(instance2.dataset());
double sim = 0;
try {
boolean saveLengthNormalized = m_lengthNormalized;
m_lengthNormalized = false;
sim = similaritySparseNonSparse(instance1, instance2);
m_lengthNormalized = saveLengthNormalized;
} catch (Exception e) { e.printStackTrace();}
double length1 = lengthWeighted(instance1);
double length2 = lengthWeighted(instance2);
double denom = length1 * length1 * length2 * length2;
if (denom == 0) { // don't need to calculate - all zeros
return diffInstance;
}
// iterate through all attributes
for (int i = 0; i < diffInstance.numValues(); i++) {
if (i != m_classIndex) {
double val1 = instance1.value(i);
double val2 = instance2.value(i);
double val = (val1 * val2 * length1 * length2
- sim * (val1 * val1 * length2 / (2 * length1) +
val2 * val2 * length1 / (2 * length2))) / denom;
diffInstance.setValue(i, val);
}
}
return diffInstance;
};
/**
* Returns distance between two instances using the current conversion
* type (CONVERSION_LAPLACIAN, CONVERSION_EXPONENTIAL, CONVERSION_UNIT, ...)
* @param instance1 First instance.
* @param instance2 Second instance.
* @exception Exception if distance could not be estimated.
*/
public double distance (Instance instance1, Instance instance2) throws Exception {
if (m_trainable && m_external && m_trained) {
return m_metricLearner.getDistance(instance1, instance2);
}
switch (m_conversionType) {
case CONVERSION_LAPLACIAN:
return 1 / (1 + similarity(instance1, instance2));
case CONVERSION_UNIT:
return 2 * (1 - similarity(instance1, instance2));
case CONVERSION_EXPONENTIAL:
return Math.exp(-similarity(instance1, instance2));
default:
throw new Exception ("Unknown similarity to distance conversion method");
}
}
/**
* Returns distance between two instances using the current conversion without using the weights
* type (CONVERSION_LAPLACIAN, CONVERSION_EXPONENTIAL, CONVERSION_UNIT, ...)
* @param instance1 First instance.
* @param instance2 Second instance.
* @exception Exception if distance could not be estimated.
*/
public double distanceNonWeighted (Instance instance1, Instance instance2) throws Exception {
switch (m_conversionType) {
case CONVERSION_LAPLACIAN:
return 1 / (1 + similarityNonWeighted(instance1, instance2));
case CONVERSION_UNIT:
return 2 * (1 - similarityNonWeighted(instance1, instance2));
case CONVERSION_EXPONENTIAL:
return Math.exp(-similarityNonWeighted(instance1, instance2));
default:
throw new Exception ("Unknown similarity to distance conversion method");
}
}
/**
* Set the type of similarity to distance conversion. Values other
* than CONVERSION_LAPLACIAN, CONVERSION_UNIT, or CONVERSION_EXPONENTIAL will be ignored
*
* @param type type of the similarity to distance conversion to use
*/
public void setConversionType(SelectedTag conversionType) {
if (conversionType.getTags() == TAGS_CONVERSION) {
m_conversionType = conversionType.getSelectedTag().getID();
}
}
/**
* return the type of similarity to distance conversion
* @return one of CONVERSION_LAPLACIAN, CONVERSION_UNIT, or CONVERSION_EXPONENTIAL
*/
public SelectedTag getConversionType() {
return new SelectedTag(m_conversionType, TAGS_CONVERSION);
}
/** Set normalization by instance length to be on or off
* @param lengthNormalized if true, similarity is normalized by the length of the vectors
*/
public void setLengthNormalized(boolean lengthNormalized) {
m_lengthNormalized = lengthNormalized;
}
/** Check whether similarity is normalized by the length of the vectors
* @returns true if similarity is normalized by the length of the vectors
*/
public boolean getLengthNormalized() {
return m_lengthNormalized;
}
/**
* Updates the weights
*/
public void learnMetric(Instances data) throws Exception{
if (m_metricLearner == null) {
System.err.println("Metric learner for WeightedDotP is not initalized. No training was conducted");
return;
}
m_metricLearner.trainMetric(this, data);
}
/**
* Set the distance metric learner
*
* @param metricLearner the metric learner
*/
public void setMetricLearner (MetricLearner metricLearner) {
m_metricLearner = metricLearner;
}
/**
* Get the distance metric learner
*
* @returns the distance metric learner that this metric employs
*/
public MetricLearner getMetricLearner () {
return m_metricLearner;
}
/**
* Create a sparse instance with features corresponding to dot-product components of the two given instances
* @param instance1 first sparse instance
* @param instance2 second sparse instance
*/
// OLD VERSION
// protected SparseInstance createDiffInstanceSparse (SparseInstance instance1, SparseInstance instance2) {
// int maxNumValues = instance1.numValues() + instance2.numValues(); // the overall number of attributes
// // arrays that will hold values and internal indeces of attribute indices
// // these will be cut off later
// double [] attrValues = new double[maxNumValues];
// int [] indices = new int[maxNumValues];
// int counter = 0;
// Arrays.fill(attrValues, Double.NaN);
// Arrays.fill(indices, Integer.MAX_VALUE);
// // iterate through the attributes that are present in the first instance
// for (int i = 0; i < instance1.numValues(); i++) {
// Attribute attribute = instance1.attributeSparse(i);
// int attrIdx = attribute.index();
// // For the class index, assign value 0 if instances are from same class, 1 if from different classes
// if (attrIdx != m_classIndex) {
// // if second instance has the corresponding value, create diff. attribute
// int idx2 = instance2.locateIndex(attrIdx);
// if (idx2 >=0 && attrIdx == instance2.index(idx2)) {
// attrValues[counter] = m_attrWeights[attrIdx] * instance1.value(attrIdx) * instance2.value(attrIdx);
// indices[counter] = attrIdx;
// counter++;
// }
// }
// }
// // Create the sparse difference instance
// double [] trueAttrValues = new double[counter];
// int [] trueIndices = new int[counter];
// for (int i = 0; i < counter; i++) {
// trueAttrValues[i] = attrValues[i];
// trueIndices[i] = indices[i];
// }
// SparseInstance diffInstance = new SparseInstance(1.0, trueAttrValues, trueIndices, maxNumValues);
// diffInstance.setDataset(instance1.dataset());
// return diffInstance;
// }
// /**
// * Create an instance with features corresponding to dot-product components of the two given instances
// * @param instance1 first sparse instance
// * @param instance2 second non-sparse instance
// */
// protected Instance createDiffInstanceSparseNonSparse (SparseInstance instance1, Instance instance2) {
// double[] values2 = instance2.toDoubleArray();
// int numAttributes = values2.length;
// // create an extra attribute if there was no class index originally
// int classIndex = m_classIndex;
// if (classIndex < 0) {
// classIndex = numAttributes;
// numAttributes++;
// }
// double[] diffInstanceValues = new double[numAttributes];
// // iterate through the attributes that are present in the sparse instance
// for (int i = 0; i < instance1.numValues(); i++) {
// Attribute attribute = instance1.attributeSparse(i);
// int attrIdx = attribute.index();
// if (attrIdx != classIndex) {
// diffInstanceValues[attrIdx] = m_attrWeights[attrIdx] * instance1.value(attrIdx) * values2[attrIdx];
// }
// }
// Instance diffInstance = new Instance(1.0, diffInstanceValues);
// diffInstance.setDataset(instance1.dataset());
// return diffInstance;
// }
/** The computation of a metric can be either based on distance, or on similarity
* @returns false because dot product fundamentally computes similarity
*/
public boolean isDistanceBased() {
return false;
}
/**
* Given a cluster of instances, return the centroid of that cluster
* @param instances objects belonging to a cluster
* @param fastMode whether fast mode should be used for SparseInstances
* @param normalized normalize centroids for SPKMeans
* @return a centroid instance for the given cluster
*/
public Instance getCentroidInstance(Instances instances, boolean fastMode, boolean normalized) {
double [] values = new double[instances.numAttributes()];
if (fastMode) {
values = meanOrMode(instances); // uses fast meanOrMode
} else {
for (int j = 0; j < instances.numAttributes(); j++) {
values[j] = instances.meanOrMode(j); // uses usual meanOrMode
}
}
Instance centroid = new Instance(1.0, values);
// cluster centroids are dense in SPKMeans
if (normalized) {
try {
normalizeInstanceWeighted(centroid);
} catch (Exception e) {
e.printStackTrace();
}
}
return centroid;
}
/** Get the values of the partial derivates for the metric components
* for a particular instance pair
@param instance1 the first instance
@param instance2 the first instance
*/
public double[] getGradients(Instance instance1, Instance instance2) throws Exception {
double[] gradients = new double[m_numAttributes];
double length1 = lengthWeighted(instance1);
double length2 = lengthWeighted(instance2);
double l1l2 = length1 * length2;
double l1sq = length1 * length1;
double l2sq = length2 * length2;
double dotp = similarityInternal(instance1, instance2);
// take care of SparseInstances by enumerating over the values of the first instance
int numAttrs = Math.min(m_numAttributes, instance1.numValues());
for (int i = 0; i < numAttrs; i++) {
// get the values
double val1 = instance1.valueSparse(i);
Attribute attr = instance1.attributeSparse(i);
double val2 = instance2.value(attr);
int attrIdx = attr.index();
if (attrIdx != m_classIndex) {
gradients[attrIdx] = val1 * val2 / l1l2
- dotp / l1l2 * (l1sq * val2 * val2 +
l2sq * val1 * val1) / (2*l1l2);
}
}
return gradients;
}
/** get the normalizer value */
public double getNormalizer() {
return 0;
}
/** Get the norm-2 length of an instance assuming all attributes are numeric
* and utilizing the attribute weights
* @returns norm-2 length of an instance
*/
public double lengthWeighted(Instance instance) {
int classIndex = instance.classIndex();
double length = 0;
if (instance instanceof SparseInstance) {
for (int i = 0; i < instance.numValues(); i++) {
int attrIdx = instance.index(i);
if (attrIdx != classIndex) {
double value = instance.valueSparse(i);
length += m_attrWeights[attrIdx] * value * value;
}
}
} else { // non-sparse instance
double[] values = instance.toDoubleArray();
for (int i = 0; i < values.length; i++) {
if (i != classIndex) {
// length += values[i] * values[i];
length += m_attrWeights[i] * values[i] * values[i];
}
}
}
return Math.sqrt(length);
}
/**
* Parses a given list of options. Valid options are:<p>
*
* -N <br>
* Normalize the dot product by vectors lengths
*
* -E <br>
* Use exponential conversion from similarity to distance
* (default laplacian conversion) <p>
*
* -U <br>
* Use unit conversion from similarity to distance (dist=1-sim)
* (default laplacian conversion) <p>
*
* -R <br>
* The metric is trainable and will be trained using the current MetricLearner
* (default non-trainable)
*
* @param options the list of options as an array of strings
* @exception Exception if an option is not supported
*/
public void setOptions(String[] options) throws Exception {
if (Utils.getFlag('E', options)) {
setConversionType(new SelectedTag(CONVERSION_EXPONENTIAL, TAGS_CONVERSION));
} else if (Utils.getFlag('U', options)) {
setConversionType(new SelectedTag(CONVERSION_UNIT, TAGS_CONVERSION));
} else {
setConversionType(new SelectedTag(CONVERSION_LAPLACIAN, TAGS_CONVERSION));
}
setLengthNormalized(Utils.getFlag('N', options));
if (Utils.getFlag('R', options)) {
setTrainable(Utils.getFlag('R', options));
setExternal(Utils.getFlag('X', options));
String metricLearnerString = Utils.getOption('L', options);
if (metricLearnerString.length() != 0) {
String [] metricLearnerSpec = Utils.splitOptions(metricLearnerString);
String metricLearnerName = metricLearnerSpec[0];
metricLearnerSpec[0] = "";
System.out.println("Got metric learner spec: " + metricLearnerSpec);
setMetricLearner(MetricLearner.forName(metricLearnerName, metricLearnerSpec));
}
}
Utils.checkForRemainingOptions(options);
}
/**
* Returns an enumeration describing the available options.
*
* @return an enumeration of all the available options.
*/
public Enumeration listOptions() {
Vector newVector = new Vector(4);
newVector.addElement(new Option("\tNormalize the dot product by vectors lengths\n",
"N", 0, "-N"));
newVector.addElement(new Option("\tUse exponential conversion from similarity to distance\n",
"E", 0, "-E"));
newVector.addElement(new Option("\tUse unit conversion from similarity to distance\n",
"U", 0, "-U"));
newVector.addElement(new Option("\tTrain the metric\n",
"R", 0, "-R"));
newVector.addElement(new Option("\tUse the metric learner for similarity calculations(\"external\")",
"X", 0, "-X"));
newVector.addElement(new Option(
"\tFull class name of metric learner to use, followed\n"
+ "\tby scheme options. (required)\n"
+ "\teg: \"weka.core.metrics.ClassifierMetricLearner -B weka.classifiers.function.SMO\"",
"L", 1, "-L <classifier specification>"));
return newVector.elements();
}
/**
* Gets the current settings of WeightedDotP.
*
* @return an array of strings suitable for passing to setOptions()
*/
public String [] getOptions() {
String [] options = new String [45];
int current = 0;
if (getLengthNormalized()) {
options[current++] = "-LN";
}
if (m_conversionType == CONVERSION_EXPONENTIAL) {
options[current++] = "-E";
} else if (m_conversionType == CONVERSION_UNIT) {
options[current++] = "-U";
}
if (m_trainable) {
options[current++] = "-R";
if (m_external) {
options[current++] = "-X";
}
options[current++] = "-T";
options[current++] = Utils.removeSubstring(m_metricLearner.getClass().getName(), "weka.core.metrics.");
if (m_metricLearner instanceof OptionHandler) {
String[] metricOptions = ((OptionHandler)m_metricLearner).getOptions();
for (int i = 0; i < metricOptions.length; i++) {
options[current++] = metricOptions[i];
}
}
}
while (current < options.length) {
options[current++] = "";
}
return options;
}
public static void main(String[] args) {
try {
// Create numeric attributes "length" and "weight"
Attribute length = new Attribute("length");
Attribute weight = new Attribute("weight");
Attribute height = new Attribute("height");
// Create vector to hold nominal values "first", "second", "third"
FastVector my_nominal_values = new FastVector(3);
my_nominal_values.addElement("first");
my_nominal_values.addElement("second");
my_nominal_values.addElement("third");
// Create nominal attribute "position"
Attribute position = new Attribute("position", my_nominal_values);
// Create vector of the above attributes
FastVector attributes = new FastVector(3);
attributes.addElement(length);
attributes.addElement(weight);
attributes.addElement(height);
attributes.addElement(position);
// Create the empty dataset "race" with above attributes
Instances race = new Instances("race", attributes, 0);
// Make position the class attribute
race.setClassIndex(position.index());
// Create a sparse instance with three attribute values
SparseInstance s_inst1 = new SparseInstance(1, new double[0], new int[0], 4);
s_inst1.setValue(length, 2);
s_inst1.setValue(weight, 1);
s_inst1.setValue(position, "third");
// Create a sparse instance with three attribute values
SparseInstance s_inst2 = new SparseInstance(1, new double[0], new int[0], 4);
s_inst2.setValue(length, 1);
s_inst2.setValue(height, 5);
s_inst2.setValue(position, "second");
// Create a non-sparse instance with all attribute values
Instance inst1 = new Instance(4);
inst1.setValue(length, 3);
inst1.setValue(weight, 4);
inst1.setValue(height, 5);
inst1.setValue(position, "first");
// Create a sparse instance with three attribute values
Instance inst2 = new Instance(4);
inst2.setValue(length, 2);
inst2.setValue(weight, 2);
inst2.setValue(height, 2);
inst2.setValue(position, "second");
// Set instances' dataset to be the dataset "race"
s_inst1.setDataset(race);
s_inst2.setDataset(race);
inst1.setDataset(race);
inst2.setDataset(race);
// Print the instances
System.out.println("Sparse instance S1: " + s_inst1);
System.out.println("Sparse instance S2: " + s_inst2);
System.out.println("Non-sparse instance NS1: " + inst1);
System.out.println("Non-sparse instance NS2: " + inst2);
// Print the class attribute
System.out.println("Class attribute: " + s_inst1.classAttribute());
// Print the class index
System.out.println("Class index: " + s_inst1.classIndex());
// Create a new metric and print the distances
WeightedDotP metric = new WeightedDotP(3);
metric.setClassIndex(position.index());
System.out.println("Similarity is length-normalized? " + metric.getLengthNormalized() + "\n");
System.out.println("Similarity between S1 and S2: " + metric.similarity(s_inst1, s_inst2));
System.out.println("Similarity between S1 and NS1: " + metric.similarity(s_inst1, inst1));
System.out.println("Similarity between NS1 and S1: " + metric.similarity(inst1, s_inst1));
System.out.println("Similarity between S1 and S2: " + metric.similarity(inst1, inst2));
System.out.println("\nSimilarity-distance conversion type: " +
metric.getConversionType().getSelectedTag().getReadable());
System.out.println("Distance between S1 and S2: " + metric.distance(s_inst1, s_inst2));
System.out.println("Distance between S1 and NS1: " + metric.distance(s_inst1, inst1));
System.out.println("Distance between NS1 and S1: " + metric.distance(inst1, s_inst1));
System.out.println("Distance between S1 and S2: " + metric.distance(inst1, inst2));
System.out.println();
System.out.println("Difference instance S1-S2: " + metric.createDiffInstance(s_inst1, s_inst2));
System.out.println("Difference instance S1-NS1: " + metric.createDiffInstance(s_inst1, inst1));
System.out.println("Difference instance NS1-S1: " + metric.createDiffInstance(inst1, s_inst1));
System.out.println("Difference instance NS1-NS2: " + metric.createDiffInstance(inst1, inst2));
System.out.println();
double[] weights = {0.2, 0.3, 0.4};
metric.setWeights(weights);
System.out.println("Weights: 0.2 0.3 0.4");
System.out.print("NS1: " + inst1);
metric.normalizeInstanceWeighted(inst1);
System.out.println("\tnormalized: " + inst1);
System.out.print("S1: " + s_inst1);
metric.normalizeInstanceWeighted(s_inst1);
System.out.println("\tnormalized: " + s_inst1);
} catch (Exception e) {
e.printStackTrace();
}
}
}