From 1049e5497642ef01e31dfab931fc946c5ddfa13b Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Sat, 2 Nov 2019 10:58:23 +0100 Subject: [PATCH] ...and use dill in the evolutionary algo --- .../EvolutionaryAlgorithm.ipynb | 2035 +---------------- 1 file changed, 1 insertion(+), 2034 deletions(-) diff --git a/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb b/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb index 799c046..525b4a6 100644 --- a/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb +++ b/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb @@ -1,2034 +1 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Evolutionary Algorithm" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - " \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - " \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import sys\n", - "sys.path.append(\"../\")\n", - "sys.path.append(\"../RecipeAnalysis/\")\n", - "\n", - "import settings\n", - "\n", - "import pycrfsuite\n", - "\n", - "import json\n", - "\n", - "import db.db_settings as db_settings\n", - "from db.database_connection import DatabaseConnection\n", - "\n", - "from Tagging.conllu_generator import ConlluGenerator\n", - "from Tagging.crf_data_generator import *\n", - "\n", - "from RecipeAnalysis.Recipe import Ingredient\n", - "\n", - "from difflib import SequenceMatcher\n", - "\n", - "import numpy as np\n", - "\n", - "import plotly.graph_objs as go\n", - "from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot\n", - "from plotly.subplots import make_subplots\n", - "init_notebook_mode(connected=True)\n", - "\n", - "from graphviz import Digraph\n", - "\n", - "import itertools\n", - "\n", - "import random\n", - "\n", - "import plotly.io as pio\n", - "pio.renderers.default = \"jupyterlab\"\n", - "\n", - "from IPython.display import Markdown, HTML, display\n", - "\n", - "from copy import deepcopy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## load adjacency matrices" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import pickle\n", - "m_act = pickle.load(open(\"m_act.pickle\", \"rb\"))\n", - "m_mix = pickle.load(open(\"m_mix.pickle\", \"rb\"))\n", - "m_base_act = pickle.load(open(\"m_base_act.pickle\", \"rb\"))\n", - "m_base_mix = pickle.load(open(\"m_base_mix.pickle\", \"rb\"))\n", - "\n", - "c_act = m_act.get_csr()\n", - "c_mix = m_mix.get_csr()\n", - "c_base_act = m_base_act.get_csr()\n", - "c_base_mix = m_base_mix.get_csr()\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "actions = m_act.get_labels()[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "base_ingredients = m_base_mix.get_labels()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "sym_label_buffer = {}\n", - "fw_label_buffer = {}\n", - "bw_label_buffer = {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### helper functions for adjacency matrices" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "def get_sym_adjacent(key, m, c):\n", - " index = m._label_index[key]\n", - " i1 = c[index,:].nonzero()[1]\n", - " i2 = c[:,index].nonzero()[0]\n", - " \n", - " i = np.concatenate((i1,i2))\n", - " \n", - " if m in sym_label_buffer:\n", - " names = sym_label_buffer[m][i]\n", - " else:\n", - " names = np.array(m.get_labels())\n", - " sym_label_buffer[m] = names\n", - " names = names[i]\n", - " \n", - " counts = np.concatenate((c[index, i1].toarray().flatten(), c[i2, index].toarray().flatten()))\n", - " \n", - " s = np.argsort(-counts)\n", - " \n", - " return names[s], counts[s]" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "def get_forward_adjacent(key, m, c):\n", - " index = m._x_label_index[key]\n", - " i = c[index,:].nonzero()[1]\n", - " \n", - " if m in fw_label_buffer:\n", - " names = fw_label_buffer[m][i]\n", - " else:\n", - " names = np.array(m._y_labels)\n", - " fw_label_buffer[m] = names\n", - " names = names[i]\n", - " \n", - " \n", - " counts = c[index, i].toarray().flatten()\n", - " \n", - " s = np.argsort(-counts)\n", - " \n", - " return names[s], counts[s]" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "def get_backward_adjacent(key, m, c):\n", - " index = m._y_label_index[key]\n", - " i = c[:,index].nonzero()[0]\n", - " \n", - " if m in bw_label_buffer:\n", - " names = bw_label_buffer[m][i]\n", - " else:\n", - " names = np.array(m._x_labels)\n", - " bw_label_buffer[m] = names\n", - " names = names[i]\n", - " \n", - " \n", - " counts = c[i, index].toarray().flatten()\n", - " \n", - " s = np.argsort(-counts)\n", - " \n", - " return names[s], counts[s]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array(['heat', 'simmer', 'cook', 'bake', 'boil', 'place', 'cut', 'slice',\n", - " 'chop', 'dice', 'pour', 'drain', 'cool', 'refrigerate', 'thicken',\n", - " 'warm', 'chill', 'brown', 'blend', 'spread', 'grill', 'fry',\n", - " 'saute', 'mash', 'melt', 'whisk', 'peel', 'freeze', 'wash',\n", - " 'grate', 'squeeze', 'broil', 'marinate', 'skim', 'soak', 'mince',\n", - " 'break', 'open', 'sour', 'thaw', 'beat', 'wipe', 'carve', 'curdle'],\n", - " dtype=' 1 and p._single_child)\n", - " \n", - " for c in childs:\n", - " self.remove_child(c)\n", - " \n", - " if p is not None:\n", - " p.remove_child(self)\n", - " \n", - " if self._single_child and self._child is not None and p._name == self._child._name:\n", - " # two adjacent nodes with same name would remain after deletion.\n", - " # merge them! (by adding the child's childs to our parent instead of our childs)\n", - " childs = self._child.childs()\n", - " self._child.remove()\n", - " \n", - " \n", - " for c in childs:\n", - " p.add_child(c)\n", - " \n", - " def insert_before(self, n):\n", - " p = self._parent\n", - " if p is not None:\n", - " p.remove_child(self)\n", - " p.add_child(n)\n", - " n.add_child(self)\n", - " \n", - " def mutate(self):\n", - " n_node = self.n_node_mutate_options()\n", - " n_edge = self.n_edge_mutate_options()\n", - " \n", - " choice = random.choice(range(n_node + n_edge))\n", - " if choice < n_node:\n", - " self.mutate_node()\n", - " else:\n", - " self.mutate_edges()\n", - " \n", - " def mutate_edges(self):\n", - " ings = self.traverse_ingredients()\n", - " ing = random.choice(ings)\n", - " \n", - " a, w = get_backward_adjacent(ing._base_ingredient, m_base_act, c_base_act)\n", - " \n", - " action = random.choices(a, w)[0]\n", - " self.insert_before(ActionNode(action))\n", - " \n", - " def mutate_node(self):\n", - " raise NotImplementedError\n", - " \n", - " def n_node_mutate_options(self):\n", - " \n", - " return 0 if self._constant else 1\n", - " \n", - " def n_edge_mutate_options(self):\n", - " n = 1 if self._parent is not None else 0\n", - " return n\n", - " \n", - " def n_mutate_options(self):\n", - " return self.n_edge_mutate_options() + self.n_node_mutate_options()\n", - " \n", - " def dot_node(self, dot):\n", - " raise NotImplementedError()\n", - " \n", - " def dot(self, d=None):\n", - " if d is None:\n", - " d = Digraph()\n", - " self.dot_node(d)\n", - " \n", - " else:\n", - " self.dot_node(d)\n", - " if self._parent is not None:\n", - " d.edge(self._parent._id, self._id)\n", - " \n", - " \n", - " for c in self.childs():\n", - " c.dot(d)\n", - " \n", - " return d\n", - " \n", - " def serialize(self):\n", - " r = {}\n", - " r['type'] = str(self.__class__.__name__)\n", - " r['id'] = self._id\n", - " r['parent'] = self._parent._id if self._parent is not None else None\n", - " r['name'] = self._name\n", - " r['childs'] = [c._id for c in self.childs()]\n", - " r['constant'] = self._constant\n", - " r['single_child'] = self._single_child\n", - " \n", - " return r\n", - " \n", - " def node_score(self):\n", - " raise NotImplementedError()\n", - " \n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Mix Node" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "class MixNode(RecipeTreeNode):\n", - " def __init__(self, constant=False):\n", - " super().__init__(\"mix\", constant, single_child=False)\n", - " \n", - " def dot_node(self, dot):\n", - " dot.node(self._id, label=f\"< {self._name}
node score: {self.node_score()}>\", shape=\"diamond\")\n", - " \n", - " def split(self, set_above, set_below, node_between):\n", - " assert len(set_above.difference(self.childs())) == 0\n", - " assert len(set_below.difference(self.childs())) == 0\n", - " \n", - " n_above = MixNode()\n", - " n_below = MixNode()\n", - " \n", - " p = self.parent()\n", - " \n", - " for c in self.childs().copy():\n", - " self.remove_child(c)\n", - " self.remove()\n", - " \n", - " for c in set_below:\n", - " n_below.add_child(c)\n", - " \n", - " for c in set_above:\n", - " n_above.add_child(c)\n", - " \n", - " n_above.add_child(node_between)\n", - " node_between.add_child(n_below)\n", - " \n", - " if p is not None:\n", - " p.add_child(n_above)\n", - " \n", - " # test whether the mix nodes are useless\n", - " if len(n_above.childs()) == 1:\n", - " n_above.remove()\n", - " \n", - " if len(n_below.childs()) == 1:\n", - " n_below.remove()\n", - " \n", - " def n_node_mutate_options(self):\n", - " return 0 if self._constant or len(self.childs()) <= 2 else len(self.childs())\n", - " \n", - " def mutate_node(self):\n", - " \n", - " childs = self.childs()\n", - " \n", - " if len(childs) <= 2:\n", - " print(\"Warning: cannot modify mix node\")\n", - " return\n", - " \n", - " childs = random.sample(childs, len(childs))\n", - " \n", - " n = random.choice(range(1, len(childs)-1))\n", - " \n", - " between_node = ActionNode(random.choice(actions))\n", - " \n", - " self.split(set(childs[:n]), set(childs[n:]), between_node)\n", - " \n", - " \n", - " def node_score(self):\n", - " child_ingredients = [c.traverse_ingredients() for c in self.childs()]\n", - " \n", - " tmp_set = set()\n", - " cumulative_sets = []\n", - " \n", - " pairwise_tuples = []\n", - " \n", - " for c in child_ingredients:\n", - " if len(tmp_set) > 0:\n", - " cumulative_sets.append(tmp_set)\n", - " pairwise_tuples += [x for x in itertools.product(tmp_set, c)]\n", - " tmp_set = tmp_set.union(set(c))\n", - " \n", - " s_base = 0\n", - " s = 0\n", - " \n", - " for ing_a, ing_b in pairwise_tuples:\n", - " try:\n", - " #s_base += sym_score(ing_a._base_ingredient, ing_b._base_ingredient, m_base_mix, c_base_mix)\n", - " \n", - " #s += sym_score(ing_a.to_json(), ing_b.to_json(), m_mix, c_mix)\n", - " p1 = sym_p_a_given_b(ing_a.to_json(), ing_b.to_json(), m_mix, c_mix)\n", - " p2 = sym_p_a_given_b(ing_b.to_json(), ing_a.to_json(), m_mix, c_mix)\n", - " \n", - " s += 0.5 * p1 + 0.5 * p2\n", - " \n", - " except:\n", - " pass\n", - " \n", - " #s_base /= len(pairwise_tuples)\n", - " s /= len(pairwise_tuples)\n", - " \n", - " #return 0.5 * (s_base + s)\n", - " return s\n", - " \n", - " \n", - " \n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Ingredient Node Class" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "class IngredientNode(RecipeTreeNode):\n", - " def __init__(self, name, constant=False):\n", - " super().__init__(name, constant, single_child=True)\n", - " \n", - " def get_actions(self):\n", - " a_list = []\n", - " n = self.parent()\n", - " while n is not None:\n", - " if type(n) == ActionNode:\n", - " a_list.append(n.name())\n", - " n = n.parent()\n", - " return a_list\n", - " \n", - " def mutate_node(self):\n", - " self._name = random.choice(base_ingredients)\n", - " #TODO: change w.r.t. mixing probabilities \n", - " \n", - " def traverse_ingredients(self):\n", - " return [Ingredient(self._name)]\n", - " \n", - " def node_score(self):\n", - " actions = self.get_actions()\n", - " \n", - " if len(actions) == 0:\n", - " return 1\n", - " \n", - " seen_actions = set()\n", - " n_duplicates = 0\n", - " for act in actions:\n", - " if act in seen_actions:\n", - " n_duplicates += 1\n", - " else:\n", - " seen_actions.add(act)\n", - " \n", - " return len(seen_actions) / len(actions)\n", - " \n", - " \n", - " def dot_node(self, dot):\n", - " dot.node(self._id, label=f\"< {self._name}
node score:{self.node_score()}>\", shape=\"box\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Action Node Class" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "class ActionNode(RecipeTreeNode):\n", - " def __init__(self, name, constant=False):\n", - " super().__init__(name, constant, single_child=True)\n", - " \n", - " def n_node_mutate_options(self):\n", - " # beacause we can change or remove ourselve!\n", - " return 0 if self._constant else 2 \n", - " def mutate_node(self):\n", - " if random.choice(range(2)) == 0:\n", - " # change action\n", - " self._name = random.choice(actions)\n", - " else:\n", - " # delete\n", - " self.remove()\n", - " \n", - " def traverse_ingredients(self):\n", - " ingredient_set = super().traverse_ingredients()\n", - " for ing in ingredient_set:\n", - " ing.apply_action(self._name)\n", - " \n", - " return ingredient_set\n", - " \n", - " def node_score(self):\n", - " ings = self.child().traverse_ingredients()\n", - " \n", - " s = 0\n", - " \n", - " for ing in ings:\n", - " try:\n", - " #score = asym_score(self._name, ing.to_json(), m_act, c_act)\n", - " #base_score = asym_score(self._name, ing._base_ingredient, m_base_act, c_base_act)\n", - " \n", - " score = fw_p_a_given_b(self._name, ing._base_ingredient, m_base_act, c_base_act)\n", - " \n", - " s += score\n", - " except KeyError as e:\n", - " pass\n", - " \n", - " \n", - " return s / len(ings)\n", - " \n", - " def dot_node(self, dot):\n", - " dot.node(self._id, label=f\"< {self._name}
node score: {self.node_score()}>\", shape=\"ellipse\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Tree Class" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "class Tree(object):\n", - " \n", - " @staticmethod\n", - " def from_ingredients(ingredients: list):\n", - " root = MixNode()\n", - " \n", - " for ing in ingredients:\n", - " root.add_child(IngredientNode(ing, constant=True))\n", - " \n", - " return Tree(root)\n", - " \n", - " @staticmethod\n", - " def from_serialization(s):\n", - " def empty_node(raw_n):\n", - " if raw_n['type'] == \"MixNode\":\n", - " node = MixNode(raw_n['constant'])\n", - " elif raw_n['type'] == \"IngredientNode\":\n", - " node = IngredientNode(raw_n['name'], raw_n['constant'])\n", - " elif raw_n['type'] == \"ActionNode\":\n", - " node = ActionNode(raw_n['name'], raw_n['constant'])\n", - " else:\n", - " print(\"unknown node detected\")\n", - " return\n", - " \n", - " return node\n", - " \n", - " nodes = {}\n", - " for n in s:\n", - " nodes[n['id']] = empty_node(n)\n", - " \n", - " for n in s:\n", - " childs = n['childs']\n", - " id = n['id']\n", - " for c in childs:\n", - " nodes[id].add_child(nodes[c])\n", - " \n", - " return Tree(nodes[s[0]['id']])\n", - " \n", - " \n", - " def __init__(self, root):\n", - " # create a dummy entry node\n", - " self._root = RecipeTreeNode(\"root\", single_child=True)\n", - " self._root.add_child(root)\n", - " \n", - " def root(self):\n", - " return self._root.child()\n", - " \n", - " def mutate(self):\n", - " nodes = self.root().traverse()\n", - " weights = [n.n_mutate_options() for n in nodes]\n", - " \n", - " n = random.choices(nodes, weights)[0]\n", - " \n", - " n.mutate()\n", - " \n", - " def dot(self):\n", - " return self.root().dot()\n", - " \n", - " def serialize(self):\n", - " return [n.serialize() for n in self.root().traverse()]\n", - " \n", - " def structure_score(self):\n", - " n_duplicates = 0\n", - " \n", - " \n", - " def collect_scores(self):\n", - " self._mix_scores = []\n", - " self._act_scores = []\n", - " self._ing_scores = []\n", - " \n", - " nodes = self.root().traverse()\n", - " self._n_mix_nodes = 0\n", - " self._n_act_nodes = 0\n", - " self._n_ing_nodes = 0\n", - " \n", - " s = 0\n", - " for n in nodes:\n", - " if type(n) == MixNode:\n", - " self._mix_scores.append(n.node_score())\n", - " self._n_mix_nodes += 1\n", - " if type(n) == ActionNode:\n", - " self._act_scores.append(n.node_score())\n", - " self._n_act_nodes += 1\n", - " if type(n) == IngredientNode:\n", - " self._ing_scores.append(n.node_score())\n", - " self._n_ing_nodes += 1\n", - " \n", - " self._n_duplicates = 0\n", - " seen_actions = set()\n", - " \n", - " for n in nodes:\n", - " if type(n) == ActionNode:\n", - " if n.name() in seen_actions:\n", - " self._n_duplicates += 1\n", - " else:\n", - " seen_actions.add(n.name())\n", - " \n", - " self._mix_scores = np.array(self._mix_scores)\n", - " self._act_scores = np.array(self._act_scores)\n", - " self._ing_scores = np.array(self._ing_scores)\n", - " \n", - " \n", - " def mix_scores(self):\n", - " return self._mix_scores\n", - " \n", - " def action_scores(self):\n", - " return self._act_scores\n", - " \n", - " def ing_scores(self):\n", - " return self._ing_scores\n", - " \n", - " def bounds_mix_scores(self):\n", - " \n", - " nonzeros = self._mix_scores[self._mix_scores > 0]\n", - " if len(nonzeros) > 0:\n", - " mmax = np.max(nonzeros)\n", - " mmin = np.min(nonzeros)\n", - " return mmin, mmax\n", - " else:\n", - " return None, None\n", - " \n", - " def bounds_act_scores(self):\n", - " \n", - " if len(self._act_scores) == 0:\n", - " return None, None\n", - " nonzeros = self._act_scores[self._act_scores > 0]\n", - " if len(nonzeros) > 0:\n", - " mmax = np.max(nonzeros)\n", - " mmin = np.min(nonzeros)\n", - " return mmin, mmax\n", - " else:\n", - " return None, None\n", - " \n", - " def normalized_mix_scores(self, min, max):\n", - " if (max != min):\n", - " normalized = (self._mix_scores - min)/(max-min)\n", - " normalized[normalized <= 0] = 0\n", - " return normalized\n", - " else:\n", - " return None\n", - " \n", - " def normalized_act_scores(self, min, max):\n", - " if len(self._act_scores) == 0 or max == min:\n", - " return None\n", - " normalized = (self._act_scores - min)/(max-min)\n", - " normalized[normalized <= 0] = 0\n", - " return normalized\n", - " \n", - " def copy(self):\n", - " return Tree.from_serialization(self.serialize())\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Population" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "class Population(object):\n", - " def __init__(self, start_ingredients, n_population = 10):\n", - " self.population = [Tree.from_ingredients(start_ingredients) for i in range(n_population)]\n", - " self._n = n_population\n", - " self._mix_min = None\n", - " self._mix_max = None\n", - " self._act_min = None\n", - " self._act_max = None\n", - " self._mix_scores = None\n", - " self._act_scores = None\n", - " self._scores = None\n", - " \n", - " def mutate(self):\n", - " for tree in self.population.copy():\n", - " t_clone = tree.copy()\n", - " t_clone.mutate()\n", - " self.population.append(t_clone)\n", - " \n", - " def pairwise_competition(self):\n", - " new_population = []\n", - " indices = list(range(len(self.population)))\n", - " random.shuffle(indices)\n", - " \n", - " for i in range(len(self.population) // 2):\n", - " i_a = indices[2*i]\n", - " i_b = indices[2*i+1]\n", - " \n", - " if self._scores[i_a] > self._scores[i_b]:\n", - " new_population.append(self.population[i_a])\n", - " else:\n", - " new_population.append(self.population[i_b])\n", - " \n", - " self.population = new_population\n", - " \n", - " def hold_best(self, n=10):\n", - " sorted_indices = np.argsort(-self._scores)\n", - " \n", - " self.population = np.array(self.population)[sorted_indices[:n]].tolist()\n", - " \n", - " def analyse_scores(self):\n", - " for tree in self.population:\n", - " min, max = tree.bounds_mix_scores()\n", - " if min is not None and max is not None:\n", - " if self._mix_min is None or min < self._mix_min:\n", - " self._mix_min = min\n", - " if self._mix_max is None or max > self._mix_max:\n", - " self._mix_max = max\n", - " \n", - " min, max = tree.bounds_act_scores()\n", - " if min is not None and max is not None:\n", - " if self._act_min is None or min < self._act_min:\n", - " self._act_min = min\n", - " if self._act_max is None or max > self._act_max:\n", - " self._act_max = max\n", - " \n", - " def single_score(self, mix_scores, act_scores, ing_scores):\n", - " if mix_scores is None or act_scores is None or ing_scores is None:\n", - " return 0\n", - " return np.average(mix_scores) * np.average(act_scores) * np.average(ing_scores)\n", - " \n", - " \n", - " \n", - " def collect_scores(self):\n", - " for tree in self.population:\n", - " tree.collect_scores()\n", - " \n", - " self.analyse_scores()\n", - " \n", - " if self._mix_min is not None and self._mix_max is not None:\n", - " self._mix_scores = [t.normalized_mix_scores(self._mix_min, self._mix_max) for t in self.population]\n", - " else:\n", - " # if no normalization can be done, all values are the same or 0.\n", - " # in this case just fill in zeros as score\n", - " self._mix_scores = [np.zeros(shape=t._mix_scores.shape) for t in self.population]\n", - " \n", - " if self._act_min is not None and self._act_max is not None:\n", - " self._act_scores = [t.normalized_act_scores(self._act_min, self._act_max) for t in self.population]\n", - " else:\n", - " self._act_scores = [np.zeros(shape=t._act_scores) for t in self.population]\n", - " \n", - " self._scores = []\n", - " for i in range(len(self._mix_scores)):\n", - " #print (self._mix_scores[i], self._act_scores[i])\n", - " if self._act_scores is None or self._mix_scores is None or self._act_scores[i] is None:\n", - " self._scores.append(0)\n", - " continue\n", - " \n", - " self._scores.append(self.single_score(self._mix_scores[i], self._act_scores[i], self.population[i].ing_scores()))\n", - " self._scores = np.array(self._scores)\n", - " \n", - " def run(self, n=50):\n", - " for i in range(n):\n", - " print(i)\n", - " self.mutate()\n", - " self.mutate()\n", - " self.collect_scores()\n", - " #self.pairwise_competition()\n", - " #self.collect_scores()\n", - " self.hold_best(self._n)\n", - " \n", - " \n", - " \n", - " def plot_population(self):\n", - " self.collect_scores()\n", - " #print(self._mix_scores)\n", - " #print(self._act_scores)\n", - " #print(self._scores)\n", - " for i, t in enumerate(self.population):\n", - " print(self._scores[i])\n", - " display(t.root().dot())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run Evolutionary Algorithm" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "p = Population([\"tomato\",\"onion\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n" - ] - } - ], - "source": [ - "p.run(1)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.3304126483795514\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "152\n", - "\n", - " \n", - "bake\n", - "node score: 0.4546361041518653\n", - "\n", - "\n", - "\n", - "153\n", - "\n", - " \n", - "mix\n", - "node score: 0.3897435897435897\n", - "\n", - "\n", - "\n", - "152->153\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "157\n", - "\n", - " \n", - "chop\n", - "node score: 0.21089630931458697\n", - "\n", - "\n", - "\n", - "153->157\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "155\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "153->155\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "154\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "157->154\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.28606469692626313\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "139\n", - "\n", - " \n", - "cook\n", - "node score: 0.8829787234042552\n", - "\n", - "\n", - "\n", - "135\n", - "\n", - " \n", - "mix\n", - "node score: 0.20029027576197386\n", - "\n", - "\n", - "\n", - "139->135\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "137\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "135->137\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "136\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "135->136\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.2081128747795414\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "185\n", - "\n", - " \n", - "mix\n", - "node score: 0.3897435897435897\n", - "\n", - "\n", - "\n", - "188\n", - "\n", - " \n", - "tomato\n", - "node score:1\n", - "\n", - "\n", - "\n", - "185->188\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "186\n", - "\n", - " \n", - "chop\n", - "node score: 0.21089630931458697\n", - "\n", - "\n", - "\n", - "185->186\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "187\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "186->187\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.1467369569343487\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "74\n", - "\n", - " \n", - "bake\n", - "node score: 0.4546361041518653\n", - "\n", - "\n", - "\n", - "70\n", - "\n", - " \n", - "mix\n", - "node score: 0.20029027576197386\n", - "\n", - "\n", - "\n", - "74->70\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "71\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "70->71\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "72\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "70->72\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.1467369569343487\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "54\n", - "\n", - " \n", - "bake\n", - "node score: 0.4546361041518653\n", - "\n", - "\n", - "\n", - "50\n", - "\n", - " \n", - "mix\n", - "node score: 0.20029027576197386\n", - "\n", - "\n", - "\n", - "54->50\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "51\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "50->51\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "52\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "50->52\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.10385674736299129\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "114\n", - "\n", - " \n", - "saute\n", - "node score: 0.3228072143489262\n", - "\n", - "\n", - "\n", - "110\n", - "\n", - " \n", - "mix\n", - "node score: 0.20029027576197386\n", - "\n", - "\n", - "\n", - "114->110\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "111\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "110->111\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "112\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "110->112\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.0979739710108862\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "49\n", - "\n", - " \n", - "place\n", - "node score: 0.3047214847499034\n", - "\n", - "\n", - "\n", - "45\n", - "\n", - " \n", - "mix\n", - "node score: 0.20029027576197386\n", - "\n", - "\n", - "\n", - "49->45\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "47\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "45->47\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "46\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "45->46\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.048854207472685374\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "104\n", - "\n", - " \n", - "pour\n", - "node score: 0.15371000511037156\n", - "\n", - "\n", - "\n", - "100\n", - "\n", - " \n", - "mix\n", - "node score: 0.20029027576197386\n", - "\n", - "\n", - "\n", - "104->100\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "102\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "100->102\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "101\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "100->101\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.029112476630023528\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "95\n", - "\n", - " \n", - "mix\n", - "node score: 0.13782051282051283\n", - "\n", - "\n", - "\n", - "97\n", - "\n", - " \n", - "tomato\n", - "node score:1\n", - "\n", - "\n", - "\n", - "95->97\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "99\n", - "\n", - " \n", - "brown\n", - "node score: 0.2899824253075571\n", - "\n", - "\n", - "\n", - "95->99\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "96\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "99->96\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.012923052806571338\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "89\n", - "\n", - " \n", - "peel\n", - "node score: 0.04324496129828366\n", - "\n", - "\n", - "\n", - "85\n", - "\n", - " \n", - "mix\n", - "node score: 0.20029027576197386\n", - "\n", - "\n", - "\n", - "89->85\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "86\n", - "\n", - " \n", - "tomato\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "85->86\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "87\n", - "\n", - " \n", - "onion\n", - "node score:1.0\n", - "\n", - "\n", - "\n", - "85->87\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "p.plot_population()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - }, - "toc-autonumbering": false, - "toc-showcode": false, - "toc-showmarkdowntxt": false, - "toc-showtags": false - }, - "nbformat": 4, - "nbformat_minor": 4 -} +{"cells":[{"cell_type":"markdown","metadata":{},"source":"# Evolutionary Algorithm"},{"cell_type":"markdown","metadata":{},"source":"the Evolutionary Algorithm that is supposed to create new recipes based on the Recipe Matrices that are created during the *Recipe Analysis* step.\n\nThe Population of the Evolutional Algorithm consists of a set of recipe trees. Each Recipe Tree consists of several Nodes where each node is of one of the following Types:\n\n* **Ingredient Node:**\n these are the leaf nodes. Containing an ingredient. The score is determined by the actions, that are applied if you follow up the path. At the Moment it measures how many duplicate actions are applied.\n* **Action Node:**\n An Action that is applied on it's child and this child's subtree. Score indicates the average likelihood that this action is applied on the ingredients inside the subtree\n* **Mix Node:**\n Mixing ingredients together. This is also the only Node that can have more than one child. The score is the average of all pairwise likelihoods that two ingredients are mixed togethter"},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[{"data":{"text/html":" \n "},"metadata":{},"output_type":"display_data"},{"data":{"text/html":" \n "},"metadata":{},"output_type":"display_data"}],"source":"import sys\nsys.path.append(\"../\")\nsys.path.append(\"../RecipeAnalysis/\")\n\nimport settings\n\nimport pycrfsuite\n\nimport json\n\nimport db.db_settings as db_settings\nfrom db.database_connection import DatabaseConnection\n\nfrom Tagging.conllu_generator import ConlluGenerator\nfrom Tagging.crf_data_generator import *\n\nfrom RecipeAnalysis.Recipe import Ingredient\n\nfrom difflib import SequenceMatcher\n\nimport numpy as np\n\nimport plotly.graph_objs as go\nfrom plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot\nfrom plotly.subplots import make_subplots\ninit_notebook_mode(connected=True)\n\nfrom graphviz import Digraph\n\nimport itertools\n\nimport random\n\nimport plotly.io as pio\npio.renderers.default = \"jupyterlab\"\n\nfrom IPython.display import Markdown, HTML, display\n\nfrom copy import deepcopy"},{"cell_type":"code","execution_count":59,"metadata":{},"outputs":[],"source":"def gaussian(x, mu, sig):\n return 1./(np.sqrt(2.*np.pi)*sig)*np.exp(-np.power((x - mu)/sig, 2.)/2)"},{"cell_type":"markdown","metadata":{},"source":"## load adjacency matrices"},{"cell_type":"code","execution_count":2,"metadata":{},"outputs":[],"source":"import dill\nm_act = dill.load(open(\"../RecipeAnalysis/m_act.dill\", \"rb\"))\nm_mix = dill.load(open(\"../RecipeAnalysis/m_mix.dill\", \"rb\"))\nm_base_act = dill.load(open(\"../RecipeAnalysis/m_base_act.dill\", \"rb\"))\nm_base_mix = dill.load(open(\"../RecipeAnalysis/m_base_mix.dill\", \"rb\"))\n\n#m_act.apply_threshold(3)\n#m_mix.apply_threshold(3)\n#m_base_act.apply_threshold(5)\n#m_base_mix.apply_threshold(5)\n\n\n#c_act = m_act.get_csr()\n#c_mix = m_mix.get_csr()\n#c_base_act = m_base_act.get_csr()\n#c_base_mix = m_base_mix.get_csr()\n\nc_act = m_act._csr\nc_mix = m_mix._csr\nc_base_act = m_base_act._csr\nc_base_mix = m_base_mix._csr\n"},{"cell_type":"code","execution_count":3,"metadata":{},"outputs":[],"source":"actions = m_act.get_labels()[0]"},{"cell_type":"code","execution_count":4,"metadata":{},"outputs":[],"source":"base_ingredients = m_base_mix.get_labels()"},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[],"source":"sym_label_buffer = {}\nfw_label_buffer = {}\nbw_label_buffer = {}"},{"cell_type":"markdown","metadata":{},"source":"### helper functions for adjacency matrices"},{"cell_type":"code","execution_count":6,"metadata":{},"outputs":[],"source":"def get_sym_adjacent(key, m, c):\n index = m._label_index[key]\n i1 = c[index,:].nonzero()[1]\n i2 = c[:,index].nonzero()[0]\n \n i = np.concatenate((i1,i2))\n \n if m in sym_label_buffer:\n names = sym_label_buffer[m][i]\n else:\n names = np.array(m.get_labels())\n sym_label_buffer[m] = names\n names = names[i]\n \n counts = np.concatenate((c[index, i1].toarray().flatten(), c[i2, index].toarray().flatten()))\n \n s = np.argsort(-counts)\n \n return names[s], counts[s]"},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[],"source":"def get_forward_adjacent(key, m, c):\n index = m._x_label_index[key]\n i = c[index,:].nonzero()[1]\n \n if m in fw_label_buffer:\n names = fw_label_buffer[m][i]\n else:\n names = np.array(m._y_labels)\n fw_label_buffer[m] = names\n names = names[i]\n \n \n counts = c[index, i].toarray().flatten()\n \n s = np.argsort(-counts)\n \n return names[s], counts[s]"},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[],"source":"def get_backward_adjacent(key, m, c):\n index = m._y_label_index[key]\n i = c[:,index].nonzero()[0]\n \n if m in bw_label_buffer:\n names = bw_label_buffer[m][i]\n else:\n names = np.array(m._x_labels)\n bw_label_buffer[m] = names\n names = names[i]\n \n \n counts = c[i, index].toarray().flatten()\n \n s = np.argsort(-counts)\n \n return names[s], counts[s]"},{"cell_type":"code","execution_count":9,"metadata":{},"outputs":[{"data":{"text/plain":"(array(['heat', 'simmer', 'cook', 'boil', 'bake', 'place', 'slice', 'cut',\n 'chop', 'cool', 'dice', 'refrigerate', 'pour', 'drain', 'brown',\n 'warm', 'blend', 'chill', 'spread', 'thicken', 'grill', 'saute',\n 'peel', 'fry', 'mash', 'whisk', 'break', 'freeze', 'melt'],\n dtype=' 1 and p._single_child)\n \n for c in childs:\n self.remove_child(c)\n \n if p is not None:\n p.remove_child(self)\n \n if self._single_child and self._child is not None and p._name == self._child._name:\n # two adjacent nodes with same name would remain after deletion.\n # merge them! (by adding the child's childs to our parent instead of our childs)\n childs = self._child.childs()\n self._child.remove()\n \n \n for c in childs:\n p.add_child(c)\n \n def insert_before(self, n):\n p = self._parent\n if p is not None:\n p.remove_child(self)\n p.add_child(n)\n n.add_child(self)\n \n def mutate(self):\n n_node = self.n_node_mutate_options()\n n_edge = self.n_edge_mutate_options()\n \n choice = random.choice(range(n_node + n_edge))\n if choice < n_node:\n self.mutate_node()\n else:\n self.mutate_edges()\n \n def mutate_edges(self):\n ings = self.traverse_ingredients()\n ing = random.choice(ings)\n \n a, w = get_backward_adjacent(ing._base_ingredient, m_base_act, c_base_act)\n \n action = random.choices(a, w)[0]\n self.insert_before(ActionNode(action))\n \n def mutate_node(self):\n raise NotImplementedError\n \n def n_node_mutate_options(self):\n \n return 0 if self._constant else 1\n \n def n_edge_mutate_options(self):\n n = 1 if self._parent is not None else 0\n return n\n \n def n_mutate_options(self):\n return self.n_edge_mutate_options() + self.n_node_mutate_options()\n \n def dot_node(self, dot):\n raise NotImplementedError()\n \n def dot(self, d=None):\n if d is None:\n d = Digraph()\n self.dot_node(d)\n \n else:\n self.dot_node(d)\n if self._parent is not None:\n d.edge(self._parent._id, self._id)\n \n \n for c in self.childs():\n c.dot(d)\n \n return d\n \n def serialize(self):\n r = {}\n r['type'] = str(self.__class__.__name__)\n r['id'] = self._id\n r['parent'] = self._parent._id if self._parent is not None else None\n r['name'] = self._name\n r['childs'] = [c._id for c in self.childs()]\n r['constant'] = self._constant\n r['single_child'] = self._single_child\n \n return r\n \n def node_score(self):\n raise NotImplementedError()\n \n"},{"cell_type":"markdown","metadata":{},"source":"### Mix Node"},{"cell_type":"code","execution_count":32,"metadata":{},"outputs":[],"source":"class MixNode(RecipeTreeNode):\n def __init__(self, constant=False):\n super().__init__(\"mix\", constant, single_child=False)\n \n def dot_node(self, dot):\n dot.node(self._id, label=f\"< {self._name}
node score: {self.node_score()}>\", shape=\"diamond\")\n \n def split(self, set_above, set_below, node_between):\n assert len(set_above.difference(self.childs())) == 0\n assert len(set_below.difference(self.childs())) == 0\n \n n_above = MixNode()\n n_below = MixNode()\n \n p = self.parent()\n \n for c in self.childs().copy():\n self.remove_child(c)\n self.remove()\n \n for c in set_below:\n n_below.add_child(c)\n \n for c in set_above:\n n_above.add_child(c)\n \n n_above.add_child(node_between)\n node_between.add_child(n_below)\n \n if p is not None:\n p.add_child(n_above)\n \n # test whether the mix nodes are useless\n if len(n_above.childs()) == 1:\n n_above.remove()\n \n if len(n_below.childs()) == 1:\n n_below.remove()\n \n def n_node_mutate_options(self):\n return 0 if self._constant or len(self.childs()) <= 2 else len(self.childs())\n \n def mutate_node(self):\n \n childs = self.childs()\n \n if len(childs) <= 2:\n print(\"Warning: cannot modify mix node\")\n return\n \n childs = random.sample(childs, len(childs))\n \n n = random.choice(range(1, len(childs)-1))\n \n between_node = ActionNode(random.choice(actions))\n \n self.split(set(childs[:n]), set(childs[n:]), between_node)\n \n \n def node_score(self):\n child_ingredients = [c.traverse_ingredients() for c in self.childs()]\n \n tmp_set = set()\n cumulative_sets = []\n \n pairwise_tuples = []\n \n for c in child_ingredients:\n if len(tmp_set) > 0:\n cumulative_sets.append(tmp_set)\n pairwise_tuples += [x for x in itertools.product(tmp_set, c)]\n tmp_set = tmp_set.union(set(c))\n \n s_base = 0\n s = 0\n \n for ing_a, ing_b in pairwise_tuples:\n try:\n #s_base += sym_score(ing_a._base_ingredient, ing_b._base_ingredient, m_base_mix, c_base_mix)\n \n #s += sym_score(ing_a.to_json(), ing_b.to_json(), m_mix, c_mix)\n p1 = sym_p_a_given_b(ing_a.to_json(), ing_b.to_json(), m_mix, c_mix)\n p2 = sym_p_a_given_b(ing_b.to_json(), ing_a.to_json(), m_mix, c_mix)\n \n s += 0.5 * p1 + 0.5 * p2\n \n except:\n pass\n \n #s_base /= len(pairwise_tuples)\n s /= len(pairwise_tuples)\n \n #return 0.5 * (s_base + s)\n return s\n \n \n \n"},{"cell_type":"markdown","metadata":{},"source":"### Ingredient Node Class"},{"cell_type":"code","execution_count":163,"metadata":{},"outputs":[],"source":"class IngredientNode(RecipeTreeNode):\n def __init__(self, name, constant=False):\n super().__init__(name, constant, single_child=True)\n \n def get_actions(self):\n a_list = []\n n = self.parent()\n while n is not None:\n if type(n) == ActionNode:\n a_list.append(n.name())\n n = n.parent()\n return a_list\n \n def mutate_node(self):\n self._name = random.choice(base_ingredients)\n #TODO: change w.r.t. mixing probabilities \n \n def traverse_ingredients(self):\n return [Ingredient(self._name)]\n \n def node_score(self):\n actions = self.get_actions()\n \n if len(actions) == 0:\n if p_ingredient_unprepared(self._name) < 0.2:\n return 0\n return 1\n \n seen_actions = set()\n n_duplicates = 0\n for act in actions:\n if act in seen_actions:\n n_duplicates += 1\n else:\n seen_actions.add(act)\n \n duplicate_actions_score = len(seen_actions) / len(actions)\n \n return duplicate_actions_score\n \n \n def dot_node(self, dot):\n dot.node(self._id, label=f\"< {self._name}
node score:{self.node_score()}>\", shape=\"box\")"},{"cell_type":"markdown","metadata":{},"source":"### Action Node Class"},{"cell_type":"code","execution_count":164,"metadata":{},"outputs":[],"source":"class ActionNode(RecipeTreeNode):\n def __init__(self, name, constant=False):\n super().__init__(name, constant, single_child=True)\n \n def n_node_mutate_options(self):\n # beacause we can change or remove ourselve!\n return 0 if self._constant else 2 \n def mutate_node(self):\n if random.choice(range(2)) == 0:\n # change action\n self._name = random.choice(actions)\n else:\n # delete\n self.remove()\n \n def traverse_ingredients(self):\n ingredient_set = super().traverse_ingredients()\n for ing in ingredient_set:\n ing.apply_action(self._name)\n \n return ingredient_set\n \n def node_score(self):\n ings = self.child().traverse_ingredients()\n \n s = 0\n \n for ing in ings:\n try:\n #score = asym_score(self._name, ing.to_json(), m_act, c_act)\n #base_score = asym_score(self._name, ing._base_ingredient, m_base_act, c_base_act)\n \n score = fw_p_a_given_b(self._name, ing._base_ingredient, m_base_act, c_base_act)\n \n s += score\n except KeyError as e:\n pass\n \n \n return s / len(ings)\n \n def dot_node(self, dot):\n dot.node(self._id, label=f\"< {self._name}
node score: {self.node_score()}>\", shape=\"ellipse\")"},{"cell_type":"markdown","metadata":{},"source":"### Tree Class"},{"cell_type":"code","execution_count":166,"metadata":{},"outputs":[],"source":"class Tree(object):\n \n @staticmethod\n def from_ingredients(ingredients: list):\n root = MixNode()\n \n for ing in ingredients:\n root.add_child(IngredientNode(ing, constant=True))\n \n return Tree(root)\n \n @staticmethod\n def from_serialization(s):\n def empty_node(raw_n):\n if raw_n['type'] == \"MixNode\":\n node = MixNode(raw_n['constant'])\n elif raw_n['type'] == \"IngredientNode\":\n node = IngredientNode(raw_n['name'], raw_n['constant'])\n elif raw_n['type'] == \"ActionNode\":\n node = ActionNode(raw_n['name'], raw_n['constant'])\n else:\n print(\"unknown node detected\")\n return\n \n return node\n \n nodes = {}\n for n in s:\n nodes[n['id']] = empty_node(n)\n \n for n in s:\n childs = n['childs']\n id = n['id']\n for c in childs:\n nodes[id].add_child(nodes[c])\n \n return Tree(nodes[s[0]['id']])\n \n \n def __init__(self, root):\n # create a dummy entry node\n self._root = RecipeTreeNode(\"root\", single_child=True)\n self._root.add_child(root)\n \n def root(self):\n return self._root.child()\n \n def mutate(self):\n nodes = self.root().traverse()\n weights = [n.n_mutate_options() for n in nodes]\n \n n = random.choices(nodes, weights)[0]\n \n n.mutate()\n \n def dot(self):\n return self.root().dot()\n \n def serialize(self):\n return [n.serialize() for n in self.root().traverse()]\n \n def structure_score(self):\n n_duplicates = 0\n \n \n def collect_scores(self):\n self._mix_scores = []\n self._act_scores = []\n self._ing_scores = []\n \n nodes = self.root().traverse()\n self._n_mix_nodes = 0\n self._n_act_nodes = 0\n self._n_ing_nodes = 0\n \n s = 0\n for n in nodes:\n if type(n) == MixNode:\n self._mix_scores.append(n.node_score())\n self._n_mix_nodes += 1\n if type(n) == ActionNode:\n self._act_scores.append(n.node_score())\n self._n_act_nodes += 1\n if type(n) == IngredientNode:\n self._ing_scores.append(n.node_score())\n self._n_ing_nodes += 1\n \n self._n_duplicates = 0\n seen_actions = set()\n \n for n in nodes:\n if type(n) == ActionNode:\n if n.name() in seen_actions:\n self._n_duplicates += 1\n else:\n seen_actions.add(n.name())\n \n self._mix_scores = np.array(self._mix_scores)\n self._act_scores = np.array(self._act_scores)\n self._ing_scores = np.array(self._ing_scores)\n \n \n def mix_scores(self):\n return self._mix_scores\n \n def action_scores(self):\n return self._act_scores\n \n def ing_scores(self):\n return self._ing_scores\n \n def bounds_mix_scores(self):\n \n nonzeros = self._mix_scores[self._mix_scores > 0]\n if len(nonzeros) > 0:\n mmax = np.max(nonzeros)\n mmin = np.min(nonzeros)\n return mmin, mmax\n else:\n return None, None\n \n def bounds_act_scores(self):\n \n if len(self._act_scores) == 0:\n return None, None\n nonzeros = self._act_scores[self._act_scores > 0]\n if len(nonzeros) > 0:\n mmax = np.max(nonzeros)\n mmin = np.min(nonzeros)\n return mmin, mmax\n else:\n return None, None\n \n def normalized_mix_scores(self, min, max):\n if (max != min):\n normalized = (self._mix_scores - min)/(max-min)\n normalized[normalized <= 0] = 0\n return normalized\n else:\n return None\n \n def normalized_act_scores(self, min, max):\n if len(self._act_scores) == 0 or max == min:\n return None\n normalized = (self._act_scores - min)/(max-min)\n normalized[normalized <= 0] = 0\n return normalized\n \n def copy(self):\n return Tree.from_serialization(self.serialize())\n"},{"cell_type":"markdown","metadata":{},"source":"## Population"},{"cell_type":"code","execution_count":167,"metadata":{},"outputs":[],"source":"class Population(object):\n def __init__(self, start_ingredients, n_population = 10):\n self.population = [Tree.from_ingredients(start_ingredients) for i in range(n_population)]\n self._n = n_population\n self._mix_min = None\n self._mix_max = None\n self._act_min = None\n self._act_max = None\n self._mix_scores = None\n self._act_scores = None\n self._scores = None\n \n def mutate(self):\n for tree in self.population.copy():\n t_clone = tree.copy()\n t_clone.mutate()\n self.population.append(t_clone)\n \n def pairwise_competition(self):\n new_population = []\n indices = list(range(len(self.population)))\n random.shuffle(indices)\n \n for i in range(len(self.population) // 2):\n i_a = indices[2*i]\n i_b = indices[2*i+1]\n \n if self._scores[i_a] > self._scores[i_b]:\n new_population.append(self.population[i_a])\n else:\n new_population.append(self.population[i_b])\n \n self.population = new_population\n \n def hold_best(self, n=10):\n sorted_indices = np.argsort(-self._scores)\n \n self.population = np.array(self.population)[sorted_indices[:n]].tolist()\n \n def analyse_scores(self):\n for tree in self.population:\n min, max = tree.bounds_mix_scores()\n if min is not None and max is not None:\n if self._mix_min is None or min < self._mix_min:\n self._mix_min = min\n if self._mix_max is None or max > self._mix_max:\n self._mix_max = max\n \n min, max = tree.bounds_act_scores()\n if min is not None and max is not None:\n if self._act_min is None or min < self._act_min:\n self._act_min = min\n if self._act_max is None or max > self._act_max:\n self._act_max = max\n \n def single_score(self, mix_scores, act_scores, ing_scores):\n if mix_scores is None or act_scores is None or ing_scores is None:\n return 0\n score = (0.5 * np.average(mix_scores) + 0.5 * np.average(act_scores)) * np.average(ing_scores)\n # judging also how many actions we have. So far use a gaussian with mean at number of ingredients\n \n score *= gaussian(len(act_scores), len(mix_scores), 1)\n return score\n \n \n \n def collect_scores(self):\n for tree in self.population:\n tree.collect_scores()\n \n self.analyse_scores()\n \n if self._mix_min is not None and self._mix_max is not None:\n self._mix_scores = [t.normalized_mix_scores(self._mix_min, self._mix_max) for t in self.population]\n else:\n # if no normalization can be done, all values are the same or 0.\n # in this case just fill in zeros as score\n self._mix_scores = [np.zeros(shape=t._mix_scores.shape) for t in self.population]\n \n if self._act_min is not None and self._act_max is not None:\n self._act_scores = [t.normalized_act_scores(self._act_min, self._act_max) for t in self.population]\n else:\n self._act_scores = [np.zeros(shape=t._act_scores) for t in self.population]\n \n self._scores = []\n for i in range(len(self._mix_scores)):\n #print (self._mix_scores[i], self._act_scores[i])\n if self._act_scores is None or self._mix_scores is None or self._act_scores[i] is None:\n self._scores.append(0)\n continue\n \n s = self.single_score(self._mix_scores[i], self._act_scores[i], self.population[i].ing_scores())\n self._scores.append(s)\n self._scores = np.array(self._scores)\n \n def run(self, n=50):\n for i in range(n):\n print(i)\n self.mutate()\n self.mutate()\n self.collect_scores()\n #self.pairwise_competition()\n #self.collect_scores()\n self.hold_best(self._n)\n \n \n \n def plot_population(self):\n self.collect_scores()\n #print(self._mix_scores)\n #print(self._act_scores)\n #print(self._scores)\n for i, t in enumerate(self.population):\n display(self._scores[i])\n display(t.root().dot())"},{"cell_type":"markdown","metadata":{},"source":"## Run Evolutionary Algorithm"},{"cell_type":"code","execution_count":176,"metadata":{},"outputs":[],"source":"p = Population([\"noodle\", \"bacon\", \"salt\", \"pepper\", \"tomato\", \"onion\"])"},{"cell_type":"code","execution_count":172,"metadata":{},"outputs":[],"source":"#p_ingredient_unprepared(list(p.population[0].root().childs())[0]._name) < 0.2"},{"cell_type":"code","execution_count":177,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":"0\n1\n2\n3\n4\n"}],"source":"p.run(5)"},{"cell_type":"code","execution_count":178,"metadata":{},"outputs":[{"data":{"text/plain":"0.23706667598847336"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n28554\n\n \nheat\nnode score: 0.7713748266936733\n\n\n\n28546\n\n \nmix\nnode score: 0.256507387234994\n\n\n\n28554->28546\n\n\n\n\n\n28548\n\n \ntomato\nnode score:1.0\n\n\n\n28546->28548\n\n\n\n\n\n28552\n\n \nsalt\nnode score:1.0\n\n\n\n28546->28552\n\n\n\n\n\n28547\n\n \nnoodle\nnode score:1.0\n\n\n\n28546->28547\n\n\n\n\n\n28551\n\n \nbacon\nnode score:1.0\n\n\n\n28546->28551\n\n\n\n\n\n28549\n\n \nonion\nnode score:1.0\n\n\n\n28546->28549\n\n\n\n\n\n28550\n\n \npepper\nnode score:1.0\n\n\n\n28546->28550\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.20204020608318787"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n29216\n\n \ncool\nnode score: 0.24156450774085103\n\n\n\n29226\n\n \nmix\nnode score: 0.0031457570642523616\n\n\n\n29216->29226\n\n\n\n\n\n29219\n\n \nbacon\nnode score:1.0\n\n\n\n29226->29219\n\n\n\n\n\n29225\n\n \ncook\nnode score: 0.7852322377351193\n\n\n\n29226->29225\n\n\n\n\n\n29221\n\n \ntomato\nnode score:1.0\n\n\n\n29226->29221\n\n\n\n\n\n29220\n\n \nnoodle\nnode score:1.0\n\n\n\n29226->29220\n\n\n\n\n\n29227\n\n \nmix\nnode score: 0.6102252273053411\n\n\n\n29225->29227\n\n\n\n\n\n29218\n\n \nsalt\nnode score:1.0\n\n\n\n29227->29218\n\n\n\n\n\n29222\n\n \npepper\nnode score:1.0\n\n\n\n29227->29222\n\n\n\n\n\n29223\n\n \nonion\nnode score:1.0\n\n\n\n29227->29223\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.17162627779561146"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n29095\n\n \nmix\nnode score: 0.0\n\n\n\n29094\n\n \ncook\nnode score: 1.0\n\n\n\n29095->29094\n\n\n\n\n\n29084\n\n \nwhip\nnode score: 0.0026102706158107817\n\n\n\n29095->29084\n\n\n\n\n\n29096\n\n \nmix\nnode score: 0.0\n\n\n\n29094->29096\n\n\n\n\n\n29091\n\n \nheat\nnode score: 0.9351222826086957\n\n\n\n29096->29091\n\n\n\n\n\n29090\n\n \nnoodle\nnode score:1.0\n\n\n\n29096->29090\n\n\n\n\n\n29092\n\n \nonion\nnode score:1.0\n\n\n\n29091->29092\n\n\n\n\n\n29085\n\n \nmix\nnode score: 0.3944813826951458\n\n\n\n29084->29085\n\n\n\n\n\n29088\n\n \npepper\nnode score:1.0\n\n\n\n29085->29088\n\n\n\n\n\n29087\n\n \nsalt\nnode score:1.0\n\n\n\n29085->29087\n\n\n\n\n\n29086\n\n \nbacon\nnode score:1.0\n\n\n\n29085->29086\n\n\n\n\n\n29089\n\n \ntomato\nnode score:1.0\n\n\n\n29085->29089\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.1713840220963046"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n29967\n\n \nmix\nnode score: 0.0\n\n\n\n29970\n\n \nnoodle\nnode score:0\n\n\n\n29967->29970\n\n\n\n\n\n29968\n\n \ncook\nnode score: 1.0\n\n\n\n29967->29968\n\n\n\n\n\n29971\n\n \nsimmer\nnode score: 0.41806154430428866\n\n\n\n29967->29971\n\n\n\n\n\n29969\n\n \nonion\nnode score:1.0\n\n\n\n29968->29969\n\n\n\n\n\n29972\n\n \nmix\nnode score: 0.3944813826951458\n\n\n\n29971->29972\n\n\n\n\n\n29973\n\n \nsalt\nnode score:1.0\n\n\n\n29972->29973\n\n\n\n\n\n29976\n\n \nbacon\nnode score:1.0\n\n\n\n29972->29976\n\n\n\n\n\n29974\n\n \npepper\nnode score:1.0\n\n\n\n29972->29974\n\n\n\n\n\n29975\n\n \ntomato\nnode score:1.0\n\n\n\n29972->29975\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.1659860909419401"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n28699\n\n \nmix\nnode score: 0.0\n\n\n\n28690\n\n \nheat\nnode score: 0.9351222826086957\n\n\n\n28699->28690\n\n\n\n\n\n28696\n\n \nnoodle\nnode score:0\n\n\n\n28699->28696\n\n\n\n\n\n28698\n\n \nsimmer\nnode score: 0.4180615443042887\n\n\n\n28699->28698\n\n\n\n\n\n28691\n\n \nonion\nnode score:1.0\n\n\n\n28690->28691\n\n\n\n\n\n28700\n\n \nmix\nnode score: 0.3944813826951458\n\n\n\n28698->28700\n\n\n\n\n\n28692\n\n \nsalt\nnode score:1.0\n\n\n\n28700->28692\n\n\n\n\n\n28695\n\n \npepper\nnode score:1.0\n\n\n\n28700->28695\n\n\n\n\n\n28693\n\n \ntomato\nnode score:1.0\n\n\n\n28700->28693\n\n\n\n\n\n28694\n\n \nbacon\nnode score:1.0\n\n\n\n28700->28694\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.16337906864086424"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n29574\n\n \nmix\nnode score: 0.0\n\n\n\n29575\n\n \ncook\nnode score: 1.0\n\n\n\n29574->29575\n\n\n\n\n\n29580\n\n \nwhip\nnode score: 0.0026102706158107817\n\n\n\n29574->29580\n\n\n\n\n\n29576\n\n \nmix\nnode score: 0.0\n\n\n\n29575->29576\n\n\n\n\n\n29577\n\n \nheat\nnode score: 0.9351222826086957\n\n\n\n29576->29577\n\n\n\n\n\n29579\n\n \nnoodle\nnode score:1.0\n\n\n\n29576->29579\n\n\n\n\n\n29578\n\n \nonion\nnode score:1.0\n\n\n\n29577->29578\n\n\n\n\n\n29588\n\n \nmix\nnode score: 0.0\n\n\n\n29580->29588\n\n\n\n\n\n29584\n\n \nbacon\nnode score:1.0\n\n\n\n29588->29584\n\n\n\n\n\n29587\n\n \nplace\nnode score: 0.39444081146694154\n\n\n\n29588->29587\n\n\n\n\n\n29589\n\n \nmix\nnode score: 0.5773519979266957\n\n\n\n29587->29589\n\n\n\n\n\n29582\n\n \npepper\nnode score:1.0\n\n\n\n29589->29582\n\n\n\n\n\n29583\n\n \nsalt\nnode score:1.0\n\n\n\n29589->29583\n\n\n\n\n\n29585\n\n \ntomato\nnode score:1.0\n\n\n\n29589->29585\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.1579882299064732"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n29552\n\n \nheat\nnode score: 0.7713748266936734\n\n\n\n29553\n\n \nmix\nnode score: 0.25828931697593543\n\n\n\n29552->29553\n\n\n\n\n\n29559\n\n \npepper\nnode score:1.0\n\n\n\n29553->29559\n\n\n\n\n\n29561\n\n \ncook\nnode score: 1.0\n\n\n\n29553->29561\n\n\n\n\n\n29554\n\n \ntomato\nnode score:1.0\n\n\n\n29553->29554\n\n\n\n\n\n29555\n\n \nsalt\nnode score:1.0\n\n\n\n29553->29555\n\n\n\n\n\n29558\n\n \nonion\nnode score:1.0\n\n\n\n29553->29558\n\n\n\n\n\n29557\n\n \nbacon\nnode score:1.0\n\n\n\n29553->29557\n\n\n\n\n\n29556\n\n \nnoodle\nnode score:1.0\n\n\n\n29561->29556\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.15466055286561975"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n29590\n\n \nmix\nnode score: 0.0\n\n\n\n29593\n\n \nnoodle\nnode score:0\n\n\n\n29590->29593\n\n\n\n\n\n29591\n\n \nheat\nnode score: 0.9351222826086957\n\n\n\n29590->29591\n\n\n\n\n\n29594\n\n \nsimmer\nnode score: 0.4180615443042887\n\n\n\n29590->29594\n\n\n\n\n\n29592\n\n \nonion\nnode score:1.0\n\n\n\n29591->29592\n\n\n\n\n\n29602\n\n \nmix\nnode score: 0.07341265771678888\n\n\n\n29594->29602\n\n\n\n\n\n29601\n\n \nheat\nnode score: 1.0\n\n\n\n29602->29601\n\n\n\n\n\n29599\n\n \nbacon\nnode score:1.0\n\n\n\n29602->29599\n\n\n\n\n\n29596\n\n \nsalt\nnode score:1.0\n\n\n\n29602->29596\n\n\n\n\n\n29603\n\n \nmix\nnode score: 0.19925133063788839\n\n\n\n29601->29603\n\n\n\n\n\n29598\n\n \ntomato\nnode score:1.0\n\n\n\n29603->29598\n\n\n\n\n\n29597\n\n \npepper\nnode score:1.0\n\n\n\n29603->29597\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.15272938996506372"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n29059\n\n \nheat\nnode score: 0.7713748266936734\n\n\n\n29069\n\n \nmix\nnode score: 0.29412064068068344\n\n\n\n29059->29069\n\n\n\n\n\n29068\n\n \nslice\nnode score: 0.1757654159815022\n\n\n\n29069->29068\n\n\n\n\n\n29062\n\n \nsalt\nnode score:1.0\n\n\n\n29069->29062\n\n\n\n\n\n29066\n\n \npepper\nnode score:1.0\n\n\n\n29069->29066\n\n\n\n\n\n29070\n\n \nmix\nnode score: 0.06751022103892917\n\n\n\n29068->29070\n\n\n\n\n\n29063\n\n \nnoodle\nnode score:1.0\n\n\n\n29070->29063\n\n\n\n\n\n29064\n\n \nbacon\nnode score:1.0\n\n\n\n29070->29064\n\n\n\n\n\n29065\n\n \nonion\nnode score:1.0\n\n\n\n29070->29065\n\n\n\n\n\n29061\n\n \ntomato\nnode score:1.0\n\n\n\n29070->29061\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"},{"data":{"text/plain":"0.14929647567328216"},"metadata":{},"output_type":"display_data"},{"data":{"image/svg+xml":"\n\n\n\n\n\n%3\n\n\n\n30129\n\n \ncook\nnode score: 0.851970571945862\n\n\n\n30131\n\n \nmix\nnode score: 0.25828931697593543\n\n\n\n30129->30131\n\n\n\n\n\n30137\n\n \ntomato\nnode score:1.0\n\n\n\n30131->30137\n\n\n\n\n\n30132\n\n \nonion\nnode score:1.0\n\n\n\n30131->30132\n\n\n\n\n\n30134\n\n \npepper\nnode score:1.0\n\n\n\n30131->30134\n\n\n\n\n\n30135\n\n \ncook\nnode score: 1.0\n\n\n\n30131->30135\n\n\n\n\n\n30133\n\n \nsalt\nnode score:1.0\n\n\n\n30131->30133\n\n\n\n\n\n30138\n\n \nbacon\nnode score:1.0\n\n\n\n30131->30138\n\n\n\n\n\n30136\n\n \nnoodle\nnode score:0.5\n\n\n\n30135->30136\n\n\n\n\n\n","text/plain":""},"metadata":{},"output_type":"display_data"}],"source":"p.plot_population()"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":""}],"nbformat":4,"nbformat_minor":2,"metadata":{"language_info":{"name":"python","codemirror_mode":{"name":"ipython","version":3}},"orig_nbformat":2,"file_extension":".py","mimetype":"text/x-python","name":"python","npconvert_exporter":"python","pygments_lexer":"ipython3","version":3}} \ No newline at end of file