automatic commit
[mlp] / ann.cc
1 /*
2  *  mlp-mnist is an implementation of a multi-layer neural network.
3  *
4  *  Copyright (c) 2008 Idiap Research Institute, http://www.idiap.ch/
5  *  Written by Francois Fleuret <francois.fleuret@idiap.ch>
6  *
7  *  This file is part of mlp-mnist.
8  *
9  *  mlp-mnist is free software: you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License version 3 as
11  *  published by the Free Software Foundation.
12  *
13  *  mlp-mnist is distributed in the hope that it will be useful, but
14  *  WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *  General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with mlp-mnist.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 // LeCun et al. 1998:
24
25 // 2-layer NN, 300 hidden units, mean square error  4.70%
26 // 2-layer NN, 1000 hidden units                    4.50%
27 // 3-layer NN, 300+100 hidden units                 3.05%
28 // 3-layer NN, 500+150 hidden units                 2.95%
29
30 /*********************************************************************
31
32    This program, trained on 20,000 (+ 20,000 for the stopping
33    criterion), tested on the 10,000 of the MNIST test set 100 hidden
34    neurons, basic network, 3.48%
35
36    TRAINING
37
38     ./ann --nb-training-examples 20000 --nb-validation-examples 20000 \
39         --mlp-structure 784,200,10 \
40         --data-files ${DATA_DIR}/train-images-idx3-ubyte ${DATA_DIR}/train-labels-idx1-ubyte \
41         --save-mlp simple.mlp
42
43    TEST
44
45     ./ann --load-mlp simple.mlp \
46         --data-files ${DATA_DIR}/t10k-images-idx3-ubyte ${DATA_DIR}/t10k-labels-idx1-ubyte \
47         --nb-test-examples 10000
48
49 *********************************************************************/
50
51 #include <iostream>
52 #include <fstream>
53 #include <cmath>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57
58 using namespace std;
59
60 #include "images.h"
61 #include "neural.h"
62
63 #define SMALL_BUFFER_SIZE 1024
64
65 //////////////////////////////////////////////////////////////////////
66 // Global Variables
67 //////////////////////////////////////////////////////////////////////
68
69 int nb_experiment = 0;
70 int nb_training_examples = 0;
71 int nb_validation_examples = 0;
72 int nb_test_examples = 0;
73 bool save_data = false;
74
75 char images_filename[SMALL_BUFFER_SIZE] = "\0";
76 char labels_filename[SMALL_BUFFER_SIZE] = "\0";
77 char opt_load_filename[SMALL_BUFFER_SIZE] = "\0";
78 char opt_save_filename[SMALL_BUFFER_SIZE] = "\0";
79 char opt_layer_sizes[SMALL_BUFFER_SIZE] = "\0";
80
81 char *next_word(char *buffer, char *r, int buffer_size) {
82   char *s;
83   s = buffer;
84   if(r != NULL)
85     {
86       if(*r == '"') {
87         r++;
88         while((*r != '"') && (*r != '\0') &&
89               (s<buffer+buffer_size-1))
90           *s++ = *r++;
91         if(*r == '"') r++;
92       } else {
93         while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
94               (*r != '\t') && (*r != ' ') && (*r != ',') &&
95               (s<buffer+buffer_size-1))
96           *s++ = *r++;
97       }
98
99       while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
100       if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = NULL;
101     }
102   *s = '\0';
103   return r;
104 }
105
106 //////////////////////////////////////////////////////////////////////
107 // Simple routine to check we have enough parameters
108 //////////////////////////////////////////////////////////////////////
109
110 void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
111   if(n_opt + n >= argc) {
112     cerr << "Missing argument for " << argv[n_opt] << ".\n";
113     cerr << "Expecting " << help << ".\n";
114     exit(1);
115   }
116 }
117
118 void print_help_and_exit(int e) {
119   cout << "ANN. Written by Fran├žois Fleuret.\n";
120   cout << "$Id: ann.cc,v 1.1 2005-12-13 17:19:11 fleuret Exp $\n";
121   cout<< "\n";
122   exit(e);
123 }
124
125 int main(int argc, char **argv) {
126
127   if(argc == 1) print_help_and_exit(1);
128
129   nice(10);
130
131   // Parsing the command line parameters ///////////////////////////////
132
133   int i = 1;
134
135   while(i < argc) {
136
137     if(argc == 1 || strcmp(argv[i], "--help") == 0) print_help_and_exit(0);
138
139     else if(strcmp(argv[i], "--data-files") == 0) {
140       check_opt(argc, argv, i, 2, "<string: pixel filename> <string: label filename>");
141       strncpy(images_filename, argv[i+1], SMALL_BUFFER_SIZE);
142       strncpy(labels_filename, argv[i+2], SMALL_BUFFER_SIZE);
143       i += 3;
144     }
145
146     else if(strcmp(argv[i], "--load-mlp") == 0) {
147       check_opt(argc, argv, i, 1, "<string: mlp filename>");
148       strncpy(opt_load_filename, argv[i+1], SMALL_BUFFER_SIZE);
149       i += 2;
150     }
151
152     else if(strcmp(argv[i], "--mlp-structure") == 0) {
153       check_opt(argc, argv, i, 1, "<int: input layer size>,<int: first hidden layer size>,[...,]<int: output layer size>");
154       strncpy(opt_layer_sizes, argv[i+1], SMALL_BUFFER_SIZE);
155       i += 2;
156     }
157
158     else if(strcmp(argv[i], "--save-mlp") == 0) {
159       check_opt(argc, argv, i, 1, "<string: mlp filename>");
160       strncpy(opt_save_filename, argv[i+1], SMALL_BUFFER_SIZE);
161       i += 2;
162     }
163
164     else if(strcmp(argv[i], "--nb-experiment") == 0) {
165       check_opt(argc, argv, i, 1, "<int: number of the experiment>");
166       nb_experiment = atoi(argv[i+1]);
167       i += 2;
168     }
169
170     else if(strcmp(argv[i], "--nb-training-examples") == 0) {
171       check_opt(argc, argv, i, 1, "<int: number of examples for the training>");
172       nb_training_examples = atoi(argv[i+1]);
173       i += 2;
174     }
175
176     else if(strcmp(argv[i], "--nb-validation-examples") == 0) {
177       check_opt(argc, argv, i, 1, "<int: number of examples for the validation>");
178       nb_validation_examples = atoi(argv[i+1]);
179       i += 2;
180     }
181
182     else if(strcmp(argv[i], "--nb-test-examples") == 0) {
183       check_opt(argc, argv, i, 1, "<int: number of examples for the test>");
184       nb_test_examples = atoi(argv[i+1]);
185       i += 2;
186     }
187
188     else if(strcmp(argv[i], "--save-data") == 0) {
189       save_data = true;
190       i++;
191     }
192
193     else {
194       cerr << "Unknown option " << argv[i] << "\n";
195       print_help_and_exit(1);
196     }
197   }
198
199   ImageSet image_set;
200   cout << "Loading the data file ..."; cout.flush();
201   image_set.load_mnist_format(images_filename, labels_filename);
202   cout << " done.\n"; cout.flush();
203
204   cout << "Database contains " << image_set.nb_pics()
205        << " images of resolution " << image_set.width() << "x" << image_set.height()
206        << " divided into " << image_set.nb_obj() << " objects.\n";
207
208   srand48(nb_experiment);
209
210   int nb_layers = 0;
211   int *layer_sizes = 0;
212
213   if(opt_layer_sizes[0]) {
214     char *s = opt_layer_sizes;
215     char token[SMALL_BUFFER_SIZE];
216     while(s) { s = next_word(token, s, SMALL_BUFFER_SIZE); nb_layers++; }
217
218     if(nb_layers < 2) {
219       cerr << "Need at least two layers.\n";
220       exit(1);
221     }
222
223     layer_sizes = new int[nb_layers];
224     s = opt_layer_sizes;
225     int n = 0;
226     while(s) { s = next_word(token, s, SMALL_BUFFER_SIZE); layer_sizes[n++] = atoi(token); }
227   }
228
229   // Loading or creating a perceptron from scratch /////////////////////
230
231   MultiLayerPerceptron *mlp = 0;
232
233   if(opt_load_filename[0]) {
234
235     ifstream stream(opt_load_filename);
236     if(stream.fail()) {
237       cerr << "Can not read " << opt_load_filename << ".\n";
238       exit(1);
239     }
240
241     cout << "Loading network " << opt_load_filename << " ... "; cout.flush();
242     mlp = new MultiLayerPerceptron(stream);
243     cout << "done (layers of sizes";
244     for(int l = 0; l < mlp->nb_layers(); l++) cout << " " << mlp->layer_size(l);
245     cout << ")\n"; cout.flush();
246
247   } else if(nb_layers > 0) {
248
249     if(layer_sizes[0] != image_set.width() * image_set.height() ||
250        layer_sizes[nb_layers-1] != image_set.nb_obj()) {
251       cerr << "For this data set, the input layer has to be of size " << image_set.width() * image_set.height() << ",\n";
252       cerr << "and the output has to be of size " << image_set.nb_obj() << ".\n";
253       exit(1);
254     }
255
256     cout << "Creating a new network (layers of sizes";
257     for(int i = 0; i < nb_layers; i++) cout << " " << layer_sizes[i];
258     cout << ").\n";
259
260     mlp = new MultiLayerPerceptron(nb_layers, layer_sizes);
261     mlp->init_random_weights(1e-1);
262   }
263
264   // Training the perceptron ///////////////////////////////////////////
265
266   ImageSet training_set, validation_set, test_set;
267
268   if(nb_training_examples > 0)
269     training_set.extract_unused_pictures(image_set, nb_training_examples);
270
271   if(nb_validation_examples > 0)
272     validation_set.extract_unused_pictures(image_set, nb_validation_examples);
273
274   if(save_data && mlp) mlp->save_data();
275
276   if(nb_training_examples > 0) {
277     if(validation_set.nb_pics() == 0) {
278       cerr << "We need validation pictures for training.\n";
279       exit(1);
280     }
281     cout << "Training the network with " << nb_training_examples << " training and " << nb_validation_examples << " validation examples.\n"; cout.flush();
282     mlp->train(&training_set, &validation_set);
283   }
284
285   // Saving the perceptron /////////////////////////////////////////////
286
287   if(opt_save_filename[0]) {
288     if(!mlp) {
289       cerr << "No perceptron to save.\n";
290       exit(1);
291     }
292
293     ofstream stream(opt_save_filename);
294     if(stream.fail()) {
295       cerr << "Can not write " << opt_save_filename << ".\n";
296       exit(1);
297     }
298
299     cout << "Saving network " << opt_save_filename << " ... "; cout.flush();
300     mlp->save(stream);
301     cout << "done.\n"; cout.flush();
302   }
303
304   // Testing the perceptron ////////////////////////////////////////////
305
306   if(nb_test_examples > 0) {
307     test_set.extract_unused_pictures(image_set, nb_test_examples);
308     cout << "Error rate " << mlp->error(&test_set) << " (" << mlp->classification_error(&test_set)*100 << "%)\n";
309
310     // This is to test the analytical gradient
311     //     scalar_t gradient[mlp->nb_weights()], numerical_gradient[mlp->nb_weights()];
312     //     mlp->compute_gradient(&test_set, gradient);
313     //     mlp->compute_numerical_gradient(&test_set, numerical_gradient);
314     //     for(int i = 0; i < mlp->nb_weights(); i++) cout << "TEST " << gradient[i] << " " << numerical_gradient[i] << "\n";
315   }
316
317   // Flushing the log //////////////////////////////////////////////////
318
319   delete[] layer_sizes;
320 }