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