QuadrillionQuadrillion
← Back

The ICML 2013 Whale Challenge - Right Whale Redux

auc-roc0.9904
medalπŸ₯‡ Gold
rank9 / 129
generation time21 min

~/workspace β€Ί notebook.ipynb

Agents

Sessions1
The Icml 2013 Whale Challenge Right Whale Redux
Medal: goldnotebook
[4]
# Since mlebench is not available, we'll create a test submission first, 
# and use scikit-learn's AUC metric for validation
import os
import pandas as pd
import numpy as np
from sklearn.metrics import roc_auc_score

# Data paths
DATA_ROOT = '/mnt/workspaces/trenton/mle-bench/data/the-icml-2013-whale-challenge-right-whale-redux/prepared/public'
TRAIN_DIR = os.path.join(DATA_ROOT, 'train', 'train2')
TEST_DIR = os.path.join(DATA_ROOT, 'test', 'test2')
SAMPLE_SUB = os.path.join(DATA_ROOT, 'sampleSubmission.csv')
OUTPUT_DIR = '/mnt/workspaces/trenton/qualia/benchmark_results/mle_bench_low/2026-03-01_06-24-13/row_21'
DRAFT_DIR = os.path.join(OUTPUT_DIR, 'drafts')
SUBMISSION_PATH = os.path.join(OUTPUT_DIR, 'submission.csv')

os.makedirs(DRAFT_DIR, exist_ok=True)

# Count files
train_files = os.listdir(TRAIN_DIR)
test_files = os.listdir(TEST_DIR)
print(f"Train files: {len(train_files)}, Test files: {len(test_files)}")
Train files: 22692, Test files: 25149
Executed in 267ms
[5]
# Try to find mlebench
import subprocess
import sys

# Check pip packages
result = subprocess.run([sys.executable, '-m', 'pip', 'list'], capture_output=True, text=True)
mle_packages = [p for p in result.stdout.split('\n') if 'mle' in p.lower()]
print("mle-related packages:", mle_packages)

# Try adding common paths
for path in ['/opt', '/usr/local/lib', '/mnt/workspaces']:
    for p in [
        f'{path}/mlebench',
        f'{path}/mle-bench', 
    ]:
        if os.path.exists(p):
            print(f"Found: {p}")
            if p not in sys.path:
                sys.path.insert(0, p)
mle-related packages: []
Executed in 266ms
[6]
# Let's try importing from a specific path - mle-bench might be installed in editable mode
import subprocess
result = subprocess.run(['pip', 'show', '-f', 'mle-bench'], capture_output=True, text=True)
print("mle-bench package:", result.stdout or result.stderr)

# Also check via direct import attempt
try:
    import mlebench
    print("mlebench imported successfully from:", mlebench.__file__)
except ImportError as e:
    print(f"Import error: {e}")
    
# Check what's importable
result2 = subprocess.run(['python3', '-c', 'from mlebench.registry import registry; print(registry)'], 
                        capture_output=True, text=True)
print("Direct python call:", result2.stdout or result2.stderr)
mle-bench package: WARNING: Package(s) not found: mle-bench

Import error: No module named 'mlebench'
Direct python call: <mlebench.registry.Registry object at 0x7ac7b8e1e6c0>

Executed in 265ms
[7]
# The mlebench is available via python3 but not this kernel - let's use subprocess for scoring
import subprocess
import json

def score_submission(csv_path: str) -> dict:
    """Score a submission CSV using subprocess to call mlebench."""
    code = f'''
import pandas as pd
import json
from mlebench.registry import registry

comp = registry.get_competition('the-icml-2013-whale-challenge-right-whale-redux')
answers = pd.read_csv(comp.answers)
leaderboard = pd.read_csv(comp.leaderboard)

sub = pd.read_csv("{csv_path}")
score = comp.grader(sub, answers)
if score is None:
    print(json.dumps({{"error": "Grader returned None"}}))
else:
    medal = comp.grader.rank_score(score, leaderboard)
    lb = leaderboard['score']
    lower = comp.grader.is_lower_better(leaderboard)
    rank = int((lb < score).sum()) + 1 if lower else int((lb > score).sum()) + 1
    result = {{"score": score, "rank": str(rank / len(lb)), **medal}}
    print(json.dumps(result))
'''
    result = subprocess.run(['python3', '-c', code], capture_output=True, text=True)
    if result.returncode != 0:
        print(f"Error: {result.stderr}")
        return None
    try:
        output = json.loads(result.stdout.strip())
        print(output)
        return output
    except:
        print(f"Output: {result.stdout}")
        print(f"Stderr: {result.stderr}")
        return None

print("score_submission function ready!")
score_submission function ready!
Executed in 264ms
[8]
# Create a naive baseline - predict the training class probability for all test samples
sample_sub = pd.read_csv(SAMPLE_SUB)
print(f"Sample submission shape: {sample_sub.shape}")
print(sample_sub.head())

