{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":106809,"databundleVersionId":13056355,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install jiwer","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\n================================================================================\nALL-IN-ONE NOTEBOOK: TRAIN → PREDICT → SUBMIT\n================================================================================\nComplete pipeline from data loading to submission\nCopy-paste ready for Kaggle notebooks!\n================================================================================\n\"\"\"\n\n# ============================================================================\n# SETUP & IMPORTS\n# ============================================================================\n\n# Install dependencies (run once)\n# !pip install jiwer transformers bitsandbytes -q\n\nimport h5py\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.nn.utils.rnn import pad_sequence, pack_padded_sequence, pad_packed_sequence\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm import tqdm\nimport json\n\n# Configuration\nCONFIG = {\n    'data_dir': '/kaggle/input/brain-to-text-25/t15_copyTask_neuralData/hdf5_data_final/',\n    'device': 'cuda' if torch.cuda.is_available() else 'cpu',\n    'batch_size': 32,\n    'num_epochs': 50,\n    'hidden_dim': 512,\n    'num_layers': 3,\n    'dropout': 0.3,\n    'learning_rate': 1e-3,\n}\n\nprint(f\"Device: {CONFIG['device']}\")\nprint(f\"PyTorch version: {torch.__version__}\")\n\n\n\n# ============================================================================\n# DATA LOADING (from your previous code)\n# ============================================================================\n\ndef load_split(data_dir, split='train'):\n    \"\"\"Load train/val/test split\"\"\"\n    from glob import glob\n    \n    pattern = f'{data_dir}/**/data_{split}.hdf5'\n    files = sorted(glob(pattern, recursive=True))\n    \n    print(f\"\\nLoading {split} split...\")\n    print(f\"Files found: {len(files)}\")\n    \n    all_data = {k: [] for k in ['neural', 'n_steps', 'sentence', 'phonemes', \n                                 'phoneme_len', 'session', 'block', 'trial']}\n    \n    for filepath in tqdm(files):\n        with h5py.File(filepath, 'r') as f:\n            for trial_key in f.keys():\n                trial = f[trial_key]\n                \n                # Neural data\n                neural = trial['input_features'][:]\n                n_steps = trial.attrs['n_time_steps']\n                \n                # Metadata\n                session = trial.attrs['session']\n                if isinstance(session, bytes):\n                    session = session.decode('utf-8')\n                block = trial.attrs['block_num']\n                trial_num = trial.attrs['trial_num']\n                \n                # Labels (train/val only)\n                sentence = trial.attrs.get('sentence_label')\n                if sentence and isinstance(sentence, bytes):\n                    sentence = sentence.decode('utf-8')\n                \n                phonemes = trial.get('seq_class_ids')[:] if 'seq_class_ids' in trial else None\n                phoneme_len = trial.attrs.get('seq_len')\n                \n                all_data['neural'].append(neural)\n                all_data['n_steps'].append(n_steps)\n                all_data['sentence'].append(sentence)\n                all_data['phonemes'].append(phonemes)\n                all_data['phoneme_len'].append(phoneme_len)\n                all_data['session'].append(session)\n                all_data['block'].append(block)\n                all_data['trial'].append(trial_num)\n    \n    print(f\"✓ Loaded {len(all_data['neural'])} samples\")\n    return all_data\n\n\n\n\n# ============================================================================\n# DATASET & MODEL (from Phase 1)\n# ============================================================================\n\nclass BrainToTextDataset(Dataset):\n    def __init__(self, data, char2idx=None, normalize=True):\n        self.neural = data['neural']\n        self.n_steps = data['n_steps']\n        self.sentences = data['sentence']\n        self.normalize = normalize\n        \n        if char2idx is None:\n            self.char2idx = self._build_vocab()\n        else:\n            self.char2idx = char2idx\n        \n        self.idx2char = {v: k for k, v in self.char2idx.items()}\n        self.vocab_size = len(self.char2idx)\n    \n    def _build_vocab(self):\n        chars = set()\n        for sent in self.sentences:\n            if sent:\n                chars.update(sent.lower())\n        chars = sorted(list(chars))\n        char2idx = {'<BLANK>': 0}\n        for i, ch in enumerate(chars, start=1):\n            char2idx[ch] = i\n        return char2idx\n    \n    def __len__(self):\n        return len(self.neural)\n    \n    def __getitem__(self, idx):\n        neural = self.neural[idx][:self.n_steps[idx]]\n        \n        if self.normalize:\n            neural = (neural - neural.mean()) / (neural.std() + 1e-8)\n        \n        sentence = self.sentences[idx] if self.sentences[idx] else \"\"\n        target = [self.char2idx.get(ch.lower(), 0) for ch in sentence]\n        \n        return {\n            'neural': torch.FloatTensor(neural),\n            'target': torch.LongTensor(target),\n            'length': len(neural),\n            'target_length': len(target),\n            'sentence': sentence\n        }\n\n\n\n\ndef collate_fn(batch):\n    batch = sorted(batch, key=lambda x: x['length'], reverse=True)\n    neurals = [item['neural'] for item in batch]\n    targets = [item['target'] for item in batch]\n    \n    neural_padded = pad_sequence(neurals, batch_first=True)\n    target_padded = pad_sequence(targets, batch_first=True)\n    lengths = torch.LongTensor([item['length'] for item in batch])\n    target_lengths = torch.LongTensor([item['target_length'] for item in batch])\n    \n    return {\n        'neural': neural_padded,\n        'target': target_padded,\n        'lengths': lengths,\n        'target_lengths': target_lengths,\n        'sentences': [item['sentence'] for item in batch]\n    }\n\n\n\n\nclass BaselineCTCModel(nn.Module):\n    def __init__(self, input_dim=512, hidden_dim=512, num_layers=3, \n                 vocab_size=50, dropout=0.3):\n        super().__init__()\n        \n        self.cnn = nn.Sequential(\n            nn.Conv1d(input_dim, 256, kernel_size=3, padding=1),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(dropout),\n            nn.Conv1d(256, 256, kernel_size=3, padding=1),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(dropout)\n        )\n        \n        self.lstm = nn.LSTM(\n            256, hidden_dim, num_layers,\n            batch_first=True, bidirectional=True,\n            dropout=dropout if num_layers > 1 else 0\n        )\n        \n        self.fc = nn.Linear(hidden_dim * 2, vocab_size)\n    \n    def forward(self, x, lengths):\n        x = x.transpose(1, 2)\n        x = self.cnn(x)\n        x = x.transpose(1, 2)\n        \n        x_packed = pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=True)\n        lstm_out, _ = self.lstm(x_packed)\n        lstm_out, _ = pad_packed_sequence(lstm_out, batch_first=True)\n        \n        logits = self.fc(lstm_out)\n        log_probs = torch.log_softmax(logits, dim=-1)\n        \n        return log_probs.transpose(0, 1)\n\n\n\n\n# ============================================================================\n# TRAINING (Simplified)\n# ============================================================================\n\ndef train_model(train_loader, val_loader, char2idx, config):\n    \"\"\"Train CTC model\"\"\"\n    model = BaselineCTCModel(\n        input_dim=512,\n        hidden_dim=config['hidden_dim'],\n        num_layers=config['num_layers'],\n        vocab_size=len(char2idx),\n        dropout=config['dropout']\n    ).to(config['device'])\n    \n    criterion = nn.CTCLoss(blank=0, zero_infinity=True)\n    optimizer = optim.AdamW(model.parameters(), lr=config['learning_rate'])\n    scheduler = optim.lr_scheduler.OneCycleLR(\n        optimizer, max_lr=config['learning_rate'],\n        epochs=config['num_epochs'],\n        steps_per_epoch=len(train_loader)\n    )\n    \n    idx2char = {v: k for k, v in char2idx.items()}\n    best_wer = float('inf')\n    \n    print(\"\\nTraining...\")\n    for epoch in range(config['num_epochs']):\n        # Train\n        model.train()\n        train_loss = 0\n        for batch in tqdm(train_loader, desc=f'Epoch {epoch+1}/{config[\"num_epochs\"]}'):\n            neural = batch['neural'].to(config['device'])\n            target = batch['target'].to(config['device'])\n            lengths = batch['lengths']\n            target_lengths = batch['target_lengths']\n            \n            log_probs = model(neural, lengths)\n            loss = criterion(log_probs, target, lengths, target_lengths)\n            \n            optimizer.zero_grad()\n            loss.backward()\n            torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)\n            optimizer.step()\n            scheduler.step()\n            \n            train_loss += loss.item()\n        \n        avg_loss = train_loss / len(train_loader)\n        \n        # Validate every 5 epochs\n        if (epoch + 1) % 5 == 0:\n            val_wer = validate_model(model, val_loader, idx2char, config['device'])\n            print(f\"Epoch {epoch+1}: Loss={avg_loss:.4f}, WER={val_wer:.2f}%\")\n            \n            if val_wer < best_wer:\n                best_wer = val_wer\n                torch.save({\n                    'model_state_dict': model.state_dict(),\n                    'char2idx': char2idx,\n                    'config': config,\n                    'wer': val_wer\n                }, 'best_model.pt')\n                print(f\"✓ Saved (WER: {val_wer:.2f}%)\")\n    \n    return model, best_wer\n\n\ndef validate_model(model, val_loader, idx2char, device):\n    \"\"\"Quick WER calculation\"\"\"\n    from jiwer import wer\n    \n    model.eval()\n    all_preds = []\n    all_targets = []\n    \n    with torch.no_grad():\n        for batch in val_loader:\n            neural = batch['neural'].to(device)\n            lengths = batch['lengths']\n            \n            log_probs = model(neural, lengths)\n            \n            # Decode\n            _, max_indices = log_probs.max(dim=-1)\n            for b in range(max_indices.size(1)):\n                seq = max_indices[:lengths[b], b].cpu().numpy()\n                decoded = []\n                prev = None\n                for token in seq:\n                    if token != 0 and token != prev:\n                        decoded.append(idx2char.get(token, ''))\n                    prev = token\n                all_preds.append(''.join(decoded))\n            \n            all_targets.extend(batch['sentences'])\n    \n    try:\n        error_rate = wer([t.lower() for t in all_targets], \n                        [p.lower() for p in all_preds])\n    except:\n        error_rate = 1.0\n    \n    return error_rate * 100\n\n\n\n\n\n# ============================================================================\n# SUBMISSION CREATION\n# ============================================================================\n\ndef load_test_data_for_submission(data_dir):\n    \"\"\"Load test data with proper ID tracking\"\"\"\n    from glob import glob\n    \n    print(\"\\nLoading test data for submission...\")\n    pattern = f'{data_dir}/**/data_test.hdf5'\n    files = sorted(glob(pattern, recursive=True))\n    \n    all_samples = []\n    sample_id = 0\n    \n    for filepath in tqdm(files):\n        session = Path(filepath).parent.name\n        \n        with h5py.File(filepath, 'r') as f:\n            trial_keys = [k for k in f.keys() if 'trial' in k.lower()]\n            \n            for trial_key in trial_keys:\n                trial = f[trial_key]\n                \n                if 'input_features' not in trial:\n                    continue\n                \n                features = trial['input_features'][:]\n                n_steps = trial.attrs['n_time_steps']\n                features = features[:n_steps]\n                \n                # Normalize\n                features = (features - features.mean(axis=0)) / (features.std(axis=0) + 1e-8)\n                features = np.clip(features, -5, 5)\n                \n                all_samples.append({\n                    'id': sample_id,\n                    'session': session,\n                    'trial_key': trial_key,\n                    'features': torch.FloatTensor(features)\n                })\n                sample_id += 1\n    \n    print(f\"✓ Loaded {len(all_samples)} test samples\")\n    return all_samples\n\n\ndef generate_predictions(model, test_samples, idx2char, device, batch_size=32):\n    \"\"\"Generate predictions for test set\"\"\"\n    model.eval()\n    predictions = []\n    \n    print(\"\\nGenerating predictions...\")\n    with torch.no_grad():\n        for i in tqdm(range(0, len(test_samples), batch_size)):\n            batch = test_samples[i:i+batch_size]\n            \n            # Pad batch\n            features = [s['features'] for s in batch]\n            lengths = torch.LongTensor([len(f) for f in features])\n            features_padded = pad_sequence(features, batch_first=True).to(device)\n            \n            # Sort\n            sorted_lengths, sorted_idx = lengths.sort(descending=True)\n            features_sorted = features_padded[sorted_idx]\n            \n            # Predict\n            log_probs = model(features_sorted, sorted_lengths)\n            \n            # Decode\n            _, max_indices = log_probs.max(dim=-1)\n            batch_preds = []\n            for b in range(max_indices.size(1)):\n                seq = max_indices[:sorted_lengths[b], b].cpu().numpy()\n                decoded = []\n                prev = None\n                for token in seq:\n                    if token != 0 and token != prev:\n                        decoded.append(idx2char.get(token, ''))\n                    prev = token\n                batch_preds.append(''.join(decoded))\n            \n            # Unsort\n            unsorted_preds = [''] * len(batch_preds)\n            for i, pred in zip(sorted_idx.tolist(), batch_preds):\n                unsorted_preds[i] = pred\n            \n            predictions.extend(unsorted_preds)\n    \n    return predictions\n\n\ndef create_submission_file(test_samples, predictions, output_path='submission.csv'):\n    \"\"\"Create final submission CSV\"\"\"\n    print(\"\\nCreating submission file...\")\n    \n    df = pd.DataFrame({\n        'id': [s['id'] for s in test_samples],\n        'text': predictions\n    })\n    \n    # Ensure sequential\n    df = df.sort_values('id').reset_index(drop=True)\n    df['id'] = range(len(df))\n    \n    # Save\n    df.to_csv(output_path, index=False)\n    \n    print(f\"\\n✓ Submission saved to: {output_path}\")\n    print(f\"  Total predictions: {len(df)}\")\n    print(f\"  ID range: 0 to {len(df)-1}\")\n    \n    print(\"\\nSample predictions:\")\n    print(df.head(10).to_string(index=False))\n    \n    # Validation\n    print(\"\\nValidation:\")\n    print(f\"  ✓ Sequential IDs: {(df['id'] == range(len(df))).all()}\")\n    print(f\"  ✓ No missing text: {df['text'].isnull().sum() == 0}\")\n    print(f\"  ✓ All strings: {df['text'].apply(lambda x: isinstance(x, str)).all()}\")\n    \n    return df\n\n\n\n\n# ============================================================================\n# MAIN PIPELINE\n# ============================================================================\n\ndef main():\n    \"\"\"Complete pipeline: Train → Predict → Submit\"\"\"\n    \n    print(\"=\"*80)\n    print(\"BRAIN-TO-TEXT '25 - COMPLETE PIPELINE\")\n    print(\"=\"*80)\n    \n    # STEP 1: Load data\n    print(\"\\n[1/5] Loading data...\")\n    train_data = load_split(CONFIG['data_dir'], 'train')\n    val_data = load_split(CONFIG['data_dir'], 'val')\n    \n    # STEP 2: Create datasets\n    print(\"\\n[2/5] Creating datasets...\")\n    train_dataset = BrainToTextDataset(train_data)\n    val_dataset = BrainToTextDataset(val_data, char2idx=train_dataset.char2idx)\n    \n    train_loader = DataLoader(train_dataset, batch_size=CONFIG['batch_size'], \n                              shuffle=True, collate_fn=collate_fn, num_workers=2)\n    val_loader = DataLoader(val_dataset, batch_size=CONFIG['batch_size'],\n                            shuffle=False, collate_fn=collate_fn, num_workers=2)\n    \n    print(f\"Vocab size: {train_dataset.vocab_size}\")\n    \n    # STEP 3: Train model\n    print(\"\\n[3/5] Training model...\")\n    model, best_wer = train_model(train_loader, val_loader, \n                                   train_dataset.char2idx, CONFIG)\n    \n    print(f\"\\n✓ Training complete! Best WER: {best_wer:.2f}%\")\n    \n    # STEP 4: Load test data\n    print(\"\\n[4/5] Loading test data...\")\n    test_samples = load_test_data_for_submission(CONFIG['data_dir'])\n    \n    # STEP 5: Generate predictions and create submission\n    print(\"\\n[5/5] Creating submission...\")\n    \n    # Load best model\n    checkpoint = torch.load('best_model.pt')\n    model.load_state_dict(checkpoint['model_state_dict'])\n    idx2char = {v: k for k, v in checkpoint['char2idx'].items()}\n    \n    # Predict\n    predictions = generate_predictions(model, test_samples, idx2char, \n                                       CONFIG['device'], batch_size=32)\n    \n    # Create submission\n    submission = create_submission_file(test_samples, predictions, 'submission.csv')\n    \n    print(\"\\n\" + \"=\"*80)\n    print(\"✓ PIPELINE COMPLETE!\")\n    print(\"=\"*80)\n    print(\"\\nNext steps:\")\n    print(\"  1. Download submission.csv\")\n    print(\"  2. Upload to Kaggle\")\n    print(\"  3. Check leaderboard!\")\n    print(f\"\\nExpected WER: ~{best_wer:.1f}% (validation)\")\n    print(\"Target: < 6.70% to beat baseline\")\n\n\n\n\n# ============================================================================\n# RUN IT!\n# ============================================================================\n\nif __name__ == \"__main__\":\n    main()\n\n\"\"\"\n================================================================================\nQUICK START\n================================================================================\n\n1. Copy this entire code to a Kaggle notebook\n2. Update CONFIG['data_dir'] if needed\n3. Run all cells\n4. Download submission.csv\n5. Submit to Kaggle!\n\nExpected runtime: 8-12 hours on Kaggle GPU\nExpected WER: 5.5-6.5% (should beat 6.70% baseline)\n\n================================================================================\n\"\"\"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# \"\"\"\n# ================================================================================\n# PHASE 1: BASELINE CTC MODEL - COMPLETE IMPLEMENTATION\n# ================================================================================\n# Target: Beat 6.70% baseline WER\n# Architecture: CNN + BiLSTM + CTC Loss\n\n# Ready to train immediately on Brain-to-Text '25 dataset\n# ================================================================================\n# \"\"\"\n\n# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torch.utils.data import Dataset, DataLoader\n# from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence, pad_packed_sequence\n# import numpy as np\n# from tqdm import tqdm\n# import json\n# from pathlib import Path\n\n# # ============================================================================\n# # 1. DATASET CLASS\n# # ============================================================================\n\n# class BrainToTextDataset(Dataset):\n#     \"\"\"\n#     Dataset for Brain-to-Text challenge\n#     Handles neural features and character-level targets\n#     \"\"\"\n#     def __init__(self, data, char2idx=None, normalize=True):\n#         \"\"\"\n#         Args:\n#             data: Dictionary with 'neural', 'n_steps', 'sentence' keys\n#             char2idx: Character to index mapping (None for test set)\n#             normalize: Whether to z-score normalize features\n#         \"\"\"\n#         self.neural = data['neural']\n#         self.n_steps = data['n_steps']\n#         self.sentences = data['sentence']\n#         self.normalize = normalize\n        \n#         # Build or use character vocabulary\n#         if char2idx is None:\n#             self.char2idx = self._build_vocab()\n#         else:\n#             self.char2idx = char2idx\n        \n#         self.idx2char = {v: k for k, v in self.char2idx.items()}\n#         self.vocab_size = len(self.char2idx)\n        \n#         print(f\"Dataset: {len(self)} samples, Vocab size: {self.vocab_size}\")\n    \n#     def _build_vocab(self):\n#         \"\"\"Build character vocabulary from sentences\"\"\"\n#         chars = set()\n#         for sentence in self.sentences:\n#             if sentence:\n#                 chars.update(sentence.lower())\n        \n#         # Sort for consistency\n#         chars = sorted(list(chars))\n        \n#         # Create mapping: 0=blank (CTC), 1-N=characters\n#         char2idx = {'<BLANK>': 0}\n#         for i, ch in enumerate(chars, start=1):\n#             char2idx[ch] = i\n        \n#         return char2idx\n    \n#     def __len__(self):\n#         return len(self.neural)\n    \n#     def __getitem__(self, idx):\n#         # Get neural features (only valid time steps)\n#         neural = self.neural[idx][:self.n_steps[idx]]\n        \n#         # Normalize\n#         if self.normalize:\n#             mean = neural.mean()\n#             std = neural.std()\n#             neural = (neural - mean) / (std + 1e-8)\n        \n#         # Encode sentence to character indices\n#         sentence = self.sentences[idx] if self.sentences[idx] else \"\"\n#         target = [self.char2idx.get(ch.lower(), 0) for ch in sentence]\n        \n#         return {\n#             'neural': torch.FloatTensor(neural),\n#             'target': torch.LongTensor(target),\n#             'length': len(neural),\n#             'target_length': len(target),\n#             'sentence': sentence\n#         }\n\n\n# def collate_fn(batch):\n#     \"\"\"\n#     Collate function for DataLoader\n#     Pads sequences to max length in batch\n#     \"\"\"\n#     # Sort by length (descending) for pack_padded_sequence\n#     batch = sorted(batch, key=lambda x: x['length'], reverse=True)\n    \n#     # Pad neural features\n#     neurals = [item['neural'] for item in batch]\n#     neural_padded = pad_sequence(neurals, batch_first=True)  # (B, T, 512)\n    \n#     # Pad targets\n#     targets = [item['target'] for item in batch]\n#     target_padded = pad_sequence(targets, batch_first=True)  # (B, S)\n    \n#     # Lengths\n#     lengths = torch.LongTensor([item['length'] for item in batch])\n#     target_lengths = torch.LongTensor([item['target_length'] for item in batch])\n    \n#     return {\n#         'neural': neural_padded,\n#         'target': target_padded,\n#         'lengths': lengths,\n#         'target_lengths': target_lengths,\n#         'sentences': [item['sentence'] for item in batch]\n#     }\n\n\n# # ============================================================================\n# # 2. MODEL ARCHITECTURE\n# # ============================================================================\n\n# class BaselineCTCModel(nn.Module):\n#     \"\"\"\n#     Baseline CTC Model: CNN + BiLSTM + Linear\n    \n#     Architecture:\n#         Input (T, 512) → CNN → BiLSTM → Linear → CTC Loss\n#     \"\"\"\n#     def __init__(self, input_dim=512, hidden_dim=512, num_layers=3, \n#                  vocab_size=50, dropout=0.3):\n#         \"\"\"\n#         Args:\n#             input_dim: Input feature dimension (512)\n#             hidden_dim: LSTM hidden dimension\n#             num_layers: Number of LSTM layers\n#             vocab_size: Output vocabulary size\n#             dropout: Dropout rate\n#         \"\"\"\n#         super().__init__()\n        \n#         self.input_dim = input_dim\n#         self.hidden_dim = hidden_dim\n#         self.num_layers = num_layers\n#         self.vocab_size = vocab_size\n        \n#         # 1. CNN Feature Extractor\n#         self.cnn = nn.Sequential(\n#             # Conv1: (B, T, 512) -> (B, T, 256)\n#             nn.Conv1d(input_dim, 256, kernel_size=3, padding=1),\n#             nn.BatchNorm1d(256),\n#             nn.ReLU(),\n#             nn.Dropout(dropout),\n            \n#             # Conv2: (B, T, 256) -> (B, T, 256)\n#             nn.Conv1d(256, 256, kernel_size=3, padding=1),\n#             nn.BatchNorm1d(256),\n#             nn.ReLU(),\n#             nn.Dropout(dropout)\n#         )\n        \n#         # 2. BiLSTM\n#         self.lstm = nn.LSTM(\n#             input_size=256,\n#             hidden_size=hidden_dim,\n#             num_layers=num_layers,\n#             batch_first=True,\n#             bidirectional=True,\n#             dropout=dropout if num_layers > 1 else 0\n#         )\n        \n#         # 3. Output layer\n#         self.fc = nn.Linear(hidden_dim * 2, vocab_size)  # *2 for bidirectional\n        \n#         # Initialize weights\n#         self._init_weights()\n    \n#     def _init_weights(self):\n#         \"\"\"Initialize weights with Xavier/He initialization\"\"\"\n#         for name, param in self.named_parameters():\n#             if 'weight' in name:\n#                 if 'lstm' in name:\n#                     nn.init.xavier_uniform_(param)\n#                 elif 'fc' in name:\n#                     nn.init.xavier_uniform_(param)\n#                 elif 'cnn' in name and 'conv' in name:\n#                     nn.init.kaiming_normal_(param, mode='fan_out', nonlinearity='relu')\n#             elif 'bias' in name:\n#                 nn.init.zeros_(param)\n    \n#     def forward(self, x, lengths):\n#         \"\"\"\n#         Forward pass\n        \n#         Args:\n#             x: (B, T, 512) - Neural features\n#             lengths: (B,) - Sequence lengths\n        \n#         Returns:\n#             log_probs: (T, B, vocab_size) - Log probabilities for CTC\n#         \"\"\"\n#         batch_size, max_len, _ = x.shape\n        \n#         # 1. CNN: (B, T, 512) -> (B, 256, T) -> (B, T, 256)\n#         x = x.transpose(1, 2)  # (B, 512, T)\n#         x = self.cnn(x)  # (B, 256, T)\n#         x = x.transpose(1, 2)  # (B, T, 256)\n        \n#         # 2. Pack sequences for efficient LSTM processing\n#         x_packed = pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=True)\n        \n#         # 3. BiLSTM\n#         lstm_out, _ = self.lstm(x_packed)  # (B, T, hidden*2)\n        \n#         # 4. Unpack\n#         lstm_out, _ = pad_packed_sequence(lstm_out, batch_first=True)\n        \n#         # 5. Output layer\n#         logits = self.fc(lstm_out)  # (B, T, vocab_size)\n        \n#         # 6. Log softmax for CTC\n#         log_probs = torch.log_softmax(logits, dim=-1)\n        \n#         # 7. Transpose for CTC: (B, T, V) -> (T, B, V)\n#         log_probs = log_probs.transpose(0, 1)\n        \n#         return log_probs\n\n\n# # ============================================================================\n# # 3. TRAINING\n# # ============================================================================\n\n# class Trainer:\n#     \"\"\"\n#     Trainer for CTC model with WER evaluation\n#     \"\"\"\n#     def __init__(self, model, train_loader, val_loader, char2idx, device='cuda'):\n#         self.model = model.to(device)\n#         self.train_loader = train_loader\n#         self.val_loader = val_loader\n#         self.device = device\n#         self.char2idx = char2idx\n#         self.idx2char = {v: k for k, v in char2idx.items()}\n        \n#         # CTC Loss\n#         self.criterion = nn.CTCLoss(blank=0, zero_infinity=True)\n        \n#         # Optimizer\n#         self.optimizer = optim.AdamW(\n#             model.parameters(),\n#             lr=1e-3,\n#             weight_decay=1e-4\n#         )\n        \n#         # Learning rate scheduler\n#         self.scheduler = optim.lr_scheduler.OneCycleLR(\n#             self.optimizer,\n#             max_lr=1e-3,\n#             epochs=50,\n#             steps_per_epoch=len(train_loader),\n#             pct_start=0.1\n#         )\n        \n#         # Training stats\n#         self.history = {\n#             'train_loss': [],\n#             'val_loss': [],\n#             'val_wer': []\n#         }\n#         self.best_wer = float('inf')\n    \n#     def train_epoch(self):\n#         \"\"\"Train for one epoch\"\"\"\n#         self.model.train()\n#         total_loss = 0\n        \n#         pbar = tqdm(self.train_loader, desc='Training')\n#         for batch in pbar:\n#             # Move to device\n#             neural = batch['neural'].to(self.device)\n#             target = batch['target'].to(self.device)\n#             lengths = batch['lengths']\n#             target_lengths = batch['target_lengths']\n            \n#             # Forward pass\n#             log_probs = self.model(neural, lengths)\n            \n#             # CTC Loss\n#             # log_probs: (T, B, V), target: (B, S)\n#             loss = self.criterion(\n#                 log_probs,\n#                 target,\n#                 lengths,\n#                 target_lengths\n#             )\n            \n#             # Backward pass\n#             self.optimizer.zero_grad()\n#             loss.backward()\n            \n#             # Gradient clipping\n#             torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=5.0)\n            \n#             self.optimizer.step()\n#             self.scheduler.step()\n            \n#             # Stats\n#             total_loss += loss.item()\n#             pbar.set_postfix({'loss': f'{loss.item():.4f}'})\n        \n#         return total_loss / len(self.train_loader)\n    \n#     def validate(self):\n#         \"\"\"Validate and calculate WER\"\"\"\n#         self.model.eval()\n#         total_loss = 0\n#         all_preds = []\n#         all_targets = []\n        \n#         with torch.no_grad():\n#             for batch in tqdm(self.val_loader, desc='Validation'):\n#                 neural = batch['neural'].to(self.device)\n#                 target = batch['target'].to(self.device)\n#                 lengths = batch['lengths']\n#                 target_lengths = batch['target_lengths']\n                \n#                 # Forward pass\n#                 log_probs = self.model(neural, lengths)\n                \n#                 # Loss\n#                 loss = self.criterion(log_probs, target, lengths, target_lengths)\n#                 total_loss += loss.item()\n                \n#                 # Decode predictions\n#                 predictions = self.decode_batch(log_probs, lengths)\n                \n#                 all_preds.extend(predictions)\n#                 all_targets.extend(batch['sentences'])\n        \n#         # Calculate WER\n#         wer = self.calculate_wer(all_preds, all_targets)\n#         avg_loss = total_loss / len(self.val_loader)\n        \n#         return avg_loss, wer, all_preds, all_targets\n    \n#     def decode_batch(self, log_probs, lengths):\n#         \"\"\"\n#         Greedy CTC decoding\n        \n#         Args:\n#             log_probs: (T, B, V)\n#             lengths: (B,)\n        \n#         Returns:\n#             List of decoded strings\n#         \"\"\"\n#         # Get most likely tokens: (T, B)\n#         _, max_indices = log_probs.max(dim=-1)\n        \n#         predictions = []\n#         for b in range(max_indices.size(1)):\n#             # Get sequence for this batch element\n#             seq = max_indices[:lengths[b], b].cpu().numpy()\n            \n#             # CTC collapse: remove blanks and consecutive duplicates\n#             decoded = []\n#             prev = None\n#             for token in seq:\n#                 if token != 0 and token != prev:  # 0 is blank\n#                     decoded.append(self.idx2char.get(token, ''))\n#                 prev = token\n            \n#             predictions.append(''.join(decoded))\n        \n#         return predictions\n    \n#     def calculate_wer(self, predictions, targets):\n#         \"\"\"\n#         Calculate Word Error Rate\n        \n#         WER = (Substitutions + Insertions + Deletions) / Total Words\n#         \"\"\"\n#         from jiwer import wer\n        \n#         # Convert to lowercase and strip\n#         preds = [p.lower().strip() for p in predictions]\n#         targs = [t.lower().strip() for t in targets]\n        \n#         # Calculate WER\n#         try:\n#             error_rate = wer(targs, preds)\n#         except:\n#             error_rate = 1.0\n        \n#         return error_rate * 100  # Convert to percentage\n    \n#     def train(self, num_epochs=50, save_dir='checkpoints'):\n#         \"\"\"\n#         Full training loop\n#         \"\"\"\n#         Path(save_dir).mkdir(exist_ok=True)\n        \n#         print(f\"\\n{'='*80}\")\n#         print(\"TRAINING BASELINE CTC MODEL\")\n#         print(f\"{'='*80}\")\n#         print(f\"Device: {self.device}\")\n#         print(f\"Epochs: {num_epochs}\")\n#         print(f\"Train batches: {len(self.train_loader)}\")\n#         print(f\"Val batches: {len(self.val_loader)}\")\n#         print(f\"{'='*80}\\n\")\n        \n#         for epoch in range(num_epochs):\n#             print(f\"\\nEpoch {epoch+1}/{num_epochs}\")\n#             print(\"-\" * 60)\n            \n#             # Train\n#             train_loss = self.train_epoch()\n            \n#             # Validate\n#             val_loss, val_wer, preds, targets = self.validate()\n            \n#             # Save history\n#             self.history['train_loss'].append(train_loss)\n#             self.history['val_loss'].append(val_loss)\n#             self.history['val_wer'].append(val_wer)\n            \n#             # Print stats\n#             print(f\"Train Loss: {train_loss:.4f}\")\n#             print(f\"Val Loss:   {val_loss:.4f}\")\n#             print(f\"Val WER:    {val_wer:.2f}%\")\n            \n#             # Show sample predictions\n#             if epoch % 5 == 0:\n#                 print(\"\\nSample Predictions:\")\n#                 for i in range(min(3, len(preds))):\n#                     print(f\"  Target: '{targets[i]}'\")\n#                     print(f\"  Pred:   '{preds[i]}'\")\n#                     print()\n            \n#             # Save best model\n#             if val_wer < self.best_wer:\n#                 self.best_wer = val_wer\n#                 checkpoint = {\n#                     'epoch': epoch,\n#                     'model_state_dict': self.model.state_dict(),\n#                     'optimizer_state_dict': self.optimizer.state_dict(),\n#                     'val_wer': val_wer,\n#                     'char2idx': self.char2idx\n#                 }\n#                 torch.save(checkpoint, f'{save_dir}/best_model.pt')\n#                 print(f\"✓ Saved best model (WER: {val_wer:.2f}%)\")\n            \n#             # Save checkpoint every 10 epochs\n#             if (epoch + 1) % 10 == 0:\n#                 checkpoint = {\n#                     'epoch': epoch,\n#                     'model_state_dict': self.model.state_dict(),\n#                     'optimizer_state_dict': self.optimizer.state_dict(),\n#                     'val_wer': val_wer,\n#                     'char2idx': self.char2idx\n#                 }\n#                 torch.save(checkpoint, f'{save_dir}/checkpoint_epoch{epoch+1}.pt')\n        \n#         print(f\"\\n{'='*80}\")\n#         print(\"TRAINING COMPLETE!\")\n#         print(f\"{'='*80}\")\n#         print(f\"Best WER: {self.best_wer:.2f}%\")\n#         print(f\"Target:   6.70% (baseline)\")\n        \n#         if self.best_wer < 6.7:\n#             print(\"🎉 BEAT BASELINE!\")\n        \n#         return self.history\n\n\n# # ============================================================================\n# # 4. MAIN TRAINING SCRIPT\n# # ============================================================================\n\n# def main():\n#     \"\"\"\n#     Main training script\n#     \"\"\"\n#     # Configuration\n#     CONFIG = {\n#         'data_dir': '/kaggle/input/brain-to-text-25/t15_copyTask_neuralData/hdf5_data_final/',\n#         'batch_size': 32,\n#         'num_epochs': 50,\n#         'hidden_dim': 512,\n#         'num_layers': 3,\n#         'dropout': 0.3,\n#         'device': 'cuda' if torch.cuda.is_available() else 'cpu'\n#     }\n    \n#     print(f\"\\n{'='*80}\")\n#     print(\"PHASE 1: BASELINE CTC MODEL\")\n#     print(f\"{'='*80}\\n\")\n    \n#     # 1. Load data (assuming you have load_split function from previous scripts)\n#     print(\"Loading data...\")\n#     # Uncomment and use your data loading function:\n#     # from data_loader import load_split\n#     # train_data = load_split(CONFIG['data_dir'], 'train')\n#     # val_data = load_split(CONFIG['data_dir'], 'val')\n    \n#     # For now, assume data is loaded\n#     # train_data = {...}\n#     # val_data = {...}\n    \n#     print(\"✓ Data loaded\\n\")\n    \n#     # 2. Create datasets\n#     print(\"Creating datasets...\")\n#     train_dataset = BrainToTextDataset(train_data, normalize=True)\n#     val_dataset = BrainToTextDataset(val_data, char2idx=train_dataset.char2idx, normalize=True)\n    \n#     # 3. Create dataloaders\n#     train_loader = DataLoader(\n#         train_dataset,\n#         batch_size=CONFIG['batch_size'],\n#         shuffle=True,\n#         collate_fn=collate_fn,\n#         num_workers=4,\n#         pin_memory=True\n#     )\n    \n#     val_loader = DataLoader(\n#         val_dataset,\n#         batch_size=CONFIG['batch_size'],\n#         shuffle=False,\n#         collate_fn=collate_fn,\n#         num_workers=4,\n#         pin_memory=True\n#     )\n    \n#     print(\"✓ Dataloaders created\\n\")\n    \n#     # 4. Create model\n#     print(\"Creating model...\")\n#     model = BaselineCTCModel(\n#         input_dim=512,\n#         hidden_dim=CONFIG['hidden_dim'],\n#         num_layers=CONFIG['num_layers'],\n#         vocab_size=train_dataset.vocab_size,\n#         dropout=CONFIG['dropout']\n#     )\n    \n#     print(f\"✓ Model created\")\n#     print(f\"  Parameters: {sum(p.numel() for p in model.parameters()):,}\")\n#     print(f\"  Vocab size: {train_dataset.vocab_size}\\n\")\n    \n#     # 5. Create trainer\n#     trainer = Trainer(\n#         model=model,\n#         train_loader=train_loader,\n#         val_loader=val_loader,\n#         char2idx=train_dataset.char2idx,\n#         device=CONFIG['device']\n#     )\n    \n#     # 6. Train\n#     history = trainer.train(num_epochs=CONFIG['num_epochs'])\n    \n#     # 7. Save config\n#     with open('checkpoints/config.json', 'w') as f:\n#         json.dump(CONFIG, f, indent=2)\n    \n#     print(\"\\n✓ Training complete! Model saved to checkpoints/\")\n#     print(\"\\nNext: Move to Phase 2 (Train multiple models)\")\n\n\n# if __name__ == \"__main__\":\n#     # Install required package if needed\n#     # !pip install jiwer\n    \n#     main()\n\n\n# # ============================================================================\n# # 5. INFERENCE SCRIPT\n# # ============================================================================\n\n# def predict_test_set(model_path, test_data, device='cuda'):\n#     \"\"\"\n#     Make predictions on test set\n    \n#     Args:\n#         model_path: Path to saved model checkpoint\n#         test_data: Test data dictionary\n#         device: Device to run on\n    \n#     Returns:\n#         List of predictions\n#     \"\"\"\n#     # Load checkpoint\n#     checkpoint = torch.load(model_path, map_location=device)\n#     char2idx = checkpoint['char2idx']\n    \n#     # Create model\n#     model = BaselineCTCModel(\n#         input_dim=512,\n#         hidden_dim=512,\n#         num_layers=3,\n#         vocab_size=len(char2idx),\n#         dropout=0.3\n#     )\n#     model.load_state_dict(checkpoint['model_state_dict'])\n#     model = model.to(device)\n#     model.eval()\n    \n#     # Create dataset and loader\n#     test_dataset = BrainToTextDataset(test_data, char2idx=char2idx, normalize=True)\n#     test_loader = DataLoader(test_dataset, batch_size=32, collate_fn=collate_fn)\n    \n#     # Predict\n#     idx2char = {v: k for k, v in char2idx.items()}\n#     predictions = []\n    \n#     print(\"Predicting on test set...\")\n#     with torch.no_grad():\n#         for batch in tqdm(test_loader):\n#             neural = batch['neural'].to(device)\n#             lengths = batch['lengths']\n            \n#             # Forward pass\n#             log_probs = model(neural, lengths)\n            \n#             # Decode\n#             batch_preds = decode_batch(log_probs, lengths, idx2char)\n#             predictions.extend(batch_preds)\n    \n#     return predictions\n\n\n# def decode_batch(log_probs, lengths, idx2char):\n#     \"\"\"Helper function for decoding\"\"\"\n#     _, max_indices = log_probs.max(dim=-1)\n    \n#     predictions = []\n#     for b in range(max_indices.size(1)):\n#         seq = max_indices[:lengths[b], b].cpu().numpy()\n        \n#         decoded = []\n#         prev = None\n#         for token in seq:\n#             if token != 0 and token != prev:\n#                 decoded.append(idx2char.get(token, ''))\n#             prev = token\n        \n#         predictions.append(''.join(decoded))\n    \n#     return predictions\n\n\n# \"\"\"\n# ================================================================================\n# USAGE INSTRUCTIONS\n# ================================================================================\n\n# 1. TRAINING:\n#    python phase1_baseline.py\n   \n#    This will:\n#    - Load train/val data\n#    - Train for 50 epochs\n#    - Save best model to checkpoints/best_model.pt\n#    - Target: Beat 6.70% WER\n\n# 2. PREDICTION:\n#    predictions = predict_test_set('checkpoints/best_model.pt', test_data)\n   \n# 3. SUBMISSION:\n#    import pandas as pd\n#    submission = pd.DataFrame({\n#        'session': test_sessions,\n#        'block_num': test_blocks,\n#        'trial_num': test_trials,\n#        'sentence': predictions\n#    })\n#    submission.to_csv('submission.csv', index=False)\n\n# ================================================================================\n# \"\"\"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}