{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# BirdCLEF+ 2025 - Classification Audio Multi-\u00e9tiquettes\n\n## License\n\nThis Notebook has been released under the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) open source license."}, {"cell_type": "markdown", "metadata": {}, "source": "# BirdCLEF+ 2025\n\nUne approche pour r\u00e9soudre le probl\u00e8me d'identification des esp\u00e8ces en danger dans la vall\u00e9e du Magdalena en Colombie \u00e0 travers leurs signatures acoustiques.\n\n![BirdCLEF+ 2025](https://storage.googleapis.com/kaggle-competitions/kaggle/51658/logos/header.png?t=2023-12-18-18-08-05)\n\n## Introduction\n\nLa comp\u00e9tition BirdCLEF+ 2025 vise \u00e0 d\u00e9velopper des m\u00e9thodes computationnelles pour identifier diff\u00e9rentes esp\u00e8ces (oiseaux, amphibiens, mammif\u00e8res et insectes) \u00e0 partir de leurs signatures acoustiques dans la vall\u00e9e du Magdalena en Colombie. Cette r\u00e9gion est l'une des plus riches en biodiversit\u00e9 au monde, mais elle est menac\u00e9e par la d\u00e9forestation et d'autres activit\u00e9s humaines.\n\nDans ce notebook, nous pr\u00e9sentons une approche compl\u00e8te pour r\u00e9soudre ce probl\u00e8me de classification audio multi-\u00e9tiquettes, en utilisant des techniques de traitement du signal et d'apprentissage profond."}, {"cell_type": "markdown", "metadata": {}, "source": "## Table des mati\u00e8res\n\n1. [Installation des d\u00e9pendances](#1.-Installation-des-d\u00e9pendances)\n2. [Exploration des donn\u00e9es](#2.-Exploration-des-donn\u00e9es)\n3. [Pr\u00e9traitement des donn\u00e9es audio](#3.-Pr\u00e9traitement-des-donn\u00e9es-audio)\n4. [Extraction de caract\u00e9ristiques](#4.-Extraction-de-caract\u00e9ristiques)\n5. [Construction du mod\u00e8le](#5.-Construction-du-mod\u00e8le)\n6. [Entra\u00eenement et validation](#6.-Entra\u00eenement-et-validation)\n7. [Optimisation des performances](#7.-Optimisation-des-performances)\n8. [G\u00e9n\u00e9ration des pr\u00e9dictions](#8.-G\u00e9n\u00e9ration-des-pr\u00e9dictions)\n9. [Soumission des r\u00e9sultats](#9.-Soumission-des-r\u00e9sultats)"}, {"cell_type": "markdown", "metadata": {}, "source": "## 1. Installation des d\u00e9pendances\n\nCommen\u00e7ons par installer les biblioth\u00e8ques n\u00e9cessaires pour notre projet."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Installation des biblioth\u00e8ques n\u00e9cessaires\n!pip install librosa soundfile tensorflow scikit-learn pandas numpy matplotlib seaborn tqdm optuna"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Importation des biblioth\u00e8ques\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport librosa\nimport librosa.display\nimport soundfile as sf\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, optimizers, callbacks\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score, precision_score, recall_score, roc_auc_score\nfrom tqdm.notebook import tqdm\nimport warnings\n\n# Ignorer les avertissements\nwarnings.filterwarnings('ignore')\n\n# D\u00e9finir les styles de visualisation\nsns.set_style(\"whitegrid\")\nplt.rcParams['figure.figsize'] = (12, 8)\n\n# V\u00e9rifier la disponibilit\u00e9 du GPU\nprint(\"GPU disponible:\", tf.config.list_physical_devices('GPU'))"}, {"cell_type": "markdown", "metadata": {}, "source": "## 2. Exploration des donn\u00e9es\n\nExplorons les donn\u00e9es de la comp\u00e9tition pour mieux comprendre leur structure et leurs caract\u00e9ristiques."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# D\u00e9finir les chemins des donn\u00e9es\nDATA_DIR = '../input/birdclef-2025/'\nTRAIN_AUDIO_DIR = os.path.join(DATA_DIR, 'train_audio')\nTEST_AUDIO_DIR = os.path.join(DATA_DIR, 'test_soundscapes')\nSAMPLE_SUBMISSION = os.path.join(DATA_DIR, 'sample_submission.csv')\n\n# Charger le fichier d'exemple de soumission pour comprendre la structure des pr\u00e9dictions attendues\nsample_submission = pd.read_csv(SAMPLE_SUBMISSION)\nprint(f\"Forme du fichier de soumission: {sample_submission.shape}\")\nprint(f\"Colonnes du fichier de soumission: {sample_submission.columns[:10]}...\")\nsample_submission.head()"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Analyser le nombre d'esp\u00e8ces \u00e0 pr\u00e9dire\nspecies_columns = sample_submission.columns[1:]\nnum_species = len(species_columns)\nprint(f\"Nombre d'esp\u00e8ces \u00e0 pr\u00e9dire: {num_species}\")\n\n# Afficher quelques exemples d'esp\u00e8ces\nprint(f\"Exemples d'esp\u00e8ces: {list(species_columns[:10])}...\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Explorer les fichiers audio d'entra\u00eenement\ndef explore_audio_directory(directory):\n    audio_files = []\n    for root, _, files in os.walk(directory):\n        for file in files:\n            if file.endswith('.ogg') or file.endswith('.mp3') or file.endswith('.wav'):\n                audio_files.append(os.path.join(root, file))\n    return audio_files\n\ntrain_audio_files = explore_audio_directory(TRAIN_AUDIO_DIR)\nprint(f\"Nombre de fichiers audio d'entra\u00eenement: {len(train_audio_files)}\")\nprint(f\"Exemples de fichiers audio: {[os.path.basename(f) for f in train_audio_files[:5]]}...\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Analyser la dur\u00e9e des fichiers audio d'entra\u00eenement (\u00e9chantillon)\ndef get_audio_duration(file_path):\n    try:\n        y, sr = librosa.load(file_path, sr=None, duration=10)  # Charger seulement les 10 premi\u00e8res secondes pour \u00eatre rapide\n        duration = librosa.get_duration(y=y, sr=sr)\n        return duration\n    except Exception as e:\n        print(f\"Erreur lors du chargement de {file_path}: {e}\")\n        return None\n\n# Analyser un \u00e9chantillon de fichiers audio\nsample_size = min(100, len(train_audio_files))\nsample_files = np.random.choice(train_audio_files, sample_size, replace=False)\ndurations = [get_audio_duration(f) for f in tqdm(sample_files)]\ndurations = [d for d in durations if d is not None]\n\n# Visualiser la distribution des dur\u00e9es\nplt.figure(figsize=(10, 6))\nplt.hist(durations, bins=20)\nplt.xlabel('Dur\u00e9e (secondes)')\nplt.ylabel('Nombre de fichiers')\nplt.title('Distribution des dur\u00e9es des fichiers audio (\u00e9chantillon)')\nplt.grid(True)\nplt.show()\n\nprint(f\"Dur\u00e9e moyenne: {np.mean(durations):.2f} secondes\")\nprint(f\"Dur\u00e9e m\u00e9diane: {np.median(durations):.2f} secondes\")\nprint(f\"Dur\u00e9e minimale: {np.min(durations):.2f} secondes\")\nprint(f\"Dur\u00e9e maximale: {np.max(durations):.2f} secondes\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Visualiser un spectrogramme d'exemple\ndef plot_spectrogram(file_path):\n    y, sr = librosa.load(file_path, sr=None)\n    plt.figure(figsize=(12, 8))\n    \n    # Forme d'onde\n    plt.subplot(3, 1, 1)\n    librosa.display.waveshow(y, sr=sr)\n    plt.title('Forme d\\'onde')\n    \n    # Spectrogramme\n    plt.subplot(3, 1, 2)\n    D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)\n    librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='log')\n    plt.colorbar(format='%+2.0f dB')\n    plt.title('Spectrogramme')\n    \n    # Spectrogramme Mel\n    plt.subplot(3, 1, 3)\n    S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)\n    S_dB = librosa.power_to_db(S, ref=np.max)\n    librosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel')\n    plt.colorbar(format='%+2.0f dB')\n    plt.title('Spectrogramme Mel')\n    \n    plt.tight_layout()\n    plt.show()\n    \n    return y, sr\n\n# S\u00e9lectionner un fichier audio al\u00e9atoire\nexample_file = np.random.choice(train_audio_files)\nprint(f\"Fichier d'exemple: {os.path.basename(example_file)}\")\ny, sr = plot_spectrogram(example_file)"}, {"cell_type": "markdown", "metadata": {}, "source": "## 3. Pr\u00e9traitement des donn\u00e9es audio\n\nImpl\u00e9mentons les fonctions de pr\u00e9traitement des donn\u00e9es audio pour pr\u00e9parer notre ensemble d'entra\u00eenement."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "class AudioPreprocessor:\n    \"\"\"Classe pour pr\u00e9traiter les donn\u00e9es audio\"\"\"\n    \n    def __init__(self, config=None):\n        \"\"\"Initialise le pr\u00e9processeur avec la configuration sp\u00e9cifi\u00e9e\"\"\"\n        # Configuration par d\u00e9faut\n        self.config = {\n            'sample_rate': 32000,      # Taux d'\u00e9chantillonnage cible\n            'n_mels': 128,             # Nombre de bandes mel\n            'n_fft': 1024,             # Taille de la FFT\n            'hop_length': 512,         # Longueur du saut pour la STFT\n            'segment_duration': 5,     # Dur\u00e9e des segments en secondes\n            'overlap': 2.5,            # Chevauchement entre segments en secondes\n            'min_duration': 2,         # Dur\u00e9e minimale pour un segment valide\n            'normalize': True,         # Normaliser l'audio\n            'augment': True            # Appliquer l'augmentation de donn\u00e9es\n        }\n        \n        # Mettre \u00e0 jour la configuration si fournie\n        if config:\n            self.config.update(config)\n    \n    def load_audio(self, file_path, start=0, duration=None):\n        \"\"\"Charge un fichier audio avec le taux d'\u00e9chantillonnage sp\u00e9cifi\u00e9\"\"\"\n        try:\n            y, sr = librosa.load(file_path, sr=self.config['sample_rate'], offset=start, duration=duration)\n            return y, sr\n        except Exception as e:\n            print(f\"Erreur lors du chargement de {file_path}: {e}\")\n            return None, None\n    \n    def normalize_audio(self, y):\n        \"\"\"Normalise l'audio pour avoir une amplitude maximale de 1\"\"\"\n        if y is None:\n            return None\n        \n        if self.config['normalize']:\n            max_amp = np.max(np.abs(y))\n            if max_amp > 0:\n                y = y / max_amp\n        return y\n    \n    def segment_audio(self, y, sr):\n        \"\"\"Segmente l'audio en segments de dur\u00e9e fixe avec chevauchement\"\"\"\n        if y is None or sr is None:\n            return []\n        \n        segment_length = int(self.config['segment_duration'] * sr)\n        overlap_length = int(self.config['overlap'] * sr)\n        hop_length = segment_length - overlap_length\n        \n        # Calculer le nombre de segments\n        n_segments = 1 + (len(y) - segment_length) // hop_length\n        if n_segments <= 0:\n            # Si l'audio est trop court, le renvoyer tel quel s'il d\u00e9passe la dur\u00e9e minimale\n            if len(y) >= int(self.config['min_duration'] * sr):\n                return [y]\n            else:\n                return []\n        \n        # Cr\u00e9er les segments\n        segments = []\n        for i in range(n_segments):\n            start = i * hop_length\n            end = start + segment_length\n            segment = y[start:end]\n            \n            # V\u00e9rifier si le segment est assez long\n            if len(segment) >= int(self.config['min_duration'] * sr):\n                # Padding si n\u00e9cessaire\n                if len(segment) < segment_length:\n                    segment = np.pad(segment, (0, segment_length - len(segment)), 'constant')\n                segments.append(segment)\n        \n        return segments\n    \n    def compute_melspectrogram(self, y, sr):\n        \"\"\"Calcule le spectrogramme mel d'un signal audio\"\"\"\n        if y is None or sr is None:\n            return None\n        \n        # Calculer le spectrogramme mel\n        mel_spec = librosa.feature.melspectrogram(\n            y=y,\n            sr=sr,\n            n_fft=self.config['n_fft'],\n            hop_length=self.config['hop_length'],\n            n_mels=self.config['n_mels']\n        )\n        \n        # Convertir en d\u00e9cibels\n        mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n        \n        return mel_spec_db\n    \n    def augment_audio(self, y, sr):\n        \"\"\"Applique des techniques d'augmentation de donn\u00e9es \u00e0 l'audio\"\"\"\n        if y is None or sr is None or not self.config['augment']:\n            return y\n        \n        # Appliquer des augmentations al\u00e9atoires\n        augmented = y.copy()\n        \n        # 1. Pitch shift (changement de hauteur)\n        if np.random.rand() > 0.5:\n            n_steps = np.random.uniform(-3, 3)\n            augmented = librosa.effects.pitch_shift(augmented, sr=sr, n_steps=n_steps)\n        \n        # 2. Time stretch (\u00e9tirement temporel)\n        if np.random.rand() > 0.5:\n            rate = np.random.uniform(0.8, 1.2)\n            augmented = librosa.effects.time_stretch(augmented, rate=rate)\n            \n            # Ajuster la longueur si n\u00e9cessaire\n            if len(augmented) > len(y):\n                augmented = augmented[:len(y)]\n            elif len(augmented) < len(y):\n                augmented = np.pad(augmented, (0, len(y) - len(augmented)), 'constant')\n        \n        # 3. Ajout de bruit blanc\n        if np.random.rand() > 0.5:\n            noise_level = np.random.uniform(0.001, 0.005)\n            noise = np.random.randn(len(augmented))\n            augmented = augmented + noise_level * noise\n        \n        # 4. Inversion de temps\n        if np.random.rand() > 0.8:  # Moins fr\u00e9quent\n            augmented = np.flip(augmented)\n        \n        return augmented\n    \n    def process_file(self, file_path, output_dir=None, augment=False):\n        \"\"\"Traite un fichier audio et sauvegarde les spectrogrammes mel\"\"\"\n        # Charger l'audio\n        y, sr = self.load_audio(file_path)\n        if y is None:\n            return []\n        \n        # Normaliser l'audio\n        y = self.normalize_audio(y)\n        \n        # Segmenter l'audio\n        segments = self.segment_audio(y, sr)\n        \n        # Traiter chaque segment\n        spectrograms = []\n        file_base = os.path.splitext(os.path.basename(file_path))[0]\n        \n        for i, segment in enumerate(segments):\n            # Appliquer l'augmentation si demand\u00e9\n            if augment:\n                segment = self.augment_audio(segment, sr)\n            \n            # Calculer le spectrogramme mel\n            mel_spec = self.compute_melspectrogram(segment, sr)\n            \n            if mel_spec is not None:\n                spectrograms.append(mel_spec)\n                \n                # Sauvegarder le spectrogramme si un r\u00e9pertoire de sortie est sp\u00e9cifi\u00e9\n                if output_dir:\n                    os.makedirs(output_dir, exist_ok=True)\n                    output_file = os.path.join(output_dir, f\"{file_base}_segment_{i}.npy\")\n                    np.save(output_file, mel_spec)\n        \n        return spectrograms\n    \n    def process_directory(self, input_dir, output_dir, pattern=\"*.ogg\", augment=False):\n        \"\"\"Traite tous les fichiers audio d'un r\u00e9pertoire\"\"\"\n        import glob\n        \n        # Trouver tous les fichiers audio correspondant au motif\n        files = []\n        for ext in ['.ogg', '.mp3', '.wav']:\n            files.extend(glob.glob(os.path.join(input_dir, f\"**/*{ext}\"), recursive=True))\n        \n        print(f\"Traitement de {len(files)} fichiers audio...\")\n        \n        # Traiter chaque fichier\n        all_spectrograms = []\n        for file in tqdm(files):\n            spectrograms = self.process_file(file, output_dir, augment)\n            all_spectrograms.extend(spectrograms)\n        \n        print(f\"Traitement termin\u00e9. {len(all_spectrograms)} spectrogrammes g\u00e9n\u00e9r\u00e9s.\")\n        \n        return all_spectrograms"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Tester le pr\u00e9processeur sur un fichier d'exemple\npreprocessor = AudioPreprocessor()\nexample_file = np.random.choice(train_audio_files)\nprint(f\"Pr\u00e9traitement du fichier: {os.path.basename(example_file)}\")\n\n# Pr\u00e9traiter le fichier\nspectrograms = preprocessor.process_file(example_file)\nprint(f\"Nombre de spectrogrammes g\u00e9n\u00e9r\u00e9s: {len(spectrograms)}\")\n\n# Visualiser un spectrogramme\nif spectrograms:\n    plt.figure(figsize=(10, 6))\n    librosa.display.specshow(spectrograms[0], sr=preprocessor.config['sample_rate'], \n                            hop_length=preprocessor.config['hop_length'], x_axis='time', y_axis='mel')\n    plt.colorbar(format='%+2.0f dB')\n    plt.title('Spectrogramme Mel')\n    plt.tight_layout()\n    plt.show()"}, {"cell_type": "markdown", "metadata": {}, "source": "## 4. Extraction de caract\u00e9ristiques\n\nImpl\u00e9mentons les fonctions d'extraction de caract\u00e9ristiques \u00e0 partir des spectrogrammes mel."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "class FeatureExtractor:\n    \"\"\"Classe pour extraire des caract\u00e9ristiques \u00e0 partir des spectrogrammes mel\"\"\"\n    \n    def __init__(self, config=None):\n        \"\"\"Initialise l'extracteur de caract\u00e9ristiques avec la configuration sp\u00e9cifi\u00e9e\"\"\"\n        # Configuration par d\u00e9faut\n        self.config = {\n            'sample_rate': 32000,  # Taux d'\u00e9chantillonnage\n            'n_mels': 128,         # Nombre de bandes mel\n            'features': [\n                'mfcc',            # Coefficients cepstraux \u00e0 l'\u00e9chelle de Mel\n                'spectral_contrast', # Contraste spectral\n                'chroma',          # Caract\u00e9ristiques chromatiques\n                'spectral_flatness', # Platitude spectrale\n                'spectral_bandwidth', # Largeur de bande spectrale\n                'spectral_rolloff', # Rolloff spectral\n                'zero_crossing_rate', # Taux de passage par z\u00e9ro\n                'rms'              # Valeur RMS\n            ],\n            'n_mfcc': 20,          # Nombre de MFCCs \u00e0 extraire\n            'n_chroma': 12,        # Nombre de bandes chromatiques\n            'standardize': True    # Standardiser les caract\u00e9ristiques\n        }\n        \n        # Mettre \u00e0 jour la configuration si fournie\n        if config:\n            self.config.update(config)\n    \n    def extract_features_from_melspec(self, mel_spec, sr=None):\n        \"\"\"Extrait des caract\u00e9ristiques \u00e0 partir d'un spectrogramme mel\"\"\"\n        if mel_spec is None:\n            return None\n        \n        # Convertir de dB \u00e0 puissance si n\u00e9cessaire\n        if np.min(mel_spec) < 0:\n            mel_spec_power = librosa.db_to_power(mel_spec)\n        else:\n            mel_spec_power = mel_spec\n        \n        features = {}\n        \n        # Extraire les caract\u00e9ristiques demand\u00e9es\n        if 'mfcc' in self.config['features']:\n            # Extraire les MFCCs directement \u00e0 partir du spectrogramme mel\n            mfccs = librosa.feature.mfcc(\n                S=mel_spec_power,\n                n_mfcc=self.config['n_mfcc'],\n                sr=self.config['sample_rate'] if sr is None else sr\n            )\n            features['mfcc_mean'] = np.mean(mfccs, axis=1)\n            features['mfcc_std'] = np.std(mfccs, axis=1)\n            features['mfcc_max'] = np.max(mfccs, axis=1)\n            features['mfcc_min'] = np.min(mfccs, axis=1)\n        \n        # Extraire des statistiques globales du spectrogramme mel\n        features['mel_mean'] = np.mean(mel_spec, axis=1)\n        features['mel_std'] = np.std(mel_spec, axis=1)\n        features['mel_max'] = np.max(mel_spec, axis=1)\n        features['mel_min'] = np.min(mel_spec, axis=1)\n        \n        # Calculer des caract\u00e9ristiques temporelles\n        features['temporal_flatness'] = np.mean(mel_spec, axis=0)\n        features['temporal_std'] = np.std(mel_spec, axis=0)\n        \n        # Calculer des caract\u00e9ristiques de forme\n        features['spectral_centroid'] = np.mean(np.sum(mel_spec * np.arange(mel_spec.shape[0])[:, np.newaxis], axis=0) / np.sum(mel_spec, axis=0))\n        features['spectral_bandwidth'] = np.mean(np.sqrt(np.sum(((np.arange(mel_spec.shape[0])[:, np.newaxis] - features['spectral_centroid']) ** 2) * mel_spec, axis=0) / np.sum(mel_spec, axis=0)))\n        \n        # Calculer des caract\u00e9ristiques de texture\n        features['spectral_contrast'] = np.mean(np.max(mel_spec, axis=0) - np.min(mel_spec, axis=0))\n        features['spectral_flatness'] = np.mean(np.exp(np.mean(np.log(mel_spec + 1e-10), axis=0)) / np.mean(mel_spec, axis=0))\n        \n        # Aplatir les caract\u00e9ristiques en un vecteur\n        feature_vector = self._flatten_features(features)\n        \n        return feature_vector\n    \n    def _flatten_features(self, features_dict):\n        \"\"\"Aplatit un dictionnaire de caract\u00e9ristiques en un vecteur\"\"\"\n        feature_vector = []\n        \n        for key, value in features_dict.items():\n            if isinstance(value, np.ndarray):\n                feature_vector.extend(value.flatten())\n            else:\n                feature_vector.append(value)\n        \n        return np.array(feature_vector)"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Tester l'extracteur de caract\u00e9ristiques sur un spectrogramme d'exemple\nif spectrograms:\n    extractor = FeatureExtractor()\n    features = extractor.extract_features_from_melspec(spectrograms[0])\n    print(f\"Nombre de caract\u00e9ristiques extraites: {len(features)}\")\n    \n    # Visualiser quelques caract\u00e9ristiques\n    plt.figure(figsize=(12, 6))\n    plt.plot(features[:50])\n    plt.title('Premi\u00e8res 50 caract\u00e9ristiques')\n    plt.xlabel('Index de caract\u00e9ristique')\n    plt.ylabel('Valeur')\n    plt.grid(True)\n    plt.show()"}, {"cell_type": "markdown", "metadata": {}, "source": "## 5. Construction du mod\u00e8le\n\nImpl\u00e9mentons diff\u00e9rentes architectures de mod\u00e8les pour la classification audio multi-\u00e9tiquettes."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "class BirdCLEFModel:\n    \"\"\"Classe pour construire et entra\u00eener un mod\u00e8le de classification multi-\u00e9tiquettes pour BirdCLEF+ 2025\"\"\"\n    \n    def __init__(self, config=None):\n        \"\"\"Initialise le mod\u00e8le avec la configuration sp\u00e9cifi\u00e9e\"\"\"\n        # Configuration par d\u00e9faut\n        self.config = {\n            'input_shape': (128, None, 1),  # (mel_bins, time_steps, channels)\n            'model_type': 'cnn',            # Type de mod\u00e8le: 'cnn', 'rnn', 'crnn'\n            'num_classes': 206,             # Nombre de classes (esp\u00e8ces)\n            'learning_rate': 0.001,         # Taux d'apprentissage\n            'batch_size': 32,               # Taille du batch\n            'epochs': 50,                   # Nombre d'\u00e9poques\n            'patience': 10,                 # Patience pour l'early stopping\n            'dropout_rate': 0.5,            # Taux de dropout\n            'use_augmentation': True,       # Utiliser l'augmentation de donn\u00e9es\n            'class_weights': None,          # Poids des classes pour g\u00e9rer le d\u00e9s\u00e9quilibre\n            'model_path': 'models/birdclef_model.h5'  # Chemin pour sauvegarder le mod\u00e8le\n        }\n        \n        # Mettre \u00e0 jour la configuration si fournie\n        if config:\n            self.config.update(config)\n        \n        # Initialiser le mod\u00e8le\n        self.model = None\n    \n    def build_cnn_model(self):\n        \"\"\"Construit un mod\u00e8le CNN pour la classification audio\"\"\"\n        inputs = layers.Input(shape=self.config['input_shape'])\n        \n        # Premier bloc convolutif\n        x = layers.Conv2D(32, (3, 3), activation='relu', padding='same')(inputs)\n        x = layers.BatchNormalization()(x)\n        x = layers.MaxPooling2D(pool_size=(2, 2))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Deuxi\u00e8me bloc convolutif\n        x = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.MaxPooling2D(pool_size=(2, 2))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Troisi\u00e8me bloc convolutif\n        x = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.MaxPooling2D(pool_size=(2, 2))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Quatri\u00e8me bloc convolutif\n        x = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.MaxPooling2D(pool_size=(2, 2))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Global pooling\n        x = layers.GlobalAveragePooling2D()(x)\n        \n        # Couches denses\n        x = layers.Dense(512, activation='relu')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Couche de sortie (sigmoid pour classification multi-\u00e9tiquettes)\n        outputs = layers.Dense(self.config['num_classes'], activation='sigmoid')(x)\n        \n        # Cr\u00e9er le mod\u00e8le\n        model = models.Model(inputs=inputs, outputs=outputs)\n        \n        # Compiler le mod\u00e8le\n        model.compile(\n            optimizer=optimizers.Adam(learning_rate=self.config['learning_rate']),\n            loss='binary_crossentropy',\n            metrics=['accuracy', tf.keras.metrics.AUC(name='auc')]\n        )\n        \n        return model\n    \n    def build_rnn_model(self):\n        \"\"\"Construit un mod\u00e8le RNN pour la classification audio\"\"\"\n        inputs = layers.Input(shape=self.config['input_shape'])\n        \n        # Reshape pour RNN (mel_bins, time_steps, channels) -> (time_steps, mel_bins * channels)\n        x = layers.Reshape((-1, self.config['input_shape'][0] * self.config['input_shape'][2]))(inputs)\n        \n        # Couches LSTM bidirectionnelles\n        x = layers.Bidirectional(layers.LSTM(128, return_sequences=True))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        x = layers.Bidirectional(layers.LSTM(128))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Couches denses\n        x = layers.Dense(256, activation='relu')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Couche de sortie (sigmoid pour classification multi-\u00e9tiquettes)\n        outputs = layers.Dense(self.config['num_classes'], activation='sigmoid')(x)\n        \n        # Cr\u00e9er le mod\u00e8le\n        model = models.Model(inputs=inputs, outputs=outputs)\n        \n        # Compiler le mod\u00e8le\n        model.compile(\n            optimizer=optimizers.Adam(learning_rate=self.config['learning_rate']),\n            loss='binary_crossentropy',\n            metrics=['accuracy', tf.keras.metrics.AUC(name='auc')]\n        )\n        \n        return model\n    \n    def build_crnn_model(self):\n        \"\"\"Construit un mod\u00e8le CRNN (CNN + RNN) pour la classification audio\"\"\"\n        inputs = layers.Input(shape=self.config['input_shape'])\n        \n        # Partie CNN\n        x = layers.Conv2D(32, (3, 3), activation='relu', padding='same')(inputs)\n        x = layers.BatchNormalization()(x)\n        x = layers.MaxPooling2D(pool_size=(2, 2))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        x = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.MaxPooling2D(pool_size=(2, 2))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Reshape pour RNN\n        # (batch, freq, time, channels) -> (batch, time, freq * channels)\n        x = layers.Reshape((-1, x.shape[1] * x.shape[3]))(x)\n        \n        # Partie RNN\n        x = layers.Bidirectional(layers.LSTM(128, return_sequences=True))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        x = layers.Bidirectional(layers.LSTM(128))(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Couches denses\n        x = layers.Dense(256, activation='relu')(x)\n        x = layers.BatchNormalization()(x)\n        x = layers.Dropout(self.config['dropout_rate'])(x)\n        \n        # Couche de sortie (sigmoid pour classification multi-\u00e9tiquettes)\n        outputs = layers.Dense(self.config['num_classes'], activation='sigmoid')(x)\n        \n        # Cr\u00e9er le mod\u00e8le\n        model = models.Model(inputs=inputs, outputs=outputs)\n        \n        # Compiler le mod\u00e8le\n        model.compile(\n            optimizer=optimizers.Adam(learning_rate=self.config['learning_rate']),\n            loss='binary_crossentropy',\n            metrics=['accuracy', tf.keras.metrics.AUC(name='auc')]\n        )\n        \n        return model\n    \n    def build_model(self):\n        \"\"\"Construit le mod\u00e8le selon le type sp\u00e9cifi\u00e9 dans la configuration\"\"\"\n        if self.config['model_type'] == 'cnn':\n            self.model = self.build_cnn_model()\n        elif self.config['model_type'] == 'rnn':\n            self.model = self.build_rnn_model()\n        elif self.config['model_type'] == 'crnn':\n            self.model = self.build_crnn_model()\n        else:\n            raise ValueError(f\"Type de mod\u00e8le non reconnu: {self.config['model_type']}\")\n        \n        return self.model"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Cr\u00e9er et afficher un mod\u00e8le CNN\nmodel_config = {\n    'input_shape': (128, 128, 1),  # Forme fixe pour l'exemple\n    'model_type': 'cnn',\n    'num_classes': len(species_columns)\n}\n\nmodel_builder = BirdCLEFModel(model_config)\nmodel = model_builder.build_model()\nmodel.summary()"}, {"cell_type": "markdown", "metadata": {}, "source": "## 6. Entra\u00eenement et validation\n\nImpl\u00e9mentons les fonctions d'entra\u00eenement et de validation du mod\u00e8le."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "def load_data(features_file, labels_file=None, test_size=0.2, random_state=42):\n    \"\"\"Charge les donn\u00e9es d'entra\u00eenement et de validation\"\"\"\n    # Charger les caract\u00e9ristiques\n    features_df = pd.read_csv(features_file)\n    \n    # S\u00e9parer les noms de fichiers et les caract\u00e9ristiques\n    file_names = features_df['file_name'].values\n    features = features_df.drop('file_name', axis=1).values\n    \n    # Charger les \u00e9tiquettes si un fichier est fourni\n    if labels_file:\n        labels_df = pd.read_csv(labels_file)\n        labels = labels_df.drop('file_name', axis=1).values\n    else:\n        # Cr\u00e9er des \u00e9tiquettes factices pour les tests\n        print(\"Aucun fichier d'\u00e9tiquettes fourni, cr\u00e9ation d'\u00e9tiquettes factices pour les tests.\")\n        num_classes = 206  # Nombre d'esp\u00e8ces dans BirdCLEF+ 2025\n        labels = np.random.randint(0, 2, size=(len(file_names), num_classes))\n    \n    # Diviser les donn\u00e9es en ensembles d'entra\u00eenement et de validation\n    X_train, X_val, y_train, y_val = train_test_split(\n        features, labels, test_size=test_size, random_state=random_state\n    )\n    \n    return X_train, X_val, y_train, y_val\n\ndef reshape_features_for_model(X, input_shape):\n    \"\"\"Reshape les caract\u00e9ristiques pour correspondre \u00e0 l'entr\u00e9e du mod\u00e8le\"\"\"\n    # D\u00e9terminer la forme cible\n    target_shape = (-1,) + input_shape\n    \n    # Reshape les caract\u00e9ristiques\n    try:\n        X_reshaped = X.reshape(target_shape)\n        return X_reshaped\n    except ValueError:\n        print(f\"Erreur lors du reshape des caract\u00e9ristiques de {X.shape} \u00e0 {target_shape}\")\n        # Essayer une approche alternative\n        n_samples = X.shape[0]\n        X_reshaped = np.zeros((n_samples,) + input_shape)\n        \n        # Copier autant de donn\u00e9es que possible\n        for i in range(n_samples):\n            # D\u00e9terminer les dimensions \u00e0 copier\n            copy_shape = tuple(min(dim, X.shape[j+1]) if j+1 < len(X.shape) else min(dim, 1) \n                              for j, dim in enumerate(input_shape))\n            \n            # Cr\u00e9er des slices pour la copie\n            slices_src = tuple(slice(None, dim) for dim in copy_shape)\n            slices_dst = tuple(slice(None, dim) for dim in copy_shape)\n            \n            # Copier les donn\u00e9es\n            if len(X.shape) == 2:  # Caract\u00e9ristiques 1D\n                X_reshaped[i, :copy_shape[0], 0, 0] = X[i, :copy_shape[0]]\n            else:  # Caract\u00e9ristiques multidimensionnelles\n                X_reshaped[i][slices_dst] = X[i][slices_src]\n        \n        return X_reshaped\n\ndef calculate_class_weights(y_train):\n    \"\"\"Calcule les poids des classes pour g\u00e9rer le d\u00e9s\u00e9quilibre\"\"\"\n    # Calculer le nombre d'\u00e9chantillons positifs pour chaque classe\n    positive_counts = np.sum(y_train, axis=0)\n    \n    # Calculer le nombre total d'\u00e9chantillons\n    n_samples = y_train.shape[0]\n    \n    # Calculer les poids des classes\n    class_weights = {}\n    for i in range(y_train.shape[1]):\n        # \u00c9viter la division par z\u00e9ro\n        if positive_counts[i] > 0:\n            # Formule de poids inversement proportionnel \u00e0 la fr\u00e9quence\n            weight = n_samples / (2 * positive_counts[i])\n        else:\n            weight = 1.0\n        \n        class_weights[i] = weight\n    \n    return class_weights"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Cr\u00e9er des donn\u00e9es factices pour tester l'entra\u00eenement\nn_samples = 1000\nn_features = 500\nn_classes = len(species_columns)\n\n# Caract\u00e9ristiques et \u00e9tiquettes factices\nX_train_dummy = np.random.rand(n_samples, n_features)\nX_val_dummy = np.random.rand(n_samples // 5, n_features)\ny_train_dummy = np.random.randint(0, 2, size=(n_samples, n_classes))\ny_val_dummy = np.random.randint(0, 2, size=(n_samples // 5, n_classes))\n\n# Calculer les poids des classes\nclass_weights = calculate_class_weights(y_train_dummy)\n\n# Reshape les caract\u00e9ristiques pour le mod\u00e8le\ninput_shape = (128, 128, 1)  # Forme fixe pour l'exemple\nX_train_reshaped = reshape_features_for_model(X_train_dummy, input_shape)\nX_val_reshaped = reshape_features_for_model(X_val_dummy, input_shape)\n\nprint(f\"Forme des caract\u00e9ristiques d'entra\u00eenement: {X_train_reshaped.shape}\")\nprint(f\"Forme des \u00e9tiquettes d'entra\u00eenement: {y_train_dummy.shape}\")"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Entra\u00eener le mod\u00e8le sur les donn\u00e9es factices (juste pour d\u00e9monstration)\n# Dans un cas r\u00e9el, nous utiliserions les vraies donn\u00e9es\n\n# Configuration du mod\u00e8le\nmodel_config = {\n    'input_shape': input_shape,\n    'model_type': 'cnn',\n    'num_classes': n_classes,\n    'learning_rate': 0.001,\n    'batch_size': 32,\n    'epochs': 5,  # R\u00e9duit pour la d\u00e9monstration\n    'patience': 3,\n    'dropout_rate': 0.5,\n    'class_weights': class_weights\n}\n\n# Cr\u00e9er et construire le mod\u00e8le\nmodel_builder = BirdCLEFModel(model_config)\nmodel = model_builder.build_model()\n\n# Callbacks\ncallbacks_list = [\n    callbacks.EarlyStopping(\n        monitor='val_auc',\n        patience=model_config['patience'],\n        restore_best_weights=True,\n        mode='max'\n    ),\n    callbacks.ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.5,\n        patience=2,\n        min_lr=1e-6\n    )\n]\n\n# Entra\u00eener le mod\u00e8le\nhistory = model.fit(\n    X_train_reshaped, y_train_dummy,\n    batch_size=model_config['batch_size'],\n    epochs=model_config['epochs'],\n    validation_data=(X_val_reshaped, y_val_dummy),\n    callbacks=callbacks_list,\n    class_weight=model_config['class_weights']\n)"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Visualiser l'historique d'entra\u00eenement\ndef visualize_training_history(history):\n    plt.figure(figsize=(12, 4))\n    \n    # Visualiser la perte\n    plt.subplot(1, 2, 1)\n    plt.plot(history.history['loss'], label='Train')\n    plt.plot(history.history['val_loss'], label='Validation')\n    plt.title('Perte')\n    plt.xlabel('\u00c9poque')\n    plt.ylabel('Perte')\n    plt.legend()\n    \n    # Visualiser l'AUC\n    plt.subplot(1, 2, 2)\n    plt.plot(history.history['auc'], label='Train')\n    plt.plot(history.history['val_auc'], label='Validation')\n    plt.title('AUC')\n    plt.xlabel('\u00c9poque')\n    plt.ylabel('AUC')\n    plt.legend()\n    \n    plt.tight_layout()\n    plt.show()\n\n# Visualiser l'historique d'entra\u00eenement\nvisualize_training_history(history)"}, {"cell_type": "markdown", "metadata": {}, "source": "## 7. Optimisation des performances\n\nImpl\u00e9mentons des techniques d'optimisation des performances du mod\u00e8le."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Exemple d'optimisation des hyperparam\u00e8tres avec Optuna\n# Note: Cette cellule est comment\u00e9e car l'ex\u00e9cution compl\u00e8te prendrait trop de temps\n# Dans un cas r\u00e9el, nous ex\u00e9cuterions cette optimisation\n\n'''\nimport optuna\nfrom optuna.integration import TFKerasPruningCallback\n\ndef objective(trial):\n    # Hyperparam\u00e8tres \u00e0 optimiser\n    model_type = trial.suggest_categorical('model_type', ['cnn', 'rnn', 'crnn'])\n    learning_rate = trial.suggest_float('learning_rate', 1e-5, 1e-2, log=True)\n    batch_size = trial.suggest_categorical('batch_size', [16, 32, 64, 128])\n    dropout_rate = trial.suggest_float('dropout_rate', 0.2, 0.7)\n    \n    # Configuration du mod\u00e8le\n    model_config = {\n        'input_shape': input_shape,\n        'model_type': model_type,\n        'num_classes': n_classes,\n        'learning_rate': learning_rate,\n        'batch_size': batch_size,\n        'epochs': 20,\n        'patience': 5,\n        'dropout_rate': dropout_rate,\n        'class_weights': class_weights\n    }\n    \n    # Cr\u00e9er et construire le mod\u00e8le\n    model_builder = BirdCLEFModel(model_config)\n    model = model_builder.build_model()\n    \n    # Callbacks\n    callbacks_list = [\n        callbacks.EarlyStopping(\n            monitor='val_auc',\n            patience=model_config['patience'],\n            restore_best_weights=True,\n            mode='max'\n        ),\n        TFKerasPruningCallback(trial, 'val_auc')\n    ]\n    \n    # Entra\u00eener le mod\u00e8le\n    history = model.fit(\n        X_train_reshaped, y_train_dummy,\n        batch_size=model_config['batch_size'],\n        epochs=model_config['epochs'],\n        validation_data=(X_val_reshaped, y_val_dummy),\n        callbacks=callbacks_list,\n        class_weight=model_config['class_weights'],\n        verbose=0\n    )\n    \n    # Retourner la meilleure valeur d'AUC\n    return max(history.history['val_auc'])\n\n# Cr\u00e9er l'\u00e9tude Optuna\nstudy = optuna.create_study(direction='maximize', pruner=optuna.pruners.MedianPruner())\nstudy.optimize(objective, n_trials=50)\n\n# Afficher les meilleurs hyperparam\u00e8tres\nprint(\"Meilleurs hyperparam\u00e8tres:\")\nprint(study.best_params)\nprint(f\"Meilleure valeur d'AUC: {study.best_value:.4f}\")\n'''"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Exemple d'ensemble de mod\u00e8les\n# Note: Cette cellule est comment\u00e9e car l'ex\u00e9cution compl\u00e8te prendrait trop de temps\n# Dans un cas r\u00e9el, nous cr\u00e9erions un ensemble de mod\u00e8les\n\n'''\nfrom sklearn.model_selection import KFold\n\n# Cr\u00e9er un ensemble de mod\u00e8les avec validation crois\u00e9e\nn_models = 5\nkf = KFold(n_splits=n_models, shuffle=True, random_state=42)\n\nensemble_models = []\nfold_metrics = []\n\nfor fold, (train_idx, val_idx) in enumerate(kf.split(X_train_reshaped)):\n    print(f\"\\nEntra\u00eenement du mod\u00e8le {fold+1}/{n_models}\")\n    \n    # Diviser les donn\u00e9es pour cette fold\n    X_fold_train, X_fold_val = X_train_reshaped[train_idx], X_train_reshaped[val_idx]\n    y_fold_train, y_fold_val = y_train_dummy[train_idx], y_train_dummy[val_idx]\n    \n    # Calculer les poids des classes pour cette fold\n    fold_class_weights = calculate_class_weights(y_fold_train)\n    \n    # Configuration du mod\u00e8le\n    fold_config = {\n        'input_shape': input_shape,\n        'model_type': 'cnn',  # Utiliser le meilleur type de mod\u00e8le trouv\u00e9 par Optuna\n        'num_classes': n_classes,\n        'learning_rate': 0.001,  # Utiliser le meilleur taux d'apprentissage trouv\u00e9 par Optuna\n        'batch_size': 32,  # Utiliser la meilleure taille de batch trouv\u00e9e par Optuna\n        'epochs': 20,\n        'patience': 5,\n        'dropout_rate': 0.5,  # Utiliser le meilleur taux de dropout trouv\u00e9 par Optuna\n        'class_weights': fold_class_weights\n    }\n    \n    # Cr\u00e9er et construire le mod\u00e8le\n    model_builder = BirdCLEFModel(fold_config)\n    model = model_builder.build_model()\n    \n    # Callbacks\n    callbacks_list = [\n        callbacks.EarlyStopping(\n            monitor='val_auc',\n            patience=fold_config['patience'],\n            restore_best_weights=True,\n            mode='max'\n        ),\n        callbacks.ReduceLROnPlateau(\n            monitor='val_loss',\n            factor=0.5,\n            patience=2,\n            min_lr=1e-6\n        )\n    ]\n    \n    # Entra\u00eener le mod\u00e8le\n    history = model.fit(\n        X_fold_train, y_fold_train,\n        batch_size=fold_config['batch_size'],\n        epochs=fold_config['epochs'],\n        validation_data=(X_fold_val, y_fold_val),\n        callbacks=callbacks_list,\n        class_weight=fold_config['class_weights']\n    )\n    \n    # \u00c9valuer le mod\u00e8le\n    metrics = model.evaluate(X_val_reshaped, y_val_dummy)\n    fold_metrics.append({\n        'loss': metrics[0],\n        'accuracy': metrics[1],\n        'auc': metrics[2]\n    })\n    \n    # Ajouter le mod\u00e8le \u00e0 l'ensemble\n    ensemble_models.append(model)\n\n# Pr\u00e9dire avec l'ensemble\ndef predict_with_ensemble(ensemble_models, X):\n    # Pr\u00e9dire avec chaque mod\u00e8le\n    predictions = []\n    for model in ensemble_models:\n        pred = model.predict(X)\n        predictions.append(pred)\n    \n    # Moyenner les pr\u00e9dictions\n    ensemble_pred = np.mean(predictions, axis=0)\n    return ensemble_pred\n\n# \u00c9valuer l'ensemble\nensemble_pred = predict_with_ensemble(ensemble_models, X_val_reshaped)\nensemble_pred_binary = (ensemble_pred > 0.5).astype(int)\n\n# Calculer les m\u00e9triques\nensemble_metrics = {\n    'f1_micro': f1_score(y_val_dummy, ensemble_pred_binary, average='micro'),\n    'f1_macro': f1_score(y_val_dummy, ensemble_pred_binary, average='macro'),\n    'precision_micro': precision_score(y_val_dummy, ensemble_pred_binary, average='micro'),\n    'precision_macro': precision_score(y_val_dummy, ensemble_pred_binary, average='macro'),\n    'recall_micro': recall_score(y_val_dummy, ensemble_pred_binary, average='micro'),\n    'recall_macro': recall_score(y_val_dummy, ensemble_pred_binary, average='macro'),\n    'roc_auc': roc_auc_score(y_val_dummy, ensemble_pred, average='macro')\n}\n\nprint(\"\\nM\u00e9triques de l'ensemble:\")\nfor metric, value in ensemble_metrics.items():\n    print(f\"{metric}: {value:.4f}\")\n'''"}, {"cell_type": "markdown", "metadata": {}, "source": "## 8. G\u00e9n\u00e9ration des pr\u00e9dictions\n\nImpl\u00e9mentons les fonctions pour g\u00e9n\u00e9rer des pr\u00e9dictions sur l'ensemble de test."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "def preprocess_test_data(test_audio_dir, output_dir=None):\n    \"\"\"Pr\u00e9traite les donn\u00e9es de test\"\"\"\n    # Cr\u00e9er le pr\u00e9processeur\n    preprocessor = AudioPreprocessor()\n    \n    # Pr\u00e9traiter les fichiers audio\n    spectrograms = preprocessor.process_directory(test_audio_dir, output_dir, pattern=\"*.ogg\")\n    \n    return spectrograms\n\ndef extract_features_from_spectrograms(spectrograms, output_file=None):\n    \"\"\"Extrait des caract\u00e9ristiques \u00e0 partir des spectrogrammes\"\"\"\n    # Cr\u00e9er l'extracteur de caract\u00e9ristiques\n    extractor = FeatureExtractor()\n    \n    # Extraire les caract\u00e9ristiques\n    features_list = []\n    file_names = []\n    \n    for i, spec in enumerate(tqdm(spectrograms)):\n        features = extractor.extract_features_from_melspec(spec)\n        if features is not None:\n            features_list.append(features)\n            file_names.append(f\"test_audio_{i}.ogg\")\n    \n    # Cr\u00e9er un DataFrame avec les caract\u00e9ristiques\n    features_df = pd.DataFrame(features_list)\n    features_df['file_name'] = file_names\n    \n    # Sauvegarder les caract\u00e9ristiques\n    if output_file:\n        features_df.to_csv(output_file, index=False)\n    \n    return features_df\n\ndef generate_submission_file(predictions, row_ids, species_columns, output_file):\n    \"\"\"G\u00e9n\u00e8re un fichier de soumission\"\"\"\n    # Cr\u00e9er un DataFrame de soumission\n    submission = pd.DataFrame()\n    submission['row_id'] = row_ids\n    \n    # Ajouter les pr\u00e9dictions pour chaque esp\u00e8ce\n    for i, species in enumerate(species_columns):\n        submission[species] = predictions[:, i]\n    \n    # Sauvegarder le fichier de soumission\n    submission.to_csv(output_file, index=False)\n    \n    return submission"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Exemple de g\u00e9n\u00e9ration de pr\u00e9dictions\n# Note: Cette cellule est comment\u00e9e car nous n'avons pas de vraies donn\u00e9es de test\n# Dans un cas r\u00e9el, nous ex\u00e9cuterions ce code\n\n'''\n# Pr\u00e9traiter les donn\u00e9es de test\ntest_spectrograms = preprocess_test_data(TEST_AUDIO_DIR, output_dir=\"test_spectrograms\")\n\n# Extraire les caract\u00e9ristiques\ntest_features_df = extract_features_from_spectrograms(test_spectrograms, output_file=\"test_features.csv\")\n\n# S\u00e9parer les noms de fichiers et les caract\u00e9ristiques\ntest_file_names = test_features_df['file_name'].values\ntest_features = test_features_df.drop('file_name', axis=1).values\n\n# Reshape les caract\u00e9ristiques pour le mod\u00e8le\ntest_features_reshaped = reshape_features_for_model(test_features, input_shape)\n\n# Pr\u00e9dire avec le mod\u00e8le ou l'ensemble\nif ensemble_models:\n    print(\"Pr\u00e9diction avec l'ensemble de mod\u00e8les...\")\n    test_predictions = predict_with_ensemble(ensemble_models, test_features_reshaped)\nelse:\n    print(\"Pr\u00e9diction avec le mod\u00e8le unique...\")\n    test_predictions = model.predict(test_features_reshaped)\n\n# Charger le fichier d'exemple de soumission pour obtenir les row_ids\nsample_submission = pd.read_csv(SAMPLE_SUBMISSION)\nrow_ids = sample_submission['row_id'].values\nspecies_cols = sample_submission.columns[1:].tolist()\n\n# G\u00e9n\u00e9rer le fichier de soumission\nsubmission = generate_submission_file(test_predictions, row_ids, species_cols, \"submission.csv\")\nsubmission.head()\n'''"}, {"cell_type": "markdown", "metadata": {}, "source": "## 9. Soumission des r\u00e9sultats\n\nImpl\u00e9mentons les fonctions pour soumettre les r\u00e9sultats \u00e0 la comp\u00e9tition Kaggle."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "def submit_to_kaggle(submission_file, competition_name, message=None):\n    \"\"\"Soumet les r\u00e9sultats \u00e0 la comp\u00e9tition Kaggle\"\"\"\n    import subprocess\n    \n    # V\u00e9rifier si le fichier de soumission existe\n    if not os.path.exists(submission_file):\n        print(f\"Le fichier de soumission {submission_file} n'existe pas.\")\n        return False\n    \n    # Construire la commande de soumission\n    cmd = [\"kaggle\", \"competitions\", \"submit\", \"-c\", competition_name, \"-f\", submission_file]\n    \n    # Ajouter le message de soumission si sp\u00e9cifi\u00e9\n    if message:\n        cmd.extend([\"-m\", message])\n    \n    # Ex\u00e9cuter la commande\n    try:\n        print(f\"Soumission du fichier {submission_file} \u00e0 la comp\u00e9tition {competition_name}...\")\n        result = subprocess.run(cmd, capture_output=True, text=True)\n        \n        if result.returncode == 0:\n            print(\"Soumission r\u00e9ussie!\")\n            print(result.stdout)\n            return True\n        else:\n            print(f\"Erreur lors de la soumission: {result.stderr}\")\n            return False\n    except Exception as e:\n        print(f\"Exception lors de la soumission: {e}\")\n        return False"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "source": "# Exemple de soumission \u00e0 Kaggle\n# Note: Cette cellule est comment\u00e9e car nous n'avons pas de vrai fichier de soumission\n# Dans un cas r\u00e9el, nous ex\u00e9cuterions ce code\n\n'''\n# Soumettre les r\u00e9sultats \u00e0 Kaggle\nsubmission_success = submit_to_kaggle(\n    submission_file=\"submission.csv\",\n    competition_name=\"birdclef-2025\",\n    message=\"Soumission avec mod\u00e8le CNN et ensemble de 5 mod\u00e8les\"\n)\n\nif submission_success:\n    print(\"Soumission r\u00e9ussie! V\u00e9rifiez votre score sur la page de la comp\u00e9tition.\")\nelse:\n    print(\"La soumission a \u00e9chou\u00e9. V\u00e9rifiez les erreurs ci-dessus.\")\n'''"}, {"cell_type": "markdown", "metadata": {}, "source": "## Conclusion\n\nDans ce notebook, nous avons pr\u00e9sent\u00e9 une approche compl\u00e8te pour r\u00e9soudre le probl\u00e8me de classification audio multi-\u00e9tiquettes de la comp\u00e9tition BirdCLEF+ 2025. Nous avons impl\u00e9ment\u00e9 :\n\n1. Des fonctions de pr\u00e9traitement des donn\u00e9es audio pour normaliser, segmenter et convertir les fichiers audio en spectrogrammes mel\n2. Des fonctions d'extraction de caract\u00e9ristiques \u00e0 partir des spectrogrammes mel\n3. Diff\u00e9rentes architectures de mod\u00e8les (CNN, RNN, CRNN) pour la classification multi-\u00e9tiquettes\n4. Des techniques d'optimisation des performances, notamment l'optimisation des hyperparam\u00e8tres et les ensembles de mod\u00e8les\n5. Des fonctions pour g\u00e9n\u00e9rer des pr\u00e9dictions sur l'ensemble de test et soumettre les r\u00e9sultats \u00e0 la comp\u00e9tition\n\nCette approche peut \u00eatre adapt\u00e9e et am\u00e9lior\u00e9e en fonction des r\u00e9sultats obtenus sur le leaderboard de la comp\u00e9tition."}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12"}}, "nbformat": 4, "nbformat_minor": 4}