Revolutionizing Mocktail Creation with Neurosymbolic AI: Building a Personalized System

Prabhat
30 min readApr 15, 2023

--

Have you ever wondered how new mocktails are created? Do you ever find yourself tired of the same old mocktail flavors, but unsure of how to create something new and exciting? With the rise of artificial intelligence and machine learning, there is a new way to create personalized mocktails that perfectly match your taste preferences. Aneurosymbolic AI system can identify the best mocktail combinations while simultaneously eliminating any bad or incompatible ingredients. In this article, we will explore how to build a neurosymbolic AI system that can create a personalized mocktail recipe based on your preferred flavors.

Neurosymbolic AI is a powerful, relatively new technology that uses a combination of deep learning and symbolic reasoning to create intelligent systems capable of decision-making and reasoning. When applied to mocktail creation, a neurosymbolic AI system can take into account all the ingredients used to make a mocktail and identify the best combination of flavors that match a user’s preferences. Using a neural network, the system can learn from the previous choices and make recommendations based on their taste. The symbolic reasoning component comes into play by removing any bad or incompatible ingredients from the list of possible combinations, ensuring that only the best options are presented to the user. Building a neurosymbolic AI system for mocktail creation involves training the neural network with a dataset of flavor combinations and teaching the system to choose the best combinations from the ones generated by the user. With the right approach, a neurosymbolic AI system can help users create personalized mocktail recipes that perfectly match their taste preferences.

Neurosymbolic AI is a hybrid approach to artificial intelligence that combines the power of machine learning with the human-like reasoning abilities of symbolic AI. By using this approach, we can create a system that not only learns from data but also incorporates human knowledge and domain expertise. The article will provide a step-by-step guide on how to design and implement a neurosymbolic AI system using the Keras library and a rule-based system. The aim is to make it easy for readers to understand and apply this approach to create their mocktail recipes using artificial intelligence.

  1. Deep learning: This is a subfield of machine learning that involves training neural networks with large amounts of data to make accurate predictions or classifications.
  2. Neural networks: This is a type of machine learning algorithm that is modeled after the structure and function of the human brain.
  3. Symbolic AI: This refers to the use of logical reasoning and symbolic representation to solve problems.
  4. Knowledge representation: This is the process of encoding knowledge into a format that can be used by a computer.
  5. Rule-based systems: These are systems that use a set of if-then rules to make decisions or draw conclusions.
  6. Keras: This is a high-level neural networks API, written in Python, that is designed to enable fast experimentation with deep neural networks.

A Simple Explanation of the neurosymbolic AI System

The neurosymbolic AI system has two key components: a symbolic reasoning component and a neural network component. The system has access to a database of over 100 mocktail recipes and their associated ingredients and flavor profiles. When a user requests a mocktail with specific flavor profiles, the system generates combinations of three ingredients from the available ingredients that match the requested flavor profiles.

This is where the symbolic reasoning component comes into play. The system uses rules and logic to eliminate combinations of ingredients that are unlikely to produce a good mocktail. For example, the system might remove a combination of maraschino cherry and lime juice because they are not likely to complement each other. Once the system has eliminated all the bad combinations, the neural network is trained on the remaining good and compatible combinations to identify the best mocktail recipe. In this way, the neurosymbolic AI system combines symbolic reasoning and neural network techniques to create a sophisticated and accurate mocktail recommendation system.

Imagine a detective trying to solve a crime. They have access to a lot of evidence, witness statements, and other information about the case. To solve the crime, the detective needs to use both logic and intuition. They need to use logic to eliminate possible suspects and scenarios that don’t make sense, and intuition to make connections between pieces of evidence that might seem unrelated.

Similarly, a neurosymbolic system combines two different approaches to solving a problem — symbolic reasoning and neural networks. The symbolic reasoning component helps to eliminate unlikely solutions based on rules and logic, while the neural network component can use machine learning techniques to find patterns and connections in data. By combining these two approaches, a neurosymbolic system can create a more accurate and effective solution to a complex problem.

With the help of a neurosymbolic AI system for mocktail creation, you can now say goodbye to the guesswork involved in mixing ingredients to make a perfect mocktail. No more frustration and disappointment of not getting the taste you desire. Instead, get ready to enjoy the perfect blend of flavors that suit your taste buds. The neurosymbolic system is like your very own mocktail personal assistant, helping you create the perfect drink based on your flavor preferences. So sit back, relax, and enjoy your personalized mocktail with the perfect blend of flavors!

Type of neurosymbolic AI approaches (Wikipedia):

  • Symbolic Neural symbolism is a way of using large language models in natural language processing, such as BERT, RoBERTa, and GPT-3, where words or subword tokens are both the input and output.
  • Symbolic[Neural] is a combination of symbolic and neural techniques used in AlphaGo, where Monte Carlo tree search is used symbolically, and neural techniques are used to evaluate game positions.
  • Neural|Symbolic uses a neural architecture to interpret perceptual data as symbols and relationships that are then reasoned about symbolically. The Neural-Concept Learner is an example.
  • Neural:Symbolic → Neural relies on symbolic reasoning to create or label training data that is then learned by a deep learning model. For example, a Macsyma-like symbolic mathematics system can be used to create or label examples to train a neural model for symbolic computation.
  • Neural_{Symbolic} uses a neural net generated from symbolic rules. The Neural Theorem Prover is an example, which constructs a neural network from an AND-OR proof tree generated from knowledge base rules and terms. Logic Tensor Networks also fall into this category.
  • Neural[Symbolic] allows a neural model to directly call a symbolic reasoning engine, such as to perform an action or evaluate a state.

Here, I am using this approach: Neural:Symbolic → Neural, where I generated the good combinations using symbolic reasoning and the neural network to predict the best combination from that.

Steps:

  1. Data collection & User input
  2. Filter matching ingredients & Generate possible combinations
  3. Symbolic reasoning
  4. Neural network training & Prediction
  5. Naming and presentation of the recommendation

Discussion of the techniques used

