Skip to content

FlamingoLindo/spotify-svm

Repository files navigation

PT-BR 🇧🇷 | ENG 🇺🇸


Análise de Popularidade e Classificação de Gêneros Musicais Brasileiros Usando Inteligência Artificial

O primeiro passo é importar as bibliotecas necessárias para o funcionamento do projeto, são elas:

import sys
import pandas as pd
import numpy as np
import seaborn as sns
import sklearn
import matplotlib
 
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, ConfusionMatrixDisplay
from sklearn.inspection import permutation_importance
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
from sklearn.inspection import permutation_importance

Versões das bibliotecas e python que foram utilizadas na execução do projeto:

  • Python 3.12.3

  • Pandas 2.2.2

  • Numpy 1.26.4

  • Seaborn 0.13.2

  • Matplotlib 3.9..0

  • Sklearn 1.5.2

Em seguida foi importado o dataset 🎹 Spotify Tracks Dataset

file_path = 'https://raw.githubusercontent.com/FlamingoLindo/spotify-svm/main/spotify.csv'
df = pd.read_csv(file_path)
df.head()

O dataset é composto pelas seguintes colunas:

Coluna Descrição
track_id O ID da faixa no Spotify.
artists Os nomes dos artistas que performaram a faixa. Se houver mais de um artista, eles são separados por ponto e vírgula (;).
album_name O nome do álbum no qual a faixa aparece.
track_name O nome da faixa.
popularity Um valor entre 0 e 100 que indica a popularidade da faixa, sendo 100 a mais popular. Calculado algoritmicamente com base em fatores como o número total de execuções e a contagem de execuções recentes. Faixas duplicadas (por exemplo, a mesma faixa de um single e de um álbum) são avaliadas independentemente. A popularidade de artistas e álbuns é derivada matematicamente da popularidade das faixas.
duration_ms A duração da faixa em milissegundos.
explicit Indica se a faixa possui letras explícitas (true = sim; false = não ou desconhecido).
danceability Uma medida de 0.0 a 1.0 que indica a adequação da faixa para dançar, baseada em elementos como tempo, estabilidade do ritmo, força da batida e regularidade. Valores maiores indicam maior dançabilidade.
energy Uma medida de 0.0 a 1.0 que representa a intensidade e atividade da faixa. Faixas com alta energia costumam parecer rápidas, altas e ruidosas. Por exemplo, death metal tem alta energia, enquanto um prelúdio de Bach tem baixa energia.
key A tonalidade da faixa, representada por um número inteiro. Utiliza a notação de Classe de Altura: 0 = Dó, 1 = Dó♯/Ré♭, 2 = Ré, etc. Se nenhuma tonalidade for detectada, o valor é -1.
loudness A intensidade geral da faixa em decibéis (dB).
mode A modalidade da escala da faixa. 1 representa maior e 0 representa menor.
speechiness Mede a presença de palavras faladas. Valores próximos a 1.0 indicam gravações majoritariamente faladas (ex: audiolivros). Entre 0.33 e 0.66 sugere músicas com discurso (ex: rap), enquanto abaixo de 0.33 representa, em sua maioria, música.
acousticness Medida de confiança de 0.0 a 1.0 sobre se a faixa é acústica, com 1.0 representando alta confiança de que é acústica.
instrumentalness Prediz se uma faixa contém ou não vocais. Valores mais próximos de 1.0 indicam maior probabilidade de não ter vocais. Sons de "ooh" e "aah" são considerados instrumentais, mas faixas de rap ou fala são vocais.
liveness Detecta a presença de uma audiência. Valores altos aumentam a probabilidade de que a faixa foi gravada ao vivo, com valores acima de 0.8 indicando forte probabilidade de gravação ao vivo.
valence Uma medida de 0.0 a 1.0 da positividade musical da faixa. Valores altos transmitem emoções mais positivas (ex: feliz, alegre), enquanto valores baixos indicam emoções mais negativas (ex: triste, irritado).
tempo O tempo estimado em batidas por minuto (BPM), refletindo a velocidade ou ritmo da faixa.
time_signature Uma assinatura de tempo estimada, variando de 3 a 7, indicando métricas como 3/4 até 7/4.
track_genre O gênero ao qual a faixa pertence.

Então foi feito uma análise de valores únicos da coluna track_genre e esse foi o resultado:

array(['acoustic', 'afrobeat', 'alt-rock', 'alternative', 'ambient',
       'anime', 'black-metal', 'bluegrass', 'blues', 'brazil',
       'breakbeat', 'british', 'cantopop', 'chicago-house', 'children',
       'chill', 'classical', 'club', 'comedy', 'country', 'dance',
       'dancehall', 'death-metal', 'deep-house', 'detroit-techno',
       'disco', 'disney', 'drum-and-bass', 'dub', 'dubstep', 'edm',
       'electro', 'electronic', 'emo', 'folk', 'forro', 'french', 'funk',
       'garage', 'german', 'gospel', 'goth', 'grindcore', 'groove',
       'grunge', 'guitar', 'happy', 'hard-rock', 'hardcore', 'hardstyle',
       'heavy-metal', 'hip-hop', 'honky-tonk', 'house', 'idm', 'indian',
       'indie-pop', 'indie', 'industrial', 'iranian', 'j-dance', 'j-idol',
       'j-pop', 'j-rock', 'jazz', 'k-pop', 'kids', 'latin', 'latino',
       'malay', 'mandopop', 'metal', 'metalcore', 'minimal-techno', 'mpb',
       'new-age', 'opera', 'pagode', 'party', 'piano', 'pop-film', 'pop',
       'power-pop', 'progressive-house', 'psych-rock', 'punk-rock',
       'punk', 'r-n-b', 'reggae', 'reggaeton', 'rock-n-roll', 'rock',
       'rockabilly', 'romance', 'sad', 'salsa', 'samba', 'sertanejo',
       'show-tunes', 'singer-songwriter', 'ska', 'sleep', 'songwriter',
       'soul', 'spanish', 'study', 'swedish', 'synth-pop', 'tango',
       'techno', 'trance', 'trip-hop', 'turkish', 'world-music'],
      dtype=object)

