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() |