Parcourir la source

MO development for knapsack

Jérôme BUISINE il y a 3 ans
Parent
commit
1892c4e38d

+ 6 - 11
knapsackMultiExample.py

@@ -26,9 +26,9 @@ logging.basicConfig(format='%(asctime)s %(message)s', filename='data/exampleMOEA
 
 random.seed(42)
 
-elements_score1 = [ random.randint(1, 20) for _ in range(30) ]
-elements_score2 = [ random.randint(1, 20) for _ in range(30) ]
-elements_weight = [ random.randint(2, 5) for _ in range(30) ]
+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) ]
 
 def knapsackWeight(_solution):
 
@@ -41,14 +41,14 @@ def knapsackWeight(_solution):
 # default validator
 def validator(_solution):
 
-    if knapsackWeight(_solution) <= 80:
+    if knapsackWeight(_solution) <= 600:
         return True
     else:
         False
 
 # define init random solution
 def init():
-    return BinarySolution([], 30).random(validator)
+    return BinarySolution([], 200).random(validator)
 
 def evaluator1(_solution):
 
@@ -78,12 +78,7 @@ def main():
     algo = MOEAD(20, 5, init, [evaluator1, evaluator2], operators, policy, validator, _maximise=True)
     algo.addCallback(MultiCheckpoint(_every=5, _filepath=filepath))
 
-    bestSol = algo.run(1000)
-    bestSol = algo.run(1000)
-
-    print('Solution score1 is {}'.format(evaluator1(bestSol)))
-    print('Solution score2 is {}'.format(evaluator2(bestSol)))
-    print('Solution weigth is {}'.format(knapsackWeight(bestSol)))
+    paretoFront = algo.run(100000)
 
 if __name__ == "__main__":
     main()

+ 127 - 11
macop/algorithms/multi/MOEAD.py

