Feature Selection Toolbox FST4
UTIA · Czech Academy of Sciences
Loading...
Searching...
No Matches
classifier_extern.hpp
Go to the documentation of this file.
1#ifndef FSTCLASSIFIEREXTERN_H
2#define FSTCLASSIFIEREXTERN_H
3 /*
28=========================================================================
29Copyright and license:
30 * FST4 software (with the exception of any bundled or externally linked
31 third-party components) is Copyright (C) 2000-present Institute of
32 Information Theory and Automation of the Czech Academy of Sciences (UTIA).
33 * FST4 is free software: you can redistribute it and/or modify it under
34 the terms of the GNU Affero General Public License as published by the
35 Free Software Foundation, version 3. The full text of the license is in
36 the file LICENSE in the distribution root and at
37 https://www.gnu.org/licenses/agpl-3.0.html.
38 * FST4 is distributed in the hope that it will be useful, but WITHOUT ANY
39 WARRANTY; without even the implied warranty of MERCHANTABILITY or
40 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
41 License for more details.
42 * Closed-source use without the AGPL's copyleft obligations requires a
43 commercial license from UTIA; in case of interest write to
44 utia@utia.cas.cz. LICENSING.md in the distribution root explains both
45 options, the contributor license agreement that every contribution
46 requires, and the contacts (technical questions and pull requests:
47 somol@utia.cas.cz, with "FST" in the subject line).
48========================================================================== */
49
50#include <memory>
51#include <vector>
52#include <string>
53#include <cstdio>
54#include <cstdlib>
55#include <cassert>
56#include <iostream>
57#include "error.hpp"
58#include "global.hpp"
59#include "clonable.hpp"
60#include "classifier.hpp"
61#include "extern_bridge.hpp" // the platform-sensitive process plumbing, shared with Criterion_Extern_Value
62
63/*============== Template parameter type naming conventions ==============
64--------- Numeric types: -------------------------------------------------
65DATATYPE - data sample values - usually real numbers (but may be integers
66 in text processing etc.)
67REALTYPE - must be real numbers - for representing intermediate results of
68 calculations like mean, covariance etc.
69IDXTYPE - index values for enumeration of data samples - (nonnegative) integers,
70 extent depends on numbers of samples in data
71DIMTYPE - index values for enumeration of features (dimensions), or classes (not
72 class sizes) - (nonnegative) integers, usually lower extent than IDXTYPE,
73 but be aware of expressions like _classes*_features*_features !
74 in linearized representations of feature matrices for all classes
75BINTYPE - feature selection marker type - represents ca. <10 different feature
76 states (selected, deselected, sel./desel. temporarily 1st nested loop, 2nd...)
77RETURNTYPE - criterion value: real value, but may be extended in future to support
78 multiple values
79--------- Class types: ---------------------------------------------------
80SUBSET - class of class type Subset
81CLASSIFIER - class implementing interface defined in abstract class Classifier
82EVALUATOR - class implementing interface defined in abstract class Sequential_Step
83DISTANCE - class implementing interface defined in abstract class Distance
84DATAACCESSOR - class implementing interface defined in abstract class Data_Accessor
85INTERVALCONTAINER - class of class type Data_Intervaller
86CONTAINER - STL container of class type Data_Interval
87========================================================================== */
88
89namespace FST {
90
145template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
146class Classifier_Extern : public Clonable_Impl<Classifier_Extern<RETURNTYPE,DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>,Classifier<RETURNTYPE,DIMTYPE,SUBSET,DATAACCESSOR> > {
147public:
150 typedef std::shared_ptr<DATAACCESSOR> PDataAccessor;
151 typedef std::shared_ptr<SUBSET> PSubset;
152 typedef typename DATAACCESSOR::PPattern PPattern;
153 Classifier_Extern() : _bridge("Classifier_Extern"), _classes(0), _features(0), _subfeatures(0) {notify("Classifier_Extern constructor.");}
154 virtual ~Classifier_Extern() {notify("Classifier_Extern destructor.");} // the bridge destructor shuts the child down
155
156 void set_command(const std::string &cmd) {_bridge.set_command(cmd);}
157 const std::string& get_command() const {return _bridge.get_command();}
158
163 void initialize(const PDataAccessor da);
164
169 virtual bool classify(DIMTYPE &cls, const PPattern &pattern) override;
177 virtual bool train(const PDataAccessor da, const PSubset sub) override;
181 virtual bool test(RETURNTYPE &result, const PDataAccessor da) override;
186 virtual bool test_confusion(std::vector<RETURNTYPE> &confusion, DIMTYPE &classes, const PDataAccessor da) override;
187
188 virtual std::ostream& print(std::ostream& os) const override {os << "Classifier_Extern(cmd=\"" << _bridge.get_command() << "\")"; return os;}
189private:
190 friend parent_impl;
196 Classifier_Extern(const Classifier_Extern& ce) :
197 _bridge("Classifier_Extern"), // clones NEVER share pipes — each fresh bridge spawns its own child lazily
198 _classes(ce._classes),
201 _index(ce._index)
202 {
203 _bridge.set_command(ce._bridge.get_command());
204 notify("Classifier_Extern copy-constructor.");
205 }
206protected:
208 DIMTYPE _classes;
209 DIMTYPE _features;
210 DIMTYPE _subfeatures;
211 std::vector<DIMTYPE> _index;
212
218 void send_row(const DIMTYPE label_or_none, const bool with_label, const PPattern &pattern);
224 long parse_prediction(const std::string &line);
232 bool run_test_block(std::vector<DIMTYPE> &predictions, const PDataAccessor da);
233};
234
235template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
237{
238 notify("Classifier_Extern::initialize().");
239 assert(da);
240 assert(da->getNoOfFeatures()>0);
241 if(da->getNoOfClasses()<2) throw fst_error("Classifier_Extern requires at least 2 classes");
242 if(_bridge.get_command().empty()) throw fst_error("Classifier_Extern: no command set (see --extern-cmd)");
243 _classes=da->getNoOfClasses();
244 _features=da->getNoOfFeatures();
245 _index.resize(_features);
246 _subfeatures=0; // no model yet
247}
248
249// (the process spawn/shutdown/line-I/O machinery lives in extern_bridge.hpp, shared with
250// Criterion_Extern_Value; its error messages are prefixed with the component name)
251template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
252void Classifier_Extern<RETURNTYPE,DATATYPE,REALTYPE,IDXTYPE,DIMTYPE,SUBSET,DATAACCESSOR>::send_row(const DIMTYPE label_or_none, const bool with_label, const PPattern &pattern)
253{
254 std::FILE *out=_bridge.to_child();
255 char buf[64];
256 if(with_label) {std::snprintf(buf,sizeof(buf),"%ld",(long)label_or_none); std::fputs(buf,out);}
257 for(DIMTYPE f=0;f<_subfeatures;f++)
258 {
259 std::snprintf(buf,sizeof(buf),"%.17g",(double)pattern[_index[f]]); // bit round-trip for doubles
260 if(with_label || f>0) std::fputc(' ',out);
261 std::fputs(buf,out);
262 }
263 if(std::fputc('\n',out)==EOF)
264 _bridge.die_on_write();
265}
266
267template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
269{
270 // strict full-line numeric parse: a bare strtol would accept any garbage line (a stray child
271 // diagnostic, an "ERROR ..." message) as a VALID class 0 prediction and silently produce
272 // wrong results; the TRAIN path fails loudly on non-OK, TEST must be equally loud
273 const char *s=line.c_str();
274 char *end=0;
275 const long v=std::strtol(s,&end,10);
276 if(end==s || *end!='\0')
277 {_bridge.shutdown_process(); throw fst_error("Classifier_Extern: '"+_bridge.get_command()+"' returned a non-numeric prediction line: '"+line+"'");}
278 return v;
279}
280
281template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
283{
284 notify("Classifier_Extern::train().");
285 assert(da);
286 assert(sub);
287 assert(_classes==da->getNoOfClasses());
288 assert(_features==da->getNoOfFeatures());
289 assert(sub->get_n_raw()==da->getNoOfFeatures());
290
291 // buffer the feature subset index
292 _subfeatures=0;
293 for(DIMTYPE fsel : sub->selected_features()) {assert(_subfeatures<_features); _index[_subfeatures++]=fsel;}
294 assert(_subfeatures==sub->get_d_raw());
295 assert(_subfeatures>0);
296
297 _bridge.ensure_process();
298
299 // header first: count the training patterns (cheap block-size sweep)
300 unsigned long long n=0;
301 {typename DATAACCESSOR::PPattern p; IDXTYPE s,i; (void)p; (void)i;
302 for(DIMTYPE c=0;c<_classes;c++) {da->setClass(c); for(const auto &blk : da->blocks(TRAIN)) {s=blk.size; n+=(unsigned long long)s;}}}
303 if(n==0) return false;
304 _bridge.send_line("TRAIN "+std::to_string(n)+" "+std::to_string((long)_subfeatures)+" "+std::to_string((long)_classes));
305
306 typename DATAACCESSOR::PPattern p;
307 IDXTYPE s,i;
308 const DIMTYPE stride=da->getNoOfFeatures();
309 for(DIMTYPE c=0;c<_classes;c++)
310 {
311 da->setClass(c);
312 for(const auto &blk : da->blocks(TRAIN)) for(i=0,p=blk.data,s=blk.size;i<s;i++)
313 send_row(c,true,&p[(std::size_t)i*stride]);
314 }
315 _bridge.flush_to_child();
316 const std::string resp=_bridge.read_line();
317 if(resp!="OK") throw fst_error("Classifier_Extern: '"+_bridge.get_command()+"' rejected TRAIN: "+resp);
318 return true;
319}
320
321template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
323{
324 assert(_subfeatures>0); // train() must have succeeded
325 _bridge.ensure_process();
326 _bridge.send_line("TEST 1 "+std::to_string((long)_subfeatures));
327 send_row(0,false,pattern);
328 _bridge.flush_to_child();
329 const long v=parse_prediction(_bridge.read_line());
330 if(v<0 || v>=(long)_classes) {_bridge.shutdown_process(); return false;} // desynced/misbehaving child: respawn on next use
331 cls=(DIMTYPE)v;
332 return true;
333}
334
335template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
337{
338 // one batched TEST block over the designated test part; prediction order =
339 // the canonical enumeration (class-major, block order) shared by all classifiers
340 assert(da);
341 assert(_subfeatures>0);
342 _bridge.ensure_process();
343 unsigned long long m=0;
344 {IDXTYPE s; for(DIMTYPE c=0;c<_classes;c++) {da->setClass(c); for(const auto &blk : da->blocks(TEST)) {s=blk.size; m+=(unsigned long long)s;}}}
345 if(m==0) return false;
346 _bridge.send_line("TEST "+std::to_string(m)+" "+std::to_string((long)_subfeatures));
347 typename DATAACCESSOR::PPattern p;
348 IDXTYPE s,i;
349 const DIMTYPE stride=da->getNoOfFeatures();
350 for(DIMTYPE c=0;c<_classes;c++)
351 {
352 da->setClass(c);
353 for(const auto &blk : da->blocks(TEST)) for(i=0,p=blk.data,s=blk.size;i<s;i++)
354 send_row(0,false,&p[(std::size_t)i*stride]);
355 }
356 _bridge.flush_to_child();
357 predictions.clear();
358 predictions.reserve((std::size_t)m);
359 for(unsigned long long k=0;k<m;k++)
360 {
361 const long v=parse_prediction(_bridge.read_line());
362 if(v<0 || v>=(long)_classes) {
363 // the remaining m-k-1 response lines stay unread - a desynced child would make
364 // the NEXT round-trip fail with a misleading "rejected TRAIN: <number>" error;
365 // drop the child instead, the next evaluation respawns it cleanly
366 _bridge.shutdown_process();
367 return false;
368 }
369 predictions.push_back((DIMTYPE)v);
370 }
371 return true;
372}
373
374template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
376{
377 notify("Classifier_Extern::test().");
378 std::vector<DIMTYPE> pred;
379 if(!run_test_block(pred,da)) return false;
380 // tally against true labels in the same canonical enumeration order
381 unsigned long long count=0, correct=0;
382 IDXTYPE s,i;
383 for(DIMTYPE c_test=0;c_test<_classes;c_test++)
384 {
385 da->setClass(c_test);
386 for(const auto &blk : da->blocks(TEST)) for(i=0,s=blk.size;i<s;i++)
387 {
388 if(pred[count]==c_test) correct++;
389 count++;
390 }
391 }
392 assert(count==pred.size());
393 if(count==0) return false;
394 result=(RETURNTYPE)correct/(RETURNTYPE)count;
395 return true;
396}
397
398template<class RETURNTYPE, typename DATATYPE, typename REALTYPE, typename IDXTYPE, typename DIMTYPE, class SUBSET, class DATAACCESSOR>
400{
401 // batched override: ONE TEST round-trip instead of the base default's per-pattern classify()
402 notify("Classifier_Extern::test_confusion().");
403 std::vector<DIMTYPE> pred;
404 if(!run_test_block(pred,da)) return false;
405 classes=_classes;
406 confusion.assign((std::size_t)_classes*(std::size_t)_classes,(RETURNTYPE)0.0);
407 unsigned long long count=0;
408 IDXTYPE s,i;
409 for(DIMTYPE c_test=0;c_test<_classes;c_test++)
410 {
411 da->setClass(c_test);
412 for(const auto &blk : da->blocks(TEST)) for(i=0,s=blk.size;i<s;i++)
413 {
414 confusion[(std::size_t)c_test*(std::size_t)_classes+(std::size_t)pred[count]]+=1.0;
415 count++;
416 }
417 }
418 assert(count==pred.size());
419 return count>0;
420}
421
422} // namespace
423#endif // FSTCLASSIFIEREXTERN_H ///:~
Bridge classifier delegating training and prediction to a user-supplied external command.
Definition classifier_extern.hpp:146
DIMTYPE _subfeatures
d = current subset size
Definition classifier_extern.hpp:210
std::shared_ptr< SUBSET > PSubset
shared pointer to the feature subset being trained/evaluated
Definition classifier_extern.hpp:151
virtual bool classify(DIMTYPE &cls, const PPattern &pattern) override
Definition classifier_extern.hpp:322
DIMTYPE _classes
class count, set by initialize()
Definition classifier_extern.hpp:208
DATAACCESSOR::PPattern PPattern
one data sample as exposed by DATAACCESSOR
Definition classifier_extern.hpp:152
void send_row(const DIMTYPE label_or_none, const bool with_label, const PPattern &pattern)
Writes one %.17g-formatted data row over the selected subset to the child's stdin....
Definition classifier_extern.hpp:252
DIMTYPE _features
raw (full, unnarrowed) feature count, set by initialize()
Definition classifier_extern.hpp:209
bool run_test_block(std::vector< DIMTYPE > &predictions, const PDataAccessor da)
Runs one batched TEST round-trip over the designated test part, shared by test()/test_confusion(): se...
Definition classifier_extern.hpp:336
Classifier< RETURNTYPE, DIMTYPE, SUBSET, DATAACCESSOR > parent
the immediate abstract base
Definition classifier_extern.hpp:148
Clonable_Impl< Classifier_Extern, parent > parent_impl
implements clone() via the copy-constructor below
Definition classifier_extern.hpp:149
virtual bool train(const PDataAccessor da, const PSubset sub) override
Streams the designated training part as one TRAIN block: buffers the subset index,...
Definition classifier_extern.hpp:282
std::shared_ptr< DATAACCESSOR > PDataAccessor
shared pointer to the data accessor supplying train/test blocks
Definition classifier_extern.hpp:150
const std::string & get_command() const
returns the configured command line
Definition classifier_extern.hpp:157
void initialize(const PDataAccessor da)
Records the data shape and allocates the index buffer. Must be called before train()....
Definition classifier_extern.hpp:236
void set_command(const std::string &cmd)
sets the external command line (--extern-cmd); the bridge asserts it is not already running
Definition classifier_extern.hpp:156
virtual bool test(RETURNTYPE &result, const PDataAccessor da) override
Definition classifier_extern.hpp:375
std::vector< DIMTYPE > _index
subset position -> raw feature index (first d entries valid)
Definition classifier_extern.hpp:211
Classifier_Extern(const Classifier_Extern &ce)
Copy-constructor backing clone(): copies shape/config (class and feature counts, the narrowing _index...
Definition classifier_extern.hpp:196
virtual std::ostream & print(std::ostream &os) const override
writes a human-readable identification of the object; base default writes nothing
Definition classifier_extern.hpp:188
long parse_prediction(const std::string &line)
Strict full-line numeric parse of one predicted class index: the ENTIRE line must be consumed by strt...
Definition classifier_extern.hpp:268
virtual bool test_confusion(std::vector< RETURNTYPE > &confusion, DIMTYPE &classes, const PDataAccessor da) override
Definition classifier_extern.hpp:399
Extern_Bridge _bridge
persistent child process + line I/O (per instance; never shared by clones)
Definition classifier_extern.hpp:207
abstract class, defines interface for classifier implementations (mainly to be used in wrappers)
Definition classifier.hpp:97
CRTP mixin implementing the Clonable protocol for a concrete class.
Definition clonable.hpp:184
Persistent-child-process line-protocol plumbing shared by the external-tool bridges: Classifier_Exter...
Definition extern_bridge.hpp:105
const std::string & get_command() const
returns the configured command line
Definition extern_bridge.hpp:113
FST4 specific error passing class.
Definition error.hpp:61
Defines interface for classifier implementations.
Defines interface for classes that may need to get cloned.
FST4 specific error passing class.
Persistent-child-process line-protocol plumbing shared by the external-tool bridges (Classifier_Exter...
Global definitions.
The Feature Selection Toolbox 4 library: every library component – data access, criteria and classifi...
Definition data_accessor.hpp:80
void notify(const std::string msg)
Prints msg followed by a newline via syncout::print(); a no-op unless the preprocessor macro DEBUG is...
Definition global.hpp:112
@ TEST
the split's test part
Definition data_accessor.hpp:86
@ TRAIN
the split's training part
Definition data_accessor.hpp:85
Copyright © 2000–present ÚTIA, Institute of Information Theory and Automation of the Czech Academy of Sciences · Contacts · This site sets no cookies and uses no analytics.