<jupyter_start><jupyter_text>PreprocessingPrimerament, he volgut observar les dades proveides en el dataset, entrendre la seva distribució o si ens feia falta fer neteja d'aquest, o per exemple balancejar-lo.<jupyter_code>from src.generate_features import *

# Visualitzarem només 3 decimals per mostra
pd.set_option('display.float_format', lambda x: '%.3f' % x)

dataset_train, dataset_test, dataset_validate, dataset_full = generate_dataset(type='non-nlp')

data = dataset_test.values
target = dataset_test.target

x = data[:, :18]
y = target

print("Dimensionalitat de la BBDD:", dataset_full.shape)
print("Dimensionalitat de les entrades X", x.shape)
print("Dimensionalitat de l'atribut Y", y.shape)<jupyter_output>Index(['COLUMNS: decade', 'COLUMNS: danceability', 'COLUMNS: energy',
       'COLUMNS: key', 'COLUMNS: loudness', 'COLUMNS: mode',
       'COLUMNS: speechiness', 'COLUMNS: acousticness',
       'COLUMNS: instrumentalness', 'COLUMNS: liveness', 'COLUMNS: valence',
       'COLUMNS: tempo', 'COLUMNS: duration_ms', 'COLUMNS: time_signature',
       'COLUMNS: chorus_hit', 'COLUMNS: sections', 'COLUMNS: target'],
      dtype='object')
Dimensionalitat de la BBDD: (41106, 17)
Dimensionalitat de les entrades X (18499, 17)
Dimensionalitat de l'atribut Y (18499,)<jupyter_text>Dintre de la generació del nostre dataset, es a dir la carrega del mateix en un Dtaframe de Pandas i possible neteja, he dividit el proces en 2 parts. Primerament, vaig voler probar a  utilitzar el dataset proveit tal qual, eliminant les columnes amb text com per exemple el nom del artista o URL de spotify de la canço. Més endavant, (apartat NLP d'aquest notebook) vaig voler probar a utilitzar Natural Language Processing per probar si per exemple podria extreure coneixemtn util dels noms de les cançons.Ambdos idees es tradueixen en la funció generate_dataset que li puc passar l'argument 'NLP' o 'non-nlp' respectivament. El meu dataset estava dividit en diferents .csv depenent de cada decada:    - 60 (Decada dels 60 - 1960-1970)    - 70 (Decada dels 60 - 1970-1980)    - 80 (Decada dels 60 - 1980-1990)    - 90 (Decada dels 60 - 1990-2000)    - 00 (Decada dels 60 - 2000-2010)    - 10 (Decada dels 60 - 2010-2020)    Aleshores, la idea que he seguit es la de juntar tots els datasets desde la decada dels 60 fins als 90 per a train, deixant la ultima o ultimes decades (00, 10) per dades de test. Com veurem més endavant cada decada es important, la música ha anat evolucionant, aixi com el concepte de canço exitosa.Primerament observem el nombre de valors nulls. Podem observar que no presentem cap.<jupyter_code>print("Per comptar el nombre de valors no existents:")
print(dataset_full.isnull().sum())<jupyter_output>Per comptar el nombre de valors no existents:
decade              0
danceability        0
energy              0
key                 0
loudness            0
mode                0
speechiness         0
acousticness        0
instrumentalness    0
liveness            0
valence             0
tempo               0
duration_ms         0
time_signature      0
chorus_hit          0
sections            0
target              0
dtype: int64<jupyter_text>El nostre arbre tampoc esta desbalancejat, tenim el mateix nombre de dades de ambdos classes:<jupyter_code>print(dataset_full['target'].value_counts())<jupyter_output>1    20553
0    20553
Name: target, dtype: int64<jupyter_text>Per introduir-les, trobem aquestes variables depenents el seu valor de cada canço. Com veurem més endavant hi ha variiables que han tingut més importancia que altres durant les diferents decades peró que realment s'ha mantingut sempre com les numero 1.Aquestes dades s'extreuen amb la API de Spotify de Sound Analysis, que també he utilitzat en la creació de la pagina web.<jupyter_code>print("Per visualitzar les primeres 5 mostres de la BBDD:")
dataset_full.head()<jupyter_output>Per visualitzar les primeres 5 mostres de la BBDD:<jupyter_text>Anem a analtizar les dades. Com tenim diferents decades, vaig observar que potser, clar, la música a anat evolucionant amb el temps, aixi com els gustos de la gent, o factors que determinen quina canó es un exit. Per tant, primerament vaig mostrar en una grafica la mitjana de hits i no hits depenent de cada factor.<jupyter_code>from sklearn.preprocessing import StandardScaler 
#create two var lists, one with Spotify's features (spfeatures_var_list) and one with the song traits (song_traits_var_list)
spfeatures_var_list = ['danceability', 'energy', 'key', 'loudness','mode', 'speechiness', 'acousticness', 
                       'instrumentalness', 'liveness','valence']