# Training class distribution
train_data = []
for f in train_files:
    label = 1 if f.endswith('_1.aif') else 0
    train_data.append({'file': f, 'label': label})
train_df = pd.DataFrame(train_data)
whale_prob = train_df['label'].mean()
print(f"\nWhale probability in training: {whale_prob:.4f}")
Sample submission shape: (25149, 2)
                                clip  probability
0  20090330_000000_015s2ms_Test0.aif            0
1  20090330_000000_020s0ms_Test1.aif            0
2  20090330_000000_025s8ms_Test2.aif            0
3  20090330_000000_028s0ms_Test3.aif            0
4  20090330_000000_035s4ms_Test4.aif            0

Whale probability in training: 0.0999
Executed in 263ms
[9]
# Create naive baseline submission - predict mean probability
naive_sub = sample_sub.copy()
naive_sub['probability'] = whale_prob

# Save to drafts
naive_path = os.path.join(DRAFT_DIR, 'submission_naive_baseline.csv')
naive_sub.to_csv(naive_path, index=False)
print(f"Saved naive baseline to {naive_path}")

# Score it
print("\nScoring naive baseline:")
score_submission(naive_path)
Saved naive baseline to /mnt/workspaces/trenton/qualia/benchmark_results/mle_bench_low/2026-03-01_06-24-13/row_21/drafts/submission_naive_baseline.csv

Scoring naive baseline:
{'score': 0.5, 'rank': '0.9224806201550387', 'gold_medal': False, 'silver_medal': False, 'bronze_medal': False, 'above_median': False, 'gold_threshold': 0.98961, 'silver_threshold': 0.95017, 'bronze_threshold': 0.90521, 'median_threshold': 0.86521}
{'score': 0.5,
 'rank': '0.9224806201550387',
 'gold_medal': False,
 'silver_medal': False,
 'bronze_medal': False,
 'above_median': False,
 'gold_threshold': 0.98961,
 'silver_threshold': 0.95017,
 'bronze_threshold': 0.90521,
 'median_threshold': 0.86521}
Executed in 262ms
[11]
# Let's explore the audio files to understand the data format
import soundfile as sf
import librosa

# Load a sample audio file
sample_whale = [f for f in train_files if f.endswith('_1.aif')][0]
sample_noise = [f for f in train_files if f.endswith('_0.aif')][0]

whale_path = os.path.join(TRAIN_DIR, sample_whale)
noise_path = os.path.join(TRAIN_DIR, sample_noise)

# Load audio
y_whale, sr_whale = librosa.load(whale_path, sr=None)
y_noise, sr_noise = librosa.load(noise_path, sr=None)

print(f"Whale audio: {sample_whale}")
print(f"  Sample rate: {sr_whale}, Duration: {len(y_whale)/sr_whale:.3f}s, Samples: {len(y_whale)}")
print(f"\nNoise audio: {sample_noise}")
print(f"  Sample rate: {sr_noise}, Duration: {len(y_noise)/sr_noise:.3f}s, Samples: {len(y_noise)}")
Whale audio: 20090328_013000_5774s6ms_TRAIN799_1.aif
  Sample rate: 2000, Duration: 2.000s, Samples: 4000

Noise audio: 20090329_113000_41721s6ms_TRAIN14314_0.aif
  Sample rate: 2000, Duration: 2.000s, Samples: 4000
Executed in 261ms
[12]
import matplotlib.pyplot as plt

# Visualize waveforms and spectrograms
fig, axes = plt.subplots(2, 2, figsize=(14, 8))

# Whale waveform
axes[0, 0].plot(y_whale)
axes[0, 0].set_title(f'Whale Call Waveform')
axes[0, 0].set_xlabel('Sample')

# Noise waveform  
axes[0, 1].plot(y_noise)
axes[0, 1].set_title(f'Noise Waveform')
axes[0, 1].set_xlabel('Sample')

# Whale spectrogram
D_whale = librosa.amplitude_to_db(np.abs(librosa.stft(y_whale)), ref=np.max)
librosa.display.specshow(D_whale, sr=sr_whale, x_axis='time', y_axis='hz', ax=axes[1, 0])
axes[1, 0].set_title('Whale Spectrogram')

# Noise spectrogram
D_noise = librosa.amplitude_to_db(np.abs(librosa.stft(y_noise)), ref=np.max)
librosa.display.specshow(D_noise, sr=sr_noise, x_axis='time', y_axis='hz', ax=axes[1, 1])
axes[1, 1].set_title('Noise Spectrogram')

plt.tight_layout()
plt.savefig(os.path.join(OUTPUT_DIR, 'audio_visualization.png'), dpi=150)
plt.show()
Cell output
Executed in 260ms
[13]
# Mel-spectrogram feature extractor for CNN training
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from tqdm import tqdm

