{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}},"cells":[{"cell_type":"markdown","source":"# Competition: freesound-audio-tagging-2019\n\n**Generated by Alexandria Research Assistant**\n\n**Dataset:** freesound-audio-tagging-2019\n\n**Task:** competition - kaggle-competition\n\n---\n\n⚠️ **Note:** This notebook contains Alexandria markers (lines starting with `# ⚠️ ALEXANDRIA MARKER`) at the top of each code cell. These markers enable the 'Sync from Kaggle' feature to track cell outputs. Please do not delete them.","metadata":{}},{"cell_type":"markdown","source":"## Setup & Imports\n\nInstall and import all required libraries for audio processing, data manipulation, and modeling.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_1_START===\")\n\n# Setup & Imports\n\n# Install required packages (if not already available in the Kaggle environment)\n!pip install --quiet torch torchaudio librosa pandas numpy scikit-learn matplotlib seaborn\n\n# Import essential libraries\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torchaudio\nimport librosa\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Set data path (as required)\nDATA_PATH = \"/kaggle/input/freesound-audio-tagging-2019\"\n\n# Set random seeds for reproducibility\ndef seed_everything(seed=42):\n    print(f\"Setting random seed: {seed}\")\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nseed_everything(42)\n\n# Print library versions for reproducibility\nprint(\"torch:\", torch.__version__)\nprint(\"torchaudio:\", torchaudio.__version__)\nprint(\"librosa:\", librosa.__version__)\nprint(\"pandas:\", pd.__version__)\nprint(\"numpy:\", np.__version__)\n\n# Print dataset files to verify structure\nprint(\"Files in DATA_PATH:\")\nfor fname in os.listdir(DATA_PATH):\n    print(\"  -\", fname)","outputs":[],"cell_number":1,"version":1,"status":"generated","created_at":"2025-11-16T18:34:24.586538+00:00","metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Data Loading\n\nLoad all competition files, including CSVs and audio archives, and verify their structure.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_2_START===\")\n\n# ===ALEXANDRIA_CELL_2_START===\n# Data Loading\n\nimport zipfile\nimport pandas as pd\nimport os\n\n# Define the data path\nDATA_PATH = \"/kaggle/input/freesound-audio-tagging-2019\"\n\n# List all files in the directory\nprint(\"Files in DATA_PATH:\")\nfor fname in os.listdir(DATA_PATH):\n    print(\"  -\", fname)\n\n# Load CSV files\nprint(\"\\nLoading CSV files...\")\ntrain_curated_csv = pd.read_csv(os.path.join(DATA_PATH, \"train_curated.csv\"))\ntrain_noisy_csv = pd.read_csv(os.path.join(DATA_PATH, \"train_noisy.csv\"))\nsample_submission_csv = pd.read_csv(os.path.join(DATA_PATH, \"sample_submission.csv\"))\n\n# Display basic info about each CSV\nprint(\"\\ntrain_curated.csv shape:\", train_curated_csv.shape)\nprint(\"train_curated.csv columns:\", list(train_curated_csv.columns))\nprint(\"\\ntrain_noisy.csv shape:\", train_noisy_csv.shape)\nprint(\"train_noisy.csv columns:\", list(train_noisy_csv.columns))\nprint(\"\\nsample_submission.csv shape:\", sample_submission_csv.shape)\nprint(\"sample_submission.csv columns:\", list(sample_submission_csv.columns))\n\n# Extract and list audio files from zip archives\nprint(\"\\nExtracting and listing audio files from zip archives...\")\n\nzip_files = [\"train_curated.zip\", \"train_noisy.zip\", \"test.zip\"]\naudio_files = {}\n\nfor zip_name in zip_files:\n    zip_path = os.path.join(DATA_PATH, zip_name)\n    if os.path.exists(zip_path):\n        print(f\"Processing {zip_name}...\")\n        with zipfile.ZipFile(zip_path, 'r') as z:\n            # List all files in the zip\n            file_list = z.namelist()\n            # Filter for audio files (assuming .wav extension)\n            audio_list = [f for f in file_list if f.lower().endswith('.wav')]\n            audio_files[zip_name] = audio_list\n            print(f\"  Found {len(audio_list)} audio files in {zip_name}\")\n    else:\n        print(f\"Warning: {zip_name} not found in {DATA_PATH}\")\n        audio_files[zip_name] = []\n\n# Check for missing or corrupted files\nprint(\"\\nChecking for missing or corrupted files...\")\n\nfor zip_name, files in audio_files.items():\n    if not files:\n        print(f\"Warning: No audio files found in {zip_name}\")\n    else:\n        # Check if all files are readable (basic check)\n        for f in files[:5]:  # Check first 5 files as sample\n            try:\n                with zipfile.ZipFile(os.path.join(DATA_PATH, zip_name), 'r') as z:\n                    with z.open(f) as audio_file:\n                        # Try to read a small portion to check if file is not corrupted\n                        audio_file.read(100)\n            except Exception as e:\n                print(f\"Error reading {f} from {zip_name}: {e}\")\n\nprint(\"\\nData loading and verification complete.\")","outputs":[],"cell_number":2,"version":1,"status":"generated","created_at":"2025-11-16T18:34:36.961072+00:00","metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Exploratory Data Analysis (EDA)\n\nAnalyze label distributions, audio durations, and data quality in curated and noisy sets.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_3_START===\")\n\n# ===ALEXANDRIA_CELL_3_START===\n# Exploratory Data Analysis (EDA): Label distributions, audio durations, and data quality\n\nimport os\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport librosa\nfrom collections import Counter\nfrom itertools import combinations\n\n# Use variables from previous cells\n# train_curated_csv, train_noisy_csv, audio_files, DATA_PATH\n\nprint(\"=== Exploratory Data Analysis (EDA) ===\")\n\n# 1. Label frequency analysis\n\ndef get_label_counts(df, label_col=\"labels\"):\n    \"\"\"Count frequency of each label in a multi-label column (comma-separated).\"\"\"\n    all_labels = []\n    for labels in df[label_col]:\n        all_labels.extend(labels.split(','))\n    return pd.Series(Counter(all_labels)).sort_values(ascending=False)\n\nprint(\"\\n[1] Label frequency in curated set:\")\ncurated_label_counts = get_label_counts(train_curated_csv)\nprint(curated_label_counts.head(10))\n\nprint(\"\\n[2] Label frequency in noisy set:\")\nnoisy_label_counts = get_label_counts(train_noisy_csv)\nprint(noisy_label_counts.head(10))\n\n# Plot label frequency (top 30 for readability)\nplt.figure(figsize=(16, 6))\nsns.barplot(x=curated_label_counts.head(30).index, y=curated_label_counts.head(30).values, color=\"b\", alpha=0.7, label=\"Curated\")\nsns.barplot(x=noisy_label_counts.head(30).index, y=noisy_label_counts.head(30).values, color=\"r\", alpha=0.4, label=\"Noisy\")\nplt.title(\"Top 30 Label Frequencies (Curated: Blue, Noisy: Red Overlay)\")\nplt.ylabel(\"Count\")\nplt.xticks(rotation=75)\nplt.legend([\"Curated\", \"Noisy\"])\nplt.tight_layout()\nplt.show()\n\n# 2. Label co-occurrence matrix (curated set)\ndef compute_cooccurrence_matrix(df, label_col=\"labels\"):\n    \"\"\"Compute label co-occurrence matrix for a multi-label dataframe.\"\"\"\n    labels = sorted(set(l for row in df[label_col] for l in row.split(',')))\n    label_idx = {l: i for i, l in enumerate(labels)}\n    matrix = np.zeros((len(labels), len(labels)), dtype=int)\n    for row in df[label_col]:\n        row_labels = row.split(',')\n        for l1, l2 in combinations(sorted(set(row_labels)), 2):\n            i, j = label_idx[l1], label_idx[l2]\n            matrix[i, j] += 1\n            matrix[j, i] += 1\n    np.fill_diagonal(matrix, 0)\n    return pd.DataFrame(matrix, index=labels, columns=labels)\n\nprint(\"\\n[3] Computing label co-occurrence matrix for curated set...\")\ncurated_co_matrix = compute_cooccurrence_matrix(train_curated_csv)\nplt.figure(figsize=(12, 10))\nsns.heatmap(curated_co_matrix, cmap=\"Blues\", square=True, cbar_kws={\"label\": \"Co-occurrence\"})\nplt.title(\"Label Co-occurrence Matrix (Curated Set)\")\nplt.xlabel(\"Label\")\nplt.ylabel(\"Label\")\nplt.tight_layout()\nplt.show()\n\n# 3. Audio duration histograms for curated and noisy sets\n\ndef get_audio_durations(zip_path, file_list, sample_size=1000):\n    \"\"\"Extract durations for a sample of audio files in a zip archive.\"\"\"\n    durations = []\n    with zipfile.ZipFile(zip_path, 'r') as z:\n        # Sample up to sample_size files for speed\n        files = file_list if len(file_list) <= sample_size else np.random.choice(file_list, sample_size, replace=False)\n        for fname in files:\n            with z.open(fname) as f:\n                try:\n                    y, sr = librosa.load(f, sr=None, mono=True)\n                    durations.append(len(y) / sr)\n                except Exception as e:\n                    # Skip unreadable/corrupted files\n                    continue\n    return durations\n\nprint(\"\\n[4] Extracting audio durations (this may take a minute)...\")\ncurated_zip = os.path.join(DATA_PATH, \"train_curated.zip\")\nnoisy_zip = os.path.join(DATA_PATH, \"train_noisy.zip\")\ncurated_files = audio_files.get(\"train_curated.zip\", [])\nnoisy_files = audio_files.get(\"train_noisy.zip\", [])\n\ncurated_durations = get_audio_durations(curated_zip, curated_files, sample_size=1000)\nnoisy_durations = get_audio_durations(noisy_zip, noisy_files, sample_size=1000)\n\nprint(f\"  Curated: {len(curated_durations)} files sampled\")\nprint(f\"  Noisy:   {len(noisy_durations)} files sampled\")\n\nplt.figure(figsize=(14, 5))\nsns.histplot(curated_durations, bins=50, color=\"b\", label=\"Curated\", kde=True, stat=\"density\", alpha=0.7)\nsns.histplot(noisy_durations, bins=50, color=\"r\", label=\"Noisy\", kde=True, stat=\"density\", alpha=0.5)\nplt.title(\"Audio Duration Distribution (Curated vs. Noisy, Sampled)\")\nplt.xlabel(\"Duration (seconds)\")\nplt.ylabel(\"Density\")\nplt.legend()\nplt.tight_layout()\nplt.show()\n\n# 4. Compare curated vs. noisy label reliability (label overlap)\n\ncurated_labels_set = set(curated_label_counts.index)\nnoisy_labels_set = set(noisy_label_counts.index)\ncommon_labels = curated_labels_set & noisy_labels_set\nunique_curated = curated_labels_set - noisy_labels_set\nunique_noisy = noisy_labels_set - curated_labels_set\n\nprint(\"\\n[5] Label overlap analysis:\")\nprint(f\"  Labels in both sets: {len(common_labels)}\")\nprint(f\"  Labels only in curated: {len(unique_curated)}\")\nprint(f\"  Labels only in noisy: {len(unique_noisy)}\")\n\n# Visualize overlap\nplt.figure(figsize=(6, 6))\nvenn_counts = [len(unique_curated), len(unique_noisy), len(common_labels)]\nplt.bar([\"Curated only\", \"Noisy only\", \"Both\"], venn_counts, color=[\"b\", \"r\", \"purple\"])\nplt.title(\"Label Overlap: Curated vs. Noisy\")\nplt.ylabel(\"Number of Labels\")\nplt.tight_layout()\nplt.show()\n\n# 5. Data quality: Check for missing or duplicate audio files in CSVs\n\ndef check_audio_file_existence(df, audio_list, fname_col=\"fname\"):\n    \"\"\"Check if all files listed in CSV exist in the audio archive.\"\"\"\n    csv_files = set(df[fname_col])\n    audio_files_set = set(os.path.basename(f) for f in audio_list)\n    missing = csv_files - audio_files_set\n    duplicates = df[fname_col][df[fname_col].duplicated()].unique()\n    return missing, duplicates\n\nprint(\"\\n[6] Data quality checks:\")\ncurated_missing, curated_duplicates = check_audio_file_existence(train_curated_csv, curated_files)\nnoisy_missing, noisy_duplicates = check_audio_file_existence(train_noisy_csv, noisy_files)\n\nprint(f\"  Curated: {len(curated_missing)} missing files, {len(curated_duplicates)} duplicate entries\")\nprint(f\"  Noisy:   {len(noisy_missing)} missing files, {len(noisy_duplicates)} duplicate entries\")\n\nif curated_missing:\n    print(\"  Missing files in curated:\", list(curated_missing)[:5], \"...\")\nif noisy_missing:\n    print(\"  Missing files in noisy:\", list(noisy_missing)[:5], \"...\")\nif curated_duplicates.size > 0:\n    print(\"  Duplicate files in curated:\", list(curated_duplicates))\nif noisy_duplicates.size > 0:\n    print(\"  Duplicate files in noisy:\", list(noisy_duplicates))\n\nprint(\"\\nEDA complete.\")","outputs":[],"cell_number":3,"version":1,"status":"generated","created_at":"2025-11-16T18:34:56.986351+00:00","metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Preprocessing\n\nPrepare audio data for modeling by cleaning, normalizing, and converting to spectrograms.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_4_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_4_START===\")\n\n# Preprocessing: Prepare audio data for modeling\n\nimport os\nimport zipfile\nimport torch\nimport torchaudio\nimport torchaudio.transforms as T\nimport numpy as np\nimport pandas as pd\n\n# Use variables from previous cells\n# DATA_PATH, train_curated_csv, train_noisy_csv, audio_files\n\n# Parameters for preprocessing\nTARGET_SAMPLE_RATE = 32000  # Common for audio tagging tasks\nAUDIO_DURATION = 5.0        # seconds (fixed length for all clips)\nN_MELS = 128                # Number of mel bands\nFMIN = 20                   # Minimum frequency for mel filterbank\nFMAX = TARGET_SAMPLE_RATE // 2  # Maximum frequency for mel filterbank\n\n# Calculate target number of samples\nTARGET_NUM_SAMPLES = int(TARGET_SAMPLE_RATE * AUDIO_DURATION)\n\n# MelSpectrogram transform (log-mel)\nmel_transform = T.MelSpectrogram(\n    sample_rate=TARGET_SAMPLE_RATE,\n    n_fft=2048,\n    hop_length=512,\n    n_mels=N_MELS,\n    f_min=FMIN,\n    f_max=FMAX,\n    power=2.0,\n    normalized=False,\n)\namplitude_to_db = T.AmplitudeToDB(stype=\"power\", top_db=80)\n\ndef preprocess_audio_file(zip_path, audio_fname):\n    \"\"\"\n    Load, resample, trim/pad, and convert an audio file to log-mel spectrogram.\n    Returns: log-mel spectrogram (Tensor), label(s), and metadata.\n    \"\"\"\n    # Load audio from zip archive\n    with zipfile.ZipFile(zip_path, 'r') as z:\n        with z.open(audio_fname) as f:\n            waveform, orig_sr = torchaudio.load(f)\n    \n    # Convert to mono if not already\n    if waveform.shape > 1:\n        waveform = torch.mean(waveform, dim=0, keepdim=True)\n    \n    # Resample if needed\n    if orig_sr != TARGET_SAMPLE_RATE:\n        resampler = T.Resample(orig_sr, TARGET_SAMPLE_RATE)\n        waveform = resampler(waveform)\n    \n    # Trim or pad to fixed length\n    num_samples = waveform.shape[1]\n    if num_samples > TARGET_NUM_SAMPLES:\n        # Center trim\n        start = (num_samples - TARGET_NUM_SAMPLES) // 2\n        waveform = waveform[:, start:start+TARGET_NUM_SAMPLES]\n    elif num_samples < TARGET_NUM_SAMPLES:\n        # Pad with zeros at end\n        pad_amt = TARGET_NUM_SAMPLES - num_samples\n        waveform = torch.nn.functional.pad(waveform, (0, pad_amt))\n    \n    # Normalize waveform to [-1, 1]\n    waveform = waveform / waveform.abs().max() if waveform.abs().max() > 0 else waveform\n\n    # Convert to log-mel spectrogram\n    mel_spec = mel_transform(waveform)\n    log_mel_spec = amplitude_to_db(mel_spec)\n    # Shape: (n_mels, time)\n    return log_mel_spec\n\n# Example: Preprocess a small sample of curated and noisy audio files\n\ndef get_label_for_file(df, fname):\n    \"\"\"Return the label(s) for a given filename from the dataframe.\"\"\"\n    row = df[df[\"fname\"] == fname]\n    if not row.empty:\n        return row.iloc[\"labels\"]\n    return None\n\n# Choose a small sample for demonstration (can scale up for full dataset)\ncurated_zip = os.path.join(DATA_PATH, \"train_curated.zip\")\nnoisy_zip = os.path.join(DATA_PATH, \"train_noisy.zip\")\ncurated_files = audio_files.get(\"train_curated.zip\", [])[:10]\nnoisy_files = audio_files.get(\"train_noisy.zip\", [])[:10]\n\npreprocessed_curated = []\npreprocessed_noisy = []\n\nprint(\"Preprocessing curated audio files...\")\nfor fname in curated_files:\n    log_mel = preprocess_audio_file(curated_zip, fname)\n    label = get_label_for_file(train_curated_csv, os.path.basename(fname))\n    preprocessed_curated.append({\n        \"fname\": fname,\n        \"log_mel\": log_mel,\n        \"label\": label\n    })\nprint(f\"Processed {len(preprocessed_curated)} curated files.\")\n\nprint(\"Preprocessing noisy audio files...\")\nfor fname in noisy_files:\n    log_mel = preprocess_audio_file(noisy_zip, fname)\n    label = get_label_for_file(train_noisy_csv, os.path.basename(fname))\n    preprocessed_noisy.append({\n        \"fname\": fname,\n        \"log_mel\": log_mel,\n        \"label\": label\n    })\nprint(f\"Processed {len(preprocessed_noisy)} noisy files.\")\n\n# Optional: Data cleaning for noisy labels (filtering out rare or ambiguous labels)\n# Here, as an example, filter out noisy samples with labels not present in curated set\ncurated_label_set = set(l for labels in train_curated_csv[\"labels\"] for l in labels.split(','))\nfiltered_noisy = [\n    item for item in preprocessed_noisy\n    if all(l in curated_label_set for l in item[\"label\"].split(','))\n]\nprint(f\"Filtered noisy files: {len(filtered_noisy)} (from {len(preprocessed_noisy)})\")\n\n# Show example log-mel spectrogram\nimport matplotlib.pyplot as plt\n\ndef plot_log_mel(log_mel, title=\"Log-Mel Spectrogram\"):\n    plt.figure(figsize=(10, 4))\n    plt.imshow(log_mel.squeeze().numpy(), aspect='auto', origin='lower', cmap='magma')\n    plt.colorbar(format=\"%+2.0f dB\")\n    plt.title(title)\n    plt.xlabel(\"Time frames\")\n    plt.ylabel(\"Mel bins\")\n    plt.tight_layout()\n    plt.show()\n\n# Plot first curated sample\nif preprocessed_curated:\n    print(\"Displaying example log-mel spectrogram (curated):\")\n    plot_log_mel(preprocessed_curated[\"log_mel\"], title=f\"Curated: {preprocessed_curated['fname']}\")\n\n# Plot first filtered noisy sample\nif filtered_noisy:\n    print(\"Displaying example log-mel spectrogram (filtered noisy):\")\n    plot_log_mel(filtered_noisy[\"log_mel\"], title=f\"Noisy: {filtered_noisy['fname']}\")\n\nprint(\"Preprocessing complete.\")","outputs":[],"cell_number":4,"version":1,"status":"generated","created_at":"2025-11-16T18:35:13.829106+00:00","metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Feature Engineering\n\nGenerate task-specific features such as augmented spectrograms and delta features.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_5_START===\")\n\nprint(\"===ALEXANDRIA_CELL_5_START===\")\n\n# Feature Engineering: Augment spectrograms, compute delta features, stack tensors\n\nimport os\nimport zipfile\nimport torch\nimport torchaudio\nimport torchaudio.transforms as T\nimport numpy as np\nimport pandas as pd\nimport random\nimport librosa\nimport matplotlib.pyplot as plt\n\n# Use variables from prior cells\n# DATA_PATH, train_curated_csv, train_noisy_csv, audio_files\n\n# --- Augmentation Functions ---\n\ndef spec_augment(mel_spec, time_mask_param=30, freq_mask_param=13, num_time_masks=2, num_freq_masks=2):\n    \"\"\"Apply SpecAugment: time and frequency masking on log-mel spectrogram.\"\"\"\n    augmented = mel_spec.clone()\n    _, num_frames = augmented.shape\n    # Time masking\n    for _ in range(num_time_masks):\n        t = random.randint(0, time_mask_param)\n        t0 = random.randint(0, max(1, num_frames - t))\n        augmented[:, t0:t0 + t] = 0\n    # Frequency masking\n    num_mels, _ = augmented.shape\n    for _ in range(num_freq_masks):\n        f = random.randint(0, freq_mask_param)\n        f0 = random.randint(0, max(1, num_mels - f))\n        augmented[f0:f0 + f, :] = 0\n    return augmented\n\ndef mixup(spec1, spec2, label1, label2, alpha=0.4):\n    \"\"\"Apply Mixup to two spectrograms and their labels.\"\"\"\n    lam = np.random.beta(alpha, alpha)\n    mixed_spec = lam * spec1 + (1 - lam) * spec2\n    # For multi-label: convert to one-hot and mix\n    if isinstance(label1, np.ndarray) and isinstance(label2, np.ndarray):\n        mixed_label = lam * label1 + (1 - lam) * label2\n    else:\n        mixed_label = (label1, label2, lam)\n    return mixed_spec, mixed_label\n\ndef random_crop(spec, crop_frames):\n    \"\"\"Randomly crop spectrogram along time axis to crop_frames.\"\"\"\n    _, t = spec.shape\n    if t <= crop_frames:\n        return spec\n    start = random.randint(0, t - crop_frames)\n    return spec[:, start:start + crop_frames]\n\ndef compute_deltas(spec):\n    \"\"\"Compute delta and delta-delta features for a spectrogram.\"\"\"\n    spec_np = spec.cpu().numpy()\n    delta = librosa.feature.delta(spec_np)\n    delta2 = librosa.feature.delta(spec_np, order=2)\n    # Convert back to torch tensors\n    delta = torch.tensor(delta, dtype=spec.dtype)\n    delta2 = torch.tensor(delta2, dtype=spec.dtype)\n    return delta, delta2\n\ndef stack_features(log_mel, delta, delta2):\n    \"\"\"Stack log-mel, delta, and delta-delta into a 3-channel tensor.\"\"\"\n    return torch.stack([log_mel, delta, delta2], dim=0)\n\n# --- Label Encoding ---\n\nall_labels = sorted(set(l for labels in train_curated_csv[\"labels\"] for l in labels.split(',')))\nlabel_to_idx = {l: i for i, l in enumerate(all_labels)}\n\ndef labels_to_onehot(labels):\n    \"\"\"Convert comma-separated label string to one-hot numpy array.\"\"\"\n    arr = np.zeros(len(all_labels), dtype=np.float32)\n    for l in labels.split(','):\n        if l in label_to_idx:\n            arr[label_to_idx[l]] = 1.0\n    return arr\n\n# --- Feature Generation Pipeline ---\n\ndef generate_features(zip_path, file_list, csv_df, num_samples=20, crop_frames=128):\n    \"\"\"Generate augmented features for a sample of audio files.\"\"\"\n    features = []\n    print(f\"Generating features from {zip_path} ...\")\n    with zipfile.ZipFile(zip_path, 'r') as z:\n        sample_files = file_list if len(file_list) <= num_samples else random.sample(file_list, num_samples)\n        for fname in sample_files:\n            # Load and preprocess audio\n            with z.open(fname) as f:\n                waveform, orig_sr = torchaudio.load(f)\n            if waveform.shape > 1:\n                waveform = torch.mean(waveform, dim=0, keepdim=True)\n            if orig_sr != TARGET_SAMPLE_RATE:\n                resampler = T.Resample(orig_sr, TARGET_SAMPLE_RATE)\n                waveform = resampler(waveform)\n            num_samples_audio = waveform.shape[1]\n            if num_samples_audio > TARGET_NUM_SAMPLES:\n                start = (num_samples_audio - TARGET_NUM_SAMPLES) // 2\n                waveform = waveform[:, start:start + TARGET_NUM_SAMPLES]\n            elif num_samples_audio < TARGET_NUM_SAMPLES:\n                pad_amt = TARGET_NUM_SAMPLES - num_samples_audio\n                waveform = torch.nn.functional.pad(waveform, (0, pad_amt))\n            waveform = waveform / waveform.abs().max() if waveform.abs().max() > 0 else waveform\n\n            # Log-mel spectrogram\n            mel_spec = mel_transform(waveform)\n            log_mel = amplitude_to_db(mel_spec).squeeze(0)\n\n            # Augmentations\n            aug_log_mel = spec_augment(log_mel)\n            aug_log_mel = random_crop(aug_log_mel, crop_frames)\n\n            # Delta features\n            delta, delta2 = compute_deltas(aug_log_mel)\n\n            # Stack features\n            stacked = stack_features(aug_log_mel, delta, delta2)\n\n            # Label encoding\n            base_fname = os.path.basename(fname)\n            label_row = csv_df[csv_df[\"fname\"] == base_fname]\n            label_str = label_row[\"labels\"].values if not label_row.empty else \"\"\n            label_onehot = labels_to_onehot(label_str)\n\n            features.append({\n                \"fname\": fname,\n                \"features\": stacked,\n                \"label\": label_onehot\n            })\n    print(f\"  Generated features for {len(features)} files.\")\n    return features\n\n# --- Mixup Augmentation for Feature Batches ---\n\ndef apply_mixup_batch(features, alpha=0.4):\n    \"\"\"Apply Mixup to a batch of features and labels.\"\"\"\n    mixed_features = []\n    indices = list(range(len(features)))\n    random.shuffle(indices)\n    for i in range(0, len(indices) - 1, 2):\n        idx1, idx2 = indices[i], indices[i + 1]\n        spec1, label1 = features[idx1][\"features\"], features[idx1][\"label\"]\n        spec2, label2 = features[idx2][\"features\"], features[idx2][\"label\"]\n        mixed_spec, mixed_label = mixup(spec1, spec2, label1, label2, alpha=alpha)\n        mixed_features.append({\n            \"features\": mixed_spec,\n            \"label\": mixed_label\n        })\n    print(f\"  Mixup applied to {len(mixed_features)} pairs.\")\n    return mixed_features\n\n# --- Main Feature Engineering Execution ---\n\ncurated_zip = os.path.join(DATA_PATH, \"train_curated.zip\")\nnoisy_zip = os.path.join(DATA_PATH, \"train_noisy.zip\")\ncurated_files = audio_files.get(\"train_curated.zip\", [])\nnoisy_files = audio_files.get(\"train_noisy.zip\", [])\n\n# Generate features for a small batch (can scale up for full dataset)\ncurated_features = generate_features(curated_zip, curated_files, train_curated_csv, num_samples=20, crop_frames=128)\nnoisy_features = generate_features(noisy_zip, noisy_files, train_noisy_csv, num_samples=20, crop_frames=128)\n\n# Apply Mixup augmentation\ncurated_mixup = apply_mixup_batch(curated_features, alpha=0.4)\nnoisy_mixup = apply_mixup_batch(noisy_features, alpha=0.4)\n\n# Stack all features into tensors for model input\ndef stack_batch(features):\n    X = torch.stack([item[\"features\"] for item in features])\n    y = torch.stack([torch.tensor(item[\"label\"], dtype=torch.float32) for item in features])\n    return X, y\n\nX_curated, y_curated = stack_batch(curated_features)\nX_curated_mixup, y_curated_mixup = stack_batch(curated_mixup)\nX_noisy, y_noisy = stack_batch(noisy_features)\nX_noisy_mixup, y_noisy_mixup = stack_batch(noisy_mixup)\n\nprint(\"Feature engineering complete.\")\nprint(f\"Curated batch shape: {X_curated.shape}, labels shape: {y_curated.shape}\")\nprint(f\"Curated Mixup batch shape: {X_curated_mixup.shape}, labels shape: {y_curated_mixup.shape}\")\nprint(f\"Noisy batch shape: {X_noisy.shape}, labels shape: {y_noisy.shape}\")\nprint(f\"Noisy Mixup batch shape: {X_noisy_mixup.shape}, labels shape: {y_noisy_mixup.shape}\")\n\n# Visualize example augmented spectrograms\ndef plot_augmented_spec(spec, title=\"Augmented Spectrogram\"):\n    plt.figure(figsize=(10, 4))\n    plt.imshow(spec.numpy(), aspect='auto', origin='lower', cmap='magma')\n    plt.colorbar(format=\"%+2.0f dB\")\n    plt.title(title)\n    plt.xlabel(\"Time frames\")\n    plt.ylabel(\"Mel bins\")\n    plt.tight_layout()\n    plt.show()\n\nprint(\"Displaying example augmented spectrograms:\")\nif curated_features:\n    plot_augmented_spec(curated_features[\"features\"], title=\"Curated: Log-Mel (Augmented)\")\n    plot_augmented_spec(curated_features[\"features\"][1], title=\"Curated: Delta\")\n    plot_augmented_spec(curated_features[\"features\"][2], title=\"Curated: Delta-Delta\")\nif curated_mixup:\n    plot_augmented_spec(curated_mixup[\"features\"], title=\"Curated Mixup: Log-Mel (Augmented)\")\n\nprint(\"All features are ready for model input.\")","outputs":[],"cell_number":5,"version":1,"status":"generated","created_at":"2025-11-16T18:35:31.04213+00:00","metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Model Training\n\nTrain a multi-label CNN model with attention and skip connections using curated and noisy data.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_6_START===\")\n\n# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_6_START===\")\n\n# Model Training: CNN with Attention and Skip Connections\n\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import StratifiedKFold\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Use variables from prior cells\n# DATA_PATH, train_curated_csv, train_noisy_csv, all_labels, label_to_idx, labels_to_onehot\n\n# --- Device Setup ---\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n# --- CNN Architecture with Attention and Skip Connections ---\n\nclass AttentionBlock(nn.Module):\n    \"\"\"Channel attention mechanism.\"\"\"\n    def __init__(self, channels, reduction=16):\n        super(AttentionBlock, self).__init__()\n        self.avg_pool = nn.AdaptiveAvgPool2d(1)\n        self.max_pool = nn.AdaptiveMaxPool2d(1)\n        self.fc = nn.Sequential(\n            nn.Linear(channels, channels // reduction, bias=False),\n            nn.ReLU(inplace=True),\n            nn.Linear(channels // reduction, channels, bias=False)\n        )\n        self.sigmoid = nn.Sigmoid()\n    \n    def forward(self, x):\n        avg_out = self.fc(self.avg_pool(x).view(x.size(0), -1))\n        max_out = self.fc(self.max_pool(x).view(x.size(0), -1))\n        out = avg_out + max_out\n        return self.sigmoid(out).view(x.size(0), -1, 1, 1) * x\n\nclass ConvBlock(nn.Module):\n    \"\"\"Convolutional block with batch norm and activation.\"\"\"\n    def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1):\n        super(ConvBlock, self).__init__()\n        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=False)\n        self.bn = nn.BatchNorm2d(out_channels)\n        self.relu = nn.ReLU(inplace=True)\n    \n    def forward(self, x):\n        return self.relu(self.bn(self.conv(x)))\n\nclass AudioTaggerCNN(nn.Module):\n    \"\"\"CNN with attention, skip connections, and auxiliary classifiers.\"\"\"\n    def __init__(self, num_classes=80, num_channels=3):\n        super(AudioTaggerCNN, self).__init__()\n        self.num_classes = num_classes\n        \n        # Initial conv block\n        self.conv1 = ConvBlock(num_channels, 64, kernel_size=3, stride=1, padding=1)\n        self.pool1 = nn.MaxPool2d(2, 2)\n        \n        # Block 1\n        self.conv2a = ConvBlock(64, 128, kernel_size=3, stride=1, padding=1)\n        self.conv2b = ConvBlock(128, 128, kernel_size=3, stride=1, padding=1)\n        self.attention2 = AttentionBlock(128)\n        self.pool2 = nn.MaxPool2d(2, 2)\n        \n        # Block 2\n        self.conv3a = ConvBlock(128, 256, kernel_size=3, stride=1, padding=1)\n        self.conv3b = ConvBlock(256, 256, kernel_size=3, stride=1, padding=1)\n        self.attention3 = AttentionBlock(256)\n        self.pool3 = nn.MaxPool2d(2, 2)\n        \n        # Block 3\n        self.conv4a = ConvBlock(256, 512, kernel_size=3, stride=1, padding=1)\n        self.conv4b = ConvBlock(512, 512, kernel_size=3, stride=1, padding=1)\n        self.attention4 = AttentionBlock(512)\n        self.pool4 = nn.MaxPool2d(2, 2)\n        \n        # Global average pooling\n        self.global_pool = nn.AdaptiveAvgPool2d(1)\n        \n        # Dropout\n        self.dropout = nn.Dropout(0.5)\n        \n        # Classification head\n        self.fc1 = nn.Linear(512, 256)\n        self.fc1_bn = nn.BatchNorm1d(256)\n        self.fc1_relu = nn.ReLU(inplace=True)\n        self.fc2 = nn.Linear(256, num_classes)\n        \n        # Auxiliary classifiers for intermediate layers\n        self.aux_classifier2 = nn.Linear(128, num_classes)\n        self.aux_classifier3 = nn.Linear(256, num_classes)\n        self.aux_classifier4 = nn.Linear(512, num_classes)\n    \n    def forward(self, x, return_aux=False):\n        # Initial conv\n        x = self.conv1(x)\n        x = self.pool1(x)\n        \n        # Block 1\n        x = self.conv2a(x)\n        x = self.conv2b(x)\n        x = self.attention2(x)\n        x = self.pool2(x)\n        aux2 = self.global_pool(x).view(x.size(0), -1)\n        \n        # Block 2\n        x = self.conv3a(x)\n        x = self.conv3b(x)\n        x = self.attention3(x)\n        x = self.pool3(x)\n        aux3 = self.global_pool(x).view(x.size(0), -1)\n        \n        # Block 3\n        x = self.conv4a(x)\n        x = self.conv4b(x)\n        x = self.attention4(x)\n        x = self.pool4(x)\n        aux4 = self.global_pool(x).view(x.size(0), -1)\n        \n        # Global pooling and classification\n        x = self.global_pool(x)\n        x = x.view(x.size(0), -1)\n        x = self.dropout(x)\n        x = self.fc1(x)\n        x = self.fc1_bn(x)\n        x = self.fc1_relu(x)\n        x = self.dropout(x)\n        logits = self.fc2(x)\n        \n        if return_aux:\n            aux2_logits = self.aux_classifier2(aux2)\n            aux3_logits = self.aux_classifier3(aux3)\n            aux4_logits = self.aux_classifier4(aux4)\n            return logits, aux2_logits, aux3_logits, aux4_logits\n        \n        return logits\n\n# --- Loss Functions ---\n\nclass BCEWithLogitsLoss(nn.Module):\n    \"\"\"Binary Cross-Entropy loss for multi-label classification.\"\"\"\n    def __init__(self):\n        super(BCEWithLogitsLoss, self).__init__()\n        self.loss_fn = nn.BCEWithLogitsLoss()\n    \n    def forward(self, logits, targets):\n        return self.loss_fn(logits, targets)\n\nclass SoftLoss(nn.Module):\n    \"\"\"Soft loss for noisy labels (label smoothing variant).\"\"\"\n    def __init__(self, beta=0.7):\n        super(SoftLoss, self).__init__()\n        self.beta = beta\n        self.bce = nn.BCEWithLogitsLoss()\n    \n    def forward(self, logits, targets):\n        # Apply label smoothing: targets = targets * beta + (1 - beta) * 0.5\n        smoothed_targets = targets * self.beta + (1 - self.beta) * 0.5\n        return self.bce(logits, smoothed_targets)\n\n# --- Custom Dataset ---\n\nclass AudioDataset(Dataset):\n    \"\"\"Dataset for audio features and labels.\"\"\"\n    def __init__(self, features, labels, augment=False):\n        self.features = features\n        self.labels = labels\n        self.augment = augment\n    \n    def __len__(self):\n        return len(self.features)\n    \n    def __getitem__(self, idx):\n        feature = self.features[idx].clone()\n        label = self.labels[idx].clone()\n        \n        if self.augment:\n            # Random time masking\n            if np.random.rand() > 0.5:\n                t_mask = np.random.randint(5, 20)\n                t_start = np.random.randint(0, max(1, feature.shape[-1] - t_mask))\n                feature[:, :, t_start:t_start + t_mask] = 0\n            \n            # Random frequency masking\n            if np.random.rand() > 0.5:\n                f_mask = np.random.randint(5, 15)\n                f_start = np.random.randint(0, max(1, feature.shape[1] - f_mask))\n                feature[:, f_start:f_start + f_mask, :] = 0\n        \n        return feature, label\n\n# --- Training Function ---\n\ndef train_epoch(model, train_loader, optimizer, criterion_curated, criterion_noisy, device, epoch):\n    \"\"\"Train for one epoch with mixed curated and noisy batches.\"\"\"\n    model.train()\n    total_loss = 0.0\n    num_batches = 0\n    \n    for batch_idx, (features, labels, data_type) in enumerate(train_loader):\n        features = features.to(device)\n        labels = labels.to(device)\n        \n        optimizer.zero_grad()\n        \n        # Forward pass with auxiliary outputs\n        logits, aux2, aux3, aux4 = model(features, return_aux=True)\n        \n        # Select loss based on data type\n        if data_type[0] == 'curated':\n            loss = criterion_curated(logits, labels)\n            aux_loss = (criterion_curated(aux2, labels) + \n                       criterion_curated(aux3, labels) + \n                       criterion_curated(aux4, labels)) / 3.0\n        else:\n            loss = criterion_noisy(logits, labels)\n            aux_loss = (criterion_noisy(aux2, labels) + \n                       criterion_noisy(aux3, labels) + \n                       criterion_noisy(aux4, labels)) / 3.0\n        \n        total_loss_batch = loss + 0.3 * aux_loss\n        \n        total_loss_batch.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        optimizer.step()\n        \n        total_loss += total_loss_batch.item()\n        num_batches += 1\n        \n        if (batch_idx + 1) % 10 == 0:\n            print(f\"  Epoch {epoch}, Batch {batch_idx + 1}/{len(train_loader)}, Loss: {total_loss / num_batches:.4f}\")\n    \n    avg_loss = total_loss / num_batches\n    return avg_loss\n\ndef validate(model, val_loader, criterion, device):\n    \"\"\"Validate model on validation set.\"\"\"\n    model.eval()\n    total_loss = 0.0\n    num_batches = 0\n    \n    with torch.no_grad():\n        for features, labels, _ in val_loader:\n            features = features.to(device)\n            labels = labels.to(device)\n            \n            logits = model(features, return_aux=False)\n            loss = criterion(logits, labels)\n            \n            total_loss += loss.item()\n            num_batches += 1\n    \n    avg_loss = total_loss / num_batches\n    return avg_loss\n\n# --- Stratified K-Fold Cross-Validation ---\n\ndef prepare_kfold_data(X_curated, y_curated, X_noisy, y_noisy, n_splits=5):\n    \"\"\"Prepare data for stratified K-fold cross-validation.\"\"\"\n    # Combine curated and noisy data\n    X_combined = torch.cat([X_curated, X_noisy], dim=0)\n    y_combined = torch.cat([y_curated, y_noisy], dim=0)\n    data_type = ['curated'] * len(X_curated) + ['noisy'] * len(X_noisy)\n    \n    # Create stratified folds based on primary label\n    primary_labels = torch.argmax(y_combined, dim=1).numpy()\n    skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n    \n    folds = []\n    for train_idx, val_idx in skf.split(X_combined, primary_labels):\n        folds.append({\n            'train_idx': train_idx,\n            'val_idx': val_idx\n        })\n    \n    return X_combined, y_combined, data_type, folds\n\n# --- Main Training Loop ---\n\nprint(\"\\n=== Model Training with Stratified K-Fold Cross-Validation ===\\n\")\n\n# Prepare data (using features from prior cell)\nprint(\"Preparing data for training...\")\nX_combined = torch.cat([X_curated, X_noisy], dim=0)\ny_combined = torch.cat([y_curated, y_noisy], dim=0)\ndata_type_combined = ['curated'] * len(X_curated) + ['noisy'] * len(X_noisy)\n\n# Stratified K-fold\nprimary_labels = torch.argmax(y_combined, dim=1).numpy()\nn_splits = 3\nskf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n\nfold_results = []\nbest_model_state = None\nbest_val_loss = float('inf')\n\nfor fold_idx, (train_idx, val_idx) in enumerate(skf.split(X_combined, primary_labels)):\n    print(f\"\\n--- Fold {fold_idx + 1}/{n_splits} ---\")\n    \n    # Split data\n    X_train_fold = X_combined[train_idx]\n    y_train_fold = y_combined[train_idx]\n    data_type_train = [data_type_combined[i] for i in train_idx]\n    \n    X_val_fold = X_combined[val_idx]\n    y_val_fold = y_combined[val_idx]\n    \n    # Create datasets\n    train_dataset = AudioDataset(X_train_fold, y_train_fold, augment=True)\n    val_dataset = AudioDataset(X_val_fold, y_val_fold, augment=False)\n    \n    # Create dataloaders with custom collate to include data type\n    def collate_fn(batch):\n        features, labels = zip(*batch)\n        features = torch.stack(features)\n        labels = torch.stack(labels)\n        return features, labels, ['mixed'] * len(batch)\n    \n    train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True, collate_fn=collate_fn)\n    val_loader = DataLoader(val_dataset, batch_size=16, shuffle=False, collate_fn=collate_fn)\n    \n    # Initialize model\n    model = AudioTaggerCNN(num_classes=len(all_labels), num_channels=3).to(device)\n    \n    # Loss functions\n    criterion_curated = BCEWithLogitsLoss()\n    criterion_noisy = SoftLoss(beta=0.7)\n    \n    # Optimizer\n    optimizer = optim.Adam(model.parameters(), lr=0.0009, weight_decay=1e-5)\n    scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n        optimizer, mode='min', factor=0.6, patience=6, verbose=True\n    )\n    \n    # Training loop\n    num_epochs = 15\n    train_losses = []\n    val_losses = []\n    \n    for epoch in range(num_epochs):\n        print(f\"\\nEpoch {epoch + 1}/{num_epochs}\")\n        \n        train_loss = train_epoch(model, train_loader, optimizer, criterion_curated, criterion_noisy, device, epoch + 1)\n        val_loss = validate(model, val_loader, criterion_curated, device)\n        \n        train_losses.append(train_loss)\n        val_losses.append(val_loss)\n        \n        print(f\"Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}\")\n        \n        scheduler.step(val_loss)\n        \n        # Save best model\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            best_model_state = model.state_dict().copy()\n            print(f\"Best model updated (Val Loss: {val_loss:.4f})\")\n    \n    fold_results.append({\n        'fold': fold_idx + 1,\n        'train_losses': train_losses,\n        'val_losses': val_losses,\n        'best_val_loss': min(val_losses)\n    })\n    \n    print(f\"Fold {fold_idx + 1} complete. Best Val Loss: {min(val_losses):.4f}\")\n\n# --- Plot Training Results ---\n\nprint(\"\\n=== Training Complete ===\\n\")\n\nplt.figure(figsize=(12, 5))\nfor result in fold_results:\n    plt.plot(result['train_losses'], label=f\"Fold {result['fold']} Train\", alpha=0.7)\n    plt.plot(result['val_losses'], label=f\"Fold {result['fold']} Val\", alpha=0.7, linestyle='--')\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training and Validation Loss Across Folds\")\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.show()\n\n# Summary statistics\nprint(\"\\nFold Summary:\")\nfor result in fold_results:\n    print(f\"Fold {result['fold']}: Best Val Loss = {result['best_val_loss']:.4f}\")\n\navg_best_loss = np.mean([r['best_val_loss'] for r in fold_results])\nprint(f\"\\nAverage Best Val Loss: {avg_best_loss:.4f}\")\n\n# Save best model\nif best_model_state is not None:\n    torch.save(best_model_state, '/kaggle/working/best_model.pth')\n    print(\"\\nBest model saved to /kaggle/working/best_model.pth\")\n\nprint(\"\\nModel training complete.\")","outputs":[],"cell_number":6,"version":1,"status":"generated","created_at":"2025-11-16T18:35:58.031561+00:00","metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Evaluation\n\nEvaluate model performance using label-weighted label-ranking average precision (lwlrap) and other metrics.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_7_START===\")\n\nprint(\"===ALEXANDRIA_CELL_7_START===\")\n\n# Evaluation: Compute lwlrap and other metrics, visualize confusion matrix and PR curves\n\nimport numpy as np\nimport pandas as pd\nimport torch\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import multilabel_confusion_matrix, precision_recall_curve, average_precision_score\n\n# lwlrap implementation (label-weighted label-ranking average precision)\ndef calculate_per_class_lwlrap(truth, scores):\n    \"\"\"Calculate label-weighted label-ranking average precision (lwlrap) per class.\"\"\"\n    num_samples, num_classes = scores.shape\n    precisions_for_samples_by_classes = np.zeros((num_samples, num_classes))\n    for k in range(num_classes):\n        y_true = truth[:, k]\n        y_score = scores[:, k]\n        if not np.any(y_true):\n            continue\n        # Sort by score descending\n        order = np.argsort(-y_score)\n        y_true = y_true[order]\n        y_score = y_score[order]\n        pos_count = 0\n        total_count = 0\n        precisions = []\n        for i in range(len(y_true)):\n            if y_true[i]:\n                pos_count += 1\n                precisions.append(pos_count / (i + 1))\n        if precisions:\n            precisions_for_samples_by_classes[:, k] = np.mean(precisions)\n    # Per-class lwlrap\n    labels_per_class = np.sum(truth, axis=0)\n    weight_per_class = labels_per_class / np.sum(labels_per_class)\n    per_class_lwlrap = np.sum(precisions_for_samples_by_classes, axis=0) / np.maximum(labels_per_class, 1)\n    overall_lwlrap = (per_class_lwlrap * weight_per_class).sum()\n    return overall_lwlrap, per_class_lwlrap\n\ndef compute_lwlrap(truth, scores):\n    \"\"\"Wrapper for lwlrap calculation.\"\"\"\n    # truth: (num_samples, num_classes), binary\n    # scores: (num_samples, num_classes), float\n    num_classes = scores.shape[1]\n    precisions = []\n    weights = []\n    for k in range(num_classes):\n        y_true = truth[:, k]\n        y_score = scores[:, k]\n        if not np.any(y_true):\n            continue\n        order = np.argsort(-y_score)\n        y_true = y_true[order]\n        y_score = y_score[order]\n        pos_count = 0\n        precisions_k = []\n        for i in range(len(y_true)):\n            if y_true[i]:\n                pos_count += 1\n                precisions_k.append(pos_count / (i + 1))\n        if precisions_k:\n            precisions.append(np.mean(precisions_k))\n            weights.append(np.sum(y_true))\n        else:\n            precisions.append(0.0)\n            weights.append(0.0)\n    lwlrap = np.average(precisions, weights=weights)\n    return lwlrap\n\n# Prepare validation predictions and ground truth\nprint(\"Preparing validation predictions and ground truth for evaluation...\")\n\n# Use best model from previous cell\nmodel = AudioTaggerCNN(num_classes=len(all_labels), num_channels=3)\nmodel.load_state_dict(torch.load('/kaggle/working/best_model.pth', map_location='cpu'))\nmodel.eval()\n\n# Use validation set from last fold (as example)\nval_idx = fold_results[-1]['fold'] - 1\nskf = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)\nprimary_labels = torch.argmax(y_combined, dim=1).numpy()\nsplits = list(skf.split(X_combined, primary_labels))\n_, val_indices = splits[val_idx]\nX_val = X_combined[val_indices]\ny_val = y_combined[val_indices]\n\n# Predict on validation set\nprint(\"Predicting on validation set...\")\nwith torch.no_grad():\n    y_val_pred = []\n    for i in range(0, len(X_val), 16):\n        batch = X_val[i:i+16]\n        logits = model(batch)\n        probs = torch.sigmoid(logits).cpu().numpy()\n        y_val_pred.append(probs)\n    y_val_pred = np.vstack(y_val_pred)\n    y_val_true = y_val.cpu().numpy()\n\n# Compute lwlrap\nprint(\"Calculating lwlrap on validation set...\")\nval_lwlrap = compute_lwlrap(y_val_true, y_val_pred)\nprint(f\"Validation lwlrap: {val_lwlrap:.4f}\")\n\n# Compute average precision (macro/micro)\nmacro_ap = average_precision_score(y_val_true, y_val_pred, average='macro')\nmicro_ap = average_precision_score(y_val_true, y_val_pred, average='micro')\nprint(f\"Macro Average Precision: {macro_ap:.4f}\")\nprint(f\"Micro Average Precision: {micro_ap:.4f}\")\n\n# Confusion matrix visualization (for top N labels)\nN = 10\nlabel_counts = y_val_true.sum(axis=0)\ntop_labels_idx = np.argsort(-label_counts)[:N]\ntop_labels = [all_labels[i] for i in top_labels_idx]\n\n# Binarize predictions at threshold 0.5\ny_val_pred_bin = (y_val_pred[:, top_labels_idx] > 0.5).astype(int)\ny_val_true_bin = y_val_true[:, top_labels_idx]\n\ncm = multilabel_confusion_matrix(y_val_true_bin, y_val_pred_bin)\nfig, axes = plt.subplots(2, N//2, figsize=(18, 8))\naxes = axes.flatten()\nfor i, ax in enumerate(axes):\n    sns.heatmap(cm[i], annot=True, fmt='d', cmap='Blues', ax=ax, cbar=False)\n    ax.set_title(f\"Label: {top_labels[i]}\")\n    ax.set_xlabel(\"Predicted\")\n    ax.set_ylabel(\"True\")\nplt.tight_layout()\nplt.show()\n\n# Precision-Recall curves for top N labels\nplt.figure(figsize=(14, 8))\nfor i, idx in enumerate(top_labels_idx):\n    precision, recall, _ = precision_recall_curve(y_val_true[:, idx], y_val_pred[:, idx])\n    ap = average_precision_score(y_val_true[:, idx], y_val_pred[:, idx])\n    plt.plot(recall, precision, label=f\"{all_labels[idx]} (AP={ap:.2f})\")\nplt.xlabel(\"Recall\")\nplt.ylabel(\"Precision\")\nplt.title(\"Precision-Recall Curves (Top Labels)\")\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.show()\n\n# Evaluate on test set (if test features available)\n# Here, we demonstrate how to generate predictions for the test set for submission\n\nprint(\"Preparing test set predictions for submission...\")\n\nimport zipfile\n\ntest_zip_path = os.path.join(DATA_PATH, \"test.zip\")\nsample_submission = pd.read_csv(os.path.join(DATA_PATH, \"sample_submission.csv\"))\ntest_files = []\nwith zipfile.ZipFile(test_zip_path, 'r') as z:\n    test_files = [f for f in z.namelist() if f.lower().endswith('.wav')]\n\n# Preprocess and predict for each test file\ntest_preds = []\nmodel.eval()\nfor i, fname in enumerate(test_files):\n    with zipfile.ZipFile(test_zip_path, 'r') as z:\n        with z.open(fname) as f:\n            waveform, orig_sr = torchaudio.load(f)\n    if waveform.shape > 1:\n        waveform = torch.mean(waveform, dim=0, keepdim=True)\n    if orig_sr != TARGET_SAMPLE_RATE:\n        resampler = T.Resample(orig_sr, TARGET_SAMPLE_RATE)\n        waveform = resampler(waveform)\n    num_samples = waveform.shape[1]\n    if num_samples > TARGET_NUM_SAMPLES:\n        start = (num_samples - TARGET_NUM_SAMPLES) // 2\n        waveform = waveform[:, start:start+TARGET_NUM_SAMPLES]\n    elif num_samples < TARGET_NUM_SAMPLES:\n        pad_amt = TARGET_NUM_SAMPLES - num_samples\n        waveform = torch.nn.functional.pad(waveform, (0, pad_amt))\n    waveform = waveform / waveform.abs().max() if waveform.abs().max() > 0 else waveform\n    mel_spec = mel_transform(waveform)\n    log_mel = amplitude_to_db(mel_spec).squeeze(0)\n    delta, delta2 = librosa.feature.delta(log_mel.cpu().numpy()), librosa.feature.delta(log_mel.cpu().numpy(), order=2)\n    stacked = torch.stack([\n        log_mel,\n        torch.tensor(delta, dtype=log_mel.dtype),\n        torch.tensor(delta2, dtype=log_mel.dtype)\n    ], dim=0).unsqueeze(0)\n    with torch.no_grad():\n        logits = model(stacked)\n        probs = torch.sigmoid(logits).cpu().numpy()\n    test_preds.append(probs)\n    if (i+1) % 100 == 0 or (i+1) == len(test_files):\n        print(f\"  Processed {i+1}/{len(test_files)} test files\")\n\ntest_preds = np.vstack(test_preds)\n\n# Prepare submission\nsubmission = pd.DataFrame(test_preds, columns=all_labels)\nsubmission.insert(0, \"fname\", [os.path.basename(f) for f in test_files])\nsubmission = submission.set_index(\"fname\").reindex(sample_submission[\"fname\"]).reset_index()\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(\"Submission file saved to /kaggle/working/submission.csv\")\n\nprint(\"Evaluation complete.\")","outputs":[],"cell_number":7,"version":1,"status":"generated","created_at":"2025-11-16T18:36:32.66137+00:00","metadata":{},"execution_count":null},{"cell_type":"markdown","source":"## Submission/Results\n\nFormat predictions for Kaggle submission and display key results.","metadata":{}},{"cell_type":"code","source":"# ⚠️ ALEXANDRIA MARKER - DO NOT DELETE (used for syncing outputs from Kaggle)\nprint(\"===ALEXANDRIA_CELL_8_START===\")\n\nprint(\"===ALEXANDRIA_CELL_8_START===\")\n\n# Submission/Results: Format predictions for Kaggle submission and display key results\n\nimport os\nimport pandas as pd\n\n# Paths\nDATA_PATH = \"/kaggle/input/freesound-audio-tagging-2019\"\nSAMPLE_SUB_PATH = os.path.join(DATA_PATH, \"sample_submission.csv\")\nSUBMISSION_PATH = \"/kaggle/working/submission.csv\"\n\n# Load sample submission to get correct format and order\nsample_submission = pd.read_csv(SAMPLE_SUB_PATH)\nprint(f\"Loaded sample_submission.csv: shape={sample_submission.shape}\")\n\n# Check that test predictions and test file list exist\nif 'test_preds' not in locals() or 'test_files' not in locals():\n    raise RuntimeError(\"Test predictions (test_preds) or test file list (test_files) not found. Run the test prediction cell first.\")\n\n# Prepare submission DataFrame\nsubmission = pd.DataFrame(test_preds, columns=all_labels)\nsubmission.insert(0, \"fname\", [os.path.basename(f) for f in test_files])\n\n# Reindex to match sample_submission order (handles missing/test files order)\nsubmission = submission.set_index(\"fname\").reindex(sample_submission[\"fname\"]).reset_index()\nassert submission.shape == sample_submission.shape, \"Submission shape does not match sample_submission!\"\n\n# Save to CSV\nsubmission.to_csv(SUBMISSION_PATH, index=False)\nprint(f\"Submission file saved: {SUBMISSION_PATH}\")\nprint(submission.head())\n\n# Display key results and leaderboard expectations\nprint(\"\\n=== Final Model Performance Summary ===\")\nif 'val_lwlrap' in locals():\n    print(f\"Validation lwlrap: {val_lwlrap:.4f}\")\nif 'macro_ap' in locals() and 'micro_ap' in locals():\n    print(f\"Macro Average Precision: {macro_ap:.4f}\")\n    print(f\"Micro Average Precision: {micro_ap:.4f}\")\nif 'avg_best_loss' in locals():\n    print(f\"Average Best Validation Loss (across folds): {avg_best_loss:.4f}\")\n\nprint(\"\\nLeaderboard expectations:\")\nprint(\" - Submission file matches required format and order.\")\nprint(\" - Validation lwlrap is a good proxy for public leaderboard score, but actual LB may differ due to hidden test set distribution.\")\nprint(\" - For best results, ensemble multiple folds/models and tune thresholds if time permits.\")\n\n# Confirm file exists for Kaggle submission\nif os.path.exists(SUBMISSION_PATH):\n    print(f\"\\nReady for submission: {SUBMISSION_PATH}\")\nelse:\n    print(\"\\nSubmission file not found. Please check for errors above.\")","outputs":[],"cell_number":8,"version":1,"status":"generated","created_at":"2025-11-16T18:36:42.044346+00:00","metadata":{},"execution_count":null}],"nbformat":4,"nbformat_minor":4}