{"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":[{"sourceId":21669,"databundleVersionId":1692278,"sourceType":"competition"}],"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-08-17T08:45:12.092536Z","iopub.execute_input":"2025-08-17T08:45:12.093125Z","iopub.status.idle":"2025-08-17T08:45:17.865941Z","shell.execute_reply.started":"2025-08-17T08:45:12.093100Z","shell.execute_reply":"2025-08-17T08:45:17.865266Z"}},"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-08-17T08:45:17.867456Z","iopub.execute_input":"2025-08-17T08:45:17.867947Z","iopub.status.idle":"2025-08-17T08:45:17.872936Z","shell.execute_reply.started":"2025-08-17T08:45:17.867922Z","shell.execute_reply":"2025-08-17T08:45:17.872152Z"}},"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-08-17T08:45:17.873777Z","iopub.execute_input":"2025-08-17T08:45:17.874026Z","iopub.status.idle":"2025-08-17T08:45:17.890857Z","shell.execute_reply.started":"2025-08-17T08:45:17.874000Z","shell.execute_reply":"2025-08-17T08:45:17.890358Z"}},"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-08-17T08:45:17.892281Z","iopub.execute_input":"2025-08-17T08:45:17.892528Z","iopub.status.idle":"2025-08-17T08:45:17.970866Z","shell.execute_reply.started":"2025-08-17T08:45:17.892502Z","shell.execute_reply":"2025-08-17T08:45:17.970283Z"}},"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-08-17T08:45:17.971382Z","iopub.execute_input":"2025-08-17T08:45:17.971565Z","iopub.status.idle":"2025-08-17T08:45:17.975034Z","shell.execute_reply.started":"2025-08-17T08:45:17.971550Z","shell.execute_reply":"2025-08-17T08:45:17.974317Z"}},"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-08-17T08:45:17.975845Z","iopub.execute_input":"2025-08-17T08:45:17.976022Z","iopub.status.idle":"2025-08-17T08:45:24.504034Z","shell.execute_reply.started":"2025-08-17T08:45:17.976007Z","shell.execute_reply":"2025-08-17T08:45:24.502701Z"}},"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-08-17T08:45:24.504961Z","iopub.execute_input":"2025-08-17T08:45:24.505290Z","iopub.status.idle":"2025-08-17T08:45:34.956259Z","shell.execute_reply.started":"2025-08-17T08:45:24.505272Z","shell.execute_reply":"2025-08-17T08:45:34.955451Z"}},"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-08-17T08:45:34.956953Z","iopub.execute_input":"2025-08-17T08:45:34.957135Z","iopub.status.idle":"2025-08-17T08:45:35.036814Z","shell.execute_reply.started":"2025-08-17T08:45:34.957119Z","shell.execute_reply":"2025-08-17T08:45:35.036110Z"}},"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-08-17T08:45:35.037494Z","iopub.execute_input":"2025-08-17T08:45:35.037750Z","iopub.status.idle":"2025-08-17T08:47:45.246633Z","shell.execute_reply.started":"2025-08-17T08:45:35.037725Z","shell.execute_reply":"2025-08-17T08:47:45.245843Z"}},"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-08-17T08:47:45.248358Z","iopub.execute_input":"2025-08-17T08:47:45.248581Z","iopub.status.idle":"2025-08-17T08:47:45.265326Z","shell.execute_reply.started":"2025-08-17T08:47:45.248563Z","shell.execute_reply":"2025-08-17T08:47:45.264588Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Label smoothing","metadata":{}},{"cell_type":"code","source":"def label_smoothing(y, pos_v=0.5, neg_v=0.1):\n    return y*pos_v+(1-y)*neg_v\n\ndef label_smoothing_class(y, factor=0.1, num_classes=numSpecies):\n    return y*(1-factor)+factor/num_classes","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:47:45.266457Z","iopub.execute_input":"2025-08-17T08:47:45.266792Z","iopub.status.idle":"2025-08-17T08:47:45.273491Z","shell.execute_reply.started":"2025-08-17T08:47:45.266773Z","shell.execute_reply":"2025-08-17T08:47:45.272914Z"}},"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-08-11T06:39:37.385971Z","iopub.execute_input":"2025-08-11T06:39:37.386192Z","iopub.status.idle":"2025-08-11T06:39:48.296607Z","shell.execute_reply.started":"2025-08-11T06:39:37.386167Z","shell.execute_reply":"2025-08-11T06:39:48.295783Z"}},"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-08-11T06:39:48.297613Z","iopub.execute_input":"2025-08-11T06:39:48.298021Z","iopub.status.idle":"2025-08-11T06:39:48.568432Z","shell.execute_reply.started":"2025-08-11T06:39:48.297999Z","shell.execute_reply":"2025-08-11T06:39:48.567775Z"}},"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-08-11T06:39:48.569145Z","iopub.execute_input":"2025-08-11T06:39:48.569721Z","iopub.status.idle":"2025-08-11T06:39:48.575222Z","shell.execute_reply.started":"2025-08-11T06:39:48.569702Z","shell.execute_reply":"2025-08-11T06:39:48.574597Z"}},"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-08-11T06:39:48.576102Z","iopub.execute_input":"2025-08-11T06:39:48.576356Z","iopub.status.idle":"2025-08-11T06:39:48.593719Z","shell.execute_reply.started":"2025-08-11T06:39:48.576332Z","shell.execute_reply":"2025-08-11T06:39:48.593168Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Define model","metadata":{}},{"cell_type":"code","source":"# torch.cuda.empty_cache()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T06:39:48.594349Z","iopub.execute_input":"2025-08-11T06:39:48.594527Z","iopub.status.idle":"2025-08-11T06:39:48.597848Z","shell.execute_reply.started":"2025-08-11T06:39:48.594513Z","shell.execute_reply":"2025-08-11T06:39:48.597194Z"}},"outputs":[],"execution_count":null},{"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, 32, (3,3)),\n            nn.ReLU(),\n            #nn.MaxPool2d(2),\n            nn.Flatten(),\n            #nn.Linear(32*(inputSize[0]-4)*(inputSize[1]-4), 128),\n            nn.Linear(32*(inputSize[0]-6)*(inputSize[1]-6), 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-08-11T06:40:32.355737Z","iopub.execute_input":"2025-08-11T06:40:32.356034Z","iopub.status.idle":"2025-08-11T06:40:32.361689Z","shell.execute_reply.started":"2025-08-11T06:40:32.356015Z","shell.execute_reply":"2025-08-11T06:40:32.360815Z"}},"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                #labels=label_smoothing(labels) # apply smoothing\n                labels=label_smoothing_class(labels)\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-08-11T06:40:34.916427Z","iopub.execute_input":"2025-08-11T06:40:34.917004Z","iopub.status.idle":"2025-08-11T06:40:34.927747Z","shell.execute_reply.started":"2025-08-11T06:40:34.916979Z","shell.execute_reply":"2025-08-11T06:40:34.926928Z"}},"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-08-11T06:40:38.516046Z","iopub.execute_input":"2025-08-11T06:40:38.516348Z","iopub.status.idle":"2025-08-11T06:40:39.648157Z","shell.execute_reply.started":"2025-08-11T06:40:38.516322Z","shell.execute_reply":"2025-08-11T06:40:39.647568Z"}},"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-08-11T06:40:41.584352Z","iopub.execute_input":"2025-08-11T06:40:41.584618Z","iopub.status.idle":"2025-08-11T06:42:33.938817Z","shell.execute_reply.started":"2025-08-11T06:40:41.584599Z","shell.execute_reply":"2025-08-11T06:42:33.937892Z"}},"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-08-11T06:42:33.939973Z","iopub.execute_input":"2025-08-11T06:42:33.940209Z","iopub.status.idle":"2025-08-11T06:42:34.322841Z","shell.execute_reply.started":"2025-08-11T06:42:33.940192Z","shell.execute_reply":"2025-08-11T06:42:34.322027Z"}},"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-08-11T06:42:34.323713Z","iopub.execute_input":"2025-08-11T06:42:34.323979Z","iopub.status.idle":"2025-08-11T06:42:34.329436Z","shell.execute_reply.started":"2025-08-11T06:42:34.323951Z","shell.execute_reply":"2025-08-11T06:42:34.328607Z"}},"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-08-11T06:42:34.331323Z","iopub.execute_input":"2025-08-11T06:42:34.331533Z","iopub.status.idle":"2025-08-11T06:42:34.342659Z","shell.execute_reply.started":"2025-08-11T06:42:34.331517Z","shell.execute_reply":"2025-08-11T06:42:34.342032Z"}},"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-08-11T06:42:34.343359Z","iopub.execute_input":"2025-08-11T06:42:34.343581Z","iopub.status.idle":"2025-08-11T06:42:34.852228Z","shell.execute_reply.started":"2025-08-11T06:42:34.343560Z","shell.execute_reply":"2025-08-11T06:42:34.850971Z"}},"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-08-11T06:42:34.853836Z","iopub.execute_input":"2025-08-11T06:42:34.854042Z","iopub.status.idle":"2025-08-11T06:42:34.860945Z","shell.execute_reply.started":"2025-08-11T06:42:34.854027Z","shell.execute_reply":"2025-08-11T06:42:34.859866Z"}},"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-08-11T06:42:34.861896Z","iopub.execute_input":"2025-08-11T06:42:34.862084Z","iopub.status.idle":"2025-08-11T06:53:17.906606Z","shell.execute_reply.started":"2025-08-11T06:42:34.862070Z","shell.execute_reply":"2025-08-11T06:53:17.906056Z"}},"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-08-10T15:36:39.718693Z","iopub.execute_input":"2025-08-10T15:36:39.719397Z","iopub.status.idle":"2025-08-10T15:36:54.687502Z","shell.execute_reply.started":"2025-08-10T15:36:39.719372Z","shell.execute_reply":"2025-08-10T15:36:54.686860Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"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-08-10T15:36:54.693257Z","iopub.execute_input":"2025-08-10T15:36:54.693447Z","iopub.status.idle":"2025-08-10T15:36:54.726902Z","shell.execute_reply.started":"2025-08-10T15:36:54.693432Z","shell.execute_reply":"2025-08-10T15:36:54.726313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(input_size)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T15:36:54.728607Z","iopub.execute_input":"2025-08-10T15:36:54.728855Z","iopub.status.idle":"2025-08-10T15:36:54.738967Z","shell.execute_reply.started":"2025-08-10T15:36:54.728836Z","shell.execute_reply":"2025-08-10T15:36:54.738237Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset handling","metadata":{}},{"cell_type":"code","source":"# Label smoothing\ndef label_smoothing_map(x, y):\n    return x, label_smoothing(y)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T15:36:54.739732Z","iopub.execute_input":"2025-08-10T15:36:54.739940Z","iopub.status.idle":"2025-08-10T15:36:54.752348Z","shell.execute_reply.started":"2025-08-10T15:36:54.739924Z","shell.execute_reply":"2025-08-10T15:36:54.751612Z"}},"outputs":[],"execution_count":null},{"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-08-10T15:36:54.753022Z","iopub.execute_input":"2025-08-10T15:36:54.753299Z","iopub.status.idle":"2025-08-10T15:36:57.763941Z","shell.execute_reply.started":"2025-08-10T15:36:54.753272Z","shell.execute_reply":"2025-08-10T15:36:57.763092Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds=train_ds.map(label_smoothing_map)\nval_ds=val_ds.map(label_smoothing_map)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T15:39:42.827650Z","iopub.execute_input":"2025-08-10T15:39:42.827931Z","iopub.status.idle":"2025-08-10T15:39:42.868175Z","shell.execute_reply.started":"2025-08-10T15:39:42.827911Z","shell.execute_reply":"2025-08-10T15:39:42.867554Z"}},"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    layers.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    #layers.Dense(numSpecies) # if from_logits=True\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T15:39:42.869401Z","iopub.execute_input":"2025-08-10T15:39:42.869797Z","iopub.status.idle":"2025-08-10T15:39:43.840475Z","shell.execute_reply.started":"2025-08-10T15:39:42.869779Z","shell.execute_reply":"2025-08-10T15:39:43.839873Z"}},"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-08-10T15:39:43.841201Z","iopub.execute_input":"2025-08-10T15:39:43.841428Z","iopub.status.idle":"2025-08-10T15:39:43.851827Z","shell.execute_reply.started":"2025-08-10T15:39:43.841410Z","shell.execute_reply":"2025-08-10T15:39:43.850948Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Compile model","metadata":{}},{"cell_type":"code","source":"# Compile model\nmodel_keras.compile(\n    optimizer=optimizer, \n    #loss=keras.losses.BinaryCrossentropy(from_logits=True),\n    loss=keras.losses.BinaryCrossentropy(),\n    metrics=[keras.metrics.CategoricalAccuracy()])\nmodel_keras.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T15:39:43.853469Z","iopub.execute_input":"2025-08-10T15:39:43.853689Z","iopub.status.idle":"2025-08-10T15:39:43.878696Z","shell.execute_reply.started":"2025-08-10T15:39:43.853664Z","shell.execute_reply":"2025-08-10T15:39:43.878162Z"}},"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-08-10T15:39:43.879330Z","iopub.execute_input":"2025-08-10T15:39:43.879523Z","iopub.status.idle":"2025-08-10T15:42:00.545440Z","shell.execute_reply.started":"2025-08-10T15:39:43.879508Z","shell.execute_reply":"2025-08-10T15:42:00.544669Z"}},"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-08-10T15:42:00.546333Z","iopub.execute_input":"2025-08-10T15:42:00.546564Z","iopub.status.idle":"2025-08-10T15:42:00.972718Z","shell.execute_reply.started":"2025-08-10T15:42:00.546546Z","shell.execute_reply":"2025-08-10T15:42:00.971754Z"}},"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-08-10T15:42:00.973578Z","iopub.execute_input":"2025-08-10T15:42:00.974211Z","iopub.status.idle":"2025-08-10T15:42:00.979979Z","shell.execute_reply.started":"2025-08-10T15:42:00.974184Z","shell.execute_reply":"2025-08-10T15:42:00.979060Z"}},"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-08-10T15:42:00.980771Z","iopub.execute_input":"2025-08-10T15:42:00.981021Z","iopub.status.idle":"2025-08-10T15:42:00.997751Z","shell.execute_reply.started":"2025-08-10T15:42:00.981005Z","shell.execute_reply":"2025-08-10T15:42:00.997140Z"}},"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-08-10T15:42:00.998490Z","iopub.execute_input":"2025-08-10T15:42:00.998694Z","iopub.status.idle":"2025-08-10T15:42:05.666320Z","shell.execute_reply.started":"2025-08-10T15:42:00.998678Z","shell.execute_reply":"2025-08-10T15:42:05.665463Z"},"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-10T15:42:05.668045Z","iopub.execute_input":"2025-08-10T15:42:05.668304Z","iopub.status.idle":"2025-08-10T15:42:06.646391Z","shell.execute_reply.started":"2025-08-10T15:42:05.668285Z","shell.execute_reply":"2025-08-10T15:42:06.645456Z"}},"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-10T15:42:06.647298Z","iopub.execute_input":"2025-08-10T15:42:06.647508Z","iopub.status.idle":"2025-08-10T15:42:06.653910Z","shell.execute_reply.started":"2025-08-10T15:42:06.647491Z","shell.execute_reply":"2025-08-10T15:42:06.653159Z"}},"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-10T15:42:06.655384Z","iopub.execute_input":"2025-08-10T15:42:06.655624Z","iopub.status.idle":"2025-08-10T15:55:40.366893Z","shell.execute_reply.started":"2025-08-10T15:42:06.655607Z","shell.execute_reply":"2025-08-10T15:55:40.366000Z"}},"outputs":[],"execution_count":null}]}