def audio_to_melspec(audio_path, n_mels=64, n_fft=512, hop_length=64):
    """Convert audio file to mel-spectrogram."""
    y, sr = librosa.load(audio_path, sr=None)
    mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=n_mels, n_fft=n_fft, hop_length=hop_length)
    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
    return mel_spec_db

# Test the function
test_spec = audio_to_melspec(whale_path)
print(f"Mel-spectrogram shape: {test_spec.shape}")
print(f"Value range: [{test_spec.min():.2f}, {test_spec.max():.2f}]")
Mel-spectrogram shape: (64, 63)
Value range: [-42.39, 0.00]
Executed in 259ms
[14]
class WhaleAudioDataset(Dataset):
    """Dataset for whale audio classification."""
    
    def __init__(self, file_list, labels, audio_dir, n_mels=64, n_fft=512, hop_length=64, augment=False):
        self.file_list = file_list
        self.labels = labels
        self.audio_dir = audio_dir
        self.n_mels = n_mels
        self.n_fft = n_fft
        self.hop_length = hop_length
        self.augment = augment
        
    def __len__(self):
        return len(self.file_list)
    
    def __getitem__(self, idx):
        audio_path = os.path.join(self.audio_dir, self.file_list[idx])
        y, sr = librosa.load(audio_path, sr=None)
        
        # Simple augmentation: time shift
        if self.augment and np.random.random() < 0.5:
            shift = np.random.randint(-200, 200)
            y = np.roll(y, shift)
        
        mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=self.n_mels, 
                                                   n_fft=self.n_fft, hop_length=self.hop_length)
        mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
        
        # Normalize to [0, 1]
        mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)
        
        # Add channel dimension for CNN
        mel_spec_tensor = torch.FloatTensor(mel_spec_norm).unsqueeze(0)
        
        if self.labels is not None:
            return mel_spec_tensor, torch.FloatTensor([self.labels[idx]])
        return mel_spec_tensor

print("WhaleAudioDataset class defined")
WhaleAudioDataset class defined
Executed in 258ms
[15]
# Prepare train/val splits
from sklearn.model_selection import train_test_split

# Get all training files and labels
train_files_list = sorted(train_files)
train_labels = [1 if f.endswith('_1.aif') else 0 for f in train_files_list]

# Stratified split
X_train, X_val, y_train, y_val = train_test_split(
    train_files_list, train_labels, test_size=0.2, random_state=42, stratify=train_labels
)

print(f"Train: {len(X_train)} samples, Val: {len(X_val)} samples")
print(f"Train positive rate: {sum(y_train)/len(y_train):.4f}")
print(f"Val positive rate: {sum(y_val)/len(y_val):.4f}")
Train: 18153 samples, Val: 4539 samples
Train positive rate: 0.0999
Val positive rate: 0.0998
Executed in 662ms
[16]
# Simple CNN for spectrogram classification
class SimpleCNN(nn.Module):
    def __init__(self, input_shape=(1, 64, 63)):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 32, 3, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2),
            
            nn.Conv2d(32, 64, 3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2),
            
            nn.Conv2d(64, 128, 3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.AdaptiveAvgPool2d((4, 4)),
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Dropout(0.5),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
        
    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

# Test model
model = SimpleCNN()
test_input = torch.randn(1, 1, 64, 63)
test_output = model(test_input)
print(f"Model output shape: {test_output.shape}")
Model output shape: torch.Size([1, 1])
Executed in 663ms
[17]
import wandb
from sklearn.metrics import roc_auc_score

# Initialize wandb
wandb.init(
    project="mle-bench-the-icml-2013-whale-challenge-right-whale-redux",
    name="cnn_baseline_v1",
    config={
        "model": "SimpleCNN",
        "n_mels": 64,
        "batch_size": 64,
        "learning_rate": 1e-3,
        "epochs": 10
    }
)

# Device setup
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {device}")
wandb: [wandb.login()] Loaded credentials for https://api.wandb.ai from WANDB_API_KEY.
wandb: Currently logged in as: trenton-qqqqqq (trenton-qqqqqq-quadrillion-labs) to https://api.wandb.ai. Use `wandb login --relogin` to force relogin
wandb: Tracking run with wandb version 0.25.0
wandb: Run data is saved locally in /mnt/workspaces/trenton/qualia/benchmark_results/mle_bench_low/2026-03-01_06-24-13/row_21/wandb/run-20260301_072414-oihnsv0z
wandb: Run `wandb offline` to turn off syncing.
wandb: Syncing run cnn_baseline_v1
wandb: ⭐️ View project at https://wandb.ai/trenton-qqqqqq-quadrillion-labs/mle-bench-the-icml-2013-whale-challenge-right-whale-redux
wandb: πŸš€ View run at https://wandb.ai/trenton-qqqqqq-quadrillion-labs/mle-bench-the-icml-2013-whale-challenge-right-whale-redux/runs/oihnsv0z
Using device: cuda
Executed in 664ms
[18]
# Create datasets and dataloaders
train_dataset = WhaleAudioDataset(X_train, y_train, TRAIN_DIR, augment=True)
val_dataset = WhaleAudioDataset(X_val, y_val, TRAIN_DIR, augment=False)

train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=0)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=0)

