converted some more samples to C++
[profile/ivi/opencv.git] / samples / c / mushroom.cpp
1 #include "opencv2/core/core_c.h"
2 #include "opencv2/ml/ml.hpp"
3 #include <stdio.h>
4
5 /*
6 The sample demonstrates how to build a decision tree for classifying mushrooms.
7 It uses the sample base agaricus-lepiota.data from UCI Repository, here is the link:
8
9 Newman, D.J. & Hettich, S. & Blake, C.L. & Merz, C.J. (1998).
10 UCI Repository of machine learning databases
11 [http://www.ics.uci.edu/~mlearn/MLRepository.html].
12 Irvine, CA: University of California, Department of Information and Computer Science.
13 */
14
15 // loads the mushroom database, which is a text file, containing
16 // one training sample per row, all the input variables and the output variable are categorical,
17 // the values are encoded by characters.
18 int mushroom_read_database( const char* filename, CvMat** data, CvMat** missing, CvMat** responses )
19 {
20     const int M = 1024;
21     FILE* f = fopen( filename, "rt" );
22     CvMemStorage* storage;
23     CvSeq* seq;
24     char buf[M+2], *ptr;
25     float* el_ptr;
26     CvSeqReader reader;
27     int i, j, var_count = 0;
28
29     if( !f )
30         return 0;
31
32     // read the first line and determine the number of variables
33     if( !fgets( buf, M, f ))
34     {
35         fclose(f);
36         return 0;
37     }
38
39     for( ptr = buf; *ptr != '\0'; ptr++ )
40         var_count += *ptr == ',';
41     assert( ptr - buf == (var_count+1)*2 );
42
43     // create temporary memory storage to store the whole database
44     el_ptr = new float[var_count+1];
45     storage = cvCreateMemStorage();
46     seq = cvCreateSeq( 0, sizeof(*seq), (var_count+1)*sizeof(float), storage );
47
48     for(;;)
49     {
50         for( i = 0; i <= var_count; i++ )
51         {
52             int c = buf[i*2];
53             el_ptr[i] = c == '?' ? -1.f : (float)c;
54         }
55         if( i != var_count+1 )
56             break;
57         cvSeqPush( seq, el_ptr );
58         if( !fgets( buf, M, f ) || !strchr( buf, ',' ) )
59             break;
60     }
61     fclose(f);
62
63     // allocate the output matrices and copy the base there
64     *data = cvCreateMat( seq->total, var_count, CV_32F );
65     *missing = cvCreateMat( seq->total, var_count, CV_8U );
66     *responses = cvCreateMat( seq->total, 1, CV_32F );
67
68     cvStartReadSeq( seq, &reader );
69
70     for( i = 0; i < seq->total; i++ )
71     {
72         const float* sdata = (float*)reader.ptr + 1;
73         float* ddata = data[0]->data.fl + var_count*i;
74         float* dr = responses[0]->data.fl + i;
75         uchar* dm = missing[0]->data.ptr + var_count*i;
76
77         for( j = 0; j < var_count; j++ )
78         {
79             ddata[j] = sdata[j];
80             dm[j] = sdata[j] < 0;
81         }
82         *dr = sdata[-1];
83         CV_NEXT_SEQ_ELEM( seq->elem_size, reader );
84     }
85
86     cvReleaseMemStorage( &storage );
87     delete el_ptr;
88     return 1;
89 }
90
91
92 CvDTree* mushroom_create_dtree( const CvMat* data, const CvMat* missing,
93                                 const CvMat* responses, float p_weight )
94 {
95     CvDTree* dtree;
96     CvMat* var_type;
97     int i, hr1 = 0, hr2 = 0, p_total = 0;
98     float priors[] = { 1, p_weight };
99
100     var_type = cvCreateMat( data->cols + 1, 1, CV_8U );
101     cvSet( var_type, cvScalarAll(CV_VAR_CATEGORICAL) ); // all the variables are categorical
102
103     dtree = new CvDTree;
104     
105     dtree->train( data, CV_ROW_SAMPLE, responses, 0, 0, var_type, missing,
106                   CvDTreeParams( 8, // max depth
107                                  10, // min sample count
108                                  0, // regression accuracy: N/A here
109                                  true, // compute surrogate split, as we have missing data
110                                  15, // max number of categories (use sub-optimal algorithm for larger numbers)
111                                  10, // the number of cross-validation folds
112                                  true, // use 1SE rule => smaller tree
113                                  true, // throw away the pruned tree branches
114                                  priors // the array of priors, the bigger p_weight, the more attention
115                                         // to the poisonous mushrooms
116                                         // (a mushroom will be judjed to be poisonous with bigger chance)
117                                  ));
118
119     // compute hit-rate on the training database, demonstrates predict usage.
120     for( i = 0; i < data->rows; i++ )
121     {
122         CvMat sample, mask;
123         cvGetRow( data, &sample, i );
124         cvGetRow( missing, &mask, i );
125         double r = dtree->predict( &sample, &mask )->value;
126         int d = fabs(r - responses->data.fl[i]) >= FLT_EPSILON;
127         if( d )
128         {
129             if( r != 'p' )
130                 hr1++;
131             else
132                 hr2++;
133         }
134         p_total += responses->data.fl[i] == 'p';
135     }
136
137     printf( "Results on the training database:\n"
138             "\tPoisonous mushrooms mis-predicted: %d (%g%%)\n"
139             "\tFalse-alarms: %d (%g%%)\n", hr1, (double)hr1*100/p_total,
140             hr2, (double)hr2*100/(data->rows - p_total) );
141
142     cvReleaseMat( &var_type );
143
144     return dtree;
145 }
146
147
148 static const char* var_desc[] =
149 {
150     "cap shape (bell=b,conical=c,convex=x,flat=f)",
151     "cap surface (fibrous=f,grooves=g,scaly=y,smooth=s)",
152     "cap color (brown=n,buff=b,cinnamon=c,gray=g,green=r,\n\tpink=p,purple=u,red=e,white=w,yellow=y)",
153     "bruises? (bruises=t,no=f)",
154     "odor (almond=a,anise=l,creosote=c,fishy=y,foul=f,\n\tmusty=m,none=n,pungent=p,spicy=s)",
155     "gill attachment (attached=a,descending=d,free=f,notched=n)",
156     "gill spacing (close=c,crowded=w,distant=d)",
157     "gill size (broad=b,narrow=n)",
158     "gill color (black=k,brown=n,buff=b,chocolate=h,gray=g,\n\tgreen=r,orange=o,pink=p,purple=u,red=e,white=w,yellow=y)",
159     "stalk shape (enlarging=e,tapering=t)",
160     "stalk root (bulbous=b,club=c,cup=u,equal=e,rhizomorphs=z,rooted=r)",
161     "stalk surface above ring (ibrous=f,scaly=y,silky=k,smooth=s)",
162     "stalk surface below ring (ibrous=f,scaly=y,silky=k,smooth=s)",
163     "stalk color above ring (brown=n,buff=b,cinnamon=c,gray=g,orange=o,\n\tpink=p,red=e,white=w,yellow=y)",
164     "stalk color below ring (brown=n,buff=b,cinnamon=c,gray=g,orange=o,\n\tpink=p,red=e,white=w,yellow=y)",
165     "veil type (partial=p,universal=u)",
166     "veil color (brown=n,orange=o,white=w,yellow=y)",
167     "ring number (none=n,one=o,two=t)",
168     "ring type (cobwebby=c,evanescent=e,flaring=f,large=l,\n\tnone=n,pendant=p,sheathing=s,zone=z)",
169     "spore print color (black=k,brown=n,buff=b,chocolate=h,green=r,\n\torange=o,purple=u,white=w,yellow=y)",
170     "population (abundant=a,clustered=c,numerous=n,\n\tscattered=s,several=v,solitary=y)",
171     "habitat (grasses=g,leaves=l,meadows=m,paths=p\n\turban=u,waste=w,woods=d)",
172     0
173 };
174
175
176 void print_variable_importance( CvDTree* dtree, const char** var_desc )
177 {
178     const CvMat* var_importance = dtree->get_var_importance();
179     int i;
180     char input[1000];
181
182     if( !var_importance )
183     {
184         printf( "Error: Variable importance can not be retrieved\n" );
185         return;
186     }
187
188     printf( "Print variable importance information? (y/n) " );
189     scanf( "%1s", input );
190     if( input[0] != 'y' && input[0] != 'Y' )
191         return;
192
193     for( i = 0; i < var_importance->cols*var_importance->rows; i++ )
194     {
195         double val = var_importance->data.db[i];
196         if( var_desc )
197         {
198             char buf[100];
199             int len = strchr( var_desc[i], '(' ) - var_desc[i] - 1;
200             strncpy( buf, var_desc[i], len );
201             buf[len] = '\0';
202             printf( "%s", buf );
203         }
204         else
205             printf( "var #%d", i );
206         printf( ": %g%%\n", val*100. );
207     }
208 }
209
210 void interactive_classification( CvDTree* dtree, const char** var_desc )
211 {
212     char input[1000];
213     const CvDTreeNode* root;
214     CvDTreeTrainData* data;
215
216     if( !dtree )
217         return;
218
219     root = dtree->get_root();
220     data = dtree->get_data();
221
222     for(;;)
223     {
224         const CvDTreeNode* node;
225         
226         printf( "Start/Proceed with interactive mushroom classification (y/n): " );
227         scanf( "%1s", input );
228         if( input[0] != 'y' && input[0] != 'Y' )
229             break;
230         printf( "Enter 1-letter answers, '?' for missing/unknown value...\n" ); 
231
232         // custom version of predict
233         node = root;
234         for(;;)
235         {
236             CvDTreeSplit* split = node->split;
237             int dir = 0;
238             
239             if( !node->left || node->Tn <= dtree->get_pruned_tree_idx() || !node->split )
240                 break;
241
242             for( ; split != 0; )
243             {
244                 int vi = split->var_idx, j;
245                 int count = data->cat_count->data.i[vi];
246                 const int* map = data->cat_map->data.i + data->cat_ofs->data.i[vi];
247
248                 printf( "%s: ", var_desc[vi] );
249                 scanf( "%1s", input );
250
251                 if( input[0] == '?' )
252                 {
253                     split = split->next;
254                     continue;
255                 }
256
257                 // convert the input character to the normalized value of the variable
258                 for( j = 0; j < count; j++ )
259                     if( map[j] == input[0] )
260                         break;
261                 if( j < count )
262                 {
263                     dir = (split->subset[j>>5] & (1 << (j&31))) ? -1 : 1;
264                     if( split->inversed )
265                         dir = -dir;
266                     break;
267                 }
268                 else
269                     printf( "Error: unrecognized value\n" );
270             }
271             
272             if( !dir )
273             {
274                 printf( "Impossible to classify the sample\n");
275                 node = 0;
276                 break;
277             }
278             node = dir < 0 ? node->left : node->right;
279         }
280
281         if( node )
282             printf( "Prediction result: the mushroom is %s\n",
283                     node->class_idx == 0 ? "EDIBLE" : "POISONOUS" );
284         printf( "\n-----------------------------\n" );
285     }
286 }
287
288
289 int main( int argc, char** argv )
290 {
291     CvMat *data = 0, *missing = 0, *responses = 0;
292     CvDTree* dtree;
293     const char* base_path = argc >= 2 ? argv[1] : "agaricus-lepiota.data";
294
295     if( !mushroom_read_database( base_path, &data, &missing, &responses ) )
296     {
297         printf( "Unable to load the training database\n"
298                 "Pass it as a parameter: dtree <path to agaricus-lepiota.data>\n" );
299         return 0;
300         return -1;
301     }
302
303     dtree = mushroom_create_dtree( data, missing, responses,
304         10 // poisonous mushrooms will have 10x higher weight in the decision tree
305         );
306     cvReleaseMat( &data );
307     cvReleaseMat( &missing );
308     cvReleaseMat( &responses );
309
310     print_variable_importance( dtree, var_desc );
311     interactive_classification( dtree, var_desc );
312     delete dtree;
313
314     return 0;
315 }