· 8 years ago · Jun 11, 2018, 09:22 AM
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 * LVQ.java
19 * Copyright (C) 2000-2011 University of Waikato, Hamilton, New Zealand
20 *
21 */
22package weka.clusterers;
23
24import java.util.Enumeration;
25import java.util.Vector;
26import java.util.logging.Level;
27import java.util.logging.Logger;
28import weka.core.Attribute;
29import weka.core.Capabilities;
30import weka.core.Capabilities.Capability;
31import weka.core.DenseInstance;
32import weka.core.Instance;
33import weka.core.Instances;
34import weka.core.Option;
35import weka.core.OptionHandler;
36import weka.core.RevisionUtils;
37import weka.core.Utils;
38import weka.core.EuclideanDistance;
39
40/**
41<!-- globalinfo-start -->
42 * A Clusterer that implements Learning Vector Quantization algorithm for
43 * unsupervised clustering. <br/>
44 * T. Kohonen, “Learning Vector Quantizationâ€, The Handbook of Brain Theory and Neural Networks, 2nd Edition, MIT Press, 2003, pp. 631-634.
45 * <p/>
46<!-- globalinfo-end -->
47 *
48<!-- options-start -->
49 * Valid options are: <p/>
50 *
51 * <pre> -L < learning rate>
52 * The learning rate for the training algorithm.
53 * (Value should be greater than 0 and less or equal to 1, Default = 1).</pre>
54 *
55 * <pre> -T <number of training epochs>
56 * Number of training epochs.
57 * (Value should be greater than or equal to 1, Default = 1000).</pre>
58 *
59 * <pre> -C <number of clusters>
60 * The number of clusters.
61 * (Value should be > 0, Default = 2).</pre>
62 *
63 * <pre> -I
64 * Normalizing the attributes will NOT be done.
65 * (Set this to not normalize the attributes).</pre>
66 *
67 * <pre> -S
68 * Statistics will NOT be calculated after training.
69 * (Set this to not calculate statistics).</pre>
70 *
71<!-- options-end -->
72 *
73 * @author John Salatas (jsalatas at gmail.com)
74 * @version $Revision: 1 $
75 */
76public class LVQ extends AbstractClusterer
77 implements OptionHandler {
78
79 /** for serialization */
80 static final long serialVersionUID = -3028490959617832916L;
81 /** the distance function used. */
82 private EuclideanDistance m_euclideanDistance = new EuclideanDistance();
83 /** The number of clusters */
84 private int m_numOfClusters;
85 /** The number of training epochs */
86 private int m_epochs;
87 /** The learning rate for the network */
88 private double m_learningRate;
89 /** The training instances. */
90 private Instances m_instances;
91 /** The weights for each unit in the hidden layer. */
92 private Instances m_clusters;
93 // It is used in EuclideanDistance.closestPoint
94 private int[] m_clusterList;
95 /** This flag states that the user wants the input values normalized. */
96 private boolean m_normalizeAttributes;
97 /** The maximum value for all the attributes. */
98 private double[] m_attributeMax;
99 /** The minimum value for all the attributes. */
100 private double[] m_attributeMin;
101 /** This flag states that the user wants to calculate statistics after training. */
102 private boolean m_calcStats;
103 /** holds the training instances to clusters assignments */
104 private Instances[] m_clusterInstances;
105 /** holds the cluster statistics */
106 private double[][][] m_clusterStats;
107
108 /**
109 * @return The number of clusters.
110 */
111 public int getNumOfClusters() {
112 return m_numOfClusters;
113 }
114
115 /**
116 * Sets the number of clusters.
117 * @param numOfClusters The number of clusters.
118 */
119 public void setNumOfClusters(int numOfClusters) {
120 if (numOfClusters > 0) {
121 this.m_numOfClusters = numOfClusters;
122 }
123 }
124
125 /**
126 * @return The number of training epochs.
127 */
128 public int getEpochs() {
129 return m_epochs;
130 }
131
132 /**
133 * Set the number of training epochs.
134 * Must be greater than or equal to 1.
135 * @param n The number of epochs.
136 */
137 public void setEpochs(int n) {
138 if (n >= 1000) {
139 m_epochs = n;
140 }
141 }
142
143 /**
144 * @return The learning rate for the nodes.
145 */
146 public double getLearningRate() {
147 return m_learningRate;
148 }
149
150 /**
151 * The learning rate can be set using this command.
152 * Must be greater than 0 and no more than 1.
153 * @param l The initial learning rate.
154 */
155 public void setLearningRate(double l) {
156 if (l > 0 && l <= 1) {
157 m_learningRate = l;
158 }
159 }
160
161 /**
162 * @return The flag for normalizing attributes.
163 */
164 public boolean getNormalizeAttributes() {
165 return m_normalizeAttributes;
166 }
167
168 /**
169 * @param a True if the attributes should be normalized (even nominal
170 * attributes will get normalized here) (range goes between -1 - 1).
171 */
172 public void setNormalizeAttributes(boolean a) {
173 m_normalizeAttributes = a;
174 }
175
176 /**
177 * @return The flag for calculating statistics after training.
178 */
179 public boolean getCalcStats() {
180 return m_calcStats;
181 }
182
183 /**
184 *
185 * @param c True if statistics should be calculated.
186 */
187 public void setCalcStats(boolean c) {
188 this.m_calcStats = c;
189 }
190
191 /**
192 * @return a string to describe the number of clusters option.
193 */
194 public String numOfClustersTipText() {
195 return "The number of clusters.";
196 }
197
198 /**
199 * @return a string to describe the caclulate statistics option.
200 */
201 public String calcStatsTipText() {
202 return "This should calculate statistics for each cluster after training.";
203 }
204
205 /**
206 * @return a string to describe the learning rate option.
207 */
208 public String learningRateTipText() {
209 return "The amount the weights are updated.";
210 }
211
212 /**
213 * @return a string to describe the number of training epochs option.
214 */
215 public String epochsTipText() {
216 return "The number training epochs phase.";
217 }
218
219 /**
220 * @return a string to describe the normalize attributes option.
221 */
222 public String normalizeAttributesTipText() {
223 return "This will normalize the attributes.";
224 }
225
226 /**
227 * This will return a string describing the clusterer.
228 * @return The string.
229 */
230 public String globalInfo() {
231 return "A Clusterer that implements Learning Vector Quantization\n"
232 + "algorithm for unsupervised clustering.";
233 }
234
235 /**
236 * Returns the revision string.
237 *
238 * @return the revision
239 */
240 public String getRevision() {
241 return RevisionUtils.extract("$Revision: 1 $");
242 }
243
244 /**
245 * The constructor.
246 */
247 public LVQ() {
248 m_clusters = null;
249 m_numOfClusters = 2;
250 m_epochs = 1000;
251 m_learningRate = 1.0;
252 m_normalizeAttributes = true;
253 m_calcStats = true;
254 }
255
256 /**
257 * Returns default capabilities of the classifier.
258 *
259 * @return the capabilities of this classifier
260 */
261 public Capabilities getCapabilities() {
262 Capabilities result = super.getCapabilities();
263 result.disableAll();
264 result.enable(Capability.NO_CLASS);
265
266 // attributes
267 result.enable(Capability.NUMERIC_ATTRIBUTES);
268 result.enable(Capability.NOMINAL_ATTRIBUTES);
269 result.enable(Capability.MISSING_VALUES);
270
271 return result;
272 }
273
274 /**
275 * Parses a given list of options. <p/>
276 *
277 <!-- options-start -->
278 * Valid options are: <p/>
279 *
280 * <pre> -L < learning rate>
281 * The learning rate for the training algorithm.
282 * (Value should be greater than 0 and less or equal to 1, Default = 1).</pre>
283 *
284 * <pre> -T <number of training epochs>
285 * Number of training epochs.
286 * (Value should be greater than or equal to 1, Default = 1000).</pre>
287 *
288 * <pre> -C <number of clusters>
289 * The number of clusters.
290 * (Value should be > 0, Default = 2).</pre>
291 *
292 * <pre> -I
293 * Normalizing the attributes will NOT be done.
294 * (Set this to not normalize the attributes).</pre>
295 *
296 * <pre> -S
297 * Statistics will NOT be calculated after training.
298 * (Set this to not calculate statistics).</pre>
299 *
300 *
301 <!-- options-end -->
302 *
303 * @param options the list of options as an array of strings
304 * @throws Exception if an option is not supported
305 */
306 public void setOptions(String[] options) throws Exception {
307 //the defaults can be found here!!!!
308 String learningString = Utils.getOption('L', options);
309 if (learningString.length() != 0) {
310 setLearningRate((new Double(learningString)).doubleValue());
311 } else {
312 setLearningRate(1);
313 }
314 String epochsString = Utils.getOption('T', options);
315 if (epochsString.length() != 0) {
316 setEpochs(Integer.parseInt(epochsString));
317 } else {
318 setEpochs(1000);
319 }
320 String numOfClustersString = Utils.getOption('C', options);
321 if (numOfClustersString.length() != 0) {
322 setNumOfClusters(Integer.parseInt(numOfClustersString));
323 } else {
324 setNumOfClusters(2);
325 }
326 if (Utils.getFlag('I', options)) {
327 setNormalizeAttributes(false);
328 } else {
329 setNormalizeAttributes(true);
330 }
331 if (Utils.getFlag('S', options)) {
332 setCalcStats(false);
333 } else {
334 setCalcStats(true);
335 }
336 Utils.checkForRemainingOptions(options);
337 }
338
339 /**
340 * Gets the current settings of NeuralNet.
341 *
342 * @return an array of strings suitable for passing to setOptions()
343 */
344 public String[] getOptions() {
345
346 String[] options = new String[12];
347 int current = 0;
348 options[current++] = "-L";
349 options[current++] = "" + getLearningRate();
350 options[current++] = "-T";
351 options[current++] = "" + getEpochs();
352 options[current++] = "-C";
353 options[current++] = "" + getNumOfClusters();
354 if (!getNormalizeAttributes()) {
355 options[current++] = "-I";
356 }
357 if (!getCalcStats()) {
358 options[current++] = "-S";
359 }
360 while (current < options.length) {
361 options[current++] = "";
362 }
363 return options;
364 }
365
366 /**
367 * Classifies a given instance.
368 *
369 * @param i the instance to be assigned to a cluster
370 * @return the number of the assigned cluster as an interger
371 * if the class is enumerated, otherwise the predicted value
372 * @throws Exception if instance could not be classified
373 * successfully
374 */
375 public int clusterInstance(Instance i) throws Exception {
376 if ((m_clusters == null) || (m_instances == null)) {
377 return 0;
378 }
379
380 Instance instance = new DenseInstance(i);
381
382 if (m_normalizeAttributes) {
383 instance = normalizeInstance(instance);
384 }
385
386 return m_euclideanDistance.closestPoint(instance, m_clusters, m_clusterList);
387 }
388
389 /**
390 * Returns an enumeration describing the available options.
391 *
392 * @return an enumeration of all the available options.
393 */
394 public Enumeration listOptions() {
395 Vector result = new Vector();
396
397 result.addElement(new Option(
398 "\tLearning Rate for the training algorithm.\n"
399 + "\t(default 1)",
400 "L", 1, "-L <num>"));
401
402 result.addElement(new Option(
403 "\tNumber of training epochs.\n"
404 + "\t(default 1000)",
405 "T", 1, "-T <num>"));
406
407 result.addElement(new Option(
408 "\tNumber of clusters.\n"
409 + "\t(default 2)",
410 "C", 1, "-C <num>"));
411
412 result.addElement(new Option(
413 "\tNormalizing the attributes will NOT be done.\n"
414 + "\t(Set this to not normalize the attributes).",
415 "I", 0, "-I"));
416
417 return result.elements();
418 }
419
420 private String pad(String source, String padChar,
421 int length, boolean leftPad) {
422 StringBuffer temp = new StringBuffer();
423
424 if (leftPad) {
425 for (int i = 0; i < length; i++) {
426 temp.append(padChar);
427 }
428 temp.append(source);
429 } else {
430 temp.append(source);
431 for (int i = 0; i < length; i++) {
432 temp.append(padChar);
433 }
434 }
435 return temp.toString();
436 }
437
438 /**
439 * return a string describing this clusterer.
440 *
441 * @return a description of the clusterer as a string
442 */
443 public String toString() {
444 StringBuilder sb = new StringBuilder();
445 sb.append("\nLVQ\n==================\n");
446 if ((m_clusters == null) || (m_instances == null)) {
447 sb.append("No clusterer built yet!\n");
448 sb.append("==================\n\n");
449 return sb.toString();
450 }
451
452 sb.append("\nNumber of clusters: " + m_numOfClusters + "\n");
453
454 int maxWidth = 0;
455 int maxAttWidth = 0;
456
457 if (m_calcStats) {
458 // set up max widths
459 // attributes
460 for (int i = 0; i < m_clusters.numAttributes(); i++) {
461 Attribute a = m_clusters.attribute(i);
462 if (a.name().length() > maxAttWidth) {
463 maxAttWidth = m_clusters.attribute(i).name().length();
464 }
465 }
466 for (int i = 0; i < m_clusters.numInstances(); i++) {
467 for (int j = 0; j < m_clusters.numAttributes(); j++) {
468 // check mean and std. dev. against maxWidth
469 double mean = Math.log(Math.abs(m_clusterStats[j][i][2])) / Math.log(10.0);
470 double stdD = Math.log(Math.abs(m_clusterStats[j][i][3])) / Math.log(10.0);
471 double width = (mean > stdD)
472 ? mean
473 : stdD;
474 if (width < 0) {
475 width = 1;
476 }
477 // decimal + # decimal places + 1
478 width += 6.0;
479 if ((int) width > maxWidth) {
480 maxWidth = (int) width;
481 }
482 }
483 }
484
485 if (maxAttWidth < "Attribute".length()) {
486 maxAttWidth = "Attribute".length();
487 }
488
489 maxAttWidth += 2;
490
491 sb.append("\n\n");
492 sb.append(pad("Cluster", " ",
493 (maxAttWidth + maxWidth + 1) - "Cluster".length(),
494 true));
495
496 sb.append("\n");
497 sb.append(pad("Attribute", " ", maxAttWidth - "Attribute".length(), false));
498
499 // cluster #'s
500 for (int i = 0; i < m_clusters.numInstances(); i++) {
501 String classL = "" + i;
502 sb.append(pad(classL, " ", maxWidth + 1 - classL.length(), true));
503 }
504 sb.append("\n");
505
506 sb.append(pad("", " ", maxAttWidth, true));
507 for (int i = 0; i < m_clusters.numInstances(); i++) {
508 String numInst = Utils.doubleToString(m_clusterInstances[i].numInstances(), maxWidth, 2).trim();
509 numInst = "(" + numInst + ")";
510 sb.append(pad(numInst, " ", maxWidth + 1 - numInst.length(), true));
511 }
512
513 sb.append("\n");
514 sb.append(pad("", "=", maxAttWidth
515 + (maxWidth * m_clusters.numInstances())
516 + m_clusters.numInstances() + 1, true));
517 sb.append("\n");
518
519 for (int i = 0; i < m_clusters.numAttributes(); i++) {
520 String attName = m_clusters.attribute(i).name();
521 sb.append(attName + "\n");
522
523 String valueL = " value";
524 sb.append(pad(valueL, " ", maxAttWidth + 1 - valueL.length(), false));
525 for (int j = 0; j < m_clusters.numInstances(); j++) {
526 // values
527 String value =
528 Utils.doubleToString(denormalizeInstance(m_clusters.get(j)).value(i), maxWidth, 4).trim();
529 sb.append(pad(value, " ", maxWidth + 1 - value.length(), true));
530 }
531 sb.append("\n");
532 String minL = " min";
533 sb.append(pad(minL, " ", maxAttWidth + 1 - minL.length(), false));
534 for (int j = 0; j < m_clusters.numInstances(); j++) {
535 // means
536 String min =
537 Utils.doubleToString(m_clusterStats[i][j][0], maxWidth, 4).trim();
538 sb.append(pad(min, " ", maxWidth + 1 - min.length(), true));
539 }
540 sb.append("\n");
541 String maxL = " max";
542 sb.append(pad(maxL, " ", maxAttWidth + 1 - maxL.length(), false));
543 for (int j = 0; j < m_clusters.numInstances(); j++) {
544 // means
545 String max =
546 Utils.doubleToString(m_clusterStats[i][j][1], maxWidth, 4).trim();
547 sb.append(pad(max, " ", maxWidth + 1 - max.length(), true));
548 }
549 sb.append("\n");
550 String meanL = " mean";
551 sb.append(pad(meanL, " ", maxAttWidth + 1 - meanL.length(), false));
552 for (int j = 0; j < m_clusters.numInstances(); j++) {
553 // means
554 String mean =
555 Utils.doubleToString(m_clusterStats[i][j][2], maxWidth, 4).trim();
556 sb.append(pad(mean, " ", maxWidth + 1 - mean.length(), true));
557 }
558 sb.append("\n");
559 // now do std deviations
560 String stdDevL = " std. dev.";
561 sb.append(pad(stdDevL, " ", maxAttWidth + 1 - stdDevL.length(), false));
562 for (int j = 0; j < m_clusters.numInstances(); j++) {
563 String stdDev =
564 Utils.doubleToString(m_clusterStats[i][j][3], maxWidth, 4).trim();
565 sb.append(pad(stdDev, " ", maxWidth + 1 - stdDev.length(), true));
566 }
567 sb.append("\n\n");
568 }
569 }
570
571 return sb.toString();
572 }
573
574 /**
575 * Generates a clusterer. Has to initialize all fields of the clusterer
576 * that are not being set via options.
577 *
578 * @param data set of instances serving as training data
579 * @throws Exception if the clusterer has not been
580 * generated successfully
581 */
582 public void buildClusterer(Instances data) throws Exception {
583 // can clusterer handle the data?
584 getCapabilities().testWithFail(data);
585
586 // copy the original instances
587 m_instances = new Instances(data);
588
589 // normalize instances
590 m_instances = normalize(m_instances);
591
592 // init clusters
593 m_clusters = initClusters();
594
595 // init the pointList (used in EuclideanDistance.closestPoint)
596 m_clusterList = new int[m_clusters.numInstances()];
597 for (int i = 0; i < m_clusterList.length; i++) {
598 m_clusterList[i] = i;
599 }
600
601 // init euclidean distance
602 m_euclideanDistance.setDontNormalize(true);
603 m_euclideanDistance.setInstances(m_clusters);
604 // the winner neuron
605 int winningNeuron;
606
607 for (int epoch = 1; epoch <= m_epochs; epoch++) {
608 for (int instance = 0; instance < m_instances.numInstances(); instance++) {
609 winningNeuron = m_euclideanDistance.closestPoint(m_instances.get(instance), m_clusters, m_clusterList);
610
611 // update the weights
612 for (int j = 0; j < m_clusters.numAttributes(); j++) {
613 double diff = m_learningRate * (m_instances.get(instance).value(j) - m_clusters.get(winningNeuron).value(j));
614 if (!Double.isNaN(diff)) {
615 m_clusters.get(winningNeuron).setValue(j, m_clusters.get(winningNeuron).value(j) + diff);
616 }
617 }
618 }
619 }
620
621 if (m_calcStats) {
622 calcStatistics();
623 }
624 }
625
626 /**
627 * This function calculates the clusterer's statistics
628 */
629 private void calcStatistics() {
630 // init cluster statistics
631 m_clusterStats = new double[m_instances.numAttributes()][m_numOfClusters][4];
632
633 // init clusters assignements
634 m_clusterInstances = new Instances[m_numOfClusters];
635
636 // keep cluster's attributes
637 for (int i = 0; i < m_clusters.numInstances(); i++) {
638 m_clusterInstances[i] = new Instances(m_instances);
639 m_clusterInstances[i].clear();
640
641 try {
642 Instances clusters = getClusters();
643 for (int j = 0; j < clusters.numAttributes(); j++) {
644 m_clusterStats[j][i][0] = clusters.get(i).value(j);
645 }
646 } catch (Exception ex) {
647 Logger.getLogger(LVQ.class.getName()).log(Level.SEVERE, null, ex);
648 }
649
650 }
651
652 // get instances in each class
653 for (int instance = 0; instance < m_instances.numInstances(); instance++) {
654 Instance inst = m_instances.get(instance);
655 int cluster = 0;
656 try {
657 cluster = clusterInstance(denormalizeInstance(inst));
658 } catch (Exception ex) {
659 }
660 m_clusterInstances[cluster].add(denormalizeInstance(inst));
661 }
662
663 //calc min, max, mean, stdev for each cluster
664 for (int cluster = 0; cluster < m_clusters.numInstances(); cluster++) {
665 for (int attr = 0; attr < m_clusters.numAttributes(); attr++) {
666 int unknownValues = 0;
667 double min = Double.POSITIVE_INFINITY;
668 double max = Double.NEGATIVE_INFINITY;
669 double mean = 0;
670 double stdev = 0;
671 for (int instance = 0; instance < m_clusterInstances[cluster].numInstances(); instance++) {
672 Instance inst = m_clusterInstances[cluster].get(instance);
673 if (!Double.isNaN(inst.value(attr))) {
674 mean += inst.value(attr);
675 if (inst.value(attr) < min) {
676 min = inst.value(attr);
677 }
678 if (inst.value(attr) > max) {
679 max = inst.value(attr);
680 }
681 } else {
682 unknownValues++;
683 }
684
685 }
686 mean /= (m_clusterInstances[cluster].numInstances() - unknownValues);
687 for (int instance = 0; instance < m_clusterInstances[cluster].numInstances(); instance++) {
688 Instance inst = m_clusterInstances[cluster].get(instance);
689 if (!Double.isNaN(inst.value(attr))) {
690 stdev += (inst.value(attr) - mean) * (inst.value(attr) - mean);
691 }
692 }
693 stdev /= (m_clusterInstances[cluster].numInstances() - 1 - unknownValues);
694 stdev = Math.sqrt(stdev);
695 if (min == Double.POSITIVE_INFINITY) {
696 min = 0;
697 }
698 if (max == Double.NEGATIVE_INFINITY) {
699 max = 0;
700 }
701 if((m_clusterInstances[cluster].numInstances() - unknownValues)==0) {
702 min = Double.NaN;
703 max = Double.NaN;
704 stdev = Double.NaN;
705 }
706
707 m_clusterStats[attr][cluster][0] = min;
708 m_clusterStats[attr][cluster][1] = max;
709 m_clusterStats[attr][cluster][2] = mean;
710 m_clusterStats[attr][cluster][3] = stdev;
711 }
712 }
713 }
714
715 /**
716 * This function performs the denormalization of the attributes of an instance.
717 *
718 * @param inst the instance.
719 * @return The modified instance. This needs to be done as it deep copies
720 * the instance which will need to be passed back out.
721 */
722 protected Instance denormalizeInstance(Instance inst) {
723 inst = new DenseInstance(inst);
724 if (m_normalizeAttributes) {
725 for (int noa = 0; noa < inst.numAttributes(); noa++) {
726 inst.setValue(noa, (inst.value(noa) * (m_attributeMax[noa] - m_attributeMin[noa]) + (m_attributeMax[noa] + m_attributeMin[noa])) / 2);
727 }
728 }
729 return inst;
730 }
731
732 /**
733 * This function performs the normalization of the attributes of an instance.
734 *
735 * @param inst the instance.
736 * @return The modified instance. This needs to be done as it deep copies
737 * the instance which will need to be passed back out.
738 */
739 protected Instance normalizeInstance(Instance inst) {
740 inst = new DenseInstance(inst);
741 double min;
742 double max;
743 for (int noa = 0; noa < m_instances.numAttributes(); noa++) {
744 if (inst.value(noa) > m_attributeMax[noa]) {
745 max = inst.value(noa);
746 } else {
747 max = m_attributeMax[noa];
748 }
749 if (inst.value(noa) < m_attributeMin[noa]) {
750 min = inst.value(noa);
751 } else {
752 min = m_attributeMin[noa];
753 }
754 if ((max - min) != 0) {
755 inst.setValue(noa, -1 + 2 * (inst.value(noa) - min) / (max - min));
756 } else {
757 inst.setValue(noa, inst.value(noa));
758 }
759 }
760 return inst;
761 }
762
763 /**
764 * This function performs the normalization of the attributes if applicable.
765 * (note that regardless of the options it will fill an array with the range
766 * and base, set to normalize all attributes and the class to be between -1
767 * and 1)
768 * @param inst the instances.
769 * @return The modified instances. This needs to be done. If the attributes
770 * are normalized then deep copies will be made of all the instances which
771 * will need to be passed back out.
772 */
773 private Instances normalize(Instances inst) throws Exception {
774 if (inst != null) {
775 inst = new Instances(inst);
776 // x bounds
777 double min = Double.POSITIVE_INFINITY;
778 double max = Double.NEGATIVE_INFINITY;
779 double value;
780 m_attributeMax = new double[inst.numAttributes()];
781 m_attributeMin = new double[inst.numAttributes()];
782
783 for (int noa = 0; noa < inst.numAttributes(); noa++) {
784 min = Double.POSITIVE_INFINITY;
785 max = Double.NEGATIVE_INFINITY;
786 for (int i = 0; i < inst.numInstances(); i++) {
787 if (!inst.instance(i).isMissing(noa)) {
788 value = inst.instance(i).value(noa);
789 if (value < min) {
790 min = value;
791 }
792 if (value > max) {
793 max = value;
794 }
795 }
796 }
797
798 m_attributeMax[noa] = max;
799 m_attributeMin[noa] = min;
800 }
801 }
802
803 if (m_normalizeAttributes) {
804 for (int i = 0; i < inst.numInstances(); i++) {
805 inst.set(i, normalizeInstance(inst.instance(i)));
806 }
807 }
808
809 return inst;
810 }
811
812 /**
813 * This function initializes the clusters' weights.
814 *
815 * @return The initialized clusters
816 */
817 protected Instances initClusters() {
818 Instances weights = new Instances(m_instances, m_numOfClusters);
819 for (int i = 0; i < m_numOfClusters; i++) {
820 double[] instValues = new double[m_instances.numAttributes()];
821 for (int j = 0; j < m_instances.numAttributes(); j++) {
822 if (m_normalizeAttributes) {
823 instValues[j] = 0;
824 } else {
825 instValues[j] = (m_attributeMax[j] + m_attributeMin[j]) / 2;
826 }
827 }
828 Instance inst = new DenseInstance(1, instValues);
829 weights.add(i, inst);
830 }
831
832 return weights;
833 }
834
835 /**
836 * Returns the number of clusters.
837 *
838 * @return the number of clusters generated for a training dataset.
839 * @exception Exception if number of clusters could not be returned
840 * successfully
841 */
842 public int numberOfClusters() throws Exception {
843 return m_numOfClusters;
844 }
845
846 /**
847 * This function returns the clusters if the clusterer is build
848 * or an exception if the clusterer is not build.
849 *
850 * The clusters are returned dernomalized even if normalizeAttributes
851 * option is set.
852 *
853 * @return The clusters
854 * @throws Exception
855 */
856 public Instances getClusters() throws Exception {
857 if (m_clusters == null) {
858 throw new Exception("No clusterer built yet!");
859 }
860 Instances inst = new Instances(m_clusters);
861 if (m_normalizeAttributes) {
862 for (int i = 0; i < inst.numInstances(); i++) {
863 inst.set(i, denormalizeInstance(inst.instance(i)));
864 }
865 }
866
867 return inst;
868 }
869
870 /**
871 * This function returns the training statistics in a 3-dimension array
872 * as follows:<br/>
873 * First dimension: the attribute index<br/>
874 * Second dimension: the cluster index<br/>
875 * Third dimension: the static index (Valid values are 0: min, 1: max, 2: mean, 3: st. dev.)
876 *
877 * @return the statistics array
878 * @throws Exception
879 */
880 public double[][][] getStatistics() throws Exception {
881 if (m_calcStats) {
882 if (m_clusterStats == null) {
883 throw new Exception("No clusterer built yet!");
884 }
885 } else {
886 throw new Exception("Statistics are not calculated");
887 }
888 return m_clusterStats;
889 }
890
891 /**
892 * This function returns the cluster assignment for each of the
893 * training instances. The array's index indicates the corresponding
894 * cluster.
895 *
896 * @return the cluster assignments array
897 * @throws Exception
898 */
899 public Instances[] getClusterInstances() throws Exception {
900 if (m_calcStats) {
901 if (m_clusterInstances == null) {
902 throw new Exception("No clusterer built yet!");
903 }
904 } else {
905 throw new Exception("Statistics are not calculated");
906 }
907 return m_clusterInstances;
908 }
909}