From 2b5d5c623162bb969836c32eea7d1560467ee930 Mon Sep 17 00:00:00 2001 From: bhughes Date: Sat, 25 Jun 2022 21:47:28 -0600 Subject: [PATCH 1/2] Increase speed/performance by roughly 10% use list comprehension DRY refactor Use enumerate Reverse order in if statement to avoid math when possible Remove unnecessary assignment Remove unnecessary cast (when more efficient) Remove unnecessary elif statement Remove unnecessary variables passed to functions --- skXCS/Classifier.py | 13 ++---------- skXCS/ClassifierSet.py | 44 +++++++++++++++-------------------------- skXCS/XCS.py | 45 +++++++++++++++--------------------------- 3 files changed, 34 insertions(+), 68 deletions(-) diff --git a/skXCS/Classifier.py b/skXCS/Classifier.py index 66cef152..a9965271 100644 --- a/skXCS/Classifier.py +++ b/skXCS/Classifier.py @@ -20,8 +20,6 @@ def __init__(self,xcs): self.initTimeStamp = xcs.iterationCount self.deletionProb = None - pass - def initializeWithParentClassifier(self,classifier): self.specifiedAttList = copy.deepcopy(classifier.specifiedAttList) self.condition = copy.deepcopy(classifier.condition) @@ -49,8 +47,6 @@ def match(self,state,xcs): else: if instanceValue == self.condition[i]: pass - elif instanceValue == None: - return False else: return False return True @@ -176,9 +172,7 @@ def uniformCrossover(self,classifier,xcs): p_cl_specifiedAttList = copy.deepcopy(classifier.specifiedAttList) # Make list of attribute references appearing in at least one of the parents.----------------------------- - comboAttList = [] - for i in p_self_specifiedAttList: - comboAttList.append(i) + comboAttList = [i for i in p_self_specifiedAttList] for i in p_cl_specifiedAttList: if i not in comboAttList: comboAttList.append(i) @@ -235,12 +229,10 @@ def uniformCrossover(self,classifier,xcs): if tempKey == 2: self.condition[i_cl1] = [newMin, newMax] classifier.condition.pop(i_cl2) - classifier.specifiedAttList.remove(attRef) else: classifier.condition[i_cl2] = [newMin, newMax] self.condition.pop(i_cl1) - self.specifiedAttList.remove(attRef) # Discrete Attribute @@ -311,9 +303,8 @@ def mutateAction(self,xcs): return changed def getDelProp(self,meanFitness,xcs): - if self.fitness / self.numerosity >= xcs.delta * meanFitness or self.experience < xcs.theta_del: + if self.experience < xcs.theta_del or self.fitness / self.numerosity >= xcs.delta * meanFitness: deletionVote = self.actionSetSize * self.numerosity - elif self.fitness == 0.0: deletionVote = self.actionSetSize * self.numerosity * meanFitness / (xcs.init_fit / self.numerosity) else: diff --git a/skXCS/ClassifierSet.py b/skXCS/ClassifierSet.py index 3bb6f5ec..bf06b27e 100644 --- a/skXCS/ClassifierSet.py +++ b/skXCS/ClassifierSet.py @@ -16,8 +16,7 @@ def createMatchSet(self,state,xcs): actionsNotCovered = copy.deepcopy(xcs.env.formatData.phenotypeList) totalNumActions = len(xcs.env.formatData.phenotypeList) - for i in range(len(self.popSet)): - classifier = self.popSet[i] + for i, classifier in enumerate(self.popSet): if classifier.match(state,xcs): self.matchSet.append(i) if classifier.action in actionsNotCovered: @@ -35,7 +34,7 @@ def createMatchSet(self,state,xcs): action = random.choice(copy.deepcopy(xcs.env.formatData.phenotypeList)) coveredClassifier = Classifier(xcs) coveredClassifier.initializeWithMatchingStateAndGivenAction(1,state,action,xcs) - self.addClassifierToPopulation(xcs,coveredClassifier,True) + self.addClassifierToPopulation(coveredClassifier,True) self.matchSet.append(len(self.popSet)-1) if len(actionsNotCovered) != 0: actionsNotCovered.remove(action) @@ -47,16 +46,16 @@ def createMatchSet(self,state,xcs): self.popSet[ref].matchCount += 1 xcs.timer.stopTimeMatching() - def getIdenticalClassifier(self,xcs,newClassifier): + def getIdenticalClassifier(self,newClassifier): for classifier in self.popSet: if newClassifier.equals(classifier): return classifier return None - def addClassifierToPopulation(self,xcs,classifier,isCovering): + def addClassifierToPopulation(self,classifier,isCovering): oldCl = None if not isCovering: - oldCl = self.getIdenticalClassifier(xcs,classifier) + oldCl = self.getIdenticalClassifier(classifier) if oldCl != None: oldCl.updateNumerosity(1) self.microPopSize += 1 @@ -96,18 +95,14 @@ def updateFitnessSet(self,xcs): accuracySum = 0 accuracies = [] - i = 0 - for clRef in self.actionSet: + for i, clRef in enumerate(self.actionSet): classifier = self.popSet[clRef] accuracies.append(classifier.getAccuracy(xcs)) accuracySum = accuracySum + accuracies[i]*classifier.numerosity - i+=1 - i = 0 - for clRef in self.actionSet: + for i, clRef in enumerate(self.actionSet): classifier = self.popSet[clRef] classifier.updateFitness(accuracySum,accuracies[i],xcs) - i+=1 ####Action Set Subsumption#### def do_action_set_subsumption(self,xcs): @@ -203,9 +198,9 @@ def insertDiscoveredClassifiers(self,child1,child2,parent1,parent2,xcs): xcs.timer.stopTimeSubsumption() else: if len(child1.specifiedAttList) > 0: - self.addClassifierToPopulation(xcs, child1, False) + self.addClassifierToPopulation(child1, False) if len(child2.specifiedAttList) > 0: - self.addClassifierToPopulation(xcs, child2, False) + self.addClassifierToPopulation(child2, False) def subsumeClassifier(self,child,parent1,parent2,xcs): if parent1.subsumes(child,xcs): @@ -218,7 +213,7 @@ def subsumeClassifier(self,child,parent1,parent2,xcs): xcs.trackingObj.subsumptionCount += 1 else: #No additional [A] subsumption w/ offspring rules if len(child.specifiedAttList) > 0: - self.addClassifierToPopulation(xcs, child, False) + self.addClassifierToPopulation(child, False) def getIterStampAverage(self): #Average GA Timestamp sumCl = 0 @@ -278,17 +273,15 @@ def deleteFromPopulation(self,xcs): vote = classifier.getDelProp(meanFitness,xcs) deletionProbSum += vote voteList.append(vote) - i = 0 - for classifier in self.popSet: + for i, classifier in enumerate(self.popSet): classifier.deletionProb = voteList[i]/deletionProbSum - i+=1 choicePoint = deletionProbSum * random.random() newSum = 0 - for i in range(len(voteList)): - classifier = self.popSet[i] - newSum = newSum + voteList[i] + for i, vote in enumerate(voteList): + newSum += vote if newSum > choicePoint: + classifier = self.popSet[i] classifier.updateNumerosity(-1) self.microPopSize -= 1 if classifier.numerosity < 1: @@ -326,22 +319,17 @@ def getAveGenerality(self,xcs): aveGenerality = 0 else: aveGenerality = generalitySum/self.microPopSize - return aveGenerality def getAttributeSpecificityList(self,xcs): #To be changed for XCS - attributeSpecList = [] - for i in range(xcs.env.formatData.numAttributes): - attributeSpecList.append(0) + attributeSpecList = [0] * xcs.env.formatData.numAttributes for cl in self.popSet: for ref in cl.specifiedAttList: attributeSpecList[ref] += cl.numerosity return attributeSpecList def getAttributeAccuracyList(self,xcs): #To be changed for XCS - attributeAccList = [] - for i in range(xcs.env.formatData.numAttributes): - attributeAccList.append(0.0) + attributeAccList = [0.0] * xcs.env.formatData.numAttributes for cl in self.popSet: for ref in cl.specifiedAttList: attributeAccList[ref] += cl.numerosity * cl.getAccuracy(xcs) diff --git a/skXCS/XCS.py b/skXCS/XCS.py index f5f795f9..9673502d 100644 --- a/skXCS/XCS.py +++ b/skXCS/XCS.py @@ -21,7 +21,7 @@ def __init__(self,learning_iterations=10000,N=1000,p_general=0.5,beta=0.2,alpha= random_state=None,prediction_error_reduction=0.25,fitness_reduction=0.1,reboot_filename=None): ''' - :param learning_iterations: Must be nonnegative integer. The number of explore or exploit learning iterations to run + :param learning_iterations: Must be nonnegative integer. The number of explore or exploit learning iterations to run :param N: Must be nonnegative integer. Maximum micropopulation size :param p_general: Must be float from 0 - 1. Probability of generalizing an allele during covering :param beta: Must be float. Learning Rate for updating statistics @@ -38,22 +38,22 @@ def __init__(self,learning_iterations=10000,N=1000,p_general=0.5,beta=0.2,alpha= :param init_fitness: Must be float. The initial prediction value when generating a new classifier (e.g in covering) :param p_explore: Must be float from 0 - 1. Probability of doing an explore cycle instead of an exploit cycle :param theta_matching: Must be nonnegative integer. Number of unique actions that must be represented in the match set (otherwise, covering) - :param do_GA_subsumption: Must be boolean. Do subsumption in GA - :param do_action_set_subsumption: Must be boolean. Do subsumption in [A] - :param max_payoff: Must be float. For single step problems, what the maximum reward for correctness + :param do_GA_subsumption: Must be boolean. Do subsumption in GA + :param do_action_set_subsumption: Must be boolean. Do subsumption in [A] + :param max_payoff: Must be float. For single step problems, what the maximum reward for correctness :param theta_sub: Must be nonnegative integer. The experience of a classifier required to be a subsumer :param theta_select: Must be float from 0 - 1. The fraction of the action set to be included in tournament selection - :param discrete_attribute_limit: Must be nonnegative integer OR "c" OR "d". Multipurpose param. If it is a nonnegative integer, discrete_attribute_limit determines the threshold that determines + :param discrete_attribute_limit: Must be nonnegative integer OR "c" OR "d". Multipurpose param. If it is a nonnegative integer, discrete_attribute_limit determines the threshold that determines if an attribute will be treated as a continuous or discrete attribute. For example, if discrete_attribute_limit == 10, if an attribute has more than 10 unique values in the dataset, the attribute will be continuous. If the attribute has 10 or less unique values, it will be discrete. Alternatively, discrete_attribute_limit can take the value of "c" or "d". See next param for this - :param specified_attributes: Must be an ndarray type of nonnegative integer attributeIndices (zero indexed). + :param specified_attributes: Must be an ndarray type of nonnegative integer attributeIndices (zero indexed). If "c", attributes specified by index in this param will be continuous and the rest will be discrete. If "d", attributes specified by index in this param will be discrete and the rest will be continuous. - :param random_state: Must be an integer or None. Set a constant random seed value to some integer (in order to obtain reproducible results). Put None if none (for pseudo-random algorithm runs) - :param prediction_error_reduction: Must be float. The reduction of the prediction error when generating an offspring classifier - :param fitness_reduction: Must be float. The reduction of the fitness when generating an offspring classifier - :param reboot_filename: Must be String or None. Filename of model to be rebooted + :param random_state: Must be an integer or None. Set a constant random seed value to some integer (in order to obtain reproducible results). Put None if none (for pseudo-random algorithm runs) + :param prediction_error_reduction: Must be float. The reduction of the prediction error when generating an offspring classifier + :param fitness_reduction: Must be float. The reduction of the fitness when generating an offspring classifier + :param reboot_filename: Must be String or None. Filename of model to be rebooted ''' #learning_iterations @@ -261,7 +261,7 @@ def __init__(self,learning_iterations=10000,N=1000,p_general=0.5,beta=0.2,alpha= def checkIsInt(self, num): try: - n = float(num) + float(num) # this unnecessary float cast improves performance! if num - int(num) == 0: return True else: @@ -271,7 +271,7 @@ def checkIsInt(self, num): def checkIsFloat(self,num): try: - n = float(num) + float(num) return True except: return False @@ -357,23 +357,19 @@ def fit(self,X,y): def runIteration(self,state): self.trackingObj.resetAll() shouldExplore = random.random() < self.p_explore + self.population.createMatchSet(state,self) + predictionArray = PredictionArray(self.population,self) if shouldExplore: - self.population.createMatchSet(state,self) - predictionArray = PredictionArray(self.population,self) actionWinner = predictionArray.randomActionWinner() self.population.createActionSet(actionWinner) reward = self.env.executeAction(actionWinner) self.population.updateActionSet(reward,self) self.population.runGA(state,self) - self.population.deletion(self) else: - self.population.createMatchSet(state, self) - predictionArray = PredictionArray(self.population, self) actionWinner = predictionArray.bestActionWinner() self.population.createActionSet(actionWinner) reward = self.env.executeAction(actionWinner) self.population.updateActionSet(reward, self) - self.population.deletion(self) if reward == self.max_payoff: if len(self.trackedAccuracy) == self.movingAvgCount: @@ -383,7 +379,7 @@ def runIteration(self,state): if len(self.trackedAccuracy) == self.movingAvgCount: del self.trackedAccuracy[0] self.trackedAccuracy.append(0) - + self.population.deletion(self) self.trackingObj.avgIterAge = self.iterationCount - self.population.getInitStampAverage() self.trackingObj.macroPopSize = len(self.population.popSet) self.trackingObj.microPopSize = self.population.microPopSize @@ -659,16 +655,7 @@ def get_final_attribute_accuracy_list(self): class TempTrackingObj(): #Tracks stats of every iteration (except accuracy, avg generality, and times) def __init__(self): - self.macroPopSize = 0 - self.microPopSize = 0 - self.matchSetSize = 0 - self.correctSetSize = 0 - self.avgIterAge = 0 - self.subsumptionCount = 0 - self.crossOverCount = 0 - self.mutationCount = 0 - self.coveringCount = 0 - self.deletionCount = 0 + self.resetAll() def resetAll(self): self.macroPopSize = 0 From 3b084e20cf8e60f1ae1f85447ba63dfb016b565e Mon Sep 17 00:00:00 2001 From: bhughes Date: Sun, 26 Jun 2022 15:34:25 -0600 Subject: [PATCH 2/2] Use builtin sum function. --- skXCS/ClassifierSet.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/skXCS/ClassifierSet.py b/skXCS/ClassifierSet.py index bf06b27e..2370044a 100644 --- a/skXCS/ClassifierSet.py +++ b/skXCS/ClassifierSet.py @@ -293,10 +293,7 @@ def deleteFromPopulation(self,xcs): return def getFitnessSum(self): - sum = 0 - for classifier in self.popSet: - sum += classifier.fitness - return sum + return sum(classifier.fitness for classifier in self.popSet) ####Clear Sets#### def clearSets(self):