song_traits_var_list = ['key', 'loudness','tempo', 'time_signature', 'chorus_hit','sections'] 
#duration_ms has been removed since it has such larger numbers than the other variables 

all_songs_hits = dataset_train[spfeatures_var_list].loc[dataset_train['target'] == 1]

all_songs_flops = dataset_train[spfeatures_var_list].loc[dataset_train['target'] == 0]

#create a dataframe that includes the means for hits and flops
hits_means = pd.DataFrame(all_songs_hits.describe().loc['mean'])
flops_means = pd.DataFrame(all_songs_flops.describe().loc['mean'])
means_joined = pd.concat([hits_means,flops_means], axis = 1)
means_joined.columns = ['hit_mean', 'flop_mean']

ss = StandardScaler()
means_joined_scaled = pd.DataFrame(ss.fit_transform(means_joined),index= means_joined.index, columns = means_joined.columns)
means_joined_scaled


means_joined_scaled.plot(kind = 'bar', figsize=(10, 5), color = ('purple', 'grey'), title = 'Means of Hit Songs and Flop Songs for Song Features')
plt.legend(labels=['Hits', 'Flops'], loc='upper right')
plt.tight_layout()
plt.show()<jupyter_output><empty_output><jupyter_text>Com es pot anar observant, la key o tonalitat musical en la que es troba la cançó així com el loudness, presenten molt més pes que les altres.Anem a veure l'histograma de cada variable per buscar les distribucions de les nostres variables<jupyter_code>#create histograpms of all the variables to see distributions
fig, ax = plt.subplots(5,3, figsize=(10, 10))
fig.tight_layout()

def hist_plot(row, column, variable, binsnum, color):
    ax[row, column].hist(dataset_train[variable], bins = binsnum, color = color)
    ax[row, column].set_title(variable + ' histogram')
    
hist_plot(0, 0, 'danceability', 10, 'purple')
hist_plot(0, 1, 'energy', 10, 'orchid')
hist_plot(0, 2, 'key', 10, 'plum')
hist_plot(1,0, 'loudness', 10, 'purple')
hist_plot(1,1, 'mode', 10, 'orchid')
hist_plot(1,2, 'speechiness', 10, 'plum')
hist_plot(2,0, 'acousticness', 10, 'purple')
hist_plot(2,1, 'instrumentalness', 10, 'orchid')
hist_plot(2,2, 'liveness', 10, 'plum')
hist_plot(3,0, 'valence', 10, 'purple')
hist_plot(3,1, 'tempo', 10, 'orchid')
hist_plot(3,2, 'duration_ms', 50, 'plum')
hist_plot(4,0, 'time_signature', 10, 'purple')
hist_plot(4,1, 'chorus_hit', 10, 'orchid')
hist_plot(4,2, 'sections', 50, 'plum')
plt.tight_layout()
plt.show()<jupyter_output><empty_output><jupyter_text>Aquí hi ha alguns patrons interessants: les cançons solen ser més ballables que menys ballables, les cançons tendeixen a tenir més energia que menys energia, la tonalitat de C és la tonalitat més popular, les cançons solen ser inferiors a 10 decibels, la majoria de les cançons estan en escales majors, la majoria de les cançons contenen més música que la parla, la majoria de les cançons no són en directe, la majoria de les cançons no són acústiques, la majoria de les cançons contenen música, hi ha una bona barreja de cançons alegres i tristes, la majoria de cançons tenen uns 80-90 pulsacions per minut i la majoria de cançons són en 4/4 de temps. En el apartat de Model Testing, s'observara amb més profunditat altres factors de les dades.   Model Testing Primer vaig començar per probar diferents models estadistics classificadors per veure quin hem donava millor resultat. L'escull dels mateixos ha sigut un process de busqueda en internet dels més recomenats, i amb comparar amb altres persones que han treballat en aquest cas Kaggle també.<jupyter_code>from src.train_model import *

from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, AdaBoostClassifier
from sklearn.svm import LinearSVC, SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import cross_val_score

x_train, x_test, y_train, y_test = get_train_test(dataset_train, dataset_test, dataset_validate)