Neural AI: The neural AI part of a neurosymbolic AI system involves the use of deep learning techniques, such as neural networks, to process and analyze data. This component is responsible for tasks such as feature extraction, pattern recognition, and prediction, and it typically learns from large amounts of labeled data. It is designed to handle complex, high-dimensional data and can make predictions or classifications with high accuracy.

Symbolic AI: Symbolic AI, also known as rule-based AI or expert systems, is a branch of artificial intelligence that deals with the representation and manipulation of knowledge formally and logically. It involves the use of logic and symbolic reasoning to make decisions and draw conclusions based on a set of rules or knowledge bases. Symbolic AI is often used in domains where human expertise and knowledge are required, such as in medical diagnosis or legal reasoning.

Neural Network: A type of machine learning algorithm that is modeled after the structure and function of the human brain. It is used for tasks such as pattern recognition and prediction.

Step 1: Data collection & User input

The dataset used for the mocktail-creating neurosymbolic system consists of several columns: Cocktail Name, Ingredient 1, Ingredient 2, Ingredient 3, Flavor Profile 1, Flavor Profile 2, and User Rating. The Cocktail Name column contains the name of the mocktail, while the Ingredient 1, Ingredient 2, and Ingredient 3 columns contain the names of the ingredients used to make the mocktail. The Flavor Profile 1 and Flavor Profile 2 columns represent the primary and secondary flavor profiles of the mocktail. Finally, the User Rating column contains the average rating given by users who have tried the mocktail.

This dataset was generated by ChatGPT, a large language model trained by OpenAI, based on the GPT-3.5 architecture. ChatGPT was trained on a vast amount of text data from the internet and can generate high-quality text in natural language. For this specific dataset, ChatGPT used its knowledge of cocktail recipes and flavor profiles to generate a list of mocktails and their corresponding ingredients and ratings.

Code:

1. Installing & importing the necessary libraries:

The code imports several libraries to support the implementation of this neurosymbolic AI mocktail creation system. These libraries include pandas, numpy, TensorFlow, itertools, random and several models from tensorflow.keras models.

!pip install tensorflow-gpu==2.4.1
# Import required libraries
import numpy as np # linear algebra
import tensorflow as tf
import pandas as pd
import itertools
import random
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, BatchNormalization
from keras.callbacks import EarlyStopping

# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
for filename in filenames:
print(os.path.join(dirname, filename))

2. Importing the data

I have different versions of the dataset and the final version I am using is “Mocktail data v5.csv”

# Load the dataset
df = pd.read_csv('/kaggle/input/mocktail-dataset/Mocktail data v5.csv')
df.head()
  • Mocktail data v5.csv: This dataset contains the names of the mocktails, the three ingredients used, and the two flavor profiles (primary and secondary)

3. Preprocessing the data

Since this dataset has been generated by ChatGPT, there aren't a lot of preprocessing steps to be done here. However, there are a few important checks to be done like checking for any null values, checking for duplicate values, etc.

#Checking any null values
df.isna().sum()
# check for duplicates
duplicates = df.duplicated()

# print the boolean series of duplicates
print(duplicates)

4. Defining flavor profiles from user input

This code is a Python program that helps the user choose their preferred flavor profile from a list of options. The program displays the options to the user and prompts them to select their preferred option by entering a number. The program then processes the user’s choice by assigning two flavor profiles based on the chosen option. To make things a little interesting, I have made all the statements said by the Python program to be similar to that of Ryan Reynolds (courtesy of ChatGPT) and it seems pretty funny.

If the user selects option 4, the program prompts the user to enter their preferred flavor profiles. If the entered flavor profiles are not valid, the program prompts the user to enter valid flavor profiles. If the user only selects one flavor profile, the program prints the selected flavor profile. Otherwise, the program recommends complementary flavor profiles and prompts the user to confirm whether they want to accept the recommendations or not. The program then prints the preferred flavor profiles and a message acknowledging the user’s choice.

# Define possible flavor profiles
flavors = ['Sweet', 'Sour', 'Bitter', 'Refreshing', 'Creamy']

# Set complimentary flavours
complementary_flavors={
'Sweet':'Sour',
'Sour':'Sweet',
'Bitter':'Refreshing',
'Refreshing':'Bitter',
'Creamy':'Refreshing'
}

# Display options to the user
print("Hey there! We've got some amazing options for you to choose from. Take a look at these options:")
print("1. Sweet & Sour - for those who like it classic")
print("2. Creamy & Refreshing - because life's too short to be anything else")
print("3. Bitter & Refreshing - for the adventurous souls out there")
print("4. Custom - because you're a rebel, and we like that!")

# Get user's choice
choice = input("\nAlright, let's get to it. What's your poison? Enter the number of your preferred option:")

# Process user's choice
if choice == "1":
flavour_1 = "Sweet"
flavour_2 = "Sour"
print("Ah, the classic sweet and sour combo. You have a refined palate!")
preferred_flavors = [flavour_1, flavour_2]
elif choice == "2":
flavour_1 = "Creamy"
flavour_2 = "Refreshing"
print("Creamy and refreshing? You're definitely a person who knows how to keep it cool!")
preferred_flavors = [flavour_1, flavour_2]
elif choice == "3":
flavour_1 = "Bitter"
flavour_2 = "Refreshing"
print("Bitter and refreshing? You like to live dangerously, don't you?")
preferred_flavors = [flavour_1, flavour_2]
elif choice == "4":
flavour_1 = input('What is the first flavor profile would you like? (Sweet, Sour, Bitter, Refreshing, Creamy)')
if flavour_1 not in flavors:
print("That is not a valid flavour profile (atleast according to my program")
flavour_1 = input("Please enter a valid flavour profile - Sweet, Sour, Bitter, Refreshing, Creamy")

flavour_2 = input('What is the second flavor profile would you like? (Sweet, Sour, Bitter, Refreshing, Creamy) (Leave blank if you only want one flavor profile)')
if flavour_2:
if flavour_2 not in flavors:
print("That is a not valid flavour profile")
flavour_2 = input("Please enter a valid flavour profile")


