I am trying to solve a multi-class classification involving prediction the outcome of a football match (target variable = Win, Lose or Draw). With a dataset of 2280 rows, which is 6 seasons of football data.
I have features with both numerical and categorical values (which I have encoded using one encoding). The data is split into a train and test set, in a way so the test set is only the most recent season of data.
I wanted to understand as this is my first machine learning project if this overall process looks correct and if there is anything I should be doing better/more optimal.
Splitting data into train test split
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report, accuracy_score
from sklearn.naive_bayes import MultinomialNB
# Assign our target variable to label
label = match_df['FTR']
# Flatten the label array
y = np.ravel(label)
# Assign all columns expect the FTR column to the features variable
X = match_df.loc[:, match_df.columns != 'FTR']
# Split our data into training and testing sets
# We set shuffle to false as we want to keep the order of the matches in the data frame so we can use the 2022/2023 season as our test set
# Use a test size of 0.1665 as this will give us 380 test samples which is the same as the number of matches in a season
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1665, random_state=0, shuffle=False)
Testing our base model, then performing hyper parameter tuning
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import GridSearchCV, StratifiedKFold
# Try without normalization also try min max scaler
# Normalize the data set as we have a several features with different data scales
scaler = MinMaxScaler()
# Fit the scaler to the training set and transform the training set
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# Create SVM model
svm_model = SVC(random_state=0)
# Create KNN model
knn_model = KNeighborsClassifier()
# Create Naive Bayes model
nb_model = GaussianNB()
# Create a dictionary of the models
models = {'KNN':knn_model, 'SVM':svm_model, 'Naive_Bayes':nb_model}
# Create the StratifiedKFold object
skf = StratifiedKFold(n_splits=10)
# Train the base models and evaluate them using cross validation
for model_name, model in models.items():
model.fit(X_train, y_train)
scores = cross_val_score(model, X_train, y_train, cv=skf)
print(f"Accuracy during cross validation for BASE {model_name}: {scores.mean()}")
# Perform hyper parameter tuning on each model using grid search
# Create a dictionary of hyper parameters for each model we want to tune
svm_parameters = {'kernel':['poly', 'rbf', 'linear'], 'C':[0.1, 1, 10, 100], 'gamma':['scale', 'auto', 0.1, 1], 'degree':list(range(1, 10))}
# For knn neighbor param, we make sure it is odd to prevent ties
knn_parameters = {'n_neighbors':[i for i in range(2, 31) if i % 2 != 0], 'weights':['uniform', 'distance'], 'algorithm':['auto', 'ball_tree', 'kd_tree', 'brute'],
'leaf_size':[i for i in range(1, 40)], 'p':[1, 2], 'metric':['minkowski', 'euclidean', 'manhattan']}
nb_parameters = {'var_smoothing':[1e-09, 1e-08, 1e-07, 1e-06, 1e-05]}
# Create a dictionary of the parameters
parameters = {'SVM':svm_parameters, 'KNN':knn_parameters, 'Naive_Bayes':nb_parameters}
# import scoring metrics
from sklearn.metrics import accuracy_score, balanced_accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
from sklearn.metrics import make_scorer
scoring = {
'accuracy': make_scorer(accuracy_score),
'balanced_accuracy': make_scorer(balanced_accuracy_score),
'precision': make_scorer(precision_score, average='macro'),
'recall': make_scorer(recall_score, average='macro'),
'f1': make_scorer(f1_score, average='macro')
}
# Loop through each model and perform hyper parameter tuning
for model_name, model in models.items():
print(f"Performing hyper parameter tuning on {model_name}...")
# Create a grid search object and fit it to the data to perform hyper parameter tuning
search = GridSearchCV(estimator=model, param_grid=parameters[model_name], scoring=scoring, refit='accuracy', cv=skf, n_jobs=-1)
# Fit the grid search object to the train data
searchResults = search.fit(X_train, y_train)
# Get the optimal hyper parameters and corresponding accuracy score
print(f"Best parameters: {search.best_params_}, Best Score: {search.best_score_}")
print("Evaluating the model on the test data...")
bestModel = searchResults.best_estimator_
print(bestModel)
print(f"Test Score: {bestModel.score(X_test, y_test)}\n\n")
# Fit the best parameters to the model
models[model_name] = bestModel
Final test of our hyper parameter tuning models and display their confusion matrix
for model_name, model in models.items():
# Produce a confusion matrix for the final model
conf_matrix = confusion_matrix(y_test, model.predict(X_test))
# Plot the confusion matrix
sns.heatmap(conf_matrix, annot=True, cmap='Blues')
# Set our x, y labels and title
plt.xlabel('Predicted labels')
plt.ylabel('True labels')
plt.title(f'Confusion Matrix for {model_name}')
# Display the plot
plt.show()