This is a project to predict the valence of songs in a dataset of 232,725 Spotify songs. Primarily, the goal was to build a machine learning model using PyTorch and deploy it to production as an API using Flask; the dataset just provided an interesting problem to solve in the process!
You might remember "valence" from high school chemistry. It has to do with how many electrons an atom will lose, gain, or share when it joins with another atom. Psychologists put a spin on that concept, using the word "valence" to describe whether something is likely to make someone feel happy (positive valence) or sad (negative valence).
The dataset, in addition to identifying the songs, artists, etc, contains values measuring their key, tempo, time signature, and so on, and includes a roughly equal number of around 10,000 songs per genre. Here are some example songs from the dataset, without any preprocessing:
genre | artist_name | track_name | track_id | popularity | acousticness | danceability | duration_ms | energy | instrumentalness | key | liveness | loudness | mode | speechiness | tempo | time_signature | valence |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Electronic | BT | Flaming June | 3zdsnLKSspBgqoBKowL6cJ | 29 | 0.0596 | 0.454 | 258333 | 0.814 | 0.0447 | F# | 0.109 | -4.099 | Minor | 0.0546 | 137.964 | 4/4 | 0.168 |
Rock | Pink Floyd | Eclipse | 1tDWVeCR9oWGX8d5J9rswk | 62 | 0.0591 | 0.359 | 130429 | 0.579 | 0.746 | A# | 0.0686 | -10.765 | Major | 0.0406 | 68.102 | 4/4 | 0.135 |
Pop | Justin Bieber | Baby | 6epn3r7S14KUqlReYr77hA | 74 | 0.0544 | 0.656 | 214240 | 0.841 | 0 | F | 0.122 | -5.183 | Minor | 0.232 | 65.024 | 4/4 | 0.522 |
After preprocessing the data (e.g. scaling and encoding the features as needed), it is fed into two models, one regression and one classification, both with the goal of learning to determine the valence of the given song(s).
Actually, before getting to the results, I should point out that the accuracy of the models was fairly predictable with only one line of code, simplified below:
$ print(data_frame.corr())
Danceability Energy Loudness Valence
Danceability 1.000000 0.325807 0.438668 0.547154
Energy 0.325807 1.000000 0.816088 0.436771
Loudness 0.438668 0.816088 1.000000 0.399901
Valence 0.547154 0.436771 0.399901 1.000000
And here's a more pretty visualisation using Matplotlib and Seaborn, showing all features except for the categorical ones (Genre, Key, Mode and Time Signature):
As you can see, danceability, energy, and loudness are really the only three features that have a somewhat-significant positive linear correlation to valence, as shown by the pairwise correlation coefficients above. And even then the correlations aren't particularly strong. So even before starting on the models I wasn't all that optimistic of achieving great results.
Surprisingly, the regression model performed quite well: training after only one epoch results in an average Mean Squared Error of 0.036 and a Mean Absolute Error of 0.149. The latter number means that after training, when evaluating the test data set the model's predicted valence of a song has on average an absolute difference of 14.9% from the actual valence. Again, that's not incredible, but given the subjectivity of valence, and the fact that none of the features are highly correlated to it, it's pretty good.
Developed with Python version 3.8.2.
See dependencies.txt for packages and versions (and below to install).
Clone the Git repo.
Install the dependencies:
$ pip install -r dependencies.txt
Download the input data file into the data/spotify_features.csv
path.
At its simplest, the project can be run with sensible defaults by simply running:
$ python musical_valence_predictor.py
In addition, you can override the defaults by specifying the model type, number of training epochs, batch size, and so on. To view the supported arguments, run:
$ python musical_valence_predictor.py --help
usage: musical_valence_predictor.py [-h] [--model {regression,classification}] [--epochs EPOCHS] [--batch-size BATCH_SIZE] [--skip-training] [--print-batch]
PyTorch machine learning model to predict valence of a song based on musical characteristics, e.g. tempo, key, etc.
optional arguments:
-h, --help show this help message and exit
--model {regression,classification}
Which model to run (regression or classification). Defaults to regression.
--epochs EPOCHS Number of times that the training process will run through the training data set.
--batch-size BATCH_SIZE
Number of examples from the training data set used per training iteration.
--skip-training Skip the training of the model and load a pre-trained one (model trains by default).
--print-batch Print a sample mini-batch from the training data set.
The models can be deployed easily via TorchServe, making use of its inbuilt logging and metrics features. Note that at the time of writing, TorchServe is extremely new, and as such is subject to change.
First, package the artifacts of at least one of the models into a model archive (.mar
) file with torch-model-archiver
(these files are in the .gitignore
file, so make sure to do a training run first):
$ torch-model-archiver --model-name regression_model --version 1.0 --model-file musical_valence_predictor/models/regression_model.py --serialized-file models/RegressionModel.pth --export-path models/ --archive-format default
Then start the server:
$ torchserve --start --foreground --no-config-snapshots --model-store models/ --models regression=regression_model.mar
Note the use of the --foreground
argument. Remove it to start the server in the background, in which case you will want to use torchserve --stop
to stop it afterwards.
You can now use TorchServe's inference API. You can confirm this with the following command:
$ curl -X OPTIONS http://localhost:8080
And the models should be available on the following URL (for the regression model, as an example):
$ curl -X POST http://localhost:8080/predictions/regression
You don't have to use the command line; the project wraps the regression model in a JSON API built with Flask, allowing for easy deployment. To start the server in development mode, run:
$ FLASK_ENV=development FLASK_APP=musical_valence_predictor flask run
Then query the API:
$ curl -v -H "Accept: application/json" "http://localhost:5000/predict?artist_name=BT&track_name=Yahweh"
< HTTP/1.0 200 OK
< Content-Type: application/json
<
{
"expected": 0.36,
"prediction": 0.34
}
The dataset used in this project was obtained from Zaheen Hamidani, who in turn generated it using this tool by Tomi Gelo. The data itself was ultimately sourced from Spotify's API.