/* * 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(); } } }