ROOT  6.06/09
Reference Guide
CostComplexityPruneTool.cxx
Go to the documentation of this file.
1 /**********************************************************************************
2  * Project: TMVA - a Root-integrated toolkit for multivariate data analysis *
3  * Package: TMVA *
4  * Class : TMVA::DecisionTree *
5  * Web : http://tmva.sourceforge.net *
6  * *
7  * Description: *
8  * Implementation of a Decision Tree *
9  * *
10  * Authors (alphabetical): *
11  * Andreas Hoecker <Andreas.Hocker@cern.ch> - CERN, Switzerland *
12  * Helge Voss <Helge.Voss@cern.ch> - MPI-K Heidelberg, Germany *
13  * Kai Voss <Kai.Voss@cern.ch> - U. of Victoria, Canada *
14  * Doug Schouten <dschoute@sfu.ca> - Simon Fraser U., Canada *
15  * *
16  * Copyright (c) 2005: *
17  * CERN, Switzerland *
18  * U. of Victoria, Canada *
19  * MPI-K Heidelberg, Germany *
20  * *
21  * Redistribution and use in source and binary forms, with or without *
22  * modification, are permitted according to the terms listed in LICENSE *
23  * (http://mva.sourceforge.net/license.txt) *
24  * *
25  **********************************************************************************/
26 
28 
29 #include "TMVA/MsgLogger.h"
30 
31 #include <fstream>
32 #include <limits>
33 #include <math.h>
34 
35 using namespace TMVA;
36 
37 
38 ////////////////////////////////////////////////////////////////////////////////
39 /// the constructor for the cost complexity prunig
40 
42  IPruneTool(),
43  fLogger(new MsgLogger("CostComplexityPruneTool") )
44 {
45  fOptimalK = -1;
46 
47  // !! changed from Dougs code. Now use the QualityIndex stored already
48  // in the nodes when no "new" QualityIndex calculator is given. Like this
49  // I can easily implement the Regression. For Regression, the pruning uses the
50  // same sepearation index as in the tree building, hence doesn't need to re-calculate
51  // (which would need more info than simply "s" and "b")
52 
53  fQualityIndexTool = qualityIndex;
54 
55  //fLogger->SetMinType( kDEBUG );
57 }
58 
59 ////////////////////////////////////////////////////////////////////////////////
60 /// the destructor for the cost complexity prunig
61 
64 }
65 
66 ////////////////////////////////////////////////////////////////////////////////
67 
70  const IPruneTool::EventSample* validationSample,
71  Bool_t isAutomatic )
72 {
73  // the routine that basically "steers" the pruning process. Call the calculation of
74  // the pruning sequence, the tree quality and alike..
75 
76  if( isAutomatic ) SetAutomatic();
77 
78  if( dt == NULL || (IsAutomatic() && validationSample == NULL) ) {
79  // must have a valid decision tree to prune, and if the prune strength
80  // is to be chosen automatically, must have a test sample from
81  // which to calculate the quality of the pruned tree(s)
82  return NULL;
83  }
84 
85  Double_t Q = -1.0;
86  Double_t W = 1.0;
87 
88  if(IsAutomatic()) {
89  // run the pruning validation sample through the unpruned tree
90  dt->ApplyValidationSample(validationSample);
91  W = dt->GetSumWeights(validationSample); // get the sum of weights in the pruning validation sample
92  // calculate the quality of the tree in the unpruned case
93  Q = dt->TestPrunedTreeQuality();
94 
95  Log() << kDEBUG << "Node purity limit is: " << dt->GetNodePurityLimit() << Endl;
96  Log() << kDEBUG << "Sum of weights in pruning validation sample: " << W << Endl;
97  Log() << kDEBUG << "Quality of tree prior to any pruning is " << Q/W << Endl;
98  }
99 
100  // store the cost complexity metadata for the decision tree at each node
101  try {
103  }
104  catch(std::string error) {
105  Log() << kERROR << "Couldn't initialize the tree meta data because of error ("
106  << error << ")" << Endl;
107  return NULL;
108  }
109 
110  Log() << kDEBUG << "Automatic cost complexity pruning is " << (IsAutomatic()?"on":"off") << "." << Endl;
111 
112  try {
113  Optimize( dt, W ); // run the cost complexity pruning algorithm
114  }
115  catch(std::string error) {
116  Log() << kERROR << "Error optimzing pruning sequence ("
117  << error << ")" << Endl;
118  return NULL;
119  }
120 
121  Log() << kDEBUG << "Index of pruning sequence to stop at: " << fOptimalK << Endl;
122 
123  PruningInfo* info = new PruningInfo();
124 
125 
126  if(fOptimalK < 0) {
127  // no pruning necessary, or wasn't able to compute a sequence
128  info->PruneStrength = 0;
129  info->QualityIndex = Q/W;
130  info->PruneSequence.clear();
131  Log() << kINFO << "no proper pruning could be calulated. Tree "
132  << dt->GetTreeID() << " will not be pruned. Do not worry if this "
133  << " happens for a few trees " << Endl;
134  return info;
135  }
137  Log() << kDEBUG << " prune until k=" << fOptimalK << " with alpha="<<fPruneStrengthList[fOptimalK]<< Endl;
138  for( Int_t i = 0; i < fOptimalK; i++ ){
139  info->PruneSequence.push_back(fPruneSequence[i]);
140  }
141  if( IsAutomatic() ){
143  }
144  else {
146  }
147 
148  return info;
149 }
150 
151 ////////////////////////////////////////////////////////////////////////////////
152 /// initialise "meta data" for the pruning, like the "costcomplexity", the
153 /// critical alpha, the minimal alpha down the tree, etc... for each node!!
154 
156  if( n == NULL ) return;
157 
158  Double_t s = n->GetNSigEvents();
159  Double_t b = n->GetNBkgEvents();
160  // set R(t) = N_events*Gini(t) or MisclassificationError(t), etc.
162  else n->SetNodeR( (s+b)*n->GetSeparationIndex() );
163 
164  if(n->GetLeft() != NULL && n->GetRight() != NULL) { // n is an interior (non-leaf) node
165  n->SetTerminal(kFALSE);
166  // traverse the tree
169  // set |~T_t|
170  n->SetNTerminal( n->GetLeft()->GetNTerminal() +
171  n->GetRight()->GetNTerminal());
172  // set R(T) = sum[n' in ~T]{ R(n') }
173  n->SetSubTreeR( (n->GetLeft()->GetSubTreeR() +
174  n->GetRight()->GetSubTreeR()));
175  // set alpha_c, the alpha value at which it becomes advantageaus to prune at node n
176  n->SetAlpha( ((n->GetNodeR() - n->GetSubTreeR()) /
177  (n->GetNTerminal() - 1)));
178 
179  // G(t) = min( alpha_c, G(l(n)), G(r(n)) )
180  // the minimum alpha in subtree rooted at this node
182  n->GetRight()->GetAlphaMinSubtree())));
183  n->SetCC(n->GetAlpha());
184 
185  } else { // n is a terminal node
186  n->SetNTerminal( 1 ); n->SetTerminal( );
188  else n->SetSubTreeR( (s+b)*n->GetSeparationIndex() );
191  n->SetCC(n->GetAlpha());
192  }
193 
194 // DecisionTreeNode* R = (DecisionTreeNode*)mdt->GetRoot();
195 // Double_t x = R->GetAlphaMinSubtree();
196 // Log() << "alphaMin(Root) = " << x << Endl;
197 }
198 
199 
200 ////////////////////////////////////////////////////////////////////////////////
201 /// after the critical alpha values (at which the corresponding nodes would
202 /// be pruned away) had been established in the "InitMetaData" we need now:
203 /// automatic pruning:
204 /// find the value of "alpha" for which the test sample gives minimal error,
205 /// on the tree with all nodes pruned that have alpha_critital < alpha,
206 /// fixed parameter pruning
207 ///
208 
210  Int_t k = 1;
211  Double_t alpha = -1.0e10;
213 
214  fQualityIndexList.clear();
215  fPruneSequence.clear();
216  fPruneStrengthList.clear();
217 
219 
220  Double_t qmin = 0.0;
221  if(IsAutomatic()){
222  // initialize the tree quality (actually at this stage, it is the quality of the yet unpruned tree
223  qmin = dt->TestPrunedTreeQuality()/weights;
224  }
225 
226  // now prune the tree in steps until it is gone. At each pruning step, the pruning
227  // takes place at the node that is regarded as the "weakest link".
228  // for automatic pruning, at each step, we calculate the current quality of the
229  // tree and in the end we will prune at the minimum of the tree quality
230  // for the fixed parameter pruing, the cut is simply set at a relative position
231  // in the sequence according to the "lenght" of the sequence of pruned trees.
232  // 100: at the end (pruned until the root node would be the next pruning candidate
233  // 50: in the middle of the sequence
234  // etc...
235  while(R->GetNTerminal() > 1) { // prune upwards to the root node
236 
237  // initialize alpha
238  alpha = TMath::Max(R->GetAlphaMinSubtree(), alpha);
239 
240  if( R->GetAlphaMinSubtree() >= R->GetAlpha() ) {
241  Log() << kDEBUG << "\nCaught trying to prune the root node!" << Endl;
242  break;
243  }
244 
245 
246  DecisionTreeNode* t = R;
247 
248  // descend to the weakest link
249  while(t->GetAlphaMinSubtree() < t->GetAlpha()) {
250 // std::cout << t->GetAlphaMinSubtree() << " " << t->GetAlpha()<< " "
251 // << t->GetAlphaMinSubtree()- t->GetAlpha()<< " t==R?" << int(t == R) << std::endl;
252  // while( (t->GetAlphaMinSubtree() - t->GetAlpha()) < epsilon) {
253  // if(TMath::Abs(t->GetAlphaMinSubtree() - t->GetLeft()->GetAlphaMinSubtree())/TMath::Abs(t->GetAlphaMinSubtree()) < epsilon) {
254  if(TMath::Abs(t->GetAlphaMinSubtree() - t->GetLeft()->GetAlphaMinSubtree()) < epsilon) {
255  t = t->GetLeft();
256  } else {
257  t = t->GetRight();
258  }
259  }
260 
261  if( t == R ) {
262  Log() << kDEBUG << "\nCaught trying to prune the root node!" << Endl;
263  break;
264  }
265 
266  DecisionTreeNode* n = t;
267 
268 // Log() << kDEBUG << "alpha[" << k << "]: " << alpha << Endl;
269 // Log() << kDEBUG << "===========================" << Endl
270 // << "Pruning branch listed below the node" << Endl;
271 // t->Print( Log() );
272 // Log() << kDEBUG << "===========================" << Endl;
273 // t->PrintRecPrune( Log() );
274 
275  dt->PruneNodeInPlace(t); // prune the branch rooted at node t
276 
277  while(t != R) { // go back up the (pruned) tree and recalculate R(T), alpha_c
278  t = t->GetParent();
279  t->SetNTerminal(t->GetLeft()->GetNTerminal() + t->GetRight()->GetNTerminal());
280  t->SetSubTreeR(t->GetLeft()->GetSubTreeR() + t->GetRight()->GetSubTreeR());
281  t->SetAlpha((t->GetNodeR() - t->GetSubTreeR())/(t->GetNTerminal() - 1));
283  t->GetRight()->GetAlphaMinSubtree())));
284  t->SetCC(t->GetAlpha());
285  }
286  k += 1;
287 
288  Log() << kDEBUG << "after this pruning step I would have " << R->GetNTerminal() << " remaining terminal nodes " << Endl;
289 
290  if(IsAutomatic()) {
291  Double_t q = dt->TestPrunedTreeQuality()/weights;
292  fQualityIndexList.push_back(q);
293  }
294  else {
295  fQualityIndexList.push_back(1.0);
296  }
297  fPruneSequence.push_back(n);
298  fPruneStrengthList.push_back(alpha);
299  }
300 
301  if(fPruneSequence.empty()) {
302  fOptimalK = -1;
303  return;
304  }
305 
306  if(IsAutomatic()) {
307  k = -1;
308  for(UInt_t i = 0; i < fQualityIndexList.size(); i++) {
309  if(fQualityIndexList[i] < qmin) {
310  qmin = fQualityIndexList[i];
311  k = i;
312  }
313  }
314  fOptimalK = k;
315  }
316  else {
317  // regularize the prune strength relative to this tree
318  fOptimalK = int(fPruneStrength/100.0 * fPruneSequence.size() );
319  Log() << kDEBUG << "SequenzeSize="<<fPruneSequence.size()
320  << " fOptimalK " << fOptimalK << Endl;
321 
322  }
323 
324  Log() << kDEBUG << "\n************ Summary for Tree " << dt->GetTreeID() << " *******" << Endl
325  << "Number of trees in the sequence: " << fPruneSequence.size() << Endl;
326 
327  Log() << kDEBUG << "Pruning strength parameters: [";
328  for(UInt_t i = 0; i < fPruneStrengthList.size()-1; i++)
329  Log() << kDEBUG << fPruneStrengthList[i] << ", ";
330  Log() << kDEBUG << fPruneStrengthList[fPruneStrengthList.size()-1] << "]" << Endl;
331 
332  Log() << kDEBUG << "Misclassification rates: [";
333  for(UInt_t i = 0; i < fQualityIndexList.size()-1; i++)
334  Log() << kDEBUG << fQualityIndexList[i] << ", ";
335  Log() << kDEBUG << fQualityIndexList[fQualityIndexList.size()-1] << "]" << Endl;
336 
337  Log() << kDEBUG << "Prune index: " << fOptimalK+1 << Endl;
338 
339 }
340 
Double_t PruneStrength
quality measure for a pruned subtree T of T_max
Definition: IPruneTool.h:50
Int_t fOptimalK
map of R(T) -> pruning index
static Vc_ALWAYS_INLINE int_v min(const int_v &x, const int_v &y)
Definition: vector.h:433
virtual ~CostComplexityPruneTool()
the destructor for the cost complexity prunig
MsgLogger & Endl(MsgLogger &ml)
Definition: MsgLogger.h:162
Double_t fPruneStrength
Definition: IPruneTool.h:103
CostComplexityPruneTool(SeparationBase *qualityIndex=NULL)
the constructor for the cost complexity prunig
Double_t GetNodePurityLimit() const
Definition: DecisionTree.h:170
Double_t GetAlpha() const
virtual DecisionTreeNode * GetRight() const
int Int_t
Definition: RtypesCore.h:41
std::vector< DecisionTreeNode * > PruneSequence
the regularization parameter for pruning
Definition: IPruneTool.h:51
bool Bool_t
Definition: RtypesCore.h:59
const Bool_t kFALSE
Definition: Rtypes.h:92
virtual DecisionTreeNode * GetLeft() const
void SetAutomatic()
Definition: IPruneTool.h:96
virtual DecisionTreeNode * GetParent() const
Short_t Abs(Short_t d)
Definition: TMathBase.h:110
virtual DecisionTreeNode * GetRoot() const
Definition: DecisionTree.h:102
Double_t GetSumWeights(const EventConstList *validationSample) const
calculate the normalization factor for a pruning validation sample
void SetNodeR(Double_t r)
void SetMinType(EMsgType minType)
Definition: MsgLogger.h:76
std::vector< const Event * > EventSample
Definition: IPruneTool.h:76
void Optimize(DecisionTree *dt, Double_t weights)
after the critical alpha values (at which the corresponding nodes would be pruned away) had been esta...
Double_t GetSubTreeR() const
Double_t GetAlphaMinSubtree() const
Float_t GetNBkgEvents(void) const
void SetSubTreeR(Double_t r)
MsgLogger & Log() const
output stream to save logging information
Int_t GetNTerminal() const
void SetAlpha(Double_t alpha)
unsigned int UInt_t
Definition: RtypesCore.h:42
const Double_t infinity
Definition: CsgOps.cxx:85
Bool_t IsAutomatic() const
Definition: IPruneTool.h:97
void PruneNodeInPlace(TMVA::DecisionTreeNode *node)
prune a node temporaily (without actually deleting its decendants which allows testing the pruned tre...
REAL epsilon
Definition: triangle.c:617
Double_t GetNodeR() const
double Double_t
Definition: RtypesCore.h:55
void SetAlphaMinSubtree(Double_t g)
void SetTerminal(Bool_t s=kTRUE)
std::vector< Double_t > fPruneStrengthList
map of weakest links (i.e., branches to prune) -> pruning index
std::vector< Double_t > fQualityIndexList
map of alpha -> pruning index
std::vector< DecisionTreeNode * > fPruneSequence
the quality index used to calculate R(t), R(T) = sum[t in ~T]{ R(t) }
Abstract ClassifierFactory template that handles arbitrary types.
void InitTreePruningMetaData(DecisionTreeNode *n)
the optimal index of the prune sequence
virtual Double_t GetSeparationIndex(const Double_t &s, const Double_t &b)=0
Short_t Max(Short_t a, Short_t b)
Definition: TMathBase.h:202
Double_t TestPrunedTreeQuality(const DecisionTreeNode *dt=NULL, Int_t mode=0) const
return the misclassification rate of a pruned tree a "pruned tree" may have set the variable "IsTermi...
Float_t GetSeparationIndex(void) const
#define NULL
Definition: Rtypes.h:82
Double_t QualityIndex
Definition: IPruneTool.h:49
Float_t GetNSigEvents(void) const
float * q
Definition: THbookFile.cxx:87
const Int_t n
Definition: legend1.C:16
TRandom3 R
a TMatrixD.
Definition: testIO.cxx:28
static double Q[]
virtual PruningInfo * CalculatePruningInfo(DecisionTree *dt, const IPruneTool::EventSample *testEvents=NULL, Bool_t isAutomatic=kFALSE)
void ApplyValidationSample(const EventConstList *validationSample) const
run the validation sample through the (pruned) tree and fill in the nodes the variables NSValidation ...