Após à análise foi observado que haviam diversos tipos de gêneros musicais dentro desse dataset, então optamos por apenas utilizar os gêneros brasileiros: brazil, mpb, pagode, samba e sertanejo.

df = df[(df['track_genre'].isin(['brazil','mpb','pagode','samba','sertanejo']))]

Então é criado um novo dataframe chamdo train_df, para que todas as alterações seguintes sejam feitas nele ao invés de serem feitas no dataframe original.

train_df = df.copy()

Após a criação do novo dataframe é feita uma outra verificação só que dessa vez para ánalisar se há algum valor nulo no dataframe, caso haja eles serão deletados.

train_df.dropna(inplace=True)
train_df.isna().sum()

Antes de separar os dados em treino e teste é necessário já remover duas colunas que são desnecessárias para o treinamento do modelo, são elas: Unnamed: 0 e track_id

train_df.drop([ 'Unnamed: 0', 'track_id'], axis=1, inplace=True)

Então é feito uma contagem de quantidade de linhas que sobraram na cópia do dataset, e a quantidade restante é 5000.

print(len(train_df))
5000

O ultímo passo do pré-processamento dos dados é transformar as colunas categorias (artists, album_name, track_name e track_genre) em colunas com valores númericos.

Esse processo é feito utilizando um tipo de encoding, nessa pesquisa foi utilizado o `labelEcoder.

le = LabelEncoder()

train_df['artists'] = le.fit_transform(train_df['artists'])

train_df['album_name'] = le.fit_transform(train_df['album_name'])

train_df['track_name'] = le.fit_transform(train_df['track_name'])

train_df['track_genre'] = le.fit_transform(train_df['track_genre'])

Depois de todas as etapas do pré-processamento concluídas começa-se o treinamento do modelo.

Primeiro é feito a divisão dos dados em Xe y, onde X serão as todas as colunas menos a coluna de popularidade (popularity) e o y será apenas a coluna de popularidade.

Então os dados são dividos em 80% para treino e 20% para testes.

É importante usar o valor 42 no parâmetro random_state para que os resultados sejam os mesmos não importa em qual máquina esse script seja executado.

X = train_df.drop('popularity', axis=1)
y = train_df['popularity']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)


scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Em sequência é feito o primeiro treino do modelo, apenas utilizando o SVC (Classificação de Vetores de Suporte) puro.

model = SVC()
model.fit(X_train_scaled, y_train)

Então é criado um gráfico do estilo matriz de confusão onde é possivel verificar os resultados desse primeiro teste.

y_pred = model.predict(X_test_scaled)
conf_matrix = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(14, 11))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', cbar=False, xticklabels=np.unique(y_test), yticklabels=np.unique(y_test))
plt.title('Matriz de Confusão - SVM Linear')
plt.xlabel('Predito')
plt.ylabel('Real')
plt.show()
print(classification_report(y_test, y_pred))

matrizPop1

matrizGen1

Então é feito um novo treino do modelo só que dessa vez utilizando uma grade de hiperparâmetros com diferentes valores para C, gamma e kernel.

E tambem é feito um grid search com valor igual a 5, métrica de avalição accuracy, n_jobs = -1 e verbose = 3 apenas para exibir informações no console de acordo com o progresso do treino.

svm = SVC(probability=True)
param_grid = {
    'C': [0.1, 1, 10, 100],
    'gamma': [1, 0.1, 0.01, 0.001],
    'kernel': ['linear', 'rbf', 'poly']
}

grid_search = GridSearchCV(svm, param_grid, cv=5, scoring='accuracy', n_jobs=-1, verbose=3)

grid_search.fit(X_train_scaled, y_train)

Após o termino do segundo treino é impresso melhores parâmetros e estimadores.

# POPULARIDADE
print('Melhores parâmetros: ', grid_search.best_params_)
print('Melhor estimador: ', grid_search.best_estimator_)

Melhores parâmetros:  {'C': 10, 'gamma': 0.1, 'kernel': 'rbf'}
Melhor estimador:  SVC(C=10, gamma=0.1, probability=True)
# GENÊRO
print('Melhores parâmetros: ', grid_search.best_params_)
print('Melhor estimador: ', grid_search.best_estimator_)

Melhores parâmetros:  {'C': 10, 'gamma': 0.01, 'kernel': 'rbf'}
Melhor estimador:  SVC(C=10, gamma=0.01, probability=True)

E então é feita a criação da matriz de confusão utilizandos os estimadores.

cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm)
fig, ax = plt.subplots(figsize=(14, 11))
disp.plot(cmap=plt.cm.Blues, ax=ax)
plt.title('Matriz de Confusão')
plt.show()

print(classification_report(y_test, y_pred))

matrizPop2

matrizGen2

E por fim é feito um gráfica para a análise das features do dataset e a sua importância para a classificação de popularidade ou genêro.

result = permutation_importance(best_svm, X_test_scaled, y_test, n_repeats=10, random_state=42)
sorted_idx = result.importances_mean.argsort()
plt.figure(figsize=(10, 6))
plt.barh(range(len(sorted_idx)), result.importances_mean[sorted_idx], align='center')
plt.yticks(range(len(sorted_idx)), [X.columns[i] for i in sorted_idx])
plt.xlabel('Importância das features')
plt.title('Importância das Features via Permutação no dataset')
plt.show()

featPop

featGen

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors