JRip.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 52k
Category:

Windows Develop

Development Platform:

Java

  1. /*
  2.  *    This program is free software; you can redistribute it and/or modify
  3.  *    it under the terms of the GNU General Public License as published by
  4.  *    the Free Software Foundation; either version 2 of the License, or
  5.  *    (at your option) any later version.
  6.  *
  7.  *    This program is distributed in the hope that it will be useful,
  8.  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  9.  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  10.  *    GNU General Public License for more details.
  11.  *
  12.  *    You should have received a copy of the GNU General Public License
  13.  *    along with this program; if not, write to the Free Software
  14.  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15.  */
  16. /*
  17.  *    JRip.java
  18.  *    Copyright (C) 2001 Xin Xu, Eibe Frank
  19.  */
  20. package weka.classifiers.rules;
  21. import java.util.Enumeration;
  22. import java.util.Random;
  23. import java.util.Vector;
  24. import weka.core.FastVector;
  25. import weka.core.Instances;
  26. import weka.core.Instance;
  27. import weka.core.Attribute;
  28. import weka.core.AttributeStats;
  29. import weka.core.Utils;
  30. import weka.core.OptionHandler;
  31. import weka.core.Option;
  32. import weka.core.Copyable;
  33. import weka.core.WeightedInstancesHandler;
  34. import weka.core.AdditionalMeasureProducer;
  35. import weka.core.UnsupportedAttributeTypeException;
  36. import weka.core.UnsupportedClassTypeException;
  37. import weka.filters.supervised.attribute.ClassOrder;
  38. import weka.filters.Filter;
  39. import weka.classifiers.DistributionClassifier;
  40. import weka.classifiers.Evaluation;
  41. /**
  42.  * This class implements a propositional rule learner, Repeated Incremental
  43.  * Pruning to Produce Error Reduction (RIPPER), which is proposed by William
  44.  * W. Cohen as an optimzed version of IREP. <p>
  45.  * 
  46.  * The algorithm is briefly described as follows: <p>
  47.  * Initialize RS = {}, and for each class from the less prevalent one to 
  48.  * the more frequent one, DO: <p>
  49.  *
  50.  * 1. Building stage: repeat 1.1 and 1.2 until the descrition length (DL)
  51.  *    of the ruleset and examples is 64 bits greater than the smallest DL
  52.  *    met so far, or there are no positive examples, or the error rate >= 50%. 
  53.  *    <p>
  54.  * 1.1. Grow phase:<br>
  55.  *      Grow one rule by greedily adding antecedents (or conditions) to
  56.  *      the rule until the rule is perfect (i.e. 100% accurate).  The
  57.  *      procedure tries every possible value of each attribute and selects
  58.  *      the condition with highest information gain: p(log(p/t)-log(P/T)).
  59.  *      <p>
  60.  * 1.2. Prune phase:<br>
  61.  *      Incrementally prune each rule and allow the pruning of any
  62.  *      final sequences of the antecedents;<br>
  63.  *      The pruning metric is (p-n)/(p+n) -- but it's actually 
  64.  *      2p/(p+n) -1, so in this implementation we simply use p/(p+n)
  65.  *      (actually (p+1)/(p+n+2), thus if p+n is 0, it's 0.5).<p>
  66.  *
  67.  * 2. Optimization stage: after generating the initial ruleset {Ri}, 
  68.  *    generate and prune two variants of each rule Ri from randomized data
  69.  *    using procedure 1.1 and 1.2.  But one variant is generated from an 
  70.  *    empty rule while the other is generated by greedily adding antecedents
  71.  *    to the original rule.  Moreover, the pruning metric used here is 
  72.  *    (TP+TN)/(P+N).<br>
  73.  *    Then the smallest possible DL for each variant and the original rule
  74.  *    is computed.  The variant with the minimal DL is selected as the final 
  75.  *    representative of Ri in the ruleset. <br>
  76.  *    After all the rules in {Ri} have been examined and if there are still
  77.  *    residual positives, more rules are generated based on the residual 
  78.  *    positives using Building Stage again. <p>
  79.  *
  80.  * 3. Delete the rules from the ruleset that would increase the DL of the
  81.  *    whole ruleset if it were in it. and add resultant ruleset to RS. <p>
  82.  * 
  83.  * ENDDO<p>
  84.  *
  85.  * Note that there seem to be 2 bugs in the ripper program that would
  86.  * affect the ruleset size and accuracy slightly.  This implementation avoids
  87.  * these bugs and thus is a little bit different from Cohen's original 
  88.  * implementation.  Even after fixing the bugs, since the order of classes with
  89.  * the same frequency is not defined in ripper, there still seems to be 
  90.  * some trivial difference between this implementation and the original ripper,
  91.  * especially for audiology data in UCI repository, where there are lots of
  92.  * classes of few instances.<p>
  93.  *
  94.  * If wrapped by other classes, typical usage of this class is:<br>
  95.  *
  96.  * <code>JRip rip = new JRip();
  97.  * Instances data = ... // Data from somewhere
  98.  * double[] orderedClasses = ... // Get the ordered class counts for the data 
  99.  * double expFPRate = ... // Calculate the expected FP/(FP+FN) rate
  100.  * double classIndex = ...  // The class index for which ruleset is built
  101.  * // DL of default rule, no theory DL, only data DL
  102.  * double defDL = RuleStats.dataDL(expFPRate, 0.0, data.sumOfWeights(),
  103.  *    0.0, orderedClasses[(int)classIndex]);
  104.  *     
  105.  * rip.rulesetForOneClass(expFPRate, data, classIndex, defDL); 
  106.  * RuleStats rulesetStats = rip.getRuleStats(0);
  107.  *
  108.  * // Can get heaps of information from RuleStats, e.g. combined DL, 
  109.  * // simpleStats, etc.
  110.  * double comDL = rulesetStats.combinedDL(expFPRate, classIndex);
  111.  * int whichRule = ... // Want simple stats of which rule?
  112.  * double[] simpleStats = rulesetStats.getSimpleStats(whichRule);
  113.  * ...
  114.  * </code>
  115.  *
  116.  * Details please see "Fast Effective Rule Induction", William W. Cohen, 
  117.  * 'Machine Learning: Proceedings of the Twelfth International Conference'
  118.  * (ML95). <p>
  119.  *
  120.  * PS.  We have compared this implementation with the original ripper 
  121.  * implementation in aspects of accuracy, ruleset size and running time 
  122.  * on both artificial data "ab+bcd+defg" and UCI datasets.  In all these
  123.  * aspects it seems to be quite comparable to the original ripper 
  124.  * implementation.  However, we didn't consider memory consumption
  125.  * optimization in this implementation.<p>
  126.  *
  127.  * @author Xin Xu (xx5@cs.waikato.ac.nz)
  128.  * @author Eibe Frank (eibe@cs.waikato.ac.nz)
  129.  * @version $Revision: 1.7 $
  130.  */
  131. public class JRip extends DistributionClassifier 
  132.   implements OptionHandler, 
  133.      AdditionalMeasureProducer, 
  134.      WeightedInstancesHandler{    
  135.   /** The limit of description length surplus in ruleset generation */
  136.   private static double MAX_DL_SURPLUS = 64.0;
  137.     
  138.   /** The class attribute of the data*/
  139.   private Attribute m_Class; 
  140.     
  141.   /** The ruleset */
  142.   private FastVector m_Ruleset;
  143.   
  144.     /** The predicted class distribution */
  145.     private FastVector m_Distributions;
  146.   
  147.   /** Runs of optimizations */
  148.   private int m_Optimizations = 2;
  149.     
  150.   /** Random object used in this class */
  151.   private Random m_Random = null;
  152.     
  153.   /** # of all the possible conditions in a rule */
  154.   private double m_Total = 0;
  155.   /** The seed to perform randomization */
  156.   private long m_Seed = 1;
  157.   /** The number of folds to split data into Grow and Prune for IREP */
  158.   private int m_Folds = 3;
  159.     
  160.   /** The minimal number of instance weights within a split*/
  161.   private double m_MinNo = 2.0;
  162.   /** Whether in a debug mode */
  163.   private boolean m_Debug = false;
  164.   /** Whether check the error rate >= 0.5 in stopping criteria */
  165.   private boolean m_CheckErr = true;
  166.   /** Whether use pruning, i.e. the data is clean or not */
  167.   private boolean m_UsePruning = true;
  168.   /** The filter used to randomize the class order */
  169.   private Filter m_Filter = null;
  170.   /** The RuleStats for the ruleset of each class value */
  171.   private FastVector m_RulesetStats;
  172.     
  173.   /**
  174.    * Returns an enumeration describing the available options
  175.    * Valid options are: <p>
  176.    *  
  177.    * -F number <br>
  178.    * The number of folds for reduced error pruning. One fold is
  179.    * used as the pruning set. (Default: 3) <p>
  180.    * 
  181.    * -N number <br>
  182.    * The minimal weights of instances within a split.
  183.    * (Default: 2) <p>
  184.    *    
  185.    * -O number <br>
  186.    * Set the number of runs of optimizations. (Default: 2)<p>
  187.    *
  188.    * -D <br>
  189.    * Whether turn on the debug mode
  190.    *
  191.    * -S number <br>
  192.    * The seed of randomization used in Ripper.(Default: 1)<p>
  193.    *
  194.    * -E <br>
  195.    * Whether NOT check the error rate >= 0.5 in stopping criteria.
  196.    * (default: check)<p> 
  197.    *
  198.    * -P <br>
  199.    * Whether NOT use pruning. (default: use pruning)<p>
  200.    *
  201.    * @return an enumeration of all the available options
  202.    */
  203.   public Enumeration listOptions() {
  204.     Vector newVector = new Vector(3);
  205.     newVector.addElement(new Option("tSet number of folds for REPn" +
  206.     "tOne fold is used as pruning set.n" +
  207.     "t(default 3)","F", 1, "-F <number of folds>"));
  208.     newVector.addElement(new Option("tSet the minimal weights of instancesn" +
  209.     "twithin a split.n" +
  210.     "t(default 2.0)","N", 1, "-N <min. weights>"));  
  211.     newVector.addElement(new Option("tSet the number of runs ofn"+
  212.     "toptimizations. (Default: 2)", "O",
  213.     1,"-O <number of runs>"));
  214.     newVector.addElement(new Option("tSet whether turn on then"+
  215.     "tdebug mode (Default: false)", "D",
  216.     0,"-D"));
  217.     newVector.addElement(new Option("tThe seed of randomizationn"+
  218.     "t(Default: 1)", "S",
  219.     1,"-S <seed>"));
  220.     newVector.addElement(new Option("Whether NOT check the error rate>=0.5n"
  221.     +"tin stopping criteria "
  222.     +"t(default: check)", "E", 
  223.     0, "-E")); 
  224.     newVector.addElement(new Option("Whether NOT use pruningn"
  225.     +"t(default: use pruning)", "P", 
  226.     0, "-P")); 
  227.     return newVector.elements();
  228.   }
  229.     
  230.   /**
  231.    * Parses a given list of options.
  232.    *
  233.    * @param options the list of options as an array of strings
  234.    * @exception Exception if an option is not supported
  235.    */
  236.   public void setOptions(String[] options) throws Exception{
  237.     String numFoldsString = Utils.getOption('F', options);
  238.     if (numFoldsString.length() != 0) 
  239.       m_Folds = Integer.parseInt(numFoldsString);
  240.     else 
  241.       m_Folds = 3;   
  242.     String minNoString = Utils.getOption('N', options);
  243.     if (minNoString.length() != 0) 
  244.       m_MinNo = Double.parseDouble(minNoString);
  245.     else 
  246.       m_MinNo = 2.0;
  247.     String seedString = Utils.getOption('S', options);
  248.     if (seedString.length() != 0)
  249.       m_Seed = Long.parseLong(seedString);
  250.     else 
  251.       m_Seed = 1;
  252.     String runString = Utils.getOption('O', options);
  253.     if (runString.length() != 0)
  254.       m_Optimizations = Integer.parseInt(runString);
  255.     else 
  256.       m_Optimizations = 2;
  257.     m_Debug = Utils.getFlag('D', options);
  258.     m_CheckErr = !Utils.getFlag('E', options);
  259.     m_UsePruning = !Utils.getFlag('P', options);
  260.   }
  261.     
  262.   /**
  263.    * Gets the current settings of the Classifier.
  264.    *
  265.    * @return an array of strings suitable for passing to setOptions
  266.    */
  267.   public String [] getOptions() {
  268.     String [] options = new String [11];
  269.     int current = 0;
  270.     options[current++] = "-F"; options[current++] = "" + m_Folds;
  271.     options[current++] = "-N"; options[current++] = "" + m_MinNo;
  272.     options[current++] = "-O"; options[current++] = "" + m_Optimizations;
  273.     options[current++] = "-S"; options[current++] = "" + m_Seed;
  274.     if(m_Debug)
  275.       options[current++] = "-D";
  276.     if(!m_CheckErr)
  277.       options[current++] = "-E";
  278.     if(!m_UsePruning)
  279.       options[current++] = "-P";
  280.     while(current < options.length)
  281.       options[current++] = "";
  282.     return options;
  283.   }
  284.   /**
  285.    * Returns an enumeration of the additional measure names
  286.    * @return an enumeration of the measure names
  287.    */
  288.   public Enumeration enumerateMeasures() {
  289.     Vector newVector = new Vector(1);
  290.     newVector.addElement("measureNumRules");
  291.     return newVector.elements();
  292.   }
  293.     
  294.   /**
  295.    * Returns the value of the named measure
  296.    * @param measureName the name of the measure to query for its value
  297.    * @return the value of the named measure
  298.    * @exception IllegalArgumentException if the named measure is not supported
  299.    */
  300.   public double getMeasure(String additionalMeasureName) {
  301.     if (additionalMeasureName.compareTo("measureNumRules") == 0) 
  302.       return m_Ruleset.size();
  303.     else 
  304.       throw new IllegalArgumentException(additionalMeasureName+" not supported (RIPPER)");
  305.   }  
  306.     
  307.   public void setFolds(int fold){ m_Folds = fold; }
  308.   public int getFolds(){ return m_Folds; }
  309.   public void setMinNo(double m){  m_MinNo = m; }
  310.   public double getMinNo(){ return m_MinNo; }
  311.   public void setSeed(long s){ m_Seed = s; }
  312.   public long getSeed(){ return m_Seed; }
  313.   public void setOptimizations(int run){ m_Optimizations = run; }
  314.   public int getOptimizations(){ return m_Optimizations; }
  315.   public void setDebug(boolean d){m_Debug = d;}
  316.   public boolean getDebug(){ return m_Debug; }
  317.   public void setCheckErrorRate(boolean d){ m_CheckErr = d;}
  318.   public boolean getCheckErrorRate(){ return m_CheckErr; }
  319.   public void setUsePruning(boolean d){ m_UsePruning = d;}
  320.   public boolean getUsePruning(){ return m_UsePruning; }
  321.     
  322.   /** 
  323.    * Get the ruleset generated by Ripper 
  324.    *
  325.    * @return the ruleset
  326.    */
  327.   public FastVector getRuleset(){ return m_Ruleset; }
  328.   /** 
  329.    * Get the statistics of the ruleset in the given position
  330.    *
  331.    * @param pos the position of the stats, assuming correct
  332.    */
  333.   public RuleStats getRuleStats(int pos) {
  334.     return (RuleStats)m_RulesetStats.elementAt(pos);
  335.   }
  336.     
  337.   /** 
  338.    * The single antecedent in the rule, which is composed of an attribute and 
  339.    * the corresponding value.  There are two inherited classes, namely NumericAntd
  340.    * and NominalAntd in which the attributes are numeric and nominal respectively.
  341.    */    
  342.   private abstract class Antd 
  343.     implements WeightedInstancesHandler, Copyable{
  344.     /* The attribute of the antecedent */
  345.     protected Attribute att;
  346.     /* The attribute value of the antecedent.  
  347.        For numeric attribute, value is either 0(1st bag) or 1(2nd bag) */
  348.     protected double value; 
  349.     /* The maximum infoGain achieved by this antecedent test 
  350.      * in the growing data */
  351.     protected double maxInfoGain;
  352.     /* The accurate rate of this antecedent test on the growing data */
  353.     protected double accuRate;
  354.     /* The coverage of this antecedent in the growing data */
  355.     protected double cover;
  356.     /* The accurate data for this antecedent in the growing data */
  357.     protected double accu;
  358.     /* Constructor*/
  359.     public Antd(Attribute a){
  360.       att=a;
  361.       value=Double.NaN; 
  362.       maxInfoGain = 0;
  363.       accuRate = Double.NaN;
  364.       cover = Double.NaN;
  365.       accu = Double.NaN;
  366.     }
  367.     /* The abstract members for inheritance */
  368.     public abstract Instances[] splitData(Instances data, double defAcRt, 
  369.   double cla);
  370.     public abstract boolean covers(Instance inst);
  371.     public abstract String toString();
  372.     /** Implements Copyable */
  373.     public abstract Object copy(); 
  374.    
  375.     /* Get functions of this antecedent */
  376.     public Attribute getAttr(){ return att; }
  377.     public double getAttrValue(){ return value; }
  378.     public double getMaxInfoGain(){ return maxInfoGain; }
  379.     public double getAccuRate(){ return accuRate; } 
  380.     public double getAccu(){ return accu; } 
  381.     public double getCover(){ return cover; } 
  382.   }
  383.     
  384.   /** 
  385.    * The antecedent with numeric attribute
  386.    */
  387.   private class NumericAntd extends Antd{
  388.     /* The split point for this numeric antecedent */
  389.     private double splitPoint;
  390.     
  391.     /* Constructor*/
  392.     public NumericAntd(Attribute a){ 
  393.       super(a);
  394.       splitPoint = Double.NaN;
  395.     }    
  396.     /* Get split point of this numeric antecedent */
  397.     public double getSplitPoint(){ return splitPoint; }
  398.     /** Implements Copyable */
  399.     public Object copy(){ 
  400.       NumericAntd na = new NumericAntd(getAttr());
  401.       na.value = this.value;
  402.       na.splitPoint = this.splitPoint;
  403.       return na;
  404.     }
  405.     /**
  406.      * Implements the splitData function.  
  407.      * This procedure is to split the data into two bags according 
  408.      * to the information gain of the numeric attribute value
  409.      * The maximum infoGain is also calculated.  
  410.      * 
  411.      * @param insts the data to be split
  412.      * @param defAcRt the default accuracy rate for data
  413.      * @param cl the class label to be predicted
  414.      * @return the array of data after split
  415.      */
  416.     public Instances[] splitData(Instances insts, double defAcRt, 
  417.  double cl){
  418. Instances data = insts;
  419.       int total=data.numInstances();// Total number of instances without 
  420.       // missing value for att
  421.     
  422.       int split=1;                  // Current split position
  423.       int prev=0;                   // Previous split position
  424.       int finalSplit=split;         // Final split position
  425.       maxInfoGain = 0;
  426.       value = 0;
  427.     
  428.       double fstCover=0, sndCover=0, fstAccu=0, sndAccu=0;
  429.     
  430.       data.sort(att);
  431.       // Find the las instance without missing value 
  432.       for(int x=0; x<data.numInstances(); x++){
  433. Instance inst = data.instance(x);
  434. if(inst.isMissing(att)){
  435.   total = x;
  436.   break;
  437. }
  438. sndCover += inst.weight();
  439. if(Utils.eq(inst.classValue(), cl))
  440.   sndAccu += inst.weight();
  441.       }     
  442.       if(total == 0) return null; // Data all missing for the attribute
  443.       splitPoint = data.instance(total-1).value(att);
  444.     
  445.       for(; split <= total; split++){
  446. if((split == total) ||
  447.    (data.instance(split).value(att) > // Can't split within
  448.     data.instance(prev).value(att))){ // same value     
  449.     
  450.   for(int y=prev; y<split; y++){
  451.     Instance inst = data.instance(y);
  452.     fstCover += inst.weight(); 
  453.     if(Utils.eq(data.instance(y).classValue(), cl)){
  454.       fstAccu += inst.weight();  // First bag positive# ++
  455.     }          
  456.   }
  457.     
  458.   double fstAccuRate = (fstAccu+1.0)/(fstCover+1.0),
  459.     sndAccuRate = (sndAccu+1.0)/(sndCover+1.0);
  460.     
  461.   /* Which bag has higher information gain? */
  462.   boolean isFirst; 
  463.   double fstInfoGain, sndInfoGain;
  464.   double accRate, infoGain, coverage, accurate;
  465.     
  466.   fstInfoGain = 
  467.     //Utils.eq(defAcRt, 1.0) ? 
  468.     //fstAccu/(double)numConds : 
  469.     fstAccu*(Utils.log2(fstAccuRate)-Utils.log2(defAcRt));
  470.     
  471.   sndInfoGain = 
  472.     //Utils.eq(defAcRt, 1.0) ? 
  473.     //sndAccu/(double)numConds : 
  474.     sndAccu*(Utils.log2(sndAccuRate)-Utils.log2(defAcRt));
  475.     
  476.   if(fstInfoGain > sndInfoGain){
  477.     isFirst = true;
  478.     infoGain = fstInfoGain;
  479.     accRate = fstAccuRate;
  480.     accurate = fstAccu;
  481.     coverage = fstCover;
  482.   }
  483.   else{
  484.     isFirst = false;
  485.     infoGain = sndInfoGain;
  486.     accRate = sndAccuRate;
  487.     accurate = sndAccu;
  488.     coverage = sndCover;
  489.   }
  490.     
  491.   /* Check whether so far the max infoGain */
  492.   if(infoGain > maxInfoGain){
  493.     splitPoint = data.instance(prev).value(att);
  494.     value = (isFirst) ? 0 : 1;
  495.     accuRate = accRate;
  496.     accu = accurate;
  497.     cover = coverage;
  498.     maxInfoGain = infoGain;
  499.     finalSplit = (isFirst) ? split : prev;
  500.   }
  501.     
  502.   for(int y=prev; y<split; y++){
  503.     Instance inst = data.instance(y);
  504.     sndCover -= inst.weight(); 
  505.     if(Utils.eq(data.instance(y).classValue(), cl)){
  506.       sndAccu -= inst.weight();  // Second bag positive# --
  507.     }          
  508.   }     
  509.   prev=split;
  510. }
  511.       }
  512.     
  513.       /* Split the data */
  514.       Instances[] splitData = new Instances[2];
  515.       splitData[0] = new Instances(data, 0, finalSplit);
  516.       splitData[1] = new Instances(data, finalSplit, total-finalSplit);
  517.     
  518.       return splitData;
  519.     }
  520.     /**
  521.      * Whether the instance is covered by this antecedent
  522.      * 
  523.      * @param inst the instance in question
  524.      * @return the boolean value indicating whether the instance is covered
  525.      *         by this antecedent
  526.      */
  527.     public boolean covers(Instance inst){
  528.       boolean isCover=true;
  529.       if(!inst.isMissing(att)){
  530. if((int)value == 0){ // First bag
  531.   if(inst.value(att) > splitPoint)
  532.     isCover=false;
  533. }
  534. else if(inst.value(att) < splitPoint) // Second bag
  535.   isCover=false;
  536.       }
  537.       else
  538. isCover = false;
  539.     
  540.       return isCover;
  541.     }
  542.     /**
  543.      * Prints this antecedent
  544.      *
  545.      * @return a textual description of this antecedent
  546.      */
  547.     public String toString() {
  548.       String symbol = ((int)value == 0) ? " <= " : " >= ";
  549.       return (att.name() + symbol + Utils.doubleToString(splitPoint, 6));
  550.     }   
  551.   }
  552.     
  553.     
  554.   /** 
  555.    * The antecedent with nominal attribute
  556.    */
  557.   private class NominalAntd extends Antd{
  558.     /* The parameters of infoGain calculated for each attribute value
  559.      * in the growing data */
  560.     private double[] accurate;
  561.     private double[] coverage;
  562.     /* Constructor*/
  563.     public NominalAntd(Attribute a){ 
  564.       super(a);    
  565.       int bag = att.numValues();
  566.       accurate = new double[bag];
  567.       coverage = new double[bag];
  568.     }   
  569.     /** Implements Copyable */
  570.     public Object copy(){
  571.       Antd antec = new NominalAntd(getAttr());
  572.       antec.value = this.value;
  573.       return antec;     
  574.     }
  575.     /**
  576.      * Implements the splitData function.  
  577.      * This procedure is to split the data into bags according 
  578.      * to the nominal attribute value
  579.      * The infoGain for each bag is also calculated.  
  580.      * 
  581.      * @param data the data to be split
  582.      * @param defAcRt the default accuracy rate for data
  583.      * @param cl the class label to be predicted
  584.      * @return the array of data after split
  585.      */
  586.     public Instances[] splitData(Instances data, double defAcRt, 
  587.  double cl){
  588.       int bag = att.numValues();
  589.       Instances[] splitData = new Instances[bag];
  590.     
  591.       for(int x=0; x<bag; x++){
  592. splitData[x] = new Instances(data, data.numInstances());
  593. accurate[x] = 0;
  594. coverage[x] = 0;
  595.       }
  596.     
  597.       for(int x=0; x<data.numInstances(); x++){
  598. Instance inst=data.instance(x);
  599. if(!inst.isMissing(att)){
  600.   int v = (int)inst.value(att);
  601.   splitData[v].add(inst);
  602.   coverage[v] += inst.weight();
  603.   if((int)inst.classValue() == (int)cl)
  604.     accurate[v] += inst.weight();
  605. }
  606.       }
  607.     
  608.       for(int x=0; x<bag; x++){
  609. double t = coverage[x]+1.0;
  610. double p = accurate[x] + 1.0;
  611. double infoGain = 
  612.   //Utils.eq(defAcRt, 1.0) ? 
  613.   //accurate[x]/(double)numConds : 
  614.   accurate[x]*(Utils.log2(p/t)-Utils.log2(defAcRt));
  615. if(infoGain > maxInfoGain){
  616.   maxInfoGain = infoGain;
  617.   cover = coverage[x];
  618.   accu = accurate[x];
  619.   accuRate = p/t;
  620.   value = (double)x;
  621. }
  622.       }
  623.     
  624.       return splitData;
  625.     }
  626.     /**
  627.      * Whether the instance is covered by this antecedent
  628.      * 
  629.      * @param inst the instance in question
  630.      * @return the boolean value indicating whether the instance is
  631.      *         covered by this antecedent
  632.      */
  633.     public boolean covers(Instance inst){
  634.       boolean isCover=false;
  635.       if(!inst.isMissing(att)){
  636. if((int)inst.value(att) == (int)value)
  637.   isCover=true;     
  638.       }
  639.       return isCover;
  640.     }
  641.     /**
  642.      * Prints this antecedent
  643.      *
  644.      * @return a textual description of this antecedent
  645.      */
  646.     public String toString() {
  647.       return (att.name() + " = " +att.value((int)value));
  648.     } 
  649.   }
  650.     
  651.   /**
  652.    * This class implements a single rule that predicts specified class.  
  653.    *
  654.    * A rule consists of antecedents "AND"ed together and the consequent 
  655.    * (class value) for the classification.  
  656.    * In this class, the Information Gain (p*[log(p/t) - log(P/T)]) is used to
  657.    * select an antecedent and Reduced Error Prunning (REP) with the metric
  658.    * of accuracy rate p/(p+n) or (TP+TN)/(P+N) is used to prune the rule. 
  659.    */
  660.     
  661.   protected class RipperRule extends Rule{
  662.     /** The internal representation of the class label to be predicted*/
  663.     private double m_Consequent = -1;
  664.     /** The vector of antecedents of this rule*/
  665.     protected FastVector m_Antds = null;
  666.     public void setConsequent(double cl){  m_Consequent = cl; }
  667.     public double getConsequent(){ return m_Consequent; }
  668.     /** Constructor */
  669.     public RipperRule(){    
  670.       m_Antds = new FastVector();
  671.     }
  672.     /**
  673.      * Get a shallow copy of this rule
  674.      *
  675.      * @return the copy
  676.      */
  677.     public Object copy(){
  678.       RipperRule copy = new RipperRule();
  679.       copy.setConsequent(getConsequent());
  680.       copy.m_Antds = (FastVector)this.m_Antds.copyElements();
  681.       return copy;
  682.     }
  683.     /**
  684.      * Whether the instance covered by this rule
  685.      * 
  686.      * @param inst the instance in question
  687.      * @return the boolean value indicating whether the instance 
  688.      *         is covered by this rule
  689.      */
  690.     public boolean covers(Instance datum){
  691.       boolean isCover=true;
  692.     
  693.       for(int i=0; i<m_Antds.size(); i++){
  694. Antd antd = (Antd)m_Antds.elementAt(i);
  695. if(!antd.covers(datum)){
  696.   isCover = false;
  697.   break;
  698. }
  699.       }
  700.     
  701.       return isCover;
  702.     }        
  703.     /**
  704.      * Whether this rule has antecedents, i.e. whether it is a default rule
  705.      * 
  706.      * @return the boolean value indicating whether the rule has antecedents
  707.      */
  708.     public boolean hasAntds(){
  709.       if (m_Antds == null)
  710. return false;
  711.       else
  712. return (m_Antds.size() > 0);
  713.     }      
  714.     /** 
  715.      * the number of antecedents of the rule
  716.      *
  717.      * @return the size of this rule
  718.      */
  719.     public double size(){ return (double)m_Antds.size(); }
  720.     /**
  721.      * Private function to compute default number of accurate instances
  722.      * in the specified data for the consequent of the rule
  723.      * 
  724.      * @param data the data in question
  725.      * @return the default accuracy number
  726.      */
  727.     private double computeDefAccu(Instances data){ 
  728.       double defAccu=0;
  729.       for(int i=0; i<data.numInstances(); i++){
  730. Instance inst = data.instance(i);
  731. if((int)inst.classValue() == (int)m_Consequent)
  732.   defAccu += inst.weight();
  733.       }
  734.       return defAccu;
  735.     }
  736.     /**
  737.      * Build one rule using the growing data
  738.      *
  739.      * @param data the growing data used to build the rule
  740.      * @exception if the consequent is not set yet
  741.      */    
  742.       public void grow(Instances data) throws Exception{
  743.       if(m_Consequent == -1)
  744. throw new Exception(" Consequent not set yet.");
  745.     
  746.       Instances growData = data;          
  747.       double sumOfWeights = growData.sumOfWeights();
  748.       if(!Utils.gr(sumOfWeights, 0.0))
  749. return;
  750.     
  751.       /* Compute the default accurate rate of the growing data */
  752.       double defAccu = computeDefAccu(growData);
  753.       double defAcRt = (defAccu+1.0)/(sumOfWeights+1.0); 
  754.     
  755.       /* Keep the record of which attributes have already been used*/    
  756.       boolean[] used=new boolean [growData.numAttributes()];
  757.       for (int k=0; k<used.length; k++)
  758. used[k]=false;
  759.       int numUnused=used.length;
  760.     
  761.       // If there are already antecedents existing
  762.       for(int j=0; j < m_Antds.size(); j++){
  763. Antd antdj = (Antd)m_Antds.elementAt(j);
  764. if(!antdj.getAttr().isNumeric()){ 
  765.   used[antdj.getAttr().index()]=true;
  766.   numUnused--;
  767.       }     
  768.     
  769.       double maxInfoGain;     
  770.       while (Utils.gr(growData.numInstances(), 0.0) && 
  771.      (numUnused > 0) 
  772.      && Utils.sm(defAcRt, 1.0)
  773.      ){   
  774. // We require that infoGain be positive
  775. /*if(numAntds == originalSize)
  776.   maxInfoGain = 0.0; // At least one condition allowed
  777.   else
  778.   maxInfoGain = Utils.eq(defAcRt, 1.0) ? 
  779.   defAccu/(double)numAntds : 0.0; */
  780. maxInfoGain = 0.0; 
  781. /* Build a list of antecedents */
  782. Antd oneAntd=null;
  783. Instances coverData = null;
  784. Enumeration enumAttr=growData.enumerateAttributes();     
  785. int index=-1;  
  786. /* Build one condition based on all attributes not used yet*/
  787. while (enumAttr.hasMoreElements()){
  788.   Attribute att= (Attribute)(enumAttr.nextElement());
  789.   index++;
  790.   if(m_Debug)
  791.     System.err.println("nOne condition: size = " 
  792.        + growData.sumOfWeights());
  793.    
  794.   Antd antd =null;
  795.   if(att.isNumeric())
  796.     antd = new NumericAntd(att);
  797.   else
  798.     antd = new NominalAntd(att);
  799.     
  800.   if(!used[index]){
  801.     /* Compute the best information gain for each attribute,
  802.        it's stored in the antecedent formed by this attribute.
  803.        This procedure returns the data covered by the antecedent*/
  804.     Instances coveredData = computeInfoGain(growData, defAcRt,
  805.     antd);
  806.     if(coveredData != null){
  807.       double infoGain = antd.getMaxInfoGain();      
  808.       if(m_Debug)
  809. System.err.println("Test of '"+antd.toString()+
  810.    "': infoGain = "+
  811.    infoGain + " | Accuracy = " +
  812.    antd.getAccuRate()+
  813.    "="+antd.getAccu()
  814.    +"/"+antd.getCover()+
  815.    " def. accuracy: "+defAcRt);
  816.     
  817.       if(infoGain > maxInfoGain){         
  818. oneAntd=antd;
  819. coverData = coveredData;  
  820. maxInfoGain = infoGain;
  821.       }     
  822.     }
  823.   }
  824. }
  825. if(oneAntd == null) break; // Cannot find antds
  826. if(Utils.sm(oneAntd.getAccu(), m_MinNo)) break;// Too low coverage
  827. //Numeric attributes can be used more than once
  828. if(!oneAntd.getAttr().isNumeric()){ 
  829.   used[oneAntd.getAttr().index()]=true;
  830.   numUnused--;
  831. }
  832. m_Antds.addElement(oneAntd);
  833. growData = coverData;// Grow data size is shrinking 
  834. defAcRt = oneAntd.getAccuRate();
  835.       }
  836.     }
  837.     /** 
  838.      * Compute the best information gain for the specified antecedent
  839.      *  
  840.      * @param instances the data based on which the infoGain is computed
  841.      * @param defAcRt the default accuracy rate of data
  842.      * @param antd the specific antecedent
  843.      * @param numConds the number of antecedents in the rule so far
  844.      * @return the data covered by the antecedent
  845.      */
  846.     private Instances computeInfoGain(Instances instances, double defAcRt, 
  847.       Antd antd){
  848. Instances data = instances;
  849.       /* Split the data into bags.
  850.  The information gain of each bag is also calculated in this procedure */
  851.       Instances[] splitData = antd.splitData(data, defAcRt, 
  852.      m_Consequent); 
  853.     
  854.       /* Get the bag of data to be used for next antecedents */
  855.       if(splitData != null)
  856. return splitData[(int)antd.getAttrValue()];
  857.       else return null;
  858.     }
  859.     /**
  860.      * Prune all the possible final sequences of the rule using the 
  861.      * pruning data.  The measure used to prune the rule is based on
  862.      * flag given.
  863.      *
  864.      * @param pruneData the pruning data used to prune the rule
  865.      * @param useWhole flag to indicate whether use the error rate of
  866.      *                 the whole pruning data instead of the data covered
  867.      */    
  868.     public void prune(Instances pruneData, boolean useWhole){
  869. Instances data = pruneData;
  870.       double total = data.sumOfWeights();
  871.       if(!Utils.gr(total, 0.0))
  872. return;
  873.       /* The default accurate # and rate on pruning data */
  874.       double defAccu=computeDefAccu(data);
  875.     
  876.       if(m_Debug)
  877. System.err.println("Pruning with " + defAccu + 
  878.    " positive data out of " + total +
  879.    " instances");
  880.     
  881.       int size=m_Antds.size();
  882.       if(size == 0) return; // Default rule before pruning
  883.     
  884.       double[] worthRt = new double[size];
  885.       double[] coverage = new double[size];
  886.       double[] worthValue = new double[size];
  887.       for(int w=0; w<size; w++){
  888. worthRt[w]=coverage[w]=worthValue[w]=0.0;
  889.       }
  890.     
  891.       /* Calculate accuracy parameters for all the antecedents in this rule */
  892.       double tn = 0.0; // True negative if useWhole
  893.       for(int x=0; x<size; x++){
  894. Antd antd=(Antd)m_Antds.elementAt(x);
  895. Attribute attr= antd.getAttr();
  896. Instances newData = data;
  897. data = new Instances(newData, 0); // Make data empty
  898. for(int y=0; y<newData.numInstances(); y++){
  899.   Instance ins=newData.instance(y);
  900.     
  901.   if(antd.covers(ins)){   // Covered by this antecedent
  902.     coverage[x] += ins.weight();
  903.     data.add(ins);                 // Add to data for further pruning
  904.     if((int)ins.classValue() == (int)m_Consequent) // Accurate prediction
  905.       worthValue[x] += ins.weight();
  906.   }
  907.   else if(useWhole){ // Not covered
  908.     if((int)ins.classValue() != (int)m_Consequent)
  909.       tn += ins.weight();
  910.   }
  911. }
  912. if(useWhole){
  913.   worthValue[x] += tn;
  914.   worthRt[x] = worthValue[x] / total;
  915. }
  916. else // Note if coverage is 0, accuracy is 0.5
  917.   worthRt[x] = (worthValue[x]+1.0)/(coverage[x]+2.0);
  918.       }
  919.     
  920.       double maxValue = (defAccu+1.0)/(total+2.0);
  921.       int maxIndex = -1;
  922.       for(int i=0; i<worthValue.length; i++){
  923. if(m_Debug){
  924.   double denom = useWhole ? total : coverage[i];
  925.   System.err.println(i+"(useAccuray? "+!useWhole+"): "
  926.      + worthRt[i] + 
  927.      "="+worthValue[i]+
  928.      "/"+denom);
  929. }
  930. if(worthRt[i] > maxValue){ // Prefer to the 
  931.   maxValue = worthRt[i]; // shorter rule
  932.   maxIndex = i;
  933. }
  934.       }
  935.     
  936.       /* Prune the antecedents according to the accuracy parameters */
  937.       for(int z=size-1;z>maxIndex;z--)
  938. m_Antds.removeElementAt(z);       
  939.     }
  940.     /**
  941.      * Prints this rule
  942.      *
  943.      * @param classAttr the class attribute in the data
  944.      * @return a textual description of this rule
  945.      */
  946.     public String toString(Attribute classAttr) {
  947.       StringBuffer text =  new StringBuffer();
  948.       if(m_Antds.size() > 0){
  949. for(int j=0; j< (m_Antds.size()-1); j++)
  950.   text.append("(" + ((Antd)(m_Antds.elementAt(j))).toString()+ ") and ");
  951. text.append("("+((Antd)(m_Antds.lastElement())).toString() + ")");
  952.       }
  953.       text.append(" => " + classAttr.name() +
  954.   "=" + classAttr.value((int)m_Consequent));
  955.     
  956.       return text.toString();
  957.     }
  958.   }
  959.     
  960.   /**
  961.    * Builds Ripper in the order of class frequencies.  For each class
  962.    * it's built in two stages: building and optimization 
  963.    *
  964.    * @param instances the training data
  965.    * @exception Exception if classifier can't be built successfully
  966.    */
  967.   public void buildClassifier(Instances instances) throws Exception{
  968.      
  969.       if(instances.numInstances() == 0)
  970.   throw new Exception(" No instances with a class value!");
  971.       
  972.       if (instances.checkForStringAttributes()) 
  973.   throw new UnsupportedAttributeTypeException(" Cannot handle string attributes!");
  974.       
  975.       if (!instances.classAttribute().isNominal()) 
  976.   throw new UnsupportedClassTypeException(" Only nominal class, please.");
  977.       
  978.       m_Random = new Random(m_Seed); 
  979.       m_Total = RuleStats.numAllConditions(instances);
  980.       if(m_Debug)
  981.   System.err.println("Number of all possible conditions = "+m_Total);
  982.       
  983.       Instances data = null;
  984.       m_Filter = new ClassOrder();
  985.       
  986.       // Sth. to make the class order different each time in cross-validations
  987.     Instance inst = 
  988.       instances.instance((int)(m_Random.nextDouble()*(double)instances.numInstances()));
  989.     ((ClassOrder)m_Filter).setSeed((long)inst.toString().hashCode());
  990.     ((ClassOrder)m_Filter).setClassOrder(ClassOrder.FREQ_ASCEND);
  991.     m_Filter.setInputFormat(instances);
  992.     data = Filter.useFilter(instances, m_Filter);
  993.     if(data == null)
  994.       throw new Exception(" Unable to randomize the class orders.");
  995.     data.deleteWithMissingClass();
  996.     if(data.numInstances() == 0)
  997.       throw new Exception(" No instances with a class value!");
  998.     
  999.     if(data.numInstances() < m_Folds)
  1000. throw new Exception(" Not enough data for REP.");
  1001.     
  1002.     m_Class = data.classAttribute();
  1003.     m_Ruleset = new FastVector();
  1004.     m_RulesetStats = new FastVector();
  1005.     m_Distributions = new FastVector();
  1006.     // Sort by classes frequency
  1007.     double[] orderedClasses = ((ClassOrder)m_Filter).getClassCounts();
  1008.     if(m_Debug){
  1009.       System.err.println("Sorted classes:");
  1010.       for(int x=0; x < m_Class.numValues(); x++)
  1011. System.err.println(x+": "+m_Class.value(x) + " has " +
  1012.    orderedClasses[x] + " instances.");
  1013.     }
  1014.     // Iterate from less prevalent class to more frequent one
  1015.   oneClass:
  1016.     for(int y=0; y < data.numClasses()-1; y++){ // For each class       
  1017.     
  1018.       double classIndex = (double)y;
  1019.       if(m_Debug){
  1020. int ci = (int)classIndex;
  1021. System.err.println("nnClass "+m_Class.value(ci)+"("+ci+"): "
  1022.    + orderedClasses[y] + "instancesn"+
  1023.    "=====================================n");
  1024.       }
  1025.       if(Utils.eq(orderedClasses[y],0.0)) // No data for this class
  1026. continue oneClass;
  1027.     
  1028.       // The expected FP/err is the proportion of the class
  1029.       double all = 0;
  1030.       for(int i=y; i<orderedClasses.length; i++)
  1031. all += orderedClasses[i];
  1032.       double expFPRate = orderedClasses[y] / all;     
  1033.       double classYWeights = 0, totalWeights = 0;
  1034.       for(int j=0; j < data.numInstances(); j++){
  1035.   Instance datum = data.instance(j);
  1036.   totalWeights += datum.weight();
  1037.   if((int)datum.classValue() == y){
  1038.       classYWeights += datum.weight();
  1039.   }           
  1040.       }
  1041.           
  1042.       // DL of default rule, no theory DL, only data DL
  1043.       double defDL;
  1044.       if(classYWeights > 0)
  1045.   defDL = RuleStats.dataDL(expFPRate, 
  1046.    0.0,
  1047.    totalWeights,
  1048.    0.0,
  1049.    classYWeights);     
  1050.       else
  1051.   continue oneClass; // Subsumed by previous rules
  1052.       if(Double.isNaN(defDL) || Double.isInfinite(defDL))
  1053. throw new Exception("Should never happen: "+
  1054.     "defDL NaN or infinite!");
  1055.       if(m_Debug)
  1056. System.err.println("The default DL = "+defDL);
  1057.     
  1058.       data = rulesetForOneClass(expFPRate, data, classIndex, defDL);
  1059.     }
  1060.     // Set the default rule
  1061.     RipperRule defRule = new RipperRule();
  1062.     defRule.setConsequent((double)(data.numClasses()-1));
  1063.     m_Ruleset.addElement(defRule);
  1064.     RuleStats defRuleStat = new RuleStats();
  1065.     defRuleStat.setData(data);
  1066.     defRuleStat.setNumAllConds(m_Total);
  1067.     defRuleStat.addAndUpdate(defRule);
  1068.     m_RulesetStats.addElement(defRuleStat);
  1069.     for(int z=0; z < m_RulesetStats.size(); z++){
  1070. RuleStats oneClass = (RuleStats)m_RulesetStats.elementAt(z);
  1071. for(int xyz=0; xyz < oneClass.getRulesetSize(); xyz++){
  1072.     double[] classDist = oneClass.getDistributions(xyz);
  1073.     Utils.normalize(classDist);
  1074.     if(classDist != null)
  1075. m_Distributions.addElement(((ClassOrder)m_Filter).distributionsByOriginalIndex(classDist));
  1076. }
  1077.     }    
  1078.   }
  1079.     
  1080.   /**
  1081.    * Classify the test instance with the rule learner and provide
  1082.    * the class distributions 
  1083.    *
  1084.    * @param datum the instance to be classified
  1085.    * @return the distribution
  1086.    */
  1087.     public double[] distributionForInstance(Instance datum){
  1088. try{
  1089.     for(int i=0; i < m_Ruleset.size(); i++){
  1090. RipperRule rule = (RipperRule)m_Ruleset.elementAt(i);
  1091. if(rule.covers(datum))
  1092.     return (double[])m_Distributions.elementAt(i); 
  1093.     }
  1094. }catch(Exception e){
  1095.     System.err.println(e.getMessage());
  1096.     e.printStackTrace();
  1097. }
  1098. System.err.println("Should never happen!");
  1099. return new double[datum.classAttribute().numValues()];
  1100.     }
  1101.   /** Build a ruleset for the given class according to the given data
  1102.    *
  1103.    * @param expFPRate the expected FP/(FP+FN) used in DL calculation
  1104.    * @param data the given data
  1105.    * @param classIndex the given class index
  1106.    * @param defDL the default DL in the data
  1107.    * @exception if the ruleset can be built properly
  1108.    */
  1109.   protected Instances rulesetForOneClass(double expFPRate, 
  1110.  Instances data, 
  1111.  double classIndex,
  1112.  double defDL)
  1113.     throws Exception{
  1114.     Instances newData = data, growData, pruneData;  
  1115.     boolean stop = false;
  1116.     FastVector ruleset = new FastVector();
  1117.     double dl = defDL, minDL = defDL;
  1118.     RuleStats rstats = null;
  1119.     double[] rst;
  1120.     // Check whether data have positive examples
  1121.     boolean defHasPositive = true; // No longer used
  1122.     boolean hasPositive = defHasPositive;
  1123.     /********************** Building stage ***********************/
  1124.     if(m_Debug)
  1125.       System.err.println("n*** Building stage ***");
  1126.     
  1127.     while((!stop) && hasPositive){ // Generate new rules until
  1128.       // stopping criteria met
  1129.       RipperRule oneRule;
  1130.       if(m_UsePruning){
  1131. /* Split data into Grow and Prune*/
  1132. // We should have stratified the data, but ripper seems
  1133. // to have a bug that makes it not to do so.  In order
  1134. // to simulate it more precisely, we do the same thing.
  1135. //newData.randomize(m_Random);
  1136. newData = RuleStats.stratify(newData, m_Folds, m_Random);
  1137. Instances[] part = RuleStats.partition(newData, m_Folds);
  1138. growData=part[0];
  1139. pruneData=part[1];
  1140. //growData=newData.trainCV(m_Folds, m_Folds-1);
  1141. //pruneData=newData.testCV(m_Folds, m_Folds-1);
  1142. oneRule = new RipperRule();
  1143. oneRule.setConsequent(classIndex);  // Must set first
  1144. if(m_Debug)
  1145.   System.err.println("nGrowing a rule ...");  
  1146. oneRule.grow(growData);             // Build the rule
  1147. if(m_Debug)
  1148.   System.err.println("One rule found before pruning:"+
  1149.      oneRule.toString(m_Class));
  1150. if(m_Debug)
  1151.   System.err.println("nPruning the rule ...");  
  1152. oneRule.prune(pruneData, false);    // Prune the rule
  1153. if(m_Debug)
  1154.   System.err.println("One rule found after pruning:"+
  1155.      oneRule.toString(m_Class));
  1156.       }
  1157.       else{
  1158. oneRule = new RipperRule();
  1159. oneRule.setConsequent(classIndex);  // Must set first
  1160. if(m_Debug)
  1161.   System.err.println("nNo pruning: growing a rule ...");
  1162. oneRule.grow(newData);             // Build the rule
  1163. if(m_Debug)
  1164.   System.err.println("No pruning: one rule found:n"+
  1165.      oneRule.toString(m_Class));
  1166.       }
  1167.     
  1168.       // Compute the DL of this ruleset
  1169.       if(rstats == null){ // First rule
  1170. rstats = new RuleStats();
  1171. rstats.setNumAllConds(m_Total);
  1172. rstats.setData(newData);
  1173.       }
  1174.     
  1175.       rstats.addAndUpdate(oneRule);     
  1176.       int last = rstats.getRuleset().size()-1; // Index of last rule
  1177.       dl += rstats.relativeDL(last, expFPRate, m_CheckErr);
  1178.     
  1179.       if(Double.isNaN(dl) || Double.isInfinite(dl))
  1180. throw new Exception("Should never happen: dl in "+
  1181.     "building stage NaN or infinite!");
  1182.       if(m_Debug)
  1183. System.err.println("Before optimization("+last+
  1184.    "): the dl = "+dl+" | best: "+minDL);
  1185.     
  1186.       if(dl < minDL)
  1187. minDL = dl;  // The best dl so far
  1188.     
  1189.       rst = rstats.getSimpleStats(last);     
  1190.       if(m_Debug)
  1191. System.err.println("The rule covers: "+rst[0]+
  1192.    " | pos = " + rst[2] + 
  1193.    " | neg = " + rst[4]+
  1194.    "nThe rule doesn't cover: "+rst[1]+
  1195.    " | pos = " + rst[5]);
  1196.     
  1197.       stop = checkStop(rst, minDL, dl);
  1198.     
  1199.       if(!stop){   
  1200. ruleset.addElement(oneRule);          // Accepted 
  1201. newData = rstats.getFiltered(last)[1];// Data not covered
  1202. hasPositive = Utils.gr(rst[5], 0.0);  // Positives remaining?
  1203. if(m_Debug)
  1204.   System.err.println("One rule added: has positive? "
  1205.      +hasPositive);
  1206.       }
  1207.       else{
  1208. if(m_Debug)
  1209.   System.err.println("Quit rule");
  1210. rstats.removeLast(); // Remove last to be re-used
  1211.       }
  1212.     }// while !stop
  1213.     /******************** Optimization stage *******************/
  1214.     RuleStats finalRulesetStat = null;
  1215.     if(m_UsePruning){  
  1216.       for(int z=0; z < m_Optimizations; z++){
  1217. if(m_Debug)
  1218.   System.err.println("n*** Optimization: run #"
  1219.      +z+" ***");
  1220. newData = data;     
  1221. finalRulesetStat = new RuleStats();
  1222. finalRulesetStat.setData(newData);
  1223. finalRulesetStat.setNumAllConds(m_Total);
  1224. int position=0;
  1225. stop = false;
  1226. boolean isResidual = false;     
  1227. hasPositive = defHasPositive;     
  1228. dl = minDL = defDL;
  1229.       oneRule:    
  1230. while(!stop && hasPositive){
  1231.     
  1232.   isResidual = (position>=ruleset.size()); // Cover residual positive examples  
  1233.   // Re-do shuffling and stratification    
  1234.   //newData.randomize(m_Random);
  1235.   newData = RuleStats.stratify(newData, m_Folds, m_Random);
  1236.   Instances[] part = RuleStats.partition(newData, m_Folds);
  1237.   growData=part[0];
  1238.   pruneData=part[1];
  1239.   //growData=newData.trainCV(m_Folds, m_Folds-1);
  1240.   //pruneData=newData.testCV(m_Folds, m_Folds-1);    
  1241.   RipperRule finalRule;
  1242.     
  1243.   if(m_Debug)
  1244.     System.err.println("nRule #"+position +
  1245.        "| isResidual?" + isResidual+
  1246.        "| data size: "+newData.sumOfWeights());
  1247.     
  1248.   if(isResidual){
  1249.     RipperRule newRule = new RipperRule();   
  1250.     newRule.setConsequent(classIndex);
  1251.     if(m_Debug)
  1252.       System.err.println("nGrowing and pruning"+
  1253.  " a new rule ..."); 
  1254.     newRule.grow(growData);
  1255.     newRule.prune(pruneData, false);
  1256.     finalRule = newRule;
  1257.     if(m_Debug)
  1258.       System.err.println("nNew rule found: "+
  1259.  newRule.toString(m_Class));
  1260.   }
  1261.   else{
  1262.     RipperRule oldRule = (RipperRule)ruleset.elementAt(position);
  1263.     boolean covers = false;
  1264.     // Test coverage of the next old rule
  1265.     for(int i=0; i<newData.numInstances(); i++)
  1266.       if(oldRule.covers(newData.instance(i))){
  1267. covers = true;
  1268. break;
  1269.       }
  1270.     if(!covers){// Null coverage, no variants can be generated
  1271.       finalRulesetStat.addAndUpdate(oldRule);
  1272.       position++;
  1273.       continue oneRule;
  1274.     }  
  1275.     // 2 variants 
  1276.     if(m_Debug)
  1277.       System.err.println("nGrowing and pruning"+
  1278.  " Replace ..."); 
  1279.     RipperRule replace = new RipperRule();   
  1280.     replace.setConsequent(classIndex);
  1281.     replace.grow(growData);
  1282.     // Remove the pruning data covered by the following
  1283.     // rules, then simply compute the error rate of the
  1284.     // current rule to prune it.  According to Ripper,
  1285.     // it's equivalent to computing the error of the 
  1286.     // whole ruleset -- is it true?
  1287.     pruneData = RuleStats.rmCoveredBySuccessives(pruneData,ruleset, position);      
  1288.     replace.prune(pruneData, true);
  1289.     if(m_Debug)
  1290.       System.err.println("nGrowing and pruning"+
  1291.  " Revision ..."); 
  1292.     RipperRule revision = (RipperRule)oldRule.copy(); 
  1293.     // For revision, first rm the data covered by the old rule
  1294.     Instances newGrowData = new Instances(growData, 0);
  1295.     for(int b=0; b<growData.numInstances(); b++){
  1296.       Instance inst = growData.instance(b);
  1297.       if(revision.covers(inst))
  1298. newGrowData.add(inst);
  1299.     }
  1300.     revision.grow(newGrowData);       
  1301.     revision.prune(pruneData, true);
  1302.     double[][] prevRuleStats = new double[position][6];
  1303.     for(int c=0; c < position; c++)
  1304. prevRuleStats[c] = finalRulesetStat.getSimpleStats(c);
  1305.     // Now compare the relative DL of variants
  1306.     FastVector tempRules = (FastVector)ruleset.copyElements();
  1307.     tempRules.setElementAt(replace, position);
  1308.     RuleStats repStat = new RuleStats(data, tempRules);
  1309.     repStat.setNumAllConds(m_Total);
  1310.     repStat.countData(position, newData, prevRuleStats);
  1311.     //repStat.countData();
  1312.     rst = repStat.getSimpleStats(position);     
  1313.     if(m_Debug)
  1314.       System.err.println("Replace rule covers: "+rst[0]+
  1315.  " | pos = " + rst[2] + 
  1316.  " | neg = " + rst[4]+
  1317.  "nThe rule doesn't cover: "+rst[1]+
  1318.  " | pos = " + rst[5]);
  1319.     double repDL = repStat.relativeDL(position, expFPRate,
  1320.       m_CheckErr);
  1321.     if(m_Debug)
  1322.       System.err.println("nReplace: "+
  1323.  replace.toString(m_Class)
  1324.  +" |dl = "+repDL); 
  1325.     if(Double.isNaN(repDL) || Double.isInfinite(repDL))
  1326.       throw new Exception("Should never happen: repDL"+
  1327.   "in optmz. stage NaN or "+
  1328.   "infinite!");
  1329.     tempRules.setElementAt(revision, position);
  1330.     RuleStats revStat = new RuleStats(data, tempRules);
  1331.     revStat.setNumAllConds(m_Total);
  1332.     revStat.countData(position, newData, prevRuleStats);
  1333.     //revStat.countData();
  1334.     double revDL = revStat.relativeDL(position, expFPRate,
  1335.       m_CheckErr);
  1336.     if(m_Debug)
  1337.       System.err.println("Revision: "
  1338.  + revision.toString(m_Class)
  1339.  +" |dl = "+revDL);
  1340.     if(Double.isNaN(revDL) || Double.isInfinite(revDL))
  1341.       throw new Exception("Should never happen: revDL"+
  1342.   "in optmz. stage NaN or "+
  1343.   "infinite!");
  1344.     rstats = new RuleStats(data, ruleset);
  1345.     rstats.setNumAllConds(m_Total);
  1346.     rstats.countData(position, newData, prevRuleStats);
  1347.     //rstats.countData();
  1348.     double oldDL = rstats.relativeDL(position, expFPRate,
  1349.      m_CheckErr);
  1350.     if(Double.isNaN(oldDL) || Double.isInfinite(oldDL))
  1351.       throw new Exception("Should never happen: oldDL"+
  1352.   "in optmz. stage NaN or "+
  1353.   "infinite!");
  1354.     if(m_Debug)
  1355.       System.err.println("Old rule: "+
  1356.  oldRule.toString(m_Class)
  1357.  +" |dl = "+oldDL); 
  1358.     if(m_Debug)
  1359.       System.err.println("nrepDL: "+repDL+ 
  1360.  "nrevDL: "+revDL+
  1361.  "noldDL: "+oldDL);
  1362.     if((oldDL <= revDL) && (oldDL <= repDL))
  1363.       finalRule = oldRule; // Old the best
  1364.     else if(revDL <= repDL)
  1365.       finalRule = revision; // Revision the best
  1366.     else
  1367.       finalRule = replace; // Replace the best  
  1368.   }
  1369.     
  1370.   finalRulesetStat.addAndUpdate(finalRule);    
  1371.   rst = finalRulesetStat.getSimpleStats(position);
  1372.     
  1373.   if(isResidual){
  1374.     dl += finalRulesetStat.relativeDL(position, 
  1375.       expFPRate,
  1376.       m_CheckErr);
  1377.     if(m_Debug)
  1378.       System.err.println("After optimization: the dl"
  1379.  +"="+dl+" | best: "+minDL);
  1380.     if(dl < minDL)
  1381.       minDL = dl;  // The best dl so far
  1382.     stop = checkStop(rst, minDL, dl);
  1383.     if(!stop)
  1384.       ruleset.addElement(finalRule); // Accepted 
  1385.     else{
  1386.       finalRulesetStat.removeLast(); // Remove last to be re-used
  1387.       position--;
  1388.     }
  1389.   }
  1390.   else
  1391.     ruleset.setElementAt(finalRule, position); // Accepted 
  1392.   if(m_Debug){
  1393.     System.err.println("The rule covers: "+rst[0]+
  1394.        " | pos = " + rst[2] + 
  1395.        " | neg = " + rst[4]+
  1396.        "nThe rule doesn't cover: "+rst[1]+
  1397.        " | pos = " + rst[5]);
  1398.     System.err.println("nRuleset so far: ");
  1399.     for(int x=0; x<ruleset.size(); x++)
  1400.       System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
  1401.     System.err.println();
  1402.   }
  1403.     
  1404.   //Data not covered
  1405.   if(finalRulesetStat.getRulesetSize() > 0)// If any rules
  1406.     newData = finalRulesetStat.getFiltered(position)[1]; 
  1407.   hasPositive = Utils.gr(rst[5], 0.0); //Positives remaining? 
  1408.   position++;
  1409. } // while !stop && hasPositive
  1410. if(ruleset.size() > (position+1)){ // Hasn't gone through yet
  1411.   for(int k=position+1; k<ruleset.size(); k++)
  1412.     finalRulesetStat.addAndUpdate((Rule)ruleset.elementAt(k));
  1413. }
  1414. if(m_Debug)
  1415.   System.err.println("nDeleting rules to decrease"+
  1416.      " DL of the whole ruleset ..."); 
  1417. finalRulesetStat.reduceDL(expFPRate, m_CheckErr);
  1418. if(m_Debug){
  1419.   int del = ruleset.size() -
  1420.     finalRulesetStat.getRulesetSize(); 
  1421.   System.err.println(del+" rules are deleted"+
  1422.      " after DL reduction procedure");
  1423. }
  1424. ruleset = finalRulesetStat.getRuleset();
  1425. rstats = finalRulesetStat;            
  1426.       } // For each run of optimization
  1427.     } // if pruning is used
  1428.     // Concatenate the ruleset for this class to the whole ruleset
  1429.     if(m_Debug){
  1430.       System.err.println("nFinal ruleset: ");
  1431.       for(int x=0; x<ruleset.size(); x++)
  1432. System.err.println(x+": "+((RipperRule)ruleset.elementAt(x)).toString(m_Class));
  1433.       System.err.println();
  1434.     }
  1435.     m_Ruleset.appendElements(ruleset);
  1436.     m_RulesetStats.addElement(rstats);
  1437.     if(ruleset.size() > 0)// If any rules for this class
  1438.       return rstats.getFiltered(ruleset.size()-1)[1]; // Data not 
  1439.     else                                                // covered
  1440.       return data; 
  1441.   }   
  1442.     
  1443.   /**
  1444.    * Check whether the stopping criterion meets
  1445.    *
  1446.    * @param rst the statistic of the ruleset
  1447.    * @param minDL the min description length so far
  1448.    * @param dl the current description length of the ruleset
  1449.    * @return true if stop criterion meets, false otherwise
  1450.    */
  1451.   private boolean checkStop(double[] rst, double minDL, double dl){
  1452.     if(dl > minDL+MAX_DL_SURPLUS){
  1453.       if(m_Debug)
  1454. System.err.println("DL too large: "+dl+" | "+minDL);
  1455.       return true;
  1456.     }
  1457.     else if(!Utils.gr(rst[2], 0.0)){// Covered positives
  1458.       if(m_Debug)
  1459. System.err.println("Too few positives.");
  1460.       return true;
  1461.     }
  1462.     else if((rst[4]/rst[0]) >= 0.5){// Err rate
  1463.       if(m_CheckErr){
  1464. if(m_Debug)
  1465.   System.err.println("Error too large: "+
  1466.      rst[4] + "/" + rst[0]);
  1467. return  true;
  1468.       }
  1469.       else
  1470. return false;
  1471.     }
  1472.     else{// Not stops
  1473.       if(m_Debug)
  1474. System.err.println("Continue.");
  1475.       return  false;
  1476.     }
  1477.   }
  1478.  
  1479.   /**
  1480.    * Prints the all the rules of the rule learner.
  1481.    *
  1482.    * @return a textual description of the classifier
  1483.    */
  1484.   public String toString() {
  1485.     if (m_Ruleset == null) 
  1486.       return "JRIP: No model built yet.";
  1487.     StringBuffer sb = new StringBuffer("JRIP rules:n"+
  1488.        "===========nn"); 
  1489.     for(int j=0; j<m_RulesetStats.size(); j++){
  1490.       RuleStats rs = (RuleStats)m_RulesetStats.elementAt(j);
  1491.       FastVector rules = rs.getRuleset();
  1492.       for(int k=0; k<rules.size(); k++){
  1493. double[] simStats = rs.getSimpleStats(k);
  1494. sb.append(((RipperRule)rules.elementAt(k)).toString(m_Class)
  1495.   + " ("+simStats[0]+"/"+simStats[4]+")n");
  1496.       }     
  1497.     }
  1498.     if(m_Debug){
  1499.       System.err.println("Inside m_Ruleset");
  1500.       for(int i=0; i<m_Ruleset.size(); i++)
  1501. System.err.println(((RipperRule)m_Ruleset.elementAt(i)).toString(m_Class));
  1502.     }
  1503.     sb.append("nNumber of Rules : " 
  1504.       + m_Ruleset.size() + "n");
  1505.     return sb.toString();
  1506.   }
  1507.     
  1508.   /**
  1509.    * Main method.
  1510.    *
  1511.    * @param args the options for the classifier
  1512.    */
  1513.   public static void main(String[] args) {
  1514.     try {
  1515.       System.out.println(Evaluation.evaluateModel(new JRip(), args));
  1516.     } catch (Exception e) {
  1517.       e.printStackTrace();
  1518.       System.err.println(e.getMessage());
  1519.     }
  1520.   } 
  1521. }