From a7324b844519d4e6e5f1b86035dda439bb427676 Mon Sep 17 00:00:00 2001 From: jake-windle Date: Tue, 24 Sep 2019 16:15:53 -0400 Subject: [PATCH] New blogpost, ES meal planning --- .../evolutionary-strategy-meal-planning.md | 133 ++++++++++++++++++ static/img/post/run1.png | Bin 0 -> 14645 bytes static/img/post/run2.png | Bin 0 -> 12807 bytes 3 files changed, 133 insertions(+) create mode 100644 content/post/evolutionary-strategy-meal-planning.md create mode 100644 static/img/post/run1.png create mode 100644 static/img/post/run2.png diff --git a/content/post/evolutionary-strategy-meal-planning.md b/content/post/evolutionary-strategy-meal-planning.md new file mode 100644 index 0000000..ca04de3 --- /dev/null +++ b/content/post/evolutionary-strategy-meal-planning.md @@ -0,0 +1,133 @@ +--- +title: "Evolutionary Strategy Meal Planning" +date: 2019-09-24T15:23:59-04:00 +draft: false +--- + +While sitting in my house just day dreaming, I was looking for ideas for my next project. Thoughts flew across my mind relating things that +I was currently doing, challenges I was currently facing, and technology. Thinking back to my days at GMU, I remembered how much I really liked my +Artificial Intelligence class, specifically evolutionary algorithms and genetic programming. I also thought of how hard it was to track macronutrients +(something I'm currently doing) whenever I went out to eat at my favorite restaurants. Thinking about different ways to automatically meal plan, I had an idea. +What if, given a database of nutrition information from a specific restaurant, I could "evolve" the best meal for myself based on evolutionary strategy +algorithms I had learned about in class? With this question in mind, I set out to see whether or not my question was feasible. + +## Candidate Solutions, and Evaluating Fitness + +What would my candidates even look like that I would feed into the algorithm. Can I model meals in a way that I can "breed" or "mutate" them in order to +achieve better fitness scores over time? What would my fitness evaluator function look like? + +I started on the question of candidates. I downloaded the nutritional spreadsheet from my favorite local sports bar, Beef O'Brady's, and got to work. My +favorite programming language, Julia, has a complex type system. It allows for very complex data to be modeled and represented in a native type. +In the end though, my choice of data structure was influenced by the structure of my data. The nutritional spreadsheet that I downloaded from the sports bar had +each menu item represented in a row. To read the data, I used the Julia library ExcelReaders.jl, which was able to stuff my data into a Julia DataFrame. I +decided that candidate solutions could just be n-row DataFrame objects, a randomly selected subset of size n taken from the entire dataset. + +```julia +""" + Helper function to generate a random candidate from the dataset. + + n is the size of the candidate. +""" +function randomCandidate(n::Integer) + # Select n random rows from the dataset. + rows = [randRow() for i = 1:n] + df[rows, :] +end + + +""" + generateInitialPopulation(lambda::Integer, candidateSize::Integer) + +From our dataset, generate an array of initial candidates to begin the search. +""" +function generateInitialPopulation(lambda::Integer, candidateSize::Integer) + [randomCandidate(candidateSize) for i = 1:lambda] +end + +``` + +With the design of the candidate solutions in hand, I thought that the easiest method for evaluating the fitness of these solutions would be a simple subtraction. +I built my fitness evaluation function to take the absolute value of the difference of my target calories with the total calories of a candidate meal. + +```julia +""" + fitness(candidate::DataFrames.DataFrame) + +Calculate the fitness of the candidate, which is the +absolute value of the difference of TARGETCALORIES and the sum +of all calories in the meal. +""" +function fitness(candidate::DataFrames.DataFrame) + abs(TARGETCALORIES - sum(+, candidate[:Calories])) +end +``` + +With the candidate solutions designed, and the fitness evaluation function now complete, I had to decide on an actual algorithm to grow generations +of meals with better and better fitness scores. To find the right algorithm I turned to an old textbook of mine, The Essentials of Metaheuristics. Within +this book, Algorithm 18, the (mu, lambda) strategy, seemed perfect [1]. + +## The (mu/lambda) Evolution Strategy. + +Boiled down, this algorithm consists of a few, discrete steps. The first step is to generate an initial population. I do this using the functions I pasted +above to build a population of size lambda. The next step, is to begin evolving new solutions to our existing problem. The evolution process can be broken +down into a few steps as well. The first step is to evaluate the fitness of all members of the population, saving the most fit candidate we saw in the +evaluation process. The next step is to save the `mu` candidates who had the highest fitness scores (this is called truncation selection). The final step is +to iterate over each of our `mu` parents we selected, and to `lambda/mu` times generate new children by mutating copies of the parent. We only exit the +evolution process if the best candidate from the generation is the ideal solution, or we have ran out of time. + +## My Mutation Algorithm + +The mutation algorithm that I designed is very simple, it merely walks each of the items within a meal (rows in a small dataframe), and flips a coin on each one. +If the result is heads, it will replace that food item with a randomly selected row from the dataset. If the result is tails, it will move on to the next food +item and flip another coin. It repeats this process until it has iterated through all rows in a candidate solution. + +```julia +""" +Our mutator function + + steps through the parent, and randomly selects the + allelles to delete. Will replace the alleles with new + alleles (meals). +""" +function mutate(parent) + + # Copy the parent so we can do some work. + child = deepcopy(parent) + toDelete = [] + + for i in 1:size(parent, 1) + if rand(Float64) > 0.5 # NOTE: make this tunable + push!(toDelete, i) + end + # If we get tails, delete the row and push a new one to it. + end + # Delete all rows we don't want at once. + DataFrames.deleterows!(child, toDelete) + + # Add new random rows from the ones we deleted + for i in 1:length(toDelete) + push!(child, df[randRow(), :]) + end + + child +end +``` + +## Results + +Using Algorithm 18 [1], and all the above functions, I implemented the (mu, lambda) evolutionary meal planning strategy. My candidate solutions were subset DataFrames +drawn from the DataFrame of the entire dataset. My mutator function was based on a simple "coin flip" probability, replacing random meal items with other random +meal items in hopes of mutating into a higher fitness meal. The results from a few runs are shown below. + +![Image](/img/post/run1.png) + +![Image](/img/post/run2.png) + +I definitely think that even from my small experiment, the results do show that evolutionary strategies are adequate search algorithms in the search space of meal +planning. If you can model meals in such a way that they are made up of food items which can be modified or replaced (like alleles), then applying the algorithm +is straightforward after that. + +## References + +[1]: [The Essentials of Metaheuristics, Sean Luke, Pg. 33](https://cs.gmu.edu/~sean/book/metaheuristics/Essentials.pdf) + diff --git a/static/img/post/run1.png b/static/img/post/run1.png new file mode 100644 index 0000000000000000000000000000000000000000..416535241be92a10afabfe40ab302b2ea2b53e8d GIT binary patch literal 14645 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 U^b?(Dupb9exTz{zfHC#|Un3()xBvhE literal 0 HcmV?d00001 diff --git a/static/img/post/run2.png b/static/img/post/run2.png new file mode 100644 index 0000000000000000000000000000000000000000..e6144964a37ed265fd9080a3ae7b77291d75f891 GIT binary patch literal 12807 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 literal 0 HcmV?d00001