Parcourir la source

add pareto checkpoint; update moead use

Jérôme BUISINE il y a 3 ans
Parent
commit
9bd549721d

+ 1 - 2
.gitignore

@@ -58,8 +58,7 @@ docs/_build/
 # PyBuilder
 target/
 
-example.log
-checkpoints.csv
+data
 !docs/build
 .vscode
 .python-version

+ 1 - 0
docs/source/macop.rst

@@ -22,6 +22,7 @@ macop.callbacks
    
    macop.callbacks.BasicCheckpoint
    macop.callbacks.MultiCheckpoint
+   macop.callbacks.ParetoCheckpoint
    macop.callbacks.Callback
 
 macop.evaluators

+ 14 - 8
knapsackMultiExample.py

@@ -17,6 +17,7 @@ from macop.operators.policies.UCBPolicy import UCBPolicy
 
 from macop.algorithms.multi.MOEAD import MOEAD
 from macop.callbacks.MultiCheckpoint import MultiCheckpoint
+from macop.callbacks.ParetoCheckpoint import ParetoCheckpoint
 
 if not os.path.exists('data'):
     os.makedirs('data')
@@ -26,9 +27,9 @@ logging.basicConfig(format='%(asctime)s %(message)s', filename='data/exampleMOEA
 
 random.seed(42)
 
-elements_score1 = [ random.randint(1, 100) for _ in range(200) ]
-elements_score2 = [ random.randint(1, 200) for _ in range(200) ]
-elements_weight = [ random.randint(2, 10) for _ in range(200) ]
+elements_score1 = [ random.randint(1, 100) for _ in range(500) ]
+elements_score2 = [ random.randint(1, 200) for _ in range(500) ]
+elements_weight = [ random.randint(90, 100) for _ in range(500) ]
 
 def knapsackWeight(_solution):
 
@@ -41,7 +42,7 @@ def knapsackWeight(_solution):
 # default validator
 def validator(_solution):
 
-    if knapsackWeight(_solution) <= 600:
+    if knapsackWeight(_solution) <= 15000:
         return True
     else:
         False
@@ -67,18 +68,23 @@ def evaluator2(_solution):
     return fitness
 
 
-filepath = "data/checkpointsMOEAD.csv"
+mo_checkpoint_path = "data/checkpointsMOEAD.csv"
+pf_checkpoint_path = "data/pfMOEAD.csv"
+
 
 def main():
 
     operators = [SimpleBinaryMutation(), SimpleMutation(), SimpleCrossover(), RandomSplitCrossover()]
-    policy = UCBPolicy(operators)
+    policy = RandomPolicy(operators)
 
     # pass list of evaluators
     algo = MOEAD(20, 5, init, [evaluator1, evaluator2], operators, policy, validator, _maximise=True)
-    algo.addCallback(MultiCheckpoint(_every=5, _filepath=filepath))
+    algo.addCallback(MultiCheckpoint(_every=5, _filepath=mo_checkpoint_path))
+    algo.addCallback(ParetoCheckpoint(_every=5, _filepath=pf_checkpoint_path))
+
+    paretoFront = algo.run(10000)
 
-    paretoFront = algo.run(100000)
+    print("Pareto front is composed of", len(paretoFront), "solutions")
 
 if __name__ == "__main__":
     main()

+ 5 - 0
macop/algorithms/multi/MOEAD.py

@@ -170,8 +170,13 @@ class MOEAD(Algorithm):
                         # create new solution based on current new if better, computes fitness associated to new solution for sub problem
                         class_name = type(spBestSolution).__name__
                         newSolution = getattr(globals()['macop.solutions.' + class_name], class_name)(spBestSolution.data, len(spBestSolution.data))
+
+                        # evaluate solution for new sub problem and update as best solution
                         self.subProblems[j].evaluate(newSolution)
                         self.subProblems[j].bestSolution = newSolution
+
+                        # update population solution for this sub problem
+                        self.population[j] = newSolution
                        
                         improvment = True
 

+ 3 - 2
macop/callbacks/MultiCheckpoint.py

@@ -90,8 +90,9 @@ class MultiCheckpoint(Callback):
 
                     self.algo.pfPop[i] = self.algo.population[i]
 
-            print(
-                macop_text('Restart algorithm from evaluation {}.'.format(
+            print(macop_line())
+            print(macop_text('Load of available population from `{}`'.format(self.filepath)))
+            print(macop_text('Restart algorithm from evaluation {}.'.format(
                     self.algo.numberOfEvaluations)))
 
         else:

+ 101 - 0
macop/callbacks/ParetoCheckpoint.py

@@ -0,0 +1,101 @@
+"""Pareto front Checkpoint class implementation
+"""
+
+# main imports
+import os
+import logging
+import numpy as np
+import pkgutil
+
+# module imports
+from .Callback import Callback
+from ..utils.color import macop_text, macop_line
+
+# import all available solutions
+for loader, module_name, is_pkg in pkgutil.walk_packages(
+        path=['macop/solutions'], prefix='macop.solutions.'):
+    _module = loader.find_module(module_name).load_module(module_name)
+    globals()[module_name] = _module
+
+class ParetoCheckpoint(Callback):
+    """
+    Pareto checkpoint is used for loading previous computations and start again after loading checkpoint
+
+    Attributes:
+        algo: {Algorithm} -- main algorithm instance reference
+        every: {int} -- checkpoint frequency used (based on number of evaluations)
+        filepath: {str} -- file path where checkpoints will be saved
+    """
+    def run(self):
+        """
+        Check if necessary to do backup based on `every` variable
+        """
+        # get current population
+        pfPop = self.algo.pfPop
+
+        currentEvaluation = self.algo.getGlobalEvaluation()
+
+        # backup if necessary
+        if currentEvaluation % self.every == 0:
+
+            logging.info("Checkpoint is done into " + self.filepath)
+
+            with open(self.filepath, 'w') as f:
+                        
+                for solution in pfPop:
+                    solutionData = ""
+                    solutionSize = len(solution.data)
+
+                    for index, val in enumerate(solution.data):
+                        solutionData += str(val)
+
+                        if index < solutionSize - 1:
+                            solutionData += ' '
+
+                    line = ''
+
+                    for i in range(len(self.algo.evaluator)):
+                        line += str(solution.scores[i]) + ';'
+
+                    line += solutionData + ';\n'
+                    
+                    f.write(line)
+
+    def load(self):
+        """
+        Load backup lines as population and set algorithm state (population and pareto front) at this backup
+        """
+        if os.path.exists(self.filepath):
+
+            logging.info('Load best solution from last checkpoint')
+            with open(self.filepath) as f:
+                
+                # reinit pf population
+                self.algo.pfPop = []
+
+                # retrieve class name from algo
+                class_name = type(self.algo.population[0]).__name__
+
+                # read data for each line
+                for line in f.readlines():
+
+                    data = line.replace(';\n', '').split(';')
+                
+                    nObjectives = len(self.algo.evaluator)
+                    scores = [ float(s) for s in data[0:nObjectives] ]
+
+                    # get best solution data information
+                    solutionData = list(map(int, data[-1].split(' ')))
+
+                    newSolution = getattr(globals()['macop.solutions.' + class_name], class_name)(solutionData, len(solutionData))
+                    newSolution.scores = scores
+
+                    self.algo.pfPop.append(newSolution)
+
+            print(macop_text('Load of available pareto front backup from `{}`'.format(self.filepath)))
+
+        else:
+            print(macop_text('No pareto front found... Start running algorithm with new pareto front population.'))
+            logging.info("No pareto front backup used...")
+
+        print(macop_line())