Skip to content

Commit 987a05d

Browse files
FastNominalAttributeClassObserver implemented
* improves performance for nominal attributes with large number of attribute values, it simply calculates distribution on the fly to avoid loops in getClassDistsResultingFrom* and probabilityOfAttributeValueGivenClass methods.
1 parent 5a1d26b commit 987a05d

1 file changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* NominalAttributeClassObserver.java
3+
* Copyright (C) 2007 University of Waikato, Hamilton, New Zealand
4+
* @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
5+
*
6+
* This program is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
*/
20+
package moa.classifiers.core.attributeclassobservers;
21+
22+
import moa.classifiers.core.AttributeSplitSuggestion;
23+
import moa.classifiers.core.conditionaltests.NominalAttributeBinaryTest;
24+
import moa.classifiers.core.conditionaltests.NominalAttributeMultiwayTest;
25+
import moa.classifiers.core.splitcriteria.SplitCriterion;
26+
import moa.core.DoubleVector;
27+
import moa.core.ObjectRepository;
28+
import moa.core.Utils;
29+
import moa.options.AbstractOptionHandler;
30+
import moa.tasks.TaskMonitor;
31+
32+
import java.util.HashMap;
33+
import java.util.Map;
34+
35+
36+
/**
37+
* Class for observing the class data distribution for a nominal attribute.
38+
* This observer monitors the class distribution of a given attribute.
39+
* Used in naive Bayes and decision trees to monitor data statistics on leaves.
40+
*
41+
* @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
42+
* @author Eugene Kamenev (eugene.kamenev@gmail.com)
43+
* @version $Revision: 7 $
44+
*/
45+
public class FastNominalAttributeClassObserver extends AbstractOptionHandler implements DiscreteAttributeClassObserver {
46+
47+
private static final long serialVersionUID = 1L;
48+
49+
protected double totalWeightObserved = 0.0;
50+
51+
protected double missingWeightObserved = 0.0;
52+
53+
protected Map<Integer, Map<Integer, Double>> attValDistPerClassCount = new HashMap<>();
54+
55+
protected Map<Integer, Double> classTotalCount = new HashMap<>();
56+
57+
protected Map<Integer, Integer> maxAttrValue = new HashMap<>();
58+
59+
@Override
60+
public void observeAttributeClass(double attVal, int classVal, double weight) {
61+
if (Utils.isMissingValue(attVal)) {
62+
this.missingWeightObserved += weight;
63+
} else {
64+
int attValInt = (int) attVal;
65+
Map<Integer, Double> valDistCount = this.attValDistPerClassCount.computeIfAbsent(classVal, k -> new HashMap<>());
66+
// update distribution count
67+
valDistCount.put(attValInt, valDistCount.getOrDefault(attValInt, 0.0) + weight);
68+
Integer maxValue = this.maxAttrValue.get(classVal);
69+
if (maxValue == null || attVal > maxValue) {
70+
// update max attribute value
71+
this.maxAttrValue.put(classVal, attValInt);
72+
}
73+
// update the total count for the class
74+
this.classTotalCount.put(classVal, this.classTotalCount.getOrDefault(classVal, 0.0) + weight);
75+
}
76+
this.totalWeightObserved += weight;
77+
}
78+
79+
@Override
80+
public double probabilityOfAttributeValueGivenClass(double attVal, int classVal) {
81+
Map<Integer, Double> obs = this.attValDistPerClassCount.get(classVal);
82+
Double sumCounts = this.classTotalCount.getOrDefault(classVal, 0.0);
83+
Integer max = this.maxAttrValue.getOrDefault(classVal, (int) attVal) + 1;
84+
return obs != null ? (obs.getOrDefault((int) attVal, 0.0) + 1.0) / (sumCounts + max) : 0.0;
85+
}
86+
87+
public double[][] getClassDistsResultingFromMultiwaySplit(int maxAttValsObserved) {
88+
DoubleVector[] resultingDists = new DoubleVector[maxAttValsObserved];
89+
for (int i = 0; i < resultingDists.length; i++) {
90+
resultingDists[i] = new DoubleVector();
91+
}
92+
for (Map.Entry<Integer, Map<Integer, Double>> entry : this.attValDistPerClassCount.entrySet()) {
93+
int classVal = entry.getKey();
94+
Map<Integer, Double> attValDistCount = entry.getValue();
95+
for (int j = 0; j < maxAttValsObserved; j++) {
96+
resultingDists[j].addToValue(classVal, attValDistCount.getOrDefault(j, 0.0));
97+
}
98+
}
99+
double[][] distributions = new double[maxAttValsObserved][];
100+
for (int i = 0; i < distributions.length; i++) {
101+
distributions[i] = resultingDists[i].getArrayRef();
102+
}
103+
return distributions;
104+
}
105+
106+
public double[][] getClassDistsResultingFromBinarySplit(int valIndex) {
107+
DoubleVector equalsDist = new DoubleVector();
108+
DoubleVector notEqualDist = new DoubleVector();
109+
for (Map.Entry<Integer, Map<Integer, Double>> entry : this.attValDistPerClassCount.entrySet()) {
110+
int classVal = entry.getKey();
111+
Map<Integer, Double> attValDistCount = entry.getValue();
112+
double count = attValDistCount.getOrDefault(valIndex, 0.0);
113+
equalsDist.addToValue(classVal, count);
114+
notEqualDist.addToValue(classVal, this.classTotalCount.get(classVal) - count);
115+
}
116+
return new double[][]{equalsDist.getArrayRef(),
117+
notEqualDist.getArrayRef()};
118+
}
119+
120+
@Override
121+
public AttributeSplitSuggestion getBestEvaluatedSplitSuggestion(SplitCriterion criterion, double[] preSplitDist,
122+
int attIndex, boolean binaryOnly) {
123+
AttributeSplitSuggestion bestSuggestion = null;
124+
int maxAttValsObserved = 0;
125+
for (Integer max : this.maxAttrValue.values()) {
126+
if (max > maxAttValsObserved) {
127+
maxAttValsObserved = max + 1;
128+
}
129+
}
130+
if (!binaryOnly) {
131+
double[][] postSplitDists = getClassDistsResultingFromMultiwaySplit(maxAttValsObserved);
132+
double merit = criterion.getMeritOfSplit(preSplitDist,
133+
postSplitDists);
134+
bestSuggestion = new AttributeSplitSuggestion(
135+
new NominalAttributeMultiwayTest(attIndex), postSplitDists,
136+
merit);
137+
}
138+
for (int valIndex = 0; valIndex < maxAttValsObserved; valIndex++) {
139+
double[][] postSplitDists = getClassDistsResultingFromBinarySplit(valIndex);
140+
double merit = criterion.getMeritOfSplit(preSplitDist,
141+
postSplitDists);
142+
if ((bestSuggestion == null) || (merit > bestSuggestion.merit)) {
143+
bestSuggestion = new AttributeSplitSuggestion(
144+
new NominalAttributeBinaryTest(attIndex, valIndex),
145+
postSplitDists, merit);
146+
}
147+
}
148+
return bestSuggestion;
149+
}
150+
151+
@Override
152+
public void observeAttributeTarget(double v, double v1) {
153+
throw new UnsupportedOperationException("Not supported yet.");
154+
}
155+
156+
@Override
157+
protected void prepareForUseImpl(TaskMonitor monitor, ObjectRepository repository) {
158+
// TODO Auto-generated method stub
159+
}
160+
161+
@Override
162+
public void getDescription(StringBuilder sb, int indent) {
163+
// TODO Auto-generated method stub
164+
}
165+
166+
public double totalWeightOfClassObservations() {
167+
return this.totalWeightObserved;
168+
}
169+
170+
public double weightOfObservedMissingValues() {
171+
return this.missingWeightObserved;
172+
}
173+
}

0 commit comments

Comments
 (0)