dtrees_rs/algorithms/optimal/depth2/
info_gain_maximizer.rs1use crate::algorithms::common::errors::{ErrorWrapper, NativeError};
2use crate::algorithms::common::heuristics::helpers::{entropy, information_gain};
3use crate::algorithms::common::heuristics::{Heuristic, InformationGain};
4use crate::algorithms::common::types::FitError;
5use crate::algorithms::common::utils::{
6 build_labels_count_distribution_matrix, deduce_sibling_error,
7};
8use crate::algorithms::optimal::depth2::OptimalDepth2Tree;
9use crate::cover::Cover;
10use crate::globals::{float_is_null, get_tree_root_error, item};
11use crate::tree::Tree;
12
13pub struct InfoGainMaximizer<E>
16where
17 E: ErrorWrapper,
18{
19 error_fn: Box<E>,
20 heuristic_fn: InformationGain,
21}
22
23impl Default for InfoGainMaximizer<NativeError> {
24 fn default() -> Self {
25 Self {
26 error_fn: Box::<NativeError>::default(),
27 heuristic_fn: InformationGain,
28 }
29 }
30}
31
32impl<E> OptimalDepth2Tree for InfoGainMaximizer<E>
33where
34 E: ErrorWrapper,
35{
36 fn find_optimal_depth_one_tree(
37 &self,
38 min_sup: usize,
39 cover: &mut Cover,
40 provided_candidates: Option<&[usize]>,
41 ) -> Result<Tree, FitError> {
42 let mut candidates = self.get_candidates(cover, min_sup, provided_candidates);
43 if candidates.is_empty() {
44 return Err(FitError::EmptyCandidates);
45 }
46
47 if let Some(best_attr) = self.find_best_attribute(cover, &mut candidates) {
48 let root_distribution = cover.labels_count();
49 cover.branch_on(item(best_attr, 0));
50 let left_distribution = cover.labels_count();
51 cover.backtrack();
52 let right_distribution = deduce_sibling_error(&root_distribution, &left_distribution);
53
54 let left_error = self.error_fn.compute(&left_distribution);
55 let right_error = self.error_fn.compute(&right_distribution);
56
57 let mut tree = Tree::empty_tree(1);
58
59 let (left, right) = tree.node_children(tree.get_root_index());
60
61 tree.update_leaf_node(left, left_error);
62 tree.update_leaf_node(right, right_error);
63
64 tree.update_root()
65 .map(|updater| updater.test(best_attr).error(left_error.0 + right_error.0));
66
67 if tree.root_error().is_infinite() {
68 return Err(FitError::EmptyTree);
69 }
70 return Ok(tree);
71 }
72
73 Err(FitError::EmptyTree)
74 }
75
76 fn find_optimal_depth_two_tree(
77 &self,
78 min_sup: usize,
79 cover: &mut Cover,
80 provided_candidates: Option<&[usize]>,
81 ) -> Result<Tree, FitError> {
82 let candidates = self.get_candidates(cover, min_sup, provided_candidates);
83 if candidates.is_empty() {
84 return Err(FitError::EmptyCandidates);
85 }
86 if candidates.len() < 2 {
87 return self.find_optimal_depth_one_tree(min_sup, cover, provided_candidates);
88 }
89
90 let matrix = build_labels_count_distribution_matrix(cover, &candidates);
91
92 let root_distribution = cover.labels_count();
93
94 let parent_entropy = entropy(&root_distribution);
95
96 if float_is_null(parent_entropy) {
97 return self.find_optimal_depth_one_tree(min_sup, cover, provided_candidates);
98 }
99
100 let mut best_tree = Tree::empty_tree(2);
101
102 for (i, &first_attr) in candidates.iter().enumerate() {
103 let mut candidate_tree = Tree::empty_tree(2);
104
105 let (left_index, right_index) = candidate_tree
106 .update_root()
107 .map_or((0, 0), |updater| updater.test(first_attr).get_children());
108
109 for (j, &second_attr) in candidates.iter().enumerate() {
110 if i == j {
111 continue;
112 }
113
114 let mut root_error = 0.0;
115 let mut root_gain = 0f64;
116
117 for &val in [0usize, 1].iter() {
118 let left_leaf_distribution = Self::deduce_leaves_classes_support(
119 &matrix,
120 (i, val),
121 (j, 0),
122 &root_distribution,
123 );
124 let right_leaf_distribution = Self::deduce_leaves_classes_support(
125 &matrix,
126 (i, val),
127 (j, 1),
128 &root_distribution,
129 );
130
131 let branch_index = if val == 0 { left_index } else { right_index };
135
136 let branch_gain = information_gain(
137 &root_distribution,
138 &left_leaf_distribution,
139 &right_leaf_distribution,
140 parent_entropy,
141 );
142
143 let left_leaf_error = self.error_fn.compute(&left_leaf_distribution);
144 let right_leaf_error = self.error_fn.compute(&right_leaf_distribution);
145
146 let stored_gain = candidate_tree.node_metric(branch_index).unwrap_or(0.0);
147
148 if branch_gain > stored_gain {
149 let (left_leaf, righ_leaf) = candidate_tree
150 .update_node(branch_index)
151 .map_or((0, 0), |updater| {
152 updater
153 .test(second_attr)
154 .error(left_leaf_error.0 + right_leaf_error.0)
155 .metric(branch_gain)
156 .get_children()
157 });
158
159 candidate_tree.update_leaf_node(left_leaf, left_leaf_error);
160 candidate_tree.update_leaf_node(righ_leaf, right_leaf_error);
161 }
162
163 root_error += candidate_tree.node_error(branch_index);
164 root_gain += candidate_tree.node_metric(branch_index).unwrap_or(0.0);
165 }
166
167 candidate_tree
168 .update_root()
169 .map(|updater| updater.error(root_error).metric(root_gain));
170
171 if float_is_null(root_error) {
172 break;
173 }
174 }
175
176 if candidate_tree
177 .node_metric(candidate_tree.get_root_index())
178 .unwrap_or(0.0)
179 > best_tree
180 .node_metric(best_tree.get_root_index())
181 .unwrap_or(0.0)
182 {
183 best_tree = candidate_tree
184 }
185
186 if float_is_null(get_tree_root_error(&best_tree)) {
187 break;
188 }
189 }
190
191 if best_tree.root_error().is_infinite() {
192 return Err(FitError::EmptyTree);
193 }
194
195 Ok(best_tree)
196 }
197
198 fn error(&self, distribution: &[usize]) -> (f64, f64) {
199 self.error_fn.compute(distribution)
200 }
201}
202
203impl<E> InfoGainMaximizer<E>
204where
205 E: ErrorWrapper,
206{
207 fn find_best_attribute(&self, cover: &mut Cover, candidates: &mut Vec<usize>) -> Option<usize> {
208 if candidates.is_empty() {
209 return None;
210 }
211 self.heuristic_fn.compute(cover, candidates);
212 candidates.first().copied()
213 }
214
215 fn deduce_leaves_classes_support(
219 matrix: &[Vec<Vec<usize>>],
220 first: (usize, usize),
221 second: (usize, usize),
222 root_classes_support: &[usize],
223 ) -> Vec<usize> {
224 let (attr1, is_left1) = (first.0, first.1 == 0);
225 let (attr2, is_left2) = (second.0, second.1 == 0);
226
227 let attr1_right_dist = &matrix[attr1][attr1];
228 let attr1_right_attr2_right_dist = &matrix[attr1][attr2];
229
230 match (is_left1, is_left2) {
231 (true, true) => {
232 let attr1_left_dist = deduce_sibling_error(root_classes_support, attr1_right_dist);
233 let attr1_left_attr2_right_dist =
234 deduce_sibling_error(&matrix[attr2][attr2], attr1_right_attr2_right_dist);
235 deduce_sibling_error(&attr1_left_dist, &attr1_left_attr2_right_dist)
236 }
237 (true, false) => {
238 deduce_sibling_error(&matrix[attr2][attr2], attr1_right_attr2_right_dist)
239 }
240 (false, true) => deduce_sibling_error(attr1_right_dist, attr1_right_attr2_right_dist),
241 (false, false) => attr1_right_attr2_right_dist.to_vec(),
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use crate::algorithms::optimal::depth2::info_gain_maximizer::InfoGainMaximizer;
249 use crate::algorithms::optimal::depth2::OptimalDepth2Tree;
250 use crate::reader::data_reader::DataReader;
251 use std::path::Path;
252
253 #[test]
254 fn run_small_data() {
255 let reader = DataReader::default();
256 let path = Path::new("test_data/anneal.txt");
257 let cover_result = reader.read_file(path);
258
259 let mut cover = cover_result.expect("the test data is readable");
260
261 let info_gain_maximizer = InfoGainMaximizer::default();
262 let tree = info_gain_maximizer.fit(1, 2, &mut cover, None);
263
264 if let Ok(t) = tree {
265 println!("Error {}", t.root_error());
266 println!("{}", t)
267 }
268 }
269
270 #[test]
271 fn the_depth_one_tree_has_a_test_and_two_leaves() {
272 let mut cover = DataReader::default()
273 .read_file(Path::new("test_data/anneal.txt"))
274 .expect("the test data is readable");
275
276 let tree = InfoGainMaximizer::default()
277 .fit(1, 1, &mut cover, None)
278 .expect("a split exists");
279
280 assert_eq!(tree.len(), 3);
281 assert!(tree.root_test().is_some());
282 let (left, right) = tree.node_children(tree.get_root_index());
283 assert_eq!(
284 tree.root_error(),
285 tree.node_error(left) + tree.node_error(right)
286 );
287 assert!(tree.root_error() < cover.count() as f64);
288 }
289}