Spaces:
Running
Running
added chart and other details
Browse files- app.py +13 -6
- frontend/index.html +116 -6
app.py
CHANGED
@@ -2,6 +2,7 @@ from flask import Flask, request, jsonify, send_from_directory
|
|
2 |
from lyricsgenius import Genius
|
3 |
import json
|
4 |
import torch
|
|
|
5 |
import numpy as np
|
6 |
import os
|
7 |
from transformers import BertTokenizer, BertForSequenceClassification, AutoTokenizer, AutoModelForSequenceClassification
|
@@ -11,6 +12,10 @@ os.environ['TRANSFORMERS_CACHE'] = './hf_cache'
|
|
11 |
|
12 |
app = Flask(__name__)
|
13 |
|
|
|
|
|
|
|
|
|
14 |
mood_map = {
|
15 |
0: 'Angry',
|
16 |
1: 'Happy',
|
@@ -77,13 +82,15 @@ def get_prediction(iids, ams):
|
|
77 |
attention_mask=ams)
|
78 |
logits = outputs.logits.detach().numpy()
|
79 |
pred_flat = np.argmax(logits, axis=1).flatten()
|
80 |
-
|
|
|
81 |
|
82 |
def classify_lyrics(lyrics):
|
83 |
input_ids, attention_masks = tokenize_and_format([lyrics.replace('\n', ' ')])
|
84 |
-
prediction = get_prediction(input_ids, attention_masks)
|
85 |
mood = ["Angry", "Happy", "Relaxed", "Sad"][prediction]
|
86 |
-
|
|
|
87 |
|
88 |
@app.route('/')
|
89 |
def index():
|
@@ -96,9 +103,9 @@ def predict():
|
|
96 |
artist_name = data['artist']
|
97 |
success, lyrics = get_lyrics(song_title, artist_name)
|
98 |
if success:
|
99 |
-
mood = classify_lyrics(lyrics)
|
100 |
-
return jsonify({'mood': mood, 'lyrics': lyrics})
|
101 |
-
return jsonify({'mood': '-', 'lyrics': lyrics})
|
102 |
|
103 |
def get_lyrics(song_title, artist_name):
|
104 |
token = config.get('GENIUS_TOKEN')
|
|
|
2 |
from lyricsgenius import Genius
|
3 |
import json
|
4 |
import torch
|
5 |
+
import logging
|
6 |
import numpy as np
|
7 |
import os
|
8 |
from transformers import BertTokenizer, BertForSequenceClassification, AutoTokenizer, AutoModelForSequenceClassification
|
|
|
12 |
|
13 |
app = Flask(__name__)
|
14 |
|
15 |
+
# Configure Flask logging
|
16 |
+
logging.basicConfig(level=logging.INFO,
|
17 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
18 |
+
handlers=[logging.StreamHandler()])
|
19 |
mood_map = {
|
20 |
0: 'Angry',
|
21 |
1: 'Happy',
|
|
|
82 |
attention_mask=ams)
|
83 |
logits = outputs.logits.detach().numpy()
|
84 |
pred_flat = np.argmax(logits, axis=1).flatten()
|
85 |
+
probabilities = torch.softmax(outputs.logits, dim=1).tolist()[0]
|
86 |
+
return pred_flat[0], probabilities
|
87 |
|
88 |
def classify_lyrics(lyrics):
|
89 |
input_ids, attention_masks = tokenize_and_format([lyrics.replace('\n', ' ')])
|
90 |
+
prediction, probabilities = get_prediction(input_ids, attention_masks)
|
91 |
mood = ["Angry", "Happy", "Relaxed", "Sad"][prediction]
|
92 |
+
app.logger.info(f"probabilities: {probabilities}")
|
93 |
+
return mood, probabilities
|
94 |
|
95 |
@app.route('/')
|
96 |
def index():
|
|
|
103 |
artist_name = data['artist']
|
104 |
success, lyrics = get_lyrics(song_title, artist_name)
|
105 |
if success:
|
106 |
+
mood, probabilities = classify_lyrics(lyrics)
|
107 |
+
return jsonify({'mood': mood, 'lyrics': lyrics, 'probabilities': probabilities})
|
108 |
+
return jsonify({'mood': '-', 'lyrics': lyrics, 'probabilities': [0, 0, 0, 0]})
|
109 |
|
110 |
def get_lyrics(song_title, artist_name):
|
111 |
token = config.get('GENIUS_TOKEN')
|
frontend/index.html
CHANGED
@@ -16,11 +16,11 @@
|
|
16 |
justify-content: center;
|
17 |
align-items: center;
|
18 |
height: 100vh;
|
19 |
-
transition: background 1s ease-in-out; /*
|
20 |
}
|
21 |
.container {
|
22 |
background: rgba(255, 255, 255, 0.9);
|
23 |
-
padding:
|
24 |
border-radius: 20px;
|
25 |
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
26 |
max-width: 700px;
|
@@ -29,11 +29,14 @@
|
|
29 |
overflow-y: auto;
|
30 |
max-height: 90vh;
|
31 |
transition: background-color 0.5s ease-in-out;
|
|
|
|
|
|
|
32 |
}
|
33 |
h1 {
|
34 |
color: #2c3e50;
|
35 |
font-size: 2.5em;
|
36 |
-
margin-bottom:
|
37 |
font-family: 'Playfair Display', serif;
|
38 |
}
|
39 |
form {
|
@@ -72,10 +75,14 @@
|
|
72 |
transform: translateY(-2px);
|
73 |
}
|
74 |
#mood-result {
|
75 |
-
margin-top:
|
76 |
font-size: 1.5em;
|
77 |
color: #2980b9;
|
78 |
}
|
|
|
|
|
|
|
|
|
79 |
pre {
|
80 |
background: rgba(244, 244, 249, 0.9);
|
81 |
padding: 20px;
|
@@ -88,11 +95,40 @@
|
|
88 |
transition: background-color 0.5s ease-in-out;
|
89 |
}
|
90 |
#results-container {
|
91 |
-
margin-top:
|
92 |
padding: 20px;
|
93 |
border-radius: 10px;
|
94 |
transition: background-color 0.5s ease-in-out;
|
95 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
</style>
|
97 |
</head>
|
98 |
<body>
|
@@ -105,15 +141,71 @@
|
|
105 |
</form>
|
106 |
<div id="results-container">
|
107 |
<h2 id="mood-result"></h2>
|
|
|
108 |
<pre id="lyrics"></pre>
|
109 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
</div>
|
|
|
111 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
document.getElementById('mood-form').addEventListener('submit', async function(event) {
|
113 |
event.preventDefault();
|
114 |
const title = document.getElementById('title').value;
|
115 |
const artist = document.getElementById('artist').value;
|
116 |
-
|
117 |
const response = await fetch('/predict', {
|
118 |
method: 'POST',
|
119 |
headers: {
|
@@ -124,6 +216,7 @@
|
|
124 |
const data = await response.json();
|
125 |
document.getElementById('mood-result').innerText = `Predicted Mood: ${data.mood}`;
|
126 |
document.getElementById('lyrics').innerText = data.lyrics;
|
|
|
127 |
// Scroll to the top of the container to ensure the form remains visible
|
128 |
document.querySelector('.container').scrollTop = 0;
|
129 |
|
@@ -151,6 +244,23 @@
|
|
151 |
resultsContainer.style.backgroundColor = 'rgba(244, 244, 249, 0.9)'; // default background color
|
152 |
body.style.background = 'linear-gradient(135deg, #f3ec78, #af4261)'; // default gradient
|
153 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
});
|
155 |
</script>
|
156 |
</body>
|
|
|
16 |
justify-content: center;
|
17 |
align-items: center;
|
18 |
height: 100vh;
|
19 |
+
transition: background 1s ease-in-out; /* Smooth gradient transition */
|
20 |
}
|
21 |
.container {
|
22 |
background: rgba(255, 255, 255, 0.9);
|
23 |
+
padding: 30px;
|
24 |
border-radius: 20px;
|
25 |
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
26 |
max-width: 700px;
|
|
|
29 |
overflow-y: auto;
|
30 |
max-height: 90vh;
|
31 |
transition: background-color 0.5s ease-in-out;
|
32 |
+
display: flex;
|
33 |
+
flex-direction: column;
|
34 |
+
gap: 20px;
|
35 |
}
|
36 |
h1 {
|
37 |
color: #2c3e50;
|
38 |
font-size: 2.5em;
|
39 |
+
margin-bottom: 20px;
|
40 |
font-family: 'Playfair Display', serif;
|
41 |
}
|
42 |
form {
|
|
|
75 |
transform: translateY(-2px);
|
76 |
}
|
77 |
#mood-result {
|
78 |
+
margin-top: 20px;
|
79 |
font-size: 1.5em;
|
80 |
color: #2980b9;
|
81 |
}
|
82 |
+
#mood-chart {
|
83 |
+
max-width: 100%;
|
84 |
+
height: 300px;
|
85 |
+
}
|
86 |
pre {
|
87 |
background: rgba(244, 244, 249, 0.9);
|
88 |
padding: 20px;
|
|
|
95 |
transition: background-color 0.5s ease-in-out;
|
96 |
}
|
97 |
#results-container {
|
98 |
+
margin-top: 20px;
|
99 |
padding: 20px;
|
100 |
border-radius: 10px;
|
101 |
transition: background-color 0.5s ease-in-out;
|
102 |
}
|
103 |
+
.footer {
|
104 |
+
margin-top: 20px;
|
105 |
+
font-size: 0.9em;
|
106 |
+
color: #555;
|
107 |
+
text-align: center;
|
108 |
+
}
|
109 |
+
#more-info {
|
110 |
+
display: none;
|
111 |
+
max-height: 0;
|
112 |
+
overflow: hidden;
|
113 |
+
transition: max-height 0.5s ease-out, opacity 0.5s ease-out;
|
114 |
+
opacity: 0;
|
115 |
+
text-align: left;
|
116 |
+
font-size: 0.9em;
|
117 |
+
color: #333;
|
118 |
+
padding: 0;
|
119 |
+
border-radius: 10px;
|
120 |
+
background: rgba(244, 244, 249, 0.9);
|
121 |
+
}
|
122 |
+
#more-info.show {
|
123 |
+
display: block;
|
124 |
+
max-height: 500px; /* Adjust as needed */
|
125 |
+
opacity: 1;
|
126 |
+
}
|
127 |
+
#more-info-link {
|
128 |
+
color: #333; /* Match the rest of the text color */
|
129 |
+
cursor: pointer;
|
130 |
+
text-decoration: underline;
|
131 |
+
}
|
132 |
</style>
|
133 |
</head>
|
134 |
<body>
|
|
|
141 |
</form>
|
142 |
<div id="results-container">
|
143 |
<h2 id="mood-result"></h2>
|
144 |
+
<canvas id="mood-chart"></canvas>
|
145 |
<pre id="lyrics"></pre>
|
146 |
</div>
|
147 |
+
<div class="footer">
|
148 |
+
This app analyzes song lyrics to predict their mood. Enter a song title and artist, and get mood predictions along with the lyrics. Deployed using Hugging Face Spaces.
|
149 |
+
<br><br>
|
150 |
+
<a href="#" id="more-info-link">Click here to learn more about how this web app works</a>
|
151 |
+
<div id="more-info">
|
152 |
+
<p><strong>How it Works:</strong></p>
|
153 |
+
<p>This web app utilizes a fine-tuned BERT model to analyze the mood of song lyrics. Upon entering a song title and artist, the app retrieves the song's lyrics from Genius, processes them through the BERT model, and predicts the mood.</p>
|
154 |
+
<p><strong>Technology:</strong></p>
|
155 |
+
<ul>
|
156 |
+
<li><strong>Model:</strong> BERT (Bidirectional Encoder Representations from Transformers) fine-tuned on song lyrics to classify moods.</li>
|
157 |
+
<li><strong>Framework:</strong> PyTorch and Hugging Face Transformers library for model training and prediction.</li>
|
158 |
+
<li><strong>Deployment:</strong> The web app is hosted and deployed using Hugging Face Spaces, which provides a simple interface for serving machine learning models.</li>
|
159 |
+
</ul>
|
160 |
+
</div>
|
161 |
+
</div>
|
162 |
</div>
|
163 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
164 |
<script>
|
165 |
+
// Initialize the chart with zero probabilities
|
166 |
+
const ctx = document.getElementById('mood-chart').getContext('2d');
|
167 |
+
let moodChart = new Chart(ctx, {
|
168 |
+
type: 'bar',
|
169 |
+
data: {
|
170 |
+
labels: ['Angry', 'Happy', 'Relaxed', 'Sad'],
|
171 |
+
datasets: [{
|
172 |
+
label: 'Mood Probabilities',
|
173 |
+
data: [0, 0, 0, 0], // Initial data
|
174 |
+
backgroundColor: [
|
175 |
+
'rgba(255, 99, 132, 0.2)',
|
176 |
+
'rgba(255, 159, 64, 0.2)',
|
177 |
+
'rgba(75, 192, 192, 0.2)',
|
178 |
+
'rgba(153, 102, 255, 0.2)'
|
179 |
+
],
|
180 |
+
borderColor: [
|
181 |
+
'rgba(255, 99, 132, 1)',
|
182 |
+
'rgba(255, 159, 64, 1)',
|
183 |
+
'rgba(75, 192, 192, 1)',
|
184 |
+
'rgba(153, 102, 255, 1)'
|
185 |
+
],
|
186 |
+
borderWidth: 1
|
187 |
+
}]
|
188 |
+
},
|
189 |
+
options: {
|
190 |
+
scales: {
|
191 |
+
y: {
|
192 |
+
beginAtZero: true,
|
193 |
+
min: 0,
|
194 |
+
max: 1,
|
195 |
+
ticks: {
|
196 |
+
stepSize: 0.1,
|
197 |
+
callback: function(value) { return value.toFixed(1); } // Format y-axis labels
|
198 |
+
}
|
199 |
+
}
|
200 |
+
}
|
201 |
+
}
|
202 |
+
});
|
203 |
+
|
204 |
document.getElementById('mood-form').addEventListener('submit', async function(event) {
|
205 |
event.preventDefault();
|
206 |
const title = document.getElementById('title').value;
|
207 |
const artist = document.getElementById('artist').value;
|
208 |
+
|
209 |
const response = await fetch('/predict', {
|
210 |
method: 'POST',
|
211 |
headers: {
|
|
|
216 |
const data = await response.json();
|
217 |
document.getElementById('mood-result').innerText = `Predicted Mood: ${data.mood}`;
|
218 |
document.getElementById('lyrics').innerText = data.lyrics;
|
219 |
+
|
220 |
// Scroll to the top of the container to ensure the form remains visible
|
221 |
document.querySelector('.container').scrollTop = 0;
|
222 |
|
|
|
244 |
resultsContainer.style.backgroundColor = 'rgba(244, 244, 249, 0.9)'; // default background color
|
245 |
body.style.background = 'linear-gradient(135deg, #f3ec78, #af4261)'; // default gradient
|
246 |
}
|
247 |
+
|
248 |
+
// Update chart with new data
|
249 |
+
moodChart.data.datasets[0].data = data.probabilities || [0, 0, 0, 0];
|
250 |
+
moodChart.update();
|
251 |
+
});
|
252 |
+
|
253 |
+
// Toggle detailed description with smooth effect
|
254 |
+
document.getElementById('more-info-link').addEventListener('click', function(event) {
|
255 |
+
event.preventDefault();
|
256 |
+
const moreInfo = document.getElementById('more-info');
|
257 |
+
if (moreInfo.style.display === 'none' || !moreInfo.style.display) {
|
258 |
+
moreInfo.style.display = 'block';
|
259 |
+
setTimeout(() => moreInfo.classList.add('show'), 10);
|
260 |
+
} else {
|
261 |
+
moreInfo.classList.remove('show');
|
262 |
+
setTimeout(() => moreInfo.style.display = 'none', 500);
|
263 |
+
}
|
264 |
});
|
265 |
</script>
|
266 |
</body>
|