if flavour_1 == flavour_2:
print("Both flavors can't be the same. Please try again.")
flavour_2 = input('Press Enter if you only want one')

# Recommend complementary flavor profile if only one flavor is sweet or sour
if flavour_1 in ['Sweet', 'Sour'] and flavour_2 not in ['Sweet', 'Sour']:
print(f"You chose {flavour_1} as your first flavor profile. We recommend {complementary_flavors[flavour_1]} as your second flavor profile.")
ans = input("Type 'Yes' if you want to this new combination - Sweet & Sour or if you type anything else, we will go ahead with your choice")
if(ans.lower() == 'yes'):
flavour_2 = complementary_flavors[flavour_1]
else:
flavour_2 = flavour_2
elif flavour_2 in ['Sweet', 'Sour'] and flavour_1 not in ['Sweet', 'Sour']:
print(f"You chose {flavour_2} as your second flavor profile. We recommend {complementary_flavors[flavour_2]} as your first flavor profile.")
ans = input("Type 'Yes' if you want to this new combination - Sweet & Sour or if you type anything else, we will go ahead with your choice")
if(ans.lower() == 'yes'):
flavour_1 = complementary_flavors[flavour_2]
else:
flavour_1 = flavour_1
elif flavour_1 in ['Bitter', 'Refreshing'] and flavour_2 not in ['Bitter', 'Refreshing']:
print(f"You chose {flavour_1} as your first flavor profile. We recommend {complementary_flavors[flavour_1]} as your second flavor profile.")
ans = input("Type 'Yes' if you want to this new combination - Bitter & Refreshing or if you type anything else, we will go ahead with your choice")
if(ans.lower() == 'yes'):
flavour_2 = complementary_flavors[flavour_2]
else:
flavour_2 = flavour_2
elif flavour_2 in ['Bitter', 'Refreshing'] and flavour_1 not in ['Bitter', 'Refreshing']:
print(f"You chose {flavour_2} as your second flavor profile. We recommend {complementary_flavors[flavour_2]} as your first flavor profile.")
ans = input("Type 'Yes' if you want to this new combination - Bitter & Resfreshing or if you type anything else, we will go ahead with your choice")
if(ans.lower() == 'yes'):
flavour_1 = complementary_flavors[flavour_2]
else:
flavour_1 = flavour_1
elif flavour_2 in ['Creamy', 'Refreshing'] and flavour_1 not in ['Creamy', 'Refreshing']:
print(f"You chose {flavour_2} as your second flavor profile. We recommend {complementary_flavors[flavour_2]} as your first flavor profile.")
ans = input("Type 'Yes' if you want to this new combination - Creamy & Refreshing or if you type anything else, we will go ahead with your choice")
if(ans.lower() == 'yes'):
flavour_1 = complementary_flavors[flavour_2]
else:
flavour_1 = flavour_1

# Print preferred flavor profiles
print(f"Your preferred flavor profiles are: {flavour_1} and {flavour_2}")
print("Custom, eh? You're the adventurous type, I like it!")

preferred_flavors = [flavour_1, flavour_2]
else:
print(f"You have chosen only {flavour_1} flavor profile.")
preferred_flavors = [flavour_1]

Example of the output:

(OPTIONAL) 5. Asking the user to rate some mocktails: In the previous project, the program used user ratings as the primary input to generate movie recommendations. However, for the current project, the approach is different. Instead of asking the user to rate the mocktails, the program is only asking for their preferred flavor profiles. This means that the program is not solely relying on user preferences to generate recommendations. Instead, it is utilizing the dataset of over 100 mocktail recipes with their respective flavor profiles and ingredients to create the combinations that match the user’s preference. This approach can help in generating more diverse and accurate mocktail recommendations for the user, as it takes into account the compatibility between different ingredients and flavor profiles, in addition to the user’s preference.

Step 2: Filter matching ingredients & Generate possible combinations

After completing the initial steps of loading the dataset and accepting the user’s preferred flavor profiles, the next crucial step in the process is to find the ingredients that match the selected flavor profiles and generate mocktail combinations that are in line with the user’s preferences.

To achieve this, we start by creating a list of all the ingredients that are present in the mocktails belonging to one of the user-selected flavor profiles. This list is compiled by analyzing the ingredients used in each mocktail belonging to the chosen flavor profile.

Once we have a comprehensive list of ingredients, we can move on to generating all possible mocktail combinations that contain three ingredients. This is done by considering every possible combination of three ingredients from the compiled ingredient list. The generation of mocktail combinations in this way ensures that we explore all possible combinations that match the selected flavor profiles, providing the user with a wide range of options to choose from.

Code

1. Filtering ingredients for preferred flavors

This code defines two functions that are used to filter a dataset of ingredients based on a user’s preferred flavor profile(s).

