/*
* 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.
*/
/*
* MLkNN.java
* Copyright (C) 2009-2010 Aristotle University of Thessaloniki, Thessaloniki, Greece
*/
package mulan.classifier.lazy;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import mulan.classifier.MultiLabelOutput;
import mulan.data.MultiLabelInstances;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.TechnicalInformation;
import weka.core.Utils;
import weka.core.TechnicalInformation.Field;
import weka.core.TechnicalInformation.Type;
/**
*
* <!-- globalinfo-start -->
*
* <pre>
* Class implementing the ML-kNN (Multi-Label k Nearest Neighbours) algorithm.
* The class is based on the pseudo-code made available by the authors,
* except for the option to use <it>normalized</it> Euclidean distance as a
* distance function.
* </pre>
*
* For more information:
*
* <pre>
* Zhang, M. and Zhou, Z. 2007. ML-KNN: A lazy learning approach to multi-label learning.
* Pattern Recogn. 40, 7 (Jul. 2007), 2038-2048. DOI=http://dx.doi.org/10.1016/j.patcog.2006.12.019
* </pre>
*
* <!-- globalinfo-end -->
*
* <!-- technical-bibtex-start -->
*
* <pre>
* @article{zhang+zhou:2007,
* author = {Min-Ling Zhang and Zhi-Hua Zhou},
* title = {ML-KNN: A lazy learning approach to multi-label learning},
* journal = {Pattern Recogn.},
* volume = {40},
* number = {7},
* year = {2007},
* issn = {0031-3203},
* pages = {2038--2048},
* doi = {http://dx.doi.org/10.1016/j.patcog.2006.12.019},
* publisher = {Elsevier Science Inc.},
* address = {New York, NY, USA},
* }
* </pre>
*
* <!-- technical-bibtex-end -->
*
* @author Eleftherios Spyromitros-Xioufis ( espyromi@csd.auth.gr )
* @version $Revision: 1.1 $
*/
@SuppressWarnings("serial")
public class MLkNN extends MultiLabelKNN {
/**
* Smoothing parameter controlling the strength of uniform prior <br>
* (Default value is set to 1 which yields the Laplace smoothing).
*/
protected double smooth;
/**
* A table holding the prior probability for an instance to belong in each
* class
*/
private double[] PriorProbabilities;
/**
* A table holding the prior probability for an instance not to belong in
* each class
*/
private double[] PriorNProbabilities;
/**
* A table holding the probability for an instance to belong in each class<br>
* given that i:0..k of its neighbors belong to that class
*/
private double[][] CondProbabilities;
/**
* A table holding the probability for an instance not to belong in each
* class<br>
* given that i:0..k of its neighbors belong to that class
*/
private double[][] CondNProbabilities;
/**
* @param numOfNeighbors :
* the number of neighbors
* @param smooth :
* the smoothing factor
*/
public MLkNN(int numOfNeighbors, double smooth) {
super(numOfNeighbors);
this.smooth = smooth;
}
/**
* The default constructor
*/
public MLkNN() {
super();
this.smooth = 1.0;
}
/**
* Returns a string describing classifier.
* @return a description suitable for
* displaying in the explorer/experimenter gui
*/
public String globalInfo() {
return "Class implementing the ML-kNN (Multi-Label k Nearest Neighbours) algorithm." + "\n\n" + "For more information, see\n\n" + getTechnicalInformation().toString();
}
/**
* 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.ARTICLE);
result.setValue(Field.AUTHOR, "Min-Ling Zhang and Zhi-Hua Zhou");
result.setValue(Field.TITLE, "ML-KNN: A lazy learning approach to multi-label learning");
result.setValue(Field.JOURNAL, "Pattern Recogn.");
result.setValue(Field.VOLUME, "40");
result.setValue(Field.NUMBER, "7");
result.setValue(Field.YEAR, "2007");
result.setValue(Field.ISSN, "0031-3203");
result.setValue(Field.PAGES, "2038--2048");
result.setValue(Field.PUBLISHER, "Elsevier Science Inc.");
result.setValue(Field.ADDRESS, "New York, NY, USA");
return result;
}
@Override
protected void buildInternal(MultiLabelInstances train) throws Exception {
super.buildInternal(train);
PriorProbabilities = new double[numLabels];
PriorNProbabilities = new double[numLabels];
CondProbabilities = new double[numLabels][numOfNeighbors + 1];
CondNProbabilities = new double[numLabels][numOfNeighbors + 1];
ComputePrior();
ComputeCond();
if (getDebug()) {
System.out.println("Computed Prior Probabilities");
for (int i = 0; i < numLabels; i++) {
System.out.println("Label " + (i + 1) + ": " + PriorProbabilities[i]);
}
System.out.println("Computed Posterior Probabilities");
for (int i = 0; i < numLabels; i++) {
System.out.println("Label " + (i + 1));
for (int j = 0; j < numOfNeighbors + 1; j++) {
System.out.println(j + " neighbours: " + CondProbabilities[i][j]);
System.out.println(j + " neighbours: " + CondNProbabilities[i][j]);
}
}
}
}
/**
* Computing Prior and PriorN Probabilities for each class of the training set
*/
private void ComputePrior() {
for (int i = 0; i < numLabels; i++) {
int temp_Ci = 0;
for (int j = 0; j < train.numInstances(); j++) {
double value = Double.parseDouble(train.attribute(labelIndices[i]).value(
(int) train.instance(j).value(labelIndices[i])));
if (Utils.eq(value, 1.0)) {
temp_Ci++;
}
}
PriorProbabilities[i] = (smooth + temp_Ci) / (smooth * 2 + train.numInstances());
PriorNProbabilities[i] = 1 - PriorProbabilities[i];
}
}
/**
* Computing Cond and CondN Probabilities for each class of the training set
*
* @throws Exception
*/
private void ComputeCond() throws Exception {
int[][] temp_Ci = new int[numLabels][numOfNeighbors + 1];
int[][] temp_NCi = new int[numLabels][numOfNeighbors + 1];
for (int i = 0; i < train.numInstances(); i++) {
Instances knn = new Instances(lnn.kNearestNeighbours(train.instance(i), numOfNeighbors));
// now compute values of temp_Ci and temp_NCi for every class label
for (int j = 0; j < numLabels; j++) {
int aces = 0; // num of aces in Knn for j
for (int k = 0; k < numOfNeighbors; k++) {
double value = Double.parseDouble(train.attribute(labelIndices[j]).value(
(int) knn.instance(k).value(labelIndices[j])));
if (Utils.eq(value, 1.0)) {
aces++;
}
}
// raise the counter of temp_Ci[j][aces] and temp_NCi[j][aces] by 1
if (Utils.eq(Double.parseDouble(train.attribute(labelIndices[j]).value(
(int) train.instance(i).value(labelIndices[j]))), 1.0)) {
temp_Ci[j][aces]++;
} else {
temp_NCi[j][aces]++;
}
}
}
// compute CondProbabilities[i][..] for labels based on temp_Ci[]
for (int i = 0; i < numLabels; i++) {
int temp1 = 0;
int temp2 = 0;
for (int j = 0; j < numOfNeighbors + 1; j++) {
temp1 += temp_Ci[i][j];
temp2 += temp_NCi[i][j];
}
for (int j = 0; j < numOfNeighbors + 1; j++) {
CondProbabilities[i][j] = (smooth + temp_Ci[i][j]) / (smooth * (numOfNeighbors + 1) + temp1);
CondNProbabilities[i][j] = (smooth + temp_NCi[i][j]) / (smooth * (numOfNeighbors + 1) + temp2);
}
}
}
protected MultiLabelOutput makePredictionInternal(Instance instance) throws Exception {
double[] confidences = new double[numLabels];
boolean[] predictions = new boolean[numLabels];
Instances knn = null;
try {
knn = new Instances(lnn.kNearestNeighbours(instance, numOfNeighbors));
} catch (Exception ex) {
Logger.getLogger(MLkNN.class.getName()).log(Level.SEVERE, null, ex);
}
for (int i = 0; i < numLabels; i++) {
// compute sum of aces in KNN
int aces = 0; // num of aces in Knn for i
for (int k = 0; k < numOfNeighbors; k++) {
double value = Double.parseDouble(train.attribute(labelIndices[i]).value(
(int) knn.instance(k).value(labelIndices[i])));
if (Utils.eq(value, 1.0)) {
aces++;
}
}
double Prob_in = PriorProbabilities[i] * CondProbabilities[i][aces];
double Prob_out = PriorNProbabilities[i] * CondNProbabilities[i][aces];
if (Prob_in > Prob_out) {
predictions[i] = true;
} else if (Prob_in < Prob_out) {
predictions[i] = false;
} else {
Random rnd = new Random();
predictions[i] = (rnd.nextInt(2) == 1) ? true : false;
}
// ranking function
confidences[i] = Prob_in / (Prob_in + Prob_out);
}
MultiLabelOutput mlo = new MultiLabelOutput(predictions, confidences);
return mlo;
}
}