""" Projet de Machine Learning : Prédiction de Maladies Cardiaques (Version corrigée) Dataset : UCI Heart Disease Dataset Objectif : Comparer deux architectures de réseaux de neurones pour la prédiction de maladies cardiaques """ import pandas as pd import numpy as np import urllib.request import ssl from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, BatchNormalization from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping import matplotlib.pyplot as plt # 1. Chargement des données avec gestion du SSL def load_data(): try: # Créer un contexte SSL non-vérifié (à utiliser avec précaution) ssl._create_default_https_context = ssl._create_unverified_context # URL du dataset url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data" # Définir les noms des colonnes columns = ['age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg', 'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'target'] print("Téléchargement des données...") # Télécharger directement dans un DataFrame data = pd.read_csv(url, names=columns) # En cas d'erreur, utiliser un dataset de démonstration if data.empty: raise Exception("Le dataset est vide") except Exception as e: print(f"Erreur lors du téléchargement des données: {e}") print("Utilisation d'un dataset de démonstration...") # Créer un petit dataset de démonstration np.random.seed(42) n_samples = 300 data = pd.DataFrame({ 'age': np.random.normal(55, 10, n_samples), 'sex': np.random.binomial(1, 0.5, n_samples), 'cp': np.random.randint(0, 4, n_samples), 'trestbps': np.random.normal(130, 20, n_samples), 'chol': np.random.normal(240, 40, n_samples), 'fbs': np.random.binomial(1, 0.2, n_samples), 'restecg': np.random.randint(0, 3, n_samples), 'thalach': np.random.normal(150, 20, n_samples), 'exang': np.random.binomial(1, 0.3, n_samples), 'oldpeak': np.random.normal(1, 1, n_samples), 'slope': np.random.randint(0, 3, n_samples), 'ca': np.random.randint(0, 4, n_samples), 'thal': np.random.randint(0, 3, n_samples), 'target': np.random.binomial(1, 0.4, n_samples) }) # Nettoyer les données data = data.replace('?', np.nan) data = data.dropna() # Convertir les colonnes en nombres for column in data.columns: data[column] = pd.to_numeric(data[column]) # Binariser la target (0 pour pas de maladie, 1 pour maladie) data['target'] = (data['target'] > 0).astype(int) return data # 2. Prétraitement des données def preprocess_data(data): # Séparer features et target X = data.drop('target', axis=1) y = data['target'] # Split train/test X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Standardisation scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) return X_train_scaled, X_test_scaled, y_train, y_test # 3. Premier modèle : Réseau dense classique def create_model_1(input_shape): model = Sequential([ Dense(64, activation='relu', input_shape=input_shape), BatchNormalization(), Dense(32, activation='relu'), Dropout(0.3), Dense(16, activation='relu'), Dense(1, activation='sigmoid') ]) model.compile(optimizer=Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy']) return model # 4. Second modèle : Réseau plus profond avec régularisation plus forte def create_model_2(input_shape): model = Sequential([ Dense(128, activation='relu', input_shape=input_shape), BatchNormalization(), Dropout(0.3), Dense(64, activation='relu'), BatchNormalization(), Dropout(0.3), Dense(32, activation='relu'), BatchNormalization(), Dense(16, activation='relu'), Dense(1, activation='sigmoid') ]) model.compile(optimizer=Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy']) return model # 5. Fonction d'entraînement et d'évaluation def train_and_evaluate(model, X_train, X_test, y_train, y_test, model_name): # Early stopping pour éviter le surapprentissage early_stopping = EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True, verbose=1 ) # Entraînement history = model.fit( X_train, y_train, validation_split=0.2, epochs=50, # Réduit pour la démonstration batch_size=32, callbacks=[early_stopping], verbose=1 ) # Évaluation test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0) print(f"\n{model_name} - Test Accuracy: {test_accuracy:.4f}") return history # 6. Visualisation des résultats def plot_training_history(history1, history2): plt.figure(figsize=(12, 4)) # Plot accuracy plt.subplot(1, 2, 1) plt.plot(history1.history['accuracy'], label='Model 1 accuracy') plt.plot(history1.history['val_accuracy'], label='Model 1 val accuracy') plt.plot(history2.history['accuracy'], label='Model 2 accuracy') plt.plot(history2.history['val_accuracy'], label='Model 2 val accuracy') plt.title('Model Accuracy') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.legend() # Plot loss plt.subplot(1, 2, 2) plt.plot(history1.history['loss'], label='Model 1 loss') plt.plot(history1.history['val_loss'], label='Model 1 val loss') plt.plot(history2.history['loss'], label='Model 2 loss') plt.plot(history2.history['val_loss'], label='Model 2 val loss') plt.title('Model Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend() plt.tight_layout() plt.show() # 7. Programme principal def main(): print("Loading data...") data = load_data() print("Data shape:", data.shape) print("\nPreprocessing data...") X_train, X_test, y_train, y_test = preprocess_data(data) input_shape = (X_train.shape[1],) print("\nTraining Model 1...") model1 = create_model_1(input_shape) history1 = train_and_evaluate(model1, X_train, X_test, y_train, y_test, "Model 1") print("\nTraining Model 2...") model2 = create_model_2(input_shape) history2 = train_and_evaluate(model2, X_train, X_test, y_train, y_test, "Model 2") print("\nPlotting results...") plot_training_history(history1, history2) if __name__ == "__main__": main() ''' Modèle 1 : Réseau Dense Classique - C'est une architecture relativement simple et légère avec 4 couches : 1. Première couche : 64 neurones avec activation ReLU - Cette couche initiale capture les patterns de base dans les données - Suivie d'une normalisation par lots (BatchNormalization) pour stabiliser l'apprentissage 2. Deuxième couche : 32 neurones avec activation ReLU - Suivie d'un Dropout de 30% pour éviter le surapprentissage 3. Troisième couche : 16 neurones avec activation ReLU - Réduit progressivement la dimensionnalité 4. Couche de sortie : 1 neurone avec activation sigmoid - Pour la prédiction binaire (malade/non malade) Modèle 2 : Réseau Plus Profond - C'est une architecture plus complexe avec 5 couches et plus de régularisation : 1. Première couche : 128 neurones avec activation ReLU - Commence avec plus de neurones pour capturer des patterns plus complexes - Suivie de BatchNormalization et Dropout 30% 2. Deuxième couche : 64 neurones avec activation ReLU - Également suivie de BatchNormalization et Dropout 3. Troisième couche : 32 neurones avec activation ReLU - Avec BatchNormalization 4. Quatrième couche : 16 neurones avec activation ReLU 5. Couche de sortie : 1 neurone avec activation sigmoid Les principales différences sont : 1. Complexité : Le modèle 2 a plus de paramètres et de couches 2. Régularisation : Le modèle 2 utilise plus de BatchNormalization et de Dropout 3. Capacité d'apprentissage : Le modèle 2 peut capturer des relations plus complexes dans les données L'idée est de comparer : - Une approche simple qui pourrait suffire pour ce problème médical relativement simple - Une approche plus complexe qui pourrait potentiellement capturer des patterns plus subtils Les deux modèles utilisent le même optimiseur (Adam) avec le même learning rate (0.001) pour une comparaison équitable. Cette configuration permet d'observer si la complexité supplémentaire du deuxième modèle apporte réellement un avantage en termes de performances, ou si le modèle plus simple est suffisant. - ReLU (Rectified Linear Unit) est une fonction d'activation très populaire en deep learning : ReLu (x) = max (0,x) - Le Dropout est une technique de régularisation cruciale en deep learning. Voici une explication détaillée : Principe de base : Pendant l'entraînement, à chaque itération Désactive aléatoirement un certain pourcentage de neurones Ces neurones sont temporairement "éteints" avec toutes leurs connexions Le pourcentage est défini par le paramètre de dropout (ex: 0.3 = 30% des neurones) - La BatchNormalization (ou normalisation par lots) est une technique très importante en deep learning. Voici une explication détaillée : Principe fondamental : Normalise les activations d'une couche pour chaque batch Maintient la moyenne proche de 0 et l'écart-type proche de 1 S'applique avant la fonction d'activation ''' ''' ## Exercice 1 : adapter le programme sur les données suivantes : https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data ## Exercice 2 : On vous demande d'implémenter 2 autres modèles en suivant le schéma du programme donné. Sur les 2 data-set. L'objectif est de rendre un rapport explicatif complet sur au moins un des modèles ; le code doit être commenté et des tests (changement de paramètres : itération, taux, couches réseaux) doivent être fait. ### Premier Modèle : Random Forest Classifier Ce modèle est particulièrement intéressant car il offre : - Une excellente performance sur les données médicales - Une interprétabilité des résultats - Une facilité relative d'implémentation Voici un exemple de structure pour l'implémentation : ```python from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV def create_model_rf(X_train, y_train): # Création du modèle avec des hyperparamètres de base rf_model = RandomForestClassifier( n_estimators=100, max_depth=10, random_state=42 ) # Définition des paramètres à optimiser param_grid = { 'n_estimators': [50, 100, 200], 'max_depth': [5, 10, 15], 'min_samples_split': [2, 5, 10] } # Recherche des meilleurs paramètres grid_search = GridSearchCV( rf_model, param_grid, cv=5, scoring='accuracy', n_jobs=-1 ) # Entraînement avec recherche des meilleurs paramètres grid_search.fit(X_train, y_train) return grid_search.best_estimator_ ``` ### Deuxième Modèle : XGBoost XGBoost est un algorithme de boosting très performant qui permet souvent d'obtenir d'excellents résultats. Voici une structure d'implémentation : ```python import xgboost as xgb from sklearn.model_selection import cross_val_score def create_model_xgb(X_train, y_train): # Création du modèle avec des paramètres de base xgb_model = xgb.XGBClassifier( learning_rate=0.1, n_estimators=100, max_depth=5, random_state=42 ) # Paramètres à optimiser param_grid = { 'learning_rate': [0.01, 0.1, 0.3], 'n_estimators': [50, 100, 200], 'max_depth': [3, 5, 7] } # Optimisation des hyperparamètres grid_search = GridSearchCV( xgb_model, param_grid, cv=5, scoring='accuracy', n_jobs=-1 ) grid_search.fit(X_train, y_train) return grid_search.best_estimator_ ``` Pour faciliter l'implémentation, voici les points essentiels à comprendre : Pour le Random Forest : - C'est un ensemble d'arbres de décision - Chaque arbre est entraîné sur un sous-ensemble aléatoire des données - La prédiction finale est obtenue par vote majoritaire des arbres - Les paramètres clés sont le nombre d'arbres (n_estimators) et la profondeur maximale (max_depth) Pour XGBoost : - C'est un algorithme de boosting qui construit les arbres séquentiellement - Chaque nouvel arbre corrige les erreurs des arbres précédents - Le learning_rate contrôle la contribution de chaque arbre - La profondeur des arbres (max_depth) limite la complexité du modèle Pour l'évaluation des modèles, on peut réutiliser les fonctions de visualisation existantes en les adaptant légèrement. Par exemple : ```python def plot_model_comparison(models_results): plt.figure(figsize=(10, 6)) for model_name, scores in models_results.items(): plt.plot(scores['val_accuracy'], label=f'{model_name} validation accuracy') plt.title('Model Comparison') plt.xlabel('Iteration') plt.ylabel('Accuracy') plt.legend() plt.show() ``` '''