The has_flavor() function takes three arguments: ingredient (a string representing the name of an ingredient), flavor_profiles (a list of strings representing the user's preferred flavor profile(s)), and df (a pandas DataFrame containing ingredient data). The function first checks if the ingredient is in the dataset df by checking if the Ingredient 1 column of df contains the ingredient. If the ingredient is not in the dataset, the function returns False. If the ingredient is in the dataset, the function counts the number of times it appears in the Ingredient 1 column of df. For each row where ingredient appears in the Ingredient 1 column, the function checks if the row has one of the desired flavor profiles in either the Flavor Profile 1 or Flavor Profile 2 columns. If any row has a desired flavor profile, the function returns True. If none of the rows have a desired flavor profile, the function returns False.

The filter_by_flavor() function takes two arguments: df (a pandas DataFrame containing ingredient data) and preferred_flavors (a list of strings representing the user's preferred flavor profile(s)). The function initializes an empty set matching_ingredients to store the ingredients that match the user's preferred flavors. The function then loops through each preferred flavor in preferred_flavors and filters the dataset df to find ingredients that have the desired flavor profile using the has_flavor() function. The filtered dataset is stored in the variable filtered_df. The function then updates the matching_ingredients set to include the ingredients in the filtered dataset by using the unique() method to get the unique values of the Ingredient 1, Ingredient 2, and Ingredient 3 columns in the filtered dataset and using the set union (|=) operator to add these unique values to the matching_ingredients set. Finally, the function returns the matching_ingredients set, which contains all the ingredients that match the user's preferred flavors.

# Check if the ingredient is part of the users preferred flavour profile(s)
def has_flavor(ingredient, flavor_profiles, df):
# Check if the ingredient is in the dataset
if not df['Ingredient 1'].isin([ingredient]).any():
return False

# Get the number of ingredients in the row
num_ingredients = df.loc[df['Ingredient 1'] == ingredient].shape[0]

# Check if the ingredient has the desired flavor profile in any of its columns
for i in range(num_ingredients):
row = df.loc[df['Ingredient 1'] == ingredient].iloc[i]
if any(flavor in [row['Flavor Profile 1'], row['Flavor Profile 2']] for flavor in flavor_profiles):
return True

# If none of the ingredients have the desired flavor profile, return False
return False

# Return all ingredients with users preferred flavours
def filter_by_flavor(df, preferred_flavors):
matching_ingredients = set()
for flavor in preferred_flavors:
# Filter the dataset to find ingredients with the desired flavor profile
filtered_df = df[(df.apply(lambda x: has_flavor(x['Ingredient 1'], [flavor], df) or
has_flavor(x['Ingredient 2'], [flavor], df) or
has_flavor(x['Ingredient 3'], [flavor], df) if len(x) > 0 else False, axis=1))]
matching_ingredients |= set(filtered_df['Ingredient 1'].unique()) | set(filtered_df['Ingredient 2'].unique()) | set(filtered_df['Ingredient 3'].unique())
return matching_ingredients

2. Displaying the ingredients with either one of the required flavor profiles

This code filters a data frame of all possible ingredients by preferred flavors and then selects the first 60 matching ingredients to be used in the mocktail creation process. Unfortunately, due to the strain on computing resources, we had to limit the number of ingredients to 60 out of the possible 100. Who knew creating mocktails could be so computationally intense? But fear not, with our neurosymbolic AI system, we are still able to create delicious and unique mocktails using this limited set of ingredients!

# Filter the dataframe by preferred flavors and getting all ingredients for either flavour profile
matching_ingredients = filter_by_flavor(df, preferred_flavors)
matching_ingredients = list(matching_ingredients)[:60]
print("Matching ingredients", matching_ingredients)
print()
print("Total number of matching ingredients", len(matching_ingredients))

Example Output:

3. Generating all possible combinations of 3 ingredients for a mocktail

This code generates all possible combinations of 3 ingredients from the set of matching ingredients. It first initializes an empty list called “possible_combinations” to store all the combinations. Then, it uses a nested loop to iterate over all possible combinations of 3 ingredients from the matching ingredients.

After generating each combination, it checks if it already exists in the list “possible_combinations”. If it does not exist, it appends the combination to the list.

Next, it creates a new list called “new_list” to store all the unique combinations. It uses a for loop to iterate over each combination in “possible_combinations”. For each combination, it sorts the ingredients alphabetically and converts them into a tuple, then checks if the sorted combination already exists in “new_list”. If it does not exist, it appends the sorted combination to “new_list”.

The code then prints out all the possible combinations and the total number of all possible combinations. It is worth noting that due to limited computing resources, the code is generating combinations using only 60 ingredients.

# Generate all possible combinations of 3 ingredients from the set
possible_combinations = []

for i in range(len(matching_ingredients)):
for j in range(i+1, len(matching_ingredients)):
for k in range(j+1, len(matching_ingredients)):
combination = [matching_ingredients[i], matching_ingredients[j], matching_ingredients[k]]
if combination not in possible_combinations:
possible_combinations.append(combination)
new_list = []
for combination in possible_combinations:
sorted_combination = tuple(sorted(combination))
if sorted_combination not in new_list:
new_list.append(sorted_combination)

print()
print("All possible combinations")
print("Total number of all possible combinations", len(new_list))
print()

Example output:

There are a total of 34220 possible mocktail combinations!

Step 3: Symbolic reasoning

The symbolic reasoning component for a neurosymbolic AI system contains a set of logical rules that check the feasibility of a given combination of ingredients for a drink based on a specified flavor profile. The function uses a set of rules that incorporate domain-specific knowledge about flavor pairings and incompatibilities among ingredients, as well as constraints for certain flavor profiles. By applying these rules, the function determines whether the combination of ingredients satisfies the given criteria or not. This type of symbolic reasoning can be useful for AI systems that require domain-specific knowledge to perform tasks, such as in the case of food and beverage recommendation systems.

Code

1. A function to remove all bad & incompatible combinations of mocktails

This code defines a function called is_feasible that checks whether a given combination of ingredients is feasible based on a set of rules. The function takes two inputs: the combination of ingredients and the flavor profile of the drink. The function first checks if any of the ingredients in the combination are incompatible with each other or with the given flavor profile. If any of the rules are violated, the function returns False; otherwise, the function returns True. The rules include avoiding combinations of ingredients that may clash in flavor, avoiding combinations of ingredients that may cause unpleasant textures or curdling, and avoiding combinations of ingredients that may result in an overpowering sweetness. The function also checks whether the combination of ingredients contains specific ingredients that are not compatible with certain flavor profiles, such as 'Jalapeno' or 'Soda Water' with 'Creamy' flavor.

#This is a function that checks if a given combination of ingredients is feasible, based on a set of rules.
#The function takes two inputs: the combination of ingredients, and the flavor profile of the drink.
#The function checks if any of the ingredients in the combination are incompatible with each other or with the given flavor profile.
#If any of the rules are violated, the function returns False.
#Otherwise, the function returns True.

def is_feasible(combination, flavor_profile):
# Based on flavour profiles
if(len(flavor_profile)==1):
flavor_1 = flavor_profile[0]
else:
flavor_1 = flavor_profile[0]
flavor_2 = flavor_profile[1]

creamc = 0

if(len(flavor_profile)==1):
if(flavor_1 == "Creamy"):
if 'Jalapeno' in combination or 'Soda Water' in combination:
return False
cream = ['Yogurt', 'Coconut Milk', 'Milk', 'Coconut Cream']
for ingredient in combination:
if ingredient in cream:
creamc = creamc + 1
if(creamc<1 or creamc>1):
return False
elif(flavor_1=="Sour"):
if 'Coconut Cream' in combination:
return False
notsour = ['Yogurt', 'Cinnamon Stick', 'Orange Peel', 'Apple Cider', 'Maraschino Cherry', 'Star Anise', 'Worcestershire Sauce', 'Carrot Juice', 'Beetroot Juice', 'Passionfruit Juice', 'Spinach', 'Cola', 'Raspberries', 'Vanilla Extract', 'Cherry Syrup', 'Tabasco Sauce', 'Celery Juice', 'Honey', 'Grenadine', 'Mango']
for ingredient in combination:
if ingredient in notsour:
return False
if 'Lime Juice' not in combination or 'Lemon Juice' not in combination or 'Grapefruit juice' not in combination or 'Pomegranate juice' not in combination:
return False


if 'Sweet' in flavor_profile:
if 'Jalapeno' in combination or 'Grapefruit Juice' in combination or 'Lime Juice' in combination:
return False

if(flavor_1 == "Creamy" or flavor_2 == 'Creamy'):
if 'Jalapeno' in combination or 'Soda Water' in combination:
return False
cream = ['Yogurt', 'Coconut Milk', 'Milk', 'Coconut Cream']
for ingredient in combination:
if ingredient in cream:
creamc = creamc + 1
if(creamc<1 or creamc>1):
return False

if(len(flavor_profile)==2):
#Sweet and Sour
if(flavor_1 in ["Sweet","Sour"] and flavor_2 in ["Sweet", "Sour"]):
if 'Milk' in combination or 'Coconut Cream' in combination:
return False
if(flavor_1 in ["Creamy", "Refreshing"] and flavor_2 in ["Creamy, Refreshing"]):
if 'Grenadine' in combination and 'Milk' in combination:
return False
cream = ['Yogurt', 'Coconut Milk', 'Coconut Cream']
for ingredient in combination:
if ingredient in cream:
creamc = creamc + 1
if(creamc<1 or creamc>1):
return False

# Removing all combinations with ingredients which do not work
# Avoid using both banana and blueberries together, as their flavors may clash/they may produce an unappetizing texture.
if 'Banana' in combination and ('Blueberries' in combination or 'Raspberries' in combination):
return False

# Avoid combining maraschino cherry with lime juice, as they may not complement each other well.
if 'Maraschino Cherry' in combination and ('Lime Juice' in combination or 'Cola' in combination):
return False

# Do not use milk with any citrus-based ingredients, such as lime juice, as the acidity may cause the milk to curdle.
if 'Milk' in combination and ('Lime Juice' in combination or 'Pineapple Juice' in combination):
return False

# Avoid using both coconut cream and cola, as the sweetness and creaminess of coconut may not work well with the carbonation of cola.
if 'Coconut Cream' in combination and ('Cola' in combination or 'Simple Syrup' in combination):
return False

# Avoid using both pineapple juice or blueberries and lime juice, as their flavors may clash.
if ('Pineapple Juice' in combination or 'Blueberries' in combination) and 'Lime Juice' in combination:
return False

# Do not use both blueberries and maraschino cherry or coconut cream together, as their flavors may clash.
if 'Blueberries' in combination and ('Maraschino Cherry' in combination or 'Coconut Cream' in combination):
return False

# Avoid using both simple syrup and sugar with coconut cream, as the sweetness may become overpowering.
if 'Coconut cream' in combination and ('Simple Syrup' in combination or 'Sugar' in combination):
return False

# Avoid using both maraschino cherry/simple syrup/blueberry and sugar together, as the sweetness may become overpowering.
if ('Blueberries' in combination or 'Maraschino Cherry' in combination or'Simple Syrup' in combination)and 'Sugar' in combination:
return False

# Avoid using both cola and lime juice together, as their flavors may clash.
if ('Cola' in combination or 'Lemon' in combination) and 'Lime Juice' in combination:
return False

# Remove any combination that contains both pineapple and cranberry juice as they have strong, distinct flavors that may clash and be overpowering when combined.
if 'Pineapple Juice' in combination and 'Cranberry Juice' in combination:
return False

#Remove any combination that contains both sprite and sugar as sprite is already sweetened and adding more sugar may make the drink too sweet.
if 'Sprite' in combination and ('Club Soda' in combination or 'Sugar' in combination):
return False

# Remove any combination that includes both kiwi juice and grapefruit slice, as the flavors may not complement each other.
if ('Kiwi Juice' in combination or 'Banana' in combination or 'Mango' in combination) and 'Grapefruit Slice' in combination:
return False

# Remove any combination that includes both mango and mint leaves/blueberries, as the flavors may clash.
if 'Mango' in combination and ('Mint Leaves' in combination or 'Blackberries' in combination):
return False

# Remove any combination that includes both apple cider and coconut milk, as the flavors may clash.
if ('Apple Cider' in combination or 'Raspberry' in combination or 'Cinnamon Stick' in combination) and 'Coconut Milk' in combination:
return False

# Remove combinations that include both citrus and dairy, as they can curdle.
if ('Grapefruit' in combination or 'Lemon' in combination or 'Lime' in combination or 'Orange' in combination or 'Tangerine' in combination) and ('Milk' in combination or 'Yogurt' in combination):
return False

# Remove combinations that include both mint and cinnamon stick, as they can clash in flavor/they may produce a strange aftertaste.
if 'Mint Leaves' in combination and ('Cinnamon Stick' in combination or 'Star Anise' in combination):
return False

# Remove combinations that include both kiwi juice and apple cider, as they may produce a gritty texture.
if 'Kiwi Juice' in combination and 'Apple Cider' in combination:
return False

# No combination should contain both Sage Leaves and Maraschino Cherry.
if 'Sage Leaves' in combination and ('Maraschino Cherry' in combination or 'Tonic Water' in combination):
return False

# Avoid combining Lime Juice and Simple Syrup, Lime.
if 'Lime juice' in combination and 'Simple Syrup, Lime' in combination:
return False

# No combination should contain both Jalapeno and Raspberries.
if 'Jalapeno' in combination and 'Raspberries' in combination:
return False

# Avoid combining Maraschino Cherry or Grenadine with Lime Juice or Mint Leaves.
if ('Maraschino Cherry' in combination or 'Grenadine' in combination) and ('Lime Juice' in combination or 'Mint Leaves' in combination):
return False

#More conditions exist (On my GitHub page)...

# If none of the above conditions are met, then the combination is feasible
return True

def get_feasible_combinations(possible_combinations, preferred_flavors):
feasible_combinations = []
for combination in possible_combinations:
if is_feasible(combination, preferred_flavors):
feasible_combinations.append(combination)
return feasible_combinations


possible_combinations = get_feasible_combinations(new_list, preferred_flavors)
print()
print("Feasible Combinations")
print("Total number of all possible combinations", len(possible_combinations))
print()

Example Output:

So we finally got 3787 possible mocktails from a total of 34220 combinations. This is possible because only certain ingredients (like coconut cream, coconut milk, milk, and yogurt) can be used to create a creamy mocktail. The number is bound to be higher with other combinations without a creamy flavor profile. However, this process can be further improved and simplified with more rules or even using advanced knowledge graphs.

Step 4: Neural AI

The neural part of our neurosymbolic AI system is built using a deep learning model implemented in Keras. We define the model architecture using the Sequential API and add several densely connected layers with varying numbers of neurons and activation functions. The first layer has 256 neurons with the rectified linear unit (ReLU) activation function and takes as input the shape of our training data. We then add a dropout layer with a rate of 0.3 to reduce overfitting. We repeat this pattern for three more layers with decreasing numbers of neurons until we reach our output layer, which has a single neuron and a linear activation function.

To prevent overfitting during training, we use the early stopping technique with a patience of 10. This means that if our model’s validation loss does not improve for 10 consecutive epochs, the training will stop early.

Finally, we compile our model using the mean squared error loss function, the Adam optimizer, and the mean absolute error metric. We then train our model on our training data with a batch size of 32 and for a maximum of 100 epochs, while monitoring the validation loss for early stopping.

Code

1. Convert the dataset into the format required for neural network

This code is for preparing data for a neural network that predicts user ratings of food recipes based on their ingredients and flavor profiles. Here’s a detailed explanation:

The first few lines of code create a list of all ingredients present in the dataset. df is a pandas data frame containing information about various recipes. ingredients_1, ingredients_2, and ingredients_3 are lists of the first, second, and third ingredients in each recipe respectively. The for loops use list comprehensions to extract these values from the data frame. These three lists are concatenated into all_ingredients, which is converted to a set to remove duplicates. The code then defines a list of possible flavor profiles, including “Sour”, “Sweet”, “Bitter”, “Creamy”, and “Refreshing”.

The row_to_input() function is defined to convert a row of the data frame into an input format that can be fed into a neural network. The function takes a single argument, rowwhich is a pandas Series containing information about a single recipe. The function first initializes two empty lists, ingredients and flavors, which will be populated with the recipe's ingredients and their corresponding flavor profiles.

The function then loops through each of the first three ingredients in the recipe (if present), extracting each ingredient and checking if it is not NaN using pd.notna(). If the ingredient is present, it is added to the ingredients list. The function then checks if the ingredient is present in all_ingredients. If it is, the function creates a new data frame ingredient_df containing only those rows where the ingredient appears in one of the three ingredient columns. The function then extracts the flavor profile for the first matching row in ingredient_df and converts it to a one-hot encoding (a binary vector indicating which of the possible flavor profiles are present in the ingredient). This one-hot encoding is appended to the flavors list. If the ingredient is not present in all_ingredients, the flavors list is appended with a vector of zeros (indicating that none of the possible flavor profiles are present).

Finally, the function checks if any ingredients were added to the ingredients list. If no ingredients were added, the function returns None. Otherwise, the flavors list is flattened into a 1D numpy array and returned.

The code then splits the dataset into training and testing sets using df.sample() and df.drop(). The training set contains 80% of the rows in the original dataset, chosen randomly with a fixed seed for reproducibility.

The train_inputs variable is created by applying row_to_input() to each row of the training set using a list comprehension. The resulting list of numpy arrays is then vertically stacked into a 2D array. The train_targets variable is created by extracting the User Rating column from the training dataframe as a numpy array.

# Get a list of all ingredients    
ingredients_1 = [i for i in df['Ingredient 1']]
ingredients_2 = [j for j in df['Ingredient 2']]
ingredients_3 = [k for k in df['Ingredient 3']]
all_ingredients = ingredients_1 + ingredients_2 + ingredients_3
all_ingredients = set(all_ingredients)

# Define the list of flavors
flavors_list = ['Sour', 'Sweet', 'Bitter', 'Creamy', 'Refreshing']

# Convert the dataset input into format for neural network
def row_to_input(row):
ingredients = []
flavors = []
for i in range(1, 4):
ingredient = row[f'Ingredient {i}']
if pd.notna(ingredient):
ingredients.append(ingredient)
if ingredient in all_ingredients:
ingredient_df = df[df[['Ingredient 1', 'Ingredient 2', 'Ingredient 3']].eq(ingredient).any(1)]
flavor = ingredient_df['Flavor Profile 1'].values[0]
flavors.append([1 if f in flavor else 0 for f in flavors_list])
else:
flavors.append([0] * len(flavors_list))
if len(ingredients) == 0:
return None
else:
return np.array(flavors).flatten()

# Split the data into training and testing data sets
train_df = df.sample(frac=0.8, random_state=123)
test_df = df.drop(train_df.index)

# Convert the training and testing dataframes into TensorFlow inputs
train_inputs = [row_to_input(row) for _, row in train_df.iterrows() if row_to_input(row) is not None]
train_inputs = np.vstack(train_inputs)
train_targets = np.array(train_df['User Rating'])

2. Creating a neural network model for predicting user ratings of mocktails

This code is implementing a neural network model to predict the user rating of a mocktail based on its ingredients and flavor profiles.

First, the code defines the model architecture using the Keras Sequential model. The model consists of several Dense layers with ReLU activation functions, followed by Dropout layers to prevent overfitting. The final output layer has a linear activation function, as the goal is to predict a continuous numerical value (user rating).

The code then compiles the model, specifying the loss function to be mean squared error and the optimizer to be Adam. The model is trained on the training data using early stopping to prevent overfitting.

Next, the code evaluates the performance of the trained model on the testing data, computing the test loss.

The code then prompts the user to input the flavor profiles of a new cocktail. The neural network model is used to predict the user rating of every possible combination of three ingredients that match the flavor profiles provided by the user. The highest predicted rating is saved, along with the combination of ingredients that produced it. Finally, the code prints out the best combination of ingredients and its corresponding rating.

# Define the model architecture
model = Sequential()
model.add(Dense(256, activation='relu', input_dim=train_inputs.shape[1]))
model.add(Dropout(0.3))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='linear'))
# Define early stopping
early_stopping = EarlyStopping(monitor='val_loss', patience=10)
# Compile the model
model.compile(loss='mean_squared_error', optimizer='adam', metrics=['mae'])
# Train the model with early stopping
model.fit(train_inputs, train_targets, epochs=100, batch_size=32, validation_split=0.2, callbacks=[early_stopping])

