Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterative work on stabilizing XCS #62

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
285a79a
Refractor Genetic Algorithm
Metron45 May 6, 2021
d849105
Add some more tests
khozzy May 7, 2021
7541ee2
Ensure no duplicates
khozzy May 7, 2021
3624335
Covering Fix
Metron45 May 7, 2021
b399696
Better RP
Metron45 May 7, 2021
da8e03b
XNCS Update
Metron45 May 13, 2021
69e1f93
Fix Duplicates XCS
Metron45 May 18, 2021
5aa92cb
XNCS duplicates fix
Metron45 May 18, 2021
39caccb
BP test fix
Metron45 May 20, 2021
3a6eb58
Fix Covering
Metron45 May 20, 2021
68b82f4
Fix Only Fittest CL into BP
Metron45 May 20, 2021
46408f9
Fixes for BP
Metron45 May 20, 2021
94f75af
Merge branch 'master' into feature/xcs_xncs
khozzy May 23, 2021
40ae90b
Test spit + more
Metron45 May 25, 2021
b78b7fc
Merge branch 'feature/xcs_xncs' of https://github.com/ParrotPredictio…
Metron45 May 25, 2021
76f0d28
Woods1 Effect fix
Metron45 May 25, 2021
6cf5767
Revert "Merge branch 'master' into feature/xcs_xncs"
Metron45 May 25, 2021
866f005
New equals
Metron45 May 25, 2021
5ad9b9c
Revert "New equals"
Metron45 May 25, 2021
c4b69da
Fix for BP
Metron45 May 25, 2021
1d9b26f
XCS fix for woods
Metron45 May 25, 2021
fc8224a
BP tests
Metron45 May 26, 2021
e80d73d
Classifier Accuracy
Metron45 May 28, 2021
ad66c09
Fixed Update in BP
Metron45 May 28, 2021
e8ae627
Moved XNCS functions to XNCS ClLst
Metron45 May 28, 2021
ca73d04
Moved update near action_set creation
Metron45 May 28, 2021
1b6a297
Split of update effect and error
Metron45 May 29, 2021
c1299be
XNCS BP fixed LMC
Metron45 May 30, 2021
14a08f8
From situation instead of best [A]
Metron45 May 30, 2021
5609be7
Child values fix
Metron45 May 31, 2021
cc96ca6
Refractor and XNCS modifications
Metron45 Jun 10, 2021
86108dc
Moved update percentage to solely cfg
Metron45 Jun 10, 2021
9527cbc
Removal of unneeded code
Metron45 Jun 11, 2021
cd1ac40
Covering fix
Metron45 Jun 12, 2021
a9391c1
Fix for Update Fitness.
Metron45 Jun 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions XCS_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

25 changes: 0 additions & 25 deletions lcs/agents/Agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
from timeit import default_timer as timer
from typing import Callable, List, Tuple

import dill
import mlflow
import numpy as np
import tempfile

from lcs.metrics import basic_metrics