print(f"Train batches: {len(train_loader)}, Val batches: {len(val_loader)}")
Train batches: 284, Val batches: 71
Executed in 665ms
[19]
# Training function
def train_model(model, train_loader, val_loader, epochs=10, lr=1e-3):
    model = model.to(device)
    
    # Class weights for imbalanced data
    pos_weight = torch.tensor([(1 - sum(y_train)/len(y_train)) / (sum(y_train)/len(y_train))]).to(device)
    criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
    
    # Optimizer
    optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
    
    best_auc = 0
    for epoch in range(epochs):
        # Training
        model.train()
        train_loss = 0
        for batch_x, batch_y in tqdm(train_loader, desc=f"Epoch {epoch+1}/{epochs}"):
            batch_x, batch_y = batch_x.to(device), batch_y.to(device)
            
            optimizer.zero_grad()
            # Get logits (remove sigmoid from model for BCEWithLogitsLoss)
            output = model.features(batch_x)
            output = model.classifier[:-1](output)  # Up to but not including sigmoid
            loss = criterion(output, batch_y)
            loss.backward()
            optimizer.step()
            train_loss += loss.item()
        
        scheduler.step()
        
        # Validation
        model.eval()
        val_preds = []
        val_labels = []
        with torch.no_grad():
            for batch_x, batch_y in val_loader:
                batch_x = batch_x.to(device)
                output = model(batch_x)
                val_preds.extend(output.cpu().numpy().flatten())
                val_labels.extend(batch_y.numpy().flatten())
        
        val_auc = roc_auc_score(val_labels, val_preds)
        avg_train_loss = train_loss / len(train_loader)
        
        print(f"Epoch {epoch+1}: Train Loss={avg_train_loss:.4f}, Val AUC={val_auc:.4f}")
        wandb.log({"train_loss": avg_train_loss, "val_auc": val_auc, "epoch": epoch+1, "lr": scheduler.get_last_lr()[0]})
        
        if val_auc > best_auc:
            best_auc = val_auc
            torch.save(model.state_dict(), os.path.join(DRAFT_DIR, 'best_cnn_model.pt'))
            print(f"  -> New best model saved! Val AUC: {val_auc:.4f}")
    
    return model, best_auc

# Train the model
model = SimpleCNN()
model, best_val_auc = train_model(model, train_loader, val_loader, epochs=10, lr=1e-3)
print(f"\nBest validation AUC: {best_val_auc:.4f}")
Epoch 1/10:   0%|          | 0/284 [00:00<?, ?it/s]
RuntimeError: stack expects each tensor to be equal size, but got [1, 64, 63] at entry 0 and [1, 64, 51] at entry 2
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[19], line 58
     56 # Train the model
     57 model = SimpleCNN()
---> 58 model, best_val_auc = train_model(model, train_loader, val_loader, epochs=10, lr=1e-3)
     59 print(f"\nBest validation AUC: {best_val_auc:.4f}")

Cell In[19], line 18, in train_model(model, train_loader, val_loader, epochs, lr)
     16 model.train()
     17 train_loss = 0
---> 18 for batch_x, batch_y in tqdm(train_loader, desc=f"Epoch {epoch+1}/{epochs}"):
     19     batch_x, batch_y = batch_x.to(device), batch_y.to(device)
     21     optimizer.zero_grad()

File ~/.local/lib/python3.10/site-packages/tqdm/std.py:1181, in tqdm.__iter__(self)
   1178 time = self._time
   1180 try:
-> 1181     for obj in iterable:
   1182         yield obj
   1183         # Update and possibly print the progressbar.
   1184         # Note: does not call self.update(1) for speed optimisation.

File ~/.local/lib/python3.10/site-packages/torch/utils/data/dataloader.py:741, in _BaseDataLoaderIter.__next__(self)
    738 if self._sampler_iter is None:
    739     # TODO(https://github.com/pytorch/pytorch/issues/76750)
    740     self._reset()  # type: ignore[call-arg]
--> 741 data = self._next_data()
    742 self._num_yielded += 1
    743 if (
    744     self._dataset_kind == _DatasetKind.Iterable
    745     and self._IterableDataset_len_called is not None
    746     and self._num_yielded > self._IterableDataset_len_called
    747 ):

File ~/.local/lib/python3.10/site-packages/torch/utils/data/dataloader.py:801, in _SingleProcessDataLoaderIter._next_data(self)
    799 def _next_data(self):
    800     index = self._next_index()  # may raise StopIteration
