Feature Selection Toolbox FST4
UTIA · Czech Academy of Sciences
Loading...
Searching...
No Matches
seq_step_sampled.hpp
Go to the documentation of this file.
1#ifndef FSTSEARCHSEQSTEPSAMPLED_H
2#define FSTSEARCHSEQSTEPSAMPLED_H
3 /*
27=========================================================================
28Copyright and license:
29 * FST4 software (with the exception of any bundled or externally linked
30 third-party components) is Copyright (C) 2000-present Institute of
31 Information Theory and Automation of the Czech Academy of Sciences (UTIA).
32 * FST4 is free software: you can redistribute it and/or modify it under
33 the terms of the GNU Affero General Public License as published by the
34 Free Software Foundation, version 3. The full text of the license is in
35 the file LICENSE in the distribution root and at
36 https://www.gnu.org/licenses/agpl-3.0.html.
37 * FST4 is distributed in the hope that it will be useful, but WITHOUT ANY
38 WARRANTY; without even the implied warranty of MERCHANTABILITY or
39 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
40 License for more details.
41 * Closed-source use without the AGPL's copyleft obligations requires a
42 commercial license from UTIA; in case of interest write to
43 utia@utia.cas.cz. LICENSING.md in the distribution root explains both
44 options, the contributor license agreement that every contribution
45 requires, and the contacts (technical questions and pull requests:
46 somol@utia.cas.cz, with "FST" in the subject line).
47========================================================================== */
48
49#include <memory>
50#include <vector>
51#include <algorithm>
52#include <cmath>
53#include <cstdlib>
54#include <cassert>
55#include <iostream>
56#include <sstream>
57#include "error.hpp"
58#include "global.hpp"
59#include "fst_rand.hpp" // FST-owned PRNG: identical random sequences on every platform
60#include "seq_step.hpp"
64
65/*============== Template parameter type naming conventions ==============
66--------- Numeric types: -------------------------------------------------
67RETURNTYPE - criterion value: real value
68DIMTYPE - index values for enumeration of features (dimensions)
69--------- Class types: ---------------------------------------------------
70SUBSET - class of class type Subset
71CRITERION - class implementing interface defined in abstract class Criterion
72WEIGHTER - Result_Weighter_Sampled instantiation (the statistics engine)
73========================================================================== */
74
75namespace FST {
76
155template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION, class WEIGHTER>
156class Sequential_Step_Sampled : public Sequential_Step<RETURNTYPE,DIMTYPE,SUBSET,CRITERION>{
157public:
158 typedef Sequential_Step<RETURNTYPE,DIMTYPE,SUBSET,CRITERION> parent;
159 typedef std::shared_ptr<CRITERION> PCriterion;
160 typedef std::shared_ptr<SUBSET> PSubset;
161 typedef std::shared_ptr<WEIGHTER> PWeighter;
163 typedef std::shared_ptr<EVALUATORENGINE> PEvaluatorEngine;
170 enum SamplerMode { SAMPLER_SOFTMAX, SAMPLER_UNIFORM, SAMPLER_TOPK };
184 Sequential_Step_Sampled(PWeighter stats, const DIMTYPE cap=100, const double explore=0.2, const double tau=0.0, const SamplerMode sampler=SAMPLER_SOFTMAX, PEvaluatorEngine engine=PEvaluatorEngine())
185 : Sequential_Step<RETURNTYPE,DIMTYPE,SUBSET,CRITERION>(), _stats(stats), _engine(engine), _cap(cap),
186 _explore(explore<0.0?0.0:(explore>1.0?1.0:explore)), _tau(tau), _sampler(sampler),
188 {
189 if(!_stats) _stats.reset(new WEIGHTER);
191 notify("Sequential_Step_Sampled constructor.");
192 }
193 virtual ~Sequential_Step_Sampled() {notify("Sequential_Step_Sampled destructor.");}
194
199 void set_cap_frac(const double frac) {_cap_frac=(frac<0.0?0.0:(frac>1.0?1.0:frac));}
200 double get_cap_frac() const {return _cap_frac;}
201
205 void set_cap_backward(const DIMTYPE cap) {_cap_backward=cap;}
206 DIMTYPE get_cap_backward() const {return _cap_backward;}
207
220 inline bool evaluate_candidates(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE _generalization_level=1, std::ostream& os=std::cout) override;
221
223
228 virtual std::ostream& print(std::ostream& os) const override {
229 os << "Sequential_Step_Sampled(cap=";
230 if(_cap_frac>0.0) os << "frac:" << _cap_frac; else os << _cap;
231 if(_cap_backward>0) os << ", cap_backward=" << _cap_backward;
232 os << ", explore=" << _explore << ", tau=";
233 if(_tau>0.0) os << _tau; else os << "auto";
234 os << ", sampler=" << (_sampler==SAMPLER_SOFTMAX?"softmax":(_sampler==SAMPLER_UNIFORM?"uniform":"topk"));
235 os << ", stats=" << (_stats->get_mode()==WEIGHTER::STATS_ZCONTRAST?"zcontrast":"rawmean") << ", decay=" << _stats->get_horizon();
236 if(_engine->is_threaded()) os << ", threads=" << _engine->get_max_threads();
237 os << ")";
238 if(parent::result_tracker_active()) os << " with " << *parent::_tracker;
239 return os;
240 }
241
242 virtual bool print_summary(std::ostream& os=std::cout) {assert(_stats); _stats->print_stats(os); return true;}
243
244protected:
247 DIMTYPE _cap;
248 DIMTYPE _cap_backward=0;
249 double _cap_frac=0.0;
250 double _explore;
251 double _tau;
253
254private:
266 inline bool full_sweep(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE g, const DIMTYPE pool, std::ostream& os);
279 inline bool sampled_forward(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE pool, const DIMTYPE cap, std::ostream& os);
291 inline bool sampled_backward(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE pool, const DIMTYPE cap, std::ostream& os);
300 inline void note_step(const bool forward, const DIMTYPE order, const DIMTYPE cnt, const DIMTYPE pool, std::ostream& os);
301 double frand() {return (double)fst_rand()/((double)FST_RAND_MAX+1.0);}
302
304
305 // instrumentation: at which position in the proposal order do step winners appear.
306 // All four counters plus all_evals accumulate over the LIFETIME of this object and are
307 // never reset; reuse the same step object across searches only if that is what you want
308 // the diagnostic averages to describe.
311 RETURNTYPE order_forward;
312 RETURNTYPE order_backward;
313 unsigned long long all_evals;
314};
315
316template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION, class WEIGHTER>
317bool Sequential_Step_Sampled<RETURNTYPE,DIMTYPE,SUBSET,CRITERION,WEIGHTER>::evaluate_candidates(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE _generalization_level, std::ostream& os)
318{
319 assert(sub);
320 assert(crit);
321 assert(_stats);
322 assert(_generalization_level>=1 && _generalization_level<=sub->get_n());
323 if(_stats->get_n()!=sub->get_n()) _stats->reset(sub->get_n()); // first use (a warmed-up engine already matches n and is kept)
324 const bool forward=sub->get_forward_mode();
325 const DIMTYPE pool = forward ? (DIMTYPE)(sub->get_n()-sub->get_d()) : sub->get_d();
326 if(pool==0) return false;
327 // frozen-mode subsets carry protected features the raw-flag scans below must not touch —
328 // the classic candidate traversal handles them transparently, so fall back to it
329 if(sub->get_frozen_mode())
330 return full_sweep(result,sub,crit,_generalization_level,pool,os);
331 if(forward) {
332 // effective per-step cap: constant count by default, ceil(frac*pool) under the
333 // constant-percentage schedule
334 DIMTYPE cap=_cap;
335 if(_cap_frac>0.0) {cap=(DIMTYPE)std::ceil(_cap_frac*(double)pool); if(cap<1) cap=1;}
336 if(cap==0 || pool<=cap || _generalization_level>1)
337 return full_sweep(result,sub,crit,_generalization_level,pool,os);
338 return sampled_forward(result,sub,crit,pool,cap,os);
339 }
340 // backward: budgeted symmetrically when y_b is set; y_b==0 keeps full backward sweeps
341 // and thereby the exact floating correction
342 if(_cap_backward==0 || pool<=_cap_backward || _generalization_level>1)
343 return full_sweep(result,sub,crit,_generalization_level,pool,os);
344 return sampled_backward(result,sub,crit,pool,_cap_backward,os);
345}
346
347template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION, class WEIGHTER>
348bool Sequential_Step_Sampled<RETURNTYPE,DIMTYPE,SUBSET,CRITERION,WEIGHTER>::full_sweep(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE g, const DIMTYPE pool, std::ostream& os)
349{
350 const bool forward=sub->get_forward_mode();
351 const bool track=parent::result_tracker_active();
352 // per-candidate side effects ride the engine's ResultCallback: the sequential engine
353 // fires it synchronously inside add_candidate, the threaded engine fires it in
354 // add-order on this thread when the batch completes - either way the
355 // statistics/tracker/instrumentation see candidates in sweep order
356 RETURNTYPE bestval=0;
357 DIMTYPE order=0,cnt=0;
358 bool best_available=false,stats_ok=true;
359 typename EVALUATORENGINE::ResultCallback on_result=[this,track,&bestval,&order,&cnt,&best_available,&stats_ok](const RETURNTYPE val, const PSubset &csub, const DIMTYPE) {
360 all_evals++;
361 if(!_stats->add(val,csub)) stats_ok=false;
362 if(track) parent::_tracker->add(val,csub);
363 cnt++;
364 if(!best_available || val>bestval) {bestval=val; order=cnt; best_available=true;}
365 };
366 // the candidate count is needed only by the threaded engine, to size its result
367 // storage (same convention as Sequential_Step_Straight)
368 const DIMTYPE max_candidates = _engine->is_threaded() ? sub->getNoOfCandidateSubsets(g,false/*reverse*/) : 1;
369 _engine->initialize(crit,max_candidates,on_result);
370 bool b=sub->getFirstCandidateSubset(g,false/*reverse*/);
371 if(!b) return false;
372 if(parent::output_detailed()) {std::ostringstream sos; sos << "Step (full)...: sub=" << *sub << std::endl << std::flush; syncout::print(os,sos);}
373 if(!_engine->add_candidate(sub)) return false;
374 b=sub->getNextCandidateSubset();
375 while(b) {
376 if(parent::output_detailed()) {std::ostringstream sos; sos << "Step (full)...: sub=" << *sub << std::endl << std::flush; syncout::print(os,sos);}
377 if(!_engine->add_candidate(sub)) return false;
378 b=sub->getNextCandidateSubset();
379 }
380 PSubset psub;
381 DIMTYPE dummyfeature;
382 if(!_engine->getBestResult(result,psub,dummyfeature)) return false;
383 if(!stats_ok) return false;
384 _stats->flush_batch();
385 assert(psub && psub->get_n()==sub->get_n());
386 sub->stateless_copy(*psub);
387 note_step(forward,order,cnt,pool,os);
388 return true;
389}
390
391template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION, class WEIGHTER>
392bool Sequential_Step_Sampled<RETURNTYPE,DIMTYPE,SUBSET,CRITERION,WEIGHTER>::sampled_forward(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE pool, const DIMTYPE cap, std::ostream& os)
393{
394 const DIMTYPE n=sub->get_n();
395 const bool track=parent::result_tracker_active();
396 assert(cap>0 && pool>cap);
397 // free features (forward mode => raw selection flags read literally)
398 std::vector<DIMTYPE> freef;
399 freef.reserve(pool);
400 for(DIMTYPE f=0;f<n;f++) if(!sub->selected_raw(f)) freef.push_back(f);
401 assert((DIMTYPE)freef.size()==pool);
402 const DIMTYPE want=cap;
403 std::vector<DIMTYPE> prop;
404 prop.reserve(want);
405 std::vector<char> taken(n,0);
406 if(_sampler==SAMPLER_TOPK) {
407 // hard cutoff: the `cap` highest-scored free features (no exploration floor)
408 std::vector<DIMTYPE> byscore(freef);
409 std::partial_sort(byscore.begin(),byscore.begin()+want,byscore.end(),
410 [this](const DIMTYPE a,const DIMTYPE b){const RETURNTYPE sa=_stats->score(a),sb=_stats->score(b); return sa>sb || (sa==sb && a<b);});
411 for(DIMTYPE i=0;i<want;i++) prop.push_back(byscore[i]);
412 } else {
413 DIMTYPE u = (_sampler==SAMPLER_UNIFORM) ? want : (DIMTYPE)(_explore*(double)want+0.5);
414 if(u>want) u=want;
415 if(u>0) {
416 // exploration floor: uniform draws from the least-evaluated features
417 // (SAMPLER_UNIFORM: uniform draws from the WHOLE pool - the stochastic-greedy baseline)
418 std::vector<DIMTYPE> bycnt(freef);
419 const DIMTYPE le = (_sampler==SAMPLER_UNIFORM) ? pool : std::min<DIMTYPE>(pool,std::max<DIMTYPE>(4*u,u));
420 if(le<pool)
421 std::partial_sort(bycnt.begin(),bycnt.begin()+le,bycnt.end(),
422 [this](const DIMTYPE a,const DIMTYPE b){const auto ca=_stats->count_is(a),cb=_stats->count_is(b); return ca<cb || (ca==cb && a<b);});
423 for(DIMTYPE k=0;k<u;k++) { // partial Fisher-Yates over the least-evaluated prefix
424 const DIMTYPE j=k+(DIMTYPE)(frand()*(double)(le-k));
425 std::swap(bycnt[k],bycnt[j]);
426 prop.push_back(bycnt[k]);
427 taken[bycnt[k]]=1;
428 }
429 }
430 const DIMTYPE rest=want-u;
431 if(rest>0) {
432 // weighted draws without replacement ~ softmax(score/tau) over the untaken pool
433 std::vector<DIMTYPE> cand;
434 cand.reserve(pool-u);
435 for(size_t i=0;i<freef.size();i++) if(!taken[freef[i]]) cand.push_back(freef[i]);
436 std::vector<double> sc(cand.size());
437 for(size_t i=0;i<cand.size();i++) sc[i]=(double)_stats->score(cand[i]);
438 double tau=_tau;
439 if(tau<=0.0) { // robust self-tuning scale: IQR/1.349 of the current free-feature scores
440 std::vector<double> tmp(sc);
441 const size_t q1=tmp.size()/4, q3=(3*tmp.size())/4;
442 std::nth_element(tmp.begin(),tmp.begin()+q1,tmp.end());
443 const double a=tmp[q1];
444 std::nth_element(tmp.begin(),tmp.begin()+q3,tmp.end());
445 const double b=tmp[q3];
446 tau=(b-a)/1.349;
447 // floor the temperature (the negated comparison also catches a NaN IQR):
448 // division by tau must stay finite. At the floor all scores that are equal
449 // give exponent 0, i.e. uniform draws.
450 if(!(tau>1e-9)) tau=1e-9;
451 }
452 const double smax=*std::max_element(sc.begin(),sc.end());
453 std::vector<double> w(sc.size());
454 double W=0.0;
455 for(size_t i=0;i<sc.size();i++) {w[i]=std::exp((sc[i]-smax)/tau); W+=w[i];}
456 for(DIMTYPE k=0;k<rest;k++) {
457 const double r=frand()*W;
458 size_t pick=w.size();
459 double acc=0.0;
460 for(size_t i=0;i<w.size();i++) {
461 if(w[i]<=0.0) continue;
462 acc+=w[i];
463 if(r<acc) {pick=i; break;}
464 }
465 if(pick==w.size()) {for(size_t i=w.size();i-->0;) if(w[i]>0.0) {pick=i; break;}} // numeric-drift guard
466 if(pick==w.size()) {
467 // complete underflow: with tau at the 1e-9 floor, exp((sc-smax)/tau)
468 // is +0.0 for every candidate off the exact-max ties; once those are
469 // drawn, W==0 and neither the draw loop nor the drift guard (both
470 // need w>0) can select. The tau->0 softmax limit over the remnant is
471 // argmax(score) - pick it deterministically (lowest index wins ties;
472 // w[i]<0 marks already-drawn entries). Without this branch,
473 // pick==w.size() slips past the NDEBUG'd assert into cand[pick]/
474 // w[pick] - an out-of-bounds read AND write whose heap garbage makes
475 // proposals duplicated and dependent on the runtime environment.
476 for(size_t i=0;i<w.size();i++) if(w[i]>=0.0 && (pick==w.size() || sc[i]>sc[pick])) pick=i;
477 }
478 assert(pick<w.size());
479 prop.push_back(cand[pick]);
480 W-=w[pick];
481 w[pick]=-1.0; // drawn sentinel (negative so all no-replacement guards keep skipping it)
482 }
483 }
484 }
485 assert((DIMTYPE)prop.size()==want);
486 // evaluate the proposed batch: candidate i = S + {prop[i]}; all fst_rand() draws of
487 // this step happened above, so fanning the evaluations out to the threaded engine
488 // leaves the master RNG stream untouched; the ResultCallback fires in add-order
489 // (= proposal order) in both engines, so statistics, tracker and winner are the same
490 // whichever engine is injected
491 if(!tmp_cand || tmp_cand->get_n()!=n) tmp_cand.reset(new SUBSET(*sub));
492 else tmp_cand->stateless_copy(*sub);
493 RETURNTYPE bestval=0;
494 DIMTYPE bestf=n,order=0,cnt=0;
495 bool stats_ok=true;
496 typename EVALUATORENGINE::ResultCallback on_result=[this,track,n,&bestval,&bestf,&order,&cnt,&stats_ok](const RETURNTYPE val, const PSubset &csub, const DIMTYPE f) {
497 all_evals++;
498 if(!_stats->add(val,csub)) stats_ok=false;
499 if(track) parent::_tracker->add(val,csub);
500 cnt++;
501 if(bestf==n || val>bestval) {
502 bestval=val;
503 bestf=f;
504 order=cnt;
505 }
506 };
507 _engine->initialize(crit,want,on_result);
508 for(size_t i=0;i<prop.size();i++) {
509 const DIMTYPE f=prop[i];
510 tmp_cand->select_raw(f);
511 if(parent::output_detailed()) {std::ostringstream sos; sos << "Step (sampled)...: +<" << f << "> sub=" << *tmp_cand << std::endl << std::flush; syncout::print(os,sos);}
512 if(!_engine->add_candidate(tmp_cand,f)) return false;
513 tmp_cand->deselect_raw(f);
514 }
515 if(!_engine->collect()) return false;
516 if(!stats_ok) return false;
517 _stats->flush_batch();
518 if(bestf==n) return false;
519 sub->select_raw(bestf);
520 result=bestval;
521 note_step(true,order,cnt,pool,os);
522 return true;
523}
524
525// The backward mirror of sampled_forward, kept as a separate body ON PURPOSE: merging the two
526// into one parameterized function would change the order in which each path consumes the
527// fst_rand() stream, and with it every seeded result the two paths produce. Differences from
528// the forward path:
529// - pool = current members of S (raw flags read literally: selected_raw(f) is
530// mode-independent membership; in backward mode select_raw(f) REMOVES f and
531// deselect_raw(f) restores it — Subset::set_forward_mode inverts raw-op meaning),
532// - proposal favors LOW scores: topk = lowest-scored members, softmax over -score/tau,
533// - exploration floor targets members whose ABSENCE is least observed (count_isnot),
534// - candidates are constructed by toggling `sub` in place (matching how the full-sweep
535// candidate traversal mutates `sub` during the step).
536template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION, class WEIGHTER>
537bool Sequential_Step_Sampled<RETURNTYPE,DIMTYPE,SUBSET,CRITERION,WEIGHTER>::sampled_backward(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE pool, const DIMTYPE cap, std::ostream& os)
538{
539 const DIMTYPE n=sub->get_n();
540 const bool track=parent::result_tracker_active();
541 assert(cap>0 && pool>cap);
542 assert(!sub->get_forward_mode());
543 // current members of S (raw membership is mode-independent: _bin[f]>0)
544 std::vector<DIMTYPE> memb;
545 memb.reserve(pool);
546 for(DIMTYPE f=0;f<n;f++) if(sub->selected_raw(f)) memb.push_back(f);
547 assert((DIMTYPE)memb.size()==pool);
548 const DIMTYPE want=cap;
549 std::vector<DIMTYPE> prop;
550 prop.reserve(want);
551 std::vector<char> taken(n,0);
552 if(_sampler==SAMPLER_TOPK) {
553 // hard cutoff: the `cap` LOWEST-scored members (removal analogue of the forward top-cut)
554 std::vector<DIMTYPE> byscore(memb);
555 std::partial_sort(byscore.begin(),byscore.begin()+want,byscore.end(),
556 [this](const DIMTYPE a,const DIMTYPE b){const RETURNTYPE sa=_stats->score(a),sb=_stats->score(b); return sa<sb || (sa==sb && a<b);});
557 for(DIMTYPE i=0;i<want;i++) prop.push_back(byscore[i]);
558 } else {
559 DIMTYPE u = (_sampler==SAMPLER_UNIFORM) ? want : (DIMTYPE)(_explore*(double)want+0.5);
560 if(u>want) u=want;
561 if(u>0) {
562 // exploration floor: uniform draws from the members whose absence is least observed
563 // (SAMPLER_UNIFORM: uniform draws from ALL members - the stochastic-greedy baseline)
564 std::vector<DIMTYPE> bycnt(memb);
565 const DIMTYPE le = (_sampler==SAMPLER_UNIFORM) ? pool : std::min<DIMTYPE>(pool,std::max<DIMTYPE>(4*u,u));
566 if(le<pool)
567 std::partial_sort(bycnt.begin(),bycnt.begin()+le,bycnt.end(),
568 [this](const DIMTYPE a,const DIMTYPE b){const auto ca=_stats->count_isnot(a),cb=_stats->count_isnot(b); return ca<cb || (ca==cb && a<b);});
569 for(DIMTYPE k=0;k<u;k++) { // partial Fisher-Yates over the least-observed prefix
570 const DIMTYPE j=k+(DIMTYPE)(frand()*(double)(le-k));
571 std::swap(bycnt[k],bycnt[j]);
572 prop.push_back(bycnt[k]);
573 taken[bycnt[k]]=1;
574 }
575 }
576 const DIMTYPE rest=want-u;
577 if(rest>0) {
578 // weighted draws without replacement ~ softmax(-score/tau) over the untaken members
579 // (LOW-scoring members are the preferred removal candidates)
580 std::vector<DIMTYPE> cand;
581 cand.reserve(pool-u);
582 for(size_t i=0;i<memb.size();i++) if(!taken[memb[i]]) cand.push_back(memb[i]);
583 std::vector<double> sc(cand.size());
584 for(size_t i=0;i<cand.size();i++) sc[i]=(double)_stats->score(cand[i]);
585 double tau=_tau;
586 if(tau<=0.0) { // robust self-tuning scale: IQR/1.349 of the current member scores
587 std::vector<double> tmp(sc);
588 const size_t q1=tmp.size()/4, q3=(3*tmp.size())/4;
589 std::nth_element(tmp.begin(),tmp.begin()+q1,tmp.end());
590 const double a=tmp[q1];
591 std::nth_element(tmp.begin(),tmp.begin()+q3,tmp.end());
592 const double b=tmp[q3];
593 tau=(b-a)/1.349;
594 // floor the temperature (the negated comparison also catches a NaN IQR):
595 // division by tau must stay finite. At the floor all scores that are equal
596 // give exponent 0, i.e. uniform draws.
597 if(!(tau>1e-9)) tau=1e-9;
598 }
599 const double smin=*std::min_element(sc.begin(),sc.end());
600 std::vector<double> w(sc.size());
601 double W=0.0;
602 for(size_t i=0;i<sc.size();i++) {w[i]=std::exp((smin-sc[i])/tau); W+=w[i];}
603 for(DIMTYPE k=0;k<rest;k++) {
604 const double r=frand()*W;
605 size_t pick=w.size();
606 double acc=0.0;
607 for(size_t i=0;i<w.size();i++) {
608 if(w[i]<=0.0) continue;
609 acc+=w[i];
610 if(r<acc) {pick=i; break;}
611 }
612 if(pick==w.size()) {for(size_t i=w.size();i-->0;) if(w[i]>0.0) {pick=i; break;}} // numeric-drift guard
613 if(pick==w.size()) {
614 // complete underflow - the backward mirror of the forward branch above:
615 // the tau->0 softmax(-score/tau) limit over the remnant is
616 // argmin(score); pick it deterministically (lowest index wins ties;
617 // w[i]<0 marks already-drawn entries). Prevents the out-of-bounds
618 // cand[pick]/w[pick] access described there.
619 for(size_t i=0;i<w.size();i++) if(w[i]>=0.0 && (pick==w.size() || sc[i]<sc[pick])) pick=i;
620 }
621 assert(pick<w.size());
622 prop.push_back(cand[pick]);
623 W-=w[pick];
624 w[pick]=-1.0; // drawn sentinel (negative so all no-replacement guards keep skipping it)
625 }
626 }
627 }
628 assert((DIMTYPE)prop.size()==want);
629 // evaluate the proposed batch in place: candidate i = S - {prop[i]}
630 // (backward-mode raw ops: select_raw removes, deselect_raw restores; the engine
631 // copies the candidate state inside add_candidate, so toggling `sub` back right
632 // after the call is safe in the threaded engine too)
633 RETURNTYPE bestval=0;
634 DIMTYPE bestf=n,order=0,cnt=0;
635 bool stats_ok=true;
636 typename EVALUATORENGINE::ResultCallback on_result=[this,track,n,&bestval,&bestf,&order,&cnt,&stats_ok](const RETURNTYPE val, const PSubset &csub, const DIMTYPE f) {
637 all_evals++;
638 if(!_stats->add(val,csub)) stats_ok=false;
639 if(track) parent::_tracker->add(val,csub);
640 cnt++;
641 if(bestf==n || val>bestval) {
642 bestval=val;
643 bestf=f;
644 order=cnt;
645 }
646 };
647 _engine->initialize(crit,want,on_result);
648 for(size_t i=0;i<prop.size();i++) {
649 const DIMTYPE f=prop[i];
650 sub->select_raw(f);
651 if(parent::output_detailed()) {std::ostringstream sos; sos << "Step (sampled)...: -<" << f << "> sub=" << *sub << std::endl << std::flush; syncout::print(os,sos);}
652 if(!_engine->add_candidate(sub,f)) return false;
653 sub->deselect_raw(f);
654 }
655 if(!_engine->collect()) return false;
656 if(!stats_ok) return false;
657 _stats->flush_batch();
658 if(bestf==n) return false;
659 sub->select_raw(bestf);
660 result=bestval;
661 note_step(false,order,cnt,pool,os);
662 return true;
663}
664
665template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION, class WEIGHTER>
666void Sequential_Step_Sampled<RETURNTYPE,DIMTYPE,SUBSET,CRITERION,WEIGHTER>::note_step(const bool forward, const DIMTYPE order, const DIMTYPE cnt, const DIMTYPE pool, std::ostream& os)
667{
668 assert(cnt>0);
669 const RETURNTYPE r=(RETURNTYPE)order/(RETURNTYPE)cnt;
670 if(forward) {
673 if(parent::output_normal()) {std::ostringstream sos; sos << std::endl << "F order = " << r << "(" << order << ") avgorder " << order_forward << " count = " << count_forward << " all_evals = " << all_evals << " pool = " << pool << " proposed = " << cnt << std::endl << std::flush; syncout::print(os,sos);}
674 } else {
677 if(parent::output_normal()) {std::ostringstream sos; sos << std::endl << "B order = " << r << "(" << order << ") avgorder " << order_backward << " count = " << count_backward << " all_evals = " << all_evals << " pool = " << pool << " proposed = " << cnt << std::endl << std::flush; syncout::print(os,sos);}
678 }
679}
680
681} // namespace
682#endif // FSTSEARCHSEQSTEPSAMPLED_H ///:~
Abstract candidate-evaluation strategy: the seam through which one search algorithm body evaluates ca...
Implements sequential in-line evaluation of a criterion on a set of subset candidates.
Implements sequential in-line evaluation of a criterion on a set of subset candidates.
Definition candidate_evaluator_sequential.hpp:62
Abstract candidate-evaluation strategy (sequential-inline vs. thread-pooled).
Definition candidate_evaluator.hpp:86
std::function< void(const RETURNTYPE, const PSubset &, const DIMTYPE)> ResultCallback
fired once per evaluated batch candidate: (criterion value, candidate, feature tag as passed to add_c...
Definition candidate_evaluator.hpp:94
Implements the budgeted (sampled) sequential selection step: forward steps evaluate only a sampled ba...
Definition seq_step_sampled.hpp:156
void note_step(const bool forward, const DIMTYPE order, const DIMTYPE cnt, const DIMTYPE pool, std::ostream &os)
Folds one completed step's outcome into the order-instrumentation running averages (order_forward/ord...
Definition seq_step_sampled.hpp:666
virtual bool print_summary(std::ostream &os=std::cout)
Prints the statistics engine's diagnostic top-scored-feature listing (Result_Weighter_Sampled::print_...
Definition seq_step_sampled.hpp:242
bool sampled_forward(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE pool, const DIMTYPE cap, std::ostream &os)
Budgeted forward step: proposes exactly cap candidate features (see the class docs above for the samp...
Definition seq_step_sampled.hpp:392
bool evaluate_candidates(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE _generalization_level=1, std::ostream &os=std::cout) override
Performs one selection step on sub: routes to the budgeted forward path, the budgeted backward path o...
Definition seq_step_sampled.hpp:317
bool sampled_backward(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE pool, const DIMTYPE cap, std::ostream &os)
Budgeted backward step, the removal mirror of sampled_forward (kept as a separate body on purpose — s...
Definition seq_step_sampled.hpp:537
Sequential_Step< RETURNTYPE, DIMTYPE, SUBSET, CRITERION > parent
Definition seq_step_sampled.hpp:158
double get_cap_frac() const
current constant-percentage schedule fraction (0 = disabled, see set_cap_frac())
Definition seq_step_sampled.hpp:200
Sequential_Step_Sampled(PWeighter stats, const DIMTYPE cap=100, const double explore=0.2, const double tau=0.0, const SamplerMode sampler=SAMPLER_SOFTMAX, PEvaluatorEngine engine=PEvaluatorEngine())
Constructs the step with its statistics engine, per-step evaluation budget, proposal sampler and (opt...
Definition seq_step_sampled.hpp:184
std::shared_ptr< SUBSET > PSubset
Definition seq_step_sampled.hpp:160
bool full_sweep(RETURNTYPE &result, const PSubset sub, const PCriterion crit, const DIMTYPE g, const DIMTYPE pool, std::ostream &os)
Full candidate sweep (the Sequential_Step_Straight fallback): evaluates every candidate subset return...
Definition seq_step_sampled.hpp:348
std::shared_ptr< CRITERION > PCriterion
Definition seq_step_sampled.hpp:159
std::shared_ptr< EVALUATORENGINE > PEvaluatorEngine
Definition seq_step_sampled.hpp:163
void set_cap_frac(const double frac)
Switches the forward budget to a constant-PERCENTAGE schedule: when frac >0 the forward step cap beco...
Definition seq_step_sampled.hpp:199
double frand()
uniform [0,1); fst_rand()-based per the --seed contract
Definition seq_step_sampled.hpp:301
PWeighter get_stats()
shared statistics engine (fst4 feeds warm-up probes through it)
Definition seq_step_sampled.hpp:222
Candidate_Evaluator< RETURNTYPE, DIMTYPE, SUBSET, CRITERION > EVALUATORENGINE
Definition seq_step_sampled.hpp:162
DIMTYPE get_cap_backward() const
current backward-step budget y_b (0 = full sweeps, see set_cap_backward())
Definition seq_step_sampled.hpp:206
void set_cap_backward(const DIMTYPE cap)
Sets the backward-step budget y_b: 0 (default) = full backward sweeps, which keeps the floating corre...
Definition seq_step_sampled.hpp:205
SamplerMode
Proposal-sampling policy for the forward/backward candidate batch (see the class docs above for the e...
Definition seq_step_sampled.hpp:170
std::shared_ptr< WEIGHTER_SAMPLED > PWeighter
Definition seq_step_sampled.hpp:161
virtual std::ostream & print(std::ostream &os) const override
Writes a human-readable identification of the step configuration (cap or the percentage schedule,...
Definition seq_step_sampled.hpp:228
bool output_normal() const
true unless output detail is SILENT
Definition seq_step.hpp:137
PResultTracker _tracker
the assigned result tracker, or null (see enable_result_tracking()/result_tracker_active())
Definition seq_step.hpp:140
bool output_detailed() const
true only at the DETAILED output level
Definition seq_step.hpp:138
bool result_tracker_active() const
true if a result tracker was assigned via enable_result_tracking() and not since disabled
Definition seq_step.hpp:133
static void print(std::ostream &os, std::ostringstream &sos)
Writes sos's buffered content to os, serializing concurrent writers under FST_THREADED via a shared m...
Definition global.hpp:86
FST4 specific error passing class.
FST4-owned pseudo-random number generator (UCRT-compatible LCG), replacing platform-specific std::ran...
Global definitions.
The Feature Selection Toolbox 4 library: every library component – data access, criteria and classifi...
Definition data_accessor.hpp:80
int fst_rand()
Returns the next pseudo-random draw in 0..FST_RAND_MAX; use instead of std::rand().
Definition fst_rand.hpp:93
const int FST_RAND_MAX
Maximum value fst_rand() can return; use instead of the libc macro RAND_MAX.
Definition fst_rand.hpp:101
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
Online per-feature statistics for budgeted (sampled) sequential steps — discounted batch-standardized...
Defines interface for implementations of one selection step in sequential search type of methods.
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.