models = {
    "Logistic Regression": LogisticRegression(),
    "K-Nearest Neighbors": KNeighborsClassifier(),
    "Decision Tree": DecisionTreeClassifier(),
    "Support Vector Machine (Linear Kernel)": SVC(max_iter=5000),
    "Random Forest": RandomForestClassifier(),
    "Naive Bayes": GaussianNB(),
    "Neural Net": MLPClassifier(),
    "AdaBoost": AdaBoostClassifier(),
    "Gradient Boosting": GradientBoostingClassifier()
}

for name, model in models.items():
    print(name)
    model.fit(x_train, y_train)
    print(name + " trained.")

for name, model in models.items():
    print(name + ": {:.2f}%".format(model.score(x_test, y_test) * 100))
    print("----------------")
    print("Model evaluation {0}".format(name))
    evaluate_model(x_train, x_test, y_train, y_test, model.predict(x_test), model)
    # print("###")
    print("Cross validation {0}".format(name))
    # print("###")
    # cross_val_preds = cross_val_predict(model, x_val, y_val)
    scores = cross_val_score(model, x_train, y_train, cv=6, scoring='accuracy')
    print("Cross-validation scores:", scores)
    print("Mean:", scores.mean())
    print("----------------")<jupyter_output>D:\Desktop\Universidad\3r Año\1r Semestre\APC\Kaggle\MD3 Spotify\src\train_model.py:36: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only
  x_train = dataset_train.drop(['target'], 1).values  # values converts it into a numpy array
D:\Desktop\Universidad\3r Año\1r Semestre\APC\Kaggle\MD3 Spotify\src\train_model.py:39: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only
  x_test = dataset_test.drop(['target'], 1).values  # values converts it into a numpy array<jupyter_text>Com podem veure, el model de Random Forest ens retorna l'accuracy més alt aixi com una precissió i recall més bona. Per tant haurem de treballar amb aquest. El seguent que volia veure era buscar quines variables ens ajuden a diferenciar entre HIT i no HIT. Anem a crear un model de random forest i provar rendiments<jupyter_code>from src.train_model import *
from src.graphic_utils import *
import matplotlib as mpl
import seaborn as sns

x_train, x_test, y_train, y_test = get_train_test(dataset_train, dataset_test, dataset_validate)

RF = RandomForestClassifier()
RF.fit(x_train, y_train)
y_pred = RF.predict(x_test)

probs = RF.predict_proba(x_test)
roc_and_pr(y_test, probs)<jupyter_output>D:\Desktop\Universidad\3r Año\1r Semestre\APC\Kaggle\MD3 Spotify\src\train_model.py:36: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only
  x_train = dataset_train.drop(['target'], 1).values  # values converts it into a numpy array
D:\Desktop\Universidad\3r Año\1r Semestre\APC\Kaggle\MD3 Spotify\src\train_model.py:39: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only
  x_test = dataset_test.drop(['target'], 1).values  # values converts it into a numpy array<jupyter_text>Ambdos curbes ens indican que tot i que podria arribar a ser més perfecte, el nostre model es bastant bo classificant, veiem com la area per sota de la corva es molt propera a 1.<jupyter_code>#create a confusion matrix to see the efficacy of the model
from sklearn import metrics
cnf_matrix = metrics.confusion_matrix(y_test, y_pred)
cnf_matrix

#create a figure/heatmap of the confusion matrix for a better visual
mpl.rcParams['figure.figsize']=(10,5)
class_names=[0,1] # name  of classes
fig, ax = plt.subplots()
tick_marks = np.arange(len(class_names))
plt.xticks(tick_marks, class_names)
plt.yticks(tick_marks, class_names)
# create heatmap
sns.heatmap(pd.DataFrame(cnf_matrix), annot=True, cmap="RdPu" ,fmt='g')
ax.xaxis.set_label_position("top")
plt.tight_layout()
plt.title('Confusion matrix', y=1.1)
plt.ylabel('Actual label')
plt.xlabel('Predicted label')
plt.tight_layout()
plt.show()<jupyter_output><empty_output><jupyter_text>La matriu de confusió demostra que el model va identificar correctament els encerts i els fracàs la majoria del temps.Podem observar com al pas de les decades, com de importants son cara atribut de les cançons ja que possiblement depenent de la decada, li donaven mes importancia potser al tant per cent de ballable  i no tant al tant percent de instrumentabilitat.<jupyter_code>indep_columns = ['danceability', 'energy', 'key', 'loudness',
       'mode', 'speechiness', 'acousticness', 'instrumentalness', 'liveness',
       'valence', 'tempo', 'duration_ms', 'time_signature', 'chorus_hit',
       'sections', 'decade']

