From f0580a988cd855c22a24665fba42ae63ef8400fd Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 28 Oct 2019 11:52:03 +0100 Subject: [PATCH] blacked all --- Confirmation_PP_FR8039550.pdf | Bin 0 -> 30053 bytes brain.py | 49 ++++++++----- car.py | 133 +++++++++++++++++++++------------- genetics.py | 51 +++++-------- main.py | 32 ++++---- maps.py | 58 ++++++++------- params.py | 26 +++---- trigo.py | 42 ++++++----- 8 files changed, 213 insertions(+), 178 deletions(-) create mode 100644 Confirmation_PP_FR8039550.pdf diff --git a/Confirmation_PP_FR8039550.pdf b/Confirmation_PP_FR8039550.pdf new file mode 100644 index 0000000000000000000000000000000000000000..57d400e14850935602284c586cfe0420227281c0 GIT binary patch literal 30053 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 literal 0 HcmV?d00001 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])