# Evaluate the performance of the model on the testing data
test_inputs = [row_to_input(row) for _, row in test_df.iterrows() if row_to_input(row) is not None]
test_inputs = np.vstack(test_inputs)
test_targets = np.array(test_df['User Rating'])
test_loss = model.evaluate(test_inputs, test_targets)
print('Test loss:', test_loss)

# Get the flavor profiles of the new cocktail from the user
new_flavors = [flavour_1,flavour_2]

# Use neural network to predict the user rating of each possible mocktail
best_rating = 0
best_combination = possible_combinations[0]
for combination in possible_combinations:
# Convert the flavor profiles into the same format as the training data
new_input = row_to_input({'Ingredient 1': combination[0], 'Ingredient 2': combination[1], 'Ingredient 3': combination[2], 'Flavor Profile 1': new_flavors[0], 'Flavor Profile 2': new_flavors[1]})
# Pass the converted input to the trained model
rating = model.predict(np.array([new_input]))[0][0]
if rating > best_rating:
best_rating = rating
print(rating)
best_combination = combination
print(combination)

print("Best combination:", best_combination)
print("Best rating:", best_rating)

Example Output:

Step 5: Presenting the recommendation

This code is creating a mocktail recipe generator that generates a random mocktail name based on the user’s preferred flavors and available ingredients. It has three dictionaries of words: flavor_profile_adjectives, flavor_nouns, and ingredient_nouns.