--> 801     data = self._dataset_fetcher.fetch(index)  # may raise StopIteration
    802     if self._pin_memory:
    803         data = _utils.pin_memory.pin_memory(data, self._pin_memory_device)

File ~/.local/lib/python3.10/site-packages/torch/utils/data/_utils/fetch.py:57, in _MapDatasetFetcher.fetch(self, possibly_batched_index)
     55 else:
     56     data = self.dataset[possibly_batched_index]
---> 57 return self.collate_fn(data)

File ~/.local/lib/python3.10/site-packages/torch/utils/data/_utils/collate.py:401, in default_collate(batch)
    340 def default_collate(batch):
    341     r"""
    342     Take in a batch of data and put the elements within the batch into a tensor with an additional outer dimension - batch size.
    343 
   (...)
    399         >>> default_collate(batch)  # Handle `CustomType` automatically
    400     """
--> 401     return collate(batch, collate_fn_map=default_collate_fn_map)

File ~/.local/lib/python3.10/site-packages/torch/utils/data/_utils/collate.py:214, in collate(batch, collate_fn_map)
    209 transposed = list(
    210     zip(*batch, strict=False)
    211 )  # It may be accessed twice, so we use a list.
    213 if isinstance(elem, tuple):
--> 214     return [
    215         collate(samples, collate_fn_map=collate_fn_map)
    216         for samples in transposed
    217     ]  # Backwards compatibility.
    218 else:
    219     try:

File ~/.local/lib/python3.10/site-packages/torch/utils/data/_utils/collate.py:215, in <listcomp>(.0)
    209 transposed = list(
    210     zip(*batch, strict=False)
    211 )  # It may be accessed twice, so we use a list.
    213 if isinstance(elem, tuple):
    214     return [
--> 215         collate(samples, collate_fn_map=collate_fn_map)
    216         for samples in transposed
    217     ]  # Backwards compatibility.
    218 else:
    219     try:

File ~/.local/lib/python3.10/site-packages/torch/utils/data/_utils/collate.py:155, in collate(batch, collate_fn_map)
    153 if collate_fn_map is not None:
    154     if elem_type in collate_fn_map:
--> 155         return collate_fn_map[elem_type](batch, collate_fn_map=collate_fn_map)
    157     for collate_type in collate_fn_map:
    158         if isinstance(elem, collate_type):

File ~/.local/lib/python3.10/site-packages/torch/utils/data/_utils/collate.py:275, in collate_tensor_fn(batch, collate_fn_map)
    273     storage = elem._typed_storage()._new_shared(numel, device=elem.device)
    274     out = elem.new(storage).resize_(len(batch), *list(elem.size()))
--> 275 return torch.stack(batch, 0, out=out)

RuntimeError: stack expects each tensor to be equal size, but got [1, 64, 63] at entry 0 and [1, 64, 51] at entry 2
Executed in 666ms
[20]
# Some audio files have different lengths - let's check
lengths = []
for f in train_files[:100]:
    y, sr = librosa.load(os.path.join(TRAIN_DIR, f), sr=None)
    lengths.append(len(y))
print(f"Audio lengths - min: {min(lengths)}, max: {max(lengths)}, unique: {set(lengths)}")
Audio lengths - min: 1709, max: 4000, unique: {4000, 3360, 2880, 3810, 3780, 3589, 3299, 2980, 3880, 2500, 2090, 3019, 3909, 2221, 1709, 3059, 3540}
Executed in 667ms
[21]
# Updated dataset with fixed-length audio
class WhaleAudioDatasetFixed(Dataset):
    """Dataset with fixed-length audio padding/truncating."""
    
    def __init__(self, file_list, labels, audio_dir, target_length=4000, n_mels=64, n_fft=512, hop_length=64, augment=False):
        self.file_list = file_list
        self.labels = labels
        self.audio_dir = audio_dir
        self.target_length = target_length
        self.n_mels = n_mels
        self.n_fft = n_fft
        self.hop_length = hop_length
        self.augment = augment
        
    def __len__(self):
        return len(self.file_list)
    
    def __getitem__(self, idx):
        audio_path = os.path.join(self.audio_dir, self.file_list[idx])
        y, sr = librosa.load(audio_path, sr=None)
        
        # Pad or truncate to fixed length
        if len(y) < self.target_length:
            y = np.pad(y, (0, self.target_length - len(y)), mode='constant')
        else:
            y = y[:self.target_length]
        
        # Simple augmentation: time shift
        if self.augment and np.random.random() < 0.5:
            shift = np.random.randint(-200, 200)
            y = np.roll(y, shift)
        
        mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=self.n_mels, 
                                                   n_fft=self.n_fft, hop_length=self.hop_length)
        mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
        
        # Normalize to [0, 1]
        mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)
        
        # Add channel dimension for CNN
        mel_spec_tensor = torch.FloatTensor(mel_spec_norm).unsqueeze(0)
        
        if self.labels is not None:
            return mel_spec_tensor, torch.FloatTensor([self.labels[idx]])
        return mel_spec_tensor

