├── Aura Pipeline.ipynb ├── app.py ├── backend_functions.py ├── requirements.txt └── song_dataset.csv /Aura Pipeline.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "8fd6dc57", 6 | "metadata": {}, 7 | "source": [ 8 | "### Step 1: Import Required Libraries" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "2f54d702", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stderr", 19 | "output_type": "stream", 20 | "text": [ 21 | "f:\\BROTOTYPE WEEK TASKS\\WEEK 31\\Project 22 - AURA\\auraenv\\lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", 22 | " from .autonotebook import tqdm as notebook_tqdm\n" 23 | ] 24 | } 25 | ], 26 | "source": [ 27 | "import re\n", 28 | "import torch # for PyTorch model operations\n", 29 | "import pandas as pd\n", 30 | "import random\n", 31 | "from transformers import AutoTokenizer, AutoModelForSequenceClassification\n", 32 | "from sentence_transformers import SentenceTransformer\n", 33 | "from sklearn.metrics.pairwise import cosine_similarity\n", 34 | "import torch.nn.functional as F" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "36a15bb7", 40 | "metadata": {}, 41 | "source": [ 42 | "### Step 2: Load the Models" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "9066d23a", 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "name": "stderr", 53 | "output_type": "stream", 54 | "text": [ 55 | "f:\\BROTOTYPE WEEK TASKS\\WEEK 31\\Project 22 - AURA\\auraenv\\lib\\site-packages\\huggingface_hub\\file_download.py:896: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", 56 | " warnings.warn(\n" 57 | ] 58 | } 59 | ], 60 | "source": [ 61 | "# Load DistilBert model and tokenizer from local path\n", 62 | "model_id = \"joeddav/distilbert-base-uncased-go-emotions-student\"\n", 63 | "\n", 64 | "# Load tokenizer from local path\n", 65 | "tokenizer = AutoTokenizer.from_pretrained(model_id)\n", 66 | "\n", 67 | "# Load model from local path\n", 68 | "model = AutoModelForSequenceClassification.from_pretrained(model_id)\n", 69 | "\n", 70 | "# Load embedding (vectorizing) model from HuggingFace (online)\n", 71 | "embedder = SentenceTransformer('all-mpnet-base-v2')\n", 72 | "\n", 73 | "# Get id2label mapping from model config\n", 74 | "id2label = model.config.id2label\n" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "e3575b3c", 80 | "metadata": {}, 81 | "source": [ 82 | "### Step 3: Text Preprocessing [User Input]" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 3, 88 | "id": "0aa61462", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "def preprocess_text(text):\n", 93 | " # Lowercase\n", 94 | " text = text.lower()\n", 95 | " # Keep only lowercase alphabets and spaces\n", 96 | " text = re.sub(r\"[^a-z\\s]\",'',text)\n", 97 | " # Remove extra spaces\n", 98 | " text = re.sub(r'\\s+',' ',text).strip()\n", 99 | " return text" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "id": "fe94e205", 105 | "metadata": {}, 106 | "source": [ 107 | "### Step 4: Predict Emotions from User Input" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "id": "e0e2ac62", 113 | "metadata": {}, 114 | "source": [ 115 | "Multi-label classification: sigmoid + thresholding" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "id": "8163b029", 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "def predict_emotions(text, threshold=0.7):\n", 126 | " # tokenize input\n", 127 | " # return_tensors='pt' : return pytorch tensor instead of python lists or dictionaries\n", 128 | " # truncation to cut off length of input length if it exeeds maximum length\n", 129 | " inputs = tokenizer(text,return_tensors='pt',truncation=True)\n", 130 | " # pass through model\n", 131 | " with torch.no_grad():\n", 132 | " outputs = model(**inputs)\n", 133 | " probs = F.softmax(outputs.logits, dim=1).squeeze()\n", 134 | " # take probabilities using sigmoid\n", 135 | " # outputs is the predictions (numbers) from the models and ouputs.logits contains the raw predictions\n", 136 | "\n", 137 | " # select emotions above threshold\n", 138 | " raw_emotions = [ id2label[i] for i , p in enumerate(probs) if p >= threshold ]\n", 139 | " print('\\nRaw Emotions:',raw_emotions)\n", 140 | " \n", 141 | " # Mapping model emotions to song emotion tags\n", 142 | " emotion_map = {\n", 143 | " \"sadness\": \"Sadness 😢\",\n", 144 | " \"disappointment\": \"Sadness 😢\",\n", 145 | " \"grief\": \"Sadness 😢\",\n", 146 | " \"remorse\": \"Sadness 😢\",\n", 147 | "\n", 148 | " \"optimism\": \"Hope ✨\",\n", 149 | " \"gratitude\": \"Hope ✨\",\n", 150 | "\n", 151 | " \"desire\": \"Motivation 🔥\",\n", 152 | " \n", 153 | " \"annoyance\": \"Stress 😣\",\n", 154 | " \"confusion\": \"Stress 😣\",\n", 155 | " \"disapproval\": \"Stress 😣\",\n", 156 | " \"embarrassment\": \"Stress 😣\",\n", 157 | "\n", 158 | " \"fear\": \"Anxiety 😰\",\n", 159 | "\n", 160 | " \"nervousness\": \"Nervousness 😬\",\n", 161 | "\n", 162 | " \"joy\": \"Joy 😄\",\n", 163 | " \"amusement\": \"Joy 😄\",\n", 164 | "\n", 165 | " \"excitement\": \"Excitement 🤩\",\n", 166 | " \"surprise\": \"Excitement 🤩\",\n", 167 | "\n", 168 | " \"love\": \"Love ❤️\",\n", 169 | "\n", 170 | " \"pride\": \"Pride 🏅\",\n", 171 | " \"admiration\": \"Pride 🏅\",\n", 172 | "\n", 173 | " \"relief\": \"Calm 😌\",\n", 174 | " \"neutral\": \"Calm 😌\",\n", 175 | "\n", 176 | " \"curiosity\": \"Curiosity 🤔\",\n", 177 | "\n", 178 | " \"anger\": \"Anger 😡\",\n", 179 | " \"disgust\": \"Anger 😡\",\n", 180 | "\n", 181 | " \"approval\": \"Confidence 💪\",\n", 182 | " \"realization\": \"Confidence 💪\",\n", 183 | "\n", 184 | " \"caring\": \"Caring 🤗\"\n", 185 | " } \n", 186 | "\n", 187 | " mapped_emotions = [emotion_map[emotion] for emotion in raw_emotions if emotion in emotion_map]\n", 188 | " return mapped_emotions\n", 189 | " " 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "id": "a02f1ad0", 195 | "metadata": {}, 196 | "source": [ 197 | "### Step 5: Load and Prepare Song Dataset" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 5, 203 | "id": "49b4c76c", 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "def load_song_dataset(file_path='song_dataset.csv'):\n", 208 | " songs_df = pd.read_csv(file_path)\n", 209 | " songs_df['Emotion_Tags'] = songs_df['Emotion_Tags'].apply(eval) # safely evaluate string to list\n", 210 | " return songs_df" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "id": "ce13888c", 216 | "metadata": {}, 217 | "source": [ 218 | "### Step 6: Match Predicted Emotions to Best Song (Cosine Similarity)" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": 6, 224 | "id": "90120e9c", 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [ 228 | "def find_best_song(predicted_emotions, songs_df):\n", 229 | " print(\"Predicted Emotions:\", predicted_emotions)\n", 230 | "\n", 231 | " # Encode all predicted emotions individually\n", 232 | " input_embs = embedder.encode(predicted_emotions)\n", 233 | "\n", 234 | " song_scores = [] # list to hold (row, score) pairs\n", 235 | " \n", 236 | " for _, row in songs_df.iterrows():\n", 237 | " song_emotions = row['Emotion_Tags'].split(',') \n", 238 | " song_embs = embedder.encode(song_emotions) #encode song emotions into embedding\n", 239 | " \n", 240 | " # Compute average similarity between all pairs of (predicted_emotion, song_emotion)\n", 241 | " total_score = 0\n", 242 | " count = 0\n", 243 | "\n", 244 | " for input_emb in input_embs:\n", 245 | " for song_emb in song_embs:\n", 246 | " # calculate similarity using cosine similarity\n", 247 | " score = cosine_similarity([input_emb], [song_emb])[0][0]\n", 248 | " total_score += score\n", 249 | " count += 1\n", 250 | "\n", 251 | " avg_score = total_score / count if count > 0 else 0\n", 252 | "\n", 253 | " song_scores.append((row, avg_score))\n", 254 | " print(f\"{row['Emotion_Tags']} → Score: {avg_score:.3f}\")\n", 255 | "\n", 256 | " # Sort songs by average similarity score\n", 257 | " song_scores.sort(key=lambda x: x[1], reverse=True)\n", 258 | "\n", 259 | " # Take top 2 songs\n", 260 | " top_songs = song_scores[:2]\n", 261 | "\n", 262 | " # Randomly select one of the top 2\n", 263 | " if top_songs:\n", 264 | " selected_row, selected_score = random.choice(top_songs)\n", 265 | " return selected_row\n", 266 | " else:\n", 267 | " return None" 268 | ] 269 | } 270 | ], 271 | "metadata": { 272 | "kernelspec": { 273 | "display_name": "auraenv", 274 | "language": "python", 275 | "name": "python3" 276 | }, 277 | "language_info": { 278 | "codemirror_mode": { 279 | "name": "ipython", 280 | "version": 3 281 | }, 282 | "file_extension": ".py", 283 | "mimetype": "text/x-python", 284 | "name": "python", 285 | "nbconvert_exporter": "python", 286 | "pygments_lexer": "ipython3", 287 | "version": "3.10.0" 288 | } 289 | }, 290 | "nbformat": 4, 291 | "nbformat_minor": 5 292 | } 293 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from backend_functions import * # import all functions from file 3 | import time # for typing animation 4 | 5 | 6 | # configure basic page settings 7 | st.set_page_config(page_title="AURA", page_icon="🌌") 8 | 9 | # Add the description to the sidebar 10 | st.sidebar.header("Welcome to AURA 🌌🤖") 11 | aura_description = """ 12 | **Meet AURA – Your Emotion-Responsive Music Assistant** 13 | 14 | AURA is here to understand how you're feeling, just by reading your messages. Whether you're anxious, happy, or feeling down. 15 | 16 | Once AURA knows how you're feeling, it suggests the perfect song 🎶 to match your mood. Whether you need something calming, motivating 💪, or uplifting, AURA instantly provides a song that fits your emotions. 17 | 18 | So if you need to relax, simply vibe to the right tune, AURA has you covered with the perfect soundtrack for your emotions. 19 | """ 20 | 21 | # Display the description in the sidebar 22 | st.sidebar.write(aura_description) 23 | 24 | # Inject custom CSS to remove the toggle button and center the content 25 | st.markdown( 26 | """ 27 | 51 | """, unsafe_allow_html=True 52 | ) 53 | 54 | 55 | # Create the centered container using custom CSS class 56 | with st.container(): 57 | st.markdown('