flavor_profile_adjectives is a dictionary with five keys, each representing a flavor profile — Sweet, Sour, Bitter, Refreshing, and Creamy. The values are lists of adjectives that describe the flavor profile.

flavor_nouns is also a dictionary with five keys representing the flavor profiles. The values are lists of nouns that are associated with the flavor profile.

ingredient_nouns is a dictionary with ingredient names as keys and lists of associated nouns as values.

The program takes the user’s preferred flavor from a list of flavors provided as input, and the ingredients available for making the mocktail. For each ingredient, it checks if there are any associated nouns in the ingredient_nouns dictionary for the corresponding flavor profile. If there are, it adds these nouns to the flavor_nouns dictionary for that flavor profile.

It then chooses a random adjective and noun from the appropriate lists in the flavor_profile_adjectives and flavor_nouns dictionaries to create the mocktail name. Finally, it prints the mocktail name and the ingredients needed to make it.

# Naming the mocktail
flavor_profile_adjectives = {
"Sweet": ['Delicious', 'Divine', 'Heavenly', 'Luscious', 'Yummy', 'Scrumptious', 'Tasty', 'Delectable'],
"Sour": ['Tangy', 'Zesty', 'Sour', 'Puckery', 'Sharp', 'Lively', 'Fresh'],
"Bitter": ['Bold', 'Robust', 'Intense', 'Earthy', 'Smokey', 'Complex', 'Strong'],
"Refreshing": ['Cool', 'Crisp', 'Invigorating', 'Refreshing', 'Revitalizing', 'Soothing'],
"Creamy": ['Creamy', 'Smooth', 'Silky', 'Luscious', 'Velvety', 'Rich']
}