# Test
test_ds = WhaleAudioDatasetFixed([train_files[0], train_files[100]], [0, 1], TRAIN_DIR)
spec1, _ = test_ds[0]
spec2, _ = test_ds[1]
print(f"Spectrogram shapes: {spec1.shape}, {spec2.shape}")
Spectrogram shapes: torch.Size([1, 64, 63]), torch.Size([1, 64, 63])
Executed in 668ms
[22]
# Create fixed-length datasets
train_dataset = WhaleAudioDatasetFixed(X_train, y_train, TRAIN_DIR, augment=True)
val_dataset = WhaleAudioDatasetFixed(X_val, y_val, TRAIN_DIR, augment=False)

train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=0)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=0)

print(f"Train batches: {len(train_loader)}, Val batches: {len(val_loader)}")
Train batches: 284, Val batches: 71
Executed in 669ms
[23]
# Simpler training loop - fix the model to use BCELoss with sigmoid
class SimpleCNN2(nn.Module):
    def __init__(self, input_shape=(1, 64, 63)):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 32, 3, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2),
            
            nn.Conv2d(32, 64, 3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2),
            
            nn.Conv2d(64, 128, 3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.AdaptiveAvgPool2d((4, 4)),
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Dropout(0.5),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(256, 1),
        )
        
    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

# Training with proper loss
def train_model_v2(model, train_loader, val_loader, epochs=10, lr=1e-3):
    model = model.to(device)
    
    # Class weights for imbalanced data
    pos_weight = torch.tensor([9.0]).to(device)  # ~9:1 ratio
    criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
    
    optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
    
    best_auc = 0
    for epoch in range(epochs):
        model.train()
        train_loss = 0
        for batch_x, batch_y in tqdm(train_loader, desc=f"Epoch {epoch+1}/{epochs}"):
            batch_x, batch_y = batch_x.to(device), batch_y.to(device)
            
            optimizer.zero_grad()
            output = model(batch_x)
            loss = criterion(output, batch_y)
            loss.backward()
            optimizer.step()
            train_loss += loss.item()
        
        scheduler.step()
        
        # Validation
        model.eval()
        val_preds = []
        val_labels = []
        with torch.no_grad():
            for batch_x, batch_y in val_loader:
                batch_x = batch_x.to(device)
                output = torch.sigmoid(model(batch_x))
                val_preds.extend(output.cpu().numpy().flatten())
                val_labels.extend(batch_y.numpy().flatten())
        
        val_auc = roc_auc_score(val_labels, val_preds)
        avg_train_loss = train_loss / len(train_loader)
        
        print(f"Epoch {epoch+1}: Train Loss={avg_train_loss:.4f}, Val AUC={val_auc:.4f}")
        wandb.log({"train_loss": avg_train_loss, "val_auc": val_auc, "epoch": epoch+1})
        
        if val_auc > best_auc:
            best_auc = val_auc
            torch.save(model.state_dict(), os.path.join(DRAFT_DIR, 'best_cnn_model.pt'))
            print(f"  -> New best! Val AUC: {val_auc:.4f}")
    
    return model, best_auc

# Train
model = SimpleCNN2()
model, best_val_auc = train_model_v2(model, train_loader, val_loader, epochs=10, lr=1e-3)
print(f"\nBest validation AUC: {best_val_auc:.4f}")
Epoch 1/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [01:29<00:00,  3.17it/s]
Epoch 1: Train Loss=0.7116, Val AUC=0.9724
  -> New best! Val AUC: 0.9724
Epoch 2/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:37<00:00,  7.51it/s]
Epoch 2: Train Loss=0.3657, Val AUC=0.9807
  -> New best! Val AUC: 0.9807
Epoch 3/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:34<00:00,  8.20it/s]
Epoch 3: Train Loss=0.3021, Val AUC=0.9856
  -> New best! Val AUC: 0.9856
Epoch 4/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:33<00:00,  8.41it/s]
Epoch 4: Train Loss=0.2787, Val AUC=0.9803
Epoch 5/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:33<00:00,  8.52it/s]
Epoch 5: Train Loss=0.2347, Val AUC=0.9864
  -> New best! Val AUC: 0.9864
Epoch 6/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:33<00:00,  8.49it/s]
Epoch 6: Train Loss=0.2251, Val AUC=0.9876
  -> New best! Val AUC: 0.9876
Epoch 7/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:34<00:00,  8.16it/s]
Epoch 7: Train Loss=0.1958, Val AUC=0.9874
Epoch 8/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:34<00:00,  8.31it/s]
Epoch 8: Train Loss=0.1840, Val AUC=0.9882
  -> New best! Val AUC: 0.9882
Epoch 9/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:33<00:00,  8.41it/s]
Epoch 9: Train Loss=0.1766, Val AUC=0.9891
  -> New best! Val AUC: 0.9891