#create a dataframe of the feature importances to determine which variables are the most important in determining a hit
all_songs_feat = RF.feature_importances_
df_indep_columns = pd.DataFrame(indep_columns)
df_all_songs_feat = pd.DataFrame(all_songs_feat)

all_songs_feat_vars = pd.concat([df_indep_columns, df_all_songs_feat], axis = 1, ignore_index=True)

all_songs_feat_vars.columns = ['Variable', 'Feature importance all decades']
all_songs_feat_vars = all_songs_feat_vars.set_index('Variable')
all_songs_feat_vars = all_songs_feat_vars.sort_values(by=['Feature importance all decades'], ascending = False)
print(all_songs_feat_vars)
print(RF.n_features_in_, len(indep_columns))

all_songs_feat_vars.plot(kind='bar', color = "purple", title = "Most important features for predicting hit and flop songs for all decades", legend = None)
plt.ylabel('Feature importance')
plt.tight_layout()
plt.show()<jupyter_output>Feature importance all decades
Variable                                        
instrumentalness                           0.171
acousticness                               0.113
danceability                               0.100
energy                                     0.076
speechiness                                0.073
loudness                                   0.073
duration_ms                                0.072
valence                                    0.065
tempo                                      0.050
liveness                                   0.047
chorus_hit                                 0.045
decade                                     0.038
sections                                   0.034
key                                        0.026
mode                                       0.009
time_signature                             0.006
16 16<jupyter_text>Durant totes les dècades, instrumentalness, danceability, acousticness, duration_ms, i loudness van ser els millors predictors de si una cançó era un èxit. Tot i aixi, he comprobat que deixant aquests valors com a unics atributs d'entrada, les metriques empitjoraven. Aixó ens indica que aquests atributs son importants en tant a visió general de la societat i els seus gustos en cada decada, pero els altres atributs més tecnics de cada cançó son desconeguts pel public general pero jugent gran paper.  Hyperparameter Search Per intentar obtenir millors rendiments, he decidit fer una busqueda dels millors hiperparametres per al nostre random forest. Per aixo, he utilizat la llibreria scikit-optimize, en especial el Buscador de Bayes amb cross validation.<jupyter_code>def hyperparameter_search(x_train, y_train, x_test, y_test, model_name, params=None):
    """ retrain model with new song """
    sc =  StandardScaler()
    x_train = sc.fit_transform(x_train)
    x_test = sc.transform(x_test)
    
    # use grid search to find best parameters for random forest

    from skopt import BayesSearchCV

    from skopt.space import Real, Categorical, Integer

    # log-uniform: understand as search over p = exp(x) by varying x
    opt = BayesSearchCV(
        RandomForestClassifier(),
        {
            'n_estimators': Integer(1, 500),
            'max_depth': Integer(1, 20),
            'max_features': Categorical(['auto', 'sqrt', 'log2']),
            'min_samples_split': Integer(2, 20),
            'min_samples_leaf': Integer(1, 20),
            'bootstrap': Categorical([True, False]),
            'criterion': Categorical(['gini', 'entropy']),
        },
        n_iter=32,
        random_state=0,
        verbose=2
    )

    # executes bayesian optimization
    _ = opt.fit(x_train, y_train)

    # model can be saved, used for predictions or scoring
    print(opt.score(x_test, y_test))
    print(opt.best_params_)
    print(opt.best_score_)
    print(opt.best_estimator_)

    return opt.score(x_test, y_test)
dataset_train, dataset_test, dataset_validate, dataset_full = generate_dataset(type='non-nlp')

x_train, x_test, y_train, y_test = get_train_test(dataset_train, dataset_test, dataset_validate)

score = hyperparameter_search(x_train=x_train, y_train=y_train, x_test=x_test, y_test=y_test, model_name='RF')

RF = RandomForestClassifier(random_state=0,bootstrap=False, criterion='entropy',max_depth=20, max_features='log2', min_samples_leaf=1,min_samples_split=7,n_estimators=175)
RF.fit(x_train, y_train)

evaluate_model(x_train, x_test, y_train, y_test, model.predict(x_test), model)
scores = cross_val_score(model, x_train, y_train, cv=6, scoring='accuracy')
print("Cross-validation scores:", scores)
print("Mean:", scores.mean())<jupyter_output>Accuracy: 78.89615654900265
Precision: 75.2364737041241
Recall: 86.08225108225108
F1 score: 0.8029477084595195
Training MSE:  0.19154629770412043
Test model MSE 0.2110384345099735
Cross-validation scores: [0.74238278 0.76299945 0.77504105 0.80678708 0.84820288 0.80018248]
Mean: 0.789265954584983<jupyter_text>Podem observar com hem obtingut pitjor rendiment.  Proves amb NLP (NLTK) Vist que tenia atributs amb paraules, i per consell del professor, vaig decidir intentar fer proves amb NLP (Natural Language Processing) en python.<jupyter_code>%matplotlib notebook
import nltk
from src.dataset_utils import *

