<jupyter_start><jupyter_text>Importing Libraries and Reading Data<jupyter_code>#Importing Libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split, KFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn import svm
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix, classification_report

%matplotlib inline
#Reading metadata with genre labels
tracks = pd.read_csv('fma-rock-vs-hiphop.csv')
tracks.head()
#Reading track metris with features
echonest_metrics = pd.read_json('echonest-metrics.json', precise_float=True)
echo_tracks = echonest_metrics.merge(tracks[['genre_top', 'track_id']], on = 'track_id')
#Main dataset to work on
echo_tracks
#Inspecting the new dataframe
echo_tracks.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
Int64Index: 4802 entries, 0 to 4801
Data columns (total 10 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   track_id          4802 non-null   int64  
 1   acousticness      4802 non-null   float64
 2   danceability      4802 non-null   float64
 3   energy            4802 non-null   float64
 4   instrumentalness  4802 non-null   float64
 5   liveness          4802 non-null   float64
 6   speechiness       4802 non-null   float64
 7   tempo             4802 non-null   float64
 8   valence           4802 non-null   float64
 9   genre_top         4802 non-null   object 
dtypes: float64(8), int64(1), object(1)
memory usage: 412.7+ KB<jupyter_text>Finding Relationship Among Continuous Variables To avoid using variables which have strong relation with other variables, using correlation to find strong variables and reduce the number of columns avoiding feature redundancy as: more number of features may result in overfitting and delay the computation time.<jupyter_code>#Correlation between columns
cor_mat = echo_tracks.corr()
cor_mat.style.background_gradient(cmap='PuBuGn')
#Plotting Heatmap using Seaborn
plt.figure(figsize=(15,10))
heat = sns.heatmap(cor_mat, annot=True)
heat.set(title = 'Correlation matrix of main echo tracks data')<jupyter_output><empty_output><jupyter_text>Normalising the data and Dimentionality Reduction The above visualisations of correlation didn't showed strong correlation among features which makes it difficult to drop a column at random. To reduce the number of features, using a dimentionality reductino algorithm Principal Component Analysis (PCA).<jupyter_code>echo_tracks.head(5)
#Separating features and labels
features = echo_tracks.iloc[:, 1:-1]
labels = echo_tracks.iloc[:, -1]
#Scaling features 
scalar = StandardScaler()
scaled_train_features = scalar.fit_transform(features)
#Applying PCA
pca = PCA()
pca.fit(scaled_train_features)<jupyter_output><empty_output><jupyter_text>Explained variance ratio in PCA is the percentage of variance which is attributed by each of the selected components.Reference: https://towardsdatascience.com/dealing-with-highly-dimensional-data-using-principal-component-analysis-pca-fea1ca817fe6<jupyter_code>exp_variance = pca.explained_variance_ratio_
exp_variance
#Plotting the exp variance ratio
# fig, ax = plt.subplots(figsize=(15,10))
# ax.bar(range(pca.n_components_), exp_variance, aplha=0.5)
# ax.set_xlabel('Principal Component')
plt.figure(figsize=(12,8))
plt.bar(range(0, len(exp_variance)), exp_variance, alpha=0.5, 
       align = 'center', label = 'Individual Explained Variance')
plt.step(range(0, len(np.cumsum(exp_variance))), np.cumsum(exp_variance),
        where='mid', label='Cumulative Explained Variance')
plt.ylabel('Explained variance ratio', size=20)
plt.xlabel('Principal component index', size=20)
plt.legend(loc='best')
plt.tight_layout()
plt.show()
#Calculating cumulative explained variance
cum_exp_variance = np.cumsum(exp_variance)
cum_exp_variance
fig, ax = plt.subplots(figsize=(15,10))
ax.plot(cum_exp_variance)
ax.axhline(y=0.85, linestyle='--')<jupyter_output><empty_output><jupyter_text>Cutoff are arbitrary near 85 roughly so the figure at cutoff figure shows to choose n components for PCA as 6.<jupyter_code>n_components = 6

#Again training PCA for the chosen number of components
pca = PCA(n_components, random_state=10)
pca.fit(scaled_train_features)
pca_projection = pca.transform(scaled_train_features)<jupyter_output><empty_output><jupyter_text>Classification Models Since the problem is a classification problem, I'll be using the following Machine Learning algorithms for training and testing the data:1. Logistic Regression2. Naive Bayes 3. SVM4. Decision Trees5. Random Forest<jupyter_code>#Splitting the data
X_train, X_test, Y_train, Y_test = train_test_split(pca_projection, 
                                                    labels, random_state=10)
#Creating objects for classifiers
lr = LogisticRegression(random_state=10)
nb = GaussianNB()
svm = svm.SVC(kernel='linear', random_state=10)
dt = DecisionTreeClassifier(random_state=10)
rf = RandomForestClassifier(random_state=10)
#Fitting the training data to the classifiers objects
lr.fit(X_train, Y_train)
nb.fit(X_train, Y_train)
svm.fit(X_train, Y_train)
dt.fit(X_train, Y_train)
rf.fit(X_train, Y_train)
#Predicting the test data to observe the performance
lr_pred = lr.predict(X_test)
nb_pred = nb.predict(X_test)
svm_pred = svm.predict(X_test)
dt_pred = dt.predict(X_test)
rf_pred = rf.predict(X_test)
#Comparing confusion matrices
print("\nLogistic Regression: \n", confusion_matrix(Y_test, lr_pred))
print("\nNaive Bayes: \n", confusion_matrix(Y_test, nb_pred))
print("\nSupport Vector Machine: \n", confusion_matrix(Y_test, svm_pred))
print("\nDecision Trees: \n", confusion_matrix(Y_test, dt_pred))
print("\nRandom Forest: \n", confusion_matrix(Y_test, rf_pred))
#Comparing classification reports
print("\nLogistic Regression: \n", classification_report(Y_test, lr_pred))
print("\nNaive Bayes: \n", classification_report(Y_test, nb_pred))
print("\nSupport Vector Machine: \n", classification_report(Y_test, svm_pred))
print("\nDecision Trees: \n", classification_report(Y_test, dt_pred))
print("\nRandom Forest: \n", classification_report(Y_test, rf_pred))<jupyter_output>Logistic Regression: 
               precision    recall  f1-score   support

     Hip-Hop       0.77      0.54      0.64       235
        Rock       0.90      0.96      0.93       966

    accuracy                           0.88      1201
   macro avg       0.83      0.75      0.78      1201
weighted avg       0.87      0.88      0.87      1201


Naive Bayes: 
               precision    recall  f1-score   support

     Hip-Hop       0.73      0.56      0.63       235
        Rock       0.90      0.95      0.92       966

    accuracy                           0.87      1201
   macro avg       0.81      0.75      0.78      1201
weighted avg       0.86      0.87      0.87      1201


Support Vector Machine: 
               precision    recall  f1-score   support

     Hip-Hop       0.80      0.52      0.63       235
        Rock       0.89      0.97      0.93       966

    accuracy                           0.88      1201
   macro avg       0.84      0.74      0.78      1201
weighte[...]<jupyter_text>With the above reports and confusion matrices, we can observe that there is a difference in the precisions of both labels. This can be due to unbalanced data training one feature more as compared to the other. As there is not much difference to observe in the f1 scores of models, Random Forest has the hight percentafe of f1 score in predicting both features.   Balancing the Data<jupyter_code>#Subset of only hip-hop tracks
hop_only = echo_tracks.loc[echo_tracks['genre_top'] == 'Hip-Hop']

#Subset of only rock tracks
rock_only = echo_tracks.loc[echo_tracks['genre_top'] == 'Rock']
#Sampling the rock songs to match the frequency of hip-hop songs. 
rock_only = rock_only.sample(hop_only.shape[0], random_state=10)
#Concatenating the dataframes rock only and hop only
rock_hop_bal = pd.concat([rock_only, hop_only])
#Features of new dataset
features = rock_hop_bal.iloc[:, 1:-1]

#Labels of new dataset
labels = rock_hop_bal.iloc[:, -1]<jupyter_output><empty_output><jupyter_text>Applying PCA and training new Data<jupyter_code>pca_projection = pca.fit_transform(scalar.fit_transform(features))
#Splitting the data
X_train, X_test, Y_train, Y_test = train_test_split(pca_projection, 
                                                    labels, random_state=10)
#Creating objects for classifiers
lr = LogisticRegression(random_state=10)
nb = GaussianNB()
svm = svm.SVC(kernel='linear', random_state=10)
dt = DecisionTreeClassifier(random_state=10)
rf = RandomForestClassifier(random_state=10)
#Fitting the training data to the classifiers objects
lr.fit(X_train, Y_train)
nb.fit(X_train, Y_train)
svm.fit(X_train, Y_train)
dt.fit(X_train, Y_train)
rf.fit(X_train, Y_train)
#Predicting the test data to observe the performance
lr_pred = lr.predict(X_test)
nb_pred = nb.predict(X_test)
svm_pred = svm.predict(X_test)
dt_pred = dt.predict(X_test)
rf_pred = rf.predict(X_test)
#Comparing confusion matrices
print("\nLogistic Regression: \n", confusion_matrix(Y_test, lr_pred))
print("\nNaive Bayes: \n", confusion_matrix(Y_test, nb_pred))
print("\nSupport Vector Machine: \n", confusion_matrix(Y_test, svm_pred))
print("\nDecision Trees: \n", confusion_matrix(Y_test, dt_pred))
print("\nRandom Forest: \n", confusion_matrix(Y_test, rf_pred))
#Comparing classification reports
print("\nLogistic Regression: \n", classification_report(Y_test, lr_pred))
print("\nNaive Bayes: \n", classification_report(Y_test, nb_pred))
print("\nSupport Vector Machine: \n", classification_report(Y_test, svm_pred))
print("\nDecision Trees: \n", classification_report(Y_test, dt_pred))
print("\nRandom Forest: \n", classification_report(Y_test, rf_pred))<jupyter_output>Logistic Regression: 
               precision    recall  f1-score   support

     Hip-Hop       0.84      0.80      0.82       230
        Rock       0.80      0.85      0.83       225

    accuracy                           0.82       455
   macro avg       0.82      0.82      0.82       455
weighted avg       0.82      0.82      0.82       455


Naive Bayes: 
               precision    recall  f1-score   support

     Hip-Hop       0.86      0.82      0.84       230
        Rock       0.83      0.86      0.84       225

    accuracy                           0.84       455
   macro avg       0.84      0.84      0.84       455
weighted avg       0.84      0.84      0.84       455


Support Vector Machine: 
               precision    recall  f1-score   support

     Hip-Hop       0.84      0.80      0.82       230
        Rock       0.80      0.85      0.83       225

    accuracy                           0.82       455
   macro avg       0.82      0.82      0.82       455
weighte[...]<jupyter_text>As we can clearly observe the difference between new classification reports and the previous reports, after balancing the data the f1 scores for both labels are nearly same making the model more efficient on predicting labels.   Cross Validation Evaluation of models The splitting of dataset can also effect the model's performance, CV attempts to split the data in multiple ways and test on each splits. I have KFold CV as a cross validation algorithm.<jupyter_code>#Declaring KFold object
kf = KFold(10)
logreg = LogisticRegression(random_state=10)
naive = GaussianNB()
SVM = svm.SVC(kernel='linear', random_state=10)
tree = DecisionTreeClassifier(random_state=10)
forest = RandomForestClassifier(random_state=10)
#Training models using KFold
log_score = cross_val_score(logreg, pca_projection, labels, cv=kf)
naive_score = cross_val_score(naive, pca_projection, labels, cv=kf)
svm_score = cross_val_score(logreg, pca_projection, labels, cv=kf)
tree_score = cross_val_score(tree, pca_projection, labels, cv=kf)
forest_score = cross_val_score(forest, pca_projection, labels, cv=kf)
print("\n Logistic Regression   : ", np.mean(log_score), 
      "\n Naive Bayes           : ", np.mean(naive_score),
      "\n Support Vector Machine: ", np.mean(svm_score), 
      "\n Decision Tree         : ", np.mean(tree_score), 
      "\n Random Forest         : ", np.mean(forest_score))<jupyter_output>Logistic Regression   :  0.782967032967033 
 Naive Bayes           :  0.7978021978021979 
 Support Vector Machine:  0.782967032967033 
 Decision Tree         :  0.7489010989010989 
 Random Forest         :  0.8142857142857143
{'fraction_non_alphanumeric': 0.08800439629455173, 'fraction_numerical': 0.045219029674988226, 'mean_word_length': 3.454195804195804, 'pattern_counts': {'":': 0, '<': 34, '<?xml version=': 0, '>': 34, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '656023', 'license': 'MIT', 'n_tokens_mistral': 4426, 'n_tokens_neox': 4024, 'n_words': 1132, 'path': 'Song Genre Classification.ipynb', 'repo_name': 'jaishankar97/Song-Genre-Classification', 'size': '12738'}