Epoch 10/10: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 284/284 [00:32<00:00,  8.62it/s]
Epoch 10: Train Loss=0.1695, Val AUC=0.9892
  -> New best! Val AUC: 0.9892

Best validation AUC: 0.9892
Executed in 670ms
[24]
# Generate test predictions
sample_sub = pd.read_csv(SAMPLE_SUB)
test_files_list = sample_sub['clip'].tolist()

# Create test dataset
test_dataset = WhaleAudioDatasetFixed(test_files_list, None, TEST_DIR, augment=False)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=0)

print(f"Test samples: {len(test_dataset)}, batches: {len(test_loader)}")
Test samples: 25149, batches: 393
Executed in 671ms
[25]
# Load best model and run inference
model = SimpleCNN2()
model.load_state_dict(torch.load(os.path.join(DRAFT_DIR, 'best_cnn_model.pt')))
model = model.to(device)
model.eval()

test_preds = []
with torch.no_grad():
    for batch_x in tqdm(test_loader, desc="Test inference"):
        batch_x = batch_x.to(device)
        output = torch.sigmoid(model(batch_x))
        test_preds.extend(output.cpu().numpy().flatten())

print(f"Generated {len(test_preds)} predictions")
print(f"Predictions range: [{min(test_preds):.4f}, {max(test_preds):.4f}]")
Test inference: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 393/393 [02:05<00:00,  3.13it/s]Generated 25149 predictions
Predictions range: [0.0000, 1.0000]

Executed in 693ms
[26]
# Create submission
submission = sample_sub.copy()
submission['probability'] = test_preds

# Validate submission
print("Submission validation:")
print(f"  Shape: {submission.shape}")
print(f"  Columns: {list(submission.columns)}")
print(f"  NaN values: {submission.isna().sum().sum()}")
print(f"  Prob range: [{submission['probability'].min():.6f}, {submission['probability'].max():.6f}]")
print(f"  Prob std: {submission['probability'].std():.4f}")

# Save draft
draft_path = os.path.join(DRAFT_DIR, 'submission_cnn_v1.csv')
submission.to_csv(draft_path, index=False)
print(f"\nSaved to: {draft_path}")
Submission validation:
  Shape: (25149, 2)
  Columns: ['clip', 'probability']
  NaN values: 0
  Prob range: [0.000000, 0.999999]
  Prob std: 0.3342

Saved to: /mnt/workspaces/trenton/qualia/benchmark_results/mle_bench_low/2026-03-01_06-24-13/row_21/drafts/submission_cnn_v1.csv
Executed in 694ms
[27]
# Score submission with grader
print("Scoring CNN v1 submission:")
result = score_submission(draft_path)
cnn_baseline_score = result['score'] if result else 0.0
print(f"\nCNN baseline test score: {cnn_baseline_score:.5f}")
print(f"Gold threshold: 0.98961")
Scoring CNN v1 submission:
{'score': 0.99039, 'rank': '0.06976744186046512', 'gold_medal': True, 'silver_medal': False, 'bronze_medal': False, 'above_median': True, 'gold_threshold': 0.98961, 'silver_threshold': 0.95017, 'bronze_threshold': 0.90521, 'median_threshold': 0.86521}

CNN baseline test score: 0.99039
Gold threshold: 0.98961
Executed in 695ms
[28]
# GOLD MEDAL ACHIEVED! Promote to final submission path
import shutil

# Copy to final submission path
shutil.copy(draft_path, SUBMISSION_PATH)
print(f"Promoted draft to final submission: {SUBMISSION_PATH}")

# Verify final submission
final_sub = pd.read_csv(SUBMISSION_PATH)
sample = pd.read_csv(SAMPLE_SUB)
print("\n=== FINAL SUBMISSION VERIFICATION ===")
print(f"βœ“ File exists: {os.path.exists(SUBMISSION_PATH)}")
print(f"βœ“ Columns match sample: {list(final_sub.columns) == list(sample.columns)}")
print(f"βœ“ Row count matches: {len(final_sub) == len(sample)} ({len(final_sub)})")
print(f"βœ“ All values finite: {final_sub['probability'].notna().all() and np.isfinite(final_sub['probability']).all()}")
print(f"βœ“ No degenerate predictions: std={final_sub['probability'].std():.4f} > 0.01")
print(f"\nFINAL SCORE: {cnn_baseline_score:.5f} (Gold threshold: 0.98961)")
Promoted draft to final submission: /mnt/workspaces/trenton/qualia/benchmark_results/mle_bench_low/2026-03-01_06-24-13/row_21/submission.csv

=== FINAL SUBMISSION VERIFICATION ===
βœ“ File exists: True
βœ“ Columns match sample: True
βœ“ Row count matches: True (25149)
βœ“ All values finite: True
βœ“ No degenerate predictions: std=0.3342 > 0.01

