{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":11075449,"sourceType":"datasetVersion","datasetId":6902504},{"sourceId":301773,"sourceType":"modelInstanceVersion","modelInstanceId":257720,"modelId":279000}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"The dataset is a multi-class, multi-species audio classification challenge with 206 species spanning birds, amphibians, mammals, and insects, sourced from xeno-canto.org, iNaturalist, and the Colombian Sound Archive. Training data consists of short audio clips (32 kHz, .ogg format) with rich metadata including primary/secondary labels, geolocation, recording quality, and collection source. The machine learning task involves predicting species presence probabilities in 1-minute soundscape segments, requiring robust feature extraction, handling of imbalanced classes, and managing complex acoustic environments with potential overlapping species sounds.\n\nAfter some exploring, I found a baseline model - `BirdCLEF+2025-EfficientNet B0`.\nThe model consists of \nI will be fine-tuning this model.","metadata":{}},{"cell_type":"markdown","source":"First, some imports","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport warnings\nimport logging\nimport time\nimport math\nimport cv2\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom torch.utils.data import Subset\nfrom sklearn.preprocessing import LabelEncoder\nimport timm\nfrom tqdm.auto import tqdm\n\nwarnings.filterwarnings(\"ignore\")\nlogging.basicConfig(level=logging.ERROR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:24.038906Z","iopub.execute_input":"2025-05-03T13:15:24.039312Z","iopub.status.idle":"2025-05-03T13:15:39.914350Z","shell.execute_reply.started":"2025-05-03T13:15:24.039269Z","shell.execute_reply":"2025-05-03T13:15:39.913034Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"This is a basic config class.","metadata":{}},{"cell_type":"code","source":"class CFG:\n \n    test_soundscapes = '/kaggle/input/birdclef-2025/test_soundscapes'\n    submission_csv = '/kaggle/input/birdclef-2025/sample_submission.csv'\n    taxonomy_csv = '/kaggle/input/birdclef-2025/taxonomy.csv'\n    train_csv = '/kaggle/input/birdclef-2025/train.csv'\n    train_data_dir = '/kaggle/input/birdclef-2025/train_audio'\n    model_path = '/kaggle/input/birdclef-2025-efficientnet-b0'  \n    \n    # Audio parameters\n    FS = 32000  \n    WINDOW_SIZE = 5  \n    \n    # Mel spectrogram parameters\n    N_FFT = 1024\n    HOP_LENGTH = 512\n    N_MELS = 148\n    FMIN = 50\n    FMAX = 14000\n    TARGET_SHAPE = (256, 256)\n    \n    model_name = 'efficientnet_b0'\n    in_channels = 1\n    device = 'cpu'  \n    \n    # Inference parameters\n    batch_size = 16\n    use_tta = False  \n    tta_count = 2\n    threshold = 0.5\n    \n    use_specific_folds = False  # If False, use all found models\n    folds = [0, 1]  # Used only if use_specific_folds is True\n    \n    debug = False\n    debug_count = 3\n\n    lr = 0.04\n    epochs = 1\n\ncfg = CFG()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:39.916145Z","iopub.execute_input":"2025-05-03T13:15:39.916529Z","iopub.status.idle":"2025-05-03T13:15:39.924275Z","shell.execute_reply.started":"2025-05-03T13:15:39.916498Z","shell.execute_reply":"2025-05-03T13:15:39.922657Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Using device: {cfg.device}\")\nprint(f\"Loading taxonomy data...\")\ntaxonomy_df = pd.read_csv(cfg.taxonomy_csv)\nspecies_ids = taxonomy_df['primary_label'].tolist()\nnum_classes = len(species_ids)\nprint(f\"Number of classes: {num_classes}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:39.927023Z","iopub.execute_input":"2025-05-03T13:15:39.927628Z","iopub.status.idle":"2025-05-03T13:15:39.988589Z","shell.execute_reply.started":"2025-05-03T13:15:39.927578Z","shell.execute_reply":"2025-05-03T13:15:39.987410Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"This is the model.\nThe model uses a pretrained convolutional neural network (CNN) backbone from the timm library. The backbone is modified to remove the default classifier and replace it with a custom classification head.\n\nSome notes on the model: \n- `self.backbone` uses a model from timm.create_model(cfg.model_name, pretrained=False, in_chans=cfg.in_channels).\n- `cfg.model_name` determines the CNN architecture (e.g., efficientnet, resnet).\n- EfficientNet removes the final classifier layer (self.backbone.classifier = nn.Identity()).","metadata":{}},{"cell_type":"code","source":"class BirdCLEFModel(nn.Module):\n    def __init__(self, cfg, num_classes):\n        super().__init__()\n        self.cfg = cfg\n        \n        self.backbone = timm.create_model(\n            cfg.model_name,\n            pretrained=False,  \n            in_chans=cfg.in_channels,\n            drop_rate=0.0,    \n            drop_path_rate=0.0\n        )\n        \n        if 'efficientnet' in cfg.model_name:\n            backbone_out = self.backbone.classifier.in_features\n            self.backbone.classifier = nn.Identity()\n        elif 'resnet' in cfg.model_name:\n            backbone_out = self.backbone.fc.in_features\n            self.backbone.fc = nn.Identity()\n        else:\n            backbone_out = self.backbone.get_classifier().in_features\n            self.backbone.reset_classifier(0, '')\n        \n        self.pooling = nn.AdaptiveAvgPool2d(1)\n        self.feat_dim = backbone_out\n        self.classifier = nn.Linear(backbone_out, num_classes)\n        \n    def forward(self, x):\n        features = self.backbone(x)\n        \n        if isinstance(features, dict):\n            features = features['features']\n            \n        if len(features.shape) == 4:\n            features = self.pooling(features)\n            features = features.view(features.size(0), -1)\n        \n        logits = self.classifier(features)\n        return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:39.990444Z","iopub.execute_input":"2025-05-03T13:15:39.990845Z","iopub.status.idle":"2025-05-03T13:15:39.999917Z","shell.execute_reply.started":"2025-05-03T13:15:39.990809Z","shell.execute_reply":"2025-05-03T13:15:39.998187Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The audio2melspec function converts raw audio data into a mel spectrogram, ensuring that missing values (NaNs) are handled by replacing them with the mean signal value. The spectrogram is then converted to decibels and normalized between 0 and 1 for consistent input scaling in machine learning models.\n\nThe process_audio_segment function prepares an audio segment, padding shorter segments as needed. It then converts the processed audio into a mel spectrogram using audio2melspec.","metadata":{}},{"cell_type":"code","source":"def audio2melspec(audio_data, cfg):\n    \"\"\"Convert audio data to mel spectrogram\"\"\"\n    if np.isnan(audio_data).any():\n        mean_signal = np.nanmean(audio_data)\n        audio_data = np.nan_to_num(audio_data, nan=mean_signal)\n\n    mel_spec = librosa.feature.melspectrogram(\n        y=audio_data,\n        sr=cfg.FS,\n        n_fft=cfg.N_FFT,\n        hop_length=cfg.HOP_LENGTH,\n        n_mels=cfg.N_MELS,\n        fmin=cfg.FMIN,\n        fmax=cfg.FMAX,\n        power=2.0\n    )\n\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n    \n    return mel_spec_norm\n\ndef process_audio_segment(audio_data, cfg):\n    \"\"\"Process audio segment to get mel spectrogram\"\"\"\n    if len(audio_data) < cfg.FS * cfg.WINDOW_SIZE:\n        audio_data = np.pad(audio_data, \n                          (0, cfg.FS * cfg.WINDOW_SIZE - len(audio_data)), \n                          mode='constant')\n    \n    mel_spec = audio2melspec(audio_data, cfg)\n    \n    # Resize if needed\n    if mel_spec.shape != cfg.TARGET_SHAPE:\n        mel_spec = cv2.resize(mel_spec, cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR)\n        \n    return mel_spec.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.001015Z","iopub.execute_input":"2025-05-03T13:15:40.001420Z","iopub.status.idle":"2025-05-03T13:15:40.029327Z","shell.execute_reply.started":"2025-05-03T13:15:40.001324Z","shell.execute_reply":"2025-05-03T13:15:40.027771Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"This code find and loads models, which is fairly strainghtforward.\n\nThe predict_on_spectrogram function processes an audio file, segmenting it into 5-second chunks and converting each segment into a mel spectrogram. If Test-Time Augmentation (TTA) is enabled, multiple variations of the spectrogram are created to improve model robustness. Each spectrogram is passed through the loaded models, which predict species presence probabilities using a sigmoid activation function. If multiple models are used, their predictions are averaged to create an ensemble output. The function returns row_ids (identifiers for each audio segment) and corresponding predictions, which can later be used to generate a submission file for competitions like BirdCLEF.","metadata":{}},{"cell_type":"code","source":"def find_model_files(cfg):\n    \"\"\"\n    Find all .pth model files in the specified model directory\n    \"\"\"\n    model_files = []\n    \n    model_dir = Path(cfg.model_path)\n    \n    for path in model_dir.glob('**/*.pth'):\n        model_files.append(str(path))\n    \n    return model_files\n\ndef load_models(cfg, num_classes):\n    \"\"\"\n    Load all found model files and prepare them for ensemble\n    \"\"\"\n    models = []\n    \n    model_files = find_model_files(cfg)\n    \n    if not model_files:\n        print(f\"Warning: No model files found under {cfg.model_path}!\")\n        return models\n    \n    print(f\"Found a total of {len(model_files)} model files.\")\n    \n    if cfg.use_specific_folds:\n        filtered_files = []\n        for fold in cfg.folds:\n            fold_files = [f for f in model_files if f\"fold{fold}\" in f]\n            filtered_files.extend(fold_files)\n        model_files = filtered_files\n        print(f\"Using {len(model_files)} model files for the specified folds ({cfg.folds}).\")\n    \n    for model_path in model_files:\n        try:\n            print(f\"Loading model: {model_path}\")\n            checkpoint = torch.load(model_path, map_location=torch.device('cpu'))\n            \n            model = BirdCLEFModel(cfg, num_classes)\n            model.load_state_dict(checkpoint['model_state_dict'])\n            model = model.to(cfg.device)\n            model.eval()\n            \n            models.append(model)\n        except Exception as e:\n            print(f\"Error loading model {model_path}: {e}\")\n    \n    return models\n\ndef predict_on_spectrogram(audio_path, models, cfg, species_ids):\n    \"\"\"Process a single audio file and predict species presence for each 5-second segment\"\"\"\n    predictions = []\n    row_ids = []\n    soundscape_id = Path(audio_path).stem\n    \n    try:\n        print(f\"Processing {soundscape_id}\")\n        audio_data, _ = librosa.load(audio_path, sr=cfg.FS)\n        \n        total_segments = int(len(audio_data) / (cfg.FS * cfg.WINDOW_SIZE))\n        \n        for segment_idx in range(total_segments):\n            start_sample = segment_idx * cfg.FS * cfg.WINDOW_SIZE\n            end_sample = start_sample + cfg.FS * cfg.WINDOW_SIZE\n            segment_audio = audio_data[start_sample:end_sample]\n            \n            end_time_sec = (segment_idx + 1) * cfg.WINDOW_SIZE\n            row_id = f\"{soundscape_id}_{end_time_sec}\"\n            row_ids.append(row_id)\n\n            if cfg.use_tta:\n                all_preds = []\n                \n                for tta_idx in range(cfg.tta_count):\n                    mel_spec = process_audio_segment(segment_audio, cfg)\n                    mel_spec = apply_tta(mel_spec, tta_idx)\n\n                    mel_spec = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n                    mel_spec = mel_spec.to(cfg.device)\n\n                    if len(models) == 1:\n                        with torch.no_grad():\n                            outputs = models[0](mel_spec)\n                            probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                            all_preds.append(probs)\n                    else:\n                        segment_preds = []\n                        for model in models:\n                            with torch.no_grad():\n                                outputs = model(mel_spec)\n                                probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                                segment_preds.append(probs)\n                        \n                        avg_preds = np.mean(segment_preds, axis=0)\n                        all_preds.append(avg_preds)\n\n                final_preds = np.mean(all_preds, axis=0)\n            else:\n                mel_spec = process_audio_segment(segment_audio, cfg)\n                \n                mel_spec = torch.tensor(mel_spec, dtype=torch.float32).unsqueeze(0).unsqueeze(0)\n                mel_spec = mel_spec.to(cfg.device)\n                \n                if len(models) == 1:\n                    with torch.no_grad():\n                        outputs = models[0](mel_spec)\n                        final_preds = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                else:\n                    segment_preds = []\n                    for model in models:\n                        with torch.no_grad():\n                            outputs = model(mel_spec)\n                            probs = torch.sigmoid(outputs).cpu().numpy().squeeze()\n                            segment_preds.append(probs)\n\n                    final_preds = np.mean(segment_preds, axis=0)\n                    \n            predictions.append(final_preds)\n            \n    except Exception as e:\n        print(f\"Error processing {audio_path}: {e}\")\n    \n    return row_ids, predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.030614Z","iopub.execute_input":"2025-05-03T13:15:40.031005Z","iopub.status.idle":"2025-05-03T13:15:40.057266Z","shell.execute_reply.started":"2025-05-03T13:15:40.030963Z","shell.execute_reply":"2025-05-03T13:15:40.056070Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The apply_tta function applies Test-Time Augmentation (TTA) to spectrograms. It supports three augmentations: no change (original spectrogram), horizontal flip (time shift), and vertical flip (frequency shift). These augmentations allow the model to generalize better by making predictions on modified versions of the input data.\n\nThe run_inference function processes a set of test audio files. The function calls predict_on_spectrogram for each test file, aggregating all predictions and row IDs. The create_submission function then organizes these predictions into a submission dataframe, ensuring it matches the required format by handling missing species columns.","metadata":{}},{"cell_type":"code","source":"def apply_tta(spec, tta_idx):\n    \"\"\"Apply test-time augmentation\"\"\"\n    if tta_idx == 0:\n        # Original spectrogram\n        return spec\n    elif tta_idx == 1:\n        # Time shift (horizontal flip)\n        return np.flip(spec, axis=1)\n    elif tta_idx == 2:\n        # Frequency shift (vertical flip)\n        return np.flip(spec, axis=0)\n    else:\n        return spec\n\ndef run_inference(cfg, models, species_ids):\n    \"\"\"Run inference on all test soundscapes\"\"\"\n    test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\n    \n    if cfg.debug:\n        print(f\"Debug mode enabled, using only {cfg.debug_count} files\")\n        test_files = test_files[:cfg.debug_count]\n    \n    print(f\"Found {len(test_files)} test soundscapes\")\n\n    all_row_ids = []\n    all_predictions = []\n\n    for audio_path in tqdm(test_files):\n        row_ids, predictions = predict_on_spectrogram(str(audio_path), models, cfg, species_ids)\n        all_row_ids.extend(row_ids)\n        all_predictions.extend(predictions)\n    \n    return all_row_ids, all_predictions\n\ndef create_submission(row_ids, predictions, species_ids, cfg):\n    \"\"\"Create submission dataframe\"\"\"\n    print(\"Creating submission dataframe...\")\n\n    submission_dict = {'row_id': row_ids}\n    \n    for i, species in enumerate(species_ids):\n        submission_dict[species] = [pred[i] for pred in predictions]\n\n    submission_df = pd.DataFrame(submission_dict)\n\n    submission_df.set_index('row_id', inplace=True)\n\n    sample_sub = pd.read_csv(cfg.submission_csv, index_col='row_id')\n\n    missing_cols = set(sample_sub.columns) - set(submission_df.columns)\n    if missing_cols:\n        print(f\"Warning: Missing {len(missing_cols)} species columns in submission\")\n        for col in missing_cols:\n            submission_df[col] = 0.0\n\n    submission_df = submission_df[sample_sub.columns]\n\n    submission_df = submission_df.reset_index()\n    \n    return submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.058750Z","iopub.execute_input":"2025-05-03T13:15:40.059156Z","iopub.status.idle":"2025-05-03T13:15:40.092916Z","shell.execute_reply.started":"2025-05-03T13:15:40.059116Z","shell.execute_reply":"2025-05-03T13:15:40.091741Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"From this spot, i will fine-tune the model. First, I need the dataset class","metadata":{}},{"cell_type":"code","source":"class BirdCLEFDataset(Dataset):\n    def __init__(self, csv_file, data_dir, cfg):\n        self.data = pd.read_csv(csv_file)\n        self.data_dir = Path(data_dir)\n        self.cfg = cfg\n        \n        self.label_encoder = LabelEncoder()\n        self.data['encoded_label'] = self.label_encoder.fit_transform(self.data['primary_label'])\n    \n    def __len__(self):\n        return len(self.data)\n    \n    def __getitem__(self, idx):\n        row = self.data.iloc[idx]\n        audio_path = self.data_dir / row['filename']\n        label = row['encoded_label']\n        \n        audio_data, _ = librosa.load(audio_path, sr=self.cfg.FS)\n        mel_spec = audio2melspec(audio_data, self.cfg)\n        \n        # Convert mel_spec from numpy array to torch tensor\n        mel_spec = torch.tensor(mel_spec, dtype=torch.float32)\n\n        # Pad the mel spectrogram to the size of the largest spectrogram\n        target_size = (mel_spec.shape[1], 512)  # You can adjust the target size\n        mel_spec = F.pad(mel_spec, (0, target_size[1] - mel_spec.shape[1]))  # Pad width\n        \n        mel_spec = mel_spec.unsqueeze(0)  # Add channel dimension if needed (C, H, W)\n        label = torch.tensor(label, dtype=torch.long)\n        \n        return mel_spec, label\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.094026Z","iopub.execute_input":"2025-05-03T13:15:40.094395Z","iopub.status.idle":"2025-05-03T13:15:40.121254Z","shell.execute_reply.started":"2025-05-03T13:15:40.094329Z","shell.execute_reply":"2025-05-03T13:15:40.120017Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"full_dataset = BirdCLEFDataset(cfg.train_csv, cfg.train_data_dir, cfg)\n\ntorch.manual_seed(42)\nnp.random.seed(42)\n\ntrain_size = int(0.1 * len(full_dataset))\nval_size = int(0.1 * len(full_dataset))\nother = len(full_dataset) - val_size - train_size\ntrain_dataset, val_dataset, _ = random_split(full_dataset, [train_size, val_size, other], generator=torch.Generator().manual_seed(42))\n\ntrain_loader = DataLoader(train_dataset, batch_size=cfg.batch_size, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=cfg.batch_size, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.123906Z","iopub.execute_input":"2025-05-03T13:15:40.124285Z","iopub.status.idle":"2025-05-03T13:15:40.419661Z","shell.execute_reply.started":"2025-05-03T13:15:40.124254Z","shell.execute_reply":"2025-05-03T13:15:40.418482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(cfg, num_classes, model):\n    device = torch.device(cfg.device)\n    model = model.to(device)\n    \n    optimizer = optim.Adam(model.parameters(), lr=cfg.lr)\n    criterion = nn.CrossEntropyLoss()  # Using CrossEntropyLoss for multi-class classification\n    \n    best_loss = float('inf')\n    for epoch in range(cfg.epochs):\n        print(f\"{epoch=}\")\n        model.train()\n        total_loss = 0.0\n        for mel_spec, label in tqdm(train_loader):\n            mel_spec, label = mel_spec.to(device), label.to(device)\n            optimizer.zero_grad()\n            \n            output = model(mel_spec)\n            loss = criterion(output, label)  # Compute loss\n            \n            loss.backward()\n            optimizer.step()\n            \n            total_loss += loss.item()\n        \n        avg_train_loss = total_loss / len(train_loader)\n        print(f\"Epoch {epoch+1}/{cfg.epochs}, Training Loss: {avg_train_loss:.4f}\")\n        \n        # Validation step\n        model.eval()\n        val_loss = 0.0\n        with torch.no_grad():\n            for mel_spec, label in tqdm(val_loader):\n                mel_spec, label = mel_spec.to(device), label.to(device)\n                output = model(mel_spec)\n                loss = criterion(output, label)\n                val_loss += loss.item()\n        \n        avg_val_loss = val_loss / len(val_loader)\n        print(f\"Validation Loss: {avg_val_loss:.4f}\")\n        return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.420795Z","iopub.execute_input":"2025-05-03T13:15:40.421227Z","iopub.status.idle":"2025-05-03T13:15:40.433337Z","shell.execute_reply.started":"2025-05-03T13:15:40.421188Z","shell.execute_reply":"2025-05-03T13:15:40.431132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# start_time = time.time()\n# print(\"Starting BirdCLEF-2025 inference...\")\n# print(f\"TTA enabled: {cfg.use_tta} (variations: {cfg.tta_count if cfg.use_tta else 0})\")\n\n# models = load_models(cfg, num_classes)\n\n# if models:\n#     for i in range(len(models)):\n#         print(f\"Fitting model {i}\")\n#         models[i] = train_model(cfg, 5, models[i])\n\n#     print(f\"Model usage: {'Single model' if len(models) == 1 else f'Ensemble of {len(models)} models'}\")\n    \n#     row_ids, predictions = run_inference(cfg, models, species_ids)\n    \n#     submission_df = create_submission(row_ids, predictions, species_ids, cfg)\n    \n#     submission_path = 'submission.csv'\n#     submission_df.to_csv(submission_path, index=False)\n#     print(f\"Submission saved to {submission_path}\")\n    \n#     end_time = time.time()\n#     print(f\"Inference completed in {(end_time - start_time)/60:.2f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.434707Z","iopub.execute_input":"2025-05-03T13:15:40.435591Z","iopub.status.idle":"2025-05-03T13:15:40.473621Z","shell.execute_reply.started":"2025-05-03T13:15:40.435537Z","shell.execute_reply":"2025-05-03T13:15:40.471680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# for i, model in enumerate(models):\n#     torch.save(model.state_dict(), f\"model{i}.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.475159Z","iopub.execute_input":"2025-05-03T13:15:40.475589Z","iopub.status.idle":"2025-05-03T13:15:40.522213Z","shell.execute_reply.started":"2025-05-03T13:15:40.475551Z","shell.execute_reply":"2025-05-03T13:15:40.520847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef load_models(cfg, num_classes):\n    \"\"\"\n    Load all found model files and prepare them for ensemble\n    \"\"\"\n    models = []\n    \n    model_files = [f\"/kaggle/input/model/pytorch/default/1/model{i}.pth\" for i in range(5)]\n    \n    if not model_files:\n        print(f\"Warning: No model files found under {cfg.model_path}!\")\n        return models\n    \n    print(f\"Found a total of {len(model_files)} model files.\")\n    \n    if cfg.use_specific_folds:\n        filtered_files = []\n        for fold in cfg.folds:\n            fold_files = [f for f in model_files if f\"fold{fold}\" in f]\n            filtered_files.extend(fold_files)\n        model_files = filtered_files\n        print(f\"Using {len(model_files)} model files for the specified folds ({cfg.folds}).\")\n    \n    for model_path in model_files:\n        try:\n            print(f\"Loading model: {model_path}\")\n            checkpoint = torch.load(model_path, map_location=torch.device('cpu'))\n            \n            model = BirdCLEFModel(cfg, num_classes)\n            model.load_state_dict(checkpoint)\n            model = model.to(cfg.device)\n            model.eval()\n            \n            models.append(model)\n        except Exception as e:\n            print(f\"Error loading model {model_path}: {e}\")\n    \n    return models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.523364Z","iopub.execute_input":"2025-05-03T13:15:40.523827Z","iopub.status.idle":"2025-05-03T13:15:40.554259Z","shell.execute_reply.started":"2025-05-03T13:15:40.523781Z","shell.execute_reply":"2025-05-03T13:15:40.552821Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"start_time = time.time()\n\nmodels = load_models(cfg, num_classes)\nif models:\n    print(f\"Model usage: {'Single model' if len(models) == 1 else f'Ensemble of {len(models)} models'}\")\n    \n    row_ids, predictions = run_inference(cfg, models, species_ids)\n    \n    submission_df = create_submission(row_ids, predictions, species_ids, cfg)\n    \n    submission_path = 'submission.csv'\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"Submission saved to {submission_path}\")\n    \n    end_time = time.time()\n    print(f\"Inference completed in {(end_time - start_time)/60:.2f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-03T13:15:40.555309Z","iopub.execute_input":"2025-05-03T13:15:40.555672Z","iopub.status.idle":"2025-05-03T13:15:43.094964Z","shell.execute_reply.started":"2025-05-03T13:15:40.555641Z","shell.execute_reply":"2025-05-03T13:15:43.093436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}