iCub-main
Loading...
Searching...
No Matches
test.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2007-2010 RobotCub Consortium, European Commission FP6 Project IST-004370
3 * author: Arjan Gijsberts
4 * email: arjan.gijsberts@iit.it
5 * website: www.robotcub.org
6 * Permission is granted to copy, distribute, and/or modify this program
7 * under the terms of the GNU General Public License, version 2 or any
8 * later version published by the Free Software Foundation.
9 *
10 * A copy of the license can be found at
11 * http://www.robotcub.org/icub/license/gpl.txt
12 *
13 * This program 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 General
16 * Public License for more details
17 */
18
19#include <algorithm>
20#include <string>
21#include <vector>
22#include <cmath>
23#include <iostream>
24#include <sstream>
25#include <fstream>
26#include <stdexcept>
27#include <cassert>
28
29#include <yarp/os/Network.h>
30#include <yarp/os/ResourceFinder.h>
31#include <yarp/os/RFModule.h>
32#include <yarp/os/PortablePair.h>
33#include <yarp/sig/Vector.h>
34#include <yarp/os/Port.h>
35#include <yarp/os/BufferedPort.h>
36#include <yarp/os/Time.h>
37#include <yarp/os/Vocab.h>
38
39#define TWOPI 6.283185307179586
40
41using namespace yarp::os;
42using namespace yarp::sig;
43
44namespace iCub {
45namespace learningmachine {
46namespace test {
47
48// the Prediction class is compatible with a PortablePair of Vector objects.
49// in this class, we demonstrate that we in fact only need standard Yarp
50// classes to make use of the learningMachine library: no learningMachine headers
51// or library linking required!
52typedef PortablePair<Vector,Vector> Prediction;
53
54// it's 2011 and I still have to implement a double to string conversion? come on!
55std::string doubletostring(double d) {
56 std::ostringstream output;
57 output << d;
58 return output.str();
59}
60
61std::string inttostring(int i) {
62 std::ostringstream output;
63 output << i;
64 return output.str();
65}
66
67std::string printVector(const std::vector<int>& v) {
68 std::ostringstream output;
69 output << "[";
70 for(unsigned int i = 0; i < v.size(); i++) {
71 if(i > 0) output << ", ";
72 output << v[i];
73 }
74 output << "]";
75 return output.str();
76}
77
78std::string printVector(const Vector& v) {
79 std::ostringstream output;
80 output << "[";
81 for(size_t i = 0; i < v.size(); i++) {
82 if(i > 0) output << ",";
83 output << v[i];
84 }
85 output << "]";
86 return output.str();
87}
88
90 std::ostringstream output;
91 Vector v = p.head;
92 output << "[";
93 for(size_t i = 0; i < v.size(); i++) {
94 if(i > 0) output << ",";
95 output << v[i];
96 }
97 v = p.body;
98 if(v.size() > 0) {
99 output << " +/- ";
100 for(size_t i = 0; i < v.size(); i++) {
101 if(i > 0) output << ",";
102 output << v[i];
103 }
104 }
105 output << "]";
106 return output.str();
107}
108
109
110class Dataset {
111private:
112 int samplesRead;
113 std::ifstream file;
114 std::string filename;
115 std::vector<int> inputCols;
116 std::vector<int> outputCols;
117
118
119public:
121 this->inputCols.resize(1);
122 this->outputCols.resize(1);
123
124 this->inputCols.clear();
125 this->outputCols.clear();
126 }
127
128 // manage input and output columns
129 void addInputColumn(int col) {
130 this->inputCols.push_back(col);
131 }
132
133 void addOutputColumn(int col) {
134 this->outputCols.push_back(col);
135 }
136
137 std::vector<int> getInputColumns() {
138 return this->inputCols;
139 }
140
141 std::vector<int> getOutputColumns() {
142 return this->outputCols;
143 }
144
145 // gets the filename of the open dataset
146 std::string getFilename() {
147 return this->filename;
148 }
149
150 // sets the filename for the dataset
151 void setFilename(std::string filename) {
152 this->filename = filename;
153 }
154
155 // manage file datastream
156 void open() {
157 this->open(this->filename);
158 }
159
160 // open a datafile
161 void open(std::string filename) {
162 // close previous file
163 if(this->file.is_open()) {
164 this->file.close();
165 }
166
167 this->file.open(filename.c_str());
168 if(!file.is_open() || file.fail()) {
169 std::string msg("could not open file '" + filename + "'");
170 this->setFilename("");
171 throw std::runtime_error(msg);
172 return; // will not be reached anyways...
173 }
174 this->setFilename(filename);
175 this->reset();
176 }
177
179 return !this->file.eof() && this->file.good();
180 }
181
182 void reset() {
183 this->samplesRead = 0;
184 this->file.clear();
185 this->file.seekg(0, std::ios::beg);
186 }
187
188 // retrieve new sample from datastream
189 std::pair<Vector,Vector> getNextSample() {
190 Vector input;
191 Vector output;
192
193 int currentCol = 1;
194 double val = 0.0;
195
196 std::string lineString;
197
198 if(!this->hasNextSample()) {
199 throw std::runtime_error("at end of dataset");
200 }
201
202 // find first valid string that does not start with #
203 do {
204 getline(file, lineString);
205 } while(lineString[0] == '#' && !file.eof());
206
207 if(!this->hasNextSample()) {
208 throw std::runtime_error("at end of dataset");
209 }
210
211 // read string as stream buffer
212 std::istringstream lineStream(lineString);
213 while(!lineStream.eof()) {
214 lineStream >> val;
215
216 // add to input sample if appropriate
217 std::vector<int>::iterator inputFind = std::find(this->inputCols.begin(), this->inputCols.end(), currentCol);
218 if(inputFind != this->inputCols.end()) {
219 input.push_back(val);
220 }
221
222 // add to output sample if appropriate
223 std::vector<int>::iterator outputFind = std::find(this->outputCols.begin(), this->outputCols.end(), currentCol);
224 if(outputFind != this->outputCols.end()) {
225 output.push_back(val);
226 }
227 currentCol++;
228 }
229
230 std::pair<Vector,Vector> sample(input, output);
231 return sample;
232 }
233};
234
244class MachineLearnerTestModule : public RFModule {
245private:
246 BufferedPort<PortablePair<Vector,Vector> > train_out;
247 Port predict_inout;
248
249 std::string portPrefix;
250
251 Dataset dataset;
252
253 int frequency; // in Hz
254
255 void registerPort(Contactable& port, std::string name) {
256 if(port.open(name.c_str()) != true) {
257 std::string msg("could not register port ");
258 msg+=name;
259 throw std::runtime_error(msg);
260 }
261 }
262
263 void registerAllPorts() {
264 this->registerPort(this->train_out, this->portPrefix + "/train:o");
265 this->train_out.setStrict();
266 this->registerPort(this->predict_inout, this->portPrefix + "/predict:io");
267 }
268
269 void unregisterAllPorts() {
270 this->train_out.close();
271 this->predict_inout.close();
272 }
273
274public:
275 MachineLearnerTestModule(std::string pp = "/lm/test") : portPrefix(pp) {
276
277 }
278
279 void printOptions(std::string error = "") {
280 if(error != "") {
281 std::cout << "Error: " << error << std::endl;
282 }
283 std::cout << "Available options" << std::endl;
284 std::cout << "--help Display this help message" << std::endl;
285 std::cout << "--trainport port Data port for the training samples" << std::endl;
286 std::cout << "--predictport port Data port for the prediction samples" << std::endl;
287 std::cout << "--datafile file Filename containing the dataset" << std::endl;
288 std::cout << "--inputs (idx1, ..) List of indices to use as inputs" << std::endl;
289 std::cout << "--outputs (idx1, ..) List of indices to use as outputs" << std::endl;
290 std::cout << "--port pfx Prefix for registering the ports" << std::endl;
291 std::cout << "--frequency f Sampling frequency in Hz" << std::endl;
292 }
293
294 void printConfig() {
295 std::cout << "* - Configuration -" << std::endl;
296 std::cout << "* Datafile: " << this->dataset.getFilename() << std::endl;
297 std::cout << "* Input columns: " << printVector(this->dataset.getInputColumns()) << std::endl;
298 std::cout << "* Output columns: " << printVector(this->dataset.getOutputColumns()) << std::endl;
299 }
300
301
302 virtual bool configure(ResourceFinder& opt) {
303 // read for the general specifiers:
304 Value* val;
305
306 // check for help request
307 if(opt.check("help")) {
308 this->printOptions();
309 return false;
310 }
311
312 // check for port specifier: portSuffix
313 if(opt.check("port", val)) {
314 this->portPrefix = val->asString().c_str();
315 }
316
317 std::cout << "* Registering ports...";
318 this->registerAllPorts();
319 std::cout << "Done!" << std::endl;
320
321 // check for train data port
322 if(opt.check("trainport", val)) {
323 Network::connect(this->train_out.where().getName().c_str(), val->asString().c_str());
324 } else {
325 // add message here if necessary
326 }
327
328 // check for predict data port
329 if(opt.check("predictport", val)) {
330 Network::connect(this->predict_inout.where().getName().c_str(), val->asString().c_str());
331 } else {
332 // add message here if necessary
333 }
334
335 // check for filename of the dataset
336 if(opt.check("datafile", val)) {
337 //this->dataset.setFilename(val->asString().c_str());
338 this->dataset.open(val->asString().c_str());
339 }
340
341 // check for the columns of the dataset that should be used for inputs
342 if(opt.check("inputs", val)) {
343 // if it's a list,
344 if(val->isList()) {
345 Bottle* inputs = val->asList();
346 for(int i = 0; i < inputs->size(); i++) {
347 if(inputs->get(i).isInt32()) {
348 this->dataset.addInputColumn(inputs->get(i).asInt32());
349 }
350 }
351 } else if(val->isInt32()) {
352 this->dataset.addInputColumn(val->asInt32());
353 }
354 } else {
355 this->dataset.addInputColumn(1);
356 }
357
358 // check for the columns of the dataset that should be used for outputs
359 if(opt.check("outputs", val)) {
360 // if it's a list,
361 if(val->isList()) {
362 Bottle* outputs = val->asList();
363 for(int i = 0; i < outputs->size(); i++) {
364 if(outputs->get(i).isInt32()) {
365 this->dataset.addOutputColumn(outputs->get(i).asInt32());
366 }
367 }
368 } else if(val->isInt32()) {
369 this->dataset.addOutputColumn(val->asInt32());
370 }
371 } else {
372 this->dataset.addOutputColumn(2);
373 }
374
375 // check for port specifier: portSuffix
376 if(opt.check("frequency", val)) {
377 this->frequency = val->asInt32();
378 } else {
379 this->frequency = 0;
380 }
381
382 this->printConfig();
383
384 this->attachTerminal();
385
386 return true;
387 }
388
389 void sendTrainSample(Vector input, Vector output) {
390 PortablePair<Vector,Vector>& sample = this->train_out.prepare();
391 sample.head = input;
392 sample.body = output;
393 this->train_out.writeStrict();
394 }
395
397 Prediction prediction;
398 this->predict_inout.write(input, prediction);
399 return prediction;
400 }
401
403 Time::delay(1.);
404 return true;
405 }
406
407 bool respond(const Bottle& cmd, Bottle& reply) {
408 bool success = false;
409
410 try {
411 switch(cmd.get(0).asVocab32()) {
412 case yarp::os::createVocab32('h','e','l','p'): // print help information
413 success = true;
414 reply.add(Value::makeVocab32("help"));
415
416 reply.addString("Testing module configuration options");
417 reply.addString(" help Displays this message");
418 reply.addString(" conf Print configuration");
419 reply.addString(" train [n] Send training samples");
420 reply.addString(" predict [n] Send testing samples");
421 reply.addString(" skip [n] Skip samples");
422 reply.addString(" reset Reset dataset");
423 reply.addString(" shoot v1 ... vn Shoot a single prediction sample");
424 reply.addString(" open fname Opens a datafile");
425 reply.addString(" freq f Sampling frequency in Hertz (0 for disabled)");
426 break;
427 case yarp::os::createVocab32('c','o','n','f'): // print the configuration
428 {
429 success = true;
430 this->printConfig();
431 break;
432 }
433
434 case yarp::os::createVocab32('s','k','i','p'): // skip some training sample(s)
435 {
436 success = true;
437 int noSamples = 1;
438 if(cmd.get(1).isInt32()) {
439 noSamples = cmd.get(1).asInt32();
440 }
441
442 for(int i = 0; i < noSamples; i++) {
443 std::pair<Vector,Vector> sample = this->dataset.getNextSample();
444 // now do nothing with it... poor sample!
445 }
446 reply.addString("Done!");
447
448 break;
449 }
450
451 case yarp::os::createVocab32('t','r','a','i'): // send training sample(s)
452 {
453 reply.add(Value::makeVocab32("help"));
454 success = true;
455 int noSamples = 1;
456 if(cmd.get(1).isInt32()) {
457 noSamples = cmd.get(1).asInt32();
458 }
459
460 //double start = yarp::os::Time::now();
461 for(int i = 0; i < noSamples; i++) {
462 std::pair<Vector,Vector> sample = this->dataset.getNextSample();
463 this->sendTrainSample(sample.first, sample.second);
464
465 // sleep
466 if(this->frequency > 0)
467 yarp::os::Time::delay(1. / this->frequency);
468 }
469 // this timing only measures how fast the module can dump the samples on the network
470 //double end = yarp::os::Time::now();
471 //std::string reply_str = "Timing: " + doubletostring(noSamples / (end - start)) + " samples per second";
472 //reply.addString(reply_str.c_str());
473 reply.addString("Done!");
474
475 break;
476 }
477
478 case yarp::os::createVocab32('p','r','e','d'): // send prediction sample(s)
479 {
480 reply.add(Value::makeVocab32("help"));
481 success = true;
482 int noSamples = 1;
483 if(cmd.get(1).isInt32()) {
484 noSamples = cmd.get(1).asInt32();
485 }
486
487 // initiate error vector (not completely failsafe!)
488 Vector error(this->dataset.getOutputColumns().size());
489 Vector nmlp(this->dataset.getOutputColumns().size());
490 error = 0.0;
491 nmlp = 0.0;
492
493 // make predictions and keep track of errors (MSE)
494 double start = yarp::os::Time::now();
495 for(int i = 0; i < noSamples; i++) {
496 std::pair<Vector,Vector> sample = this->dataset.getNextSample();
497 Prediction prediction = this->sendPredictSample(sample.first);
498
499 //std::cout << "head[" << prediction.head.size() << "]: " << prediction.head.toString() << std::endl;
500 //std::cout << "variance[" << prediction.body.size() << "]: " << prediction.body.toString() << std::endl;
501
502 Vector expected = prediction.head;
503 if(expected.size() != sample.second.size()) {
504 std::string msg("incoming prediction has incorrect dimension");
505 throw std::runtime_error(msg);
506 }
507 for(size_t j = 0; j < error.size(); j++) {
508 double dist = sample.second[j] - expected[j];
509 error[j] += (dist * dist);
510 }
511
512 Vector variance = prediction.body;
513 if(variance.size() > 0 && variance.size() != sample.second.size()) {
514 std::string msg("incoming predictive variance has invalid dimension");
515 throw std::runtime_error(msg);
516 }
517 for(size_t j = 0; j < nmlp.size(); j++) {
518 double normdist = (sample.second[j] - expected[j]) / variance[j];
519 nmlp[j] += 0.5 * ((normdist * normdist) + log(TWOPI) + 2 * log(variance[j]));
520 }
521
522 // sleep
523 if(this->frequency > 0)
524 yarp::os::Time::delay(1. / this->frequency);
525 }
526 double end = yarp::os::Time::now();
527 std::string reply_str = "Timing: " + doubletostring(noSamples / (end - start)) + " samples per second";
528 reply.addString(reply_str.c_str());
529
530 // take mean of cumulated errors
531 for(size_t i = 0; i < error.size(); i++) {
532 error[i] = error[i] / double(noSamples);
533 }
534 for(size_t i = 0; i < error.size(); i++) {
535 nmlp[i] = nmlp[i] / double(noSamples);
536 }
537
538 reply_str = "MSE: " + printVector(error);
539 reply.addString(reply_str.c_str());
540
541 reply_str = "NMLP: " + printVector(nmlp);
542 reply.addString(reply_str.c_str());
543
544 break;
545 }
546
547 case yarp::os::createVocab32('s','h','o','o'):
548 {
549 success = true;
550 Vector input;
551 for(int i = 1; i <= cmd.size(); i++) {
552 if(cmd.get(i).isFloat64() || cmd.get(i).isInt32()) {
553 input.push_back(cmd.get(i).asFloat64());
554 }
555 }
556 Prediction prediction = this->sendPredictSample(input);
557 std::string reply_str = printVector(input) + " -> " + printPrediction(prediction);
558 reply.addString(reply_str.c_str());
559
560 break;
561 }
562
563 case yarp::os::createVocab32('o','p','e','n'):
564 success = true;
565 if(cmd.get(1).isString()) {
566 std::cout << "Open..." << std::endl;
567 this->dataset.open(cmd.get(1).asString().c_str());
568 }
569 reply.addString((std::string("Opened dataset: ") + cmd.get(1).asString().c_str()).c_str());
570 break;
571
572 case yarp::os::createVocab32('r','e','s','e'):
573 case yarp::os::createVocab32('r','s','t'):
574 success = true;
575 this->dataset.reset();
576 reply.addString("Dataset reset to beginning");
577 break;
578
579 case yarp::os::createVocab32('f','r','e','q'): // set sampling frequency
580 {
581 if(cmd.size() > 1 && cmd.get(1).isInt32()) {
582 success = true;
583 this->frequency = cmd.get(1).asInt32();
584 reply.addString((std::string("Current frequency: ") + inttostring(this->frequency)).c_str());
585 }
586 break;
587 }
588
589 case yarp::os::createVocab32('s','e','t'): // set some configuration options
590 // implement some options
591 break;
592
593 default:
594 break;
595 }
596 } catch(const std::exception& e) {
597 success = true; // to make sure YARP prints the error message
598 std::string msg = std::string("Error: ") + e.what();
599 reply.addString(msg.c_str());
600 } catch(...) {
601 success = true; // to make sure YARP prints the error message
602 std::string msg = std::string("Error. (something bad happened, but I wouldn't know what!)");
603 reply.addString(msg.c_str());
604 }
605
606 return success;
607 }
608
609 bool close() {
610 this->unregisterAllPorts();
611 return true;
612 }
613
614};
615
616} // test
617} // learningmachine
618} // iCub
619
620using namespace iCub::learningmachine::test;
621
622int main(int argc, char *argv[]) {
623 Network yarp;
624 int ret;
625
626 ResourceFinder rf;
627 rf.setDefaultContext("learningMachine");
628 rf.configure(argc, argv);
629
631 try {
632 ret = module.runModule(rf);
633 //module.attachTerminal();
634 } catch(const std::exception& e) {
635 std::cerr << "Error: " << e.what() << std::endl;
636 module.close();
637 return 1;
638 } catch(char* msg) {
639 std::cerr << "Error: " << msg << std::endl;
640 module.close();
641 return 1;
642 }
643 return ret;
644}
645
#define TWOPI
void open(std::string filename)
Definition test.cpp:161
std::vector< int > getOutputColumns()
Definition test.cpp:141
std::pair< Vector, Vector > getNextSample()
Definition test.cpp:189
std::vector< int > getInputColumns()
Definition test.cpp:137
void setFilename(std::string filename)
Definition test.cpp:151
The MachineLearnerTestModule is used to feed datasets loaded from a file to the learning machines and...
Definition test.cpp:244
virtual bool configure(ResourceFinder &opt)
Definition test.cpp:302
void sendTrainSample(Vector input, Vector output)
Definition test.cpp:389
bool respond(const Bottle &cmd, Bottle &reply)
Definition test.cpp:407
MachineLearnerTestModule(std::string pp="/lm/test")
Definition test.cpp:275
cmd
Definition dataTypes.h:30
bool error
FILE * file
Definition main.cpp:81
int main()
Definition main.cpp:67
std::string printPrediction(Prediction &p)
Definition test.cpp:89
std::string printVector(const std::vector< int > &v)
Definition test.cpp:67
PortablePair< Vector, Vector > Prediction
Definition test.cpp:52
std::string doubletostring(double d)
Definition test.cpp:55
std::string inttostring(int i)
Definition test.cpp:61
This file contains the definition of unique IDs for the body parts and the skin parts of the robot.
Copyright (C) 2008 RobotCub Consortium.