/*********************************************************************** This file is part of KEEL-software, the Data Mining tool for regression, classification, clustering, pattern mining and so on. Copyright (C) 2004-2010 F. Herrera (herrera@decsai.ugr.es) L. S�nchez (luciano@uniovi.es) J. Alcal�-Fdez (jalcala@decsai.ugr.es) S. Garc�a (sglopez@ujaen.es) A. Fern�ndez (alberto.fernandez@ujaen.es) J. Luengo (julianlm@decsai.ugr.es) 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/ **********************************************************************/ /** * <p> * @author Written by Antonio Alejandro Tortosa (University of Granada) 05/04/2008 * @author Modified by Xavi Sol� (La Salle, Ram�n Llull University - Barcelona) 03/12/2008 * @version 1.3 * @since JDK1.2 * </p> */ package keel.Algorithms.Rule_Learning.Slipper; import keel.Dataset.*; import java.io.IOException; import org.core.*; public class Slipper { /** * <p> * Implementation of the classification algorithm Slipper, according to the paper [AAAI99]. * </p> */ //'Worth' metric public static int W=1; //'Accuracy'metric public static int A=2; //the datasets for training, validation and test MyDataset train, val, test; //the names for the output files String outputTr, outputTst, outputRules; //random numbers generator Randomize rand; //ratio of growing/pruning instances double pct; //number of growing rules per class int T; //to check if everything is correct. private boolean somethingWrong = false; /** * It reads the data from the input files (training, validation and test) and parse all the parameters * from the parameters array. * @param parameters parseParameters It contains the input files, output files and parameters */ public Slipper(parseParameters parameters) { train = new MyDataset(); val = new MyDataset(); test = new MyDataset(); try { System.out.println("\nReading the training set: " + parameters.getTrainingInputFile()); train.readClassificationSet(parameters.getTrainingInputFile(), true); System.out.println("\nReading the validation set: " + parameters.getValidationInputFile()); val.readClassificationSet(parameters.getValidationInputFile(), false); System.out.println("\nReading the test set: " + parameters.getTestInputFile()); test.readClassificationSet(parameters.getTestInputFile(), false); } catch (IOException e) { System.err.println( "There was a problem while reading the input data-sets: " + e); somethingWrong = true; } //We may check if there are some numerical attributes, because our algorithm may not handle them: //somethingWrong = somethingWrong || train.hasNumericalAttributes(); //somethingWrong = somethingWrong || train.hasMissingAttributes(); outputTr = parameters.getTrainingOutputFile(); outputTst = parameters.getTestOutputFile(); outputRules = parameters.getOutputFile(0); long seed = Long.parseLong(parameters.getParameter(0)); pct = Double.parseDouble(parameters.getParameter(1)); T = Integer.parseInt(parameters.getParameter(2)); rand=new Randomize(); rand.setSeed(seed); System.out.println("T: "+this.T+" pct: "+pct+" seed:"+seed); } /** * It launches the algorithm. */ public void execute() { if (somethingWrong) { //We do not execute the program System.err.println("An error was found, the data-set have numerical values."); System.err.println("Aborting the program"); //We should not use the statement: System.exit(-1); } else { //We do here the algorithm's operations Ruleset[] rulesets=this.slipperMulticlass(train); //Classificates the datasets' entries, according the generated rulesets String[] classification_train=train.classify(rulesets,rulesets.length); String[] classification_val=val.classify(rulesets,rulesets.length); String[] classification_test=test.classify(rulesets,rulesets.length); //Finally we should fill the training and test output files doOutput(this.val, this.outputTr, classification_val); doOutput(this.test, this.outputTst, classification_test); doRulesOutput2(this.outputRules,rulesets); System.out.println("Algorithm Finished"); } } /** * It generates the output file from a given dataset and stores it in a file. * @param dataset myDataset input dataset * @param filename String the name of the file * @param classification String[] gererated classification of the dataset */ private void doOutput(MyDataset dataset, String filename,String[] classification) { String output = new String(""); output = dataset.copyHeader(); //we insert the header in the output file //We write the output for each example for (int i = 0; i < dataset.getnData(); i++) { output += dataset.getOutputAsString(i) + " " +classification[i] + "\n"; } Fichero.escribeFichero(filename, output); } /** * It generates the output rules file from a given ruleset and stores it in a file * @param filename String the name of the file * @param rulesets Rulesets[] the rulesets (one for each class) */ private void doRulesOutput(String filename,Ruleset[] rulesets) { String output = new String(""); for (int i=0;i<rulesets.length-1;i++){ output+="if("; for(int j=0;j<rulesets[i].size();j++){ Rule current=rulesets[i].getRule(j); output+="("; for (int k=0;k<current.size();k++){ output+=current.getSimpleRule(k); if (k!=current.size()-1) output+=" && "; } output+=")"; if (j!=rulesets[i].size()-1) output+=" || "; } output+=")\n\t"; output+="output="+rulesets[i].getType()+"\nelse "; } output+="\n\toutput="+rulesets[rulesets.length-1].getType(); Fichero.escribeFichero(filename, output); } /** * It generates the output rules file from a given ruleset and stores it in a file * @param filename String the name of the file * @param rulesets Rulesets[] the rulesets (one for each class) */ private void doRulesOutput2(String filename,Ruleset[] rulesets) { String output = new String(""); for (int i=0;i<rulesets.length-1;i++){ if (rulesets[i].size()>0){ output += "Ruleset " + i + ":=" + rulesets[i].getType() + "\n"; for (int j = 0; j < rulesets[i].size(); j++) { Rule current = rulesets[i].getRule(j); output += "\t("; for (int k = 0; k < current.size(); k++) { output += current.getSimpleRule(k); if (k != current.size() - 1) output += " && "; } output += ") " + "w: " + current.getCr() + "\n"; } output += "Default Rule Weight=" + -rulesets[i].getDefaultCr() + "\n"; } } output+="\nDefault Class="+rulesets[rulesets.length-1].getType(); Fichero.escribeFichero(filename, output); } /** * It expands a rule, greedily adding simple rules, maximizing the following heuristic: * Z=sqrt(W+)-sqrt(W_) * W+: sum of the weights of the positive instances that are covered by the current rule * W_: sum of the weights of the negative instances that are covered by the current rule * @param data MyDataset the dataset * @param grow_positives Mask active positive entries * @param grow_negatives Mask active negative entries * @param distribution double[] the distribution D of weights * @return the grown rule */ public static Rule grow(MyDataset data,Mask grow_positives,Mask grow_negatives,double[] distribution){ Rule rule=new Rule(); Mask positives=grow_positives.copy(); Mask negatives=grow_negatives.copy(); int[] attributes=new int[data.getnInputs()]; int nattributes=attributes.length; for (int i=0;i<attributes.length;i++){ attributes[i]=i; } while (negatives.getnActive()>0 && nattributes>0 && positives.getnActive()>0){ int A=-1,P=-1; //A->best attribute, P-> relative position inside Attributes double V=0,best_global=-Double.MAX_VALUE; int Op=-1; for (int i=0;i<nattributes;i++){ int ai=attributes[i]; Score score=new Score(); double total_pos=0.0,total_neg=0.0; positives.resetIndex(); while (positives.next()){ if (!data.isMissing(positives,ai)){ double[] exemple=data.getExample(positives); total_pos+=distribution[positives.getIndex()]; int pos = score.findKey(exemple[ai]); if (pos!=-1) score.addPositive(pos,distribution[positives.getIndex()]); else score.addKey(exemple[ai],distribution[positives.getIndex()],Score.POSITIVE); } } negatives.resetIndex(); while (negatives.next()){ if (!data.isMissing(negatives,ai)){ double[] exemple=data.getExample(negatives); total_neg+=distribution[negatives.getIndex()]; int pos = score.findKey(exemple[ai]); if (pos!=-1) score.addNegative(pos,distribution[negatives.getIndex()]); else score.addKey(exemple[ai],distribution[negatives.getIndex()],Score.NEGATIVE); } } //First, to find the best value for the current attribute double best_v=0,best_h=-Double.MAX_VALUE; int best_operator=-1; if(Attributes.getInputAttribute(ai).getType()==Attribute.NOMINAL){ for (int j = 0; j < score.size(); j++) { double W_plus=score.getPositive(j); double W_minus=score.getNegative(j); double h=Math.sqrt(W_plus)-Math.sqrt(W_minus); if (Utilities.gr(h,best_h)) { best_h = h; best_v = score.getKey(j); best_operator=Rule.EQUAL; } } } else{ score.sort(); //Evaluating the first element as cutting point with operator <= double count_pos=0.0; double count_neg=0.0; double W_plus,W_minus; if (score.size()==1 && score.getPositive(0)!=0){ W_plus=score.getPositive(0); W_minus=score.getNegative(0); best_h=Math.sqrt(W_plus)-Math.sqrt(W_minus)+1.0; best_v = score.getKey(0); best_operator=Rule.EQUAL; } else if (score.size()==1){ best_h = -Double.MAX_VALUE; best_v = score.getKey(0); best_operator=Rule.EQUAL; } else best_h = -Double.MAX_VALUE; for (int j = 0; j < score.size()-1; j++) { //Evaluating the j-th element as cutting point with <= count_pos+=score.getPositive(j); count_neg+=score.getNegative(j); W_plus=count_pos; W_minus=count_neg; double h_lower=Math.sqrt(W_plus)-Math.sqrt(W_minus)+1.0; //Evaluating the j-th element as cutting point with > W_plus=total_pos-count_pos; W_minus=total_neg-count_neg; double h_greater=Math.sqrt(W_plus)-Math.sqrt(W_minus)+1.0; //Comparing with the best so far if (Utilities.gr(h_lower,h_greater) && Utilities.gr(h_lower,best_h)) { best_h = h_lower; best_v = score.getKey(j); best_operator=Rule.LOWER; } else if (Utilities.gr(h_greater,best_h)){ best_h = h_greater; best_v = score.getKey(j); best_operator=Rule.GREATER; } } } //Later, test if it is the best couple so far if (Utilities.gr(best_h,best_global)){ P=i; A=ai; V=best_v; Op=best_operator; best_global=best_h; } } //2.Add to the rule the couple (A,V) //Julian - If no attribute could be found, do not add the couple //I really don't know if this assumption it is correct, but it allows the program //to finish, so... if(A!=-1){ rule.grow(A,V,Op); data.filter(positives,A,V,Op); data.filter(negatives,A,V,Op); attributes[P]=attributes[nattributes-1]; } nattributes--; } return rule; } /** * It prunes a rule, minimizing with the heuristic: * 1 - V+ + V_ + V+�exp(-Cr) + V_�exp(Cr) * V+: sum of the weights of the positive instances of the prune set that are covered by the current rule * V_: sum of the weights of the negative instances of the prune set that are covered by the current rule * Cr: rule confidence (computed in the grow set) * @param rule Rule the rule to prune * @param data MyDataset the dataset * @param prune_positives Mask active positive entries for pruning * @param prune_negatives Mask active negative entries for pruning * @param grow_positives Mask active positive entries for growing * @param grow_negatives Mask active negative entries for growing * @param distribution double[] the distribution D of weights * @return the pruned rule. */ public static Rule prune(Rule rule,MyDataset data,Mask prune_positives,Mask prune_negatives, Mask grow_positives,Mask grow_negatives,double[] distribution){ double V_plus,V_minus,Cr; double h=Double.MAX_VALUE,next_h=0.0; SimpleRule last=null; V_plus=rule.getW(data,prune_positives,distribution); V_minus=rule.getW(data,prune_negatives,distribution); rule.setCr(data,grow_positives,grow_negatives,distribution); Cr=rule.getCr(); next_h=1.0-V_plus-V_minus+(V_plus*Math.exp(-Cr))+(V_minus*Math.exp(Cr)); while(Utilities.smOrEq(next_h,h) && rule.size()>1){ h=next_h; last=rule.getSimpleRule(rule.size()-1); rule.prune(rule.size()-1); V_plus=rule.getW(data,prune_positives,distribution); V_minus=rule.getW(data,prune_negatives,distribution); rule.setCr(data,grow_positives,grow_negatives,distribution); Cr=rule.getCr(); next_h=1.0-V_plus-V_minus+(V_plus*Math.exp(-Cr))+(V_minus*Math.exp(Cr)); } if (Utilities.gr(next_h,h)){ rule.grow(last); } return rule; } /** * It implements a multiclass variation of the algorithm Slipper: * 1. In each iteration, it takes the class with less instances in the dataset and * it splits this into positive (those of the taken class) and negative (the rest) instances. * 2. Then it invokes Slipper to generates a Ruleset for the taken class. * 3. Finally, it removes the instances covered by the ruleset and it carries on with the next iteration. * @param data MyDataset the dataset * @return a vector with a Ruleset for each class. */ public Ruleset[] slipperMulticlass(MyDataset data){ Ruleset[] rules=new Ruleset[data.getnClasses()]; Pair[] ordered_classes=new Pair[data.getnClasses()]; for (int i=0;i<data.getnClasses();i++){ ordered_classes[i]=new Pair(); ordered_classes[i].key=i; ordered_classes[i].value=data.numberInstances(i); } Utilities.mergeSort(ordered_classes,data.getnClasses()); Mask positives,negatives; Mask base=new Mask(data.size()); for (int i=0;i<data.getnClasses()-1;i++){ String target_class=Attributes.getOutputAttribute(0).getNominalValue(ordered_classes[i].key); positives=base.copy(); data.filterByClass(positives,target_class); negatives=base.and(positives.complement()); rules[i]=slipper(data,positives,negatives,T); rules[i].setType(target_class); base=negatives.copy(); } rules[rules.length-1]=new Ruleset(); rules[rules.length-1].addRule(new Rule()); rules[rules.length-1].setType(Attributes.getOutputAttribute(0).getNominalValue(ordered_classes[data.getnClasses()-1].key)); return rules; } /** * It reweights the instances, making use of the confidence of the last rule. * <li>- For each xi e Rt, set D(i)<-D(i)/exp(yi�Cr) yi e {-1,1} </li> * <li>- Let Zt= S(D(i) i e [1-m] </li> * <li>- For each xi, set D(i)<-D(i)/Zt</li> * @param data the Dataset * @param new_rule the new rule * @param positives the positives intances of the dataset (with yi=1) * @param negatives the negatives intances of the dataset (with yi=-1) * @param distribution the distribution D of weights * @param Cr the rule confidence (computed in the entire dataset) */ public void update(MyDataset data,Rule new_rule,Mask positives,Mask negatives,double[] distribution,double Cr){ Mask covered_pos=positives.copy(),covered_neg=negatives.copy(); if (new_rule.size()!=0){ data.filter(covered_pos, new_rule); data.filter(covered_neg, new_rule); } double expCr=Math.exp(Cr); covered_pos.resetIndex(); while(covered_pos.next()){ int i=covered_pos.getIndex(); distribution[i]=distribution[i]/expCr; } expCr=Math.exp(-Cr); covered_neg.resetIndex(); while(covered_neg.next()){ int i=covered_neg.getIndex(); distribution[i]=distribution[i]/expCr; } double Z=0.0; for (int i=0;i<data.size();i++) if (negatives.isActive(i) || positives.isActive(i)) Z+=distribution[i]; for (int i=0;i<data.size();i++) if (negatives.isActive(i) || positives.isActive(i)) distribution[i]=distribution[i]/Z; } /** * It implements the algorithm Slipper (2 class version). * @param data MyDataset the dataset * @param positives Mask active positive entries * @param negatives Mask active negative entries * @param T int number of growing rules per class * @return the generated ruleset */ public Ruleset slipper(MyDataset data,Mask positives,Mask negatives,int T){ Ruleset rules=new Ruleset(); /**********************Growing & Prunning***************************************/ double m=positives.getnActive()+negatives.getnActive(); double[] distribution=new double[data.size()]; for (int i=0;i<m;i++) distribution[i]=1.0/m; for (int i=0;i<T;i++){ //Splitting of the two dataset into two prune dataset and two grow dataset Mask[] gp_pos=positives.split(pct,rand); Mask[] gp_neg=negatives.split(pct,rand); Mask grow_pos=gp_pos[0], prune_pos=gp_pos[1]; Mask grow_neg=gp_neg[0], prune_neg=gp_neg[1]; //Grow & Prune Rule new_rule=Slipper.grow(data,grow_pos,grow_neg,distribution); Slipper.prune(new_rule,data,prune_pos,prune_neg,grow_pos,grow_neg,distribution); //The new rule is compared with the default //The new rule's stats double W_plus=new_rule.getW(data,positives,distribution); double W_minus=new_rule.getW(data,negatives,distribution); double Cr=0.5*Math.log((W_plus+1.0/(2.0*m))/(W_minus+1.0/(2.0*m))); double Zaux=Math.sqrt(W_plus)-Math.sqrt(W_minus)+1.0; //double Z=1.0-(Zaux*Zaux); //The default rule's stats double defaultW_plus=Rule.getDefaultW(data,positives,distribution); double defaultW_minus=Rule.getDefaultW(data,negatives,distribution); double defaultCr=0.5*Math.log((defaultW_plus+1.0/(2.0*m))/(defaultW_minus+1.0/(2.0*m))); double defaultZaux1=Math.sqrt(defaultW_plus)-Math.sqrt(defaultW_minus)+1.0; double defaultZaux2=Math.sqrt(defaultW_minus)-Math.sqrt(defaultW_plus)+1.0; double defaultZaux=(defaultZaux1>defaultZaux2)?defaultZaux1:defaultZaux2; //double defaultZ=1.0-(defaultZaux*defaultZaux); double bestCr=0.0; Rule best_rule=null; if (Utilities.gr(Zaux,defaultZaux)){ bestCr=Cr; best_rule=new_rule; new_rule.setCr(Cr); rules.addRule(new_rule); } else{ bestCr=defaultCr; best_rule=new Rule(); rules.addToDefaultCr(defaultCr); } //Updating update(data,best_rule,positives,negatives,distribution,bestCr); } return rules; } }