@@ -2,14 +2,24 @@
 """
 
 # main imports
+import pkgutil
 import logging
 import math
 import numpy as np
+import sys
+from ...utils.color import macop_text, macop_line, macop_progress
 
 # module imports
 from ..Algorithm import Algorithm
 from .MOSubProblem import MOSubProblem
 
+# 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
+
+
 def moEvaluator(_solution, _evaluator, _weights):
 
     scores = [ eval(_solution) for eval in _evaluator ]
@@ -33,7 +43,8 @@ class MOEAD(Algorithm):
         validator: {function} -- basic function to check if solution is valid or not under some constraints
         maximise: {bool} -- specify kind of optimization problem 
         currentSolution: {Solution} -- current solution managed for current evaluation
-        bestSolution: {Solution} -- best solution found so far during running algorithm
+        population: [{Solution}] -- population of solution, one for each sub problem
+        pfPop: [{Solution}] -- pareto front population
         callbacks: {[Callback]} -- list of Callback class implementation to do some instructions every number of evaluations and `load` when initializing algorithm
     """
     def __init__(self,
@@ -101,7 +112,12 @@ class MOEAD(Algorithm):
             self.subProblems.append(subProblem)
 
         self.population = [None for n in range(self.mu)]
-        self.refPoint = (0., 0.)
+        self.pfPop = []
+
+        if self.maximise:
+            self.refPoint = [ 0 for _ in range(len(_evaluator)) ]
+        else:
+            self.refPoint = [ sys.float_info.max for _ in range(len(_evaluator)) ]
         
 
     def initRun(self):
@@ -125,14 +141,15 @@ class MOEAD(Algorithm):
         # by default use of mother method to initialize variables
         super().run(_evaluations)
 
-        # enable callback resume for MOEAD
-        self.resume()
-
         # initialize each sub problem
         for i in range(self.mu):
             self.subProblems[i].run(1)
 
             self.population[i] = self.subProblems[i].bestSolution
+            self.pfPop.append(self.subProblems[i].bestSolution)
+
+        # enable callback resume for MOEAD
+        self.resume()
 
         # MOEAD algorithm implementation
         while not self.stop():
@@ -143,9 +160,30 @@ class MOEAD(Algorithm):
                 self.subProblems[i].run(1)
                 spBestSolution = self.subProblems[i].bestSolution
 
-                self.updateMinRefPoint(spBestSolution)
+                self.updateRefPoint(spBestSolution)
+
+                # for each neighbor of current sub problem update solution if better
+                improvment = False
+                for j in self.neighbors[i]:
+                    if spBestSolution.fitness() > self.subProblems[j].bestSolution.fitness():
+                        
+                        # 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))
+                        self.subProblems[j].evaluate(newSolution)
+                        self.subProblems[j].bestSolution = newSolution
+                       
+                        improvment = True
+
+                # add new solution if improvment is idenfity
+                if improvment:
+                    self.pfPop.append(spBestSolution)
 
+                # update pareto front
+                self.pfPop = self.paretoFront(self.pfPop)
 
+                # add progress here
+                self.progress()
 
                 # stop algorithm if necessary
                 if self.stop():
@@ -156,7 +194,24 @@ class MOEAD(Algorithm):
 
         self.end()
 
-        return self.bestSolution
+        return self.pfPop
+
+    def progress(self):
+        """
+        Log progress and apply callbacks if necessary
+        """
+        if len(self.callbacks) > 0:
+            for callback in self.callbacks:
+                callback.run()
+
+        macop_progress(self.getGlobalEvaluation(),
+                       self.getGlobalMaxEvaluation())
+
+        logging.info("-- %s evaluation %s of %s (%s%%)" %
+                     (type(self).__name__, self.numberOfEvaluations,
+                      self.maxEvaluations, "{0:.2f}".format(
+                          (self.numberOfEvaluations) / self.maxEvaluations *
+                          100.)))
 
     def setNeighbors(self):
 
@@ -185,11 +240,72 @@ class MOEAD(Algorithm):
                 self.neighbors[direction].append(i)
 
 
-    def updateMinRefPoint(self, _solution):
-        pass
+    def updateRefPoint(self, _solution):
 
-    def nonDominated():
-        pass
+        if self.maximise:
+            for i in range(len(self.evaluator)):
+                if _solution.scores[i] > self.refPoint[i]:
+                    self.refPoint[i] = _solution.scores[i]
+        else:
+            for i in range(len(self.evaluator)):
+                if _solution.scores[i] < self.refPoint[i]:
+                    self.refPoint[i] = _solution.scores[i]
+
+    def paretoFront(self, _population):
+
+        paFront = []
+        indexes = []
+        nObjectives = len(self.evaluator)
+        nSolutions = len(_population)
+        
+        # find dominated solution
+        for i in range(nSolutions):
+            for j in range(nSolutions):
+
+                if j in indexes:
+                    continue
+
+                nDominated = 0
+
+                # check number of dominated objectives of current solution by the others solution
+                for k in range(len(self.evaluator)):
+                    if self.maximise:
+                        if _population[i].scores[k] < _population[j].scores[k]:
+                            nDominated += 1
+                    else:
+                        if _population[i].scores[k] > _population[j].scores[k]:
+                            nDominated += 1
+
+                if nDominated == nObjectives:
+                    indexes.append(i)
+                    break
+
+        # store the non dominated solution into pareto front
+        for i in range(nSolutions):
+            if i not in indexes:
+                paFront.append(_population[i])
+
+        return paFront
+
+
+    def end(self):
+        """Display end message into `run` method
+        """
+
+        print(macop_text('({}) Found after {} evaluations'.format(type(self).__name__, self.numberOfEvaluations)))
+        
+        for i, solution in enumerate(self.pfPop):
+            print('  - [{}] {} : {}'.format(i, solution.scores, solution))
+
+        print(macop_line())
+
+    def information(self):
+        
+        logging.info("-- Pareto front :")
+
+        for i, solution in enumerate(self.pfPop):
+            logging.info("-- %s] SCORE %s - %s" %
+                        (i, solution.scores, solution))
 
     def __str__(self):
         return "%s using %s" % (type(self).__name__, type(

+ 17 - 22
macop/algorithms/multi/MOSubProblem.py

@@ -46,7 +46,7 @@ class MOSubProblem(Algorithm):
         Run the local search algorithm
 
         Args:
-            _evaluations: {int} -- number of Local search evaluations
+            _evaluations: {int} -- number of evaluations
             
         Returns:
             {Solution} -- best solution found
@@ -55,32 +55,27 @@ class MOSubProblem(Algorithm):
         # by default use of mother method to initialize variables
         super().run(_evaluations)
 
-        solutionSize = self.bestSolution.size
+        for _ in range(_evaluations):
+            # update solution using policy
+            newSolution = self.update(self.bestSolution)
 
-        # local search algorithm implementation
-        while not self.stop():
+            # if better solution than currently, replace it
+            if self.isBetter(newSolution):
+                self.bestSolution = newSolution
 
-            for _ in range(solutionSize):
+            # increase number of evaluations
+            self.increaseEvaluation()
 
-                # update solution using policy
-                newSolution = self.update(self.bestSolution)
+            self.progress()
 
-                # if better solution than currently, replace it
-                if self.isBetter(newSolution):
-                    self.bestSolution = newSolution
-
-                # increase number of evaluations
-                self.increaseEvaluation()
-
-                self.progress()
-                logging.info("---- Current %s - SCORE %s" %
-                             (newSolution, newSolution.fitness()))
-
-                # stop algorithm if necessary
-                if self.stop():
+            # stop algorithm if necessary
+            if self.stop():
                     break
 
-        logging.info("End of %s, best solution found %s" %
-                     (type(self).__name__, self.bestSolution))
+            logging.info("---- Current %s - SCORE %s" %
+                            (newSolution, newSolution.fitness()))
+
+            logging.info("End of %s, best solution found %s" %
+                        (type(self).__name__, self.bestSolution))
 
         return self.bestSolution

+ 41 - 32
macop/callbacks/MultiCheckpoint.py

@@ -1,4 +1,4 @@
-"""Basic Checkpoint class implementation
+"""Multi Checkpoint class implementation
 """
 
 # main imports
@@ -13,7 +13,7 @@ from ..utils.color import macop_text, macop_line
 
 class MultiCheckpoint(Callback):
     """
-    BasicCheckpoint is used for loading previous computations and start again after loading checkpoint
+    MultiCheckpoint is used for loading previous computations and start again after loading checkpoint
 
     Attributes:
         algo: {Algorithm} -- main algorithm instance reference
@@ -24,8 +24,8 @@ class MultiCheckpoint(Callback):
         """
         Check if necessary to do backup based on `every` variable
         """
-        # get current best solution
-        solution = self.algo.bestSolution
+        # get current population
+        population = self.algo.population
 
         currentEvaluation = self.algo.getGlobalEvaluation()
 
@@ -34,52 +34,61 @@ class MultiCheckpoint(Callback):
 
             logging.info("Checkpoint is done into " + self.filepath)
 
-            solutionData = ""
-            solutionSize = len(solution.data)
+            with open(self.filepath, 'w') as f:
+                        
+                for solution in population:
+                    solutionData = ""
+                    solutionSize = len(solution.data)
 
-            for index, val in enumerate(solution.data):
-                solutionData += str(val)
+                    for index, val in enumerate(solution.data):
+                        solutionData += str(val)
 
-                if index < solutionSize - 1:
-                    solutionData += ' '
+                        if index < solutionSize - 1:
+                            solutionData += ' '
 
-            line = str(currentEvaluation) + ';' + solutionData + ';' + str(
-                solution.fitness()) + ';\n'
+                    line = str(currentEvaluation) + ';'
 
-            # check if file exists
-            if not os.path.exists(self.filepath):
-                with open(self.filepath, 'w') as f:
-                    f.write(line)
-            else:
-                with open(self.filepath, 'a') as f:
+                    for i in range(len(self.algo.evaluator)):
+                        line += str(solution.scores[i]) + ';'
+
+                    line += solutionData + ';\n'
+                    
                     f.write(line)
 
     def load(self):
         """
-        Load last backup line of solution and set algorithm state (best solution and evaluations) at this backup
+        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:
 
-                # get last line and read data
-                lastline = f.readlines()[-1]
-                data = lastline.split(';')
+                # read data for each line
+                for i, line in enumerate(f.readlines()):
+
+                    data = line.replace(';\n', '').split(';')
+                
+                    # only the first time
+                    if i == 0:
+                        # get evaluation  information
+                        globalEvaluation = int(data[0])
 
-                # get evaluation  information
-                globalEvaluation = int(data[0])
+                        if self.algo.parent is not None:
+                            self.algo.parent.numberOfEvaluations = globalEvaluation
+                        else:
+                            self.algo.numberOfEvaluations = globalEvaluation
 
-                if self.algo.parent is not None:
-                    self.algo.parent.numberOfEvaluations = globalEvaluation
-                else:
-                    self.algo.numberOfEvaluations = globalEvaluation
+                    nObjectives = len(self.algo.evaluator)
+                    scores = [ float(s) for s in data[1:nObjectives + 1] ]
 
-                # get best solution data information
-                solutionData = list(map(int, data[1].split(' ')))
+                    # get best solution data information
+                    solutionData = list(map(int, data[-1].split(' ')))
+                        
+                    self.algo.population[i].data = np.array(solutionData)
+                    self.algo.population[i].scores = scores
 
-                self.algo.bestSolution.data = np.array(solutionData)
-                self.algo.bestSolution.score = float(data[2])
+                    self.algo.pfPop[i] = self.algo.population[i]
 
             print(
                 macop_text('Restart algorithm from evaluation {}.'.format(