ECF 1.7
AlgXCS.cpp
1#include "../ECF_base.h"
2#include "../SelRandomOp.h"
3#include "../SelFitnessProportionalOp.h"
4#include "../SelWorstOp.h"
5#include "../ECF_macro.h"
6
7#include "AlgXCS.h"
8#include "math.h"
9#include "time.h"
10#include "../Logger.h"
11#include <string>
12#include <cassert>
13//
14//#undef XCS_STEP_DEBUG
15//#undef XCS_DEBUG
16
17#pragma region Intro comments
18//References:
19// [1] An algorithmic Description of XCS, Martin V.
20// Butz and Stewart W. Wilson
21// [2] Rule-Based Evolutionary Online Learning Systems
22// Martin V. Butz
23
24/*System characteristics:
25 - single-step problems
26 - multi-step problems
27*/
28#pragma endregion
29
30//Compare function used for sorting classifier by fitness value
31bool cmp (ClassifierP a, ClassifierP b){
32 return a->ind->fitness->getValue() > b->ind->fitness->getValue();
33}
34using namespace std;
35
36XCS::XCS()
37{
38 name_ = "XCS";
39
40 //TODO: izbacit randomOp i worstOp
41 selRandomOp = static_cast<SelectionOperatorP> (new SelRandomOp);
42 selFitPropOp = static_cast<SelectionOperatorP> (new SelFitnessProportionalOp);
43 selWorstOp = static_cast<SelectionOperatorP> (new SelWorstOp);
44
45 params = static_cast<XCSParamsP> ( new XCSParams(name_));
46}
47
48
49//Method for registering algorithm parameters
50void XCS::registerParameters(StateP state)
51{
52 params->registerParams(state->getRegistry());
53
54 //Defining aditional genotype used for storing classifier parameters
55 ClassifierParamsP params = static_cast<ClassifierParamsP> (new ClassifierParams(0,0,0));
56 state->addGenotype(params);
57
58}
59bool XCS::initialize(StateP state)
60{
61 if (!Classifier::checkState(state)) {
62 throw ("");
63 }
64
65 selRandomOp->initialize(state);
66 selWorstOp->initialize(state);
67 selFitPropOp->initialize(state);
68
69 params->readParams(state->getRegistry());
70
71 time = 0;
72
73 ClassifierParamsP clParams = static_cast<ClassifierParamsP> (new ClassifierParams(0,0,0));
74 clParams->setGenotypeId(3);
75 clParams->initialize(state);
76 params->initF_ = clParams->F_;
77
78 //Environment initialization
79 environment = std::dynamic_pointer_cast<Environment> (evalOp_);
80
81 if (!environment->checkState(state)) {
82 throw ("");
83 }
84
85 try {
86 if (!environment->initialize()){
87 throw (std::string("failed to initialize"));
88 }
89 }catch (std::string text){
90 ECF_LOG_ERROR(state, "Environment error: "+text);
91 throw ("");
92 }
93
94 return true;
95}
96void XCS::printPopulation(){
97 //sort(populationSet.begin(), populationSet.end(), cmp);
98 for (uint i = 0; i < populationSet.size(); i++)
99 populationSet[i]->print();
100}
101
102bool XCS::advanceGeneration(StateP state, DemeP deme) {
103
104 if (state->getGenerationNo() == 0){
105
106 //creating population set [P]
107 ClassifierP classP;
108 PopulationP pop = state->getPopulation();
109
110 for (uint i = 0; i < pop->size(); i++){
111 for (uint j = 0; j < pop->at(i)->size(); j++){
112 classP = static_cast<ClassifierP>
113 (new Classifier(params, time, pop->at(i)->at(j), state));
114 populationSet.push_back(classP);
115 }
116 }
117
118 for (uint i = 0; i < deme->size(); i++)
119 deme->at(i)->fitness->setValue(params->initF_);
120 environment->reset();
121 }
122
123 std::vector<ClassifierP> lastActionSet; //[A]-1
124 double lastReward = 0;
125 GenotypeP lastInput;
126
127 //main generation loop
128 do {
129
130#ifdef XCS_STEP_DEBUG
131 std::cout << "Press any key to continue..." << std::endl;
132 std::cin.get();
133#endif
134 time++;
135 std::vector<ClassifierP> matchSet; //[M]
136 std::vector<ClassifierP> actionSet;//[A]
137
138 //Getting input from environment
139 GenotypeP input = environment->getInput();
140
141#ifdef XCS_DEBUG
142 std::cout << "Input value: ";
143 Classifier::printBitString(std::dynamic_pointer_cast<BitString::BitString> (input));
144 std::cout << std::endl;
145#endif
146 //Generate match set [M]
147 matchSet = generateMatchSet(state, deme, input); //[M]
148
149#ifdef XCS_DEBUG
150 std::cout << "Match set [M]: "<< std::endl;
151 for (uint i = 0; i < matchSet.size(); i++)
152 matchSet[i]->print();
153#endif
154
155 //creating prediction array PA
156 std::map<int, double> PA = generatePA(matchSet);
157 //selecting action id
158
159 int actionId = selectActionId(state, PA);
160#ifdef XCS_DEBUG
161 std::cout << "action id = " << actionId<< std::endl;
162#endif
163
164 //generating action set [A]
165 actionSet = generateActionSet(matchSet, actionId);
166
167#ifdef XCS_DEBUG
168 std::cout << "\nAction set [A]: "<< std::endl;
169 for (uint i = 0; i < actionSet.size(); i++)
170 actionSet[i]->print();
171#endif
172
173 //executing selected action
174 IndividualP ind = static_cast<IndividualP> (new Individual);
175 ind->push_back(input);
176 ind->push_back(actionSet[0]->getAction());
177
178 //getting reward from environment
179 evaluate(ind);
180 double reward = ind->fitness->getValue();
181
182#ifdef XCS_DEBUG
183 std::cout << "Reward: " << reward << std::endl;
184#endif
185
186 if (!lastActionSet.empty()){
187 double P = lastReward + params->gama_ * getMaxFromPA(PA).second;
188
189 updateActionSet(lastActionSet, deme, P, state);
190 if (!environment->isExploit()) {
191 runGA(lastActionSet, lastInput, deme, state);
192 }
193 }
194 if (environment->isOver()) {
195
196 if (!environment->isExploit()) {
197 updateActionSet(actionSet, deme, reward, state);
198
199 runGA(actionSet, input, deme, state);
200 lastActionSet.clear();
201 }
202
203 } else {
204 lastActionSet = actionSet;
205 lastReward = reward;
206 lastInput = input;
207 }
208 } while (!environment->isOver());
209
210 environment->nextTrial();
211
212#ifdef XCS_DEBUG
213 std::cout << "Classifiers:" << std::endl;
214 printPopulation();
215 std::cout << " ===== advanceGeneration end ====="<< std::endl;
216#endif
217
218 return true;
219}
220
221std::vector<ClassifierP> XCS::generateMatchSet(StateP state, DemeP deme, GenotypeP input) {
222
223 //(!) napomena mora vrijediti deme->at(i) == vClassifier[i].ind
224 std::vector<ClassifierP> matchSet;
225
226 while(matchSet.empty()){
227
228 for (uint i = 0; i < populationSet.size(); ++i){
229 if (populationSet[i]->doesMatch(input))
230 matchSet.push_back(populationSet[i]);
231 }
232
233 uint noDiffActions = (uint) getActionsFromMs(matchSet).size();
234 if (noDiffActions < params->mna_){
235
236 ClassifierP coverCl = cover(state, deme, input, matchSet);
237 deleteFromPopulation(state, deme);
238 matchSet.clear();
239 }
240 }
241
242 return matchSet;
243}
244
245std::set<int> XCS::getActionsFromMs (std::vector<ClassifierP> matchSet){
246
247 std::set<int> actions;
248 for (uint i = 0; i < matchSet.size(); ++i){
249 actions.insert(matchSet[i]->getActionId());
250 }
251 return actions;
252}
253
254//Creates cover classifier according to input value
255//and inserts it in populationSet and deme
256ClassifierP XCS::cover (StateP state, DemeP deme, GenotypeP input, std::vector<ClassifierP> matchSet){
257
258 IndividualP newInd = static_cast<IndividualP> (new Individual(state));
259
260 ClassifierP newClassifier = static_cast<ClassifierP> (new
261 Classifier (params,time, newInd, state));
262
263 //classifier must match input so that it can be added in [M]
264 newClassifier->cover(getActionsFromMs(matchSet), input, state);
265 newInd->index = (uint)deme->size();
266
267 populationSet.push_back(newClassifier);
268 deme->push_back(newInd);
269
270 assert(deme->size() == populationSet.size());
271
272 return newClassifier;
273}
274
275//Generates prediction array from match set
276std::map<int, double> XCS::generatePA(std::vector<ClassifierP> matchSet){
277 std::map<int, double> PA;
278 std::map<int, double> fsa; //fitness sum array
279
280 for (uint i = 0; i < matchSet.size(); ++i){
281
282 ClassifierP cl = matchSet[i];
283 double fitness = cl->getFitness();
284 int action = cl->getActionId();
285
286 if(PA[action] == NULL) {
287 PA[action] = cl->getPrediction() * fitness;
288 fsa[action] = 0;
289 } else
290 PA[action] += cl->getPrediction() * fitness;
291 fsa[action] += fitness;
292 }
293 for (std::map<int, double>::iterator it = PA.begin(); it != PA.end(); ++it){
294 PA[it->first] /= (fsa[it->first] == 0 ? 1 : fsa[it->first]);
295 }
296 return PA;
297}
298
299
300int XCS:: selectActionId(StateP state, std::map<int, double> PA){
301 double rnd = state->getRandomizer()->getRandomDouble();
302 int actionId = PA.begin()->first;
303
304 //if it is exploit experiment
305 if (environment->isExploit())
306 return getMaxFromPA(PA).first;
307
308 if (rnd < params->p_explore_) {
309 //exploration
310 rnd = state->getRandomizer()->getRandomDouble();
311 int i = 1;
312 for (std::map<int, double>::const_iterator it = PA.begin(); it != PA.end(); ++it){
313 if (i > PA.size() * rnd){
314 actionId = it->first;
315 break;
316 }
317 i++;
318 }
319 }else {
320 //exploitation
321 actionId = getMaxFromPA(PA).first;
322 }
323 return actionId;
324}
325
326std::vector<ClassifierP> XCS::generateActionSet(std::vector<ClassifierP> matchSet, int actionId){
327 std::vector<ClassifierP> actionSet;
328 for (uint i = 0; i < matchSet.size(); i++)
329 if (matchSet[i]->getActionId() == actionId)
330 actionSet.push_back(matchSet[i]);
331 return actionSet;
332}
333
334void XCS::updateActionSet(std::vector<ClassifierP> actionSet, DemeP deme, double reward, StateP state){
335
336 ClassifierP cl;
337 double sum = 0;
338 std::vector<double> accuracy;
339
340 int numSum = 0;
341 for (uint i = 0; i < actionSet.size(); ++i)
342 numSum += actionSet[i]->getNumerosity();
343
344 //update exp, eps, p and as
345 for (uint i = 0; i < actionSet.size(); ++i){
346 cl = actionSet[i];
347
348 double exp = cl->getExperience() + 1;
349 cl->setExperience(exp);
350
351 double eps = cl->getError();
352 double p = cl->getPrediction();
353 double as = cl->getActSetSize();
354
355 if (exp < 1 / params->beta_) {
356 p += (reward - p) / exp;
357 eps += (fabs(reward - p) - eps) / exp;
358 as += (numSum - as) / exp;
359 } else {
360 p += params->beta_ * (reward - p);
361 eps += params->beta_ * (fabs(reward - p) - eps);
362 as += params->beta_ * (numSum - as);
363 }
364
365 if (eps < params->eps0_)
366 accuracy.push_back(1);
367 else
368 accuracy.push_back( params->alpha_ * pow(eps / params->eps0_, -params->accExp_) );
369
370 sum += accuracy[i] * cl->getNumerosity();
371
372 cl->setPrediction(p);
373 cl->setError(eps);
374 cl->setActSetSize(as);
375 }
376
377 //update fitness
378 for (uint i = 0; i < actionSet.size(); ++i){
379
380 cl = actionSet[i];
381
382 double F = cl->getFitness();
383 F += params->beta_ * (accuracy[i] * cl->getNumerosity() / sum - F) ;
384 cl->setFitness(F);
385 }
386
387 actionSetSubsumption(&actionSet, deme, state);
388
389}
390
391double XCS::getAsTimeSum(std::vector<ClassifierP> as) {
392
393 double sumNum = 0, sumTsNum = 0;
394 for (uint i = 0; i < as.size(); ++i){
395 sumTsNum += as[i]->getTimeStamp() * as[i]->getNumerosity();
396 sumNum += as[i]->getNumerosity();
397 }
398
399 return sumTsNum / sumNum;
400}
401
402void XCS::runGA(std::vector<ClassifierP> actionSet, GenotypeP genInput, DemeP deme, StateP state){
403
404 if (time - getAsTimeSum(actionSet) < params->thresholdGA_) return;
405
406 std::vector<IndividualP> tournament, vActionSet;
407
408 for (uint i = 0; i < actionSet.size(); i++) {
409 if (!actionSet[i]->valid) continue;
410 vActionSet.push_back(actionSet[i]->ind);
411 actionSet[i]->setTimeStamp(time);
412 }
413 if (vActionSet.size() < 1) return;
414
415 IndividualP parent[2];
416 parent[0] = selFitPropOp->select(vActionSet);
417 parent[1] = selFitPropOp->select(vActionSet);
418
419 ClassifierP clParent[2];
420 clParent[0] = populationSet[parent[0]->index];
421 clParent[1] = populationSet[parent[1]->index];
422
423 assert(clParent[0]->getActionId() == clParent[1]->getActionId());
424 ClassifierP clChild[2];
425
426 for (int i = 0; i < 2; ++i) {
427
428 clChild[i] = static_cast<ClassifierP> (new Classifier(clParent[i]));
429
430 clChild[i]->setNumerosity(1);
431 clChild[i]->setExperience(0);
432
433 double f = clChild[i]->getFitness();
434
435 if (state->getRandomizer()->getRandomDouble() < params->pCrossover_) {
436 mate(parent[0], parent[1], clChild[i]->ind);
437
438 clChild[i]->setAction(clParent[0]->getAction());
439
440 assert(clChild[i]->getActionId() == clParent[i]->getActionId());
441
442 clChild[i]->setPrediction((clParent[0]->getPrediction() + clParent[1]->getPrediction()) / 2);
443 clChild[i]->setError((clParent[0]->getError() + clParent[1]->getError()) / 2);
444
445 f = (clParent[0]->getFitness() + clParent[1]->getFitness()) / 2;
446 }
447
448 clChild[i]->setFitness( 0.1 * f);
449
450 clChild[i]->mutateRule(genInput, state);
451 clChild[i]->mutateAction(state);
452
453 if (clParent[0]->doesSubsume(clChild[i])) {
454 clParent[0]->setNumerosity(clParent[0]->getNumerosity() + 1);
455 } else if (clParent[1]->doesSubsume(clChild[i])) {
456 clParent[1]->setNumerosity(clParent[1]->getNumerosity() + 1);
457 } else {
458 clChild[i]->ind->index = (uint)deme->size();
459 populationSet.push_back(clChild[i]);
460 deme->push_back(clChild[i]->ind);
461 }
462 assert(deme->size() == populationSet.size());
463
464 deleteFromPopulation(state, deme);
465 }
466}
467
468std::pair<int, double> XCS::getMaxFromPA(std::map<int, double> PA){
469
470 if (PA.empty()) return std::make_pair(-1, -1.);
471
472 std::pair<int, double> maxPA = *PA.begin();
473 for (std::map<int, double>::iterator it = PA.begin(); it != PA.end(); ++it){
474 if (it->second > maxPA.second)
475 maxPA = *it;
476 }
477 return maxPA;
478}
479
480void XCS::deleteFromPopulation(StateP state, DemeP deme) {
481
482 int sumNum = 0;
483 double sumFit = 0;
484
485 for ( uint i = 0; i < populationSet.size(); ++i) {
486 sumNum += populationSet[i]->getNumerosity();
487 sumFit += populationSet[i]->getFitness();
488 }
489 if (sumNum <= (int) params->popSize_) return;
490
491 double avFitInPop = sumFit / sumNum;
492 double sumVote = 0;
493
494 for ( uint i = 0; i < populationSet.size(); ++i) {
495 sumVote += populationSet[i]->getDeletionVote(avFitInPop);
496 }
497 double choicePoint = state->getRandomizer()->getRandomDouble() * sumVote;
498 sumVote = 0;
499 for ( uint i = 0; i < populationSet.size(); ++i) {
500 ClassifierP cl = populationSet[i];
501 sumVote += cl->getDeletionVote(avFitInPop);
502 if (sumVote > choicePoint) {
503 int n = cl->getNumerosity();
504 if (n > 1){
505 cl->setNumerosity(n-1);
506 } else {
507 removeFromPopSet(cl, deme);
508 }
509 return;
510 }
511 }
512 assert(deme->size() == populationSet.size());
513
514}
515
516void XCS::removeFromPopSet(ClassifierP cl, DemeP deme){
517
518 IndividualP lastInd = deme->at(deme->size()-1);
519 ClassifierP lastCl = populationSet[populationSet.size()-1];
520
521 lastInd->index = cl->ind->index;
522
523 populationSet[cl->ind->index] = lastCl;
524 populationSet.erase(populationSet.begin() + populationSet.size() - 1);
525
526 deme->at(cl->ind->index) = lastInd;
527 deme->erase(deme->begin() + deme->size() - 1);
528
529 assert(deme->size() == populationSet.size());
530 cl->valid = false;
531}
532
533void XCS::actionSetSubsumption(std::vector<ClassifierP> *actionSet, DemeP deme, StateP state){
534 ClassifierP cl;
535 bool clSet = false;
536
537 for (uint i = 0; i < actionSet->size(); ++i){
538
539 ClassifierP c = actionSet->at(i);
540 if (c->couldSubsume()){
541
542 int clDcb = c->numOfDCBits();
543 double rnd = state->getRandomizer()->getRandomDouble();
544 if (!clSet || clDcb > cl->numOfDCBits() || (clDcb == cl->numOfDCBits() && rnd < 0.5 )) {
545 cl = c;
546 clSet = true;
547 }
548 }
549 }
550 if (clSet) {
551 for (uint i = 0; i < actionSet->size(); ++i){
552 ClassifierP c = actionSet->at(i);
553 if (cl->isMoreGeneral(c)){
554 cl->setNumerosity(cl->getNumerosity() + c->getNumerosity());
555 actionSet->erase(actionSet->begin() + i);
556 removeFromPopSet(c,deme);
557 }
558 }
559 }
560}
EvaluateOpP evalOp_
sptr to evaluation operator (set by the system)
Definition Algorithm.h:82
std::string name_
algorithm name
Definition Algorithm.h:23
bool mate(IndividualP p1, IndividualP p2, IndividualP child)
Helper function: crossover two individuals.
Definition Algorithm.h:285
void evaluate(IndividualP ind)
Helper function: evaluate an individual.
Definition Algorithm.h:157
Classifier class that holds all parameters and pointer to individual to which the parameters belong.
Definition Classifier.h:20
Classifier data structure in XCS algorithm.
Individual class - inherits a vector of Genotype objects.
Definition Individual.h:12
bool advanceGeneration(StateP state, DemeP deme)
Perform a single generation on a single deme.
Definition AlgXCS.cpp:102
bool initialize(StateP state)
Initialize the algorithm, read parameters from the system, do a sanity check.
Definition AlgXCS.cpp:59
void registerParameters(StateP state)
Register algorithm's parameters (if any).
Definition AlgXCS.cpp:50