/*********************************************************************** 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/ **********************************************************************/ /* * FURIA.java * Copyright (C) 2008 Jens Christian H�hn * * (based upon) JRip.java * Copyright (C) 2001 Xin Xu, Eibe Frank */ package keel.Algorithms.Fuzzy_Rule_Learning.Hybrid.FURIA; import java.io.Serializable; import java.util.Enumeration; import java.util.Vector; import org.core.Files; import org.core.Randomize; import keel.Algorithms.Fuzzy_Rule_Learning.Hybrid.FURIA.core.*; import keel.Dataset.Attribute; import keel.Dataset.Attributes; import keel.Dataset.InstanceSet; /** <!-- globalinfo-start --> * This class implements the FURIA algorithm proposed by H�hn and H�llermeier 2009<br/> * <br/> * The FURIA algorithm is a fuzzy rule learner based on the JRip implementation (RIPRER). The main difference between FURIA and JRip is that FURIA makes no use of default rules. Furthermore FURIA has a changed pruning procedure, which means that the pruning during the IREP* runs was deactivated permanently. It was found out experimentally that this improved the classification rate. The following description from the JRip class was altered to describe the methodology of FURIA: <br/> * <br/> * Initialize RS = {}, and for each of both class DO: <br/> * <br/> * 1. Building stage:<br/> * Repeat 1.1 until the description length (DL) of the ruleset and examples is 64 bits greater than the smallest DL met so far, or there are no positive examples, or the error rate >= 50%. <br/> * <br/> * 1.1. Grow phase:<br/> * Grow one rule by greedily adding antecedents (or conditions) to the rule until the rule is perfect (i.e. 100% accurate). The procedure tries every possible value of each attribute and selects the condition with highest information gain: p(log(p/t)-log(P/T)).<br/> * <br/> * 2. Optimization stage:<br/> * after generating the initial ruleset {Ri}, generate and prune two variants of each rule Ri from randomized data using procedure 1.1 and X.1. But one variant is generated from an empty rule while the other is generated by greedily adding antecedents to the original rule. Moreover, the pruning metric used here is (TP+TN)/(P+N).Then the smallest possible DL for each variant and the original rule is computed. The variant with the minimal DL is selected as the final representative of Ri in the ruleset.After all the rules in {Ri} have been examined and if there are still residual positives, more rules are generated based on the residual positives using Building Stage again. <br/> * 3. Delete the rules from the ruleset that would increase the DL of the whole ruleset if it were in it. and add resultant ruleset to RS. <br/> * ENDDO<br/> * <br/> * Fuzzification of RS:<br/> * For each rule r in every ruleset in RS DO<br/> * 4. Fuzzification of antecedents: <br/> * Apply greedy strategy to fuzzify the existing antecedents in r the following way:<br/> * <br/> * 4.1 Examine all possible support bounds and select the one which gains the highest purity on the training data.<br/> * 4.2 Set the maximum support bound determined in 4.1 and restart with 4.1 but withouth the fuzzified antecedent.<br/> * ENDDO<br/> * <br/> * X.1. Pruning:<br/> * Incrementally prune each rule and allow the pruning of any final sequences of the antecedents;The pruning metric is (p-n)/(p+n) -- but it's actually 2p/(p+n) -1, so in this implementation we simply use p/(p+n) (actually (p+1)/(p+n+2), thus if p+n is 0, it's 0.5). * <br><br> * Classification time:<br> * If an instance is not covered by any rule, apply a rule stretching mechanism: Cut every rule just in front of the first discriminating antecedent such that the this way stretched rule covers the instance. Doing this for all rules will lead to a set of rules in which each one covers the instance (or is empty and may be excluded). To determine the rule that assigns the class calculate the weight given by its purity using the m-measure on the one hand the Laplace-fraction of antecedents left in comparison to the original number of the respective rule. The rule that maximizes that value is from the class that will be assigned. * <p/> <!-- globalinfo-end --> * <!-- technical-bibtex-start --> * BibTeX: * <pre> * @article{huehn2009furia, * author = {Jens Christian H�hn and Eyke H�llermeier}, * journal = {Data Mining and Knowledge Discovery}, * title = {FURIA: An Algorithm for Unordered Fuzzy Rule Induction}, * year = {to appear} * } * @inproceedings{Cohen1995, * author = {William W. Cohen}, * booktitle = {Twelfth International Conference on Machine Learning}, * pages = {115-123}, * publisher = {Morgan Kaufmann}, * title = {Fast Effective Rule Induction}, * year = {1995} * } * </pre> * <p/> * <!-- technical-bibtex-end --> * <!-- options-start --> * Valid options are: <p/> * * <pre> -F <number of folds> * Set number of folds for REP * One fold is used as pruning set. * (default 3)</pre> * * <pre> -N <min. weights> * Set the minimal weights of instances * within a split. * (default 2.0)</pre> * * <pre> -O <number of runs> * Set the number of runs of * optimizations. (Default: 2)</pre> * * <pre> -D * Set whether turn on the * debug mode (Default: false)</pre> * * <pre> -S <seed> * The seed of randomization * (Default: 1)</pre> * * <pre> -E * Whether NOT check the error rate>=0.5 * in stopping criteria (default: check)</pre> * * <pre> -s * Whether use rule stretching or refrain from classifying * (default: use stretching)</pre> * <!-- options-end --> * * @author written by Jens Christian H�hn (huehn@gmx.net) * @author modified for KEEL by Alberto Fern�ndez (alberto.fernandez@ujaen.es) [University of Jaen] 23/10/2013 * @version $Revision: 1.1 $ */ public class FURIA extends Classifier implements WeightedInstancesHandler { /** for serialization */ static final long serialVersionUID = -6589312996832147161L; /** The limit of description length surplus in ruleset generation */ private static double MAX_DL_SURPLUS = 64.0; /** The class attribute of the data*/ protected AttributeWeka m_Class; /** The ruleset */ public FastVector m_Ruleset; /** The predicted class distribution */ protected FastVector m_Distributions; /** Runs of optimizations */ private int m_Optimizations; /** Random object used in this class */ protected Randomize m_Random = null; /** # of all the possible conditions in a rule */ protected double m_Total = 0; /** The seed to perform randomization */ protected long m_Seed; /** The number of folds to split data into Grow and Prune for IREP */ private int m_Folds; /** The minimal number of instance weights within a split*/ double m_MinNo = 2.0; /** Whether in a debug mode */ protected boolean m_Debug = false; /** Whether check the error rate >= 0.5 in stopping criteria */ private boolean m_CheckErr = true; /** The class distribution of the training data*/ double[] aprioriDistribution; /** The RuleStats for the ruleset of each class value */ protected FastVector m_RulesetStats; /** Whether use rule stretching */ private boolean m_useRuleStretching = true; //-------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------- /** * Constructor. * * @param params The parameters of the algorithm */ private String trainFile, evalFile, testFile; private String outputTrainFile, outputTestFile, outputClassifierFile; /** filter: Normalize training data */ public static final int FILTER_NORMALIZE = 0; /** filter: Standardize training data */ public static final int FILTER_STANDARDIZE = 1; /** filter: No normalization/standardization */ public static final int FILTER_NONE = 2; public FURIA(parseParameters params) { m_Seed = Long.parseLong(params.getParameter(0)); m_Optimizations = Integer.parseInt(params.getParameter(1));//2 m_Folds = Integer.parseInt(params.getParameter(2));//3 trainFile = params.getTrainingInputFile(); evalFile = params.getValidationInputFile(); testFile = params.getTestInputFile(); outputTrainFile = params.getTrainingOutputFile(); outputTestFile = params.getTestOutputFile(); outputClassifierFile = params.getOutputFile(0); } /** * It launches the FURIA algorithm */ public void execute() { Instances isWeka; Instance instWeka; InstanceSet IS = new InstanceSet(); InstanceSet ISval = new InstanceSet(); InstanceSet IStest = new InstanceSet(); try { //*********build the FR3 classifier********/ IS.readSet(trainFile, true); isWeka = InstancesKEEL2Weka(IS, FILTER_NONE); buildClassifier(isWeka); //********validate the obtained FR3*******// ISval.readSet(evalFile, false); isWeka = InstancesKEEL2Weka(ISval, FILTER_NONE); //obtain the predicted class for each train instance Attribute a = Attributes.getOutputAttribute(0); String outputVal = new String(""); int hits = 0; for (int i = 0; i < isWeka.numInstances(); i++) { keel.Dataset.Instance inst = ISval.getInstance(i); instWeka = isWeka.instance(i); instWeka.setDataset(isWeka); int outputClass = (int)this.classifyInstance(instWeka); String realClass = inst.getOutputNominalValues(0); String predictedClass = a.getNominalValue(outputClass); if (realClass.compareTo(predictedClass) == 0) { hits++; } outputVal += realClass + " " + predictedClass+"\n"; } double accTrain = 1.0 * hits / isWeka.numInstances(); IStest.readSet(testFile, false); isWeka = InstancesKEEL2Weka(IStest, FILTER_NONE); String outputTest = new String(""); hits = 0; for (int i = 0; i < isWeka.numInstances(); i++) { keel.Dataset.Instance inst = IStest.getInstance(i); instWeka = isWeka.instance(i); instWeka.setDataset(isWeka); int outputClass = (int)this.classifyInstance(instWeka); String realClass = inst.getOutputNominalValues(0); String predictedClass = a.getNominalValue(outputClass); if (realClass.compareTo(predictedClass) == 0) { hits++; } outputTest += realClass + " " + predictedClass+"\n"; } double accTest = 1.0 * hits / isWeka.numInstances(); writeOutput(outputVal,outputTest,accTrain,accTest); } catch (Exception ex) { System.err.println("Fatal Error building the FURIA model!"); ex.printStackTrace(); } ; Files.writeFile(outputClassifierFile, toString() + "\n\n\n\n" + "REGLAS = " + m_Ruleset.size()); } /** * Creates a new allocated WEKA's set of Instances (i.e. Instances) from a KEEL's set of instances * (i.e. InstanceSet). * @param is The KEEL Instance set * @param preprocessType An integer with the type of preprocess done before exporting data to Weka format (0 = normalize, 1 = standardize, 2 = do nothing). * @param nominal2binary True if the nominal values must be converted in a set of binary ones (one bit per value of the nominal attribute). * @return A new allocated WEKA formatted Instance set */ protected Instances InstancesKEEL2Weka(InstanceSet is, int preprocessType) { Attribute a, newAt; Instance instW; keel.Dataset.Instance instK; int out, in, newNumAttributes, enlargedValueVectorPos; double values[]; Instances data; FastVector atts; // Create header of instances object out = Attributes.getInputNumAttributes(); //the class attribute is usually the last one atts = new FastVector(Attributes.getNumAttributes()); for (int i = 0; i < Attributes.getNumAttributes(); i++) { a = Attributes.getAttribute(i); //atts.addElement(a); AttributeWeka aWeka; if (a.getType() == a.NOMINAL) { FastVector nominalValues = new FastVector(a.getNumNominalValues()); for (int j = 0; j < a.getNumNominalValues(); j++) { nominalValues.addElement(a.getNominalValue(j)); } aWeka = new AttributeWeka(a.getName(), nominalValues, i); } else { String range = new String("["); range += a.getMinAttribute(); range += ","; range += a.getMaxAttribute(); range += ")"; aWeka = new AttributeWeka(a.getName(), i); aWeka.setNumericRange(range); } atts.addElement(aWeka); if (a.getDirectionAttribute() == Attribute.OUTPUT) { out = i; } } data = new Instances(Attributes.getRelationName(), atts, is.getNumInstances()); data.setClassIndex(out); newNumAttributes = Attributes.getNumAttributes(); //now fill the data in the data instanceset for (int i = 0; i < is.getNumInstances(); i++) { instK = is.getInstance(i); in = out = 0; enlargedValueVectorPos = 0; values = new double[newNumAttributes]; for (int j = 0; j < Attributes.getNumAttributes(); j++) { a = Attributes.getAttribute(j); if (a.getDirectionAttribute() == Attribute.INPUT) { if (a.getType() != Attribute.NOMINAL) { values[enlargedValueVectorPos] = instK. getAllInputValues()[in]; enlargedValueVectorPos++; } else { values[enlargedValueVectorPos] = instK. getAllInputValues()[in]; enlargedValueVectorPos++; } in++; } else { values[enlargedValueVectorPos] = instK.getAllOutputValues()[ out]; out++; enlargedValueVectorPos++; } } //**IMPORTANT** We set the weight of the instance to ONE instW = new Instance(1, values); data.add(instW); } return data; } /** * It writes the training and test files with the real and predicted classes * @param outputVal String The string with the training (validation) output * @param outputTest String The string with the test output * @param accTrain double The accuracy rate in training * @param accTest double The accuracy rate in test */ void writeOutput(String outputVal, String outputTest, double accTrain, double accTest){ String p = new String(""); p = "@relation " + Attributes.getRelationName() + "\n"; p += Attributes.getInputAttributesHeader(); p += Attributes.getOutputAttributesHeader(); p += Attributes.getInputHeader() + "\n"; p += Attributes.getOutputHeader() + "\n"; p += "@data\n"; Files.writeFile(outputTrainFile,p+outputVal); Files.writeFile(outputTestFile,p+outputTest); System.out.println("Training accuracy: "+accTrain); System.out.println("Test accuracy: "+accTest); System.out.println("m_Optimizations = " + m_Optimizations); System.out.println("m_Seed = " + m_Seed); System.out.println("m_Folds = " + m_Folds); System.out.println("m_MinNo = " + m_MinNo); } //-------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------- /** * Returns an enumeration of the additional measure names * @return an enumeration of the measure names */ public Enumeration enumerateMeasures() { Vector newVector = new Vector(1); newVector.addElement("measureNumRules"); return newVector.elements(); } /** * Returns the value of the named measure * @param additionalMeasureName the name of the measure to query for its value * @return the value of the named measure * @throws IllegalArgumentException if the named measure is not supported */ public double getMeasure(String additionalMeasureName) { if (additionalMeasureName.compareToIgnoreCase("measureNumRules") == 0) return m_Ruleset.size(); else throw new IllegalArgumentException(additionalMeasureName+" not supported (RIPPER)"); } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String foldsTipText() { return "Determines the amount of data used for pruning. One fold is used for " + "pruning, the rest for growing the rules."; } /** * Sets the number of folds to use * * @param fold the number of folds */ public void setFolds(int fold) { m_Folds = fold; } /** * Gets the number of folds * * @return the number of folds */ public int getFolds(){ return m_Folds; } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String minNoTipText() { return "The minimum total weight of the instances in a rule."; } /** * Sets the minimum total weight of the instances in a rule * * @param m the minimum total weight of the instances in a rule */ public void setMinNo(double m) { m_MinNo = m; } /** * Gets the minimum total weight of the instances in a rule * * @return the minimum total weight of the instances in a rule */ public double getMinNo(){ return m_MinNo; } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String seedTipText() { return "The seed used for randomizing the data."; } /** * Sets the seed value to use in randomizing the data * * @param s the new seed value */ public void setSeed(long s) { m_Seed = s; } /** * Gets the current seed value to use in randomizing the data * * @return the seed value */ public long getSeed(){ return m_Seed; } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String optimizationsTipText() { return "The number of optimization runs."; } /** * Sets the number of optimization runs * * @param run the number of optimization runs */ public void setOptimizations(int run) { m_Optimizations = run; } /** * Gets the the number of optimization runs * * @return the number of optimization runs */ public int getOptimizations() { return m_Optimizations; } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String debugTipText() { return "Whether debug information is output to the console."; } /** * Sets whether debug information is output to the console * * @param d whether debug information is output to the console */ public void setDebug(boolean d) { m_Debug = d; } /** * Gets whether debug information is output to the console * * @return whether debug information is output to the console */ public boolean getDebug(){ return m_Debug; } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String checkErrorRateTipText() { return "Whether check for error rate >= 1/2 is included" + " in stopping criterion."; } /** * Sets whether to check for error rate is in stopping criterion * * @param d whether to check for error rate is in stopping criterion */ public void setCheckErrorRate(boolean d) { m_CheckErr = d; } /** * Gets whether to check for error rate is in stopping criterion * * @return true if checking for error rate is in stopping criterion */ public boolean getCheckErrorRate(){ return m_CheckErr; } /** * Returns the tip text for this property * @return tip text for this property suitable for * displaying in the explorer/experimenter gui */ public String useRuleStretchingTipText() { return "Whether rule stretching is performed."; } /** * Sets whether pruning is performed * * @param d Whether pruning is performed */ public void setUseRuleStretching(boolean d) { m_useRuleStretching = d; } /** * Gets whether pruning is performed * * @return true if pruning is performed */ public boolean getUseRuleStretching(){ return m_useRuleStretching; } /** * Get the ruleset generated by Ripper * * @return the ruleset */ public FastVector getRuleset(){ return m_Ruleset; } /** * Get the statistics of the ruleset in the given position * * @param pos the position of the stats, assuming correct * @return the statistics of the ruleset in the given position */ public RuleStats getRuleStats(int pos) { return (RuleStats)m_RulesetStats.elementAt(pos); } /** * Builds Ripper in the order of class frequencies. For each class * it's built in two stages: building and optimization * * @param instances the training data * @throws Exception if classifier can't be built successfully */ public void buildClassifier(Instances instances) throws Exception { // can classifier handle the data? //getCapabilities().testWithFail(instances); // remove instances with missing class instances = new Instances(instances); instances.deleteWithMissingClass(); aprioriDistribution = new double[instances.classAttribute().numValues()]; boolean allWeightsAreOne = true; for (int i = 0 ; i < instances.numInstances(); i++){ aprioriDistribution[(int)instances.instance(i).classValue()]+=instances.instance(i).weight(); if (allWeightsAreOne && instances.instance(i).weight() != 1.0){ allWeightsAreOne = false; break; } } m_Random.setSeed(m_Seed);//m_Random = instances.getRandomNumberGenerator(m_Seed); m_Total = RuleStats.numAllConditions(instances); if(m_Debug) System.err.println("Number of all possible conditions = "+m_Total); Instances data = new Instances(instances); if(data == null) throw new Exception(" Unable to randomize the class orders."); m_Class = data.classAttribute(); m_Ruleset = new FastVector(); m_RulesetStats = new FastVector(); m_Distributions = new FastVector(); // Sort by classes frequency if(m_Debug){ System.err.println("Sorted classes:"); for(int x=0; x < m_Class.numValues(); x++) System.err.println(x+": "+m_Class.value(x) + " has " + aprioriDistribution[x] + " instances."); } // Iterate from less prevalent class to more frequent one oneClass: for(int y=0; y < data.numClasses(); y++){ // For each class double classIndex = (double)y; if(m_Debug){ int ci = (int)classIndex; System.err.println("\n\nClass "+m_Class.value(ci)+"("+ci+"): " + aprioriDistribution[y] + "instances\n"+ "=====================================\n"); } if(Utils.eq(aprioriDistribution[y],0.0)) // No data for this class continue oneClass; // The expected FP/err is the proportion of the class double expFPRate = (aprioriDistribution[y] / Utils.sum(aprioriDistribution)); double classYWeights = 0, totalWeights = 0; for(int j=0; j < data.numInstances(); j++){ Instance datum = data.instance(j); totalWeights += datum.weight(); if((int)datum.classValue() == y){ classYWeights += datum.weight(); } } // DL of default rule, no theory DL, only data DL double defDL; if(classYWeights > 0) defDL = RuleStats.dataDL(expFPRate, 0.0, totalWeights, 0.0, classYWeights); else continue oneClass; // Subsumed by previous rules if(Double.isNaN(defDL) || Double.isInfinite(defDL)) throw new Exception("Should never happen: "+ "defDL NaN or infinite!"); if(m_Debug) System.err.println("The default DL = "+defDL); rulesetForOneClass(expFPRate, data, classIndex, defDL); } for(int z=0; z < m_Ruleset.size(); z++){ RipperRule rule = (RipperRule)m_Ruleset.elementAt(z); for(int j = 0; j < rule.m_Antds.size(); j++){ Antd outerAntd = (Antd)rule.m_Antds.elementAt(j); for (int k = j+1; k < rule.m_Antds.size(); k++){ Antd innerAntd = (Antd)rule.m_Antds.elementAt(k); if (outerAntd.att.index() == innerAntd.att.index() && outerAntd.value==innerAntd.value){ rule.m_Antds.setElementAt(rule.m_Antds.elementAt(k), j); rule.m_Antds.removeElementAt(k--); } } } } for(int z=0; z < m_RulesetStats.size(); z++){ RuleStats oneClass = (RuleStats)m_RulesetStats.elementAt(z); for(int xyz=0; xyz < oneClass.getRulesetSize(); xyz++){ RipperRule rule = (RipperRule)((FastVector)oneClass.getRuleset()).elementAt(xyz); // 1st, the fuzzification for all known antecedents is done rule.findAndSetSupportBoundForKnownAntecedents(data, allWeightsAreOne); double[] classDist = oneClass.getDistributions(xyz); // Check for sum=0, because otherwise it does not work if (Utils.sum(classDist)>0) Utils.normalize(classDist); if(classDist != null) m_Distributions.addElement(classDist); } } for(int z=0; z < m_Ruleset.size(); z++){ RipperRule rule = (RipperRule)m_Ruleset.elementAt(z); for(int j = 0; j < rule.m_Antds.size(); j++){ Antd antd = (Antd)rule.m_Antds.elementAt(j); if (antd instanceof NumericAntd) { NumericAntd numAntd = (NumericAntd) antd; if (!numAntd.fuzzyYet){ for (int i = 0; i < data.numInstances(); i++){ if ((numAntd.value == 1 && numAntd.splitPoint > data.instance(i).value(numAntd.att.index()) && (numAntd.supportBound < data.instance(i).value(numAntd.att.index()) || !numAntd.fuzzyYet) ) || (numAntd.value == 0 && numAntd.splitPoint < data.instance(i).value(numAntd.att.index()) && (numAntd.supportBound > data.instance(i).value(numAntd.att.index()) || !numAntd.fuzzyYet) ) ){ numAntd.supportBound = data.instance(i).value(numAntd.att.index()); numAntd.fuzzyYet = true; } } } } } } //Determine confidence for(int z=0; z < m_Ruleset.size(); z++){ RipperRule rule = (RipperRule)m_Ruleset.elementAt(z); rule.calculateConfidences(data); } } /** * Classify the test instance with the rule learner and provide * the class distributions * * @param datum the instance to be classified * @return the distribution * @throws Exception */ public double[] distributionForInstance(Instance datum) throws Exception{ //test for multiple overlap of rules double[] rulesCoveringForEachClass = new double[datum.numClasses()]; for(int i=0; i < m_Ruleset.size(); i++){ RipperRule rule = (RipperRule)m_Ruleset.elementAt(i); /* In case that one class does not contain any instances (e.g. in UCI-dataset glass), * a default rule assigns all instances to the other class. Such a rule may be ignored here. */ if (!rule.hasAntds()) continue; // Calculate the maximum degree of coverage if(rule.covers(datum)){ rulesCoveringForEachClass[(int)rule.m_Consequent] += rule.coverageDegree(datum) * rule.getConfidence(); //System.err.println("Cov: "+rule.getConfidence()); } } // If no rule covered the example, then maybe start the rule stretching if (Utils.sum(rulesCoveringForEachClass)==0){ // If rule stretching is not allowed, return no prediction if (m_useRuleStretching == false) return new double[rulesCoveringForEachClass.length]; // Copy the ruleset as backup FastVector origRuleset = (FastVector) m_Ruleset.copyElements(); // Find for every rule the first antecedent that does not // cover the given instance. double maxConfidence = Double.NEGATIVE_INFINITY; for(int i=0; i < m_Ruleset.size(); i++){ RipperRule rule = (RipperRule)m_Ruleset.elementAt(i); double numAntdsBefore = rule.m_Antds.size(); int firstAntdToDelete = Integer.MAX_VALUE; for (int j = 0; j < rule.m_Antds.size(); j++){ if (((Antd)rule.m_Antds.elementAt(j)).covers(datum)==0){ firstAntdToDelete = j; break; } } // Prune antecedent such that it covers the instance for (int j = firstAntdToDelete; j < rule.m_Antds.size(); j++){ rule.m_Antds.removeElementAt(j--); } double numAntdsAfter = rule.m_Antds.size(); // Empty rules shall not vote here if (!rule.hasAntds()) continue; // Calculate the maximum degree of coverage and weight the rule // by its confidence and the fraction of antecedents left after // rule stretching double secondWeight = (numAntdsAfter+1)/(numAntdsBefore+2) ; if (rule.getConfidence() *secondWeight*rule.coverageDegree(datum) >= maxConfidence){ maxConfidence = rule.getConfidence()*secondWeight*rule.coverageDegree(datum); rulesCoveringForEachClass = new double[rulesCoveringForEachClass.length]; rulesCoveringForEachClass[(int)rule.m_Consequent] = 1; } } // Reestablish original ruleset m_Ruleset = origRuleset; } //check for conflicts double[] maxClasses = new double[rulesCoveringForEachClass.length]; for (int i = 0; i < rulesCoveringForEachClass.length; i++){ if (rulesCoveringForEachClass[Utils.maxIndex(rulesCoveringForEachClass)] == rulesCoveringForEachClass[i] && rulesCoveringForEachClass[i]>0) maxClasses[i] = 1; } if (Utils.sum(maxClasses)>0){ for (int i = 0; i < maxClasses.length; i++){ maxClasses[i] *= aprioriDistribution[i]/Utils.sum(aprioriDistribution); } rulesCoveringForEachClass=maxClasses; } // If no stretched rule was able to cover the instance, // then fall back to the apriori distribution if (Utils.sum(rulesCoveringForEachClass)==0){ rulesCoveringForEachClass = aprioriDistribution; } if (Utils.sum(rulesCoveringForEachClass)>0) Utils.normalize(rulesCoveringForEachClass); return rulesCoveringForEachClass; } /** Build a ruleset for the given class according to the given data * * @param expFPRate the expected FP/(FP+FN) used in DL calculation * @param data the given data * @param classIndex the given class index * @param defDL the default DL in the data * @throws Exception if the ruleset can be built properly */ protected Instances rulesetForOneClass(double expFPRate, Instances data, double classIndex, double defDL) throws Exception { Instances newData = data, growData, pruneData; boolean stop = false; FastVector ruleset = new FastVector(); double dl = defDL, minDL = defDL; RuleStats rstats = null; double[] rst; // Check whether data have positive examples boolean defHasPositive = true; // No longer used boolean hasPositive = defHasPositive; /********************** Building stage ***********************/ if(m_Debug) System.err.println("\n*** Building stage ***"); while((!stop) && hasPositive){ // Generate new rules until // stopping criteria met RipperRule oneRule; oneRule = new RipperRule(this.aprioriDistribution); oneRule.setConsequent(classIndex); // Must set first if(m_Debug) System.err.println("\nNo pruning: growing a rule ..."); oneRule.grow(newData); // Build the rule if(m_Debug) System.err.println("No pruning: one rule found:\n"+ oneRule.toString(m_Class)); // Compute the DL of this ruleset if(rstats == null){ // First rule rstats = new RuleStats(); rstats.setNumAllConds(m_Total); rstats.setData(newData); } rstats.addAndUpdate(oneRule); int last = rstats.getRuleset().size()-1; // Index of last rule dl += rstats.relativeDL(last, expFPRate, m_CheckErr); if(Double.isNaN(dl) || Double.isInfinite(dl)) throw new Exception("Should never happen: dl in "+ "building stage NaN or infinite!"); if(m_Debug) System.err.println("Before optimization("+last+ "): the dl = "+dl+" | best: "+minDL); if(dl < minDL) minDL = dl; // The best dl so far rst = rstats.getSimpleStats(last); if(m_Debug) System.err.println("The rule covers: "+rst[0]+ " | pos = " + rst[2] + " | neg = " + rst[4]+ "\nThe rule doesn't cover: "+rst[1]+ " | pos = " + rst[5]); stop = checkStop(rst, minDL, dl); if(!stop){ ruleset.addElement(oneRule); // Accepted newData = rstats.getFiltered(last)[1];// Data not covered hasPositive = Utils.gr(rst[5], 0.0); // Positives remaining? if(m_Debug) System.err.println("One rule added: has positive? " +hasPositive); } else{ if(m_Debug) System.err.println("Quit rule"); rstats.removeLast(); // Remove last to be re-used } }// while !stop /******************** Optimization stage *******************/ RuleStats finalRulesetStat = null; for(int z=0; z < m_Optimizations; z++){ if(m_Debug) System.err.println("\n*** Optimization: run #" +z+" ***"); newData = data; finalRulesetStat = new RuleStats(); finalRulesetStat.setData(newData); finalRulesetStat.setNumAllConds(m_Total); int position=0; stop = false; boolean isResidual = false; hasPositive = defHasPositive; dl = minDL = defDL; oneRule: while(!stop && hasPositive){ isResidual = (position>=ruleset.size()); // Cover residual positive examples // Re-do shuffling and stratification //newData.randomize(m_Random); newData = RuleStats.stratify(newData, m_Folds, m_Random); Instances[] part = RuleStats.partition(newData, m_Folds); growData=part[0]; pruneData=part[1]; //growData=newData.trainCV(m_Folds, m_Folds-1); //pruneData=newData.testCV(m_Folds, m_Folds-1); RipperRule finalRule; if(m_Debug) System.err.println("\nRule #"+position + "| isResidual?" + isResidual+ "| data size: "+newData.sumOfWeights()); if(isResidual){ RipperRule newRule = new RipperRule(this.aprioriDistribution); newRule.setConsequent(classIndex); if(m_Debug) System.err.println("\nGrowing and pruning"+ " a new rule ..."); newRule.grow(newData); finalRule = newRule; if(m_Debug) System.err.println("\nNew rule found: "+ newRule.toString(m_Class)); } else{ RipperRule oldRule = (RipperRule)ruleset.elementAt(position); boolean covers = false; // Test coverage of the next old rule for(int i=0; i<newData.numInstances(); i++) if(oldRule.covers(newData.instance(i))){ covers = true; break; } if(!covers){// Null coverage, no variants can be generated finalRulesetStat.addAndUpdate(oldRule); position++; continue oneRule; } // 2 variants if(m_Debug) System.err.println("\nGrowing and pruning"+ " Replace ..."); RipperRule replace = new RipperRule(this.aprioriDistribution); replace.setConsequent(classIndex); replace.grow(growData); // Remove the pruning data covered by the following // rules, then simply compute the error rate of the // current rule to prune it. According to Ripper, // it's equivalent to computing the error of the // whole ruleset -- is it true? pruneData = RuleStats.rmCoveredBySuccessives(pruneData,ruleset, position); replace.prune(pruneData, true); if(m_Debug) System.err.println("\nGrowing and pruning"+ " Revision ..."); RipperRule revision = (RipperRule)oldRule.copy(); // For revision, first rm the data covered by the old rule Instances newGrowData = new Instances(growData, 0); for(int b=0; b<growData.numInstances(); b++){ Instance inst = growData.instance(b); if(revision.covers(inst)) newGrowData.add(inst); } revision.grow(newGrowData); revision.prune(pruneData, true); double[][] prevRuleStats = new double[position][6]; for(int c=0; c < position; c++) prevRuleStats[c] = finalRulesetStat.getSimpleStats(c); // Now compare the relative DL of variants FastVector tempRules = (FastVector)ruleset.copyElements(); tempRules.setElementAt(replace, position); RuleStats repStat = new RuleStats(data, tempRules); repStat.setNumAllConds(m_Total); repStat.countData(position, newData, prevRuleStats); //repStat.countData(); rst = repStat.getSimpleStats(position); if(m_Debug) System.err.println("Replace rule covers: "+rst[0]+ " | pos = " + rst[2] + " | neg = " + rst[4]+ "\nThe rule doesn't cover: "+rst[1]+ " | pos = " + rst[5]); double repDL = repStat.relativeDL(position, expFPRate, m_CheckErr); if(m_Debug) System.err.println("\nReplace: "+ replace.toString(m_Class) +" |dl = "+repDL); if(Double.isNaN(repDL) || Double.isInfinite(repDL)) throw new Exception("Should never happen: repDL"+ "in optmz. stage NaN or "+ "infinite!"); tempRules.setElementAt(revision, position); RuleStats revStat = new RuleStats(data, tempRules); revStat.setNumAllConds(m_Total); revStat.countData(position, newData, prevRuleStats); //revStat.countData(); double revDL = revStat.relativeDL(position, expFPRate, m_CheckErr); if(m_Debug) System.err.println("Revision: " + revision.toString(m_Class) +" |dl = "+revDL); if(Double.isNaN(revDL) || Double.isInfinite(revDL)) throw new Exception("Should never happen: revDL"+ "in optmz. stage NaN or "+ "infinite!"); rstats = new RuleStats(data, ruleset); rstats.setNumAllConds(m_Total); rstats.countData(position, newData, prevRuleStats); //rstats.countData(); double oldDL = rstats.relativeDL(position, expFPRate, m_CheckErr); if(Double.isNaN(oldDL) || Double.isInfinite(oldDL)) throw new Exception("Should never happen: oldDL"+ "in optmz. stage NaN or "+ "infinite!"); if(m_Debug) System.err.println("Old rule: "+ oldRule.toString(m_Class) +" |dl = "+oldDL); if(m_Debug) System.err.println("\nrepDL: "+repDL+ "\nrevDL: "+revDL+ "\noldDL: "+oldDL); if((oldDL <= revDL) && (oldDL <= repDL)) finalRule = oldRule; // Old the best else if(revDL <= repDL) finalRule = revision; // Revision the best else finalRule = replace; // Replace the best } finalRulesetStat.addAndUpdate(finalRule); rst = finalRulesetStat.getSimpleStats(position); if(isResidual){ dl += finalRulesetStat.relativeDL(position, expFPRate, m_CheckErr); if(m_Debug) System.err.println("After optimization: the dl" +"="+dl+" | best: "+minDL); if(dl < minDL) minDL = dl; // The best dl so far stop = checkStop(rst, minDL, dl); if(!stop) ruleset.addElement(finalRule); // Accepted else{ finalRulesetStat.removeLast(); // Remove last to be re-used position--; } } else ruleset.setElementAt(finalRule, position); // Accepted if(m_Debug){ System.err.println("The rule covers: "+rst[0]+ " | pos = " + rst[2] + " | neg = " + rst[4]+ "\nThe rule doesn't cover: "+rst[1]+ " | pos = " + rst[5]); System.err.println("\nRuleset so far: "); for(int x=0; x<ruleset.size(); x++) System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class)); System.err.println(); } //Data not covered if(finalRulesetStat.getRulesetSize() > 0)// If any rules newData = finalRulesetStat.getFiltered(position)[1]; hasPositive = Utils.gr(rst[5], 0.0); //Positives remaining? position++; } // while !stop && hasPositive if(ruleset.size() > (position+1)){ // Hasn't gone through yet for(int k=position+1; k<ruleset.size(); k++) finalRulesetStat.addAndUpdate((Rule)ruleset.elementAt(k)); } if(m_Debug) System.err.println("\nDeleting rules to decrease"+ " DL of the whole ruleset ..."); finalRulesetStat.reduceDL(expFPRate, m_CheckErr); if(m_Debug){ int del = ruleset.size() - finalRulesetStat.getRulesetSize(); System.err.println(del+" rules are deleted"+ " after DL reduction procedure"); } ruleset = finalRulesetStat.getRuleset(); rstats = finalRulesetStat; } // For each run of optimization // Concatenate the ruleset for this class to the whole ruleset if(m_Debug){ System.err.println("\nFinal ruleset: "); for(int x=0; x<ruleset.size(); x++) System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class)); System.err.println(); } m_Ruleset.appendElements(ruleset); m_RulesetStats.addElement(rstats); return null; } /** * Check whether the stopping criterion meets * * @param rst the statistic of the ruleset * @param minDL the min description length so far * @param dl the current description length of the ruleset * @return true if stop criterion meets, false otherwise */ private boolean checkStop(double[] rst, double minDL, double dl){ if(dl > minDL+MAX_DL_SURPLUS){ if(m_Debug) System.err.println("DL too large: "+dl+" | "+minDL); return true; } else if(!Utils.gr(rst[2], 0.0)){// Covered positives if(m_Debug) System.err.println("Too few positives."); return true; } else if((rst[4]/rst[0]) >= 0.5){// Err rate if(m_CheckErr){ if(m_Debug) System.err.println("Error too large: "+ rst[4] + "/" + rst[0]); return true; } else return false; } else{// Not stops if(m_Debug) System.err.println("Continue."); return false; } } /** * Prints the all the rules of the rule learner. * * @return a textual description of the classifier */ public String toString() { if (m_Ruleset == null) return "FURIA: No model built yet."; StringBuffer sb = new StringBuffer("FURIA rules:\n"+ "===========\n\n"); for(int j=0; j<m_RulesetStats.size(); j++){ //System.out.println("ruleset stats = " + m_RulesetStats.size()); RuleStats rs = (RuleStats)m_RulesetStats.elementAt(j); FastVector rules = rs.getRuleset(); for(int k=0; k<rules.size(); k++){ //System.out.println("rules size = " + rules.size()); sb.append(((RipperRule)rules.elementAt(k)).toString(m_Class) + " (CF = " + Math.round(100.0*((RipperRule)rules.elementAt(k)).getConfidence())/100.0 +")\n"); } } sb.append("\n\n\nReglas Buenas\n"); if(true){//m_Debug es lo que habia sb.append("Inside m_Ruleset\n"); for(int i=0; i<m_Ruleset.size(); i++) sb.append(((RipperRule)m_Ruleset.elementAt(i)).toString(m_Class) + " (CF = " + Math.round(100.0*((RipperRule)m_Ruleset.elementAt(i)).getConfidence())/100.0 + ")\n"); } sb.append("\nNumber of Rules : " + m_Ruleset.size() + "\n"); return sb.toString(); } /** * Main method. * * @param args the options for the classifier * @throws Exception */ //public static void main(String[] args) throws Exception { // runClassifier(new FURIA(), args); //} public String getRevision() { return "1.0"; } }