<jupyter_start><jupyter_text>On Music Genre ClassificationGenre classification is a task that has lately been taken over entirely by deep learning models and convolutional neural networks. Specifying a genre for a song and its performer can be a very nebulous affair, given its subjective nature, and even the most precise audio-based models often fail to reach a high accuracy. Especially when it comes to drawing boundaries between wide-reaching genres such as rock and pop, this precision becomes a matter of correct labeling as much as it is a matter of designing a good model. *Library Dependencies*<jupyter_code>import pandas as pd
import numpy as np
import os

from sklearn.preprocessing import minmax_scale

from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV

from xgboost import XGBClassifier

import eli5
from eli5.sklearn import PermutationImportance<jupyter_output>/usr/local/lib/python3.6/dist-packages/sklearn/utils/deprecation.py:144: FutureWarning: The sklearn.feature_selection.base module is  deprecated in version 0.22 and will be removed in version 0.24. The corresponding classes / functions should instead be imported from sklearn.feature_selection. Anything that cannot be imported from sklearn.feature_selection is now part of the private API.
  warnings.warn(message, FutureWarning)<jupyter_text>Genre Classification with Cross Gradient Boosting In this notebook, a music genre classifier will be built using the features extracted from 30 second long audio samples, stored in the features dataframe. It is often sought after to work with shorter samples as it reduces storage space, and in general an audio AI model will be considered superior if it can extract information from a smalled sample. Moreover, if features are values averaged over a time span, using a brief excerpt ensures these mean values are more indicative of the content that exists in the entire sample.However, the ultimate goal here is to find similar sounding songs, and very brief segments of a song might often prove to be quite uncharacteristic of its overall character. Therefore, the 30 second samples will be used. It is optimistically anticipated that these changes will be adequately expressed by the variance features.<jupyter_code># Read GTZAn dataframe:
df = pd.read_csv(../dataframes/feature_dataframe.csv)
# Discard filename and duration:
df = df.iloc[0:, 2:] 

df.head()<jupyter_output><empty_output><jupyter_text>Features are separated from the labels (Genres):<jupyter_code># Create Feature and Label dataframes:

y = df['genre'] 
X = df.loc[:, df.columns != 'genre'] 

# Normalize features:
cols = X.columns
min_max_scaler = MinMaxScaler()
np_scaled = min_max_scaler.fit_transform(X)

X = pd.DataFrame(np_scaled, columns = cols)<jupyter_output><empty_output><jupyter_text>Training dataset and Testing dataset split:<jupyter_code># Train test split:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)<jupyter_output><empty_output><jupyter_text>Helper functions for cross validation and final assessment of the model:<jupyter_code># Helper Functions: 

# Print cross-validation results
def print_results(results):
    print('BEST PARAMS: {}\n'.format(results.best_params_))

    means = results.cv_results_['mean_test_score']
    stds = results.cv_results_['std_test_score']
    for mean, std, params in zip(means, stds, results.cv_results_['params']):
        print('{} (+/-{}) for {}'.format(round(mean, 3), round(std * 2, 3), params))

# Fit and make predictions
def model_assess(model, title = "Default"):
    model.fit(X_train, y_train)
    preds = model.predict(X_test)
    print('Accuracy', title, ':', round(accuracy_score(y_test, preds), 5), '\n')<jupyter_output><empty_output><jupyter_text>The classifier to be used is the Cross Gradient Booster. 5-fold cross validation is performed to estimate the optimum hyperparameters. (This is a process that might take a while, depending on the parameters set to investigate.)<jupyter_code># Perform Cross Validation on XGB

# Practical to ignore the following warnings:
import warnings
warnings.filterwarnings('ignore', category=Warning)
warnings.filterwarnings('ignore', category=FutureWarning)
warnings.filterwarnings('ignore', category=DeprecationWarning)

# Define XGB parameters to test:
xgb = XGBClassifier(use_label_encoder=False, eval_metric = 'mlogloss')
parameters = {
    'n_estimators': [600, 800, 1000],
    'max_depth': [1, 3, 6],
    'learning_rate': [0.01, 0.05, 1]
}

# 5-fold Cross Validation
cv = GridSearchCV(xgb, parameters, cv=5)
cv.fit(X_train, y_train.ravel())

print_results(cv)<jupyter_output>BEST PARAMS: {'learning_rate': 0.05, 'max_depth': 3, 'n_estimators': 800}

0.577 (+/-0.076) for {'learning_rate': 0.01, 'max_depth': 1, 'n_estimators': 600}
0.586 (+/-0.062) for {'learning_rate': 0.01, 'max_depth': 1, 'n_estimators': 800}
0.597 (+/-0.039) for {'learning_rate': 0.01, 'max_depth': 1, 'n_estimators': 1000}
0.669 (+/-0.041) for {'learning_rate': 0.01, 'max_depth': 3, 'n_estimators': 600}
0.676 (+/-0.058) for {'learning_rate': 0.01, 'max_depth': 3, 'n_estimators': 800}
0.677 (+/-0.059) for {'learning_rate': 0.01, 'max_depth': 3, 'n_estimators': 1000}
0.667 (+/-0.053) for {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 600}
0.667 (+/-0.06) for {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 800}
0.67 (+/-0.063) for {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 1000}
0.643 (+/-0.065) for {'learning_rate': 0.05, 'max_depth': 1, 'n_estimators': 600}
0.651 (+/-0.07) for {'learning_rate': 0.05, 'max_depth': 1, 'n_estimators': 800}
0.646 (+/-0.074) for {'[...]<jupyter_text>The best performing model is assessed on the test dataset:<jupyter_code># Use best performing XGB:
xgb = XGBClassifier(n_estimators=800, learning_rate=0.05, max_depth=3, eval_metric = 'mlogloss')
model_assess(xgb, "of the Cross Gradient Booster")<jupyter_output>Accuracy of the Cross Gradient Booster : 0.725<jupyter_text>Even though averaged over a time span of 30 seconds, the extracted features can still be utilized to achieve over 70% accuracy in a task such as genre classification. It is therefore reasonable to assume, that such features maintain enough coherent information to be used in a recommendation engine. The trained model can be saved:<jupyter_code>import pickle

# save
with open('XGB_Music_Genre.pkl','wb') as f:
    pickle.dump(xgb,f)

'''# load
with open('XGB_Music_Genre.pkl', 'rb') as f:
    xgb2 = pickle.load(f)'''<jupyter_output><empty_output><jupyter_text>The importance of each feature is calculated:<jupyter_code># Estimate feature importance using eli5
perm = PermutationImportance(estimator=xgb, random_state=1)
perm.fit(X_test, y_test)

eli5.show_weights(estimator=perm, feature_names = X_test.columns.tolist())<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10635240011497557, 'fraction_numerical': 0.03578614544409313, 'mean_word_length': 5.782651072124756, 'pattern_counts': {'":': 0, '<': 34, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '110434', 'license': 'MIT', 'n_tokens_mistral': 2395, 'n_tokens_neox': 2174, 'n_words': 752, 'path': 'Notebooks/Notebook_GTZAN_Genre_Class_XGB.ipynb', 'repo_name': 'N11K6/Song_Recommender', 'size': '6958'}