diff --git a/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb b/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb index bb24db7..b4a0ab9 100644 --- a/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb +++ b/EvolutionaryAlgorithm/EvolutionaryAlgorithm.ipynb @@ -91,7 +91,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/jonas/.local/lib/python3.7/site-packages/ipykernel_launcher.py:37: TqdmExperimentalWarning:\n", + "/home/jonas/.local/lib/python3.7/site-packages/ipykernel_launcher.py:39: TqdmExperimentalWarning:\n", "\n", "Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)\n", "\n" @@ -119,6 +119,8 @@ "\n", "import numpy as np\n", "\n", + "import ActionGroups as AG\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", @@ -459,14 +461,92 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def prepare_ratio(ing:str):\n", " keys, values = m_grouped_act.get_backward_adjacent(Ingredient(ing).to_json())\n", " action_dict = dict(zip(keys,values))\n", - " return action_dict['prepare'] / action_dict['heat']" + " return action_dict['prepare'] / action_dict['heat']\n", + "\n", + "def random_prepare(ing:str):\n", + " \"\"\"\n", + " returns randomly a boolean value if ing should be prepared, w.r.t. the prepare_ration function\n", + " \"\"\"\n", + " \n", + " return prepare_ratio(ing) > np.random.normal(0.35,0.1)\n", + "\n", + "def random_heated(ingredient:str):\n", + " action_set, action_weights = m_grouped_base_act.get_backward_adjacent(ingredient)\n", + " d = dict(zip(action_set, action_weights))\n", + " ratio = 1 - d['prepare'] / d['heat']\n", + " \n", + " return ratio > np.random.normal(0.65,0.15)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "def relative_action_rank(ingredient:str, action:str):\n", + " action_set, action_weights = m_base_act.get_backward_adjacent(ingredient)\n", + " if action not in action_set or len(action_set) <= 1:\n", + " return 0\n", + " return 1 - action_set.tolist().index(action) / (len(action_set) - 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "def filter_set_by_group(act_set, act_w, group):\n", + " new_act_set = []\n", + " new_act_w = []\n", + " for i in range(len(act_set)):\n", + " if act_set[i] in AG.inverse_groups[group]:\n", + " new_act_set.append(act_set[i])\n", + " new_act_w.append(act_w[i])\n", + " return np.array(new_act_set), np.array(new_act_w)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## better normalized scores:" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "def normalized_score(key, matrix):\n", + " sum_key = matrix.get_sum(key)\n", + " keys, values = matrix.get_adjacent(key)\n", + " normalized_values = np.array([(values[i] / matrix.get_sum(keys[i])) * (values[i] / sum_key) for i in range(len(keys))])\n", + " sort = np.argsort(-normalized_values)\n", + " return keys[sort], normalized_values[sort]\n", + "\n", + "def forward_normalized_score(key, matrix):\n", + " sum_key = matrix.get_fw_sum(key)\n", + " keys, values = matrix.get_forward_adjacent(key)\n", + " normalized_values = np.array([(values[i] / matrix.get_bw_sum(keys[i])) * (values[i] / sum_key) for i in range(len(keys))])\n", + " sort = np.argsort(-normalized_values)\n", + " return keys[sort], normalized_values[sort]\n", + "\n", + "def backward_normalized_score(key, matrix):\n", + " sum_key = matrix.get_bw_sum(key)\n", + " keys, values = matrix.get_backward_adjacent(key)\n", + " normalized_values = np.array([(values[i] / matrix.get_fw_sum(keys[i])) * (values[i] / sum_key) for i in range(len(keys))])\n", + " sort = np.argsort(-normalized_values)\n", + " return keys[sort], normalized_values[sort]" ] }, { @@ -479,7 +559,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -681,7 +761,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -803,7 +883,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -861,7 +941,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -919,7 +999,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -946,6 +1026,7 @@ " ings_for_acts[a].add(ing)\n", "\n", " '''\n", + " \n", " # choose randomly an action for each ingredient by the \"wheel of fortune\" method\n", " actions_for_ing = {}\n", " for ing in ingredients:\n", @@ -1267,6 +1348,441 @@ " return Tree.from_serialization(self.serialize())\n" ] }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "ingredients = [\"onion\", \"tomato\", \"rice\", \"salt\"] \n", + "main_ingredients = [\"rice\"]\n", + "\n", + "assert set(main_ingredients).issubset(set(ingredients))\n", + "\n", + "max_n = 5\n", + "wheel_turns = 2\n", + "\n", + "def does_action_match(ingredient:str, action:str, t = 0.6):\n", + " return relative_action_rank(ingredient, action) > t\n", + "\n", + "\n", + "# choose randomly an action for each ingredient by the \"wheel of fortune\" method\n", + "actions_for_ing = {}\n", + "for ing in ingredients:\n", + " actions_for_ing[ing] = set()\n", + " action_set, action_weights = m_base_act.get_backward_adjacent(ing)\n", + " if random_heated(ing):\n", + " #print(action_set)\n", + " action_set, action_weights = filter_set_by_group(action_set, action_weights, \"heat\")\n", + " #print(action_set)\n", + " for i in range(wheel_turns):\n", + " if ing in main_ingredients:\n", + " # if main ingredient: choose by action probability\n", + " w = np.array(list(action_weights), dtype=float)\n", + " w *= (1.0 / np.sum(w))\n", + " action = np.random.choice(list(action_set), size=1, replace=False, p=w)[0]\n", + " else:\n", + " # else: choose rank based\n", + " action = ea_tools.wheel_of_fortune_selection(action_set[:max_n], action_weights[:max_n])\n", + " actions_for_ing[ing].add(action)\n", + " #print(f\"action {action} for ing {ing}\")\n", + " #print(ing, action)\n", + " \n", + "# create ingredient nodes:\n", + "ingredient_nodes = {}\n", + "\n", + "# create ingredient nodes:\n", + "for ing in ingredients:\n", + " new_node = IngredientNode(ing, constant=True)\n", + " \n", + " # check if we should do a preparation step\n", + " if random_prepare(ing):\n", + " # choose a preparation cooking action\n", + " action_set, action_weights = m_act.get_backward_adjacent(Ingredient(ing).to_json())\n", + " action_set, action_weights = filter_set_by_group(action_set, action_weights, \"prepare\")\n", + " if len(action_set) > 0:\n", + " action = ea_tools.wheel_of_fortune_selection(action_set[:max_n], action_weights[:max_n])\n", + " act_node = ActionNode(action)\n", + " act_node.add_child(new_node)\n", + " new_node = act_node\n", + " \n", + " \n", + " ingredient_nodes[ing] = new_node\n", + "\n", + "# starting now with the actions found for the main ingredients and try to match all ingredients together\n", + "# with that:\n", + "\n", + "unprocessed_ings = set(filter(lambda x: len(actions_for_ing[x]) > 0, ingredients))\n", + "unprocessed_main_ings = set(filter(lambda x: len(actions_for_ing[x]) > 0, main_ingredients))\n", + "\n", + "while len(unprocessed_main_ings) > 0:\n", + " main_ing = unprocessed_main_ings.pop()\n", + " \n", + " # random action for that ing:\n", + " act = actions_for_ing[main_ing].pop()\n", + " \n", + " act_node = ActionNode(act)\n", + " mix_node = MixNode()\n", + " mix_node.add_child(ingredient_nodes[main_ing])\n", + " act_node.add_child(mix_node)\n", + " ingredient_nodes[main_ing] = act_node\n", + " \n", + " unprocessed_ings.remove(main_ing)\n", + " \n", + " for ing in unprocessed_ings.copy():\n", + " if does_action_match(ing, act):\n", + " mix_node.add_child(ingredient_nodes[ing])\n", + " ingredient_nodes[ing] = act_node\n", + " unprocessed_ings.remove(ing)\n", + " if ing in unprocessed_main_ings:\n", + " unprocessed_main_ings.remove(ing)\n", + " \n", + " if len(mix_node.childs()) == 1:\n", + " mix_node.remove()\n", + "\n", + "# now make the same with all remaining ingredients:\n", + "while len(unprocessed_ings) > 0:\n", + " current_ing = unprocessed_ings.pop() \n", + " \n", + " # random action for that ing:\n", + " act = actions_for_ing[current_ing].pop()\n", + " \n", + " act_node = ActionNode(act)\n", + " mix_node = MixNode()\n", + " mix_node.add_child(ingredient_nodes[current_ing])\n", + " act_node.add_child(mix_node)\n", + " \n", + " ingredient_nodes[current_ing] = act_node\n", + " \n", + " \n", + " for ing in unprocessed_ings.copy():\n", + " if does_action_match(ing, act):\n", + " mix_node.add_child(ingredient_nodes[ing])\n", + " ingredient_nodes[ing] = act_node\n", + " unprocessed_ings.remove(ing)\n", + " \n", + " if len(mix_node.childs()) == 1:\n", + " mix_node.remove()\n", + "\n", + "\n", + "root_layer = set([n.root() for n in ingredient_nodes.values()])\n", + "\n", + "root_layer_without_parents = []\n", + "for node in root_layer:\n", + " if node.parent() is None:\n", + " root_layer_without_parents.append(node)\n", + "\n", + "if len(root_layer_without_parents) == 1:\n", + " root_node = root_layer_without_parents[0]\n", + "\n", + "else:\n", + " root_node = MixNode()\n", + " for r in root_layer_without_parents:\n", + " root_node.add_child(r)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "%3\n", + "\n", + "\n", + "\n", + "10\n", + "\n", + " \n", + "mix\n", + "node score: 0.0000\n", + "\n", + "\n", + "\n", + "8\n", + "\n", + " \n", + "bake\n", + "node score: 1.0000\n", + "\n", + "\n", + "\n", + "10->8\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + " \n", + "cut\n", + "node score: 0.2505\n", + "\n", + "\n", + "\n", + "10->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "6\n", + "\n", + " \n", + "fry\n", + "node score: 0.0859\n", + "\n", + "\n", + "\n", + "10->6\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "5\n", + "\n", + " \n", + "salt\n", + "node score:1.0000\n", + "\n", + "\n", + "\n", + "8->5\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + " \n", + "tomato\n", + "node score:1.0000\n", + "\n", + "\n", + "\n", + "3->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "7\n", + "\n", + " \n", + "mix\n", + "node score: 1.0000\n", + "\n", + "\n", + "\n", + "6->7\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + " \n", + "slice\n", + "node score: 0.1501\n", + "\n", + "\n", + "\n", + "7->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + " \n", + "rice\n", + "node score:1.0000\n", + "\n", + "\n", + "\n", + "7->4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + " \n", + "onion\n", + "node score:1.0000\n", + "\n", + "\n", + "\n", + "1->0\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "root_node.dot()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "constant_ingredients = [\"noodle\", \"onion\", \"tomato\"]\n", + "main_ingredients = ['noodle']\n", + "min_additional = 3\n", + "max_additional = 6\n", + "top_ings =3" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['noodle',\n", + " 'onion',\n", + " 'tomato',\n", + " 'salt',\n", + " 'cheese',\n", + " 'garlic clove',\n", + " 'olive oil',\n", + " 'mozzarella cheese']" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "seen_items = set(constant_ingredients)\n", + "\n", + "items = []\n", + "scores = []\n", + "\n", + "assert set(main_ingredients).issubset(set(constant_ingredients))\n", + "\n", + "# additional ingredients are choosen w.r.t all given ingredients\n", + "n_additional_ings = np.random.randint(min_additional, max_additional + 1)\n", + "\n", + "# extra ings are ingredients choosen specially for the main ingredient\n", + "n_extra_ings = int((len(main_ingredients) / len(constant_ingredients)) * n_additional_ings)\n", + "\n", + "if n_extra_ings > n_additional_ings:\n", + " n_extra_ings = n_additional_ings\n", + "\n", + " \n", + "# choose extra ingredients\n", + "extra_candidates = []\n", + "extra_weights = []\n", + "\n", + "for ing in main_ingredients:\n", + " candidates, weights = normalized_score(ing, m_base_mix)\n", + " extra_candidates.append(candidates[:10])\n", + " extra_weights.append(weights[:10])\n", + "\n", + "extra_ingredients = ea_tools.combined_wheel_of_fortune_selection(extra_candidates,\n", + " extra_weights,\n", + " n_extra_ings)\n", + "\n", + "for ing in constant_ingredients:\n", + " # find best matching ingredients\n", + " best_items = []\n", + " best_scores = []\n", + "\n", + " candidates, weights = m_base_mix.get_adjacent(ing)\n", + " i = 0\n", + " while i < len(candidates) and len(best_items) < top_ings:\n", + " if candidates[i] not in seen_items:\n", + " best_items.append(candidates[i])\n", + " best_scores.append(weights[i])\n", + " i += 1\n", + "\n", + " items.append(best_items)\n", + " scores.append(best_scores)\n", + "\n", + "#TODO: error handling if too few options are availabale!\n", + "\n", + "additional_ingredients = ea_tools.combined_wheel_of_fortune_selection(items,\n", + " scores,\n", + " n_additional_ings - n_extra_ings)\n", + "list(constant_ingredients) + list(additional_ingredients) + list(extra_ingredients)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array(['ricotta cheese', 'spaghetti sauce', 'mozzarella cheese', 'cheese',\n", + " 'spinach', 'sausage', 'ground beef', 'tomato sauce', 'onion',\n", + " 'basil', 'broccoli', 'parsley', 'mushroom soup', 'mushroom',\n", + " 'sauce', 'egg', 'garlic clove', 'chicken', 'milk', 'water', 'salt',\n", + " 'zucchini', 'tomato', 'pepper', 'seasoning', 'green pepper',\n", + " 'shrimp', 'soy sauce', 'butter', 'red pepper', 'oregano',\n", + " 'clove garlic', 'olive oil', 'pork', 'carrot', 'green onion',\n", + " 'cream cheese', 'garlic', 'chicken broth', 'tablespoon butter',\n", + " 'red bell pepper', 'flour', 'cream', 'black pepper',\n", + " 'vegetable oil', 'chicken breast', 'sugar'], dtype='\n", " \n", " \n", - "
\n", + "
\n", "