Skip to main content

dtrees_rs/algorithms/optimal/depth2/
info_gain_maximizer.rs

1use 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
13/// Depth-2 solver that maximises the information gain of the tree rather
14/// than minimising its error. Used by LGDT for a less greedy lookahead.
15pub 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                    // Each branch's gain is weighted by the branch's own class
132                    // counts, not by the parent's support.
133
134                    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    /// Class counts of the leaf reached by branch `first.1` of feature
216    /// `first.0` then branch `second.1` of feature `second.0` (0 is left),
217    /// deduced from the pairwise count matrix.
218    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}