Expand Down Expand Up @@ -119,8 +116,6 @@ def _evaluate(self,
tuple
population of classifiers and metrics
"""
using_mlflow = hasattr(self.get_cfg(), 'use_mlflow') and self.get_cfg().use_mlflow

current_trial = 0
steps = 0

Expand All @@ -143,26 +138,6 @@ def _evaluate(self,

metrics.append(m)

if using_mlflow:
mlflow.log_metrics(m, current_trial)

# checkpoint model and metrics
if self.get_cfg().model_checkpoint_freq:
if current_trial % self.get_cfg().model_checkpoint_freq == 0:
prefix = f"-trial-{current_trial}"
with tempfile.TemporaryDirectory(prefix) as td:
logger.debug(f"checkpointing model to {td}")
pop_path = f"{td}/population.dill"
metrics_path = f"{td}/metrics.dill"

dill.dump(self.get_population(),
open(pop_path, mode='wb'))

dill.dump(metrics, open(metrics_path, mode='wb'))

if using_mlflow:
mlflow.log_artifacts(td, f"{current_trial}/")

# Print last metric
if current_trial % np.round(n_trials / 10) == 0:
logger.info(metrics[-1])
Expand Down
6 changes: 1 addition & 5 deletions lcs/agents/acs/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ def __init__(self,
user_metrics_collector_fcn: Callable = None,
fitness_fcn=None,
metrics_trial_frequency: int = 5,
model_checkpoint_frequency: int = None,
do_subsumption: bool = True,
beta: float = 0.05,
# gamma: float = 0.95,
Expand All @@ -21,8 +20,7 @@ def __init__(self,
epsilon: float = 0.5,
u_max: int = 100000,
theta_exp: int = 20,
theta_as: int = 20,
use_mlflow: bool = False) -> None:
theta_as: int = 20) -> None:
"""
Creates the configuration object used during training the ACS2 agent.

Expand Down Expand Up @@ -51,7 +49,6 @@ def __init__(self,
self.classifier_wildcard = classifier_wildcard
self.environment_adapter = environment_adapter
self.metrics_trial_frequency = metrics_trial_frequency
self.model_checkpoint_freq = model_checkpoint_frequency
self.user_metrics_collector_fcn = user_metrics_collector_fcn
self.fitness_fcn = fitness_fcn
self.do_subsumption = do_subsumption
Expand All @@ -62,7 +59,6 @@ def __init__(self,
self.epsilon = epsilon
self.u_max = u_max
self.theta_as = theta_as
self.use_mlflow = use_mlflow

def __str__(self) -> str:
return str(vars(self))
8 changes: 2 additions & 6 deletions lcs/agents/acs2/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def __init__(self,
user_metrics_collector_fcn: Callable = None,
fitness_fcn=None,
metrics_trial_frequency: int = 5,
model_checkpoint_frequency: int = None,
do_pee: bool = False,
do_ga: bool = False,
do_subsumption: bool = True,
Expand All @@ -33,8 +32,7 @@ def __init__(self,
theta_ga: int = 100,
theta_as: int = 20,
mu: float = 0.3,
chi: float = 0.8,
use_mlflow: bool = False):
chi: float = 0.8):

super(Configuration, self).__init__(
classifier_length,
Expand All @@ -44,16 +42,14 @@ def __init__(self,
user_metrics_collector_fcn,
fitness_fcn,
metrics_trial_frequency,
model_checkpoint_frequency,
do_subsumption,
beta,
theta_i,
theta_r,
epsilon,
u_max,
theta_exp,
theta_as,
use_mlflow)
theta_as)

self.gamma = gamma
self.do_pee = do_pee
Expand Down
20 changes: 11 additions & 9 deletions lcs/agents/xcs/Classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ def __init__(self,
condition: Union[Condition, str, None] = None,
action: Optional[int] = None,
time_stamp: int = None) -> None:

if cfg is None:
raise TypeError("Configuration should be passed to Classifier")

if type(condition) != Condition:
condition = str(condition)
condition = Condition(condition)

self.cfg = cfg # cfg
self.condition = condition # current situation
self.action = action # A - int action
Expand All @@ -42,9 +45,7 @@ def does_subsume(self, other):

@property
def could_subsume(self):
if self.experience > self.cfg.subsumption_threshold and self.error < self.cfg.initial_error:
return True
return False
return self.experience > self.cfg.subsumption_threshold and self.error < self.cfg.initial_error

def is_more_general(self, other):
if self.wildcard_number <= other.wildcard_number:
Expand All @@ -60,9 +61,10 @@ def __len__(self):

def __str__(self):
return f"Cond:{self.condition} - Act:{self.action} - Num:{self.numerosity} " + \
f"[fit: {self.fitness:.3f}, exp: {self.experience:3.2f}, pred: {self.prediction:2.3f}]"
f"[fit: {self.fitness:.3f}, exp: {self.experience:3.2f}, pred: {self.prediction:2.3f}, Error:{self.error}]"

def __eq__(self, other):
if other.action == self.action and other.condition == self.condition:
return True
return False
def __eq__(self, o):
return o.condition == self.condition and o.action == self.action

def __hash__(self):
return hash((str(self.condition), self.action))
69 changes: 38 additions & 31 deletions lcs/agents/xcs/ClassifiersList.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import numpy as np
import random
import logging
import random

import numpy as np

from lcs import TypedList, Perception
from lcs.agents.xcs import Classifier, Condition, Configuration

logger = logging.getLogger(__name__)


Expand All @@ -18,11 +18,12 @@ def __init__(self,
super().__init__(*args, oktypes=oktypes)

def insert_in_population(self, cl: Classifier):
for c in self:
if c == cl:
c.numerosity += 1
return
self.append(cl)
existing_classifiers = [c for c in self if c == cl]
if len(existing_classifiers) > 0:
assert len(existing_classifiers) == 1, 'duplicates found, while inserting'
existing_classifiers[0].numerosity += 1
else:
self.append(cl)

def generate_covering_classifier(self, situation, action, time_stamp):
# both Perception and string has __getitem__
Expand All @@ -33,22 +34,23 @@ def generate_covering_classifier(self, situation, action, time_stamp):
generalized.append(self.cfg.classifier_wildcard)
else:
generalized.append(situation[i])
cl = Classifier(cfg=self.cfg,
condition=Condition(generalized),
action=action,
time_stamp=time_stamp)
return cl

return Classifier(condition=Condition(generalized),
action=action,
time_stamp=time_stamp,
cfg=self.cfg)

def _generate_covering_and_insert(self, situation, action, time_stamp):
cl = self.generate_covering_classifier(situation, action, time_stamp)
self.insert_in_population(cl)
self.delete_from_population()
return cl

# Roulette-Wheel Deletion
# TODO: use strategies
def delete_from_population(self):
if self.numerosity > self.cfg.max_population:
# TODO: change while to if
# During woods the number of rules grew over max number
# To remedy this I added while instead of if. Correct issue should be changed.
while self.numerosity > self.cfg.max_population:
average_fitness = sum(cl.fitness for cl in self) / self.numerosity
deletion_votes = []
for cl in self:
Expand All @@ -59,7 +61,7 @@ def delete_from_population(self):
def _deletion_vote(self, cl, average_fitness):
vote = cl.action_set_size * cl.numerosity
if cl.experience > self.cfg.deletion_threshold and \
cl.fitness / cl.numerosity < \
cl.fitness / cl.numerosity < \
self.cfg.delta * average_fitness:
vote *= average_fitness / (cl.fitness / cl.numerosity)
return vote
Expand All @@ -70,16 +72,18 @@ def _remove_based_on_votes(self, deletion_votes, selector):
if selector <= 0:
if cl.numerosity > 1:
cl.numerosity -= 1
return cl
else:
self.safe_remove(cl)
return None
return cl

def generate_match_set(self, situation: Perception, time_stamp):
matching_ls = [cl for cl in self if cl.does_match(situation)]
while len(matching_ls) < self.cfg.number_of_actions:
action = self._find_not_present_action(matching_ls)
action = self._find_not_present_action(matching_ls)
while action is not None:
cl = self._generate_covering_and_insert(situation, action, time_stamp)
matching_ls.append(cl)
action = self._find_not_present_action(matching_ls)
return ClassifiersList(self.cfg, *matching_ls)

def _find_not_present_action(self, matching_set):
Expand Down Expand Up @@ -116,14 +120,18 @@ def update_set(self, p):
for cl in self:
cl.experience += 1
# update prediction, prediction error, action set size estimate
if cl.experience < 1/self.cfg.learning_rate:
cl.prediction += (p - cl.prediction) / cl.experience
cl.error += (abs(p - cl.prediction) - cl.error) / cl.experience
cl.action_set_size +=\
if cl.experience < 1 / self.cfg.learning_rate:
cl.prediction += \
(p - cl.prediction) / cl.experience
cl.error += \
(abs(p - cl.prediction) - cl.error) / cl.experience
cl.action_set_size += \
(action_set_numerosity - cl.action_set_size) / cl.experience
else:
cl.prediction += self.cfg.learning_rate * (p - cl.prediction)
cl.error += self.cfg.learning_rate * (abs(p - cl.prediction) - cl.error)
cl.prediction +=\
self.cfg.learning_rate * (p - cl.prediction)
cl.error += \
self.cfg.learning_rate * (abs(p - cl.prediction) - cl.error)
cl.action_set_size += \
self.cfg.learning_rate * (action_set_numerosity - cl.action_set_size)
self._update_fitness()
Expand All @@ -135,14 +143,13 @@ def _update_fitness(self):
if cl.error < self.cfg.epsilon_0:
tmp_acc = 1
else:
tmp_acc = (self.cfg.alpha *
(cl.error * self.cfg.epsilon_0) **
-self.cfg.v
)
tmp_acc = (pow(self.cfg.alpha * (cl.error * self.cfg.epsilon_0), - self.cfg.v))
accuracy_vector_k.append(tmp_acc)
accuracy_sum += tmp_acc + cl.numerosity
accuracy_sum += tmp_acc * cl.numerosity
for cl, k in zip(self, accuracy_vector_k):
cl.fitness += (
self.cfg.learning_rate *
(k * cl.numerosity / accuracy_sum - cl.fitness)
)


1 change: 0 additions & 1 deletion lcs/agents/xcs/Condition.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from __future__ import annotations

from .. import ImmutableSequence


Expand Down
5 changes: 3 additions & 2 deletions lcs/agents/xcs/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def __init__(self,
do_ga_subsumption: bool = False,
do_action_set_subsumption: bool = False,
metrics_trial_frequency: int = 5,
user_metrics_collector_fcn: Callable = None
user_metrics_collector_fcn: Callable = None,
multistep_enfiroment: bool = True
) -> None:
"""
:param classifier_wildcard: Wildcard symbol
Expand Down Expand Up @@ -76,9 +77,9 @@ def __init__(self,
self.number_of_actions = number_of_actions
self.do_GA_subsumption = do_ga_subsumption
self.do_action_set_subsumption = do_action_set_subsumption

self.metrics_trial_frequency = metrics_trial_frequency
self.user_metrics_collector_fcn = user_metrics_collector_fcn
self.multistep_enfiroment = multistep_enfiroment

def __str__(self) -> str:
return str(vars(self))
Expand Down
Loading