{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":21669,"databundleVersionId":1692278}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# General Settings","metadata":{}},{"cell_type":"markdown","source":"## Library Imports","metadata":{}},{"cell_type":"code","source":"#Data handling\nimport os\nimport pandas as pd\nfrom PIL import Image\n\n# Audio handling\n!pip install PySoundFile\nimport librosa\nfrom IPython.display import Audio\n\n# Visualization\nimport matplotlib.pyplot as plt\n\n# Feedback with progress bar\nfrom tqdm.notebook import tqdm\n\n# Math & Algorithms\nimport numpy as np","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-07T23:58:10.722049Z","iopub.execute_input":"2025-10-07T23:58:10.722260Z","iopub.status.idle":"2025-10-07T23:58:15.619216Z","shell.execute_reply.started":"2025-10-07T23:58:10.722243Z","shell.execute_reply":"2025-10-07T23:58:15.618514Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Settings","metadata":{}},{"cell_type":"code","source":"# Initialise random number generation\nrandom_seed = 42\nrng = np.random.default_rng()\n\n# NN training parameters\nslice_length = 3\nsr = None # Use recording native sr\nbatch_size = 16\nlr = 1e-3\nepochs = 30\npatience = 10\n\n# Folder for storing generated spectrograms\nspect_save_path='/kaggle/working/spectrograms'\nos.makedirs(spect_save_path, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:00:08.793684Z","iopub.execute_input":"2025-10-08T00:00:08.794395Z","iopub.status.idle":"2025-10-08T00:00:08.798958Z","shell.execute_reply.started":"2025-10-08T00:00:08.794370Z","shell.execute_reply":"2025-10-08T00:00:08.798408Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### File path variables","metadata":{}},{"cell_type":"code","source":"# Root data path for RainForest Species\ninput_path='/kaggle/input/rfcx-species-audio-detection'\n\n# Train and Test audio recordings data\ntrain_path=os.path.join(input_path, 'train')\ntest_path=os.path.join(input_path, 'test')\n\n# Labels\ntp_label_csv_path=os.path.join(input_path, 'train_tp.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:02:14.763303Z","iopub.execute_input":"2025-10-08T00:02:14.763580Z","iopub.status.idle":"2025-10-08T00:02:14.767529Z","shell.execute_reply.started":"2025-10-08T00:02:14.763558Z","shell.execute_reply":"2025-10-08T00:02:14.766949Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training data preparation","metadata":{}},{"cell_type":"markdown","source":"## Data Import","metadata":{}},{"cell_type":"markdown","source":"### Import labels","metadata":{}},{"cell_type":"code","source":"df_labels=pd.read_csv(tp_label_csv_path)\ndf_labels['t_length'] = df_labels['t_max'] - df_labels['t_min']\nlengths = df_labels['t_length']\nprint(f\"Length of labeled audio segments: {lengths.mean():.2f}±{lengths.std():.2f} ({lengths.min():.2f}-{lengths.max():.2f}) [s]\")\ndf_labels","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:03:18.985814Z","iopub.execute_input":"2025-10-08T00:03:18.986548Z","iopub.status.idle":"2025-10-08T00:03:19.036877Z","shell.execute_reply.started":"2025-10-08T00:03:18.986521Z","shell.execute_reply":"2025-10-08T00:03:19.036160Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Exploration","metadata":{}},{"cell_type":"markdown","source":"### Play a selected or random audio recording","metadata":{}},{"cell_type":"code","source":"recording_id = None\n#recording_id = '5b5218aba'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:06:53.805432Z","iopub.execute_input":"2025-10-08T00:06:53.806061Z","iopub.status.idle":"2025-10-08T00:06:53.809305Z","shell.execute_reply.started":"2025-10-08T00:06:53.806035Z","shell.execute_reply":"2025-10-08T00:06:53.808691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if recording_id == None:\n    # Find all .flac files\n    flac_files=[f for f in os.listdir(train_path) if f.endswith('.flac')]\n    print(f'No. of .flac samples: {len(flac_files)}')\n\n    # Select and access a random .flac from the list\n    file=rng.choice(flac_files)\n    file_path=os.path.join(train_path, file)\n    recording_id=file.replace('.flac', '')\nelse:\n    file = recording_id + '.flac'\n    file_path=os.path.join(train_path, file)\n\nprint(f'Selected sample: {file}')\n\n# Read labels and audio data\nrecord = df_labels.loc[df_labels['recording_id'] == recording_id]\naudio, rec_sr = librosa.core.load(file_path, sr = sr, mono=False)\nprint(f\"Sample rate: {rec_sr}\")\n\n# Print label info\nprint(f\"\\nLabels:\")\nprint(f\"-------------\")\nif len(record)==0:\n    print(\"No associated label\")\nelse:\n    for _, row in record.iterrows():\n        print(f\"Faj: {row['species_id']}\")\n        print(f\"Típus: {row['songtype_id']}\")\n        print(f\"Időtartam: {row['t_min']} - {row['t_max']}\")\n        print(f\"Frekvencia: {row['f_min']} - {row['f_max']}\\n\")\n\n# Display player\nAudio(audio, rate=rec_sr)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:07:24.728942Z","iopub.execute_input":"2025-10-08T00:07:24.729531Z","iopub.status.idle":"2025-10-08T00:07:30.017991Z","shell.execute_reply.started":"2025-10-08T00:07:24.729503Z","shell.execute_reply":"2025-10-08T00:07:30.016738Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Show the corresponding mel spectrogram","metadata":{}},{"cell_type":"code","source":"# Generate the Spectrogram\nS = librosa.feature.melspectrogram(y=audio, sr=rec_sr)\nS_db = librosa.power_to_db(S, ref=np.max)\n\n#Display\nfig, ax = plt.subplots(figsize=(16, 4))\nimage = librosa.display.specshow(S_db, sr=rec_sr, x_axis='time', y_axis='mel', ax=ax)\nfig.colorbar(image, ax=ax)\nax.set(title='Mel-Spectrogram')\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:12:57.339308Z","iopub.execute_input":"2025-10-08T00:12:57.340021Z","iopub.status.idle":"2025-10-08T00:13:08.044121Z","shell.execute_reply.started":"2025-10-08T00:12:57.339999Z","shell.execute_reply":"2025-10-08T00:13:08.043312Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Test mel spectrogram generation, saving and loading","metadata":{}},{"cell_type":"code","source":"# Generate spectrogram\nS = librosa.feature.melspectrogram(y=audio[0:slice_length*rec_sr], sr=rec_sr)\nS_db = librosa.power_to_db(S, ref=np.max)\n\n# Convert to image apropriate format\nS_norm = (S_db-S_db.min())/(S_db.max()-S_db.min())\nS_norm = (S_norm*255).astype(np.uint8)\n\n# Convert to PIL image\nimg = Image.fromarray(S_norm)\n\n# Save the image with PIL\nimg = Image.fromarray(S_norm)\nimg.save('/kaggle/working/test.png')\n\n# Load the image\nimg_loaded = Image.open('/kaggle/working/test.png')\nS_norm_loaded = np.array(img_loaded)\n\n# Assert equality of saved and loaded array\ntry:\n    np.testing.assert_array_equal(S_norm, S_norm_loaded)\nexcept AssertionError:\n    print(f\"The saved and loaded arrays are NOT identical:\")\nelse:\n    print(f\"The saved and loaded arrays are IDENTICAL:\")\nfinally:\n    print(f\"\\tSaved array: {S_norm.shape}; saved values: {S_norm.min()}-{S_norm.max()}; format: {type(S_norm)}\")\n    display(img)\n    print(f\"\\tLoaded array: {S_norm_loaded.shape}; saved values: {S_norm_loaded.min()}-{S_norm_loaded.max()}; format: {type(S_norm_loaded)}\")\n    display(img_loaded)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:14:18.891413Z","iopub.execute_input":"2025-10-08T00:14:18.892146Z","iopub.status.idle":"2025-10-08T00:14:18.928110Z","shell.execute_reply.started":"2025-10-08T00:14:18.892119Z","shell.execute_reply":"2025-10-08T00:14:18.927402Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data generation","metadata":{}},{"cell_type":"markdown","source":"### Generate labeled spectrograms","metadata":{}},{"cell_type":"code","source":"def spectrogram_gen(audio_folder_path, recording_id, species_id,\n                    time_min, time_max, sr, slice_length,\n                    spect_save_path):\n    \"\"\"Generates spectrograms of from a given audio file\n\n    Generates spectrograms of 'slice_length' between 'time_min' and 'time_max'\n    of the given recording.\n\n    Parameters\n    ----------\n    audio_folder_path : path str\n        The folder in which the audio files are stored\n    recording_id : str\n        The identifier of the audio recording\n    species_id : int\n        The identifier of the species in the record\n    time_min: float\n        Start time of the vocalization of the species within the recording\n    time_max: float\n        End time of the vocalization of the species within the recording\n    sr : int\n        Forced sample_rate for laoding the audio (None uses the recording native sr)\n    slice_length: int\n        Length of recording slice to turn into spectrograms in seconds\n    spect_save_path: path str\n        Folder path to save generated spectrograms into\n\n    Returns\n    -------\n    save_path: path str\n        Location at which the generated spectrogram is saved at\n    \"\"\"\n\n    # Load the audio\n    file_path = os.path.join(audio_folder_path, recording_id + '.flac')\n    audio, rec_sr = librosa.core.load(file_path, sr=sr, mono=True)\n\n    # Generate audio slice(s)\n    slice_time = time_max - time_min\n    noSlices = max(int(np.round(slice_time/slice_length)), 1) # How many slices can fit into the given intervall, rounded to nearest int\n    \n    for i in range(noSlices): \n        # Find center time of the given slice\n        center = (time_min + i * (slice_time / (noSlices+1)))\n\n        # Find start and end sample of the given slice\n        start = int(max(center - slice_length/2, 0) * rec_sr)\n        end = start + int(slice_length * rec_sr)\n        if end > len(audio):\n            end = len(audio)\n            start = end - int(slice_length * rec_sr)\n\n        # Get the sliced audio\n        sliced_audio=audio[start:end]\n\n        # Generate Spectrogram\n        S = librosa.feature.melspectrogram(y = sliced_audio, sr=rec_sr)\n        S_db=librosa.power_to_db(S, ref=np.max)\n        \n        S_norm=(S_db-S_db.min())/(S_db.max()-S_db.min())\n        S_norm = (S_norm*255).astype(np.uint8)\n        spect_size = S_norm.shape\n\n        # Save the array as an image\n        species_path=os.path.join(spect_save_path, str(species_id))\n        os.makedirs(species_path, exist_ok=True)\n\n        filename = f'{species_id}_{recording_id}_{center:.2f}.png' # {center} kell, hátha ugyanolyan nevű file keletkezne\n        save_path = os.path.join(species_path, filename)\n\n        S_image = Image.fromarray(S_norm)\n        S_image.save(save_path)\n    \n    return spect_size, save_path # későbbi visszanézésre\n\n# Spectrogram generation progress (with TQDM progress bar)\ninput_size = None\nfor i in tqdm(range(len(df_labels))):\n    row = df_labels.iloc[i]\n    \n    recording_id=row['recording_id']\n    species_id=row['species_id']\n    time_min=float(row['t_min'])\n    time_max=float(row['t_max'])\n\n    # Generate spectrogram\n    spect_size, save_path = spectrogram_gen(audio_folder_path = train_path, recording_id = recording_id, species_id = species_id,\n                                            time_min = time_min, time_max = time_max, sr = sr, slice_length = slice_length, \n                                            spect_save_path = spect_save_path)\n    if input_size == None:\n        input_size = spect_size\n    else:\n        if (input_size != spect_size):\n            print(f\"WARNING: spectrogram size for label {i} ({spect_size}) does not match the spectrogram size for the first label ({input_size})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:16:47.128513Z","iopub.execute_input":"2025-10-08T00:16:47.129291Z","iopub.status.idle":"2025-10-08T00:18:32.944207Z","shell.execute_reply.started":"2025-10-08T00:16:47.129265Z","shell.execute_reply":"2025-10-08T00:18:32.943394Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Summarise generated training data","metadata":{}},{"cell_type":"code","source":"species = [int(f) for f in os.listdir(spect_save_path) if os.path.isdir(os.path.join(spect_save_path, f))]\nspecies.sort()\nnumSpecies = len(species)\nprint(f\"Fajok száma: {numSpecies}\")\n\nsum_files=0\nprint(\"Fájlok száma az egyes species mappákban:\")\nfor f in species:\n    path = os.path.join(spect_save_path, str(f))\n    numFiles = len([name for name in os.listdir(path) if os.path.isfile(os.path.join(path, name))])\n    sum_files += numFiles\n    print(f\"{f}:\\t{numFiles}\")\nprint(f\"Összes spectrogram: {sum_files}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:20:24.306139Z","iopub.execute_input":"2025-10-08T00:20:24.306695Z","iopub.status.idle":"2025-10-08T00:20:24.322137Z","shell.execute_reply.started":"2025-10-08T00:20:24.306671Z","shell.execute_reply":"2025-10-08T00:20:24.321389Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PyTorch implementation","metadata":{}},{"cell_type":"markdown","source":"## Library imports","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\nfrom glob import glob\n\n!pip install torcheval\nfrom torcheval.metrics import MultilabelAccuracy\n\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:21:25.018057Z","iopub.execute_input":"2025-10-08T00:21:25.018676Z","iopub.status.idle":"2025-10-08T00:21:36.757098Z","shell.execute_reply.started":"2025-10-08T00:21:25.018651Z","shell.execute_reply":"2025-10-08T00:21:36.756417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## PyTorch specific settings","metadata":{}},{"cell_type":"markdown","source":"I changed `criteria = 'hamming'` to `criteria = 'exact_match'` in `MultiLabelAccuracy()`. With `hamming`, a prediction of all zeros for a sample with exactly one vocalization will result in a 23/24 = 95.8% accuracy","metadata":{}},{"cell_type":"code","source":"# Device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {torch.cuda.get_device_name(device)}\")\n\n# Metric\nmetric = MultilabelAccuracy(threshold=0.5, criteria = 'exact_match').to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:22:21.728419Z","iopub.execute_input":"2025-10-08T00:22:21.728895Z","iopub.status.idle":"2025-10-08T00:22:22.015420Z","shell.execute_reply.started":"2025-10-08T00:22:21.728871Z","shell.execute_reply":"2025-10-08T00:22:22.014493Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset handling","metadata":{}},{"cell_type":"code","source":"class AudioDataset(torch.utils.data.Dataset):\n    def __init__(self, files, numLabels):\n        self.files = files\n        self.numLabels = numLabels\n\n    def __len__(self):\n        return len(self.files)\n\n    def __getitem__(self, i):\n        f = self.files[i]\n        \n        image = Image.open(f)\n        image = np.array(image, dtype=np.float32)/255 # conv2d-hez\n        image = torch.tensor(image)\n        image = image.unsqueeze(0) # Add channel dimension\n            \n        label = int(os.path.basename(os.path.dirname(f)))\n        s = torch.zeros(self.numLabels)\n        s[label] = 1.0\n\n        return image, s","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:23:21.938610Z","iopub.execute_input":"2025-10-08T00:23:21.939294Z","iopub.status.idle":"2025-10-08T00:23:21.944721Z","shell.execute_reply.started":"2025-10-08T00:23:21.939265Z","shell.execute_reply":"2025-10-08T00:23:21.943800Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# NumSpecies\nspecies = [int(f) for f in os.listdir(spect_save_path) if os.path.isdir(os.path.join(spect_save_path, f))]\nnumSpecies = len(species)\n\n# Files\nall_files=glob(\"/kaggle/working/spectrograms/*/*.png\")\nprint(f\"Összes file: {len(all_files)}\")\ntrain_files, val_files=train_test_split(all_files, test_size=0.1)\n\n# input_size\ninput_size = np.array(Image.open(all_files[0])).shape\n\n#Datasets\ntrain_dataset=AudioDataset(train_files, numSpecies)\nval_dataset=AudioDataset(val_files, numSpecies)\n\n\n# Training loader with shuffled batches of data\ntrain_loader=torch.utils.data.DataLoader(train_dataset, batch_size = batch_size, shuffle=True)\n# Validation loader with a single data batch\nval_loader=torch.utils.data.DataLoader(val_dataset, batch_size = len(val_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:24:18.950312Z","iopub.execute_input":"2025-10-08T00:24:18.950936Z","iopub.status.idle":"2025-10-08T00:24:18.962492Z","shell.execute_reply.started":"2025-10-08T00:24:18.950912Z","shell.execute_reply":"2025-10-08T00:24:18.961877Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Define model","metadata":{}},{"cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self, inputSize, numSpecies):\n        super().__init__()\n        self.model=nn.Sequential(\n            nn.Conv2d(1, 16, (3,3)),\n            nn.ReLU(),\n            #nn.MaxPool2d(2),\n            nn.Conv2d(16, 32, (3,3)),\n            nn.ReLU(),\n            # nn.Conv2d(32, 64, (4,4)),\n            # nn.ReLU(),\n            #nn.MaxPool2d(2),\n            nn.Flatten(),\n            nn.Linear(32*(inputSize[0]-4)*(inputSize[1]-4), 128),\n            nn.ReLU(),\n            nn.Linear(128, numSpecies),\n            nn.Sigmoid()\n        )\n       \n\n    def forward(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:25:11.183919Z","iopub.execute_input":"2025-10-08T00:25:11.184657Z","iopub.status.idle":"2025-10-08T00:25:11.189035Z","shell.execute_reply.started":"2025-10-08T00:25:11.184633Z","shell.execute_reply":"2025-10-08T00:25:11.188431Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training logic","metadata":{}},{"cell_type":"code","source":"def train_model(model_torch, train_loader, val_loader, device,\n                epochs=30, lr=lr, patience=5, min_delta=1e-4,\n                class_weights=None):\n    \n    model_torch.to(device)\n\n    if class_weights is not None:\n        loss_fn=nn.BCELoss(weight=class_weights)\n        class_weights=class_weights.to(device)\n    else:\n        loss_fn=nn.BCELoss()\n\n    optimizer = torch.optim.Adam(model_torch.parameters(), lr = lr)\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.5, patience = int(patience / 2))\n    \n    patience_count = 0\n    best_val_loss = float('inf')\n\n    train_acc_history = []\n    train_loss_history = []\n    val_acc_history = []\n    val_loss_history = []\n    \n    \n    for epoch in range(epochs):\n\n        print(f\"Epoch: {epoch+1}/{epochs}:\")\n        \n        # Training\n        model_torch.train()\n        train_loss = 0.0\n        train_acc = 0.0\n\n        with tqdm(total = len(train_loader)) as t:\n            for inputs, labels in train_loader:\n\n                # Forward pass\n                inputs = inputs.to(device)\n                labels = labels.to(device)\n                outputs = model_torch(inputs)\n\n                # Accuracy\n                metric.update(outputs, labels)\n            \n                # Backprop\n                optimizer.zero_grad()\n                loss = loss_fn(outputs, labels)\n                loss.backward()\n                optimizer.step()\n                train_loss += loss.item() * inputs.size(0) # * batch_size = total loss of samples in the batch\n\n                t.update(1)\n            \n        train_acc = metric.compute().item()\n        train_acc_history.append(train_acc)\n        metric.reset()\n        \n        train_loss /= len(train_loader.dataset)\n        train_loss_history.append(train_loss)\n\n        # Evaluation\n        model_torch.eval()\n        val_loss = 0.0\n        \n        with torch.no_grad():\n            for inputs, labels in val_loader:\n\n                # Forward pass\n                inputs = inputs.to(device)\n                labels = labels.to(device)\n                outputs = model_torch(inputs)\n                \n                # Update Accuracy\n                metric.update(outputs, labels)\n                \n                loss = loss_fn(outputs, labels)\n                val_loss += loss.item() * inputs.size(0) # * batch_size = total loss of samples in the batch\n\n            val_acc = metric.compute().item()\n            val_acc_history.append(val_acc)\n            metric.reset()\n        \n            val_loss /= len(val_loader.dataset)\n            val_loss_history.append(val_loss)\n\n        scheduler.step(val_loss)\n        \n        print(f\"Train accuracy: {train_acc} - Train loss: {train_loss} - Val accuracy: {val_acc} - Val loss: {val_loss} - Learning rate: {scheduler.get_last_lr()[0]}\")\n\n        if val_loss<best_val_loss-min_delta:\n            best_val_loss = val_loss\n            patience_count = 0\n            best_model = model_torch.state_dict()\n        else:\n            patience_count += 1\n            if patience_count >= patience:\n                print(f\"Early stopping. Epoch: {epoch+1}\")\n                break\n\n    model_torch.load_state_dict(best_model)\n    history = {\"train_acc\": np.array(train_acc_history), \n               \"train_loss\": np.array(train_loss_history),\n               \"val_acc\": np.array(val_acc_history),\n               \"val_loss\": np.array(val_loss_history)}\n    return model_torch, history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:27:10.663814Z","iopub.execute_input":"2025-10-08T00:27:10.664422Z","iopub.status.idle":"2025-10-08T00:27:10.674534Z","shell.execute_reply.started":"2025-10-08T00:27:10.664396Z","shell.execute_reply":"2025-10-08T00:27:10.673748Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train the model","metadata":{}},{"cell_type":"code","source":"model_torch=CNN(input_size, numSpecies)\nprint(model_torch)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:29:35.476499Z","iopub.execute_input":"2025-10-08T00:29:35.476798Z","iopub.status.idle":"2025-10-08T00:29:36.663257Z","shell.execute_reply.started":"2025-10-08T00:29:35.476777Z","shell.execute_reply":"2025-10-08T00:29:36.662521Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_torch, history_torch = train_model(model_torch=model_torch, train_loader=train_loader, val_loader=val_loader, device=device,\n                            epochs=epochs, patience = patience)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:33:27.355179Z","iopub.execute_input":"2025-10-08T00:33:27.355475Z","iopub.status.idle":"2025-10-08T00:35:05.924530Z","shell.execute_reply.started":"2025-10-08T00:33:27.355454Z","shell.execute_reply":"2025-10-08T00:35:05.923834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Evaluate","metadata":{}},{"cell_type":"markdown","source":"### History","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(12, 8))\nplt.subplot(2,1,1)\nplt.plot(history_torch['train_acc']*100, label='Training accuracy')\nplt.plot(history_torch['val_acc']*100, label='Validation accuracy')\nplt.title('Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy [%]')\nplt.legend(loc='lower right')\n\nplt.subplot(2,1,2)\nplt.plot(history_torch['train_loss'], label='Training loss')\nplt.plot(history_torch['val_loss'], label='Validation loss')\nplt.title('Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss [%]')\nplt.legend(loc='upper right')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:35:57.852372Z","iopub.execute_input":"2025-10-08T00:35:57.852909Z","iopub.status.idle":"2025-10-08T00:35:58.241792Z","shell.execute_reply.started":"2025-10-08T00:35:57.852885Z","shell.execute_reply":"2025-10-08T00:35:58.240959Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Predict on file","metadata":{}},{"cell_type":"code","source":"# Generate spectrograms from a given file\ndef gen_test_spectrograms_torch(\n    file_path,\n    sr,\n    length):\n\n    spectrograms = []\n    audio, rec_sr = librosa.core.load(file_path, sr = sr, mono = True)\n    slice_length = rec_sr * length\n    n = len(audio) // slice_length\n\n    for i in range(n):\n        start = i * slice_length\n        end = start + slice_length\n        if end > len(audio):\n            end = len(audio)\n        sliced_audio = audio[start:end]\n\n        S = librosa.feature.melspectrogram(y = sliced_audio, sr = rec_sr)\n        S_db = librosa.power_to_db(S, ref = np.max)\n        S_norm = (S_db - S_db.min()) / (S_db.max() - S_db.min())\n        \n        spectrograms.append(S_norm)\n\n    return spectrograms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:38:43.774438Z","iopub.execute_input":"2025-10-08T00:38:43.774742Z","iopub.status.idle":"2025-10-08T00:38:43.780442Z","shell.execute_reply.started":"2025-10-08T00:38:43.774721Z","shell.execute_reply":"2025-10-08T00:38:43.779483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Predict results based on a collection of spectrograms\ndef predict_test_torch(\n    model_torch,\n    spectrograms,\n    device,\n    threshold=0.5\n):\n    model_torch.eval()\n    model_torch.to(device)\n    inputs=[]\n\n    for s in spectrograms:\n        tensor = torch.tensor(s, dtype=torch.float32)\n        tensor = tensor.unsqueeze(0).unsqueeze(0) #batch, channels\n        inputs.append(tensor)\n    inputs = torch.cat(inputs).to(device)\n\n    with torch.no_grad():\n        outputs = model_torch(inputs)\n\n    pred = outputs.max(dim = 0).values\n    binary_pred = (pred > threshold).int()\n\n    return pred.cpu().numpy(), binary_pred.cpu().numpy() ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:39:19.280561Z","iopub.execute_input":"2025-10-08T00:39:19.281304Z","iopub.status.idle":"2025-10-08T00:39:19.286341Z","shell.execute_reply.started":"2025-10-08T00:39:19.281278Z","shell.execute_reply":"2025-10-08T00:39:19.285399Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"file_path = '/kaggle/input/rfcx-species-audio-detection/train/0099c367b.flac'\nspectrograms = gen_test_spectrograms_torch(file_path, sr = sr, length = slice_length)\npred, binary_pred = predict_test_torch(model_torch, spectrograms, device)\n\nprint(\"Egyes fajok előfordulásának valószínűsége:\")\nfor i, probability in enumerate(pred):\n    print(f\"{i}.\\t{probability:.5f}\")\nprint(\"Binary prediction:\")\nprint(binary_pred)\n\naudio, rec_sr = librosa.core.load(file_path, sr = sr, mono = True)\nAudio(audio, rate = rec_sr)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:40:53.552577Z","iopub.execute_input":"2025-10-08T00:40:53.553066Z","iopub.status.idle":"2025-10-08T00:40:54.046946Z","shell.execute_reply.started":"2025-10-08T00:40:53.553040Z","shell.execute_reply":"2025-10-08T00:40:54.046141Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Create submission csv","metadata":{}},{"cell_type":"code","source":"def create_csv_torch(model_torch, test_path, device, csv_file=None):\n    rows=[]\n\n    test_paths = os.listdir(test_path)\n    for i in tqdm(range(len(test_paths))):\n        \n        file = test_paths[i]\n        \n        if file.endswith('.flac'):\n            file_path = os.path.join(test_path, file)\n            recording_id = file.replace('.flac', '')\n            spectrograms = gen_test_spectrograms_torch(file_path, sr = sr, length = slice_length)\n            pred, _ = predict_test_torch(model_torch, spectrograms, device)\n            \n            rows.append([recording_id] + list(pred))\n    \n    df = pd.DataFrame(rows, columns=['recording_id']+[f\"s{i}\" for i in range(24)])\n    if csv_file:\n        df.to_csv(csv_file, float_format='%.5f', index=False)\n    else:\n        print(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:43:04.552996Z","iopub.execute_input":"2025-10-08T00:43:04.553576Z","iopub.status.idle":"2025-10-08T00:43:04.559354Z","shell.execute_reply.started":"2025-10-08T00:43:04.553550Z","shell.execute_reply":"2025-10-08T00:43:04.558515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_dir='/kaggle/working/csv'\nos.makedirs(submission_dir, exist_ok=True)\ncsv_file = os.path.join(submission_dir, 'rainForest_submission_pyTorch.csv')\ncreate_csv_torch(model_torch, test_path, device, csv_file=csv_file)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T00:43:38.822564Z","iopub.execute_input":"2025-10-08T00:43:38.822884Z","iopub.status.idle":"2025-10-08T00:54:00.491691Z","shell.execute_reply.started":"2025-10-08T00:43:38.822862Z","shell.execute_reply":"2025-10-08T00:54:00.491122Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Keras Implementation","metadata":{}},{"cell_type":"markdown","source":"## Library imports","metadata":{}},{"cell_type":"code","source":"import keras\nfrom keras import layers","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:02:17.154794Z","iopub.execute_input":"2025-10-08T01:02:17.155424Z","iopub.status.idle":"2025-10-08T01:02:30.899371Z","shell.execute_reply.started":"2025-10-08T01:02:17.155392Z","shell.execute_reply":"2025-10-08T01:02:30.898530Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Keras specific settings","metadata":{}},{"cell_type":"code","source":"# NumSpecies\nspecies = [int(f) for f in os.listdir(spect_save_path) if os.path.isdir(os.path.join(spect_save_path, f))]\nnumSpecies = len(species)\n\n# input_size\ninput_size = np.array(Image.open(\"/kaggle/working/test.png\")).shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:02:52.224394Z","iopub.execute_input":"2025-10-08T01:02:52.224664Z","iopub.status.idle":"2025-10-08T01:02:52.230190Z","shell.execute_reply.started":"2025-10-08T01:02:52.224644Z","shell.execute_reply":"2025-10-08T01:02:52.229623Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(input_size)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:02:55.620100Z","iopub.execute_input":"2025-10-08T01:02:55.620396Z","iopub.status.idle":"2025-10-08T01:02:55.624256Z","shell.execute_reply.started":"2025-10-08T01:02:55.620375Z","shell.execute_reply":"2025-10-08T01:02:55.623448Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset handling","metadata":{}},{"cell_type":"code","source":"train_ds, val_ds = keras.utils.image_dataset_from_directory(directory =  spect_save_path, labels =\"inferred\", label_mode = \"categorical\",\n                                                            color_mode = 'grayscale', batch_size = batch_size, image_size = input_size,\n                                                            seed = random_seed, shuffle = True, validation_split = 0.1, subset = \"both\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:03:12.530426Z","iopub.execute_input":"2025-10-08T01:03:12.530953Z","iopub.status.idle":"2025-10-08T01:03:14.432720Z","shell.execute_reply.started":"2025-10-08T01:03:12.530931Z","shell.execute_reply":"2025-10-08T01:03:14.431882Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Define model","metadata":{}},{"cell_type":"code","source":"model_keras=keras.models.Sequential([\n    \n    layers.Input(shape=(input_size[0], input_size[1], 1)),\n    layers.Rescaling(1./255),\n    \n    layers.Conv2D(16, (3, 3), activation='relu'),\n    #MaxPooling2D(pool_size=(2, 2)),\n    layers.Conv2D(32, (3, 3), activation='relu'),\n    #MaxPooling2D(pool_size=(2, 2)),\n    #Conv2D(64, (3, 3), activation='relu'),\n\n    layers.Flatten(),\n    #Dense(128, activation='relu'),\n    #Dropout(0.25, seed=seed),\n    layers.Dense(128, activation='relu'),\n    #Dropout(0.25, seed=seed),\n    layers.Dense(numSpecies, activation='sigmoid')\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:06:40.023773Z","iopub.execute_input":"2025-10-08T01:06:40.024688Z","iopub.status.idle":"2025-10-08T01:06:40.905920Z","shell.execute_reply.started":"2025-10-08T01:06:40.024659Z","shell.execute_reply":"2025-10-08T01:06:40.905113Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training logic","metadata":{}},{"cell_type":"code","source":"# Optimizer\noptimizer=keras.optimizers.Adam(learning_rate = lr)\n\n# Callbacks\nreduce_lr = keras.callbacks.ReduceLROnPlateau(factor = 0.5, patience = patience / 2, verbose=1)\nearly_stop = keras.callbacks.EarlyStopping(patience = patience, verbose = 1, restore_best_weights = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:08:06.522996Z","iopub.execute_input":"2025-10-08T01:08:06.523291Z","iopub.status.idle":"2025-10-08T01:08:06.533986Z","shell.execute_reply.started":"2025-10-08T01:08:06.523271Z","shell.execute_reply":"2025-10-08T01:08:06.533386Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Aquí tienes la interpretación detallada:\n\n1. El Optimizador (Optimizer)\nPython\n\noptimizer = keras.optimizers.Adam(learning_rate = lr)\nEsta línea define cómo el modelo va a aprender a partir de los datos de entrenamiento.\n\noptimizer: Es el algoritmo que ajustará los pesos (parámetros) internos del modelo para minimizar el error (pérdida).\n\nkeras.optimizers.Adam: Es la elección del algoritmo de optimización. Adam (Adaptive Moment Estimation) es uno de los optimizadores más populares y eficientes en el Deep Learning moderno.\n\nlearning_rate = lr: Es el ritmo o tasa de aprendizaje. Es un hiperparámetro crucial que determina el tamaño de los \"pasos\" que el optimizador da para moverse hacia el mínimo de la función de pérdida. Una variable lr (que asumo fue definida antes en el código, por ejemplo, 0.001) controla este valor.\n\n2. Las Devoluciones de Llamada (Callbacks)\nLos Callbacks son funciones que se ejecutan automáticamente en varios puntos durante el entrenamiento del modelo (al inicio, al final de cada epoch, al final del entrenamiento, etc.). Están diseñados para hacer que el entrenamiento sea más eficiente y robusto.\n\nA. Reducción de la Tasa de Aprendizaje (reduce_lr)\nPython\n\nreduce_lr = keras.callbacks.ReduceLROnPlateau(factor = 0.5, patience = patience / 2, verbose=1)\nEste callback monitorea una métrica de rendimiento (normalmente la pérdida de validación) y, si esa métrica deja de mejorar después de un número de épocas, reduce la tasa de aprendizaje. Esto ayuda al modelo a converger de manera más fina cuando ya está cerca de la solución.\n\nfactor = 0.5: Si la métrica se estanca, la tasa de aprendizaje se multiplicará por 0.5 (se reducirá a la mitad).\n\npatience = patience / 2: Es el número de épocas que el modelo esperará sin que haya mejoría antes de reducir la tasa de aprendizaje. Asumo que patience se definió para el Early Stopping, por lo que esto establece que la reducción de LR ocurrirá a la mitad de ese tiempo de espera.\n\nverbose = 1: Indica que el callback imprimirá un mensaje en la consola cada vez que se active la reducción de la tasa de aprendizaje.\n\nB. Parada Temprana (early_stop)\nPython\n\nearly_stop = keras.callbacks.EarlyStopping(patience = patience, verbose = 1, restore_best_weights = True)\nEste callback es una técnica esencial para evitar el sobreajuste (overfitting). Si el rendimiento del modelo en los datos de validación deja de mejorar, detiene el entrenamiento antes de tiempo.\n\npatience = patience: Es el número de épocas que el modelo esperará sin que haya mejoría en la métrica monitoreada antes de detener el entrenamiento. Si la mejora se estanca por patience épocas consecutivas, el entrenamiento se detiene.\n\nverbose = 1: Imprime un mensaje cuando el entrenamiento se detiene.\n\nrestore_best_weights = True: Clave. Cuando el entrenamiento se detiene, esta opción asegura que el modelo cargue los pesos que obtuvo en la época donde el rendimiento fue el mejor (en lugar de los pesos de la última época).\n\nResumen de la Estrategia\nEn conjunto, estas líneas definen una estrategia de entrenamiento muy sólida:\n\nAdam guía la optimización de forma eficiente.\n\nSi el progreso se vuelve lento, ReduceLROnPlateau reduce la tasa de aprendizaje para intentar afinar la convergencia.\n\nSi el progreso se detiene por completo, EarlyStopping detiene el entrenamiento y recupera el mejor modelo, protegiendo contra el sobreajuste.","metadata":{}},{"cell_type":"markdown","source":"## Compile model","metadata":{}},{"cell_type":"code","source":"# Compile model\nmodel_keras.compile(optimizer=optimizer, loss=keras.losses.CategoricalCrossentropy(), metrics=[keras.metrics.CategoricalAccuracy()])\nmodel_keras.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:17:28.957535Z","iopub.execute_input":"2025-10-08T01:17:28.958244Z","iopub.status.idle":"2025-10-08T01:17:28.983379Z","shell.execute_reply.started":"2025-10-08T01:17:28.958216Z","shell.execute_reply":"2025-10-08T01:17:28.982783Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train the model","metadata":{}},{"cell_type":"code","source":"history_keras = model_keras.fit(train_ds, validation_data=val_ds, epochs=epochs, callbacks=[early_stop, reduce_lr])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:19:36.764936Z","iopub.execute_input":"2025-10-08T01:19:36.765257Z","iopub.status.idle":"2025-10-08T01:20:56.134465Z","shell.execute_reply.started":"2025-10-08T01:19:36.765235Z","shell.execute_reply":"2025-10-08T01:20:56.133834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Evaluate","metadata":{}},{"cell_type":"markdown","source":"### History","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(12, 8))\nplt.subplot(2,1,1)\nplt.plot(np.array(history_keras.history['categorical_accuracy'])*100, label='Training accuracy')\nplt.plot(np.array(history_keras.history['val_categorical_accuracy'])*100, label='Validation accuracy')\nplt.title('Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy [%]')\nplt.legend(loc='lower right')\n\nplt.subplot(2,1,2)\nplt.plot(history_keras.history['loss'], label='Training loss')\nplt.plot(history_keras.history['val_loss'], label='Validation loss')\nplt.title('Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss [%]')\nplt.legend(loc='upper right')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:21:12.545773Z","iopub.execute_input":"2025-10-08T01:21:12.546580Z","iopub.status.idle":"2025-10-08T01:21:12.925378Z","shell.execute_reply.started":"2025-10-08T01:21:12.546556Z","shell.execute_reply":"2025-10-08T01:21:12.924501Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Predict on file","metadata":{}},{"cell_type":"code","source":"# Generate spectrograms from a given file\ndef gen_test_spectrograms_keras(file_path, sr, length):\n\n    spectrograms = []\n    audio, rec_sr = librosa.core.load(file_path, sr = sr, mono = True)\n    slice_length = rec_sr * length\n    n = len(audio) // slice_length\n\n    for i in range(n):\n        start = i * slice_length\n        end = start + slice_length\n        if end > len(audio):\n            end = len(audio)\n        sliced_audio = audio[start:end]\n\n        S = librosa.feature.melspectrogram(y = sliced_audio, sr = rec_sr)\n        S_db = librosa.power_to_db(S, ref = np.max)\n        S_norm = (S_db - S_db.min()) / (S_db.max() - S_db.min())\n        S_norm = (S_norm * 255).astype(np.uint8)\n\n        spectrograms.append(S_norm)\n\n        # Ez volt a probléma\n        #array=np.array(S_norm)/255.0\n        #spectrograms.append(array)\n\n    return spectrograms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:22:45.724328Z","iopub.execute_input":"2025-10-08T01:22:45.725148Z","iopub.status.idle":"2025-10-08T01:22:45.730406Z","shell.execute_reply.started":"2025-10-08T01:22:45.725116Z","shell.execute_reply":"2025-10-08T01:22:45.729767Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Predict results based on a collection of spectrograms\ndef predict_test_keras(\n    model_keras,\n    spectrograms,\n    threshold=0.5\n):\n    inputs = []\n    \n    for s in spectrograms:\n        tensor = np.expand_dims(s, axis=(0, -1))  # 1, height, width, 1\n        inputs.append(tensor)\n    inputs = np.concatenate(inputs, axis=0)\n\n    outputs = model_keras.predict(inputs, verbose=0)\n    pred = np.max(outputs, axis=0)\n    binary_pred = (pred > threshold).astype(int)\n    return pred, binary_pred","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T01:23:13.158285Z","iopub.execute_input":"2025-10-08T01:23:13.158583Z","iopub.status.idle":"2025-10-08T01:23:13.163521Z","shell.execute_reply.started":"2025-10-08T01:23:13.158562Z","shell.execute_reply":"2025-10-08T01:23:13.162728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#file_path = '/kaggle/input/rfcx-species-audio-detection/train/0099c367b.flac'\nfile_path = '/kaggle/input/rfcx-species-audio-detection/train/009b760e6.flac'\nspectrograms = gen_test_spectrograms_keras(file_path, sr = sr, length = slice_length)\npred, binary_pred = predict_test_keras(model_keras, spectrograms)\n\nprint(\"Egyes fajok előfordulásának valószínűsége:\")\nfor i, probability in enumerate(pred):\n   print(f\"{i}.\\t{probability:.5f}\")\nprint(\"Binary prediction:\")\nprint(binary_pred)\n\naudio, rec_sr = librosa.core.load(file_path, sr = sr, mono = True)\nAudio(audio, rate = rec_sr)","metadata":{"execution":{"iopub.status.busy":"2025-10-08T01:23:29.761408Z","iopub.execute_input":"2025-10-08T01:23:29.761947Z","iopub.status.idle":"2025-10-08T01:23:32.449809Z","shell.execute_reply.started":"2025-10-08T01:23:29.761923Z","shell.execute_reply":"2025-10-08T01:23:32.448522Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred = np.zeros([20,24])\npred[:,10] = 0.64\npred[:,16] = 0.74\npred[:,2] = 0.35\n#print(pred)\ntruth = np.zeros(pred.shape)\ntruth[:,10] = 1.\ntruth[:,16] = 1.\n#print(truth)\nm = keras.metrics.CategoricalAccuracy(name=\"categorical_accuracy\", dtype=None)\nm.reset_state()\nm.update_state(truth, pred)\nprint(m.result())\n\ncce = keras.losses.CategoricalCrossentropy()\nprint(cce(truth, pred))\n\nm = keras.metrics.Accuracy()\nm.reset_state()\nm.update_state(truth, pred)\nprint(m.result())\n\nbce = keras.losses.BinaryCrossentropy()\nprint(bce(truth, pred))\n\nm = keras.metrics.BinaryAccuracy()\nm.reset_state()\nm.update_state(truth, pred)\nprint(m.result())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T01:15:52.847262Z","iopub.execute_input":"2025-08-07T01:15:52.847937Z","iopub.status.idle":"2025-08-07T01:15:52.88257Z","shell.execute_reply.started":"2025-08-07T01:15:52.847892Z","shell.execute_reply":"2025-08-07T01:15:52.881662Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Create submission csv","metadata":{}},{"cell_type":"code","source":"def create_csv_keras(model_keras, test_path, csv_file=None):\n    rows = []\n\n    test_paths = os.listdir(test_path)\n    for i in tqdm(range(len(test_paths))):\n        \n        file = test_paths[i]\n        \n        if file.endswith('.flac'):\n            file_path = os.path.join(test_path, file)\n            recording_id = file.replace('.flac', '')\n            spectrograms = gen_test_spectrograms_keras(file_path, sr = sr, length = slice_length)\n            pred, _ = predict_test_keras(model_keras, spectrograms)\n            \n            rows.append([recording_id] + list(pred))\n\n    df = pd.DataFrame(rows, columns=['recording_id'] + [f\"s{i}\" for i in range(24)])\n    if csv_file:\n        df.to_csv(csv_file, float_format='%.5f', index=False)\n    else:\n        print(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T01:16:17.141423Z","iopub.execute_input":"2025-08-07T01:16:17.1419Z","iopub.status.idle":"2025-08-07T01:16:17.147702Z","shell.execute_reply.started":"2025-08-07T01:16:17.141876Z","shell.execute_reply":"2025-08-07T01:16:17.146908Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_dir='/kaggle/working/csv'\nos.makedirs(submission_dir, exist_ok=True)\ncsv_file = os.path.join(submission_dir, 'rainForest_submission_keras.csv')\ncreate_csv_keras(model_keras, test_path, csv_file=csv_file)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T01:16:20.905264Z","iopub.execute_input":"2025-08-07T01:16:20.90558Z"}},"outputs":[],"execution_count":null}]}