Webinar Workflow ML
Objectif: Prédire le diagnostic du patient
Donnés: Les données proviennent de Kaggle
Méthologie: SVM
Implémentation
- Importation des données
- Exploration
- Conversion des variables catégorielles en numérique
- Séparation du jeu de données
- Entraînement
- Sélection de modèle
Voici les principaux outils que nous utilisons pour l'implémentation - Python - Pandas - Scitkit-learn
Librairies
import pickle
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.svm import LinearSVC, SVC
from sklearn.ensemble import RandomForestClassifier, BaggingClassifier, GradientBoostingClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
Importation des données
# Read data `Prostate_Cancer.csv` from my github. Original dataset come from Kaggle [https://www.kaggle.com/sajidsaifi/prostate-cancer]
try:
data = pd.read_csv("Prostate_Cancer.csv")
except:
data = pd.read_csv('https://raw.githubusercontent.com/joekakone/datasets/master/datasets/Prostate_Cancer.csv')
# Show the 10 first rows
data.head(10)
Le tableau contient 100
lignes et 10
colonnes. La première colonne id
représente les identifiants des patient, elle ne nous sera pas utile dans notre travail, nous allons l'ignorer dans la suite. La colonnes diagnosis_result
représnet quant à elle le résultat du diagnostic du patient, c'est cette valeur que nous allons prédire. Les autres colonnes décrivent l'état du patient, elles nous serviront çà prédire lle diagnostic du patient.
Nettoyage
Dans notre tableau, il n'y a pas de données manquantes. Généralement ce n'est pas le cas et il faudra corriger cela.
Exploration
Distribution de la variable objectif
Distribution des variables explicatives
Distribution des variables explicatives par la variable objectif
On constate une grande variation de perimeter
et area
en fonction de diagnosis_result
fig = plt.figure(figsize=(16, 6))
fig.add_subplot(1, 2, 1)
sns.distplot(x=data[data["diagnosis_result"]=="M"]["perimeter"])
sns.distplot(x=data[data["diagnosis_result"]=="B"]["perimeter"])
plt.title("perimeter")
fig.add_subplot(1, 2, 2)
sns.distplot(x=data[data["diagnosis_result"]=="M"]["area"])
sns.distplot(x=data[data["diagnosis_result"]=="B"]["area"])
plt.yticks([])
plt.title("area")
plt.show()
Reagrdons ce qu'il en est des correlations éventuelles entre les variables explicatives.
Regardons ce qu'il en ait des corrélations entre les variables explicatives.
Les variables perimeter
et area
sont fortement correlées ce qui n'est pas étonnant, le périmètre et la surface.
Conversion en numérique
Dans la suite, on va convertir les valeurs de diagnosis_result
par des nombres
M est encodé en 1 et B en 0
Échantillonnage
Le jeu de données a été séparé en deux parties, 80%
serviront à l'entraînement et les 20%
restants pour l'évaluation. Le soin a été pris de spécifier que l'échantionnalge doit conserver la même distribution suivant le diagnostic.
Les variables n'ont pas la même échelle de grandeur et il faut corriger cela. Si l'algorithme utilisé est juste ie s'il ne s'agit pas d'un algorithme dont l'apprentissage est itératif, on peut bien garder les valeurs comme telles. Mais dans notre exemple ici, nous allons raner toutes les variable à la même échelle de gradeur.
Centrage et Réduction
fig = plt.figure(figsize=(16, 6))
fig.add_subplot(1, 2, 1)
sns.boxplot(data=X_train, orient="h")
plt.title("Distribution des variables explicatives")
fig.add_subplot(1, 2, 2)
sns.boxplot(data=X_train_scaled, orient="h")
plt.yticks([])
plt.title("Distribution des variables explicatives\naprès normalisation")
plt.show()
On applique la même opération à l'échantillon de test. Mais attention, on utilise les valeurs calculées sur l'échatiollon d'entraînement
Modélisation
Il n'existe pas de modèle parfait capable de résoudre tous les problèmes. En général, il faut essayer plsuieurs et sélectionner le plus performant.
Nous allons trouver une droite pour séparer les points. Il s'agit de la droite qui sépare le mieux.
#
def evaluate(model, X_test_, y_test_):
# Accuracy
y_pred = model.predict(X_test_)
print("Accuracy Score: {:2.2%}".format(accuracy_score(y_test_, y_pred)))
print("Precision Score: {:2.2%}".format(precision_score(y_test_, y_pred)))
print("Recall Score: {:2.2%}".format(recall_score(y_test_, y_pred)))
print("F1 Score: {:2.2%}".format(f1_score(y_test_, y_pred)))
# Matrice de confusion
confmat = confusion_matrix(y_test_, y_pred)
sns.heatmap(confmat, annot=True, cbar=False)
plt.ylabel("Bonne étiquette")
plt.xlabel("Étiquette prédite")
plt.title("Matrice de confusion")
plt.show()
Séparatrices à Vastes Marges
1. Avec deux variables area
et smoothness
Le nombre total d'itérations a été atteint sans que l'algorithme ne converge, cela est dû à la dfférence d'echelle entre les variables.
# Genrate random values
x_1 = np.random.uniform(X_train_2["area"].min(), X_train_2["area"].max(), 5000)
x_2 = np.random.uniform(X_train_2["smoothness"].min(), X_train_2["smoothness"].max(), 5000)
y_ = svm.predict(np.column_stack([x_1, x_2]))
plt.figure(figsize=(14, 8))
sns.scatterplot(x=x_1, y=x_2, alpha=0.2, hue=y_)
y_test_ = [int(i) for i in y_test] # astuce
sns.scatterplot(x=X_test["area"], y=X_test["smoothness"], hue=y_test_)
plt.legend(loc="best")
plt.show()
Les performances sont assez modestes, voyons ce que ça donne avec les données centralisées.
2. Avec deux variables area
et smoothness
normalisées
Comme on peut le voir, la normalisation donne un meilleur résultat. En effet, la vitesse de convergence a largement augmenté.
# Genrate random values
x_1 = np.random.uniform(X_train_2["area"].min(), X_train_2["area"].max(), 5000)
x_2 = np.random.uniform(X_train_2["smoothness"].min(), X_train_2["smoothness"].max(), 5000)
y_ = svm.predict(np.column_stack([x_1, x_2]))
plt.figure(figsize=(14, 8))
sns.scatterplot(x=x_1, y=x_2, alpha=0.2, hue=y_)
y_test_ = [int(i) for i in y_test] # astuce
sns.scatterplot(x=X_test_scaled["area"], y=X_test_scaled["smoothness"], hue=y_test_)
plt.legend(loc="best")
plt.show()
On peut voir que certains points sont mal classés. En effet une droite ne peut pas séparer correctement l'ensemble des points
3. Avec un noyau non linéaire
# Genrate random values
x_1 = np.random.uniform(X_train_2["area"].min(), X_train_2["area"].max(), 5000)
x_2 = np.random.uniform(X_train_2["smoothness"].min(), X_train_2["smoothness"].max(), 5000)
y_ = svm.predict(np.column_stack([x_1, x_2]))
plt.figure(figsize=(14, 8))
sns.scatterplot(x=x_1, y=x_2, alpha=0.2, hue=y_)
y_test_ = [int(i) for i in y_test] # astuce
sns.scatterplot(x=X_test_scaled["area"], y=X_test_scaled["smoothness"], hue=y_test_)
plt.legend(loc="best")
plt.show()
Dans notre cas ici, l'utilisation d'un noyau n'a pas abouti à une amélioration significative, par contre une courbe pour séparer les données est bien plus appropriée que la droite.
4. Avec l'ensemble des huit variables
Optimisation des hyperparamètres
svm = SVC()
param_grid = {
"kernel": ["rbf", "linear", "poly", "sigmoid"],
"C": [0.75, 0.8, 0.9, 1.0],
"degree": [2, 3, 4, 5,],
"gamma": ["scale", "auto"],
}
grid = GridSearchCV(svm, param_grid=param_grid, cv=5, n_jobs=-1, verbose=1)
grid.fit(X_train_scaled, y_train)
model = grid.best_estimator_
print(model)
evaluate(model, X_test_scaled, y_test)
Sélection de modèle
Exportation
Testez le modèle en production ici https://prostate-cancer-diagnosis-jk.herokuapp.com
Pistes d'amélioration
- Éliminer les variables les moins pertinentes
- Essayer des approches ensemblistes
- Plus de données, il n'y a que 100 données dans l'exemple
- ...