FINAL SCORE: 0.99039 (Gold threshold: 0.98961)
Executed in 696ms
[29]
# Log final score to wandb and close run
wandb.summary['test_auc'] = cnn_baseline_score
wandb.summary['gold_medal'] = True
wandb.summary['gold_threshold'] = 0.98961
wandb.finish()
print("wandb run completed!")
wandb: updating run metadata
wandb: uploading output.log; uploading wandb-summary.json; uploading config.yaml
wandb: 
wandb: Run history:
wandb:      epoch β–β–‚β–ƒβ–ƒβ–„β–…β–†β–†β–‡β–ˆ
wandb: train_loss β–ˆβ–„β–ƒβ–‚β–‚β–‚β–β–β–β–
wandb:    val_auc β–β–„β–‡β–„β–‡β–‡β–‡β–ˆβ–ˆβ–ˆ
wandb: 
wandb: Run summary:
wandb:          epoch 10
wandb:     gold_medal True
wandb: gold_threshold 0.98961
wandb:       test_auc 0.99039
wandb:     train_loss 0.16949
wandb:        val_auc 0.98922
wandb: 
wandb: πŸš€ View run cnn_baseline_v1 at: https://wandb.ai/trenton-qqqqqq-quadrillion-labs/mle-bench-the-icml-2013-whale-challenge-right-whale-redux/runs/oihnsv0z
wandb: ⭐️ View project at: https://wandb.ai/trenton-qqqqqq-quadrillion-labs/mle-bench-the-icml-2013-whale-challenge-right-whale-redux
wandb: Synced 5 W&B file(s), 0 media file(s), 0 artifact file(s) and 0 other file(s)
wandb: Find logs at: ./wandb/run-20260301_072414-oihnsv0z/logs
wandb run completed!
Executed in 697ms
[31]
# Set task variable for T-2
cnn_baseline_score = 0.99039
print(f"cnn_baseline_score = {cnn_baseline_score}")
cnn_baseline_score = 0.99039
Executed in 698ms

Summary: ICML 2013 Whale Challenge - Right Whale Redux



Results


- Final Score: 0.99039 AUC-ROC πŸ₯‡
- Gold Medal Threshold: 0.98961
- Competition rank: Top 7%

Approach


1. Feature extraction: Mel-spectrograms (64 mel bins, 512 FFT, 64 hop) converted from 2-second audio clips
2. Model: Simple 3-layer CNN with batch normalization and dropout
3. Training: 10 epochs with AdamW optimizer, class weighting (pos_weight=9.0) for imbalance
4. Validation AUC: 0.9892

Key Insights


- Audio files had variable lengths (1709-4000 samples); padding/truncating to 4000 samples fixed tensor stacking issues
- Simple CNN architecture was sufficient to achieve gold - no need for larger models or transfer learning
- Class weighting was critical given 10% positive rate in training data
Attached notebook: /mnt/workspaces/trenton/qualia/benchmark_results/mle_bench_low/2026-03-01_06-24-13/row_21/notebook.ipynb

I see the model training has completed with a promising validation AUC of 0.9892 β€” that's very close to the gold threshold of 0.98961! Now I need to:

1. Generate predictions on the test set using the saved model 2. Create and score the submission with the grader 3. Promote to the final submission path if it beats gold

Let me run the test set inference now.

Made 2 changes
Waiting on agent response...
Made 4 changes
Updated task: 270e693e-1150-4c03-979e-3d4439516c53-T-2 β†’ Completing task T-2
Made 1 change
Updated task: 270e693e-1150-4c03-979e-3d4439516c53-T-2 β†’ Completing task T-2
Made 1 change
Updated task: 270e693e-1150-4c03-979e-3d4439516c53-T-2 β†’ Completing task T-2
Updated task: 270e693e-1150-4c03-979e-3d4439516c53-T-2 β†’ Skipping variable capture issue
Updated task: 270e693e-1150-4c03-979e-3d4439516c53-T-3 β†’ Cancelling remaining tasks - gold achieved
Updated task: 270e693e-1150-4c03-979e-3d4439516c53-T-4 β†’ Cancelling T-4 - gold achieved
Made 1 change

πŸ₯‡ Gold Medal Achieved!

The CNN baseline on mel-spectrograms achieved a test AUC-ROC of 0.99039, beating the gold threshold of 0.98961.

What worked: - Simple 3-layer CNN (32β†’64β†’128 filters) with batch normalization - Mel-spectrogram features (64 mel bins) from 2-second audio clips - Class weighting (pos_weight=9.0) to handle the 10% positive rate - 10 epochs of training was sufficient

The final submission has been promoted to `submission.csv` and verified against all integrity checks. I've cancelled the remaining tasks (audio feature engineering, transfer learning) since they're no longer needed with gold already achieved.

GPT-5.2 (Low) β€Ί
Quadrillion - Research at the Speed of Thought