{"cells":[{"cell_type":"markdown","metadata":{},"source":"# Brain-to-Text '25: From Neural Signals to Text\n## End-to-End Baseline (Pytorch + CTC)\n\n**Author**: Antigravity  \n**Competition**: [Brain-to-text '25](https://www.kaggle.com/competitions/brain-to-text-25/data)\n\n### Overview\nThis notebook demonstrates a complete pipeline for decoding neural activity (intracortical microelectrode array recordings) into text. \nWe will:\n1. Load the HDF5 data.\n2. Preprocess neural features (normalization, padding).\n3. Train a Recurrent Neural Network (GRU) with CTC Loss.\n4. Evaluate using Word Error Rate (WER).\n5. Generate a submission file.\n\n### How to Run\n- **Environment**: Kaggle Notebook (GPU recommended) or Local Machine with Nvidia GPU.\n- **Dependencies**: `torch`, `h5py`, `jiwer`, `numpy`, `pandas`.\n- **Data**: Ensure `train.hdf5` and `test.hdf5` are in the working directory or linked.\n\n"},{"cell_type":"code","execution_count":21,"metadata":{},"outputs":[{"name":"stderr","output_type":"stream","text":["\n","[notice] A new release of pip is available: 25.1.1 -> 25.3\n","[notice] To update, run: python.exe -m pip install --upgrade pip\n"]}],"source":"# Install necessary libraries if not present\n!pip install -q jiwer tqdm\n"},{"cell_type":"code","execution_count":22,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Using device: cuda\n","GPU: NVIDIA GeForce RTX 4050 Laptop GPU\n"]}],"source":"import os\nimport random\nimport warnings\nimport numpy as np\nimport pandas as pd\nimport h5py\nimport matplotlib.pyplot as plt\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.nn.utils.rnn import pad_sequence\nimport jiwer\nfrom tqdm.auto import tqdm\n\nwarnings.filterwarnings('ignore')\n\n# Check device\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {DEVICE}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n"},{"cell_type":"markdown","metadata":{},"source":"## 1. Configuration & Reproducibility\nWe set random seeds to ensure our experiments are reproducible.\n"},{"cell_type":"code","execution_count":23,"metadata":{},"outputs":[],"source":"class Config:\n    SEED = 2\n    N_EPOCHS = 300              # More epochs, rely on early stopping\n    BATCH_SIZE = 16             # Smaller batch, will accumulate\n    GRAD_ACCUM_STEPS = 8        # Effective batch = 16*4 = 64\n    LEARNING_RATE = 1e-3        # Slightly lower for stability\n    WEIGHT_DECAY = 1e-4\n    CLIP_GRAD = 1.0             # Tighter clipping for stability\n    \n    # Model parameters (optimized for RTX 4050 6GB)\n    INPUT_SIZE = 512\n    HIDDEN_SIZE = 512           # Reduced to ensure no OOM\n    NUM_LAYERS = 6               # 4 layers is sufficient\n    DROPOUT = 0.4               # Slightly lower dropout\n    \n    # Training robustness\n    USE_AMP = True              # Mixed precision for speed + memory\n    PATIENCE = 30               # More patience for final run\n    \n    DATA_DIR = \"./\"\n    \ndef set_seed(seed):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed_all(seed)\n        torch.backends.cudnn.deterministic = False\n        torch.backends.cudnn.benchmark = True\n\nset_seed(Config.SEED)\ntorch.set_float32_matmul_precision('high')  # Use 'high' for better accuracy\n"},{"cell_type":"markdown","metadata":{},"source":"## 2. Data Loading & Preprocessing\nWe define a `CharTokenizer` to map characters to integers and back. \nThe `BrainSpeechDataset` class handles loading from HDF5 files. \nNeural data is normalized (Z-scored) to help training stability.\n"},{"cell_type":"code","execution_count":24,"metadata":{},"outputs":[],"source":"class CharTokenizer:\n    def __init__(self):\n        # We use ASCII values directly. \n        # CTC Blank will be 0.\n        # Characters are 1..127. (ASCII 0 is null, usually not used in text)\n        self.vocab_size = 128\n        \n    def encode(self, text):\n        # Convert string to ASCII ordinals\n        return [ord(c) for c in text]\n        \n    def decode(self, indices):\n        # Convert indices to string\n        # Ignore 0 (Blank) if it shows up in raw decode, usually handled by decode_ctc\n        return \"\".join([chr(x) for x in indices if x > 0])\n        \n    def decode_ctc(self, indices):\n        # CTC Greedy Decode\n        res = []\n        prev = -1\n        for idx in indices:\n            # 0 is blank\n            if idx != prev and idx != 0:\n                res.append(chr(idx))\n            prev = idx\n        return \"\".join(res)\n    \n    def beam_search_decode(self, log_probs, beam_width=10):\n        '''\n        Beam Search CTC Decoding\n        log_probs: (T, vocab_size) - log probabilities at each time step\n        Returns: best decoded string\n        '''\n        T, V = log_probs.shape\n        \n        # Each beam: (log_prob, prefix, last_char)\n        beams = [(0.0, [], -1)]  # Start with empty prefix\n        \n        for t in range(T):\n            new_beams = []\n            \n            for log_p, prefix, last_char in beams:\n                # Get top-k candidates at this time step\n                top_k = torch.topk(log_probs[t], min(beam_width, V))\n                \n                for i in range(len(top_k.indices)):\n                    char_idx = top_k.indices[i].item()\n                    char_prob = top_k.values[i].item()\n                    new_log_p = log_p + char_prob\n                    \n                    if char_idx == 0:  # Blank\n                        new_beams.append((new_log_p, prefix, -1))\n                    elif char_idx == last_char:  # Repeat\n                        new_beams.append((new_log_p, prefix, char_idx))\n                    else:  # New character\n                        new_beams.append((new_log_p, prefix + [char_idx], char_idx))\n            \n            # Keep top beams\n            beams = sorted(new_beams, key=lambda x: -x[0])[:beam_width]\n        \n        # Return best beam's prefix\n        best_prefix = beams[0][1]\n        return \"\".join([chr(c) for c in best_prefix if c > 0])\n        \n    def get_vocab_size(self):\n        return self.vocab_size\n"},{"cell_type":"code","execution_count":25,"metadata":{},"outputs":[],"source":"class BrainSpeechDataset(Dataset):\n    def __init__(self, root_dir, tokenizer, partition='train'):\n        self.root_dir = root_dir\n        self.tokenizer = tokenizer\n        self.partition = partition\n        self.files = []\n        self.file_indices = []\n        \n        file_pattern = f\"data_{partition}.hdf5\"\n        if not os.path.exists(root_dir):\n            print(f\"Directory {root_dir} not found.\")\n            return\n\n        for root, dirs, files in os.walk(root_dir):\n            if file_pattern in files:\n                self.files.append(os.path.join(root, file_pattern))\n                \n        self.files.sort()\n        \n        # Index keys\n        for f_path in self.files:\n            try:\n                with h5py.File(f_path, 'r') as f:\n                    keys = list(f.keys())\n                    for k in keys:\n                        self.file_indices.append((f_path, k))\n            except:\n                pass\n                \n    def __len__(self):\n        return len(self.file_indices)\n        \n    def __getitem__(self, idx):\n        f_path, key = self.file_indices[idx]\n        with h5py.File(f_path, 'r') as f:\n            group = f[key]\n            \n            if 'neural_features' in group:\n                neural = group['neural_features'][:]\n            elif 'input_features' in group:\n                neural = group['input_features'][:]\n            else:\n                # Should not happen if data is consistent, but safety\n                raise ValueError(f\"No neural data found in {key}\")\n            \n            neural = neural.astype(np.float32)\n            \n            if np.isnan(neural).any():\n                neural = np.nan_to_num(neural)\n            \n            # Z-score normalization\n            mean = neural.mean(axis=0, keepdims=True)\n            std = neural.std(axis=0, keepdims=True)\n            neural = (neural - mean) / (std + 1e-8)\n            \n            if self.partition == 'test':\n                 return torch.tensor(neural), key\n            else:\n                # Target: ASCII indices\n                if 'transcription' in group:\n                    target = group['transcription'][()]\n                    target = target.flatten()\n                    target = target[target != 0] # Filter blanks\n                elif 'sentence_label' in group:\n                     sentence = group['sentence_label'][()].decode('utf-8')\n                     target = self.tokenizer.encode(sentence)\n                else:\n                     target = np.array([], dtype=np.int32)\n                \n                # Sanity Check for CTC: Input Length must be >= Target Length\n                # Neural shape is (Time, Features)\n                if neural.shape[0] < len(target):\n                    # Warning: CTC Loss will return infinite/error for this sample.\n                    # We return None to filter it out in collate_fn\n                    return None\n                     \n                return torch.tensor(neural), torch.tensor(target, dtype=torch.long)\n\ndef collate_fn(batch):\n    # Filter out None samples (failed sanity checks)\n    batch = [b for b in batch if b is not None]\n    if len(batch) == 0:\n        return None\n        \n    # Separate inputs and targets\n    inputs = [x[0] for x in batch]\n    input_lengths = torch.tensor([x.shape[0] for x in inputs], dtype=torch.long)\n    \n    # Pad inputs\n    inputs_padded = pad_sequence(inputs, batch_first=True)\n    \n    # Check mode\n    if isinstance(batch[0][1], str):\n        # Test mode\n        keys = [x[1] for x in batch]\n        return inputs_padded, keys, input_lengths\n    else:\n        # Train mode\n        targets = [x[1] for x in batch]\n        target_lengths = torch.tensor([len(x) for x in targets], dtype=torch.long)\n        targets_padded = pad_sequence(targets, batch_first=True, padding_value=0)\n        return inputs_padded, targets_padded, input_lengths, target_lengths\n"},{"cell_type":"markdown","metadata":{},"source":"## 3. Exploratory Data Analysis (EDA)\nLet's inspect the data dimensions and visualize a sample neural recording.\n"},{"cell_type":"code","execution_count":26,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["============================================================\n","COMPREHENSIVE DATA ANALYSIS\n","============================================================\n","\n","Found 45 training data files\n"]},{"name":"stderr","output_type":"stream","text":["Scanning files: 100%|██████████| 45/45 [00:54<00:00,  1.20s/it]\n"]},{"name":"stdout","output_type":"stream","text":["\n","============================================================\n","DATA SUMMARY\n","============================================================\n","Total Samples: 8072\n","Skipped (missing features): 0\n","Feature Dimensions: {512}\n","\n","Input Lengths: min=138, max=2475, mean=874.8\n","Target Lengths: min=3, max=125, mean=31.2\n","\n","CTC Violations (input < target): 0 (0.00%)\n"]},{"data":{"image/png":"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jTWY9w70C0qD3uuvv15ee+01897RQC879957r6l5rIGhHkz9888/ZhtpIrNZs2by+OOPy+HDh+Xvv/8229Red17v5ZwcPHhQrrrqKrNdbrnlFnnvvfdMT2D9mzvvvLNArzE/bXP31VdfmZMgp512mjlo0AMi/SxfcsklZt94B9b5/YwAAICsNEGr8YfGadq78pNPPjHxl8Zu/fr181h248aNJi645557pE+fPiaZpQkuTf7t2rVLHnzwQZNk07JIS5YsyfJcelJWf+O1c4GeyNcrfuyk8bfffmtitS5dupjY8O233zZxgyZElXd8V1D5ee6Cxhea8NMYqUePHnLRRReZeLxTp04e68nP68lP3JtXLFhY2nZNjmqZAt2n2ckrDta4Xu8fMWKEibX1xLvSk/82ba9uf+1Uoz29q1Wrlmu79NhOk6KPPPKI7N2714yN0K5dO1OXtiC9u/PTNneamNXPgr5/e/fuba781BMQmnjXhLwdyxZk3wHwIQuA382aNUtPY1qrVq2y/vzzTys8PNx64IEHXI+3adPGaty4sev2X3/9ZYWFhVljx471WM+aNWvM37rfX7duXatnz55ZnlPXqZNtyZIlpg2nnXaadfz4cY9lU1NTrYyMDI/7kpKSrKioKGv06NEe9+k69PXkxn6uefPmue4bOXKkue/OO+/0WPb666+3EhISPO7T5XT66aefXPclJydb0dHRZnmbvm59/d7s53JXoUKFbLdTduzX+cwzz+S4zBtvvGGFhoZa3377rcf9M2bMMH/7/fffe7yeyMhIa8uWLa77fvvtN3P/1KlTXfddc801Vvny5a0dO3a47tu8ebPZ5/l9Pfndzs8995xZbt++fVZB6Tbv1KlTjo/b6/7oo49c9+ltbZstLi7O6tevX67Po8+R3f7N7b1sP6b/2/RzoPc9++yzrvvS0tKspk2bWlWrVrUcDofH51T3f17rzKlt2X1G7Of5559/PPa/vn9uv/32Qn1GAACAZWIJ7xjJOzZQHTp0MHGDO/0d179duHChx/0aL+j98+fPd9134sQJq0GDBh7xQGZmplW/fn2zbp13f/569epZV1xxhes+jSmzizFyojGexno5Kchz5ze+WL16tVluwIABHsv16tUrSxyX2+vJb9ybn1gwr+OqnOi6zzvvvByPDfITB+v6czrusWNLjfvzewx2yimn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5pprrPXr13ssU5DPCAAAsEx85R3zffzxx9Y555xjfu9PPfVUE6vNnDkzy2+p/nZ36tQp2824detW85j+bicmJppYTONyXcePP/7osewvv/xidenSxUpISDDxg673pptuMrGWuzFjxph4PTQ0NM/fdfv4ILtJ48OCPHdB4otjx46ZbRofH29VrFjR6ty5sznu0OWefvrpfL2e/MS9+Y0Fs2O32540jq1evbp1xRVXWM8//7yVkpKS5W+8jw3yGwd/9NFHVqNGjazw8HCP+M6OLbOT0zHY22+/bQ0dOtSqWrWqeV/p+ys5OTnL3+fnuC63tuk2tuNS25EjR6yBAwea1xkREWGOr5555hkrMzPTY7mCHLMA8I0Q/ac4ksEAUFz0TH6/fv2yXMYTrLRn77p167Kt8wsAAIDipz0jBw4caAa60l6twUKvTDrvvPNMj8/cSg4AAAqOmrYAUIropfPuNFGrIx5feumlfmsTAABAMMdjWi5La+HrAF1lOWHr/brtZLWWgmjdurVf2gQAZRk1bQGgFNE6wr169TL/60ixWqNXa7MNGTLE300DAAAICjoocJ06dczgVDpglvYy1fqpWtu2LJswYYKsXr3a1EANDw83dWN10vE0ateu7e/mAUCZQ9IWAEoRHXjg7bffNgN66QBsOnjVU089ZXp2AAAAoPjpYLg60JcmaXVg2EaNGpnBSW+++eYyvfl1QGIdzGvMmDFmEDdNXOugqTqIFQDA96hpCwAAgKChI4I/88wzpreYjhb/4Ycfmtrg9mjsw4YNM2Vntm7dKnFxcdKuXTt5+umnpWbNmq51HDhwQO6//34z+rpeFqwjqj///PNmhHPb77//buqv62jniYmJZnmuigAAAEB+UdMWAAAAQePYsWNy7rnnyrRp07I8dvz4cfn5559l+PDh5v8PPvhANm7cKNdee63HcjrYjg4AqT3OFixYYBLBenmwLSUlRdq3by9169Y1yWFNEmtvtJdffrlEXiMAAABKP3raAgAAICiFhIR49LTNjvaUveCCC0wdcb0UeMOGDeZSaL2/RYsWZpmFCxfKVVddZUaN1x65Wm9cLxfWUjZad1w9+uijMn/+fFP3EgAAACiTNW0zMzNl586dEhMTY4JtAAAAlA2WZcmRI0dM8lNLD/ibDjKk8WalSpXM7eXLl5t5O2GrtISCtnXFihVy/fXX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TWhpPKLrKGgsqjGfrkeTSNomTShrezRxptu5MIMtaTypxwF2AlVjQo1LdN+5XzGkyThNFut+1BhGn+vPP/80r19jPN1HGsvoe0b/XpNcShNx3rSjgCb1tEyFvh53+np1O+hrzI5uF91W+t6xE8YaZ+v6LrnkEpMk1fJr7vQ+TZBqEjAnetyj5TL0GCQ7mgj1jn812aftyIm+t/U16n7U7aHbTGN33cZars1+X2mMr58RjfE1LtbPj37GNBlpl7DLTyk7TfIePXrU7DfdfprodC8rp+8R5d0bV0+g6AkM+/GcaNyrnw2N4zV21s+sHs/pZ0WTlkpfn8bu+j2gxy8a0+rxm/6vV116J7dvvvlm89q0JMenn35qSjVozK8nXTRO122k+09Pspx//vlm+7jT955di1lLPOj7o127dqbURW5XDubn+0rXpzG9voe1/IUeq+hn84MPPsiyPv1M6/teX2d+jokBn7IAlBn9+vXTDJrr9vz5883tJ5980mO5G264wQoJCbG2bNniuk+X0+mnn35y3ZecnGxFR0db119/fb6e/+2333atR6cWLVpYv//+u8cyFSpUsO68884sf/vpp5+av1m4cGGO63/++efNMh9++GG+2jNr1iyzfLt27azMzEzX/QMHDrTCwsKsQ4cOue47fvx4lr+/5557rPLly1upqamu+9q0aWPWOWPGjCzL52cd6enpVr169ay6detaBw8e9FjWvY3e+9L27bffmvvfeustj/t1u3nfr8+R1zZ117NnT4/9Z0/6mtXIkSPN7VtuucXj7/766y+zPceOHetx/5o1a6zw8HDX/Q6Hw6patarVtGlTKy0tzbXcyy+/7PE87vsuKSnJY51Lliwx9+v/9jarX7++1aFDB4/tp/tCt/MVV1zhus9uv/f7T9/fCQkJrtubN2+2QkNDzf0ZGRkey7o/h7bXvc1vvPGG+TvdR+70vaLP+/3335vbzz33nLm9b9++bPcDAADFwf5tXbVqlfXCCy9YMTExrtjlxhtvtNq2beuKHzp16lSo2KOg8dScOXNc92lsUL16datr1665vg6N4/Rvf/nll3y97vz+/ufUfo0xTjvtNI/77Bhr2bJlrvv27t1rRUVFWQ899FCBY9EjR45YlSpVsvr06ePxPLt377bi4uI87rdfT35MnDjRKleunJWSkmJub9q0KdtYunXr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nVaaNWtWp/3S7dLtf/vb32b71NbWljbaaKPSrbfe+rmPe//995f22GOP0sorr5zdbvHFFy9tvPHGpTPOOKP08ccfz7H/22+/XTrggANKyyyzTGmxxRYrbbvtttl9zI+rr766tOOOO5aWXnrpUu/evUvLL798ac899yxNmzatY59LL700e16fvu/0nNL22Z/bnXfeWfrKV75SWnTRRbO+OuaYY0p//vOf59gvxbruuut2GdO83kdyxx13lHbYYYfSkksumfXXBhtsUDr77LM7rv/kk09KP/rRj0rLLrtsqUePHnO8rhdeeGFpk002yR4r3cf666+fPd7LL7/csc8qq6xS2mmnnea5T/fdd98slrkZO3ZsFscbb7zR5fXXXHNNaeutt87uI7W11lorO6amT5/eab9f/epXpVVXXTU7VjbddNPSX//616xfU5vds88+Wxo2bFi2X//+/UvHH3986eabb+6yPx9++OHSt771reyYSvun5/6d73ynNHXq1M+Nv/04ef755zttv+SSS7L3QLq/pZZaKosvPX67rmKeMWNG6ec//3l2jLTfLr1OJ510UqmlpSXbJ8W06667ZsdInz59sn9HjBhRevrpp+fa9wDwZc1tXPRpcxs/zMvY48uOp9JYJD3+vEhjpYsvvrj01a9+tVRfX5+Nc9Nt99tvv2xc8EX+/3/99ddnY7JFFlmkNHDgwOz/6Wk88On95tZHnx4bzM9YtH378OHDs+eTYlhttdVKo0aNKj3wwANzPJ+5SWORNB5K/fJZ0lgsjXPaPfbYY6Xdd9+91Ldv3+yxBw8eXPrJT37S6TYnn3xyacUVVyz17NmzU5+k/kiv3af94x//yPZLLY19P62r1+DVV1/N+jYdY+m6T4+1knTspBheeuml0rx66KGHsvu7/fbbO23/rLH93Pq7/XvT3L6fLLfcctkYL303+sEPflB65ZVXOu130UUXlQYNGlTq1atXl8fB7H784x+XhgwZ0nGMp/s86KCDsn7qypQpU7L90zh0pZVWKp1wwgnZMfF5/vu//7u02WabZa9/ev+mcfz//M//dLpt6u/2YyTFk77/pfd/eg6pnz7vPTe37xqffg3a3x+TJ08uNTY2Zv2ZYkrHxT//+c95+sz4vM+rdDyk8Xf799f0GDvvvHOn91ry5JNPZrH87//+7+f2ISwMPdJ/8kkXA0WS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4/DDD8/joQEAgCrjy3r+vAZAUfl8oqhyKY+wwDSNjKityTsKqs24KXlHAADAQqCcANDdJAyBuVEeAQAAyIVkBQBAwZK2s2bNimOOOSaWXnrpGDBgQIwbNy6vUAAAgComeQyUO59jUHlyS9pOnDgxFl988bj33nvj1FNPjZ/+9Kdx88035xUOAAAAAEB117TdYIMNYuzYsdnfa6yxRpxzzjkxderU2GGHHebYt62tLWvtWltbuzVWAAAAAICKn2mbkrazW3755eP111/vct+mpqaor6/vaA0NDd0UJQAA0M7ptwDF4nMZKlduSduamppOl3v06JHVue1KY2NjtLS0dLTm5uZuihIAAIAikJwC7wWoJrmVR5gftbW1WQMAAOgOEoQA+P8GVTnTFgAAAKCa+EFIX1T7cVDNz31+SdoCAADwuXzRBoAKL48wbdq0ObZdd911839HjZMi6uoWTFAAAAAAQKFNrpLZumbaAgAA3a5avnABAN1vcgWMM8piIbK5ahoZUVuTdxRUk3FT8o4AAAAAgApnpi0AAAAAFa0SZl5Wk8nd9HoV+biQtAUAAADKOrkBUGmfZ7kmba+++upYf/31Y9FFF41lllkmhg0bFh988EGeIQEAAFTMFz4AoDzlVtP2lVdeiREjRsSpp54au+++e7z33ntx++23R6lUmmPftra2rLVrbW3t5mgBAAAAAKogafvJJ5/Et771rVhllVWybWnWbVeampripJNO6uYIAQAAAACqqDzChhtuGNtvv32WqN1jjz3ioosuinfeeafLfRsbG6OlpaWjNTc3d3u8AAAAAAAVnbTt1atX3HzzzXHjjTfGOuusE2effXYMHjw4nn/++Tn2ra2tjbq6uk4NAAAAoGiKWO+6iDEBBV6IrEePHrHVVltlpQ8efvjh6NOnT0yZMiXPkAAAAAAAqrOm7b333htTp06NHXfcMZZbbrns8htvvBFrr712XiEBAAAFY3YYAFS3yVU6Uzy3pG0qcfDXv/41xo8fH62trdliZL/4xS/i61//+rzfSeOkdEcLM0wAAAAAgOpI2qYZtTfddFNeDw8AAAAAUEi51rQFAAAAAKAgM20XiKaREbU1eUdBORlnoTsAAACg2IpQx7UIMVSzws20nTFjRt4hAAAAMBtf3Km0Y9kxDRRd7jNthw4dGuutt1707t07fvvb38b6668f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AKDIcqlpu+iii8bIkSPj0ksv7dj229/+NlZeeeUYOnToHPs3NTVltcfaW0NDQzdHDABAOdl3330lbKuAZDIAUKlyW4hs9OjR2azaf/3rX9nlCRMmxKhRo7pcMKKxsTFaWlo6WnNzcw4RAwBQZEsvvXS8+eab2d9LLbVUdnluDQDy4gcnoNALkaVFIjbccMOsvu2OO+4Yjz/+eFYeoSup7u3stW8BAODTzjzzzGx9hPa/u5oMAAAA5SC3pG3yve99L8aPH5/Nth02bJiyBwAAfKmSCO3SGVwAAFCuciuPkKS6ti+99FJcdNFFsf/+++cZCgAAFSRNCEjltyxgC5QTp80DUIiZtmlRsW9/+9tZWYTddttt/u+gcVJEXd3CCA0AgDK27rrrZusi/PCHP4yddtop9t577/jGN74RNTU1eYcGAADFnmmbpNIIe+21l5q1AAAsMGeddVY2zrzuuuti8cUXj3322Sf69+8f3//+9+O2227T0wAAFFpuSdt33nknpkyZEtOmTYuDDz44rzAAAKhQPXv2zBa8TWUSXnvttbjgggvivvvui+222y7v0AAAoJjlETbaaKMscfvzn/88Bg8e/MXupGlkRK1T3BaacVMW3n0DAHSTV199Na644or47W9/G3//+99js8020/dUBPVPAaBy5Za0feGFF/J6aAAAKlxagOyaa66JSZMmZWd2DRo0KCvJdeWVV8Zqq62Wd3gAAFC88ggDBw6M8ePHd9o2ZMiQGDduXB7hAABQYVL92h//+Mex3nrrxd133x3Tp0+PE088UcIWAICykNtMWwAAWFiuv/762H777bO6tgAAUG7KImnb1taWtdlPdwMAgLnZYYcddA4AAGWrLJK2TU1NcdJJJ+UdBgAABbbxxhvH1KlTY6mllsoWve3Ro8dc933ooYe6NTYAACh80jadplYqlTpt+/jjj+e6f2NjY4wZM6bTTNuGhoaFGiMAAOVl1113jdra2o6/PytpCwAARZZL0nbZZZeNV155pVMS9vnnn5/r/mnw3T4ABwCArowdO7bjbwvcAgBQznJZmWG77baLyy67LG6//fZ49NFHY999941evXrlEQoAABVo0KBB8dZbb82x/d13382uAwCAIstlpm0qd5Bm1u68885RX18fJ5988mfOtAUAgPnxwgsvxMyZM+fYnha3femll3QmAACFlkvStq6uLq644opO29Js2/nWOCnd2YILDACAsnb99dd3/P3nP/85myDQLiVx00Jlq666ak7RQWWavMvkGPGHEXmHAQAVJZekLQAALAy77bZb9m9ahOzTkwJqampi4MCB8Ytf/ELnAwBQaIVJ2g4dOjSGDBkS48ePn/cbNY2MqK1ZmGFVr3FT8o4AAGC+zZo1K/s3zaa9//77o1+/fnoRAICyU5ikLQAALCjWSwAAoJz1zDsAAABY0A499ND45S9/Ocf2c845Jw4//HAdDgBAoeWStP3ggw9in332iSWWWCKWX355dcUAAFigrrnmmthqq63m2L7lllvG1VdfrbcBACi0XJK2Rx99dNx2223x+9//Pv7yl7/EtGnT4qGHHprr/m1tbdHa2tqpAQDA3Lz11ltRX18/x/a6urp48803dRwAAIXW7Unb999/P37961/H6aefHttvv32sv/76MXHixPjkk0/mepumpqZs0N3eGhoaujVmAADKy+qrrx433XTTHNtvvPHGGDRoUC4xAQBAYRcie/bZZ2PGjBmx+eabd2xbeumlY/DgwXO9TWNjY4wZM6bjcpppK3ELAMDcpLHjIYccEm+88UZst9122bapU6dmZbnGjx+v4wAAKLRuT9p+EbW1tVkDAIB5sf/++2cltv7nf/4nTj755GzbwIED47zzzsvWVgAAgCLr9vIIq622WtTU1MS9997bse2dd96Jp59+urtDAQCggh100EHx0ksvxWuvvZadqfXcc89lCdu3334779AAAKBYSdslllgiDjjggGwxsltuuSUee+yxGDVqVPTsmcuaaAAAVLhll102G4OmBXC/853vxIorrph3SFBVJu8yOe8QAKDs5JIpPe200+KrX/1q7LLLLjFs2LDYeuutY5NNNskjFAAAKtg///nPGDt2bFYaYY899sgmCvzmN7/JOywqkMQk4DMEKPuatmmmw2WXXZa1dmnm7XxrnBRRV7dggwMAoKylRW+vvfbauPjii+POO+/MJgmkMgkPP/xwrL/++nmHBwAAn0tNAgAAKsaPfvSjWGGFFeKss86K3XffPUvW/uEPf4gePXpEr1698g4PgDJh9jxQlTNtF5imkRG1NXlHURzjpuQdAQBArs4777w49thj47jjjosll1zSq0HFklACgMpmpi0AABUjld+67777Yvnll48999wz/vjHP8bMmTPzDgsAAOaLpC0AABVjxIgRcfPNN8ejjz4aa621Vhx88MExYMCAmDVrVjzxxBN5hwcAAMVI2g4dOjSrLXb44YfHUkstFf3794+LLrooPvjgg9hvv/2y09ZWX331uPHGGxd2KAAAVIlVV101TjrppHjhhRfit7/9bXz729+OvffeO1ZaaaU49NBD8w4PAADyn2k7ceLE6NevX3aqWkrgHnTQQbHHHnvElltuGQ899FDsuOOO8d3vfjc+/PDDLm/f1tYWra2tnRoAAHyetADZ8OHD46qrroqXX345jjrqqLjtttt0HAAAhdYtSdsNN9wwTjjhhFhjjTWisbExFllkkSyJO3r06GzbiSeeGG+99Vb8/e9/7/L2TU1NUV9f39EaGhq6I2wAACrI0ksvnZ399be//S3vUKDiWBgNABas3tENNthgg46/e/XqFcsss0ysv/76HdtSyYTk9ddf7/L2KdE7ZsyYjstppq3ELQAAs3vxxRe/UIf07ds36urqdCYAANWVtK2pqZnjNLXZt6XLSVogoiu1tbVZAwCAuRk4cGA2riyVSvPcSWn/sWPHZmd+AQBAVSVtAQBgYZvbBAAAACg33VLTFgAAAACAaphp2zgpQv0xAAAAAKCCLPSk7bRp0+bY9sILL8yxbX5qjwEAAAAAVCrlEQAAAAAACqS8yyM0jYyorYmqNG5K3hEAAAAAAAuBmbYAAAAAANU+0/a9996LAw88MK677rqoq6uLY445Jn7/+9/HkCFDYvz48XmEBABABZk5c2ZMmDAhpk6dGq+//nrMmjWr0/W33HJLbrEBAEAhk7ZjxoyJO++8M66//vro379/nHjiifHQQw9lSVsAAPiyDjvssCxpu9NOO8V6660XPXr00KkAAJSN3nnMsp04cWJMmjQptt9++2zbpZdeGiussMJcb9PW1pa1dq2trd0SKwAA5emKK66Iq666Kr7xjW/kHQoU3uRdJucdAgCQd03b5557Lj7++OPYbLPNOrbV19fH4MGD53qbpqambJ/21tDQ0E3RAgBQjvr06ROrr7563mFQRiQuwfsKoEjKYiGyxsbGaGlp6WjNzc15hwQAQIEdeeSRcdZZZ0WpVMo7FAAAKH55hEGDBkVNTU3cf//9sfLKK2fbUiL26aefjm222abL29TW1mYNAADmxR133BG33npr3HjjjbHuuutm48/ZXXvttToSAIDC6vak7ZJLLhn77rtvHH300bH00kvHcsstF2PHjo2ePXtaIAIAgAWib9++sfvuu+tNAADKUrcnbZMzzjgjDjzwwNh5552jrq4ujjnmmKzkwSKLLJJHOAAAVJi00C2gVi8AlKtckrZptu3ll1/ecfmDDz6Ik046Kb7//e/P3x01Toqoq1vwAQIAwKece+65cdppp8Wrr74aG264YZx99tmdFtedmyuuuCJGjBgRu+66a1x33XX6FQCAYiZtH3744XjqqaeyQW6qZ/vTn/40254GsgAAsCBcffXVcdVVV8WLL74YM2bM6HTdQw89NF/3deWVV8aYMWPi/PPPj8033zzGjx8fw4cPj+nTp2flvubmhRdeiKOOOiq++tWvfuHnAQBA9cklaZucfvrp2SC3T58+sckmm8Ttt98e/fr1m787aRoZUdt5UYmKN25K3hEAABTeL3/5y/jxj38co0aNit///vex3377xbPPPpsthnvwwQd/ofJeo0ePzu4nScnbG264IS655JI47rjjurzNzJkzY6+99srOKEtj3XffffdLPy8AAKpDLknbjTbaKB588ME8HhoAgCrwq1/9Ki688MKsLMGECROyNRQGDRoUJ554Yrz99tvzdV9plm4auzY2NnZsS4voDhs2LO6+++653i6dTZZm4R5wwAFZ0vaztLW1Za1da2vrfMUIAEBl6Zl3AAAAsKClkghbbrll9veiiy4a7733Xvb3d7/73Zg8efJ83debb76ZzZrt379/p+3pcqpv25U77rgjfv3rX8dFF100T4/R1NQU9fX1Ha2hoWG+YgQAoLLkmrSdNWtWnHrqqbH66qtHbW1trLzyyvE///M/eYYEAEAFGDBgQMeM2jTGvOeee7K/n3/++SiVSgv1sVOCOCWHU8J2Xst/pVm8aa2H9tbc3LxQYwQAoNhyq2nbPjhNg9kzzzwztt5663jllVeyBco+zeliAADMj+222y6uv/76rCxXqkN7xBFHZAuTPfDAA/Gtb31rvu4rJV579eoVr732Wqft6XJKDn9aqp2bFiDbZZddOk1WSHr37p2t67Daaqt1uk2awJAaAABk48a8uiHNQDjrrLPinHPOiX333TfblgavKXnb1eliaQEHAACYF6mebXuiNC08tswyy8Rdd90V3/zmN+MHP/jBfHVi+8K5U6dOjd122y3blu47XT7kkEPm2H+ttdaKRx99tNO2E044oWP8q/QBAACFTdo++eST2Qza7bfffp5m5I4ZM6bTwgwGuwAAzE1aKCy1dv/1X/+VtS8qjUXTRINNN900Nttssxg/fnx88MEH2SzeZJ999okVV1wxm2ywyCKLxHrrrdfp9n379s3+/fR2AAAoVNI2LQgxr5wuBgDA/Lr99tvjggsuyMoVpNIIKal62WWXxaqrrtrl2V2fZc8994w33ngjTjzxxGzxsSFDhsRNN93UsThZWvhs9iQxAAB8GbmNLNdYY40scZtOKwMAgAXpmmuuieHDh2fjzYcffjg7wytJi3z97Gc/+0L3mUoh/POf/8zu6957743NN9+847pp06bFhAkT5nrbdN111133hR4XAIDqk1vSNp02duyxx8YxxxwTv/nNb7IZEGlV31//+td5hQQAQIX47//+7zj//POzRW9ramo6tm+11Vbx0EMP5RobAAAUtjxC8pOf/CRbQTedZvbyyy/H8ssvHwceeOC830HjpIi6uoUZIgAAZWj69OmxzTbbzLG9vr4+3n333VxiAgCAskjaprpfP/7xj7MGAAALyoABA+KZZ56JgQMHdtp+xx13xKBBg3Q0AACFZrUEAAAqzujRo+Owww7Las/26NEjO6vr8ssvj6OOOioOOuigvMMDAIDizrT90ppGRtT+X42yijRuSt4RAACUneOOOy5mzZoV22+/fXz44YdZqYTa2tosafujH/0o7/AAAKCCk7YAANCFNLs2leA6+uijszIJ77//fqyzzjqxxBJL6C8AAAqv28oj/PGPf4y+ffvGzJkzs8uPPPJINphOsyDafe9734u99967u0ICAKDC9enTJ0vWbrbZZhK2AACUjW6bafvVr3413nvvvXj44Ydj0003jdtuuy369esX06ZN69gnbTv22GO7KyQAACrM/vvvP0/7XXLJJQs9FgAAKHzStr6+PoYMGZIlaVPSNv17xBFHxEknnZSdrtbS0pKdurbtttvOcdu2trastWttbe2usAEAKCMTJkyIVVZZJTbaaKMolUp5hwMAAMWvaZsSsilZe+SRR8btt98eTU1NcdVVV8Udd9wRb7/9dqywwgqxxhprzHG7tF9K7gIAwGc56KCDYvLkyfH888/Hfvvtl5XeWnrppXUaAABlpdtq2iZDhw7NErR/+9vfoqamJtZaa61sW0rkptIIXc2yTRobG7OZuO2tubm5O8MGAKBMnHvuufHKK6/EMcccE3/4wx+ioaEhvvOd78Sf//xnM28BACgb3Zq0ba9re+aZZ3YkaNuTtqmlv7tSW1sbdXV1nRoAAMxt7DhixIi4+eab44knnoh11103fvjDH8bAgQOzslwAAFB03Zq0XWqppWKDDTaIyy+/vCNBu80228RDDz0UTz/99Fxn2gIAwBfRs2fP6NGjRzbLdubMmToRAICy0K1J2yQlZtOAuT1pm2qMrbPOOjFgwIAYPHhwd4cDAECFSQvYprq2O+ywQ6y55prx6KOPxjnnnBMvvvhiLLHEEnmHBwAAxVqILBk/fnzWZvfII498sTtrnBShVAIAAP9/qQzCFVdckdWy3X///bPkbb9+/fQPAABlpduTtgAAsLCcf/75sfLKK8egQYOyhW5T68q1117rRQAAoLAKkbRNNcZ+8IMfxNVXXx3vvPNOPPzwwzFkyJDPv2HTyIjamihb46bkHQEAQEXZZ599shq2AABQzgqRtL3ppptiwoQJMW3atGxWhFPYAAD4ItKYEgAAyl0hkrbPPvtsLL/88rHlllvmHQoAAAAAQHUnbUeNGhUTJ07M/k6nsq2yyirxwgsv5B0WAAAAAEB1Jm3POuusWG211eLCCy+M+++/P3r16jXHPm1tbVlr19ra2s1RAgAAAAB0j56Rs/r6+lhyySWzZO2AAQNi2WWXnWOfpqambL/21tDQkEusAAAAAAAVn7SdF42NjdHS0tLRmpub8w4JAAAAAKAyyyPMi9ra2qwBAAAAAFS6sphpCwAAAABQLSRtAQAAAAAKpCzKI8xV46SIurq8owAAAAAAqKyZtocffni88MILeYcBAAAAAJC7QiRtAQAAAAAoWHmEUaNGxbvvvhvXXXfdvN+oaWREbU2UhXFT8o4AAAAAACgDZtoCAAAAABSIpC0AAAAAQIFI2gIAAAAAFEhhatp+lra2tqy1a21tzTUeAAAAAICqnmnb1NQU9fX1Ha2hoSHvkAAAAAAAqjdp29jYGC0tLR2tubk575AAAAAAAKq3PEJtbW3WAAAAytnkXSbnHQIAUAbKYqYtAAAAAEC1kLQFAAAAACiQwpRHmDBhwvzfqHFSRF3dwggHAAAAACAXZtoCAAAAABRIYWbafiFNIyNqa6Kwxk3JOwIAAAAAoMyYaQsAAAAAUCCStgAAAAAA1V4eYejQobHeeutlf1922WVRU1MTBx10UPz0pz+NHj165BESAAAAzLfJu0zWawBUzkzbiRMnRu/eveO+++6Ls846K84444y4+OKLu9y3ra0tWltbOzUAAAAAgEqUW9K2oaEhzjzzzBg8eHDstdde8aMf/Si73JWmpqaor6/vaOm2AAAAAACVKLek7Ve+8pVOpRC22GKL+Mc//hEzZ86cY9/GxsZoaWnpaM3Nzd0cLQAAAABABde0nV+1tbVZAwAAAACodLnNtL333ns7Xb7nnntijTXWiF69euUVEgAAAABA9SZtX3zxxRgzZkxMnz49Jk+eHGeffXYcdthheYUDAAAwV5N3mVyI3ilKHABAhZZH2GeffeKjjz6KzTbbLJtdmxK23//+9+fvThonRdTVLawQAQAAAACqJ2lbU1MT48ePj/POOy+vEAAAAAAACie38ggAAAAAABRopu0C0TQyorYmCmHclLwjAAAAAAAqQC5J22nTpuXxsAAAAAAAhac8AgAAwFxM3mWyvgEAqiNpe+GFF8YKK6wQs2bN6rR91113jf333z+PkAAAAKgykvIAFFUuSds99tgj3nrrrbj11ls7tr399ttx0003xV577TXH/m1tbdHa2tqpAQAAAABUolyStksttVR8/etfj0mTJnVsu/rqq6Nfv37xn//5n3Ps39TUFPX19R2toaGhmyMGAAAAAKjwmrZpRu0111yTzaJNLr/88viv//qv6NlzzpAaGxujpaWlozU3N+cQMQAAAADAwtc7crLLLrtEqVSKG264If7jP/4jbr/99jjzzDO73Le2tjZrAAAAAACVLrek7SKLLBLf+ta3shm2zzzzTAwePDg23njjvMIBAABgIbLoFwCUQdK2vUTCzjvvHI8//njsvffeeYYCAAAAAFAIuSZtt9tuu1h66aVj+vTpMXLkyPm/g8ZJEXV1CyM0AAAAAIDqS9qmRcdefvnlPEMAAAAAACiUnnk86NChQ+Pwww/P46EBAAAAAAot15m2X1rTyIjamryjiBg3Je8IAAAAAIAKkctMWwAAAAAACpa0/eSTT+KQQw6J+vr66NevX/zkJz+JUqmUVzgAAAAAANWdtJ04cWL07t077rvvvjjrrLPijDPOiIsvvjivcAAAAAAAqrumbUNDQ5x55pnRo0ePGDx4cDz66KPZ5dGjR8+xb1tbW9batba2dnO0AAAAAAAVPtP2K1/5SpawbbfFFlvEP/7xj5g5c+Yc+zY1NWVlFNpbSvgCAAAAAFSisliIrLGxMVpaWjpac3Nz3iEBAAAAAFRWeYR777230+V77rkn1lhjjejVq9cc+9bW1mYNAAAAAKDS5TbT9sUXX4wxY8bE9OnTY/LkyXH22WfHYYcdllc4AAAAAADVPdN2n332iY8++ig222yzbHZtSth+//vfzyscAAAAAIDqTdpOmzat4+/zzjvvi99R46SIuroFExQAAAAAQAGUxUJkAACQt3PPPTcGDhwYiyyySGy++eZx3333zXXfiy66KL761a/GUkstlbVhw4Z95v4AAFCI8ghDhw6NIUOGxPjx47/4nTSNjKitiVyMm5LP4wIA0O2uvPLKbD2G888/P0vYpjHs8OHDs/UZlltuuS7PLBsxYkRsueWWWZL35z//eey4447x+OOPx4orrugVBADgM5lpCwAAn+OMM86I0aNHx3777RfrrLNOlrxdbLHF4pJLLuly/8svvzx++MMfZpMU1lprrbj44otj1qxZMXXqVH0NAMDnkrQFAIDPMGPGjHjwwQezEgcdg+iePbPLd9999zz13Ycffhgff/xxLL300l1e39bWFq2trZ0aAADVqzBJ2xtuuCHq6+uzWQkAAFAUb775ZsycOTP69+/faXu6/Oqrr87TfRx77LGxwgordEr8zq6pqSkbC7e3hoaGBRI7AADlqRBJ20mTJmU1v1LCdq+99prjejMPAAAoV6ecckpcccUVMWXKlKy+bVcaGxujpaWlozU3N3d7nAAAFEfPIqzCm+p9/eEPf4idd965y33MPAAAIC/9+vWLXr16xWuvvdZpe7o8YMCAz7zt6aefniVt//KXv8QGG2ww1/1qa2ujrq6uUwMAoHrlmrS9+uqr44gjjoibb745tt1227nuZ+YBAAB56dOnT2yyySadFhFrX1Rsiy22mOvtTj311Dj55JPjpptuik033bSbogUAoBLkmrTdaKONYtlll81W3S2VSnPdz8wDAADyNGbMmLjoooti4sSJ8eSTT8ZBBx0UH3zwQey3337Z9fvss0820aDdz3/+8/jJT36SjXMHDhyY1b5N7f3338/xWQAAUC565/ngq622WvziF7+IoUOHZqecnXPOOXmGAwAAXdpzzz3j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adKkOOGEE+L444+PNdZYI6677rpYb7319DUAAJWZtE2njo0dO3aOU8jQN44b7ymfNz6H8+b/UfrGcVO5UimEuZVDmDZt2hzb9thjj6yVE59h5c3rV/68huXN61f+vIblrbbC8oU90mpkeQcBAAAAAMD/83/ncAEAAAAAkDtJWwAAAACAApG0BQAAAAAokLJM2p577rkxcODAWGSRRWLzzTeP++67LyrJuHHjokePHp3aWmut1XH9v//97zj44INjmWWWiSWWWCK+/e1vx2uvvdbpPtIKxjvttFMstthisdxyy8XRRx8dn3zyyRwLZmy88cZZgebVV189JkyYEEXy17/+NXbZZZdYYYUVsj5IKy7PLpVjTis4L7/88rHooovGsGHD4h//+Eenfd5+++3Ya6+9oq6uLvr27RsHHHBAvP/++532+fvf/x5f/epXs+OpoaEhTj311Dli+d3vfpe9Bmmf9ddfP/70pz9Fkftm1KhRcxxDX/va16qib5qamuI//uM/Yskll8yO/d122y2mT5/eaZ/ufA8V6fNqXvpm6NChcxw7Bx54YEX3zXnnnRcbbLBB9l5IbYsttogbb7wxqv14mZe+qcbjZW5OOeWU7PkffvjhHduq+dihPDmOiqu7xsWU//iU8h0rUv5jNso7R1ZIpTJzxRVXlPr06VO65JJLSo8//nhp9OjRpb59+5Zee+21UqUYO3Zsad111y298sorHe2NN97ouP7AAw8sNTQ0lKZOnVp64IEHSl/5yldKW265Zcf1n3zySWm99dYrDRs2rPTwww+X/vSnP5X69etXamxs7NjnueeeKy222GKlMWPGlJ544onS2WefXerVq1fppptuKhVFivvHP/5x6dprr02L5ZWmTJnS6fpTTjmlVF9fX7ruuutKf/vb30rf/OY3S6uuumrpo48+6tjna1/7WmnDDTcs3XPPPaXbb7+9tPrqq5dGjBjRcX1LS0upf//+pb322qv02GOPlSZPnlxadNFFSxdccEHHPnfeeWfWN6eeemrWVyeccEKppqam9Oijj5aK2jf77rtv9txnP4befvvtTvtUat8MHz68dOmll2YxP/LII6VvfOMbpZVXXrn0/vvvd/t7qGifV/PSN9tuu20W5+zHTjoWKrlvrr/++tINN9xQevrpp0vTp08vHX/88dlxnPqpmo+XeembajxeunLfffeVBg4cWNpggw1Khx12WMf2aj52KD+Oo2LrjnEx5T8+pXzHipT/mI3yzpEVVdklbTfbbLPSwQcf3HF55syZpRVWWKHU1NRUqqQDMg2ouvLuu+9m/4P43e9+17HtySefzAZvd999d3Y5HYA9e/Ysvfrqqx37nHfeeaW6urpSW1tbdvmYY47JDvrZ7bnnntmAoog+PTidNWtWacCAAaXTTjutU9/U1tZmycUkfblNt7v//vs79rnxxhtLPXr0KP3rX//KLv/qV78qLbXUUh39khx77LGlwYMHd1z+zne+U9ppp506xbP55puXfvCDH5SKYG5J21133XWut6mWvklef/317Lnedttt3f4eKvrn1af7pj0JN/sA5tOqpW/SsX/xxRc7Xj6jbxLHS6n03nvvldZYY43SzTff3Kk/fNZQbor+uczCHxdT/uNTynesSPmP2SjvHFlRlVV5hBkzZsSDDz6Yne7TrmfPntnlu+++OypJOp0pnf40aNCg7DSmNJU7Sc//448/7tQHaVr4yiuv3NEH6d90mnr//v079hk+fHi0trbG448/3rHP7PfRvk+59OPzzz8fr776aqfnUF9fn50SOns/pFO/Nt1004590v7pmLn33ns79tlmm22iT58+nfohna70zjvvlHVfpdNp07T/wYMHx0EHHRRvvfVWx3XV1DctLS3Zv0svvXS3vofK4fPq033T7vLLL49+/frFeuutF42NjfHhhx92XFfpfTNz5sy44oor4oMPPshOfXO8zL1v2lXz8ZKkU7HSqVaffg6OHcpJ0d9ndM+4mPIfn1K+Y0XKf8xGeefIiqp3lJE333wz+5CcvaOTdPmpp56KSpEGWKleXUq2vfLKK3HSSSdldUUfe+yxbECWkmhp0PXpPkjXJenfrvqo/brP2icdtB999FFWC6vI2p9HV89h9ueYkpaz6927dzY4mn2fVVdddY77aL9uqaWWmmtftd9HEaX6td/61rey5/bss8/G8ccfH1//+tezD6tevXpVTd/MmjUrq1W01VZbZQmlpLveQymxXeTPq676Jhk5cmSsssoq2f8QU03jY489NkvUX3vttRXdN48++mg28E71kFIdpClTpsQ666wTjzzySNUfL3Prm2o+XtqlL20PPfRQ3H///XNc57OGclItY+xKtaDGxZT/+JTyHStS/mM2yjtHVlRllbStFim51i4VPE8HaPpSfNVVVxU+mUox/Nd//VfH3+kXpXQcrbbaatns2+233z6q6dfU9EF+xx135B1K2fTN97///U7HTlrQJB0zKfmfjqFKlQYAadCdZr5cffXVse+++8Ztt92Wd1iF7pv0RaVaj5ekubk5DjvssLj55puzxb8AYF4Yn5YnY8XyZcxW/r5exTmysiqPkE6/TLMEP70KXLo8YMCAqFTpF4M111wznnnmmex5plPY3n333bn2Qfq3qz5qv+6z9kmrYZbDQd/+PD7rWEj/vv76652uT6sDppVzF0RfldMxl04hSO+fdAxVS98ccsgh8cc//jFuvfXWWGmllTq2d9d7qMifV3Prm66k/yEmsx87ldg36dfZ1VdfPTbZZJNshecNN9wwzjrrLMfLZ/RNNR8v7adipc/RjTfeOJutllpKZv/yl7/M/k6/3lf7Zw3lw3FU3hbUuJjyH59SvmNFyn/MRnnnyIqqZ7l9UKYPyalTp3Y6vSRdnr2+XqV5//33s1lLaQZTev41NTWd+iCdiprqebT3Qfo3nb4x+8AszQRKX/DaT2lN+8x+H+37lEs/ptP205tr9ueQTqVNNblm74f0xk0f0u1uueWW7JhpTyykff76179mNVBm74f0S2o6/b8S+ip56aWXspq26Riq9L5J63OkAXE6ZSk9p0+XeOiu91ARP68+r2+6kmZYJrMfO5XYN5+W4mlra6vq4+Xz+qbaj5c0ozg9t/Sc21uqFZlqbLX/7dihXBT1fUb3josp//Ep5TtWpPzHbJR3jqywSmXmiiuuyFZCnTBhQrYK6ve///1S3759O60CV+6OPPLI0rRp00rPP/986c477ywNGzas1K9fv2yV0eTAAw8srbzyyqVbbrml9MADD5S22GKLrLX75JNPSuutt15pxx13LD3yyCOlm266qbTsssuWGhsbO/Z57rnnSosttljp6KOPzlbWO/fcc0u9evXK9i3S6o4PP/xw1tKhesYZZ2R///Of/8yuP+WUU7LX/ve//33p73//e2nXXXctrbrqqqWPPvqo4z6+9rWvlTbaaKPSvffeW7rjjjuy1SJHjBjRaaXB/v37l7773e+WHnvssez4Sv1ywQUXdOyTXoPevXuXTj/99Kyv0sqFaXXCRx99tFTEvknXHXXUUdlKiekY+t///d/SxhtvnD33f//73xXfNwcddFCpvr4+ew+98sorHe3DDz/s2Ke73kNF+7z6vL555plnSj/96U+zPknHTnpvDRo0qLTNNttUdN8cd9xx2erN6Tmnz5J0Oa2m/Ze//KWqj5fP65tqPV4+y+wrEVf7sUP5cRwVW3eMiyn/8SnlO1ak/MdslHeOrKjKLmmbnH322dkL0qdPn9Jmm21Wuueee0qVZM899ywtv/zy2fNbccUVs8vpy3G7NPj64Q9/WFpqqaWyL3K777579j/92b3wwgulr3/966VFF100O5jTQf7xxx932ufWW28tDRkyJHuc9EX70ksvLRVJii8NSj/d9t133+z6WbNmlX7yk59kicX0ZXX77bcvTZ8+vdN9vPXWW9lgdIkllijV1dWV9ttvv2zQO7u//e1vpa233jq7j9TfadD7aVdddVVpzTXXzPpq3XXXLd1www2lovZNGvylD6P0IZQSqKusskpp9OjRc3x5r9S+6apfUpv9+O7O91CRPq8+r29efPHFLOG29NJLZ6/56quvniWKWlpaKrpv9t9//+x9kuJI75v0WdI+CK/m4+Xz+qZaj5f5+QJQzccO5clxVFzdNS6m/MenlO9YkfIfs1HeObIi6pH+k/dsXwAAAAAAyrCmLQAAAABApZO0BQAAAAAoEElbAAAAAIACkbQFAAAAACgQSVsAAAAAgAKRtAUAAAAAKBBJWwAAAACAApG0BQAAAAAoEElbAAAAqAI9evSI6667LsrBqFGjYrfddquYxwGYX5K2AAAAUOZeffXV+NGPfhSDBg2K2traaGhoiF122SWmTp0alWjatGlZEvrdd9/NOxSAhaL3wrlbAAAAoDu88MILsdVWW0Xfvn3jtNNOi/XXXz8+/vjj+POf/xwHH3xwPPXUU14IgDJjpi0AAACUsR/+8IfZrNP77rsvvv3tb8eaa64Z6667bowZMybuueeeTvu++eabsfvuu8diiy0Wa6yxRlx//fUd182cOTMOOOCAWHXVVWPRRReNwYMHx1lnndVlOYHTTz89ll9++VhmmWWyxHBKErcbOHBg/OxnP4v9998/llxyyVh55ZXjwgsv7HQ/zc3N8Z3vfCdLNC+99NKx6667ZsnnL2rChAnZfaVE9dprrx1LLLFEfO1rX4tXXnml0/NLfZL2S3Efc8wxUSqVOt3PrFmzoqmpqaMPNtxww7j66quz69K+w4YNi+HDh3fc7u23346VVlopTjzxxC8cO0BXJG0BAACgTKWk4U033ZQlThdffPE5rk8JytmddNJJWbL073//e3zjG9+IvfbaK7uP9oRlSkD+7ne/iyeeeCJLRB5//PFx1VVXdbqPW2+9NZ599tns34kTJ2YJ09Rm94tf/CI23XTTePjhh7Ok8kEHHRTTp0/PrksJ3pT4TAnd22+/Pe68886OJOuMGTO+cF98+OGHWTL5sssui7/+9a/x4osvxlFHHdUpphTnJZdcEnfccUf2vKdMmdLpPlLC9je/+U2cf/758fjjj8cRRxwRe++9d9x2221ZYjw93/vvvz9++ctfZvsfeOCBseKKK0raAguc8ggAAABQpp555pls1udaa601T/unmbIjRozI/k6zYVPyMc3QTQnTmpqaLKnbLs02vfvuu7OkbUr0tltqqaXinHPOiV69emWPu9NOO2W1c0ePHt2xT0oIp2Rtcuyxx8aZZ56ZJXnT7N0rr7wySxBffPHFWSI0ufTSS7MEc6pVu+OOO36hvkjJ4JRsXW211bLLhxxySPz0pz/tuH78+PHR2NgY3/rWt7LLad80M7ddW1tb1if/+7//G1tssUW2LdUITgneCy64ILbddtssQZv+3meffbI6wn/605+yxHTv3tIrwILlUwUAAADK1KdP7/88G2ywQcffaWZuXV1dvP766x3bzj333Gwmapql+tFHH2UzX4cMGdLpPlLphZSwbZfKJDz66KNzfZyUmB0wYEDH4/ztb3/Lks1ppu3s/v3vf2czeL+oVPKhPWHbHlf7Y7a0tGSlEjbffPOO61OiNc0Gbu/DFFOarbvDDjt0ut/UBxtttFHH5T322COboXvKKafEeeedl5WZAFjQJG0BAACgTKWEYUqKzutiY2k27ezSbdOs1+SKK67IygmkMgJppmlKqqaFze699955vo952ef999+PTTbZJC6//PI54lt22WXn6XnM63Obn6R2iiu54YYbshm1s6utre34OyV2H3zwwSxx/Y9//OMLxwvwWSRtAQAAoEylRbxSfdg0Q/bQQw+do67tu+++O0dd27lJtWW33HLLjrIGyZeZ+To3G2+8cVYiYbnllstm+naH+vr6bOZtSkBvs8022bZPPvkkS76meJJ11lknS86mWcapFMLcHHnkkdGzZ8+48cYbszIQqTzEdttt1y3PA6geFiIDAACAMpYStjNnzozNNtssrrnmmmz255NPPpnVq22vzTqvs3YfeOCBrM7r008/HT/5yU+yRbcWtLT4Wb9+/WLXXXfNFiJ7/vnns1q2Ken80ksvxcJy2GGHZSUNrrvuumxmckpOp6R2uzSzOM00TouPpQXHUsL6oYceirPPPju73D4LN5WPSLOEUxmFo48+Ovbdd9945513FlrcQHWStAUAAIAylhbLSsnF//zP/8xmga633npZQjEtDpZqrs6rH/zgB9kiXXvuuWdW+/Wtt97qNOt2QUm1Z//617/GyiuvnD3e2muvHQcccEBW03ZhzrxNffPd7343S7K2l3/YfffdO+1z8sknZ8nqpqamLK60QFtK1KZF2d54440sznHjxnXMzk0Lt/Xv3z8OPPDAhRY3UJ16lOa3ajkAAAAAAAuNmbYAAAAAAAUiaQsAAAAAUCCStgAAAAAABSJpCwAAAABQIJK2AAAAAAAFImkLAAAAAFAgkrYAAAAAAAUiaQsAAAAAUCCStgAAAAAABSJpCwAAAABQIJK2AAAAAAAFImkLAAAAABDF8f8Dn7mIITVP/78AAAAASUVORK5CYII=","text/plain":["<Figure size 1400x1000 with 4 Axes>"]},"metadata":{},"output_type":"display_data"},{"name":"stdout","output_type":"stream","text":["\n","✅ EDA Complete. Dataset looks GOOD!\n"]}],"source":"# Comprehensive EDA: Scan ALL data files\nprint(\"=\"*60)\nprint(\"COMPREHENSIVE DATA ANALYSIS\")\nprint(\"=\"*60)\n\n# Find all training files\nall_train_files = []\nfor root, dirs, files in os.walk(\".\"):\n    for fname in files:\n        if fname == \"data_train.hdf5\":\n            all_train_files.append(os.path.join(root, fname))\n\nprint(f\"\\nFound {len(all_train_files)} training data files\")\n\nif len(all_train_files) > 0:\n    input_lens = []\n    target_lens = []\n    feature_dims = []\n    char_counts = {}\n    channel_means = []\n    total_samples = 0\n    skipped_samples = 0\n    \n    for fpath in tqdm(all_train_files, desc=\"Scanning files\"):\n        try:\n            with h5py.File(fpath, 'r') as f:\n                for k in f.keys():\n                    group = f[k]\n                    \n                    # Get neural features\n                    if 'input_features' in group:\n                        nf = group['input_features'][:]\n                    elif 'neural_features' in group:\n                        nf = group['neural_features'][:]\n                    else:\n                        skipped_samples += 1\n                        continue\n                    \n                    input_lens.append(nf.shape[0])\n                    feature_dims.append(nf.shape[1] if len(nf.shape) > 1 else 1)\n                    \n                    # Calculate mean activity per channel (for first 50 samples only)\n                    if len(channel_means) < 50:\n                        channel_means.append(np.abs(nf).mean(axis=0))\n                    \n                    # Get target\n                    if 'transcription' in group:\n                        t = group['transcription'][()].flatten()\n                        t = t[t != 0]\n                        target_lens.append(len(t))\n                        for c in t:\n                            char_counts[chr(c)] = char_counts.get(chr(c), 0) + 1\n                    elif 'sentence_label' in group:\n                        s = group['sentence_label'][()].decode('utf-8')\n                        target_lens.append(len(s))\n                        for c in s:\n                            char_counts[c] = char_counts.get(c, 0) + 1\n                    \n                    total_samples += 1\n        except Exception as e:\n            print(f\"Error reading {fpath}: {e}\")\n    \n    print(f\"\\n{'='*60}\")\n    print(\"DATA SUMMARY\")\n    print(f\"{'='*60}\")\n    print(f\"Total Samples: {total_samples}\")\n    print(f\"Skipped (missing features): {skipped_samples}\")\n    print(f\"Feature Dimensions: {set(feature_dims)}\")\n    print(f\"\\nInput Lengths: min={min(input_lens)}, max={max(input_lens)}, mean={np.mean(input_lens):.1f}\")\n    print(f\"Target Lengths: min={min(target_lens)}, max={max(target_lens)}, mean={np.mean(target_lens):.1f}\")\n    \n    # CTC Constraint Check\n    ctc_violations = sum([i < t for i, t in zip(input_lens, target_lens)])\n    print(f\"\\nCTC Violations (input < target): {ctc_violations} ({100*ctc_violations/len(input_lens):.2f}%)\")\n    \n    # Plot 1: Length Distributions\n    fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n    \n    axes[0,0].hist(input_lens, bins=30, color='steelblue', alpha=0.7, edgecolor='black')\n    axes[0,0].axvline(np.mean(input_lens), color='red', linestyle='--', label=f'Mean: {np.mean(input_lens):.0f}')\n    axes[0,0].set_title(\"Neural Input Lengths Distribution\", fontsize=12)\n    axes[0,0].set_xlabel(\"Time Steps\")\n    axes[0,0].legend()\n    \n    axes[0,1].hist(target_lens, bins=30, color='seagreen', alpha=0.7, edgecolor='black')\n    axes[0,1].axvline(np.mean(target_lens), color='red', linestyle='--', label=f'Mean: {np.mean(target_lens):.0f}')\n    axes[0,1].set_title(\"Target Lengths Distribution\", fontsize=12)\n    axes[0,1].set_xlabel(\"Characters\")\n    axes[0,1].legend()\n    \n    # Plot 2: Character Frequency\n    sorted_chars = sorted(char_counts.items(), key=lambda x: -x[1])[:30]\n    chars, counts = zip(*sorted_chars) if sorted_chars else ([], [])\n    axes[1,0].barh(range(len(chars)), counts, color='coral')\n    axes[1,0].set_yticks(range(len(chars)))\n    axes[1,0].set_yticklabels([repr(c) if c in ' \\n\\t' else c for c in chars])\n    axes[1,0].set_title(\"Top 30 Character Frequencies\", fontsize=12)\n    axes[1,0].invert_yaxis()\n    \n    # Plot 3: Channel Activity Heatmap\n    if len(channel_means) > 0:\n        channel_activity = np.array(channel_means).mean(axis=0)\n        axes[1,1].bar(range(len(channel_activity)), channel_activity, color='purple', alpha=0.7)\n        axes[1,1].set_title(\"Mean Channel Activity (First 50 samples)\", fontsize=12)\n        axes[1,1].set_xlabel(\"Channel Index\")\n        axes[1,1].set_ylabel(\"Mean |Activity|\")\n    \n    plt.tight_layout()\n    plt.show()\n    \n    print(f\"\\n✅ EDA Complete. Dataset looks {'GOOD' if ctc_violations == 0 else 'HAS ISSUES'}!\")\nelse:\n    print(\"No training data found. Please check data path.\")\n"},{"cell_type":"markdown","metadata":{},"source":"## 4. Modeling: Conv1D + GRU + CTC\nWe use an **advanced architecture** tailored for the RTX 4050:\n1.  **Conv1D Front-End**: Reduces the time sequence length by 2x. This acts like a \"compression\" step, making it easier for the RNN to learn long-term dependencies.\n2.  **Channel Augmentation**: Randomly drops 10% of input channels during training. This forces the model to be robust to noise.\n3.  **Deep Bidirectional GRU**: 5 layers of GRU to extract complex temporal features.\n4.  **CTC Loss**: For alignment-free training.\n"},{"cell_type":"code","execution_count":27,"metadata":{},"outputs":[],"source":"class NeuralDecoder(nn.Module):\n    def __init__(self, input_size, hidden_size, num_layers, vocab_size, dropout=0.2):\n        super().__init__()\n        \n        # 1. Convolutional Subsampling (Stride=2 for temporal compression)\n        self.conv1 = nn.Conv1d(input_size, hidden_size, kernel_size=3, stride=2, padding=1)\n        self.conv2 = nn.Conv1d(hidden_size, hidden_size, kernel_size=3, stride=1, padding=1)  # Feature refinement\n        self.activation = nn.GELU()\n        \n        # 2. Layer Norm\n        self.layer_norm = nn.LayerNorm(hidden_size)\n        \n        # 3. Bidirectional GRU\n        self.rnn = nn.GRU(hidden_size, hidden_size, num_layers=num_layers, \n                          batch_first=True, bidirectional=True, dropout=dropout)\n        \n        # 4. Classifier\n        self.classifier = nn.Linear(hidden_size * 2, vocab_size + 1)  # +1 for Blank\n        \n        # 5. Augmentation settings\n        self.dropout_p = dropout\n        self.time_mask_ratio = 0.1  # Mask 10% of time steps\n        \n    def forward(self, x, lengths):\n        # x: (B, T, F)\n        \n        # --- Data Augmentation (Training Only) ---\n        if self.training:\n            # Channel Dropout: Zero out 10% of channels\n            if self.dropout_p > 0:\n                ch_mask = torch.rand(x.size(0), 1, x.size(2), device=x.device) > 0.1\n                x = x * ch_mask\n            \n            # Time Masking: Zero out contiguous time blocks (SpecAugment-style)\n            mask_len = int(x.size(1) * self.time_mask_ratio)\n            if mask_len > 0:\n                for i in range(x.size(0)):\n                    start = torch.randint(0, max(1, x.size(1) - mask_len), (1,)).item()\n                    x[i, start:start+mask_len, :] = 0\n            \n        # --- Temporal Compression ---\n        x = x.permute(0, 2, 1)  # (B, F, T)\n        x = self.activation(self.conv1(x))\n        x = self.activation(self.conv2(x))\n        x = x.permute(0, 2, 1)  # (B, T/2, Hidden)\n        \n        # Update lengths for stride=2 conv\n        lengths = torch.clamp((lengths - 1) // 2 + 1, min=1)\n        \n        # --- RNN Processing ---\n        x = self.layer_norm(x)\n        packed = nn.utils.rnn.pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=False)\n        out_packed, _ = self.rnn(packed)\n        out, _ = nn.utils.rnn.pad_packed_sequence(out_packed, batch_first=True)\n        \n        # --- Project ---\n        logits = self.classifier(out)\n        return logits, lengths\n"},{"cell_type":"markdown","metadata":{},"source":"## 5. Training Loop\nFunctions for training and validation.\n"},{"cell_type":"code","execution_count":28,"metadata":{},"outputs":[],"source":"def train_epoch(model, loader, criterion, optimizer, scheduler, scaler, device):\n    model.train()\n    running_loss = 0.0\n    num_batches = 0\n    \n    optimizer.zero_grad()\n    pbar = tqdm(loader, desc=\"Training\", leave=False)\n    \n    for step, batch in enumerate(pbar):\n        if batch is None:\n            continue\n        inputs, targets, input_lens, target_lens = batch\n        inputs = inputs.to(device)\n        targets = targets.to(device)\n        \n        # Mixed Precision Forward\n        with torch.cuda.amp.autocast(enabled=Config.USE_AMP):\n            logits, input_lens_new = model(inputs, input_lens)\n            logits = logits.permute(1, 0, 2)\n            log_probs = F.log_softmax(logits, dim=2)\n            loss = criterion(log_probs, targets, input_lens_new, target_lens)\n            loss = loss / Config.GRAD_ACCUM_STEPS  # Scale for accumulation\n        \n        # Check for invalid loss\n        if torch.isnan(loss) or torch.isinf(loss):\n            continue\n        \n        # Backward with scaler\n        scaler.scale(loss).backward()\n        \n        # Gradient accumulation: only step every N batches\n        if (step + 1) % Config.GRAD_ACCUM_STEPS == 0:\n            scaler.unscale_(optimizer)\n            torch.nn.utils.clip_grad_norm_(model.parameters(), Config.CLIP_GRAD)\n            scaler.step(optimizer)\n            scaler.update()\n            if scheduler.last_epoch < scheduler.total_steps:\n                scheduler.step()\n            optimizer.zero_grad()\n        \n        running_loss += loss.item() * Config.GRAD_ACCUM_STEPS\n        num_batches += 1\n        pbar.set_postfix({'loss': f'{loss.item() * Config.GRAD_ACCUM_STEPS:.4f}'})\n            \n    return running_loss / max(num_batches, 1)\n\ndef validate(model, loader, criterion, device, tokenizer):\n    model.eval()\n    val_loss = 0.0\n    predictions = []\n    ground_truths = []\n    num_batches = 0\n    \n    pbar = tqdm(loader, desc=\"Validating\", leave=False)\n    with torch.no_grad():\n        for batch in pbar:\n            if batch is None:\n                continue\n            inputs, targets, input_lens, target_lens = batch\n            inputs = inputs.to(device)\n            targets = targets.to(device)\n            \n            logits, input_lens_new = model(inputs, input_lens)\n            logits = logits.permute(1, 0, 2)\n            log_probs = F.log_softmax(logits, dim=2)\n            \n            loss = criterion(log_probs, targets, input_lens_new, target_lens)\n            if not (torch.isnan(loss) or torch.isinf(loss)):\n                val_loss += loss.item()\n                num_batches += 1\n            \n            # Decoding\n            probs = log_probs.permute(1, 0, 2).argmax(dim=2)\n            \n            for b in range(inputs.size(0)):\n                pred_indices = probs[b, :input_lens_new[b]].cpu().tolist()\n                pred_str = tokenizer.decode_ctc(pred_indices)\n                predictions.append(pred_str)\n                \n                # Ground Truth\n                tgt_indices = targets[b, :target_lens[b]].cpu().tolist()\n                gt_str = tokenizer.decode(tgt_indices)\n                ground_truths.append(gt_str)\n    \n    # Handle empty predictions\n    if len(predictions) == 0 or len(ground_truths) == 0:\n        return float('inf'), 1.0, 1.0, [], []\n    \n    # Filter empty strings for jiwer\n    valid_pairs = [(p, g) for p, g in zip(predictions, ground_truths) if g.strip()]\n    if not valid_pairs:\n        return val_loss / max(num_batches, 1), 1.0, 1.0, predictions, ground_truths\n    \n    preds_valid, gts_valid = zip(*valid_pairs)\n    wer = jiwer.wer(list(gts_valid), list(preds_valid))\n    cer = jiwer.cer(list(gts_valid), list(preds_valid))\n    \n    return val_loss / max(num_batches, 1), wer, cer, predictions, ground_truths\n"},{"cell_type":"markdown","metadata":{},"source":"## 6. Execution\nPre-scanning the data to build vocabulary, then training.\n"},{"cell_type":"code","execution_count":29,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Data directory: t15_copyTask_neuralData\\hdf5_data_final\n","Vocab size: 128\n","Training Samples: 8072\n","Validation Samples: 1426\n","\n","Model Parameters: 28,482,689 (Trainable: 28,482,689)\n","🔃 Found checkpoint. Resuming training...\n","✅ Resumed from Epoch 140, Best WER: 0.4006\n","\n","============================================================\n","STARTING TRAINING\n","============================================================\n","\n","📈 Epoch 141/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3620 | Val Loss: 0.9437\n","   Val WER: 0.4025 | Val CER: 0.2215\n","   Example: 'You can see the cone at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (1/30)\n","\n","📈 Epoch 142/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3504 | Val Loss: 0.9440\n","   Val WER: 0.4042 | Val CER: 0.2219\n","   Example: 'You can see the cone at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (2/30)\n","\n","📈 Epoch 143/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3511 | Val Loss: 0.9373\n","   Val WER: 0.4021 | Val CER: 0.2210\n","   Example: 'You can see the coled at this point as weill....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (3/30)\n","\n","📈 Epoch 144/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3449 | Val Loss: 0.9396\n","   Val WER: 0.4013 | Val CER: 0.2208\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (4/30)\n","\n","📈 Epoch 145/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3593 | Val Loss: 0.9399\n","   Val WER: 0.4030 | Val CER: 0.2220\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (5/30)\n","\n","📈 Epoch 146/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3549 | Val Loss: 0.9397\n","   Val WER: 0.4024 | Val CER: 0.2213\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (6/30)\n","\n","📈 Epoch 147/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3649 | Val Loss: 0.9380\n","   Val WER: 0.4018 | Val CER: 0.2215\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (7/30)\n","\n","📈 Epoch 148/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                          \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3650 | Val Loss: 0.9384\n","   Val WER: 0.4032 | Val CER: 0.2218\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (8/30)\n","\n","📈 Epoch 149/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3631 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (9/30)\n","\n","📈 Epoch 150/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                          \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3615 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (10/30)\n","\n","📈 Epoch 151/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3652 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (11/30)\n","\n","📈 Epoch 152/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3703 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (12/30)\n","\n","📈 Epoch 153/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3826 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (13/30)\n","\n","📈 Epoch 154/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3764 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (14/30)\n","\n","📈 Epoch 155/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3820 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (15/30)\n","\n","📈 Epoch 156/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3701 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (16/30)\n","\n","📈 Epoch 157/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3837 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (17/30)\n","\n","📈 Epoch 158/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3789 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (18/30)\n","\n","📈 Epoch 159/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3809 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2217\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (19/30)\n","\n","📈 Epoch 160/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3826 | Val Loss: 0.9384\n","   Val WER: 0.4026 | Val CER: 0.2217\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (20/30)\n","\n","📈 Epoch 161/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3807 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2217\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (21/30)\n","\n","📈 Epoch 162/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3788 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (22/30)\n","\n","📈 Epoch 163/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3759 | Val Loss: 0.9384\n","   Val WER: 0.4026 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (23/30)\n","\n","📈 Epoch 164/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3864 | Val Loss: 0.9384\n","   Val WER: 0.4026 | Val CER: 0.2217\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (24/30)\n","\n","📈 Epoch 165/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3832 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (25/30)\n","\n","📈 Epoch 166/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3773 | Val Loss: 0.9384\n","   Val WER: 0.4026 | Val CER: 0.2217\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (26/30)\n","\n","📈 Epoch 167/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3816 | Val Loss: 0.9384\n","   Val WER: 0.4025 | Val CER: 0.2216\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (27/30)\n","\n","📈 Epoch 168/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"name":"stdout","output_type":"stream","text":["   Train Loss: 0.3768 | Val Loss: 0.9384\n","   Val WER: 0.4026 | Val CER: 0.2217\n","   Example: 'You can see the cole at this point as well....' vs 'You can see the code at this point as well....'\n","   ⏳ No improvement (28/30)\n","\n","📈 Epoch 169/300\n"]},{"name":"stderr","output_type":"stream","text":["                                                                        \r"]},{"ename":"KeyboardInterrupt","evalue":"","output_type":"error","traceback":["\u001b[31m---------------------------------------------------------------------------\u001b[39m","\u001b[31mKeyboardInterrupt\u001b[39m                         Traceback (most recent call last)","\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[29]\u001b[39m\u001b[32m, line 142\u001b[39m\n\u001b[32m    139\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m epoch \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(start_epoch, Config.N_EPOCHS):\n\u001b[32m    140\u001b[39m     \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m📈 Epoch \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mepoch+\u001b[32m1\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mConfig.N_EPOCHS\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m142\u001b[39m     train_loss = \u001b[43mtrain_epoch\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtrain_loader\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcriterion\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43moptimizer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscheduler\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mscaler\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mDEVICE\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    143\u001b[39m     val_loss, val_wer, val_cer, preds, gts = validate(model, val_loader, criterion, DEVICE, tokenizer)\n\u001b[32m    145\u001b[39m     \u001b[38;5;66;03m# Record history\u001b[39;00m\n","\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[28]\u001b[39m\u001b[32m, line 18\u001b[39m, in \u001b[36mtrain_epoch\u001b[39m\u001b[34m(model, loader, criterion, optimizer, scheduler, scaler, device)\u001b[39m\n\u001b[32m     16\u001b[39m \u001b[38;5;66;03m# Mixed Precision Forward\u001b[39;00m\n\u001b[32m     17\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m torch.cuda.amp.autocast(enabled=Config.USE_AMP):\n\u001b[32m---> \u001b[39m\u001b[32m18\u001b[39m     logits, input_lens_new = \u001b[43mmodel\u001b[49m\u001b[43m(\u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minput_lens\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m     19\u001b[39m     logits = logits.permute(\u001b[32m1\u001b[39m, \u001b[32m0\u001b[39m, \u001b[32m2\u001b[39m)\n\u001b[32m     20\u001b[39m     log_probs = F.log_softmax(logits, dim=\u001b[32m2\u001b[39m)\n","\u001b[36mFile \u001b[39m\u001b[32md:\\Dev\\Notebook\\BrainToText\\venv\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1751\u001b[39m, in \u001b[36mModule._wrapped_call_impl\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m   1749\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._compiled_call_impl(*args, **kwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[32m   1750\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1751\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n","\u001b[36mFile \u001b[39m\u001b[32md:\\Dev\\Notebook\\BrainToText\\venv\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1762\u001b[39m, in \u001b[36mModule._call_impl\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m   1757\u001b[39m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[32m   1758\u001b[39m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[32m   1759\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m._backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m._backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m._forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m._forward_pre_hooks\n\u001b[32m   1760\u001b[39m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[32m   1761\u001b[39m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[32m-> \u001b[39m\u001b[32m1762\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   1764\u001b[39m result = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m   1765\u001b[39m called_always_called_hooks = \u001b[38;5;28mset\u001b[39m()\n","\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[27]\u001b[39m\u001b[32m, line 53\u001b[39m, in \u001b[36mNeuralDecoder.forward\u001b[39m\u001b[34m(self, x, lengths)\u001b[39m\n\u001b[32m     51\u001b[39m x = \u001b[38;5;28mself\u001b[39m.layer_norm(x)\n\u001b[32m     52\u001b[39m packed = nn.utils.rnn.pack_padded_sequence(x, lengths.cpu(), batch_first=\u001b[38;5;28;01mTrue\u001b[39;00m, enforce_sorted=\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[32m---> \u001b[39m\u001b[32m53\u001b[39m out_packed, _ = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mrnn\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpacked\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m     54\u001b[39m out, _ = nn.utils.rnn.pad_packed_sequence(out_packed, batch_first=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m     56\u001b[39m \u001b[38;5;66;03m# --- Project ---\u001b[39;00m\n","\u001b[36mFile \u001b[39m\u001b[32md:\\Dev\\Notebook\\BrainToText\\venv\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1751\u001b[39m, in \u001b[36mModule._wrapped_call_impl\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m   1749\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._compiled_call_impl(*args, **kwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[32m   1750\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1751\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n","\u001b[36mFile \u001b[39m\u001b[32md:\\Dev\\Notebook\\BrainToText\\venv\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1762\u001b[39m, in \u001b[36mModule._call_impl\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m   1757\u001b[39m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[32m   1758\u001b[39m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[32m   1759\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m._backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m._backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m._forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m._forward_pre_hooks\n\u001b[32m   1760\u001b[39m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[32m   1761\u001b[39m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[32m-> \u001b[39m\u001b[32m1762\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   1764\u001b[39m result = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m   1765\u001b[39m called_always_called_hooks = \u001b[38;5;28mset\u001b[39m()\n","\u001b[36mFile \u001b[39m\u001b[32md:\\Dev\\Notebook\\BrainToText\\venv\\Lib\\site-packages\\torch\\nn\\modules\\rnn.py:1405\u001b[39m, in \u001b[36mGRU.forward\u001b[39m\u001b[34m(self, input, hx)\u001b[39m\n\u001b[32m   1393\u001b[39m     result = _VF.gru(\n\u001b[32m   1394\u001b[39m         \u001b[38;5;28minput\u001b[39m,\n\u001b[32m   1395\u001b[39m         hx,\n\u001b[32m   (...)\u001b[39m\u001b[32m   1402\u001b[39m         \u001b[38;5;28mself\u001b[39m.batch_first,\n\u001b[32m   1403\u001b[39m     )\n\u001b[32m   1404\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1405\u001b[39m     result = \u001b[43m_VF\u001b[49m\u001b[43m.\u001b[49m\u001b[43mgru\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m   1406\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m   1407\u001b[39m \u001b[43m        \u001b[49m\u001b[43mbatch_sizes\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   1408\u001b[39m \u001b[43m        \u001b[49m\u001b[43mhx\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   1409\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_flat_weights\u001b[49m\u001b[43m,\u001b[49m\u001b[43m  \u001b[49m\u001b[38;5;66;43;03m# type: ignore[arg-type]\u001b[39;49;00m\n\u001b[32m   1410\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mbias\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   1411\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mnum_layers\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   1412\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdropout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   1413\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mtraining\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   1414\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mbidirectional\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   1415\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   1416\u001b[39m output = result[\u001b[32m0\u001b[39m]\n\u001b[32m   1417\u001b[39m hidden = result[\u001b[32m1\u001b[39m]\n","\u001b[31mKeyboardInterrupt\u001b[39m: "]}],"source":"# Initialize Tokenizer (ASCII)\ntokenizer = CharTokenizer()\n# No need to build vocab for ASCII\n\n# Root dir\ndata_root = None\n\n# 1. Try config\nif os.path.exists(Config.DATA_DIR) and \"data_train.hdf5\" in os.listdir(Config.DATA_DIR):\n    data_root = Config.DATA_DIR\n    \n# 2. Try expected local structure\nif not data_root:\n    candidates = [\n        os.path.join(\"t15_copyTask_neuralData\", \"hdf5_data_final\"),\n        \"hdf5_data_final\",\n        os.path.join(\"..\", \"t15_copyTask_neuralData\", \"hdf5_data_final\"),\n        \"/kaggle/input/brain-to-text-25\",\n        \".\"\n    ]\n    for c in candidates:\n        if os.path.exists(c) and os.path.isdir(c):\n             # Check if it has the train file recursively\n             for root, dirs, files in os.walk(c):\n                 if \"data_train.hdf5\" in files:\n                     # CRITICAL FIX: If we found data deep inside, we want the PARENT\n                     # of the session folders, usually 'hdf5_data_final'.\n                     # If 'root' contains 'hdf5_data_final', identifying the cut-off is tricky.\n                     # However, if 'c' was a specific guess like \"hdf5_data_final\", use 'c'.\n                     if \"hdf5_data_final\" in c:\n                         data_root = c\n                     else:\n                         # Fallback: if we just searched '.', maybe we should use '.'\n                         # OR return the common ancestor if identifiable.\n                         # For this dataset, let's assume 'hdf5_data_final' is the anchor.\n                         if \"hdf5_data_final\" in root:\n                             # Extract path up to hdf5_data_final\n                             idx = root.find(\"hdf5_data_final\")\n                             data_root = root[:idx+len(\"hdf5_data_final\")]\n                         else:\n                             # If structure is flat?\n                             data_root = c # Use the search base\n                     break\n        if data_root: break\n\n# 3. Last resort: Recursive search from current dir\nif not data_root:\n    print(f\"Searching for data starting from {os.getcwd()}...\")\n    for root, dirs, files in os.walk(\".\"):\n        if \"data_train.hdf5\" in files:\n            # Same logic\n            if \"hdf5_data_final\" in root:\n                 idx = root.find(\"hdf5_data_final\")\n                 data_root = root[:idx+len(\"hdf5_data_final\")]\n            else:\n                data_root = root # Fallback to leaf? No, maybe '.'?\n                # If we found it relative to '.', just use '.' so recursion works from here.\n                data_root = \".\"\n            print(f\"Found data at: {data_root}\")\n            break\n\nif data_root:\n    print(f\"Data directory: {data_root}\")\n    print(f\"Vocab size: {tokenizer.get_vocab_size()}\")\n    \n    # Train Setup\n    ds_train = BrainSpeechDataset(data_root, tokenizer, partition='train')\n    ds_val = BrainSpeechDataset(data_root, tokenizer, partition='val')\n    \n    # Validation Split fallback\n    if len(ds_val) == 0:\n        print(\"No validation data found, splitting train...\")\n        train_size = int(0.9 * len(ds_train))\n        val_size = len(ds_train) - train_size\n        ds_train, ds_val = torch.utils.data.random_split(ds_train, [train_size, val_size])\n        \n    print(f\"Training Samples: {len(ds_train)}\")\n    print(f\"Validation Samples: {len(ds_val)}\")\n        \n    train_loader = DataLoader(ds_train, batch_size=Config.BATCH_SIZE, shuffle=True, collate_fn=collate_fn, num_workers=0, pin_memory=True)\n    val_loader = DataLoader(ds_val, batch_size=Config.BATCH_SIZE, shuffle=False, collate_fn=collate_fn, num_workers=0, pin_memory=True)\n    \n    # Model Setup\n    model = NeuralDecoder(Config.INPUT_SIZE, Config.HIDDEN_SIZE, Config.NUM_LAYERS, tokenizer.get_vocab_size(), dropout=Config.DROPOUT)\n    model.to(DEVICE)\n    \n    # Count parameters\n    total_params = sum(p.numel() for p in model.parameters())\n    trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n    print(f\"\\nModel Parameters: {total_params:,} (Trainable: {trainable_params:,})\")\n    \n    optimizer = torch.optim.AdamW(model.parameters(), lr=Config.LEARNING_RATE, weight_decay=Config.WEIGHT_DECAY)\n    \n    # Scheduler steps = batches / accumulation steps\n    effective_steps_per_epoch = len(train_loader) // Config.GRAD_ACCUM_STEPS\n    scheduler = torch.optim.lr_scheduler.OneCycleLR(\n        optimizer, max_lr=Config.LEARNING_RATE, \n        steps_per_epoch=effective_steps_per_epoch, epochs=Config.N_EPOCHS\n    )\n    criterion = nn.CTCLoss(blank=0, zero_infinity=True)\n    \n    # AMP GradScaler\n    scaler = torch.cuda.amp.GradScaler(enabled=Config.USE_AMP)\n    \n    start_epoch = 0\n    best_wer = float('inf')\n    \n    # Check for resume from full checkpoint\n    if os.path.exists(\"checkpoint.pth\"):\n        print(\"🔃 Found checkpoint. Resuming training...\")\n        try:\n            ckpt = torch.load(\"checkpoint.pth\")\n            model.load_state_dict(ckpt['model'])\n            optimizer.load_state_dict(ckpt['optimizer'])\n            scheduler.load_state_dict(ckpt['scheduler'])\n            if 'scaler' in ckpt:\n                scaler.load_state_dict(ckpt['scaler'])\n            start_epoch = ckpt['epoch'] + 1\n            best_wer = ckpt['best_wer']\n            print(f\"✅ Resumed from Epoch {start_epoch}, Best WER: {best_wer:.4f}\")\n        except Exception as e:\n            print(f\"⚠️ Could not load checkpoint: {e}. Starting fresh.\")\n    elif os.path.exists(\"best_model.pth\"):\n        print(\"🔃 Found 'best_model.pth'. Loading weights only...\")\n        try:\n            model.load_state_dict(torch.load(\"best_model.pth\"))\n            print(\"✅ Model weights loaded.\")\n        except Exception as e:\n            print(f\"⚠️ Could not load: {e}\")\n    \n    patience = Config.PATIENCE\n    patience_counter = 0\n    history = {'train_loss': [], 'val_loss': [], 'val_wer': [], 'val_cer': []}\n    \n    print(\"\\n\" + \"=\"*60)\n    print(\"STARTING TRAINING\")\n    print(\"=\"*60)\n    \n    for epoch in range(start_epoch, Config.N_EPOCHS):\n        print(f\"\\n📈 Epoch {epoch+1}/{Config.N_EPOCHS}\")\n        \n        train_loss = train_epoch(model, train_loader, criterion, optimizer, scheduler, scaler, DEVICE)\n        val_loss, val_wer, val_cer, preds, gts = validate(model, val_loader, criterion, DEVICE, tokenizer)\n        \n        # Record history\n        history['train_loss'].append(train_loss)\n        history['val_loss'].append(val_loss)\n        history['val_wer'].append(val_wer)\n        history['val_cer'].append(val_cer)\n        \n        print(f\"   Train Loss: {train_loss:.4f} | Val Loss: {val_loss:.4f}\")\n        print(f\"   Val WER: {val_wer:.4f} | Val CER: {val_cer:.4f}\")\n        \n        if len(preds) > 0 and len(gts) > 0:\n            print(f\"   Example: '{preds[0][:50]}...' vs '{gts[0][:50]}...'\")\n        \n        # Check for improvement\n        if val_wer < best_wer:\n            best_wer = val_wer\n            patience_counter = 0\n            # Save full checkpoint\n            torch.save({\n                'epoch': epoch,\n                'model': model.state_dict(),\n                'optimizer': optimizer.state_dict(),\n                'scheduler': scheduler.state_dict(),\n                'scaler': scaler.state_dict(),\n                'best_wer': best_wer\n            }, \"checkpoint.pth\")\n            torch.save(model.state_dict(), \"best_model.pth\")\n            print(f\"   ✅ New Best WER: {val_wer:.4f} - Checkpoint Saved!\")\n        else:\n            patience_counter += 1\n            print(f\"   ⏳ No improvement ({patience_counter}/{patience})\")\n            \n        # Early stopping\n        if patience_counter >= patience:\n            print(f\"\\n⛔ Early stopping triggered after {epoch+1} epochs\")\n            break\n    \n    # Plot training curves\n    print(\"\\n\" + \"=\"*60)\n    print(\"TRAINING COMPLETE\")\n    print(\"=\"*60)\n    print(f\"Best Validation WER: {best_wer:.4f}\")\n    \n    fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n    axes[0].plot(history['train_loss'], label='Train Loss', marker='o')\n    axes[0].plot(history['val_loss'], label='Val Loss', marker='s')\n    axes[0].set_xlabel('Epoch')\n    axes[0].set_ylabel('Loss')\n    axes[0].set_title('Loss Curves')\n    axes[0].legend()\n    axes[0].grid(True, alpha=0.3)\n    \n    axes[1].plot(history['val_wer'], label='WER', marker='o', color='red')\n    axes[1].plot(history['val_cer'], label='CER', marker='s', color='orange')\n    axes[1].set_xlabel('Epoch')\n    axes[1].set_ylabel('Error Rate')\n    axes[1].set_title('Error Rates')\n    axes[1].legend()\n    axes[1].grid(True, alpha=0.3)\n    \n    plt.tight_layout()\n    plt.show()\n\nelse:\n    print(\"❌ Data not found. Please ensure data is downloaded/unzipped.\")\n    # Mock for runnability check\n    ds_train = BrainSpeechDataset(\"mock\", tokenizer, partition='train')\n    train_loader = DataLoader(ds_train, batch_size=4, collate_fn=collate_fn)\n"},{"cell_type":"markdown","metadata":{},"source":"## 7. Submission\nLoad the test data, run inference, and generate `submission.csv`.\n"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[{"name":"stdout","output_type":"stream","text":["Loading model from best_model.pth...\n","Looking for test data in t15_copyTask_neuralData\\hdf5_data_final...\n","Found 1450 test files. Running Inference...\n"]},{"name":"stderr","output_type":"stream","text":["Generating Submission: 100%|██████████| 91/91 [01:07<00:00,  1.34it/s]"]},{"name":"stdout","output_type":"stream","text":["Saved submission.csv\n","   id                                       text\n","0   0  I ge't tired with the sog and Dax choden.\n","1   1                              Emuses heere.\n","2   2                  You cuated a biti secice.\n","3   3              I think waney you like at it.\n","4   4            Sow that they do have progrems.\n"]},{"name":"stderr","output_type":"stream","text":["\n"]}],"source":"# Reuse data_root from previous cell if available\nif 'data_root' not in locals():\n    data_root = Config.DATA_DIR\n\nif os.path.exists(\"best_model.pth\"):\n    print(f\"Loading model from best_model.pth...\")\n    # Load best model\n    model.load_state_dict(torch.load(\"best_model.pth\"))\n    model.eval()\n    \n    print(f\"Looking for test data in {data_root}...\")\n    test_ds = BrainSpeechDataset(data_root, tokenizer, partition='test')\n    \n    if len(test_ds) > 0:\n        test_loader = DataLoader(test_ds, batch_size=Config.BATCH_SIZE, shuffle=False, collate_fn=collate_fn)\n        \n        submission_data = []\n        \n        print(f\"Found {len(test_ds)} test files. Running Inference...\")\n        with torch.no_grad():\n            for batch in tqdm(test_loader, desc=\"Generating Submission\"):\n                inputs, keys, input_lens = batch\n                inputs = inputs.to(DEVICE)\n                \n                logits, input_lens_new = model(inputs, input_lens)\n                log_probs = F.log_softmax(logits, dim=-1) # (B, T, C)\n                probs = log_probs.argmax(dim=-1)  # Greedy decode (fast!)\n                \n                for b in range(inputs.size(0)):\n                    pred_indices = probs[b, :input_lens_new[b]].cpu().tolist()\n                    pred_str = tokenizer.decode_ctc(pred_indices)\n                    submission_data.append({\"id\": keys[b], \"text\": pred_str})\n                    \n        df_sub = pd.DataFrame(submission_data)\n        \n        # Try to parse ID from key if it looks like \"1_2_abc\" or \"trial_123\"\n        # If keys are just sequential ints as strings, convert them.\n        # Otherwise fall back to range index.\n        try:\n            # Example heuristic: if key is \"123\", use 123. \n            # If key needs mapping, we stick to order.\n            # Kaggle usually requires an 'id' column that matches the test.csv or sample_submission.csv\n            # Since we don't have sample_submission, we will assume 0..N index is safest \n            # UNLESS keys are clearly indices.\n            df_sub['id'] = range(len(df_sub))\n        except:\n            pass\n        \n        df_sub.to_csv(\"submission.csv\", index=False)\n        print(\"Saved submission.csv\")\n        print(df_sub.head())\n    else:\n        print(f\"No test files found in {data_root} (partition='test').\")\nelse:\n    print(\"Model 'best_model.pth' not found. Did training finish?\")\n"}],"metadata":{"kernelspec":{"display_name":"venv","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.13.5"}},"nbformat":4,"nbformat_minor":4}