columns_to_drop = ['uri']

dataset_full_nlp = get_full_dataset(columns_to_drop=columns_to_drop)

tokens = dataset_full_nlp['track'].values

freq = nltk.FreqDist(tokens)
fig.tight_layout()
freq.plot(10, cumulative=False)
from src.generate_features import *
from src.train_model import *
from src.score_model import *
import joblib
import matplotlib.pyplot as plt
import seaborn as sns


dataset_full = generate_dataset(type='nlp', test=1)

x_train, x_test, y_train, y_test = get_train_test(dataset_full=dataset_full)

RF, y_pred = train_model(x_train=x_train, y_train=y_train, x_test=x_test, y_test=y_test, model_name='RF')


accuracy, precision, recall, f1 = evaluate_model(x_train, x_test, y_train, y_test, y_pred, RF)

# we save the model.
filename = 'deploy/model-spotify-full-dataset-nlp.sav'
joblib.dump(RF, filename)

score = hyperparameter_search(x_train=x_train, y_train=y_train, x_test=x_test, y_test=y_test, model_name='RF')<jupyter_output>Fitting 5 folds for each of 1 candidates, totalling 5 fits
[CV] END bootstrap=True, criterion=gini, max_depth=13, max_features=log2, min_samples_leaf=17, min_samples_split=20, n_estimators=65; total time=   3.1s
[CV] END bootstrap=True, criterion=gini, max_depth=13, max_features=log2, min_samples_leaf=17, min_samples_split=20, n_estimators=65; total time=   2.8s
[CV] END bootstrap=True, criterion=gini, max_depth=13, max_features=log2, min_samples_leaf=17, min_samples_split=20, n_estimators=65; total time=   3.1s
[CV] END bootstrap=True, criterion=gini, max_depth=13, max_features=log2, min_samples_leaf=17, min_samples_split=20, n_estimators=65; total time=   3.9s
[CV] END bootstrap=True, criterion=gini, max_depth=13, max_features=log2, min_samples_leaf=17, min_samples_split=20, n_estimators=65; total time=   3.0s
Fitting 5 folds for each of 1 candidates, totalling 5 fits
[CV] END bootstrap=False, criterion=gini, max_depth=18, max_features=log2, min_samples_leaf=6, min_samples_split=5, n[...]<jupyter_text>Aleshores, veient que no obtenia massa canvi, vaig decidir i seguin el consell del professor, provar el TFIDF Vectorizer. A la funció generate_dataset ficant test=0, crida el codi que genera el vector amb TFIDF.<jupyter_code>dataset_full = generate_dataset(type='nlp', test=0)

x_train, x_test, y_train, y_test = get_train_test(dataset_full=dataset_full)

RF, y_pred = train_model(x_train=x_train, y_train=y_train, x_test=x_test, y_test=y_test, model_name='RF')


accuracy, precision, recall, f1 = evaluate_model(x_train, x_test, y_train, y_test, y_pred, RF)

# we save the model.
filename = 'deploy/model-spotify-full-dataset-nlp.sav'
joblib.dump(RF, filename)<jupyter_output>Index(['COLUMNS: decade', 'COLUMNS: track', 'COLUMNS: artist',
       'COLUMNS: danceability', 'COLUMNS: energy', 'COLUMNS: key',
       'COLUMNS: loudness', 'COLUMNS: mode', 'COLUMNS: speechiness',
       'COLUMNS: acousticness', 'COLUMNS: instrumentalness',
       'COLUMNS: liveness', 'COLUMNS: valence', 'COLUMNS: tempo',
       'COLUMNS: duration_ms', 'COLUMNS: time_signature',
       'COLUMNS: chorus_hit', 'COLUMNS: sections', 'COLUMNS: target'],
      dtype='object')
{'fraction_non_alphanumeric': 0.09487499422339295, 'fraction_numerical': 0.027866352419243033, 'mean_word_length': 4.516186591893959, 'pattern_counts': {'":': 10, '<': 43, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '324913', 'license': 'MIT', 'n_tokens_mistral': 7633, 'n_tokens_neox': 7158, 'n_words': 2156, 'path': 'md3-spotify-hit-or-not.ipynb', 'repo_name': 'elblogbruno/MD3-Kaggle-Spotify-Hit-Detector', 'size': '21639'}