diff --git a/Confirmation_PP_FR8039550.pdf b/Confirmation_PP_FR8039550.pdf new file mode 100644 index 0000000..57d400e Binary files /dev/null and b/Confirmation_PP_FR8039550.pdf differ diff --git a/brain.py b/brain.py index 4b98296..0cc48d5 100644 --- a/brain.py +++ b/brain.py @@ -1,37 +1,50 @@ import numpy as np import random -def mat_mult(A,B): - return [[sum([A[i][m]*B[m][j] for m in range(len(A[0]))]) for j in range(len(B[0]))] for i in range(len(A))] + +def mat_mult(A, B): + return [ + [sum([A[i][m] * B[m][j] for m in range(len(A[0]))]) for j in range(len(B[0]))] + for i in range(len(A)) + ] + class Neural_Network(object): # inspired from https://enlight.nyc/projects/neural-network/ def __init__(self, W1=None, W2=None): - #parameters + # parameters self.inputSize = 3 self.outputSize = 2 self.hiddenSize = 3 - self.fitness = 0 + self.fitness = 0 - #weights + # weights if W1 is not None: - self.W1=W1 - else : - self.W1 = np.random.randn(self.inputSize, self.hiddenSize) # weights from input to hidden layer - + self.W1 = W1 + else: + self.W1 = np.random.randn( + self.inputSize, self.hiddenSize + ) # weights from input to hidden layer + if W2 is not None: - self.W2=W2 - else : - self.W2 = np.random.randn(self.hiddenSize, self.outputSize) # weights from hidden to output layer + self.W2 = W2 + else: + self.W2 = np.random.randn( + self.hiddenSize, self.outputSize + ) # weights from hidden to output layer # self.w1 = [[random.random() for i in range(self.hiddenSize)] for i in range(self.inputSize)] # self.w2 = [[random.random() for i in range(self.outputSize)] for i in range(self.hiddenSize)] def predict(self, X): - #forward propagation through our network - self.z = np.dot(X, self.W1) # dot product of X (input) and first set of 3x2 weights - self.z2 = self.sigmoid(self.z) # activation function - self.z3 = np.dot(self.z2, self.W2) # dot product of hidden layer (z2) and second set of 3x1 weights - o = self.sigmoid(self.z3) # final activation function + # forward propagation through our network + self.z = np.dot( + X, self.W1 + ) # dot product of X (input) and first set of 3x2 weights + self.z2 = self.sigmoid(self.z) # activation function + self.z3 = np.dot( + self.z2, self.W2 + ) # dot product of hidden layer (z2) and second set of 3x1 weights + o = self.sigmoid(self.z3) # final activation function # self.z = mat_mult(X, self.w1) # dot product of X (input) and first set of 3x2 weights # self.z2 = self.sigmoid(self.z) # activation function # self.z3 = mat_mult(self.z2, self.w2) # dot product of hidden layer (z2) and second set of 3x1 weights @@ -40,4 +53,4 @@ class Neural_Network(object): def sigmoid(self, s): # activation function - return 1/(1+np.exp(-s)) -0.5 \ No newline at end of file + return 1 / (1 + np.exp(-s)) - 0.5 diff --git a/car.py b/car.py index 866098d..bb342b3 100644 --- a/car.py +++ b/car.py @@ -4,10 +4,20 @@ import random import pygame from brain import Neural_Network -from params import GY, CAR_MAX_SPEED, CAR_MAX_FITNESS, CAR_SIZE, CAR_STEERING_FACTOR, VISION_LENGTH, VISION_SPAN, THROTTLE_POWER, screen +from params import ( + GY, + CAR_MAX_SPEED, + CAR_MAX_FITNESS, + CAR_SIZE, + CAR_STEERING_FACTOR, + VISION_LENGTH, + VISION_SPAN, + THROTTLE_POWER, + screen, +) from trigo import angle_to_vector, get_line_feats, segments_intersection, distance -IMG = pygame.image.load("car20.png")#.convert() +IMG = pygame.image.load("car20.png") # .convert() class Car(pygame.sprite.Sprite): @@ -22,28 +32,28 @@ class Car(pygame.sprite.Sprite): self.image = self.original_image self.rect = self.image.get_rect() - self.vision_length = VISION_LENGTH # line liength - self.vision_span = VISION_SPAN # degrees + self.vision_length = VISION_LENGTH # line liength + self.vision_span = VISION_SPAN # degrees self.draw_sensors = False - # lets add 3 sensors as a start + # lets add 3 sensors as a start # 1 straight ahead # 2 left 15° # 3 right 15 ° - # we will give each of them a max lenght to - # and we will eventually detect any line crossing the sensor and + # we will give each of them a max lenght to + # and we will eventually detect any line crossing the sensor and # retain the min value as a distance to collision input self.center_sensor = None self.left_sensor = None self.right_sensor = None self.sensors = [self.left_sensor, self.center_sensor, self.right_sensor] - self.probes = [self.vision_length] *3 - - if brain : + self.probes = [self.vision_length] * 3 + + if brain: self.brain = brain - else : + else: self.brain = Neural_Network() - + self.reset_car_pos() self.update_sensors() self.probe_brain() @@ -52,9 +62,9 @@ class Car(pygame.sprite.Sprite): def reset_car_pos(self): self.rect.center = ( - 75 - int(random.random()*20) - 10, - GY -50 - int(random.random()*20)-10 - ) + 75 - int(random.random() * 20) - 10, + GY - 50 - int(random.random() * 20) - 10, + ) self.speed = 1 self.heading = random.random() * 20 self.heading_change = random.random() * 30 @@ -62,53 +72,71 @@ class Car(pygame.sprite.Sprite): def update_sensors(self): center = self.rect.center vc = angle_to_vector(self.heading) - self.center_sensor = [center, (int(self.vision_length * vc[0] + center[0]), int(-self.vision_length * vc[1] + center[1]))] - - vl = angle_to_vector(self.heading+self.vision_span) - self.left_sensor = [center, (int(self.vision_length * vl[0] + center[0]), int(-self.vision_length * vl[1] + center[1]))] - - vr = angle_to_vector(self.heading-self.vision_span) - self.right_sensor = [center, (int(self.vision_length * vr[0] + center[0]), int(-self.vision_length * vr[1] + center[1]))] + self.center_sensor = [ + center, + ( + int(self.vision_length * vc[0] + center[0]), + int(-self.vision_length * vc[1] + center[1]), + ), + ] + vl = angle_to_vector(self.heading + self.vision_span) + self.left_sensor = [ + center, + ( + int(self.vision_length * vl[0] + center[0]), + int(-self.vision_length * vl[1] + center[1]), + ), + ] + + vr = angle_to_vector(self.heading - self.vision_span) + self.right_sensor = [ + center, + ( + int(self.vision_length * vr[0] + center[0]), + int(-self.vision_length * vr[1] + center[1]), + ), + ] def update_position(self): vec = angle_to_vector(self.heading) old_center = self.rect.center - self.rect.center = (self.speed * vec[0] / 2 + old_center[0], -self.speed * vec[1] / 2 + old_center[1]) + self.rect.center = ( + self.speed * vec[0] / 2 + old_center[0], + -self.speed * vec[1] / 2 + old_center[1], + ) self.update_sensors() self.distance_run += int(distance(old_center, self.rect.center)) self.brain.fitness = int(math.sqrt(self.distance_run)) - - def probe_lines_proximity(self, lines): # print(self.center_sensor, lines[0]) - self.probes = [self.vision_length*2] *3 - for idx,sensor in enumerate([self.left_sensor, self.center_sensor, self.right_sensor]) : - for line in lines : + self.probes = [self.vision_length * 2] * 3 + for idx, sensor in enumerate( + [self.left_sensor, self.center_sensor, self.right_sensor] + ): + for line in lines: ip = segments_intersection(sensor, line) # print(ip) - if ip : - if self.draw_sensors : - pygame.draw.circle(screen, (125,125,255), ip, 4, 2) - dist = int(distance(ip,self.rect.center)) + if ip: + if self.draw_sensors: + pygame.draw.circle(screen, (125, 125, 255), ip, 4, 2) + dist = int(distance(ip, self.rect.center)) self.probes[idx] = min(dist, self.probes[idx]) - if dist < 1.2 * self.speed or self.speed < 0.01 : + if dist < 1.2 * self.speed or self.speed < 0.01: self.run = False self.speed = 0 # print(f'Car {id(self)} crashed') return # else : - # self.probes[idx] = self.vision_length * 2 + # self.probes[idx] = self.vision_length * 2 # print(self.probes) - def probe_brain(self): res = self.brain.predict(np.array(self.probes)) self.heading_change = res[0] * 15 - self.throttle = res[1] * 10 - + self.throttle = res[1] * 10 def update(self): # rotate @@ -117,9 +145,9 @@ class Car(pygame.sprite.Sprite): self.rect = self.image.get_rect() self.rect.center = old_center self.update_position() - if self.speed < 0.01 or self.brain.fitness > CAR_MAX_FITNESS : + if self.speed < 0.01 or self.brain.fitness > CAR_MAX_FITNESS: self.run = False - print(f'Car {id(self)} crashed') + print(f"Car {id(self)} crashed") # print( # 'id', id(self), # 'Speed', self.speed, @@ -128,27 +156,30 @@ class Car(pygame.sprite.Sprite): # 'heading change', self.heading_change, # ) - if self.speed : - self.heading += self.heading_change * CAR_STEERING_FACTOR / self.speed + if self.speed: + self.heading += self.heading_change * CAR_STEERING_FACTOR / self.speed self.heading = self.heading % 360 - self.speed += self.throttle #THROTTLE_POWER + self.speed += self.throttle # THROTTLE_POWER # if self.throttle : - # self.speed += self.throttle #THROTTLE_POWER + # self.speed += self.throttle #THROTTLE_POWER # else : - # self.speed -= self.throttle #THROTTLE_POWER + # self.speed -= self.throttle #THROTTLE_POWER self.speed = max(0, self.speed) self.speed = min(self.speed, CAR_MAX_SPEED) super().update() - def show_features(self): if self.draw_sensors: - pygame.draw.line(screen, (255,0,0), self.center_sensor[0], self.center_sensor[1]) - pygame.draw.line(screen, (0,255,0), self.left_sensor[0], self.left_sensor[1]) - pygame.draw.line(screen, (0,0,255), self.right_sensor[0], self.right_sensor[1]) - pygame.draw.circle(screen, (125,255,125), self.rect.center, 4, 2) - - + pygame.draw.line( + screen, (255, 0, 0), self.center_sensor[0], self.center_sensor[1] + ) + pygame.draw.line( + screen, (0, 255, 0), self.left_sensor[0], self.left_sensor[1] + ) + pygame.draw.line( + screen, (0, 0, 255), self.right_sensor[0], self.right_sensor[1] + ) + pygame.draw.circle(screen, (125, 255, 125), self.rect.center, 4, 2) diff --git a/genetics.py b/genetics.py index 7fd2754..3cfdcea 100644 --- a/genetics.py +++ b/genetics.py @@ -3,67 +3,60 @@ import random from brain import Neural_Network from params import MUTATION_RATE, SELECTION_ALG, KWAY_TOURNAMENT_PLAYERS + def kway_selection(brains, exclude=None): tourn_pool = [] best_play = None - if exclude : + if exclude: brains = [x for x in brains if x != exclude] for x in range(KWAY_TOURNAMENT_PLAYERS): new_play = random.choice(brains) - while new_play in tourn_pool : + while new_play in tourn_pool: new_play = random.choice(brains) - if not best_play or best_play.fitness < new_play.fitness : + if not best_play or best_play.fitness < new_play.fitness: best_play = new_play return best_play + def genetic_selection(brains): parents_pool = [] - half_pop = int(len(brains)/2) + half_pop = int(len(brains) / 2) if SELECTION_ALG == "kway": - for x in range(half_pop) : + for x in range(half_pop): p1 = kway_selection(brains) p2 = kway_selection(brains, exclude=p1) - parents_pool.append([ - p1, - p2 - ]) + parents_pool.append([p1, p2]) - - - elif SELECTION_ALG == "roulette" : + elif SELECTION_ALG == "roulette": # does not seem very optimized... TBR # constitute a list where every brain is represented # proportionnally to its relative fitness wheel = [] - for b in brains : + for b in brains: wheel += [b] * b.fitness - tot_fitness = len(wheel) - # selection of pool/2 pair of parents to reproduce + # selection of pool/2 pair of parents to reproduce for _ in range(half_pop): - idx1 = round(random.random()*tot_fitness - 1) - idx2 = round(random.random()*tot_fitness - 1) - parents_pool.append([ - wheel[idx1], - wheel[idx2] - ]) + idx1 = round(random.random() * tot_fitness - 1) + idx2 = round(random.random() * tot_fitness - 1) + parents_pool.append([wheel[idx1], wheel[idx2]]) return parents_pool - + def cross_mutate_genes(p1_gene, p2_gene): child = [] p1_gene = list(p1_gene) p2_gene = list(p1_gene) - for idx,x in enumerate(p2_gene): - if random.random() > 0.5 : + for idx, x in enumerate(p2_gene): + if random.random() > 0.5: choice = p1_gene[idx] - else : + else: choice = p2_gene[idx] # Mutation - if random.random() < MUTATION_RATE : + if random.random() < MUTATION_RATE: choice[random.randint(0, len(choice) - 1)] = random.random() print("Mutation !") child.append(choice) @@ -73,7 +66,7 @@ def cross_mutate_genes(p1_gene, p2_gene): def genetic_reproduction(parents_pool): # every pair of parents will produce a mixed child new_pop = [] - for [p1,p2] in parents_pool: + for [p1, p2] in parents_pool: W1_kid = cross_mutate_genes(p1.W1, p2.W1) W2_kid = cross_mutate_genes(p1.W2, p2.W2) c_brain1 = Neural_Network(W1=W1_kid, W2=W2_kid) @@ -81,7 +74,3 @@ def genetic_reproduction(parents_pool): new_pop.append(c_brain1) new_pop.append(c_brain2) return new_pop - - - - diff --git a/main.py b/main.py index 4d0f403..218e69e 100755 --- a/main.py +++ b/main.py @@ -10,15 +10,13 @@ from maps import map1 from params import CELL_COLOR, screen - -#https://medium.com/intel-student-ambassadors/demystifying-genetic-algorithms-to-enhance-neural-networks-cde902384b6e +# https://medium.com/intel-student-ambassadors/demystifying-genetic-algorithms-to-enhance-neural-networks-cde902384b6e clock = pygame.time.Clock() map_lines = map1 - all_cars = pygame.sprite.Group() for x in range(100): @@ -28,47 +26,47 @@ for x in range(100): def run_round(all_cars): running_cars = True - while running_cars : + while running_cars: running_cars = False screen.fill(CELL_COLOR) all_cars.draw(screen) - for c in all_cars : + for c in all_cars: c.show_features() - if c.run : + if c.run: running_cars = True c.probe_lines_proximity(map_lines) c.probe_brain() c.update() - for line in map_lines : - pygame.draw.line(screen, (255,255,255), line[0], line[1]) + for line in map_lines: + pygame.draw.line(screen, (255, 255, 255), line[0], line[1]) pygame.display.flip() clock.tick(48) # for c in all_cars : # print(f"Car {id(c)} Fitness : {c.brain.fitness})") - - print('Collecting brains') + + print("Collecting brains") brains = [c.brain for c in all_cars] - print(f"Max fitness = {max([b.fitness for b in brains])}" ) - print(f"Avg fitness = {sum([b.fitness for b in brains])/len(brains)}" ) - print('selecting') + print(f"Max fitness = {max([b.fitness for b in brains])}") + print(f"Avg fitness = {sum([b.fitness for b in brains])/len(brains)}") + print("selecting") parents_pool = genetic_selection(brains) # import ipdb; ipdb.set_trace() print("breeding") new_brains = genetic_reproduction(parents_pool) - print(f'building {len(new_brains)} cars with new brains') + print(f"building {len(new_brains)} cars with new brains") all_cars.empty() - for b in new_brains : + for b in new_brains: all_cars.add(Car(brain=b)) print("Waiting before new run") - for x in range(1) : + for x in range(1): time.sleep(0.5) pygame.display.flip() -while True : +while True: run_round(all_cars) pygame.display.flip() clock.tick(24) diff --git a/maps.py b/maps.py index 0b03be3..b3875f6 100644 --- a/maps.py +++ b/maps.py @@ -1,40 +1,42 @@ from params import GX, GY -def generate_map_1() : + +def generate_map_1(): path = [ - (25, int(GY-25)), - (int(GX/2), int(GY-25)), - (int(GX/2 + 75), int(GY-150)), - (int(GX/2 + 150), int(GY-150)), - (int(GX -75), int(GY/2)), - (int(GX - 100), int(GY/2 - 75)), - (int(GX - 100), int(GY/2 - 150)), - (int(GX -50), int( GY/4 )), - (int(3*GX/4 - 50), int(50)), - (int(50), int(50)), - (int(100), int(GY/2)), - (25, int(GY-25)), + (25, int(GY - 25)), + (int(GX / 2), int(GY - 25)), + (int(GX / 2 + 75), int(GY - 150)), + (int(GX / 2 + 150), int(GY - 150)), + (int(GX - 75), int(GY / 2)), + (int(GX - 100), int(GY / 2 - 75)), + (int(GX - 100), int(GY / 2 - 150)), + (int(GX - 50), int(GY / 4)), + (int(3 * GX / 4 - 50), int(50)), + (int(50), int(50)), + (int(100), int(GY / 2)), + (25, int(GY - 25)), ] path2 = [ - (100, int(GY-85)), - (int(GX/2 - 50 ), int(GY-85)), - (int(GX/2 + 50), int(GY-210)), - (int(GX/2 + 110), int(GY-210)), - (int(GX - 170), int(GY/2 + 30)), - (int(GX - 200 ), int(GY/2 - 20)), - (int(GX - 200), int(GY/2 - 200)), - (int(GX -170), int( GY/4 -20)), - (int(3*GX/4 - 100), int(120)), - (int(120), int(120)), - (int(175), int(GY/2)), - (100, int(GY-85)), + (100, int(GY - 85)), + (int(GX / 2 - 50), int(GY - 85)), + (int(GX / 2 + 50), int(GY - 210)), + (int(GX / 2 + 110), int(GY - 210)), + (int(GX - 170), int(GY / 2 + 30)), + (int(GX - 200), int(GY / 2 - 20)), + (int(GX - 200), int(GY / 2 - 200)), + (int(GX - 170), int(GY / 4 - 20)), + (int(3 * GX / 4 - 100), int(120)), + (int(120), int(120)), + (int(175), int(GY / 2)), + (100, int(GY - 85)), ] - lines = [[path[i], path[i+1]] for i in range(len(path)-1)] - lines2 = [[path2[i], path2[i+1]] for i in range(len(path2)-1)] + lines = [[path[i], path[i + 1]] for i in range(len(path) - 1)] + lines2 = [[path2[i], path2[i + 1]] for i in range(len(path2) - 1)] lines = lines + lines2 return lines -map1 = generate_map_1() \ No newline at end of file + +map1 = generate_map_1() diff --git a/params.py b/params.py index d8ff346..fff0245 100644 --- a/params.py +++ b/params.py @@ -1,21 +1,21 @@ import pygame from pygame.locals import HWSURFACE, DOUBLEBUF -FLAGS = HWSURFACE | DOUBLEBUF #| FULLSCREEN +FLAGS = HWSURFACE | DOUBLEBUF # | FULLSCREEN -GX = 1000 -GY = 1000 -CELL_COLOR = (80,80,80) -CAR_SIZE = 20 -CAR_MAX_SPEED = 100 -CAR_MAX_FITNESS = 100 -CAR_STEERING_FACTOR = 10 -VISION_LENGTH = 60 -VISION_SPAN = 35 # degrees -THROTTLE_POWER = 3 +GX = 1000 +GY = 1000 +CELL_COLOR = (80, 80, 80) +CAR_SIZE = 20 +CAR_MAX_SPEED = 100 +CAR_MAX_FITNESS = 100 +CAR_STEERING_FACTOR = 10 +VISION_LENGTH = 60 +VISION_SPAN = 35 # degrees +THROTTLE_POWER = 3 -MUTATION_RATE = 0.01 -SELECTION_ALG = "kway" # roulette +MUTATION_RATE = 0.01 +SELECTION_ALG = "kway" # roulette KWAY_TOURNAMENT_PLAYERS = 3 pygame.init() diff --git a/trigo.py b/trigo.py index 3821fcb..5ca48de 100644 --- a/trigo.py +++ b/trigo.py @@ -1,45 +1,47 @@ #!/usr/bin/env python import math + def angle_to_vector(angle): - angle=angle*math.pi/180 + angle = angle * math.pi / 180 return [math.cos(angle), math.sin(angle)] def get_line_feats(point1, point2): - x1,y1 = point1 - x2,y2 = point2 + x1, y1 = point1 + x2, y2 = point2 # if x1 == x2 : # x1=x1+1 - a = (y1-y2)/(x1-x2) + a = (y1 - y2) / (x1 - x2) b = y2 - a * x2 - return a,b + return a, b def segments_intersection(line1, line2): - p1,p2 = line1 - p3,p4 = line2 - if p1[0] == p2[0] : + p1, p2 = line1 + p3, p4 = line2 + if p1[0] == p2[0]: p1 = (p1[0] + 1, p1[1]) - if p3[0] == p4[0] : + if p3[0] == p4[0]: p3 = (p3[0] + 1, p3[1]) + a1, b1 = get_line_feats(p1, p2) + a2, b2 = get_line_feats(p3, p4) - a1,b1 = get_line_feats(p1,p2) - a2,b2 = get_line_feats(p3,p4) + if a1 == a2: + return None # parrallel lines - if a1==a2 : - return None # parrallel lines - - x = (b2-b1)/(a1-a2) + x = (b2 - b1) / (a1 - a2) - if min(p1[0], p2[0]) <= x <= max (p1[0], p2[0]) and min(p3[0], p4[0]) <= x <= max (p3[0], p4[0]) : + if min(p1[0], p2[0]) <= x <= max(p1[0], p2[0]) and min(p3[0], p4[0]) <= x <= max( + p3[0], p4[0] + ): y = a1 * x + b1 - return x,y - else : - return None # intersect is outside segments + return x, y + else: + return None # intersect is outside segments def distance(point1, point2): - return math.hypot(point1[0] - point2[0], point1[1] - point2[1]) \ No newline at end of file + return math.hypot(point1[0] - point2[0], point1[1] - point2[1])