source: src/main/java/weka/classifiers/rules/NNge.java @ 7

Last change on this file since 7 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 48.9 KB
Line 
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/*
18 *    NNge.java
19 *    Copyright (C) 2002 Brent Martin
20 *
21 */
22
23package weka.classifiers.rules;
24
25import weka.classifiers.Classifier;
26import weka.classifiers.AbstractClassifier;
27import weka.classifiers.UpdateableClassifier;
28import weka.core.Capabilities;
29import weka.core.Instance;
30import weka.core.Instances;
31import weka.core.Option;
32import weka.core.OptionHandler;
33import weka.core.RevisionUtils;
34import weka.core.TechnicalInformation;
35import weka.core.TechnicalInformationHandler;
36import weka.core.Utils;
37import weka.core.Capabilities.Capability;
38import weka.core.TechnicalInformation.Field;
39import weka.core.TechnicalInformation.Type;
40
41import java.util.Enumeration;
42import java.util.LinkedList;
43import java.util.Vector;
44
45
46/**
47 <!-- globalinfo-start -->
48 * Nearest-neighbor-like algorithm using non-nested generalized exemplars (which are hyperrectangles that can be viewed as if-then rules). For more information, see <br/>
49 * <br/>
50 * Brent Martin (1995). Instance-Based learning: Nearest Neighbor With Generalization. Hamilton, New Zealand.<br/>
51 * <br/>
52 * Sylvain Roy (2002). Nearest Neighbor With Generalization. Christchurch, New Zealand.
53 * <p/>
54 <!-- globalinfo-end -->
55 *
56 <!-- technical-bibtex-start -->
57 * BibTeX:
58 * <pre>
59 * &#64;mastersthesis{Martin1995,
60 *    address = {Hamilton, New Zealand},
61 *    author = {Brent Martin},
62 *    school = {University of Waikato},
63 *    title = {Instance-Based learning: Nearest Neighbor With Generalization},
64 *    year = {1995}
65 * }
66 *
67 * &#64;unpublished{Roy2002,
68 *    address = {Christchurch, New Zealand},
69 *    author = {Sylvain Roy},
70 *    school = {University of Canterbury},
71 *    title = {Nearest Neighbor With Generalization},
72 *    year = {2002}
73 * }
74 * </pre>
75 * <p/>
76 <!-- technical-bibtex-end -->
77 *
78 <!-- options-start -->
79 * Valid options are: <p/>
80 *
81 * <pre> -G &lt;value&gt;
82 *  Number of attempts of generalisation.
83 * </pre>
84 *
85 * <pre> -I &lt;value&gt;
86 *  Number of folder for computing the mutual information.
87 * </pre>
88 *
89 <!-- options-end -->
90 *
91 * @author Brent Martin (bim20@cosc.canterbury.ac.nz)
92 * @author Sylvain Roy (sro33@student.canterbury.ac.nz)
93 * @version $Revision: 5956 $
94 */
95public class NNge 
96  extends AbstractClassifier
97  implements UpdateableClassifier, OptionHandler, TechnicalInformationHandler {
98
99  /** for serialization */
100  static final long serialVersionUID = 4084742275553788972L;
101 
102  /**
103   * Returns a string describing classifier
104   * @return a description suitable for
105   * displaying in the explorer/experimenter gui
106   */
107  public String globalInfo() {
108
109    return "Nearest-neighbor-like algorithm using non-nested generalized exemplars "
110      + "(which are hyperrectangles that can be viewed as if-then rules). For more "
111      + "information, see \n\n"
112      + getTechnicalInformation().toString();
113  }
114
115  /**
116   * Returns an instance of a TechnicalInformation object, containing
117   * detailed information about the technical background of this class,
118   * e.g., paper reference or book this class is based on.
119   *
120   * @return the technical information about this class
121   */
122  public TechnicalInformation getTechnicalInformation() {
123    TechnicalInformation        result;
124    TechnicalInformation        additional;
125   
126    result = new TechnicalInformation(Type.MASTERSTHESIS);
127    result.setValue(Field.AUTHOR, "Brent Martin");
128    result.setValue(Field.YEAR, "1995");
129    result.setValue(Field.TITLE, "Instance-Based learning: Nearest Neighbor With Generalization");
130    result.setValue(Field.SCHOOL, "University of Waikato");
131    result.setValue(Field.ADDRESS, "Hamilton, New Zealand");
132   
133    additional = result.add(Type.UNPUBLISHED);
134    additional.setValue(Field.AUTHOR, "Sylvain Roy");
135    additional.setValue(Field.YEAR, "2002");
136    additional.setValue(Field.TITLE, "Nearest Neighbor With Generalization");
137    additional.setValue(Field.SCHOOL, "University of Canterbury");
138    additional.setValue(Field.ADDRESS, "Christchurch, New Zealand");
139   
140    return result;
141  }
142
143  /**
144   * Implements Exemplar as used by NNge : parallel axis hyperrectangle.
145   */
146  private class Exemplar 
147    extends Instances {
148   
149    /** for serialization */
150    static final long serialVersionUID = 3960180128928697216L;
151   
152    /** List of all the Exemplar */
153    private Exemplar previous = null;
154    private Exemplar next = null;
155       
156    /** List of all the Exemplar with the same class */
157    private Exemplar previousWithClass = null;
158    private Exemplar nextWithClass = null;
159
160    /** The NNge which owns this Exemplar */
161    private NNge m_NNge;
162
163    /** class of the Exemplar */
164    private double m_ClassValue;
165
166    /** Number of correct prediction for this examplar */
167    private int m_PositiveCount = 1;
168   
169    /** Number of incorrect prediction for this examplar */
170    private int m_NegativeCount = 0;
171
172    /** The max borders of the rectangle for numeric attributes */
173    private double[] m_MaxBorder;
174                             
175    /** The min borders of the rectangle for numeric attributes */
176    private double[] m_MinBorder;
177                             
178    /** The ranges of the hyperrectangle for nominal attributes */
179    private boolean[][] m_Range;
180                             
181    /** the arrays used by preGeneralise */
182    private double[] m_PreMaxBorder = null;
183    private double[] m_PreMinBorder = null;
184    private boolean[][] m_PreRange = null;
185    private Instance m_PreInst = null;
186
187
188    /**
189     * Build a new empty Exemplar
190     *
191     * @param nnge the classifier which owns this Exemplar
192     * @param inst the instances from which the header information is to be taken
193     * @param size the capacity of the Exemplar
194     * @param classV the class of the Exemplar
195     */
196    private Exemplar (NNge nnge, Instances inst, int size, double classV){
197
198      super(inst, size);
199      m_NNge = nnge;
200      m_ClassValue = classV;
201      m_MinBorder = new double[numAttributes()];
202      m_MaxBorder = new double[numAttributes()];
203      m_Range = new boolean[numAttributes()][];
204      for(int i = 0; i < numAttributes(); i++){
205        if(attribute(i).isNumeric()){
206          m_MinBorder[i] = Double.POSITIVE_INFINITY;
207          m_MaxBorder[i] = Double.NEGATIVE_INFINITY;
208          m_Range[i] = null;
209        } else {
210          m_MinBorder[i] = Double.NaN;
211          m_MaxBorder[i] = Double.NaN;
212          m_Range[i] = new boolean[attribute(i).numValues() + 1];
213          for(int j = 0; j < attribute(i).numValues() + 1; j++){
214            m_Range[i][j] = false;
215          }
216        }
217      }
218    }
219
220
221    /**
222     * Generalise the Exemplar with inst
223     *
224     * @param inst the new example used for the generalisation
225     * @throws Exception if either the class of inst is not equal to the class of the Exemplar or inst misses a value.
226     */
227    private void generalise(Instance inst) throws Exception {
228
229      if(m_ClassValue != inst.classValue())
230        throw new Exception("Exemplar.generalise : Incompatible instance's class.");
231
232      add(inst);
233
234      /* extends each range in order to cover inst */
235      for(int i = 0; i < numAttributes(); i++){
236         
237        if(inst.isMissing(i))
238          throw new Exception("Exemplar.generalise : Generalisation with missing feature impossible.");
239
240        if(i == classIndex())
241          continue;
242           
243        if(attribute(i).isNumeric()){
244          if(m_MaxBorder[i] < inst.value(i)) 
245            m_MaxBorder[i] = inst.value(i);
246          if(inst.value(i) < m_MinBorder[i]) 
247            m_MinBorder[i] = inst.value(i); 
248               
249        } else {
250          m_Range[i][(int) inst.value(i)] = true;
251        }
252      }
253    } 
254
255
256    /**
257     * pre-generalise the Exemplar with inst
258     * i.e. the boundaries of the Exemplar include inst but the Exemplar still doesn't 'own' inst.
259     * To be complete, the generalisation must be validated with validateGeneralisation.
260     * the generalisation can be canceled with cancelGeneralisation.
261     * @param inst the new example used for the generalisation
262     * @throws Exception if either the class of inst is not equal to the class of the Exemplar or inst misses a value.
263     */
264    private void preGeneralise(Instance inst) throws Exception {
265       
266      if(m_ClassValue != inst.classValue())
267        throw new Exception("Exemplar.preGeneralise : Incompatible instance's class.");
268
269      m_PreInst = inst;
270
271      /* save the current state */
272      m_PreRange = new boolean[numAttributes()][];
273      m_PreMinBorder = new double[numAttributes()];
274      m_PreMaxBorder = new double[numAttributes()];
275      for(int i = 0; i < numAttributes(); i++){
276        if(attribute(i).isNumeric()){
277          m_PreMinBorder[i] = m_MinBorder[i];
278          m_PreMaxBorder[i] = m_MaxBorder[i];
279        } else {
280          m_PreRange[i] = new boolean[attribute(i).numValues() + 1];
281          for(int j = 0; j < attribute(i).numValues() + 1; j++){
282            m_PreRange[i][j] = m_Range[i][j];
283          }
284        }
285      }
286
287      /* perform the pre-generalisation */
288      for(int i = 0; i < numAttributes(); i++){
289        if(inst.isMissing(i))
290          throw new Exception("Exemplar.preGeneralise : Generalisation with missing feature impossible.");
291        if(i == classIndex())
292          continue;
293        if(attribute(i).isNumeric()){
294          if(m_MaxBorder[i] < inst.value(i)) 
295            m_MaxBorder[i] = inst.value(i);
296          if(inst.value(i) < m_MinBorder[i]) 
297            m_MinBorder[i] = inst.value(i); 
298        } else {
299          m_Range[i][(int) inst.value(i)] = true;
300        }
301      }
302    }
303
304
305    /**
306     * Validates a generalisation started with preGeneralise.
307     * Watch out, preGeneralise must have been called before.
308     *
309     * @throws Exception is thrown if preGeneralise hasn't been called before
310     */
311    private void validateGeneralisation() throws Exception {
312      if(m_PreInst == null){
313        throw new Exception("Exemplar.validateGeneralisation : validateGeneralisation called without previous call to preGeneralise!");
314      }
315      add(m_PreInst);
316      m_PreRange = null;
317      m_PreMinBorder = null;
318      m_PreMaxBorder = null;
319    }
320
321   
322    /**
323     * Cancels a generalisation started with preGeneralise.
324     * Watch out, preGeneralise must have been called before.
325     *
326     * @throws Exception is thrown if preGeneralise hasn't been called before
327     */
328    private void cancelGeneralisation() throws Exception {
329      if(m_PreInst == null){
330        throw new Exception("Exemplar.cancelGeneralisation : cancelGeneralisation called without previous call to preGeneralise!");
331      }
332      m_PreInst = null;
333      m_Range = m_PreRange;
334      m_MinBorder = m_PreMinBorder;
335      m_MaxBorder = m_PreMaxBorder;
336      m_PreRange = null;
337      m_PreMinBorder = null;
338      m_PreMaxBorder = null;
339    }
340
341
342    /**
343     * return true if inst is held by this Exemplar, false otherwise
344     *
345     * @param inst an Instance
346     * @return true if inst is held by this hyperrectangle, false otherwise
347     */
348    private boolean holds(Instance inst) {
349       
350      if(numInstances() == 0)
351        return false;
352
353      for(int i = 0; i < numAttributes(); i++){
354        if(i != classIndex() && !holds(i, inst.value(i)))
355          return false;
356      }
357      return true;
358    }
359
360
361    /**
362     * return true if value is inside the Exemplar along the attrIndex attribute.
363     *
364     * @param attrIndex the index of an attribute
365     * @param value a value along the attrIndexth attribute
366     * @return true if value is inside the Exemplar along the attrIndex attribute.
367     */
368    private boolean holds(int attrIndex, double value) {
369       
370      if (numAttributes() == 0)
371        return false;
372       
373      if(attribute(attrIndex).isNumeric())
374        return(m_MinBorder[attrIndex] <= value && value <= m_MaxBorder[attrIndex]);
375      else
376        return m_Range[attrIndex][(int) value];
377    }
378
379
380    /**
381     * Check if the Examplar overlaps ex
382     *
383     * @param ex an Exemplar
384     * @return true if ex is overlapped by the Exemplar
385     * @throws Exception
386     */
387    private boolean overlaps(Exemplar ex) {
388
389      if(ex.isEmpty() || isEmpty())
390        return false;
391
392      for (int i = 0; i < numAttributes(); i++){
393           
394        if(i == classIndex()){
395          continue;
396        }
397        if (attribute(i).isNumeric() && 
398            (ex.m_MaxBorder[i] < m_MinBorder[i] || ex.m_MinBorder[i] > m_MaxBorder[i])){
399          return false;
400        }
401        if (attribute(i).isNominal()) {
402          boolean in = false;
403          for (int j = 0; j < attribute(i).numValues() + 1; j++){
404            if(m_Range[i][j] && ex.m_Range[i][j]){
405              in = true;
406              break;
407            }
408          }
409          if(!in) return false;
410        }
411      }
412      return true;
413    }
414
415
416    /**
417     * Compute the distance between the projection of inst and this Exemplar along the attribute attrIndex.
418     * If inst misses its value along the attribute, the function returns 0.
419     *
420     * @param inst an instance
421     * @param attrIndex the index of the attribute
422     * @return the distance between the projection of inst and this Exemplar along the attribute attrIndex.
423     */
424    private double attrDistance(Instance inst, int attrIndex) {
425
426      if(inst.isMissing(attrIndex))
427        return 0;
428
429      /* numeric attribute */
430      if(attribute(attrIndex).isNumeric()){
431
432        double norm = m_NNge.m_MaxArray[attrIndex] - m_NNge.m_MinArray[attrIndex];
433        if(norm <= 0)
434          norm = 1;
435
436        if (m_MaxBorder[attrIndex] < inst.value(attrIndex)) {
437          return (inst.value(attrIndex) - m_MaxBorder[attrIndex]) / norm;
438        } else if (inst.value(attrIndex) < m_MinBorder[attrIndex]) {
439          return (m_MinBorder[attrIndex] - inst.value(attrIndex)) / norm;
440        } else {
441          return 0;
442        }
443
444        /* nominal attribute */
445      } else {
446        if(holds(attrIndex, inst.value(attrIndex))){
447          return 0;
448        } else {
449          return 1;
450        }
451      }
452    }
453
454
455    /**
456     * Returns the square of the distance between inst and the Exemplar.
457     *
458     * @param inst an instance
459     * @return the squared distance between inst and the Exemplar.
460     */
461    private double squaredDistance(Instance inst) {
462       
463      double sum = 0, term;
464      int numNotMissingAttr = 0;
465      for(int i = 0; i < inst.numAttributes(); i++){
466           
467        if(i == classIndex())
468          continue;
469           
470        term = m_NNge.attrWeight(i) * attrDistance(inst, i);
471        term = term * term;
472        sum += term;
473
474        if (!inst.isMissing(i))
475          numNotMissingAttr++;
476
477      }
478       
479      if(numNotMissingAttr == 0){
480        return 0;
481      } else {
482        return sum / (double) (numNotMissingAttr * numNotMissingAttr);
483      }
484    }
485
486
487    /**
488     * Return the weight of the Examplar
489     *
490     * @return the weight of the Examplar.
491     */
492    private double weight(){
493      return ((double) (m_PositiveCount + m_NegativeCount)) / ((double) m_PositiveCount);
494    }
495
496
497    /**
498     * Return the class of the Exemplar
499     *
500     * @return the class of this exemplar as a double (weka format)
501     */
502    private double classValue(){
503      return m_ClassValue;
504    }
505
506
507    /**
508     * Returns the value of the inf border of the Exemplar.
509     *
510     * @param attrIndex the index of the attribute
511     * @return the value of the inf border for this attribute
512     * @throws Exception is thrown either if the attribute is nominal or if the Exemplar is empty
513     */
514    private double getMinBorder(int attrIndex) throws Exception {
515      if(!attribute(attrIndex).isNumeric())
516        throw new Exception("Exception.getMinBorder : not numeric attribute !");
517      if(numInstances() == 0)
518        throw new Exception("Exception.getMinBorder : empty Exemplar !");
519      return m_MinBorder[attrIndex];
520    }
521
522
523    /**
524     * Returns the value of the sup border of the hyperrectangle
525     * Returns NaN if the HyperRectangle doesn't have any border for this attribute
526     *
527     * @param attrIndex the index of the attribute
528     * @return the value of the sup border for this attribute
529     * @throws Exception is thrown either if the attribute is nominal or if the Exemplar is empty
530     */
531    private double getMaxBorder(int attrIndex) throws Exception {
532      if(!attribute(attrIndex).isNumeric())
533        throw new Exception("Exception.getMaxBorder : not numeric attribute !");
534      if(numInstances() == 0)
535        throw new Exception("Exception.getMaxBorder : empty Exemplar !");
536      return m_MaxBorder[attrIndex];
537    }
538
539
540    /**
541     * Returns the number of positive classifications
542     *
543     * @return the number of positive classifications
544     */
545    private int getPositiveCount(){
546      return m_PositiveCount;
547    }
548
549
550    /**
551     * Returns the number of negative classifications
552     *
553     * @return the number of negative classifications
554     */
555    private int getNegativeCount(){
556      return m_NegativeCount;
557    }
558
559
560    /**
561     * Set the number of positive classifications
562     *
563     * @param value an integer value (greater than 0 is wise...)
564     */
565    private void setPositiveCount(int value) {
566      m_PositiveCount = value;
567    }
568
569
570    /**
571     * Set the number of negative classifications
572     *
573     * @param value an integer value
574     */
575    private void setNegativeCount(int value) {
576      m_NegativeCount = value;
577    }
578
579
580    /**
581     * Increment the number of positive Classifications
582     */
583    private void incrPositiveCount(){
584      m_PositiveCount++;
585    }
586
587
588    /**
589     * Increment the number of negative Classifications
590     */
591    private void incrNegativeCount(){
592      m_NegativeCount++;
593    }
594
595
596    /**
597     * Returns true if the Exemplar is empty (i.e. doesn't yield any Instance)
598     *
599     * @return true if the Exemplar is empty, false otherwise
600     */
601    public boolean isEmpty(){
602      return (numInstances() == 0);
603    }
604
605   
606    /**
607     * Returns a description of this Exemplar
608     *
609     * @return A string that describes this Exemplar
610     */
611    private String toString2(){
612      String s;
613      Enumeration enu = null;
614      s = "Exemplar[";
615      if (numInstances() == 0) {
616        return s + "Empty]";
617      }
618      s += "{";
619      enu = enumerateInstances();
620      while(enu.hasMoreElements()){
621        s = s + "<" + enu.nextElement().toString() + "> ";
622      }
623      s = s.substring(0, s.length()-1);
624      s = s + "} {" + toRules() + "} p=" + m_PositiveCount + " n=" + m_NegativeCount + "]";
625      return s;
626    }
627
628
629    /**
630     * Returns a string of the rules induced by this examplar
631     *
632     * @return a string of the rules induced by this examplar
633     */
634    private String toRules(){
635
636      if (numInstances() == 0)
637        return "No Rules (Empty Exemplar)";
638
639      String s = "", sep = "";
640       
641      for(int i = 0; i < numAttributes(); i++){
642           
643        if(i == classIndex())
644          continue;
645           
646        if(attribute(i).isNumeric()){
647          if(m_MaxBorder[i] != m_MinBorder[i]){
648            s += sep + m_MinBorder[i] + "<=" + attribute(i).name() + "<=" + m_MaxBorder[i];
649          } else {
650            s += sep + attribute(i).name() + "=" + m_MaxBorder[i];
651          }
652          sep = " ^ ";
653           
654        } else {
655          s += sep + attribute(i).name() + " in {";
656          String virg = "";
657          for(int j = 0; j < attribute(i).numValues() + 1; j++){
658            if(m_Range[i][j]){
659              s+= virg;
660              if(j == attribute(i).numValues())
661                s += "?";
662              else
663                s += attribute(i).value(j);
664              virg = ",";
665            }
666          }
667          s+="}";
668          sep = " ^ ";
669        }           
670      }
671      s += "  ("+numInstances() +")";
672      return s;
673    }
674   
675    /**
676     * Returns the revision string.
677     *
678     * @return          the revision
679     */
680    public String getRevision() {
681      return RevisionUtils.extract("$Revision: 5956 $");
682    }
683  }
684
685
686
687  /** An empty instances to keep the headers, the classIndex, etc... */
688  private Instances m_Train;
689
690  /** The list of Exemplars */
691  private Exemplar m_Exemplars;
692
693  /** The lists of Exemplars by class */
694  private Exemplar m_ExemplarsByClass[];
695
696  /** The minimum values for numeric attributes. */
697  double [] m_MinArray;
698
699  /** The maximum values for numeric attributes. */
700  double [] m_MaxArray;
701
702  /** The number of try for generalisation */
703  private int m_NumAttemptsOfGene = 5;
704
705  /** The number of folder for the Mutual Information */
706  private int m_NumFoldersMI = 5;
707
708  /** Values to use for missing value */
709  private double [] m_MissingVector;
710
711  /** MUTUAL INFORMATION'S DATAS */
712  /* numeric attributes */
713  private int [][][] m_MI_NumAttrClassInter;
714  private int [][] m_MI_NumAttrInter;
715  private double [] m_MI_MaxArray;
716  private double [] m_MI_MinArray;
717  /* nominal attributes */
718  private int [][][] m_MI_NumAttrClassValue;
719  private int [][] m_MI_NumAttrValue;
720  /* both */
721  private int [] m_MI_NumClass;
722  private int m_MI_NumInst;
723  private double [] m_MI;
724
725
726
727  /** MAIN FUNCTIONS OF THE CLASSIFIER */
728
729
730  /**
731   * Returns default capabilities of the classifier.
732   *
733   * @return      the capabilities of this classifier
734   */
735  public Capabilities getCapabilities() {
736    Capabilities result = super.getCapabilities();
737    result.disableAll();
738
739    // attributes
740    result.enable(Capability.NOMINAL_ATTRIBUTES);
741    result.enable(Capability.NUMERIC_ATTRIBUTES);
742    result.enable(Capability.DATE_ATTRIBUTES);
743    result.enable(Capability.MISSING_VALUES);
744
745    // class
746    result.enable(Capability.NOMINAL_CLASS);
747    result.enable(Capability.MISSING_CLASS_VALUES);
748
749    // instances
750    result.setMinimumNumberInstances(0);
751   
752    return result;
753  }
754
755  /**
756   * Generates a classifier. Must initialize all fields of the classifier
757   * that are not being set via options (ie. multiple calls of buildClassifier
758   * must always lead to the same result). Must not change the dataset
759   * in any way.
760   *
761   * @param data set of instances serving as training data
762   * @throws Exception if the classifier has not been
763   * generated successfully
764   */
765  public void buildClassifier(Instances data) throws Exception {
766
767    // can classifier handle the data?
768    getCapabilities().testWithFail(data);
769
770    // remove instances with missing class
771    data = new Instances(data);
772    data.deleteWithMissingClass();
773   
774    /* initialize the classifier */
775
776    m_Train = new Instances(data, 0);
777    m_Exemplars = null;
778    m_ExemplarsByClass = new Exemplar[m_Train.numClasses()];
779    for(int i = 0; i < m_Train.numClasses(); i++){
780      m_ExemplarsByClass[i] = null;
781    }
782    m_MaxArray = new double[m_Train.numAttributes()];
783    m_MinArray = new double[m_Train.numAttributes()];
784    for(int i = 0; i < m_Train.numAttributes(); i++){
785      m_MinArray[i] = Double.POSITIVE_INFINITY;
786      m_MaxArray[i] = Double.NEGATIVE_INFINITY;
787    }
788
789    m_MI_MinArray = new double [data.numAttributes()];
790    m_MI_MaxArray = new double [data.numAttributes()];
791    m_MI_NumAttrClassInter = new int[data.numAttributes()][][];
792    m_MI_NumAttrInter = new int[data.numAttributes()][];
793    m_MI_NumAttrClassValue = new int[data.numAttributes()][][];
794    m_MI_NumAttrValue = new int[data.numAttributes()][];
795    m_MI_NumClass = new int[data.numClasses()];
796    m_MI = new double[data.numAttributes()];
797    m_MI_NumInst = 0;
798    for(int cclass = 0; cclass < data.numClasses(); cclass++)
799      m_MI_NumClass[cclass] = 0;
800    for (int attrIndex = 0; attrIndex < data.numAttributes(); attrIndex++) {
801           
802      if(attrIndex == data.classIndex())
803        continue;
804           
805      m_MI_MaxArray[attrIndex] = m_MI_MinArray[attrIndex] = Double.NaN;
806      m_MI[attrIndex] = Double.NaN;
807           
808      if(data.attribute(attrIndex).isNumeric()){
809        m_MI_NumAttrInter[attrIndex] = new int[m_NumFoldersMI];
810        for(int inter = 0; inter < m_NumFoldersMI; inter++){
811          m_MI_NumAttrInter[attrIndex][inter] = 0;
812        }
813      } else {
814        m_MI_NumAttrValue[attrIndex] = new int[data.attribute(attrIndex).numValues() + 1];
815        for(int attrValue = 0; attrValue < data.attribute(attrIndex).numValues() + 1; attrValue++){
816          m_MI_NumAttrValue[attrIndex][attrValue] = 0;
817        }
818      }
819           
820      m_MI_NumAttrClassInter[attrIndex] = new int[data.numClasses()][];
821      m_MI_NumAttrClassValue[attrIndex] = new int[data.numClasses()][];
822
823      for(int cclass = 0; cclass < data.numClasses(); cclass++){
824        if(data.attribute(attrIndex).isNumeric()){
825          m_MI_NumAttrClassInter[attrIndex][cclass] = new int[m_NumFoldersMI];
826          for(int inter = 0; inter < m_NumFoldersMI; inter++){
827            m_MI_NumAttrClassInter[attrIndex][cclass][inter] = 0;
828          }
829        } else if(data.attribute(attrIndex).isNominal()){
830          m_MI_NumAttrClassValue[attrIndex][cclass] = new int[data.attribute(attrIndex).numValues() + 1];               
831          for(int attrValue = 0; attrValue < data.attribute(attrIndex).numValues() + 1; attrValue++){
832            m_MI_NumAttrClassValue[attrIndex][cclass][attrValue] = 0;
833          }
834        }
835      }
836    }
837    m_MissingVector = new double[data.numAttributes()];
838    for(int i = 0; i < data.numAttributes(); i++){
839      if(i == data.classIndex()){
840        m_MissingVector[i] = Double.NaN;
841      } else {
842        m_MissingVector[i] = data.attribute(i).numValues();
843      }
844    }
845
846    /* update the classifier with data */
847    Enumeration enu = data.enumerateInstances();
848    while(enu.hasMoreElements()){
849      update((Instance) enu.nextElement());
850    }   
851  }
852
853   
854  /**
855   * Classifies a given instance.
856   *
857   * @param instance the instance to be classified
858   * @return index of the predicted class as a double
859   * @throws Exception if instance could not be classified
860   * successfully
861   */
862  public double classifyInstance(Instance instance) throws Exception {
863
864    /* check the instance */
865    if (m_Train.equalHeaders(instance.dataset()) == false){
866      throw new Exception("NNge.classifyInstance : Incompatible instance types !\n" + m_Train.equalHeadersMsg(instance.dataset()));
867    }
868       
869    Exemplar matched = nearestExemplar(instance); 
870    if(matched == null){
871      throw new Exception("NNge.classifyInstance : NNge hasn't been trained !");
872    }
873    return matched.classValue();
874  }
875
876
877  /**
878   * Updates the classifier using the given instance.
879   *
880   * @param instance the instance to include
881   * @throws Exception if instance could not be incorporated
882   * successfully
883   */
884  public void updateClassifier(Instance instance) throws Exception {
885
886    if (m_Train.equalHeaders(instance.dataset()) == false) {
887      throw new Exception("Incompatible instance types\n" + m_Train.equalHeadersMsg(instance.dataset()));
888    }   
889    update(instance);   
890  }
891
892
893
894  /** HIGH LEVEL SUB-FUNCTIONS */
895   
896
897
898  /**
899   * Performs the update of the classifier
900   *
901   * @param instance the new instance
902   * @throws Exception if the update fails
903   */
904  private void update(Instance instance) throws Exception {
905
906    if (instance.classIsMissing()) {
907      return;
908    }
909
910    instance.replaceMissingValues(m_MissingVector);
911    m_Train.add(instance);
912
913    /* Update the minimum and maximum for all the attributes */
914    updateMinMax(instance);
915
916    /* update the mutual information datas */
917    updateMI(instance);
918
919    /* Nearest Exemplar */
920    Exemplar nearest = nearestExemplar(instance);
921       
922    /* Adjust */
923    if(nearest == null){
924      Exemplar newEx = new Exemplar(this, m_Train, 10, instance.classValue());
925      newEx.generalise(instance);
926      initWeight(newEx);
927      addExemplar(newEx);
928      return;
929    }
930    adjust(instance, nearest);
931
932    /* Generalise */
933    generalise(instance);
934  }
935
936
937  /**
938   * Returns the nearest Exemplar
939   *
940   * @param inst an Instance
941   * @return the nearest Exemplar to inst, null if no exemplar are found.
942   */
943  private Exemplar nearestExemplar(Instance inst){
944
945    if (m_Exemplars == null)
946      return null;
947    Exemplar cur = m_Exemplars, nearest = m_Exemplars;
948    double dist, smallestDist = cur.squaredDistance(inst);
949    while (cur.next != null){
950      cur = cur.next;
951      dist = cur.squaredDistance(inst);
952      if (dist < smallestDist){
953        smallestDist = dist;
954        nearest = cur;
955      }
956    }
957    return nearest;
958  }
959
960   
961  /**
962   * Returns the nearest Exemplar with class c
963   *
964   * @param inst an Instance
965   * @param c the class of the Exemplar to return
966   * @return the nearest Exemplar to inst with class c, null if no exemplar with class c are found.
967   */
968  private Exemplar nearestExemplar(Instance inst, double c){
969
970    if (m_ExemplarsByClass[(int) c] == null)
971      return null;
972    Exemplar cur = m_ExemplarsByClass[(int) c], nearest = m_ExemplarsByClass[(int) c];
973    double dist, smallestDist = cur.squaredDistance(inst);
974    while (cur.nextWithClass != null){
975      cur = cur.nextWithClass;
976      dist = cur.squaredDistance(inst);
977      if (dist < smallestDist){
978        smallestDist = dist;
979        nearest = cur;
980      }
981    }
982    return nearest;
983  }
984
985   
986  /**
987   * Generalise an Exemplar (not necessarily predictedExemplar) to match instance.
988   * predictedExemplar must be in NNge's lists
989   *
990   * @param newInst the new instance
991   * @throws Exception in case of inconsitent situation
992   */
993  private void generalise(Instance newInst) throws Exception {
994
995    Exemplar first = m_ExemplarsByClass[(int) newInst.classValue()];
996    int n = 0;
997
998    /* try to generalise with the n first exemplars */
999    while(n < m_NumAttemptsOfGene && first != null){
1000           
1001      /* find the nearest one starting from first */
1002      Exemplar closest = first, cur = first;
1003      double smallestDist = first.squaredDistance(newInst), dist;
1004      while(cur.nextWithClass != null){
1005        cur = cur.nextWithClass;
1006        dist = cur.squaredDistance(newInst);
1007        if(dist < smallestDist){
1008          smallestDist = dist;
1009          closest = cur;
1010        }
1011      }
1012
1013      /* remove the Examplar from NNge's lists */
1014      if(closest == first)
1015        first = first.nextWithClass;
1016      removeExemplar(closest); 
1017
1018      /* try to generalise */
1019      closest.preGeneralise(newInst);
1020      if(!detectOverlapping(closest)){
1021        closest.validateGeneralisation();
1022        addExemplar(closest);
1023        return;
1024      }
1025
1026      /* it didn't work, put ungeneralised exemplar on the top of the lists */
1027      closest.cancelGeneralisation();
1028      addExemplar(closest);                     
1029
1030      n++;
1031    }
1032
1033    /* generalisation failled : add newInst as a new Examplar */
1034    Exemplar newEx = new Exemplar(this, m_Train, 5, newInst.classValue());
1035    newEx.generalise(newInst);
1036    initWeight(newEx);
1037    addExemplar(newEx);
1038  }
1039
1040
1041  /**
1042   * Adjust the NNge.
1043   *
1044   * @param newInst the instance to classify
1045   * @param predictedExemplar the Exemplar that matches newInst
1046   * @throws Exception in case of inconsistent situation
1047   */
1048  private void adjust(Instance newInst, Exemplar predictedExemplar) throws Exception {
1049
1050    /* correct prediction */
1051    if(newInst.classValue() == predictedExemplar.classValue()){
1052      predictedExemplar.incrPositiveCount();
1053      /* incorrect prediction */
1054    } else {
1055      predictedExemplar.incrNegativeCount();
1056
1057      /* new instance falls inside */
1058      if(predictedExemplar.holds(newInst)){
1059        prune(predictedExemplar, newInst);
1060      }
1061    }   
1062  }
1063
1064
1065  /**
1066   * Prunes an Exemplar that matches an Instance
1067   *
1068   * @param predictedExemplar an Exemplar
1069   * @param newInst an Instance matched by predictedExemplar
1070   * @throws Exception in case of inconsistent situation. (shouldn't happen.)
1071   */
1072  private void prune(Exemplar predictedExemplar, Instance newInst) throws Exception {
1073
1074    /* remove the Exemplar */
1075    removeExemplar(predictedExemplar);
1076
1077    /* look for the best nominal feature and the best numeric feature to cut */
1078    int numAttr = -1, nomAttr = -1;
1079    double smallestDelta = Double.POSITIVE_INFINITY, delta;
1080    int biggest_N_Nom = -1, biggest_N_Num = -1, n, m;
1081    for(int i = 0; i < m_Train.numAttributes(); i++){
1082
1083      if(i == m_Train.classIndex())
1084        continue;
1085
1086      /* numeric attribute */
1087      if(m_Train.attribute(i).isNumeric()){
1088
1089        /* compute the distance 'delta' to the closest boundary */
1090        double norm = m_MaxArray[i] - m_MinArray[i];
1091        if(norm != 0){
1092          delta = Math.min((predictedExemplar.getMaxBorder(i) - newInst.value(i)), 
1093                           (newInst.value(i) - predictedExemplar.getMinBorder(i))) / norm;
1094        } else {
1095          delta = Double.POSITIVE_INFINITY;
1096        }
1097
1098        /* compute the size of the biggest Exemplar which would be created */
1099        n = m = 0;
1100        Enumeration enu = predictedExemplar.enumerateInstances();
1101        while(enu.hasMoreElements()){
1102          Instance ins = (Instance) enu.nextElement();
1103          if(ins.value(i) < newInst.value(i))
1104            n++;
1105          else if(ins.value(i) > newInst.value(i))
1106            m++;
1107        }
1108        n = Math.max(n, m);
1109
1110        if(delta < smallestDelta){
1111          smallestDelta = delta;
1112          biggest_N_Num = n;
1113          numAttr = i;
1114        } else if(delta == smallestDelta && n > biggest_N_Num){
1115          biggest_N_Num = n;
1116          numAttr = i;
1117        }
1118
1119        /* nominal attribute */
1120      } else {
1121
1122        /* compute the size of the Exemplar which would be created */
1123        Enumeration enu = predictedExemplar.enumerateInstances();
1124        n = 0;
1125        while(enu.hasMoreElements()){
1126          if(((Instance) enu.nextElement()).value(i) != newInst.value(i))
1127            n++;
1128        }
1129        if(n > biggest_N_Nom){
1130          biggest_N_Nom = n;
1131          nomAttr = i;
1132        } 
1133      }
1134    }
1135
1136    /* selection of the feature to cut between the best nominal and the best numeric */
1137    int attrToCut;
1138    if(numAttr == -1 && nomAttr == -1){
1139      attrToCut = 0;
1140    } else if (numAttr == -1){
1141      attrToCut = nomAttr;
1142    } else if(nomAttr == -1){
1143      attrToCut = numAttr;
1144    } else {
1145      if(biggest_N_Nom > biggest_N_Num)
1146        attrToCut = nomAttr;
1147      else
1148        attrToCut = numAttr;
1149    }
1150
1151    /* split the Exemplar */
1152    Instance curInst;
1153    Exemplar a, b;
1154    a = new Exemplar(this, m_Train, 10, predictedExemplar.classValue());
1155    b = new Exemplar(this, m_Train, 10, predictedExemplar.classValue());
1156    LinkedList leftAlone = new LinkedList();
1157    Enumeration enu = predictedExemplar.enumerateInstances();
1158    if(m_Train.attribute(attrToCut).isNumeric()){
1159      while(enu.hasMoreElements()){
1160        curInst = (Instance) enu.nextElement();
1161        if(curInst.value(attrToCut) > newInst.value(attrToCut)){
1162          a.generalise(curInst);
1163        } else if (curInst.value(attrToCut) < newInst.value(attrToCut)){
1164          b.generalise(curInst);
1165        } else if (notEqualFeatures(curInst, newInst)) {
1166          leftAlone.add(curInst);
1167        }
1168      }
1169    } else {
1170      while(enu.hasMoreElements()){
1171        curInst = (Instance) enu.nextElement();
1172        if(curInst.value(attrToCut) != newInst.value(attrToCut)){
1173          a.generalise(curInst);
1174        } else if (notEqualFeatures(curInst, newInst)){
1175          leftAlone.add(curInst);
1176        }
1177      }
1178    }
1179       
1180    /* treat the left alone Instances */
1181    while(leftAlone.size() != 0){
1182
1183      Instance alone = (Instance) leftAlone.removeFirst();
1184      a.preGeneralise(alone);
1185      if(!a.holds(newInst)){
1186        a.validateGeneralisation();
1187        continue;
1188      }
1189      a.cancelGeneralisation();
1190      b.preGeneralise(alone);
1191      if(!b.holds(newInst)){
1192        b.validateGeneralisation();
1193        continue;
1194      }
1195      b.cancelGeneralisation();
1196      Exemplar exem = new Exemplar(this, m_Train, 3, alone.classValue());
1197      exem.generalise(alone);
1198      initWeight(exem);
1199      addExemplar(exem);
1200    }
1201
1202    /* add (or not) the new Exemplars */
1203    if(a.numInstances() != 0){
1204      initWeight(a);
1205      addExemplar(a);
1206    }
1207    if(b.numInstances() != 0){
1208      initWeight(b);
1209      addExemplar(b);       
1210    }
1211  }
1212
1213
1214  /**
1215   * Returns true if the instance don't have the same feature values
1216   *
1217   * @param inst1 an instance
1218   * @param inst2 an instance
1219   * @return true if the instance don't have the same feature values
1220   */
1221  private boolean notEqualFeatures(Instance inst1, Instance inst2) {
1222
1223    for(int i = 0; i < m_Train.numAttributes(); i++){
1224      if(i == m_Train.classIndex())
1225        continue;
1226      if(inst1.value(i) != inst2.value(i))
1227        return true;
1228    }
1229    return false;
1230  }
1231
1232
1233  /**
1234   * Returns true if ex overlaps any of the Exemplars in NNge's lists
1235   *
1236   * @param ex an Exemplars
1237   * @return true if ex overlaps any of the Exemplars in NNge's lists
1238   */
1239  private boolean detectOverlapping(Exemplar ex){
1240    Exemplar cur = m_Exemplars;
1241    while(cur != null){
1242      if(ex.overlaps(cur)){
1243        return true;
1244      }
1245      cur = cur.next;
1246    }
1247    return false;
1248  }
1249
1250
1251  /**
1252   * Updates the minimum, maximum, sum, sumSquare values for all the attributes
1253   *
1254   * @param instance the new instance
1255   */
1256  private void updateMinMax(Instance instance){
1257
1258    for (int j = 0; j < m_Train.numAttributes(); j++) {
1259      if(m_Train.classIndex() == j || m_Train.attribute(j).isNominal())
1260        continue;
1261      if (instance.value(j) < m_MinArray[j]) 
1262        m_MinArray[j] = instance.value(j);
1263      if (instance.value(j) > m_MaxArray[j])
1264        m_MaxArray[j] = instance.value(j);
1265    }   
1266  }
1267
1268
1269  /**
1270   * Updates the data for computing the mutual information
1271   *
1272   * MUST be called AFTER adding inst in m_Train
1273   *
1274   * @param inst the new instance
1275   * @throws Exception is thrown if an inconsistent situation is met
1276   */
1277  private void updateMI(Instance inst) throws Exception {
1278
1279    if(m_NumFoldersMI < 1){
1280      throw new Exception("NNge.updateMI : incorrect number of folders ! Option I must be greater than 1.");
1281    }
1282
1283    m_MI_NumClass[(int) inst.classValue()]++;
1284    m_MI_NumInst++;
1285
1286    /* for each attribute */
1287    for(int attrIndex = 0; attrIndex < m_Train.numAttributes(); attrIndex++){
1288
1289      /* which is the class attribute */
1290      if(m_Train.classIndex() == attrIndex)
1291        continue;
1292
1293      /* which is a numeric attribute */
1294      else if(m_Train.attribute(attrIndex).isNumeric()){
1295               
1296        /* if max-min have to be updated */
1297        if(Double.isNaN(m_MI_MaxArray[attrIndex]) ||
1298           Double.isNaN(m_MI_MinArray[attrIndex]) ||
1299           m_MI_MaxArray[attrIndex] < inst.value(attrIndex) || 
1300           inst.value(attrIndex) < m_MI_MinArray[attrIndex]){
1301
1302          /* then update them */
1303          if(Double.isNaN(m_MI_MaxArray[attrIndex])) m_MI_MaxArray[attrIndex] = inst.value(attrIndex);
1304          if(Double.isNaN(m_MI_MinArray[attrIndex])) m_MI_MinArray[attrIndex] = inst.value(attrIndex);
1305          if(m_MI_MaxArray[attrIndex] < inst.value(attrIndex)) m_MI_MaxArray[attrIndex] = inst.value(attrIndex);
1306          if(m_MI_MinArray[attrIndex] > inst.value(attrIndex)) m_MI_MinArray[attrIndex] = inst.value(attrIndex);
1307                   
1308          /* and re-compute everything from scratch... (just for this attribute) */
1309          double delta = (m_MI_MaxArray[attrIndex] - m_MI_MinArray[attrIndex]) / (double) m_NumFoldersMI;
1310
1311          /* for each interval */
1312          for(int inter = 0; inter < m_NumFoldersMI; inter++){
1313
1314            m_MI_NumAttrInter[attrIndex][inter] = 0;
1315
1316            /* for each class */
1317            for(int cclass = 0; cclass < m_Train.numClasses(); cclass++){
1318                           
1319              m_MI_NumAttrClassInter[attrIndex][cclass][inter] = 0;
1320
1321              /* count */
1322              Enumeration enu = m_Train.enumerateInstances();
1323              while(enu.hasMoreElements()){
1324                Instance cur = (Instance) enu.nextElement();
1325                if(( (m_MI_MinArray[attrIndex] + inter * delta) <= cur.value(attrIndex)       ) &&
1326                   ( cur.value(attrIndex) <= (m_MI_MinArray[attrIndex] + (inter + 1) * delta) ) &&
1327                   ( cur.classValue() == cclass ) ){
1328                  m_MI_NumAttrInter[attrIndex][inter]++;
1329                  m_MI_NumAttrClassInter[attrIndex][cclass][inter]++;
1330                }
1331              }
1332            }
1333          }
1334               
1335          /* max-min don't have to be updated */
1336        } else {
1337
1338          /* still have to incr the card of the correct interval */
1339          double delta = (m_MI_MaxArray[attrIndex] - m_MI_MinArray[attrIndex]) / (double) m_NumFoldersMI;
1340                   
1341          /* for each interval */
1342          for(int inter = 0; inter < m_NumFoldersMI; inter++){
1343            /* which contains inst*/
1344            if(( (m_MI_MinArray[attrIndex] + inter * delta) <= inst.value(attrIndex)       ) &&
1345               ( inst.value(attrIndex) <= (m_MI_MinArray[attrIndex] + (inter + 1) * delta) )){
1346              m_MI_NumAttrInter[attrIndex][inter]++;
1347              m_MI_NumAttrClassInter[attrIndex][(int) inst.classValue()][inter]++;
1348            }
1349          }
1350        }
1351               
1352        /* update the mutual information of this attribute... */
1353        m_MI[attrIndex] = 0;
1354               
1355        /* for each interval, for each class */
1356        for(int inter = 0; inter < m_NumFoldersMI; inter++){
1357          for(int cclass = 0; cclass < m_Train.numClasses(); cclass++){
1358            double pXY = ((double) m_MI_NumAttrClassInter[attrIndex][cclass][inter]) / ((double) m_MI_NumInst);
1359            double pX = ((double) m_MI_NumClass[cclass]) / ((double) m_MI_NumInst);
1360            double pY = ((double) m_MI_NumAttrInter[attrIndex][inter]) / ((double) m_MI_NumInst);
1361
1362            if(pXY != 0)
1363              m_MI[attrIndex] += pXY * Utils.log2(pXY / (pX * pY));
1364          }
1365        }
1366               
1367        /* which is a nominal attribute */
1368      } else if (m_Train.attribute(attrIndex).isNominal()){
1369               
1370        /*incr the card of the correct 'values' */
1371        m_MI_NumAttrValue[attrIndex][(int) inst.value(attrIndex)]++;
1372        m_MI_NumAttrClassValue[attrIndex][(int) inst.classValue()][(int) inst.value(attrIndex)]++;
1373               
1374        /* update the mutual information of this attribute... */
1375        m_MI[attrIndex] = 0;
1376               
1377        /* for each nominal value, for each class */
1378        for(int attrValue = 0; attrValue < m_Train.attribute(attrIndex).numValues() + 1; attrValue++){
1379          for(int cclass = 0; cclass < m_Train.numClasses(); cclass++){
1380            double pXY = ((double) m_MI_NumAttrClassValue[attrIndex][cclass][attrValue]) / ((double) m_MI_NumInst);
1381            double pX = ((double) m_MI_NumClass[cclass]) / ((double) m_MI_NumInst);
1382            double pY = ((double) m_MI_NumAttrValue[attrIndex][attrValue]) / ((double) m_MI_NumInst);
1383            if(pXY != 0)
1384              m_MI[attrIndex] += pXY * Utils.log2(pXY / (pX * pY));
1385          }
1386        }
1387
1388        /* not a nominal attribute, not a numeric attribute */
1389      } else {
1390        throw new Exception("NNge.updateMI : Cannot deal with 'string attribute'.");
1391      }
1392    }   
1393  }
1394
1395
1396  /**
1397   * Init the weight of ex
1398   * Watch out ! ex shouldn't be in NNge's lists when initialized
1399   *
1400   * @param ex the Exemplar to initialise
1401   */
1402  private void initWeight(Exemplar ex) {       
1403    int pos = 0, neg = 0, n = 0;
1404    Exemplar cur = m_Exemplars;
1405    if (cur == null){
1406      ex.setPositiveCount(1);
1407      ex.setNegativeCount(0);
1408      return;
1409    }
1410    while(cur != null){
1411      pos += cur.getPositiveCount();
1412      neg += cur.getNegativeCount();
1413      n++;
1414      cur = cur.next;
1415    }
1416    ex.setPositiveCount(pos / n);
1417    ex.setNegativeCount(neg / n);
1418  }
1419
1420
1421  /**
1422   * Adds an Exemplar in NNge's lists
1423   * Ensure that the exemplar is not already in a list : the links would be broken...
1424   *
1425   * @param ex a new Exemplar to add
1426   */
1427  private void addExemplar(Exemplar ex) {
1428       
1429    /* add ex at the top of the general list */
1430    ex.next = m_Exemplars;
1431    if(m_Exemplars != null)
1432      m_Exemplars.previous = ex;
1433    ex.previous = null;
1434    m_Exemplars = ex;
1435
1436    /* add ex at the top of the corresponding class list */
1437    ex.nextWithClass = m_ExemplarsByClass[(int) ex.classValue()];
1438    if(m_ExemplarsByClass[(int) ex.classValue()] != null)
1439      m_ExemplarsByClass[(int) ex.classValue()].previousWithClass = ex;
1440    ex.previousWithClass = null;
1441    m_ExemplarsByClass[(int) ex.classValue()] = ex;
1442  }
1443
1444
1445  /**
1446   * Removes an Exemplar from NNge's lists
1447   * Ensure that the Exemplar is actually in NNge's lists.
1448   *   Likely to do something wrong if this condition is not respected.
1449   * Due to the list implementation, the Exemplar can appear only once in the lists :
1450   *   once removed, the exemplar is not in the lists anymore.
1451   *
1452   * @param ex a new Exemplar to add
1453   */
1454  private void removeExemplar(Exemplar ex){
1455
1456    /* remove from the general list */
1457    if(m_Exemplars == ex){
1458      m_Exemplars = ex.next;
1459      if(m_Exemplars != null)
1460        m_Exemplars.previous = null;
1461       
1462    } else {
1463      ex.previous.next = ex.next;
1464      if(ex.next != null){
1465        ex.next.previous = ex.previous;
1466      }
1467    }
1468    ex.next = ex.previous = null;
1469
1470    /* remove from the class list */
1471    if(m_ExemplarsByClass[(int) ex.classValue()] == ex){
1472      m_ExemplarsByClass[(int) ex.classValue()] = ex.nextWithClass;
1473      if(m_ExemplarsByClass[(int) ex.classValue()] != null)
1474        m_ExemplarsByClass[(int) ex.classValue()].previousWithClass = null;
1475       
1476    } else {
1477      ex.previousWithClass.nextWithClass = ex.nextWithClass;
1478      if(ex.nextWithClass != null){
1479        ex.nextWithClass.previousWithClass = ex.previousWithClass;
1480      }
1481    }
1482    ex.nextWithClass = ex.previousWithClass = null;
1483  }
1484
1485
1486  /**
1487   * returns the weight of indexth attribute
1488   *
1489   * @param index attribute's index
1490   * @return the weight of indexth attribute
1491   */
1492  private double attrWeight (int index) {
1493    return m_MI[index];
1494  }
1495
1496   
1497  /**
1498   * Returns a description of this classifier.
1499   *
1500   * @return a description of this classifier as a string.
1501   */
1502  public String toString(){
1503
1504    String s;
1505    Exemplar cur = m_Exemplars;
1506    int i;     
1507
1508   if (m_MinArray == null) {
1509      return "No classifier built";
1510    }
1511     int[] nbHypClass = new int[m_Train.numClasses()];
1512    int[] nbSingleClass = new int[m_Train.numClasses()];
1513    for(i = 0; i<nbHypClass.length; i++){
1514      nbHypClass[i] = 0;
1515      nbSingleClass[i] = 0;
1516    }
1517    int nbHyp = 0, nbSingle = 0;
1518
1519    s = "\nNNGE classifier\n\nRules generated :\n";
1520
1521    while(cur != null){
1522      s += "\tclass " + m_Train.attribute(m_Train.classIndex()).value((int) cur.classValue()) + " IF : ";
1523      s += cur.toRules() + "\n";
1524      nbHyp++;
1525      nbHypClass[(int) cur.classValue()]++;         
1526      if (cur.numInstances() == 1){
1527        nbSingle++;
1528        nbSingleClass[(int) cur.classValue()]++;
1529      }
1530      cur = cur.next;
1531    }
1532    s += "\nStat :\n";
1533    for(i = 0; i<nbHypClass.length; i++){
1534      s += "\tclass " + m_Train.attribute(m_Train.classIndex()).value(i) + 
1535        " : " + Integer.toString(nbHypClass[i]) + " exemplar(s) including " + 
1536        Integer.toString(nbHypClass[i] - nbSingleClass[i]) + " Hyperrectangle(s) and " +
1537        Integer.toString(nbSingleClass[i]) + " Single(s).\n";
1538    }
1539    s += "\n\tTotal : " + Integer.toString(nbHyp) + " exemplars(s) including " + 
1540      Integer.toString(nbHyp - nbSingle) + " Hyperrectangle(s) and " +
1541      Integer.toString(nbSingle) + " Single(s).\n";
1542       
1543    s += "\n";
1544       
1545    s += "\tFeature weights : ";
1546
1547    String space = "[";
1548    for(int ii = 0; ii < m_Train.numAttributes(); ii++){
1549      if(ii != m_Train.classIndex()){
1550        s += space + Double.toString(attrWeight(ii));
1551        space = " ";
1552      }
1553    }
1554    s += "]";
1555    s += "\n\n";
1556    return s;
1557  }
1558
1559
1560
1561  /** OPTION HANDLER FUNCTION */
1562   
1563
1564  /**
1565   * Returns an enumeration of all the available options..
1566   *
1567   * @return an enumeration of all available options.
1568   */
1569  public Enumeration listOptions(){
1570
1571    Vector newVector = new Vector(2);
1572
1573    newVector.addElement(new Option(
1574                                    "\tNumber of attempts of generalisation.\n",
1575                                    "G", 
1576                                    1, 
1577                                    "-G <value>"));
1578    newVector.addElement(new Option(
1579                                    "\tNumber of folder for computing the mutual information.\n",
1580                                    "I", 
1581                                    1, 
1582                                    "-I <value>"));
1583
1584    return newVector.elements();
1585  }
1586
1587   
1588  /**
1589   * Sets the OptionHandler's options using the given list. All options
1590   * will be set (or reset) during this call (i.e. incremental setting
1591   * of options is not possible). <p/>
1592   *
1593   <!-- options-start -->
1594   * Valid options are: <p/>
1595   *
1596   * <pre> -G &lt;value&gt;
1597   *  Number of attempts of generalisation.
1598   * </pre>
1599   *
1600   * <pre> -I &lt;value&gt;
1601   *  Number of folder for computing the mutual information.
1602   * </pre>
1603   *
1604   <!-- options-end -->
1605   *
1606   * @param options the list of options as an array of strings
1607   * @throws Exception if an option is not supported
1608   */
1609  public void setOptions(String[] options) throws Exception {
1610
1611    String str;
1612
1613    /* Number max of attempts of generalisation */
1614    str = Utils.getOption('G', options);
1615    if(str.length() != 0){
1616      m_NumAttemptsOfGene = Integer.parseInt(str);
1617      if(m_NumAttemptsOfGene < 1)
1618        throw new Exception("NNge.setOptions : G option's value must be greater than 1.");
1619    } else {
1620      m_NumAttemptsOfGene = 5;
1621    }
1622
1623    /* Number of folder for computing the mutual information */
1624    str = Utils.getOption('I', options);
1625    if(str.length() != 0){
1626      m_NumFoldersMI = Integer.parseInt(str);
1627      if(m_NumFoldersMI < 1)
1628        throw new Exception("NNge.setOptions : I option's value must be greater than 1.");
1629    } else {
1630      m_NumFoldersMI = 5;
1631    }
1632  }
1633
1634   
1635  /**
1636   * Gets the current option settings for the OptionHandler.
1637   *
1638   * @return the list of current option settings as an array of strings
1639   */
1640  public String[] getOptions(){
1641
1642    String[] options = new String[5];
1643    int current = 0;
1644
1645    options[current++] = "-G"; options[current++] = "" + m_NumAttemptsOfGene;
1646    options[current++] = "-I"; options[current++] = "" + m_NumFoldersMI;
1647
1648    while (current < options.length) {
1649      options[current++] = "";
1650    }
1651    return options;
1652  }
1653
1654  /**
1655   * Returns the tip text for this property
1656   * @return tip text for this property suitable for
1657   * displaying in the explorer/experimenter gui
1658   */
1659  public String numAttemptsOfGeneOptionTipText() {
1660    return "Sets the number of attempts for generalization.";
1661  }
1662
1663  /**
1664   * Gets the number of attempts for generalisation.
1665   *
1666   * @return the value of the option G
1667   */
1668  public int getNumAttemptsOfGeneOption() {
1669    return m_NumAttemptsOfGene;
1670  }
1671
1672
1673  /**
1674   * Sets the number of attempts for generalisation.
1675   *
1676   * @param newIntParameter the new value.
1677   */
1678  public void setNumAttemptsOfGeneOption(int newIntParameter) {
1679    m_NumAttemptsOfGene = newIntParameter;
1680  }
1681
1682  /**
1683   * Returns the tip text for this property
1684   * @return tip text for this property suitable for
1685   * displaying in the explorer/experimenter gui
1686   */
1687  public String numFoldersMIOptionTipText() {
1688    return "Sets the number of folder for mutual information.";
1689  }
1690
1691  /**
1692   * Gets the number of folder for mutual information.
1693   *
1694   * @return the value of the option I
1695   */
1696  public int getNumFoldersMIOption() {
1697    return m_NumFoldersMI;
1698  }
1699
1700  /**
1701   * Sets the number of folder for mutual information.
1702   *
1703   * @param newIntParameter the new value.
1704   */
1705  public void setNumFoldersMIOption(int newIntParameter) {
1706    m_NumFoldersMI = newIntParameter;
1707  }
1708 
1709  /**
1710   * Returns the revision string.
1711   *
1712   * @return            the revision
1713   */
1714  public String getRevision() {
1715    return RevisionUtils.extract("$Revision: 5956 $");
1716  }
1717
1718  /**
1719   * Main method for testing this class.
1720   *
1721   * @param argv should contain command line arguments for evaluation
1722   * (see Evaluation).
1723   */
1724  public static void main(String [] argv) {
1725    runClassifier(new NNge(), argv);
1726  }
1727}
Note: See TracBrowser for help on using the repository browser.