From e34f601c61eb0a6892d53aadbf8df7d893752c9c Mon Sep 17 00:00:00 2001 From: rebeccagettys Date: Sun, 27 Mar 2016 13:39:08 -0400 Subject: [PATCH 1/5] WHY THE ODD ERROR?? --- evolve_text.py | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/evolve_text.py b/evolve_text.py index e0202d2..5829660 100644 --- a/evolve_text.py +++ b/evolve_text.py @@ -9,6 +9,7 @@ Full instructions are at: https://sites.google.com/site/sd15spring/home/project-toolbox/evolutionary-algorithms +All non-starter code by Rebecca Gettys except where otherwise noted """ import random @@ -91,9 +92,24 @@ def get_text(self): #----------------------------------------------------------------------------- # Genetic operators #----------------------------------------------------------------------------- +memo = {} +def levenshtein_distance(a,b): + """ from: https://programmingpraxis.com/2014/09/12/levenshtein-distance/ + I had memoized fibonacci instead - my memoized levenshtein doesn't work, not sure why + """ + if a == b: + return 0 + if a == "": + return len(b) + if b == "": + return len(a) + if (a, b) not in memo: + l1 = levenshtein_distance(a[1:], b) + 1 + l2 = levenshtein_distance(a, b[1:]) + 1 + l3 = levenshtein_distance(a[1:], b[1:]) + (a[0] != b[0]) + memo[(a,b)] = min(l1, l2, l3) + return memo[(a,b)] -# TODO: Implement levenshtein_distance function (see Day 9 in-class exercises) -# HINT: Now would be a great time to implement memoization if you haven't def evaluate_text(message, goal_text, verbose=VERBOSE): """ @@ -119,18 +135,31 @@ def mutate_text(message, prob_ins=0.05, prob_del=0.05, prob_sub=0.05): Substitution: Replace one character of the Message with a random (legal) character """ - + loc=random.randint(0, len(message)) + new_char = random.choice(VALID_CHARS) if random.random() < prob_ins: - # TODO: Implement insertion-type mutation - pass - - # TODO: Also implement deletion and substitution mutations + # done insertion-type mutation + message.insert(loc,new_char) + elif random.random() < prob_del: + message = message[:loc] + message[loc+1:] + elif random.random() < prob_sub: + message = message[:loc] + new_char + message[loc+1:] # HINT: Message objects inherit from list, so they also inherit # useful list methods # HINT: You probably want to use the VALID_CHARS global variable return (message, ) # Length 1 tuple, required by DEAP +def my_crossover_func(parent1, parent2): + "replaces the original 2 point crossover function" + point1 = random.randint (0,len(parent1)) + point2 = random.randint(0,len(parent2)) + new_p1 = parent1[:point1] + parent2[point1:point2] + parent1[point2:] + new_p2 = parent2[:point1] + parent1[point1:point2] + parent2[point2:] + return (new_p1, new_p2) + + + #----------------------------------------------------------------------------- # DEAP Toolbox and Algorithm setup @@ -150,6 +179,7 @@ def get_toolbox(text): # Genetic operators toolbox.register("evaluate", evaluate_text, goal_text=text) toolbox.register("mate", tools.cxTwoPoint) + #toolbox.register("mate", my_crossover_func) toolbox.register("mutate", mutate_text) toolbox.register("select", tools.selTournament, tournsize=3) @@ -216,3 +246,4 @@ def evolve_string(text): # Run evolutionary algorithm pop, log = evolve_string(goal) + From 11280694f8d81c3e32ccdf81e432c393ad68c151 Mon Sep 17 00:00:00 2001 From: rebeccagettys Date: Sun, 27 Mar 2016 13:44:50 -0400 Subject: [PATCH 2/5] WHY THE ODD ERROR?? --- evolve_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evolve_text.py b/evolve_text.py index 5829660..c50242c 100644 --- a/evolve_text.py +++ b/evolve_text.py @@ -156,7 +156,7 @@ def my_crossover_func(parent1, parent2): point2 = random.randint(0,len(parent2)) new_p1 = parent1[:point1] + parent2[point1:point2] + parent1[point2:] new_p2 = parent2[:point1] + parent1[point1:point2] + parent2[point2:] - return (new_p1, new_p2) + return (Message(new_p1),Message( new_p2)) From 57c5013c852bff3aeed109ae0d3b3432df6c622c Mon Sep 17 00:00:00 2001 From: rebeccagettys Date: Sun, 27 Mar 2016 16:36:41 -0400 Subject: [PATCH 3/5] all fixed, just need to write up --- evolve_text.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/evolve_text.py b/evolve_text.py index c50242c..2b6b622 100644 --- a/evolve_text.py +++ b/evolve_text.py @@ -86,7 +86,9 @@ def __repr__(self): def get_text(self): """Return Message as string (rather than actual list of characters)""" - return "".join(self) + # return "".join(self) + return ''.join([str(x) for x in self]) # list comprehensionhttp://stackoverflow.com/questions/497765/python-string-joinlist-on-object-array-rather-than-string-array + #----------------------------------------------------------------------------- @@ -137,26 +139,41 @@ def mutate_text(message, prob_ins=0.05, prob_del=0.05, prob_sub=0.05): """ loc=random.randint(0, len(message)) new_char = random.choice(VALID_CHARS) + new_message = [] + if random.random() < prob_ins: # done insertion-type mutation message.insert(loc,new_char) elif random.random() < prob_del: - message = message[:loc] + message[loc+1:] + #message = message[:loc] + message[loc+1:] + new_message.extend(message[:loc]) + new_message.extend(message[loc+1:]) + message = new_message elif random.random() < prob_sub: - message = message[:loc] + new_char + message[loc+1:] + #message = message[:loc] + new_char + message[loc+1:] + new_message.extend(message[:loc]) + new_message.extend(new_char) + new_message.extend(message[loc+1:]) + message = new_message + # HINT: Message objects inherit from list, so they also inherit # useful list methods # HINT: You probably want to use the VALID_CHARS global variable - return (message, ) # Length 1 tuple, required by DEAP + return (Message(message), ) # Length 1 tuple, required by DEAP def my_crossover_func(parent1, parent2): "replaces the original 2 point crossover function" point1 = random.randint (0,len(parent1)) - point2 = random.randint(0,len(parent2)) - new_p1 = parent1[:point1] + parent2[point1:point2] + parent1[point2:] - new_p2 = parent2[:point1] + parent1[point1:point2] + parent2[point2:] - return (Message(new_p1),Message( new_p2)) + point2 = random.randint(0,len(parent1)) + if point1>point2: #if the sizes don't work do it over, crossing over with p1 after p2 makes for gross non-genetic + #behaviors, although technically correct + return my_crossover_func(parent1,parent2) + # see Jane run! See Becca recursively code! + else: + new_p1 = parent1[:point1] + parent2[point1:point2] + parent1[point2:] + new_p2 = parent2[:point1] + parent1[point1:point2] + parent2[point2:] + return (Message(new_p1),Message( new_p2)) @@ -178,8 +195,8 @@ def get_toolbox(text): # Genetic operators toolbox.register("evaluate", evaluate_text, goal_text=text) - toolbox.register("mate", tools.cxTwoPoint) - #toolbox.register("mate", my_crossover_func) + #toolbox.register("mate", tools.cxTwoPoint) + toolbox.register("mate", my_crossover_func) toolbox.register("mutate", mutate_text) toolbox.register("select", tools.selTournament, tournsize=3) @@ -213,9 +230,9 @@ def evolve_string(text): # (See: http://deap.gel.ulaval.ca/doc/dev/api/algo.html for details) pop, log = algorithms.eaSimple(pop, toolbox, - cxpb=0.5, # Prob. of crossover (mating) + cxpb=0.2, # Prob. of crossover (mating) mutpb=0.2, # Probability of mutation - ngen=500, # Num. of generations to run + ngen=600, # Num. of generations to run stats=stats) return pop, log From 008f4f632f80ff3edacecb138cc79cc00925e6d7 Mon Sep 17 00:00:00 2001 From: rebeccagettys Date: Sun, 27 Mar 2016 22:58:56 -0400 Subject: [PATCH 4/5] done! --- evolve_text.py | 9 ++++----- results.txt | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 results.txt diff --git a/evolve_text.py b/evolve_text.py index 2b6b622..036f648 100644 --- a/evolve_text.py +++ b/evolve_text.py @@ -195,8 +195,8 @@ def get_toolbox(text): # Genetic operators toolbox.register("evaluate", evaluate_text, goal_text=text) - #toolbox.register("mate", tools.cxTwoPoint) - toolbox.register("mate", my_crossover_func) + toolbox.register("mate", tools.cxTwoPoint) + #toolbox.register("mate", my_crossover_func) toolbox.register("mutate", mutate_text) toolbox.register("select", tools.selTournament, tournsize=3) @@ -230,9 +230,9 @@ def evolve_string(text): # (See: http://deap.gel.ulaval.ca/doc/dev/api/algo.html for details) pop, log = algorithms.eaSimple(pop, toolbox, - cxpb=0.2, # Prob. of crossover (mating) + cxpb=0.5, # Prob. of crossover (mating) mutpb=0.2, # Probability of mutation - ngen=600, # Num. of generations to run + ngen=1000, # Num. of generations to run stats=stats) return pop, log @@ -263,4 +263,3 @@ def evolve_string(text): # Run evolutionary algorithm pop, log = evolve_string(goal) - diff --git a/results.txt b/results.txt new file mode 100644 index 0000000..5ce6576 --- /dev/null +++ b/results.txt @@ -0,0 +1,15 @@ +I compared my mating method with the one built into DAEP. I was expecting it to take more generations to arrive at the +correct string with distance = 0. After about 440ish generations using my crossover method, I arrived at the correct +string (THIS IS MY CRAZY STRING). In contrast, when I re-enabled the built in DAEP mating function, at 440 generations +the distance was consistently around 4. After 700 generations, the distance was around 2; I next tried 1000 generations +and discovered that finally, it had reached a distance of one. At that point, I stopped testing. In short, to my great +surprise, my double point crossover metehod resulted in a much faster result! I don't really have a good hypothesis as +to why this is, I looked through a great deal of the output without finding a consistent pattern to explain it. Anyways, +a neat discovery! I didn't include the data that this spit out because it isn't too interesting to read, it is +*extremely* long and you can produce it yourself. + +I wrote everything for this project myself (obviously, not the starter code) except for the memoized levenshtein + distance, which I pulled from the net - there was something wrong with my memoized levenshtein distance originally, I + did a memoized fibonacci instead, and I didn't think I would get a lot of learning out of re-doing it/debugging it + compared to working on other toolboxes. The memoized levenshtein distance I used can be found here. Thanks! + https://programmingpraxis.com/2014/09/12/levenshtein-distance/ From 236d1a83a9a85b65e4a104aa563dbd5ad01b8956 Mon Sep 17 00:00:00 2001 From: rebeccagettys Date: Sun, 27 Mar 2016 22:59:12 -0400 Subject: [PATCH 5/5] done! --- evolve_text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evolve_text.py b/evolve_text.py index 036f648..eceb40a 100644 --- a/evolve_text.py +++ b/evolve_text.py @@ -195,8 +195,8 @@ def get_toolbox(text): # Genetic operators toolbox.register("evaluate", evaluate_text, goal_text=text) - toolbox.register("mate", tools.cxTwoPoint) - #toolbox.register("mate", my_crossover_func) + #toolbox.register("mate", tools.cxTwoPoint) + toolbox.register("mate", my_crossover_func) toolbox.register("mutate", mutate_text) toolbox.register("select", tools.selTournament, tournsize=3)