Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miniproject 5 -- Alisha Pegan #18

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions evolve_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from deap import algorithms
from deap import base
from deap import tools
from levenshtein import levenshtein


# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -92,9 +93,17 @@ def get_text(self):
# Genetic operators
# -----------------------------------------------------------------------------

# TODO: Implement levenshtein_distance function (see Day 9 in-class exercises)
# mplement levenshtein_distance function (see Day 9 in-class exercises)
# HINT: Now would be a great time to implement memoization if you haven't

def levenshtein_distance(message, goal_text):
"""
Given a Message and a goal_text string, return the levenshtein distance
between the Message and the goal_text
"""
return levenshtein(message, goal_text)


def evaluate_text(message, goal_text, verbose=VERBOSE):
"""
Given a Message and a goal_text string, return the Levenshtein distance
Expand All @@ -107,6 +116,20 @@ def evaluate_text(message, goal_text, verbose=VERBOSE):
return (distance, ) # Length 1 tuple, required by DEAP


def twoPointCX(message1, message2):
'''
implements a two-point crossover of a string s1 and s2
'''
point1 = random.randint(0, min(len(message1), len(message2)))
point2 = random.randint(0, min(len(message1), len(message2)))

if point1 > point2:
message1[point2:point1], message2[point2:point1] = message2[point2:point1], message1[point2:point1]
else:
message1[point1:point2], message2[point1:point2] = message2[point1:point2], message1[point1:point2]

return message1, message2

def mutate_text(message, prob_ins=0.05, prob_del=0.05, prob_sub=0.05):
"""
Given a Message and independent probabilities for each mutation type,
Expand All @@ -121,13 +144,12 @@ def mutate_text(message, prob_ins=0.05, prob_del=0.05, prob_sub=0.05):
"""

if random.random() < prob_ins:
# TODO: Implement insertion-type mutation
pass
loc = random.randint(0, len(message.get_text())-1)
message.insert(loc, random.choice(VALID_CHARS))

# TODO: Also implement deletion and substitution mutations
# HINT: Message objects inherit from list, so they also inherit
# useful list methods
# HINT: You probably want to use the VALID_CHARS global variable
if random.random() < prob_del:
loc = random.randint(0, len(message.get_text())-1)
message[loc] = random.choice(VALID_CHARS)

return (message, ) # Length 1 tuple, required by DEAP

Expand All @@ -149,11 +171,11 @@ def get_toolbox(text):

# Genetic operators
toolbox.register("evaluate", evaluate_text, goal_text=text)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mate", twoPointCX)
toolbox.register("mutate", mutate_text)
toolbox.register("select", tools.selTournament, tournsize=3)

# NOTE: You can also pass function arguments as you define aliases, e.g.
# You can also pass function arguments as you define aliases, e.g.
# toolbox.register("individual", Message, max_length=200)
# toolbox.register("mutate", mutate_text, prob_sub=0.18)

Expand Down Expand Up @@ -183,9 +205,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.9, # Prob. of crossover (mating)
mutpb=0.2, # Probability of mutation
ngen=500, # Num. of generations to run
ngen=2000, # Num. of generations to run
stats=stats)

return pop, log
Expand Down
28 changes: 28 additions & 0 deletions levenshtein.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
cache = {}


def levenshtein(s1, s2):
"""
Returns levenshtein distance between message and goal
"""

if (s1, s2) in cache:
return cache[s1, s2]
if s1 == '':
return(len(s2))
if s2 == '':
return(len(s1))

if s1[-1] == s2[-1]:
cost = 0
else:
cost = 1
res = min(levenshtein(s1[:-1], s2)+1,
levenshtein(s1, s2[:-1])+1,
levenshtein(s1[:-1], s2[:-1])+cost)
cache[s1, s2] = res
return res


if __name__ == '__main__':
print(levenshtein('cola', 'coca'))
4 changes: 4 additions & 0 deletions results.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
I think the concept is super interesting, and am really curious how
this could be paired the Biomimicry. My results showed that it took
about several thousand generations to reach the final text.
Will describe more in-depth, need to sleep now.