I've created an image approximating genetic algorithm using python 3 and opencv. What it does is, it creates a population of individuals that draw random colored,sized, and opacity circles onto a blank image. The fittest eventually saturate the population after several hundred generations.
I tried to implement multiprocessing because rendering the images takes time correlating to population size and circle size, as well as target image size (important for detail fineness)
What I did is I used multiprocessing and Pool, with the array of individual objects as the iterable and mapped out only the fitness and id. In effect, in the main process none of the individuals have their own canvas, whereas in the multiprocess processes, each individuals render out their canvas and calculate fitness/difference.
However, it seems using multiprocessing makes the whole program slower? In fact, the rendering process seems to be taking the same amount of speed compared to serialized processing, but is taking slower because of the multiprocessing aspect.
class PopulationCircle:
def renderPop(self, individual):
individual.render()
return [individual.index, individual.fitness]
class IndividualCircle:
def render(self):
self.genes.sort(key=lambda x: x[-1], reverse=False)
self.canvas = np.zeros((self.height,self.width, 4), np.uint8)
for i in range(self.maxCount):
overlay=self.canvas.copy()
cv2.circle(overlay, (self.genes[i][0], self.genes[i][1]), self.genes[i][2], (self.genes[i][3],self.genes[i][4],self.genes[i][5]), -1, lineType=cv2.LINE_AA)
self.canvas = cv2.addWeighted(overlay, self.genes[i][6], self.canvas, 1-self.genes[i][6], 0)
diff = np.absolute(np.array(self.target)- np.array(self.canvas))
diffSum = np.sum(diff)
self.fitness = diffSum
def evolution(mainPop, generationLimit):
p = mp.Pool()
for i in range(int(generationLimit)):
start_time = time.time()
result =[]
print(f"""
-----------------------------------------
Current Generation: {mainPop.generation}
Initial Score: {mainPop.score}
-----------------------------------------
""")
#Multiprocessing used for rendering out canvas since it takes time.
result = p.map(mainPop.renderPop, mainPop.population)
#returns [individual.index, individual.fitness]; results is a list of list
result.sort(key = lambda x: x[0], reverse=False)
#Once multiprocessing is done, we only receive fitness value and index.
for k in mainPop.population:
k.fitness = result[k.index][1]
mainPop.population.sort(key = lambda x: x.fitness, reverse = True)
if mainPop.generation == 0:
mainPop.score = mainPop.population[-1].fitness
"""
Things to note:
In main process, none of the individuals have a canvas since the rendering
is done on a different process tree.
The only thing that changes in this main process is the individual's
fitness.
After calling .renderHD and .renderLD, the fittest member will have a canvas
drawn in this process.
"""
end_time = time.time() - start_time
print(f"Time taken: {end_time}")
if i%50==0:
mainPop.population[0].renderHD()
cv2.imwrite( f"../output/generationsPoly/generation{i}.jpg", mainPop.population[0].canvasHD)
if i%10==0:
mainPop.population[0].renderLD()
cv2.imwrite( f"../output/allGenPoly/image{i}.jpg", mainPop.population[0].canvas)
mainPop.toJSON()
mainPop.breed()
p.close()
p.join()
if __name__ == "__main__":
#Creates Population object
#init generates self.population array which is an array of IndividualCircle objects that contain DNA and render methods
pop = PopulationCircle(targetDIR, maxPop, circleAmount, mutationRate, mutationAmount, cutOff)
#Starts loop
evolution(pop, generations)
if I use 600 population with 800 circles, serial took: 11siteration avg. multiprocess: 18s/iteration avg.
I'm very new to multiprocessing so any help would be appreciated.