flavor_nouns = {
"Sweet": ['Bliss', 'Crush', 'Dream', 'Delight', 'Sweetheart'],
"Sour": ['Lemonade', 'Patch', 'Spark', 'Sensation', 'Tingle'],
"Bitter": ['Bite', 'Kick', 'Symphony'],
"Refreshing": ['Breeze', 'Splash', 'Chill', 'Refresher', 'Revive', 'Rejuvenation', 'Mist'],
"Creamy": ['Dream', 'Temptation', 'Refresher']
}

ingredient_nouns = {
"Lime Juice": ["Tang", "Zest", "Citrus", "Limeade"],
"Orange Juice": ["Citrus", "Orange Blossom", "Sunny Delight", "Juice"],
"Agave Syrup": ["Sweetener", "Nectar", "Cactus Syrup", "Honey"],
"Lemon Lime Soda": ["Fizz", "Citrus Bubbles", "Sprite", "Carbonated Water"],
"Grenadine": ["Pomegranate Syrup", "Sweet Red", "Ruby", "Red Syrup"],
"Maraschino Cherry": ["Red Cherry", "Sweet Cherry", "Luxardo", "Cherry Bomb"],
"Pineapple Juice": ["Tropic", "Pineapple Paradise", "Juicy Pineapple", "Pineapple Express"],
"Coconut Cream": ["Cream of Coconut", "Coconut Milk", "Coconut Heaven", "Coconut Dream"],
"Pineapple Slice": ["Pineapple Spear", "Pineapple Wedge", "Pineapple Ring", "Pineapple Garnish"],
"Mint Leaves": ["Fresh Mint", "Cool Mint", "Mint Sprig", "Minty"],
"Soda Water": ["Sparkling Water", "Bubbly", "Fizzy", "Carbonated Water"],
"Strawberries": ["Berry", "Sweetheart", "Strawberry Fields", "Red Delicious"],
"Simple Syrup": ["Sweet Syrup", "Sugar Syrup", "Clear Syrup", "Sweetener"],
"Tomato Juice": ["Tomatoey", "Savory Juice", "Bloody Mary Mix", "Spicy V8"],
"Worcestershire Sauce": ["Worcestershire", "Savory Sauce", "Umami Sauce", "Bold Sauce"],
"Tabasco Sauce": ["Hot Sauce", "Spicy Sauce", "Pepper Sauce", "Fiery Sauce"],
"Cherry Syrup": ["Cherry Juice", "Sweet Cherry", "Cherry Cola", "Cherry Bomb"],
"Cola": ["Soda", "Coke", "Classic", "Fizzy Drink"],
"Lemonade": ["Lemon Juice", "Lemon Refresher", "Citrus Cooler", "Lemon Splash"],
"Club Soda": ["Sparkling Water", "Bubbly", "Fizzy", "Carbonated Water"],
"Peach Syrup": ["Peach Juice", "Sweet Peach", "Peach Fuzz", "Peachy"],
"Peach Slice": ["Peach Wedge", "Peach Garnish", "Peachy Keen", "Peach Perfection"],
"Blackberries": ["Berry", "Blackberry Bliss", "Sweetheart", "Dark Delight"],
"Blueberries": ["Berry", "Blueberry Burst", "Sweetheart", "Blue Crush"],
"Banana": ["Banana Cream", "Banana Split", "Banana Blast", "Sweetheart"],
"Yogurt": ["Creamy", "Smoothie", "Yogurt Delight", "Yogurt Parfait"],
"Honey": ["Sweetener", "Nectar", "Liquid Gold", "Honeycomb"],
"Spinach": ["Leafy Green", "Green Goodness", "Popeye's Favorite", "Healthy Greens"],
"Cranberry Juice": ["Tart Juice", "Cranberry Blast", "Cranberry Splash", "Ruby Red"],
}

