meka.classifiers.multilabel.PLST.java Source code

Java tutorial

Introduction

Here is the source code for meka.classifiers.multilabel.PLST.java

Source

/*
 *   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 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package meka.classifiers.multilabel;

import meka.core.OptionUtils;
import meka.classifiers.multitarget.CR;
import meka.core.MatrixUtils;
import weka.classifiers.Classifier;
import weka.classifiers.functions.LinearRegression;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.TechnicalInformation;
import weka.core.TechnicalInformation.Field;
import weka.core.TechnicalInformation.Type;
import weka.core.TechnicalInformationHandler;
import weka.core.matrix.Matrix;
import weka.core.matrix.SingularValueDecomposition;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

/**
 * PLST - Principal Label Space Transformation. Uses SVD to generate a matrix
 * that transforms the label space. This implementation is adapted from the 
 * MatLab implementation provided by the authors at 
 * <a href="https://github.com/hsuantien/mlc_lsdr">Github</a>
 * <br>
 * See: Farbound Tai and Hsuan-Tien Lin. Multilabel classification with 
 * principal label space transformation. Neural Computation, 24(9):2508--2542, 
 * September 2012. 
 *
 * @author     Joerg Wicker (wicker@uni-mainz.de)
 */
public class PLST extends LabelTransformationClassifier implements TechnicalInformationHandler {

    private static final long serialVersionUID = 3761303322465321039L;

    /*
     * The shift matrix, used in the training and prediction
     */
    protected Matrix m_Shift;

    /*
     * Pattern Instances, needed to transform an Instance object
     * for the prediction step
     */
    protected Instances m_PatternInstances;

    /*
     * The transformation matrix which is generated using SVD.
     */
    protected Matrix m_v = null;

    /*
     * The size of the compresed / transformed matrix.
     */
    protected int m_Size = getDefaultSize();

    /**
     * Returns the global information of the classifier.
     *
     * @return Global information of the classfier
     */
    public String globalInfo() {
        return "PLST - Principle Label Space Transformation. Uses SVD to generate a matrix "
                + "that transforms the label space. This implementation is adapted from the "
                + "MatLab implementation provided by the authors.\n\n" + "https://github.com/hsuantien/mlc_lsdr\n\n"
                + "For more information see:\n " + getTechnicalInformation();
    }

    /**
     * Change default classifier to CR with Linear Regression as base as this classifier
     * uses numeric values in the compressed labels.
     */
    protected Classifier getDefaultClassifier() {
        CR cr = new CR();
        LinearRegression lr = new LinearRegression();
        cr.setClassifier(lr);
        return cr;
    }

    /**
     * The default size, set to 5.
     *
     * @return the default size.
     */
    protected int getDefaultSize() {
        return 3;
    }

    /**
     * Returns the size of the compressed labels.
     *
     * @return The size of the compressed labels, i.e., the number of columns.
     */
    public int getSize() {
        return m_Size;
    }

    /**
     * Sets the size of the compressed labels.
     *
     * @param size The size of the compressed labels, i.e., the number of columns.
     */
    public void setSize(int size) {
        this.m_Size = size;
    }

    /**
     * The tooltip for the size.
     *
     * @return the tooltip.
     */
    public String sizeTipText() {
        return "Size of the compressed matrix. Should be \n" + "less than the number of labels and more than 1.";
    }

    /**
     * Returns an instance of a TechnicalInformation object, containing
     * detailed information about the technical background of this class,
     * e.g., paper reference or book this class is based on.
     *
     * @return the technical information about this class
     */
    @Override
    public TechnicalInformation getTechnicalInformation() {
        TechnicalInformation result;

        result = new TechnicalInformation(Type.INPROCEEDINGS);
        result.setValue(Field.AUTHOR, "Farbound Tai and Hsuan-Tien Lin");
        result.setValue(Field.TITLE, "Multilabel classification with principal label space transformation");
        result.setValue(Field.BOOKTITLE, "Neural Computation");
        result.setValue(Field.YEAR, "2012");
        result.setValue(Field.PAGES, "2508-2542");
        result.setValue(Field.VOLUME, "24");
        result.setValue(Field.NUMBER, "9");

        return result;
    }

    /**
     * Returns an enumeration of the options.  
     *
     * @return Enumeration of the options.
     */
    public Enumeration listOptions() {
        Vector newVector = new Vector();

        OptionUtils.addOption(newVector, sizeTipText(), "" + getDefaultSize(), "size");

        OptionUtils.add(newVector, super.listOptions());

        return OptionUtils.toEnumeration(newVector);
    }

    /**
     * Returns an array with the options of the classifier.
     * 
     * @return Array of options.
     */
    public String[] getOptions() {
        List<String> result = new ArrayList<>();
        OptionUtils.add(result, "size", getSize());
        OptionUtils.add(result, super.getOptions());
        return OptionUtils.toArray(result);
    }

    /**
     * Sets the options to the given values in the array.
     *
     * @param options The options to be set.
     */
    public void setOptions(String[] options) throws Exception {
        setSize(OptionUtils.parse(options, "size", getDefaultSize()));
        super.setOptions(options);
    }

