Feature Selection Toolbox FST4
UTIA · Czech Academy of Sciences
Loading...
Searching...
No Matches
classifier_rf.hpp
Go to the documentation of this file.
1#ifndef FSTCLASSIFIERRF_H
2#define FSTCLASSIFIERRF_H
3 /*
25=========================================================================
26Copyright and license:
27 * FST4 software (with the exception of any bundled or externally linked
28 third-party components) is Copyright (C) 2000-present Institute of
29 Information Theory and Automation of the Czech Academy of Sciences (UTIA).
30 * FST4 is free software: you can redistribute it and/or modify it under
31 the terms of the GNU Affero General Public License as published by the
32 Free Software Foundation, version 3. The full text of the license is in
33 the file LICENSE in the distribution root and at
34 https://www.gnu.org/licenses/agpl-3.0.html.
35 * FST4 is distributed in the hope that it will be useful, but WITHOUT ANY
36 WARRANTY; without even the implied warranty of MERCHANTABILITY or
37 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
38 License for more details.
39 * Closed-source use without the AGPL's copyleft obligations requires a
40 commercial license from UTIA; in case of interest write to
41 utia@utia.cas.cz. LICENSING.md in the distribution root explains both
42 options, the contributor license agreement that every contribution
43 requires, and the contacts (technical questions and pull requests:
44 somol@utia.cas.cz, with "FST" in the subject line).
45========================================================================== */
46
47#include <memory>
48#include <vector>
49#include <algorithm>
50#include <cmath>
51#include <cstdint>
52#include <cassert>
53#include <iostream>
54#include "error.hpp"
55#include "global.hpp"
56#include "clonable.hpp"
57#include "classifier.hpp"
58
59/*============== Template parameter type naming conventions ==============
60--------- Numeric types: -------------------------------------------------
61DATATYPE - data sample values - usually real numbers (but may be integers
62 in text processing etc.)
63REALTYPE - must be real numbers - for representing intermediate results of
64 calculations like mean, covariance etc.
65IDXTYPE - index values for enumeration of data samples - (nonnegative) integers,
66 extent depends on numbers of samples in data
67DIMTYPE - index values for enumeration of features (dimensions), or classes (not
68 class sizes) - (nonnegative) integers, usually lower extent than IDXTYPE,
69 but be aware of expressions like _classes*_features*_features !
70 in linearized representations of feature matrices for all classes
71BINTYPE - feature selection marker type - represents ca. <10 different feature
72 states (selected, deselected, sel./desel. temporarily 1st nested loop, 2nd...)
73RETURNTYPE - criterion value: real value, but may be extended in future to support
74 multiple values
75--------- Class types: ---------------------------------------------------
76SUBSET - class of class type Subset
77CLASSIFIER - class implementing interface defined in abstract class Classifier
78EVALUATOR - class implementing interface defined in abstract class Sequential_Step
79DISTANCE - class implementing interface defined in abstract class Distance
80DATAACCESSOR - class implementing interface defined in abstract class Data_Accessor
81INTERVALCONTAINER - class of class type Data_Intervaller
82CONTAINER - STL container of class type Data_Interval
83========================================================================== */
84
85namespace FST {
86
110template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
111class Classifier_Random_Forest : public Clonable_Impl<Classifier_Random_Forest<RETURNTYPE,DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>,Classifier<RETURNTYPE,DIMTYPE,SUBSET,DATAACCESSOR> > {
112public:
115 typedef std::shared_ptr<DATAACCESSOR> PDataAccessor;
116 typedef std::shared_ptr<SUBSET> PSubset;
117 typedef typename DATAACCESSOR::PPattern PPattern;
118 Classifier_Random_Forest() : _tree_count(100), _mtry(0), _max_depth(0), _min_leaf(1), _seed_salt(1),
119 _classes(0), _features(0), _subfeatures(0) {notify("Classifier_Random_Forest constructor.");}
120 virtual ~Classifier_Random_Forest() {notify("Classifier_Random_Forest destructor.");}
121
122 void set_tree_count(const unsigned int t) {assert(t>0); _tree_count=t;}
123 void set_mtry(const DIMTYPE m) {_mtry=m;}
124 void set_max_depth(const unsigned int dp) {_max_depth=dp;}
125 void set_min_leaf(const IDXTYPE ml) {assert(ml>0); _min_leaf=ml;}
126 void set_seed_salt(const unsigned int s) {_seed_salt=s;}
127
128 void initialize(const PDataAccessor da);
129
130 virtual bool classify(DIMTYPE &cls, const PPattern &pattern) override; // majority tree vote; ties resolve to the lowest class index
131 virtual bool classify_scores(std::vector<RETURNTYPE> &scores, const PPattern &pattern) override; // per-class tree-vote fractions for ranking metrics (AUC)
140 virtual bool train(const PDataAccessor da, const PSubset sub) override; // grows the forest on the designated training part
141 // test() is the accuracy loop inherited from the Classifier base
142
143 virtual std::ostream& print(std::ostream& os) const override
144 {os << "Classifier_Random_Forest(trees=" << _tree_count << ", mtry=" << _mtry << ", max_depth=" << _max_depth << ", min_leaf=" << _min_leaf << ")"; return os;}
145protected:
149 struct Node {
150 DIMTYPE feature;
151 REALTYPE threshold;
152 long left;
153 long right;
154 DIMTYPE leaf_class;
155 };
156
157 // configuration
158 unsigned int _tree_count;
159 DIMTYPE _mtry;
160 unsigned int _max_depth;
161 IDXTYPE _min_leaf;
162 unsigned int _seed_salt;
163
164 // data shape + trained state
165 DIMTYPE _classes;
166 DIMTYPE _features;
167 DIMTYPE _subfeatures;
168 std::vector<DIMTYPE> _index;
169 std::vector<IDXTYPE> _votes;
170 std::vector<std::vector<Node> > _forest;
171
172 // training scratch (rebuilt per train(); copied by clones harmlessly)
173 std::vector<REALTYPE> _X;
174 std::vector<DIMTYPE> _y;
175
176 // hash-seeded local RNG (xorshift64*) - deliberately NOT the thread_local fst_rand stream
177 std::uint64_t _rng_state;
178 std::uint64_t rng_next() {std::uint64_t &s=_rng_state; s^=s>>12; s^=s<<25; s^=s>>27; return s*2685821657736338717ULL;}
179 IDXTYPE rng_below(const IDXTYPE n) {assert(n>0); return (IDXTYPE)(rng_next()%(std::uint64_t)n);}
180
187 void grow_tree(std::vector<Node> &tree, std::vector<IDXTYPE> &samples, const DIMTYPE mtry);
190 DIMTYPE tree_vote(const std::vector<Node> &tree, const PPattern &pattern) const;
191};
192
193template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
195{
196 notify("Classifier_Random_Forest::initialize().");
197 assert(da);
198 assert(da->getNoOfFeatures()>0);
199 if(da->getNoOfClasses()<2) throw fst_error("Classifier_Random_Forest requires at least 2 classes");
200 _classes=da->getNoOfClasses();
201 _features=da->getNoOfFeatures();
202 _index.resize(_features);
203 _subfeatures=0; // no model yet
204 _forest.clear();
205}
206
207template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
209{
210 notify("Classifier_Random_Forest::train().");
211 assert(da);
212 assert(sub);
213 assert(_classes==da->getNoOfClasses());
214 assert(_features==da->getNoOfFeatures());
215 assert(sub->get_n_raw()==da->getNoOfFeatures());
216
217 // buffer the feature subset index
218 _subfeatures=0;
219 for(DIMTYPE fsel : sub->selected_features()) {assert(_subfeatures<_features); _index[_subfeatures++]=fsel;}
220 assert(_subfeatures==sub->get_d_raw());
221 assert(_subfeatures>0);
222 const std::size_t d=_subfeatures;
223
224 // gather raw selected training values and labels (CART is scale-invariant — no standardization)
225 const DIMTYPE stride=da->getNoOfFeatures();
226 _X.clear(); _y.clear();
227 typename DATAACCESSOR::PPattern p;
228 IDXTYPE s,i;
229 for(DIMTYPE c=0;c<_classes;c++)
230 {
231 da->setClass(c);
232 for(const auto &blk : da->blocks(TRAIN)) for(i=0,p=blk.data,s=blk.size;i<s;i++)
233 {
234 const auto *pat=&p[(std::size_t)i*stride];
235 for(std::size_t f=0;f<d;f++) _X.push_back((REALTYPE)pat[_index[f]]);
236 _y.push_back(c);
237 }
238 }
239 const IDXTYPE N=(IDXTYPE)_y.size();
240 if(N==0) return false;
241
242 // seed the local RNG from (subset content, `--seed` salt, split index): FNV-1a 64-bit
243 std::uint64_t h=14695981039346656037ULL;
244 const std::uint64_t prime=1099511628211ULL;
245 auto mix=[&h](std::uint64_t v) {for(int b=0;b<8;b++) {h^=(v&0xffu); h*=prime; v>>=8;}};
246 mix((std::uint64_t)_seed_salt);
247 mix((std::uint64_t)da->getSplitIndex());
248 for(std::size_t f=0;f<d;f++) mix((std::uint64_t)_index[f]);
249 if(h==0) h=0x9E3779B97F4A7C15ULL; // xorshift state must be nonzero
250 _rng_state=h;
251
252 // per-node feature-subsample size
253 DIMTYPE mtry=_mtry;
254 if(mtry==0) mtry=(DIMTYPE)std::floor(std::sqrt((double)d));
255 if(mtry<1) mtry=1;
256 if(mtry>(DIMTYPE)d) mtry=(DIMTYPE)d;
257
258 // grow the forest on bootstrap samples (sequential, fixed order — deterministic)
259 _forest.assign(_tree_count,std::vector<Node>());
260 std::vector<IDXTYPE> samples(N);
261 for(unsigned int t=0;t<_tree_count;t++)
262 {
263 for(IDXTYPE n=0;n<N;n++) samples[n]=rng_below(N);
264 grow_tree(_forest[t],samples,mtry);
265 }
266 return true;
267}
268
269template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
270void Classifier_Random_Forest<RETURNTYPE,DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::grow_tree(std::vector<Node> &tree, std::vector<IDXTYPE> &samples, const DIMTYPE mtry)
271{
272 const std::size_t d=_subfeatures;
273 const DIMTYPE K=_classes;
274 struct Work {long node; IDXTYPE lo, hi; unsigned int depth;}; // samples[lo..hi) belong to the node
275 std::vector<Work> stack;
276 std::vector<IDXTYPE> cnt(K), cntL(K);
277 std::vector<DIMTYPE> feats(d);
278 std::vector<IDXTYPE> sortbuf;
279
280 tree.clear();
281 tree.push_back(Node());
282 stack.push_back(Work{0,0,(IDXTYPE)samples.size(),0});
283 while(!stack.empty())
284 {
285 const Work w=stack.back(); stack.pop_back();
286 const IDXTYPE n=w.hi-w.lo;
287 assert(n>0);
288
289 // class histogram; majority with lowest-index tie-break
290 std::fill(cnt.begin(),cnt.end(),(IDXTYPE)0);
291 for(IDXTYPE ii=w.lo;ii<w.hi;ii++) cnt[_y[samples[ii]]]++;
292 DIMTYPE major=0;
293 for(DIMTYPE c=1;c<K;c++) if(cnt[c]>cnt[major]) major=c;
294 const bool pure=(cnt[major]==n);
295
296 // n<2*_min_leaf is a shortcut only: no split of n samples can give both children at
297 // least _min_leaf. The per-split-point check further below enforces _min_leaf exactly.
298 bool make_leaf=pure || n<2*_min_leaf || (_max_depth>0 && w.depth>=_max_depth);
299 DIMTYPE best_f=0; REALTYPE best_thr=0.0, best_gain=(REALTYPE)-1.0;
300 if(!make_leaf)
301 {
302 // draw mtry distinct candidate features (partial Fisher-Yates, fixed order)
303 for(std::size_t f=0;f<d;f++) feats[f]=(DIMTYPE)f;
304 for(DIMTYPE k=0;k<mtry;k++) {const IDXTYPE j=k+rng_below((IDXTYPE)(d-k)); std::swap(feats[k],feats[j]);}
305
306 for(DIMTYPE k=0;k<mtry;k++)
307 {
308 const DIMTYPE f=feats[k];
309 sortbuf.assign(samples.begin()+w.lo,samples.begin()+w.hi);
310 // total order (value, sample index) — deterministic under equal values
311 std::sort(sortbuf.begin(),sortbuf.end(),[this,d,f](const IDXTYPE a, const IDXTYPE b)
312 {const REALTYPE va=_X[(std::size_t)a*d+f], vb=_X[(std::size_t)b*d+f]; return va<vb || (va==vb && a<b);});
313 // sweep split points between distinct consecutive values; with sL,sR the sums of
314 // squared class counts on each side, maximizing sL/nL + sR/nR is exactly the same
315 // ordering as minimizing the weighted Gini impurity (nL*giniL + nR*giniR), and
316 // avoids the divisions the impurity form would need
317 std::fill(cntL.begin(),cntL.end(),(IDXTYPE)0);
318 for(IDXTYPE ii=0;ii+1<n;ii++)
319 {
320 cntL[_y[sortbuf[ii]]]++;
321 const REALTYPE v0=_X[(std::size_t)sortbuf[ii]*d+f], v1=_X[(std::size_t)sortbuf[ii+1]*d+f];
322 if(!(v0<v1)) continue; // tie group — no split point here
323 const IDXTYPE nL=ii+1, nR=n-nL;
324 if(nL<_min_leaf || nR<_min_leaf) continue;
325 REALTYPE sL=0.0, sR=0.0;
326 for(DIMTYPE c=0;c<K;c++) {
327 const REALTYPE l=(REALTYPE)cntL[c], r=(REALTYPE)(cnt[c]-cntL[c]);
328 sL+=l*l; sR+=r*r;
329 }
330 const REALTYPE gain=sL/(REALTYPE)nL+sR/(REALTYPE)nR;
331 if(gain>best_gain) { // strict: equal gain keeps the first candidate encountered
332 REALTYPE thr=(v0+v1)/(REALTYPE)2.0;
333 // FP-midpoint guard: for adjacent representable values the midpoint can round
334 // up to v1, which would put the v1 samples on the left as well and empty the
335 // right child; falling back to v0 keeps the partition as the sweep assumed
336 if(!(thr<v1)) thr=v0;
337 best_gain=gain; best_f=f; best_thr=thr;
338 }
339 }
340 }
341 if(best_gain<(REALTYPE)0.0) make_leaf=true; // no valid split point on any candidate (e.g. all-tied values)
342 }
343
344 if(make_leaf)
345 {
346 tree[w.node].left=-1; tree[w.node].right=-1;
347 tree[w.node].feature=0; tree[w.node].threshold=0.0;
348 tree[w.node].leaf_class=major;
349 continue;
350 }
351
352 // partition the slice (stable — deterministic order for the children)
353 auto mid=std::stable_partition(samples.begin()+w.lo,samples.begin()+w.hi,
354 [this,d,best_f,best_thr](const IDXTYPE a){return _X[(std::size_t)a*d+best_f]<=best_thr;});
355 const IDXTYPE cut=(IDXTYPE)(mid-samples.begin());
356 assert(cut>w.lo && cut<w.hi); // guaranteed by the split search (both children hold >=_min_leaf>=1 samples);
357 // were it ever violated with asserts disabled, an empty child slice would
358 // trip the assert(n>0) at the top of the next iteration
359
360 const long left=(long)tree.size(); tree.push_back(Node());
361 const long right=(long)tree.size(); tree.push_back(Node());
362 tree[w.node].feature=best_f; tree[w.node].threshold=best_thr;
363 tree[w.node].left=left; tree[w.node].right=right; tree[w.node].leaf_class=0;
364 stack.push_back(Work{right,cut,w.hi,w.depth+1});
365 stack.push_back(Work{left,w.lo,cut,w.depth+1});
366 }
367}
368
369template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
371{
372 long node=0;
373 while(tree[node].left>=0)
374 {
375 const Node &nd=tree[node];
376 node=((REALTYPE)pattern[_index[nd.feature]]<=nd.threshold)?nd.left:nd.right;
377 }
378 return tree[node].leaf_class;
379}
380
381template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
383{
384 assert(_subfeatures>0 && !_forest.empty()); // train() must have succeeded
385 _votes.assign(_classes,(IDXTYPE)0); // member scratch: avoids a per-pattern allocation
386 for(const auto &tree : _forest) _votes[tree_vote(tree,pattern)]++;
387 cls=0;
388 for(DIMTYPE c=1;c<_classes;c++) if(_votes[c]>_votes[cls]) cls=c; // ties resolve to the lowest class index
389 return true;
390}
391
392template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
394{
395 assert(_subfeatures>0 && !_forest.empty());
396 scores.assign(_classes,(RETURNTYPE)0.0);
397 for(const auto &tree : _forest) scores[tree_vote(tree,pattern)]+=1.0;
398 for(DIMTYPE c=0;c<_classes;c++) scores[c]/=(RETURNTYPE)_forest.size();
399 return true;
400}
401
402
403} // namespace
404#endif // FSTCLASSIFIERRF_H ///:~
Dependency-free Random Forest classifier (CART trees, Gini impurity, bagging, per-node feature subsam...
Definition classifier_rf.hpp:111
unsigned int _max_depth
tree depth cap; 0 = unlimited; set by set_max_depth()
Definition classifier_rf.hpp:160
virtual bool classify(DIMTYPE &cls, const PPattern &pattern) override
Definition classifier_rf.hpp:382
void initialize(const PDataAccessor da)
records data shape, allocates the index buffer
Definition classifier_rf.hpp:194
std::vector< IDXTYPE > _votes
classify() vote scratch (member to avoid per-pattern allocation)
Definition classifier_rf.hpp:169
IDXTYPE rng_below(const IDXTYPE n)
a value in [0,n) via modulo of rng_next(). The modulo bias is of order n/2^64 - unmeasurable for any ...
Definition classifier_rf.hpp:179
void set_seed_salt(const unsigned int s)
salts the subset-content hash the training RNG is seeded from; default 1 (the same value the unseeded...
Definition classifier_rf.hpp:126
void grow_tree(std::vector< Node > &tree, std::vector< IDXTYPE > &samples, const DIMTYPE mtry)
Grows one CART tree (Gini-impurity best split, mtry random candidate features per node) into tree,...
Definition classifier_rf.hpp:270
std::uint64_t _rng_state
xorshift64* generator state; seeded per train() call (see train())
Definition classifier_rf.hpp:177
void set_mtry(const DIMTYPE m)
features tried per node; 0 = floor(sqrt(subset size))
Definition classifier_rf.hpp:123
IDXTYPE _min_leaf
minimum samples in each child after a split; set by set_min_leaf()
Definition classifier_rf.hpp:161
std::uint64_t rng_next()
advances the xorshift64* generator, returns its next 64-bit output
Definition classifier_rf.hpp:178
void set_tree_count(const unsigned int t)
number of trees to grow (must be >0); default 100
Definition classifier_rf.hpp:122
unsigned int _seed_salt
salts the per-evaluation subset-content hash; set by set_seed_salt(), default 1
Definition classifier_rf.hpp:162
DIMTYPE _mtry
features tried per split node; 0 = auto (floor(sqrt(subset size))); set by set_mtry()
Definition classifier_rf.hpp:159
DIMTYPE _subfeatures
d = current subset size
Definition classifier_rf.hpp:167
virtual std::ostream & print(std::ostream &os) const override
writes a human-readable identification of the object; base default writes nothing
Definition classifier_rf.hpp:143
unsigned int _tree_count
number of trees to grow; set by set_tree_count()
Definition classifier_rf.hpp:158
std::vector< REALTYPE > _X
N x d row-major selected training values.
Definition classifier_rf.hpp:173
void set_min_leaf(const IDXTYPE ml)
minimum samples in each child after a split (must be >0); default 1
Definition classifier_rf.hpp:125
DIMTYPE tree_vote(const std::vector< Node > &tree, const PPattern &pattern) const
Walks tree from its root to a leaf for pattern (via _index to map each node's subset-position feature...
Definition classifier_rf.hpp:370
std::vector< DIMTYPE > _index
subset position -> raw feature index (first d entries valid)
Definition classifier_rf.hpp:168
std::vector< DIMTYPE > _y
N training labels.
Definition classifier_rf.hpp:174
DIMTYPE _classes
number of classes, as recorded by initialize()
Definition classifier_rf.hpp:165
DIMTYPE _features
raw (full) feature count, as recorded by initialize()
Definition classifier_rf.hpp:166
Classifier< RETURNTYPE, DIMTYPE, SUBSET, DATAACCESSOR > parent
the abstract base type
Definition classifier_rf.hpp:113
virtual bool classify_scores(std::vector< RETURNTYPE > &scores, const PPattern &pattern) override
Definition classifier_rf.hpp:393
virtual bool train(const PDataAccessor da, const PSubset sub) override
Grows the forest on the designated training part of da, restricted to sub's selected features....
Definition classifier_rf.hpp:208
Clonable_Impl< Classifier_Random_Forest, parent > parent_impl
implements clone() via the (implicit, member-wise) copy-constructor - all members are value-semantic
Definition classifier_rf.hpp:114
DATAACCESSOR::PPattern PPattern
one data sample as exposed by DATAACCESSOR (a raw pointer to its feature vector)
Definition classifier_rf.hpp:117
std::shared_ptr< DATAACCESSOR > PDataAccessor
shared pointer to the data accessor supplying train/test blocks
Definition classifier_rf.hpp:115
std::vector< std::vector< Node > > _forest
the trained forest; outer index = tree, inner = that tree's node vector (root at index 0)
Definition classifier_rf.hpp:170
void set_max_depth(const unsigned int dp)
0 = unlimited
Definition classifier_rf.hpp:124
std::shared_ptr< SUBSET > PSubset
shared pointer to the feature subset being trained/classified on
Definition classifier_rf.hpp:116
abstract class, defines interface for classifier implementations (mainly to be used in wrappers)
Definition classifier.hpp:97
CRTP mixin implementing the Clonable protocol for a concrete class.
Definition clonable.hpp:184
FST4 specific error passing class.
Definition error.hpp:61
Defines interface for classifier implementations.
Defines interface for classes that may need to get cloned.
FST4 specific error passing class.
Global definitions.
The Feature Selection Toolbox 4 library: every library component – data access, criteria and classifi...
Definition data_accessor.hpp:80
void notify(const std::string msg)
Prints msg followed by a newline via syncout::print(); a no-op unless the preprocessor macro DEBUG is...
Definition global.hpp:112
@ TRAIN
the split's training part
Definition data_accessor.hpp:85
Definition classifier_rf.hpp:149
DIMTYPE leaf_class
predicted class (meaningful only when left<0, i.e. at a leaf)
Definition classifier_rf.hpp:154
long left
left-child index into the tree's node vector; <0 marks this node a leaf
Definition classifier_rf.hpp:152
REALTYPE threshold
split threshold; a pattern routes to left when its value is <=threshold
Definition classifier_rf.hpp:151
DIMTYPE feature
split feature, given as a subset position (see _index for the raw feature index)
Definition classifier_rf.hpp:150
long right
right-child index into the tree's node vector (meaningful only when left>=0)
Definition classifier_rf.hpp:153
Copyright © 2000–present ÚTIA, Institute of Information Theory and Automation of the Czech Academy of Sciences · Contacts · This site sets no cookies and uses no analytics.