Spaces:
Sleeping
Sleeping
import pandas as pd | |
import numpy as np | |
from sklearn.ensemble import RandomForestClassifier | |
from sklearn.preprocessing import LabelEncoder | |
from sklearn.model_selection import train_test_split, GridSearchCV | |
import gradio as gr | |
import os | |
import warnings | |
import logging | |
warnings.filterwarnings('ignore') | |
logging.basicConfig(level=logging.INFO) | |
# Dataset generation | |
np.random.seed(42) | |
moods = ['happy', 'stressed', 'bored', 'sad', 'excited', 'tired', 'anxious', 'content', 'nostalgic', 'hungry'] | |
snacks = [ | |
'fruit', 'chocolate', 'chips', 'popcorn', 'ice cream', 'pretzels', 'cookies', 'candy', | |
'yogurt', 'granola bar', 'crackers', 'veggies', 'cheese', | |
'chin chin', 'kuli kuli', 'plantain chips', 'puff puff', 'akara', 'coconut candy', | |
'kokoro', 'dodo ikire', 'roasted groundnuts', 'suya', 'boli', 'kilishi', | |
'buns', 'doughnuts', 'meat pie', 'egg rolls' | |
] | |
times_of_day = ['morning', 'afternoon', 'evening', 'midnight'] | |
snack_groups = { | |
'nigerian_fried': ['chin chin', 'puff puff', 'akara', 'buns', 'doughnuts', 'meat pie', 'egg rolls'], | |
'nigerian_savory': ['suya', 'kuli kuli', 'plantain chips', 'boli', 'kilishi', 'roasted groundnuts'], | |
'nigerian_sweet': ['coconut candy', 'dodo ikire', 'chocolate', 'candy', 'cookies', 'ice cream'], | |
'savory_snacks': ['chips', 'popcorn', 'pretzels', 'crackers', 'kokoro'], | |
'healthy_light': ['fruit', 'yogurt', 'veggies', 'granola bar', 'cheese'] | |
} | |
snack_to_group = {snack: group for group, snacks in snack_groups.items() for snack in snacks} | |
group_list = list(snack_groups.keys()) | |
mood_time_group_probs = { | |
'happy': { | |
'morning': {'nigerian_fried': 0.75, 'nigerian_sweet': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'nigerian_fried': 0.75, 'nigerian_sweet': 0.2, 'healthy_light': 0.05}, | |
'evening': {'nigerian_sweet': 0.75, 'healthy_light': 0.2, 'savory_snacks': 0.05}, | |
'midnight': {'nigerian_sweet': 0.75, 'nigerian_savory': 0.2, 'savory_snacks': 0.05} | |
}, | |
'stressed': { | |
'morning': {'nigerian_sweet': 0.75, 'nigerian_fried': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'nigerian_sweet': 0.75, 'nigerian_fried': 0.2, 'savory_snacks': 0.05}, | |
'evening': {'nigerian_sweet': 0.75, 'savory_snacks': 0.2, 'nigerian_savory': 0.05}, | |
'midnight': {'nigerian_sweet': 0.75, 'nigerian_savory': 0.2, 'savory_snacks': 0.05} | |
}, | |
'bored': { | |
'morning': {'savory_snacks': 0.75, 'nigerian_fried': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'savory_snacks': 0.75, 'nigerian_savory': 0.2, 'healthy_light': 0.05}, | |
'evening': {'savory_snacks': 0.75, 'nigerian_savory': 0.2, 'healthy_light': 0.05}, | |
'midnight': {'savory_snacks': 0.75, 'nigerian_savory': 0.2, 'healthy_light': 0.05} | |
}, | |
'sad': { | |
'morning': {'nigerian_sweet': 0.75, 'nigerian_fried': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'nigerian_sweet': 0.75, 'healthy_light': 0.2, 'nigerian_fried': 0.05}, | |
'evening': {'nigerian_sweet': 0.75, 'healthy_light': 0.2, 'savory_snacks': 0.05}, | |
'midnight': {'nigerian_sweet': 0.75, 'healthy_light': 0.2, 'nigerian_savory': 0.05} | |
}, | |
'excited': { | |
'morning': {'nigerian_fried': 0.75, 'nigerian_sweet': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'nigerian_fried': 0.75, 'nigerian_savory': 0.2, 'nigerian_sweet': 0.05}, | |
'evening': {'nigerian_sweet': 0.75, 'nigerian_savory': 0.2, 'savory_snacks': 0.05}, | |
'midnight': {'nigerian_savory': 0.75, 'nigerian_sweet': 0.2, 'savory_snacks': 0.05} | |
}, | |
'tired': { | |
'morning': {'healthy_light': 0.75, 'nigerian_fried': 0.2, 'nigerian_sweet': 0.05}, | |
'afternoon': {'healthy_light': 0.75, 'nigerian_fried': 0.2, 'savory_snacks': 0.05}, | |
'evening': {'healthy_light': 0.75, 'nigerian_sweet': 0.2, 'savory_snacks': 0.05}, | |
'midnight': {'healthy_light': 0.75, 'nigerian_savory': 0.2, 'nigerian_sweet': 0.05} | |
}, | |
'anxious': { | |
'morning': {'savory_snacks': 0.75, 'nigerian_fried': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'savory_snacks': 0.75, 'nigerian_savory': 0.2, 'healthy_light': 0.05}, | |
'evening': {'savory_snacks': 0.75, 'nigerian_savory': 0.2, 'healthy_light': 0.05}, | |
'midnight': {'savory_snacks': 0.75, 'nigerian_savory': 0.2, 'healthy_light': 0.05} | |
}, | |
'content': { | |
'morning': {'healthy_light': 0.75, 'nigerian_fried': 0.2, 'nigerian_sweet': 0.05}, | |
'afternoon': {'nigerian_savory': 0.75, 'healthy_light': 0.2, 'nigerian_fried': 0.05}, | |
'evening': {'healthy_light': 0.75, 'nigerian_sweet': 0.2, 'savory_snacks': 0.05}, | |
'midnight': {'healthy_light': 0.75, 'nigerian_savory': 0.2, 'nigerian_sweet': 0.05} | |
}, | |
'nostalgic': { | |
'morning': {'nigerian_sweet': 0.75, 'nigerian_fried': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'nigerian_sweet': 0.75, 'nigerian_fried': 0.2, 'healthy_light': 0.05}, | |
'evening': {'nigerian_sweet': 0.75, 'healthy_light': 0.2, 'savory_snacks': 0.05}, | |
'midnight': {'nigerian_sweet': 0.75, 'nigerian_savory': 0.2, 'healthy_light': 0.05} | |
}, | |
'hungry': { | |
'morning': {'nigerian_fried': 0.75, 'savory_snacks': 0.2, 'healthy_light': 0.05}, | |
'afternoon': {'nigerian_savory': 0.75, 'nigerian_fried': 0.2, 'savory_snacks': 0.05}, | |
'evening': {'nigerian_savory': 0.75, 'savory_snacks': 0.2, 'nigerian_sweet': 0.05}, | |
'midnight': {'nigerian_savory': 0.75, 'savory_snacks': 0.2, 'nigerian_sweet': 0.05} | |
} | |
} | |
n_samples = 1800 | |
data = {'mood': [], 'time_of_day': [], 'hunger_level': [], 'sentiment': [], 'snack': [], 'snack_group': []} | |
for _ in range(n_samples): | |
mood = np.random.choice(moods, p=[0.15, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.05]) | |
time = np.random.choice(times_of_day) | |
hunger_level = 1.0 if mood == 'hungry' else np.random.uniform(0, 0.8) | |
sentiment = round(np.random.uniform(-1, 1), 2) | |
group_probs = [mood_time_group_probs[str(mood)][time].get(g, 0.01) for g in group_list] | |
group = np.random.choice(group_list, p=group_probs / np.sum(group_probs)) | |
group_snacks = snack_groups[group] | |
snack_probs = [ | |
0.6 if (snack == 'suya' and time in ['evening', 'midnight']) or | |
(snack == 'boli' and time == 'afternoon') or | |
(snack in ['puff puff', 'buns', 'doughnuts', 'meat pie', 'egg rolls'] and time in ['morning', 'afternoon']) or | |
(snack == 'akara' and time in ['morning', 'midnight']) or | |
(snack == 'chin chin' and time in ['morning', 'afternoon', 'midnight']) | |
else 0.35 if snack in ['kuli kuli', 'plantain chips', 'popcorn', 'kokoro', 'roasted groundnuts', 'kilishi'] | |
else 0.2 for snack in group_snacks | |
] | |
if time not in ['evening', 'midnight'] and 'suya' in group_snacks: | |
snack_probs[group_snacks.index('suya')] = 0 | |
if time != 'afternoon' and 'boli' in group_snacks: | |
snack_probs[group_snacks.index('boli')] = 0 | |
if time not in ['morning', 'afternoon']: | |
for snack in ['puff puff', 'buns', 'doughnuts', 'meat pie', 'egg rolls']: | |
if snack in group_snacks: | |
snack_probs[group_snacks.index(snack)] = 0 | |
if time not in ['morning', 'midnight'] and 'akara' in group_snacks: | |
snack_probs[group_snacks.index('akara')] = 0 | |
snack_probs = [p / sum(snack_probs) if sum(snack_probs) > 0 else 0.2 for p in snack_probs] | |
snack = np.random.choice(group_snacks, p=snack_probs) | |
data['mood'].append(mood) | |
data['time_of_day'].append(time) | |
data['hunger_level'].append(hunger_level) | |
data['sentiment'].append(sentiment) | |
data['snack'].append(snack) | |
data['snack_group'].append(group) | |
df = pd.DataFrame(data) | |
# Adjust sentiment | |
df.loc[df['mood'].isin(['happy', 'excited', 'content', 'nostalgic']), 'sentiment'] = df.loc[ | |
df['mood'].isin(['happy', 'excited', 'content', 'nostalgic']), 'sentiment'].clip(lower=0.2) | |
df.loc[df['mood'].isin(['stressed', 'sad', 'anxious', 'tired']), 'sentiment'] = df.loc[ | |
df['mood'].isin(['stressed', 'sad', 'anxious', 'tired']), 'sentiment'].clip(upper=-0.1) | |
df.loc[df['mood'].isin(['bored', 'hungry']), 'sentiment'] = df.loc[ | |
df['mood'].isin(['bored', 'hungry']), 'sentiment'].clip(-0.3, 0.3) | |
# Add snack_type and snack_texture | |
snack_types = { | |
'chin chin': 'sweet', 'puff puff': 'sweet', 'akara': 'savory', 'suya': 'spicy', | |
'kuli kuli': 'spicy', 'plantain chips': 'savory', 'coconut candy': 'sweet', | |
'dodo ikire': 'sweet', 'roasted groundnuts': 'savory', 'fruit': 'light', | |
'yogurt': 'light', 'veggies': 'light', 'granola bar': 'light', 'cheese': 'light', | |
'chocolate': 'sweet', 'candy': 'sweet', 'cookies': 'sweet', 'ice cream': 'sweet', | |
'chips': 'savory', 'popcorn': 'savory', 'pretzels': 'savory', 'crackers': 'savory', | |
'kokoro': 'savory', 'boli': 'savory', 'kilishi': 'spicy', | |
'buns': 'sweet', 'doughnuts': 'sweet', 'meat pie': 'savory', 'egg rolls': 'savory' | |
} | |
snack_textures = { | |
'chin chin': 'crisp', 'puff puff': 'soft', 'akara': 'soft', 'suya': 'chewy', | |
'kuli kuli': 'crisp', 'plantain chips': 'crisp', 'coconut candy': 'chewy', | |
'dodo ikire': 'soft', 'roasted groundnuts': 'crisp', 'fruit': 'soft', | |
'yogurt': 'soft', 'veggies': 'crisp', 'granola bar': 'crisp', 'cheese': 'soft', | |
'chocolate': 'soft', 'candy': 'chewy', 'cookies': 'crisp', 'ice cream': 'soft', | |
'chips': 'crisp', 'popcorn': 'crisp', 'pretzels': 'crisp', 'crackers': 'crisp', | |
'kokoro': 'crisp', 'boli': 'soft', 'kilishi': 'chewy', | |
'buns': 'soft', 'doughnuts': 'soft', 'meat pie': 'soft', 'egg rolls': 'soft' | |
} | |
df['snack_type'] = df['snack'].map(snack_types) | |
df['snack_texture'] = df['snack'].map(snack_textures) | |
# Encode features | |
le_mood = LabelEncoder() | |
le_time = LabelEncoder() | |
le_type = LabelEncoder() | |
le_texture = LabelEncoder() | |
le_group = LabelEncoder() | |
df['mood_encoded'] = le_mood.fit_transform(df['mood']) | |
df['time_encoded'] = le_time.fit_transform(df['time_of_day']) | |
df['type_encoded'] = le_type.fit_transform(df['snack_type']) | |
df['texture_encoded'] = le_texture.fit_transform(df['snack_texture']) | |
df['group_encoded'] = le_group.fit_transform(df['snack_group']) | |
X = df[['mood_encoded', 'time_encoded', 'hunger_level', 'sentiment', 'type_encoded', 'texture_encoded']] | |
y = df['group_encoded'] | |
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) | |
# Train model | |
param_grid = { | |
'n_estimators': [300, 400], | |
'max_depth': [12, 15], | |
'min_samples_split': [5, 10] | |
} | |
model = RandomForestClassifier(class_weight='balanced', random_state=42) | |
grid_search = GridSearchCV(model, param_grid, cv=5, scoring='accuracy', n_jobs=-1) | |
grid_search.fit(X_train, y_train) | |
best_model = grid_search.best_estimator_ | |
# Prediction function | |
def predict_snack(mood, time_of_day, hunger_level, sentiment, snack_type): | |
mood_enc = le_mood.transform([mood])[0] | |
time_enc = le_time.transform([time_of_day])[0] | |
type_enc = le_type.transform([snack_type])[0] | |
type_to_texture = {'sweet': 'soft', 'savory': 'crisp', 'spicy': 'chewy', 'light': 'soft'} | |
texture_enc = le_texture.transform([type_to_texture[snack_type]])[0] | |
input_data = np.array([[mood_enc, time_enc, hunger_level, sentiment, type_enc, texture_enc]]) | |
pred = best_model.predict(input_data) | |
group = le_group.inverse_transform(pred)[0] | |
group_snacks = snack_groups[group] | |
snack_probs = [ | |
0.6 if (snack == 'suya' and time_of_day in ['evening', 'midnight']) or | |
(snack == 'boli' and time_of_day == 'afternoon') or | |
(snack in ['puff puff', 'buns', 'doughnuts', 'meat pie', 'egg rolls'] and time_of_day in ['morning', 'afternoon']) or | |
(snack == 'akara' and time_of_day in ['morning', 'midnight']) or | |
(snack == 'chin chin' and time_of_day in ['morning', 'afternoon', 'midnight']) | |
else 0.35 if snack in ['kuli kuli', 'plantain chips', 'popcorn', 'kokoro', 'roasted groundnuts', 'kilishi'] | |
else 0.2 for snack in group_snacks | |
] | |
if time_of_day not in ['evening', 'midnight'] and 'suya' in group_snacks: | |
snack_probs[group_snacks.index('suya')] = 0 | |
if time_of_day != 'afternoon' and 'boli' in group_snacks: | |
snack_probs[group_snacks.index('boli')] = 0 | |
if time_of_day not in ['morning', 'afternoon']: | |
for snack in ['puff puff', 'buns', 'doughnuts', 'meat pie', 'egg rolls']: | |
if snack in group_snacks: | |
snack_probs[group_snacks.index(snack)] = 0 | |
if time_of_day not in ['morning', 'midnight'] and 'akara' in group_snacks: | |
snack_probs[group_snacks.index('akara')] = 0 | |
snack_probs = [p / sum(snack_probs) if sum(snack_probs) > 0 else 0.2 for p in snack_probs] | |
snack = np.random.choice(group_snacks, p=snack_probs) | |
return f"You should try {snack}!", snack | |
# Gradio interface | |
with gr.Blocks(css=""" | |
body {background-color: #FFF8E7; font-family: 'Poppins', sans-serif;} | |
.gradio-container {max-width: 800px; margin: auto; padding: 20px;} | |
h1 {color: #4A2C2A; text-align: center; font-size: 2.5em; margin-bottom: 10px;} | |
p {color: #4A2C2A; text-align: center; font-size: 1.2em;} | |
.gr-button {background-color: #FF4500 !important; color: white !important; border-radius: 25px !important; padding: 10px 20px !important; font-weight: bold !important;} | |
.gr-button:hover {background-color: #E03C00 !important;} | |
.gr-textbox, .gr-dropdown, .gr-slider {border: 2px solid #D4A373 !important; border-radius: 10px !important; padding: 10px !important;} | |
.gr-image {border-radius: 15px; margin: auto; max-width: 200px;} | |
.footer {text-align: center; color: #808080; font-size: 0.9em; margin-top: 20px;} | |
.explanations {text-align: center; color: #4A2C2A; font-size: 0.9em; margin-top: 20px;} | |
.explanations p {margin: 5px 0; text-align: left; display: inline-block;} | |
""") as demo: | |
gr.HTML(""" | |
<div style='background: linear-gradient(to right, #D4A373, #FEE440); padding: 20px; border-radius: 15px; text-align: center;'> | |
<h1>Snack Predictor πͺ</h1> | |
<p>Tell us your vibe, and we'll find your perfect snack! Powered by ML (~97% accurate)</p> | |
<p style='font-size: 1em; margin-top: 10px;'>Select your mood, time, and preferences below, then hit "Find My Snack!" to get a tasty recommendation with a pic! π</p> | |
</div> | |
""") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
mood = gr.Dropdown( | |
choices=moods, | |
label="Mood", | |
value="happy", | |
elem_classes="gr-dropdown" | |
) | |
time_of_day = gr.Dropdown( | |
choices=times_of_day, | |
label="Time of Day", | |
value="morning", | |
elem_classes="gr-dropdown" | |
) | |
hunger_level = gr.Slider( | |
minimum=0, | |
maximum=1, | |
step=0.1, | |
label="Hunger Level (0 to 1)", | |
value=0.5, | |
elem_classes="gr-slider" | |
) | |
sentiment = gr.Slider( | |
minimum=-1, | |
maximum=1, | |
step=0.1, | |
label="Sentiment (-1 to 1)", | |
value=0.0, | |
elem_classes="gr-slider" | |
) | |
snack_type = gr.Dropdown( | |
choices=['sweet', 'savory', 'spicy', 'light'], | |
label="Snack Type", | |
value="sweet", | |
elem_classes="gr-dropdown" | |
) | |
predict_btn = gr.Button("Find My Snack!", variant="primary", elem_classes="gr-button") | |
with gr.Column(scale=1): | |
output_text = gr.Textbox(label="Your Snack Recommendation", elem_classes="gr-textbox") | |
output_image = gr.Image(label="Snack Preview", elem_classes="gr-image") | |
def predict_and_show(mood, time_of_day, hunger_level, sentiment, snack_type): | |
text, snack = predict_snack(mood, time_of_day, hunger_level, sentiment, snack_type) | |
image_path = f"assets/{snack.replace(' ', '_')}.jpeg" | |
if not os.path.exists(image_path): | |
logging.info(f"Image not found: {image_path}") | |
image_path = f"assets/{snack.replace(' ', '_')}.png" # Check for .png as fallback | |
if not os.path.exists(image_path): | |
logging.info(f"PNG fallback not found: {image_path}, using placeholder") | |
image_path = "assets/placeholder.jpeg" # Final fallback | |
if not os.path.exists(image_path): | |
logging.error(f"Placeholder not found: {image_path}") | |
return text, image_path | |
predict_btn.click( | |
fn=predict_and_show, | |
inputs=[mood, time_of_day, hunger_level, sentiment, snack_type], | |
outputs=[output_text, output_image] | |
) | |
gr.HTML(""" | |
<div class='explanations'> | |
<h3 style='color: #FF4500; font-size: 1.1em; margin-bottom: 10px;'>What do the inputs mean?</h3> | |
<p>Mood: How are you feeling right now? Pick a mood that matches your vibe.</p> | |
<p>Time of Day: What time is it? This helps pick snacks that suit the moment.</p> | |
<p>Hunger Level: How hungry are you? 0 = not hungry, 1 = starving!</p> | |
<p>Sentiment: What's your emotional vibe? -1 = feeling down, 0 = neutral, +1 = super upbeat.</p> | |
<p>Snack Type: What kind of snack do you crave? Sweet, savory, spicy, or light.</p> | |
</div> | |
<div class='footer'> | |
<p>Built with β€οΈ by @teganmosi</p> | |
<p>Follow my #WeeklyMLProjects for more! π</p> | |
</div> | |
""") | |
demo.launch() |