    /**
     * The method to transform the labels into another set of latent labels,
     * typically a compression method is used, e.g., Boolean matrix decomposition
     * in the case of MLC-BMaD, or matrix multiplication based on SVD for PLST.
     *
     * @param D the instances to transform into new instances with transformed labels. The
     * Instances consist of features and original labels.
     * @return The resulting instances. Instances consist of features and transformed labels.
     */
    @Override
    public Instances transformLabels(Instances D) throws Exception {
        Instances features = this.extractPart(D, false);
        Instances labels = this.extractPart(D, true);

        Matrix labelMatrix = MatrixUtils.instancesToMatrix(labels);

        // first, lets do the preprocessing as in the original implementation
        double[] averages = new double[labels.numAttributes()];

        for (int i = 0; i < labels.numAttributes(); i++) {
            double[] column = labels.attributeToDoubleArray(i);
            double sum = 0.0;
            for (int j = 0; j < column.length; j++) {
                if (column[j] == 1.0) {
                    sum += 1.0;
                } else {
                    sum += -1;
                    // The algorithm needs 1/-1 coding, so let's
                    // change the matrix here
                    labelMatrix.set(j, i, -1.0);
                }
            }
            averages[i] = sum / column.length;
        }

        double[][] shiftMatrix = new double[1][labels.numAttributes()];

        shiftMatrix[0] = averages;

        // remember shift for prediction
        this.m_Shift = new Matrix(shiftMatrix);

        double[][] shiftTrainMatrix = new double[labels.numInstances()][labels.numAttributes()];

        for (int i = 0; i < labels.numInstances(); i++) {
            shiftTrainMatrix[i] = averages;
        }

        Matrix trainShift = new Matrix(shiftTrainMatrix);

        SingularValueDecomposition svd = new SingularValueDecomposition(labelMatrix.minus(trainShift));

        // The paper uses U here, but the implementation by the authors uses V, so
        // we used V here too.
        m_v = svd.getV();

        //remove columns so only size are left
        double[][] newArr = new double[m_v.getRowDimension()][this.getSize()];

        for (int i = 0; i < newArr.length; i++) {
            for (int j = 0; j < newArr[i].length; j++) {
                newArr[i][j] = m_v.getArray()[i][j];
            }
        }

        m_v = new Matrix(newArr);

        // now the multiplication (last step of the algorithm)
        Matrix compressed = MatrixUtils.instancesToMatrix(labels).times(this.m_v);

        // and transform it to Instances
        ArrayList<Attribute> attinfos = new ArrayList<Attribute>();

        for (int i = 0; i < compressed.getColumnDimension(); i++) {

            Attribute att = new Attribute("att" + i);
            attinfos.add(att);
        }

        // create pattern instances (also used in prediction) note: this is a regression
        // problem now, labels are not binary
        this.m_PatternInstances = new Instances("compressedlabels", attinfos, compressed.getRowDimension());

        // fill result Instances
        Instances result = Instances.mergeInstances(MatrixUtils.matrixToInstances(compressed, m_PatternInstances),
                features);

        result.setClassIndex(this.getSize());
        return result;
    }

    /**
     * Transforms the predictions of the internal classifier back to the original labels.
     *
     * @param y The predictions that should be transformed back. The array consists only of
     * the predictions as they are returned from the internal classifier.
     * @return The transformed predictions.
     */
    @Override
    public double[] transformPredictionsBack(double[] y) {
        // y consists of predictions and maxindex, we need only predictions
        double[] predictions = new double[y.length / 2];

        for (int i = 0; i < predictions.length; i++) {
            predictions[i] = y[predictions.length + i];
        }

        double[][] dataArray = new double[1][predictions.length];

        dataArray[0] = predictions;

        Matrix yMat = new Matrix(dataArray);

        Matrix multiplied = yMat.times(this.m_v.transpose()).plus(m_Shift);

        double[] res = new double[multiplied.getColumnDimension()];

        // change back from -1/1 coding to 0/1
        for (int i = 0; i < res.length; i++) {
            res[i] = multiplied.getArray()[0][i] < 0.0 ? 0.0 : 1.0;
        }

        return res;
    }

    /**
     * Transforms the instance in the prediction process before given to the internal multi-label
     * or multi-target classifier. The instance is passed having the original set of labels, these
     * must be replaced with the transformed labels (attributes) so that the internla classifier
     * can predict them.
     *
     * @param x The instance to transform. Consists of features and labels.
     * @return The transformed instance. Consists of features and transformed labels.
     */
    @Override
    public Instance transformInstance(Instance x) throws Exception {
        Instances tmpInst = new Instances(x.dataset());

        tmpInst.delete();
        tmpInst.add(x);

        Instances features = this.extractPart(tmpInst, false);

        Instances labels = new Instances(this.m_PatternInstances);

        labels.add(new DenseInstance(labels.numAttributes()));

        Instances result = Instances.mergeInstances(labels, features);

        result.setClassIndex(labels.numAttributes());

        return result.instance(0);
    }

    /**
     * Returns a string representation of the model.
     *
     * @return      the model
     */
    @Override
    public String getModel() {
        return "";
    }

    @Override
    public String toString() {
        return getModel();
    }

    /**
     * Main method for testing.
     * @param args - Arguments passed from the command line
     **/
    public static void main(String[] args) throws Exception {
        AbstractMultiLabelClassifier.evaluation(new PLST(), args);
    }
}