flavor_profile = preferred_flavors[0]
ingredients = best_combination

adjectives = flavor_profile_adjectives[flavor_profile]
for ingredient in ingredients:
if ingredient in ingredient_nouns:
nouns = ingredient_nouns[ingredient]
flavor_nouns[flavor_profile].extend(nouns)

adjective = random.choice(adjectives)
noun = random.choice(flavor_nouns[flavor_profile])

mocktail_name = f"{adjective} {noun} Mocktail"

if best_combination is not None:
print(f"I suggest {mocktail_name}. This is made of {best_combination[0]}, {best_combination[1]}, and {best_combination[2]}. Enjoy!")
else:
print("Sorry, we couldn't find a mocktail that satisfies your preferences.")

Example of the output:

Conclusion

In conclusion, the development of a neurosymbolic AI system for predicting the best mocktail combinations is a significant achievement in the field of artificial intelligence. By combining neural networks with symbolic reasoning, this system can filter out bad combinations and provide users with the most compatible and delicious mocktail combinations. This innovation has the potential to revolutionize the beverage industry, as well as inspire further research and development in the realm of neurosymbolic AI. Overall, the creation of this system represents a promising step towards more advanced and efficient AI systems that can make complex decisions and judgments, while still taking into account human preferences and tastes.

Discussion of future developments & improvements

Neurosymbolic AI is an emerging field that combines symbolic reasoning and neural networks to create intelligent systems capable of reasoning and learning. Although it has shown great potential in various domains, including natural language processing, robotics, and healthcare, it is still in its early phases of development. There are different methods for creating neurosymbolic AI systems, and researchers are exploring various approaches.

In my work, I have used a neurosymbolic AI approach that combines symbolic reasoning with a rule-based system and a neural network component. The symbolic reasoning component filters out bad combinations by using a set of rules based on ingredient clashes and complementarity. The neural network component is then trained only on the good combinations selected by the symbolic reasoning component. This approach allows for efficient and effective training of the neural network.

However, there is room for improvement in the symbolic reasoning component of the system. In the future, instead of using a rule-based system, a complex knowledge graph can be used to represent ingredients and flavor profiles. This would allow for a more nuanced and flexible representation of the data and could potentially improve the accuracy of the symbolic reasoning component.

Knowledge graphs are a more advanced method of representing data and relationships between data points. They are essentially a network of nodes (representing data points) and edges (representing relationships between those data points). In the context of a mocktail recommendation system, a knowledge graph might be used to represent the relationships between different ingredients and flavor profiles.

Using a knowledge graph allows for more complex and nuanced relationships between ingredients to be identified, rather than simply relying on predefined rules. For example, a knowledge graph might identify that certain types of fruit juices can enhance the sweetness of a mocktail when combined with certain herbs, even if this relationship isn’t explicitly stated in a set of rules.

References:

Image 1: https://writtygritty.com/top-15-easy-to-make-mocktail-recipes/

Types of neurosymbolic AI approaches: https://en.wikipedia.org/wiki/Neuro-symbolic_AI

--

--

No responses yet