def finish(self): """ To be called when the algorithm finished successfully. """ self.__finished = True self.onFinished() logging.log("Finished image generation")
def step(self): """ Draw a random shape onto the image and evaluate the detected class. """ self.createRandomShape() classification = api.classifyPILImage(self.image) self._countApiCall() classId = '' confidence = 0 if len(self._targetClasses) == 0: # no specific target class is specified # use the one with the highest confidence (already sorted by API) classId = str(classification[0]["class"]) confidence = classification[0]["confidence"] else: for c in classification: if c["class"] in self._targetClasses: classId = c["class"] confidence = c["confidence"] break # break as soon as a matching class is found # confidences are sorted by the api, # so we've selected the highest possible confidence here classId = self._targetClasses[random.randrange( 0, len(self._targetClasses))] logging.log(confidence) if confidence > 0.9: self.finish() self.onStep([(classId, confidence)])
def crossover(self): logging.log("EA: crossover") for j in range(len(self.population) - 1): colorsFirst = self.population[0 + j]["colors"] colorsSecond = self.population[1 + j]["colors"] img = Image.new('RGB', (64, 64), color='black') draw = ImageDraw.Draw(img) positions = [ ((0, 0), (32, 32)), ((32, 0), (64, 32)), ((0, 32), (32, 64)), ((32, 32), (64, 64)), ] colors = [ colorsFirst[0], colorsFirst[1], colorsSecond[2], colorsSecond[3] ] for i in range(4): draw.rectangle(positions[i], fill=colors[i]) self.population.append({ "image": img, "confidence": 0, "colors": colors, "class": "" })
def step(self): if not self.initialized: self.initPopulation(self.initialPopulationSize) if self.evalFitness(): return self.selection(self.targetPopulationSize, 1) self.matchCount = self.getCountThatMatch(self.targetConfidence) self.initialized = True self.crossover() self.mutate(self.targetConfidence) if self.evalFitness(): return self.__currentGeneration += 1 self.selection(self.targetPopulationSize, 1) # avoid local maxima by adding new individuals when no improvement newMatchCount = self.getCountThatMatch(self.targetConfidence) if newMatchCount == self.matchCount: self.addRandomImage() logging.log("EA: add new individual (avoid local maxima)") self.matchCount = newMatchCount if self.matchCount > self.targetPopulationSize: self.finish()
def step(self, forTest=False): """ Simulate a step of the evolutionary algorithm """ if not self.initialized: self.initPopulation(self.initialPopulationSize) if self.evalFitness(forTest): return self.selection(self.targetPopulationSize, 2) self.matchCount = self.getCountThatMatch(self.targetConfidence) self.initialized = True self.crossover() self.mutate(self.targetConfidence) if self.evalFitness(forTest): return self.__currentGeneration += 1 self.selection(self.targetPopulationSize, 2) # avoid local maxima by adding new individuals when no improvement newMatchCount = self.getCountThatMatch(self.targetConfidence) if newMatchCount == self.matchCount: self.addRandomImage() logging.log("EA: add new individual (avoid local maxima)") self.matchCount = newMatchCount if self.matchCount > self.targetPopulationSize: self.finish()
def selection(self, bestCount, sameClassCount): """ Select best individuals from population according to the same class count (used for crossover) """ print("doing selection") logging.log("EA: selection") # sort by confidence self.population.sort(key=lambda individual: individual["confidence"], reverse=True) classesContained = [] selectedPopulation = [] for individual in self.population: # limit count of individuals with same class if (classesContained.count(individual["class"]) < sameClassCount): # do not take individuals with confidence > 90 % if (not any(selectedIndividual["confidence"] >= self.targetConfidence and selectedIndividual["class"] == individual["class"] for selectedIndividual in selectedPopulation)): selectedPopulation.append(individual) classesContained.append(individual["class"]) self.population = selectedPopulation # reduce individuals -> reduce API calls if sameClassCount is 2: # del population[int(INITIAL_POPULATION/2):] print("no individuals deleted from selection") elif sameClassCount is 1: del self.population[bestCount:]
def mutate(self, confidence): """ Mutate each individual in the population by mutating random color and polygon points """ print("doing mutation of crossover images") logging.log("EA: mutation") population_size = len(self.population) for i in range(population_size): # use only for confidence < 90% and crossovered individuals if (self.population[i]["confidence"] < self.targetConfidence and self.population[i]["lastCrossover"] is True): img = Image.new('RGB', (64, 64), color='black') draw = ImageDraw.Draw(img) # mutate colors colors = self.population[i]["colors"] colors = list( map( lambda color: (color[0] + random.randint( -self.colorMutationRate, self.colorMutationRate ), color[1] + random.randint( -self.colorMutationRate, self.colorMutationRate ), color[2] + random.randint(-self.colorMutationRate, self.colorMutationRate)), colors)) # mutate shapes shapes = self.population[i]["shapes"] newShapes = [] for shape in shapes: # add or delete point if random.random() < 0.5: idx = random.randrange(0, len(shape)) if (len(shape) > self.shapePolygonPointCount and random.random() < 0.5): del shape[idx] else: shape.insert(idx, self.randomCoord()) # mutate point shape = list( map( lambda x: (self.mutateCoord(x[0]), self.mutateCoord(x[1])), shape)) newShapes.append(shape) self.drawShapes(draw, colors, newShapes) self.population[i] = { "image": img, "confidence": 0, "colors": colors, "class": "", "shapes": newShapes, "lastCrossover": False } self.__totalMutationCount += 1
def __sendToApi(data): """ Classify an image using the API. data needs to contain the bytes of a png formatted image. """ key = config.get('API', 'key') url = config.get('API', 'url') success = False resJson = None printedRateLimitingMessage = False with __lock: while not success: try: res = requests.post( url, data={'key': key}, files={'image': data} ) resJson = res.json() success = True except JSONDecodeError: if not printedRateLimitingMessage: logging.log('API: Rate limiting detected, retrying...') printedRateLimitingMessage = True time.sleep(1) except ValueError: if not printedRateLimitingMessage: logging.log('API: Rate limiting detected, retrying...') printedRateLimitingMessage = True time.sleep(1) except Exception as e: logging.log('API: Unexpected Error: ' + str(e) + ', retrying...') time.sleep(1) return resJson
def mutate(self, confidence): logging.log("EA: mutation") oldPopulationSize = len(self.population) for j in range(oldPopulationSize): img = Image.new('RGB', (64, 64), color='black') draw = ImageDraw.Draw(img) positions = [ ((0, 0), (32, 32)), ((32, 0), (64, 32)), ((0, 32), (32, 64)), ((32, 32), (64, 64)), ] colors = self.population[j]["colors"] if (self.population[j]["confidence"] < confidence): # change the color of a random square rect = random.randint(0, 3) colors[rect] = ( colors[rect][0] + random.randint(-self.colorMutationRate, self.colorMutationRate), colors[rect][1] + random.randint(-self.colorMutationRate, self.colorMutationRate), colors[rect][2] + random.randint(-self.colorMutationRate, self.colorMutationRate), ) for i in range(4): draw.rectangle(positions[i], fill=colors[i]) self.population.append({ "image": img, "confidence": 0, "colors": colors, "class": "" }) # delete old del self.population[:oldPopulationSize]
def start(self): super().start() self.__startTime = time.time() logging.log("Started image generation")
def crossover(self): """ Crossover between individuals with same classes from the population. Sort duplicates with same classes like [vorfahrt52%, vorfahrt35%] """ print("doing crossover") seen = [] # helper list for found classes duplicates = [] # append one individual from every class for individual in self.population: if individual["class"] not in seen: duplicates.append([individual]) seen.append(individual["class"]) # print(duplicates) # append other individuals from same class for index, entry in enumerate(duplicates): for individual in self.population: if (individual not in entry and individual["class"] == entry[0]["class"]): duplicates[index] = duplicates[index] + [individual] # filter duplicates for crossover by confidence and length # crossover makes only sense for at least two individuals duplicates = [ entry for entry in duplicates if len(entry) > 1 and entry[0]["confidence"] < 0.90 ] beforeCrossover = duplicates # for testing function afterCrossover = [] newImagesAppended = 0 # crossover by adding polygons points for entry in duplicates: # remove every other point # shape = shape[1::2] image = Image.new('RGB', (64, 64)) draw = ImageDraw.Draw(image) shapes = entry[0]["shapes"] + entry[1]["shapes"] colors = entry[0]["colors"] + entry[1]["colors"][1:] self.drawShapes(draw, colors, shapes) newIndividual = { "image": image, "confidence": 0, "colors": colors, "class": "", "shapes": shapes, "lastCrossover": True } afterCrossover.append(newIndividual) self.population.append(newIndividual) # add second crossover child image = Image.new('RGB', (64, 64)) draw = ImageDraw.Draw(image) shapes = entry[1]["shapes"] + entry[0]["shapes"] colors = entry[1]["colors"] + entry[0]["colors"][1:] self.drawShapes(draw, colors, shapes) newIndividual = { "image": image, "confidence": 0, "colors": colors, "class": "", "shapes": shapes, "lastCrossover": True } afterCrossover.append(newIndividual) self.population.append(newIndividual) newImagesAppended += 2 print("crossover, appended images: " + str(newImagesAppended)) logging.log("EA: crossover, add " + str(newImagesAppended) + " individuals") # for testing crossover method return {"before": beforeCrossover, "after": afterCrossover}