{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":52950,"databundleVersionId":5973250}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## 🔄 V12 Update: Overfit + Visualize\n\n## Changes in this version:\n- **Overfit on 100 epochs**\n- **Visualize targets vs preds**\n\n## Improvements:\n\nYou can change the number of samples to display:\n```\n  - num_samples=10 - shows 10 samples (faster)\n  - num_samples=20 - shows 20 samples (more insight)\n  - num_samples=50 - shows 50 samples (comprehensive)\n\n  ---\n  Example Output\n\n  ============================================================\n  Sample #1:\n    Target:     '988 franklin lane'\n    Prediction: '98 franklin lane'\n    CER:        0.0588 (5.88%)\n    Status:     EXCELLENT\n  ============================================================\n```","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2026-02-21T16:20:21.672252Z","iopub.execute_input":"2026-02-21T16:20:21.672546Z","iopub.status.idle":"2026-02-21T16:20:22.910443Z","shell.execute_reply.started":"2026-02-21T16:20:21.672512Z","shell.execute_reply":"2026-02-21T16:20:22.909628Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\n\n# Lectura de parquets \nimport pyarrow.parquet as pq\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nSEED = 42\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:31:25.067783Z","iopub.execute_input":"2026-02-21T16:31:25.06841Z","iopub.status.idle":"2026-02-21T16:31:28.983842Z","shell.execute_reply.started":"2026-02-21T16:31:25.068375Z","shell.execute_reply":"2026-02-21T16:31:28.983029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_CSV_PATH = \"/kaggle/input/asl-fingerspelling/train.csv\"\n\ntrain_df = pd.read_csv(TRAIN_CSV_PATH)\n\nprint(\"Total muestras:\", len(train_df))\ntrain_df.head()\n\n## para que entendamos que hay dentro de los parquets, conjunto de líneas que representan un frame identificado a la vedz por un sequence_id.\n## como veremos en cada línea del parquet hay muchas coordenadas, de cara, mano izqueirda, mano derecha, etc, a nosotros solo nos va a interesar\n#coordenadas de la mano derecha que supuestamente es la que va a representar el lenguaje de signos\n\n## el dataset se divide en parquets por temas de eficiencia y porque sería poco eficiente almacenar todo en un solo parquet, las consultas \n## a las diferentes secuencias serían muy lentas\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:31:32.59817Z","iopub.execute_input":"2026-02-21T16:31:32.598656Z","iopub.status.idle":"2026-02-21T16:31:32.737327Z","shell.execute_reply.started":"2026-02-21T16:31:32.598626Z","shell.execute_reply":"2026-02-21T16:31:32.736732Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Vamos a hacer una función que filtre el \"phrase\" del dataset para que recibamos una única palabra i mediante una expresión regultar\n#que nos saque solo palabras que contengas letras del abecedario, sin números ni carácteres raros.\n\nimport re\n\ndef is_single_word(word):\n    return bool(re.fullmatch(r\"[A-Za-z]+\", word))\n\ntrain_df[\"is_single_word\"] = train_df[\"phrase\"].apply(is_single_word)\n\nsingle_word_df = train_df[train_df[\"is_single_word\"]].copy()\n\nprint(\"Total muestras:\", len(train_df))\nprint(\"Solo palabras:\", len(single_word_df))\nprint(\"Porcentaje:\", len(single_word_df) / len(train_df) * 100)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:42:51.719122Z","iopub.execute_input":"2026-02-21T16:42:51.719492Z","iopub.status.idle":"2026-02-21T16:42:51.784088Z","shell.execute_reply.started":"2026-02-21T16:42:51.71944Z","shell.execute_reply":"2026-02-21T16:42:51.783174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#No hay suficientes palabras sueltas para considerar únicament las muestras de una palabra. Como vemos, una única palabra son solo el 1% del dataset\n# y seguramente de estas palabras sueltas algunas contendran caràcteres especiales y números que queremos excluir, por lo que no usaremos este método\n\n#Como lo que queremos es detectar letra a letra, creemos que no es crítico\n\ntrain_df[\"phrase\"].head(20)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:53:50.714232Z","iopub.execute_input":"2026-02-21T16:53:50.71461Z","iopub.status.idle":"2026-02-21T16:53:50.722031Z","shell.execute_reply.started":"2026-02-21T16:53:50.714577Z","shell.execute_reply":"2026-02-21T16:53:50.72138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Como vemos, aparecen muchos carácteres especiales o números, deberíamos limpiar estas entradas.\n\nfrom collections import Counter\nimport string\n\nletters = set(string.ascii_lowercase)\n\nletter_counter = Counter()\n\nfor phrase in train_df[\"phrase\"].astype(str):\n    for ch in phrase.lower():\n        if ch in letters:\n            letter_counter[ch] += 1\n\nletter_counter","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:54:22.780889Z","iopub.execute_input":"2026-02-21T16:54:22.78122Z","iopub.status.idle":"2026-02-21T16:54:23.031734Z","shell.execute_reply.started":"2026-02-21T16:54:22.781192Z","shell.execute_reply":"2026-02-21T16:54:23.030977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(letter_counter), letter_counter.most_common()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:55:18.550689Z","iopub.execute_input":"2026-02-21T16:55:18.551569Z","iopub.status.idle":"2026-02-21T16:55:18.557184Z","shell.execute_reply.started":"2026-02-21T16:55:18.551537Z","shell.execute_reply":"2026-02-21T16:55:18.556383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Podemos ver aquí que tenemos muestras que incluyen las 26 letras del abecedario y con bastantes apariciones, por ejemplo la letra e aparece en 71986 veces\n# o la que menos, la q con 1114.\n\nimport re\n\ndef is_clean_phrase(phrase):\n    return bool(re.fullmatch(r\"[A-Za-z ]+\", phrase))\n\nclean_df = train_df[train_df[\"phrase\"].apply(is_clean_phrase)].copy()\n\nprint(\"Muestras limpias:\", len(clean_df))\nprint(\"Porcentaje:\", len(clean_df) / len(train_df) * 100)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T17:01:31.088786Z","iopub.execute_input":"2026-02-21T17:01:31.089101Z","iopub.status.idle":"2026-02-21T17:01:31.148475Z","shell.execute_reply.started":"2026-02-21T17:01:31.089074Z","shell.execute_reply":"2026-02-21T17:01:31.147773Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"clean_df.iloc[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T17:04:23.972712Z","iopub.execute_input":"2026-02-21T17:04:23.973381Z","iopub.status.idle":"2026-02-21T17:04:23.980022Z","shell.execute_reply.started":"2026-02-21T17:04:23.973349Z","shell.execute_reply":"2026-02-21T17:04:23.979353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# division por participantes, trian, evalu i test no deberían compartir los mismos participantes\nparticipants = clean_df[\"participant_id\"].unique()\nnp.random.shuffle(participants)\n\nn = len(participants)\n\ntrain_ids = participants[:int(0.8 * n)]\nval_ids   = participants[int(0.8 * n):int(0.9 * n)]\ntest_ids  = participants[int(0.9 * n):]\n\ntrain_df_split = clean_df[clean_df[\"participant_id\"].isin(train_ids)]\nval_df_split   = clean_df[clean_df[\"participant_id\"].isin(val_ids)]\ntest_df_split  = clean_df[clean_df[\"participant_id\"].isin(test_ids)]\n\nprint(\"Train:\", len(train_df_split))\nprint(\"Val:  \", len(val_df_split))\nprint(\"Test: \", len(test_df_split))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T17:07:27.909595Z","iopub.execute_input":"2026-02-21T17:07:27.909886Z","iopub.status.idle":"2026-02-21T17:07:27.919822Z","shell.execute_reply.started":"2026-02-21T17:07:27.909863Z","shell.execute_reply":"2026-02-21T17:07:27.919136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"VOCAB_PATH = \"/kaggle/input/asl-fingerspelling/character_to_prediction_index.json\"\n\nwith open(VOCAB_PATH) as f:\n    original_letter_to_int = json.load(f)\n\nprint(\"Tamaño vocabulario original:\", len(original_letter_to_int))\nprint(list(original_letter_to_int.items()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:31:42.900143Z","iopub.execute_input":"2026-02-21T16:31:42.900523Z","iopub.status.idle":"2026-02-21T16:31:42.90964Z","shell.execute_reply.started":"2026-02-21T16:31:42.900492Z","shell.execute_reply":"2026-02-21T16:31:42.908821Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Añadimos el blank para que CTC pueda detectar cuando hay un cambio de letra\nletter_to_int = {}\n\nletter_to_int[\"<blank>\"] = 0\n\n# Desplazamos el resto +1\nfor char, idx in original_letter_to_int.items():\n    letter_to_int[char] = idx + 1\n\nint_to_letter = {v: k for k, v in letter_to_int.items()}\n\nprint(\"Tamaño vocabulario CTC:\", len(letter_to_int))\nprint(list(letter_to_int.items()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T16:31:47.831132Z","iopub.execute_input":"2026-02-21T16:31:47.831929Z","iopub.status.idle":"2026-02-21T16:31:47.837012Z","shell.execute_reply.started":"2026-02-21T16:31:47.831899Z","shell.execute_reply":"2026-02-21T16:31:47.836347Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Nuevo vocabulario filtrado por las letras que vamos a procesar al principio, que no contendran números ni carácteres especiales, por lo que este\n#se reduce a 27 i el Blank, que es lo que el ctc detecta como frames donde no pasa nada, pausas entre letras, transiciones, ruido...\n\nimport string\n\n# letras permitidas\nletters = list(string.ascii_lowercase)\n\n# vocabulario CTC\nletter_to_int = {\"<blank>\": 0}\n\nfor i, ch in enumerate(letters):\n    letter_to_int[ch] = i + 1\n\nint_to_letter = {v: k for k, v in letter_to_int.items()}\n\nprint(\"Tamaño vocabulario:\", len(letter_to_int))\nprint(letter_to_int)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T17:26:54.369341Z","iopub.execute_input":"2026-02-21T17:26:54.370126Z","iopub.status.idle":"2026-02-21T17:26:54.375749Z","shell.execute_reply.started":"2026-02-21T17:26:54.370097Z","shell.execute_reply":"2026-02-21T17:26:54.375037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def encode_phrase(phrase, letter_to_int):\n    return [letter_to_int[c] for c in phrase if c in letter_to_int]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T17:24:39.236655Z","iopub.execute_input":"2026-02-21T17:24:39.237293Z","iopub.status.idle":"2026-02-21T17:24:39.241246Z","shell.execute_reply.started":"2026-02-21T17:24:39.237264Z","shell.execute_reply":"2026-02-21T17:24:39.240357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Añadir frases codificadas\ntrain_df_split = train_df_split.copy()\nval_df_split   = val_df_split.copy()\n\ntrain_df_split[\"encoded\"] = train_df_split[\"phrase\"].apply(\n    lambda x: encode_phrase(x, letter_to_int)\n)\n\nval_df_split[\"encoded\"] = val_df_split[\"phrase\"].apply(\n    lambda x: encode_phrase(x, letter_to_int)\n)\n\ntrain_df_split[[\"phrase\", \"encoded\"]].head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-21T17:24:43.702031Z","iopub.execute_input":"2026-02-21T17:24:43.702332Z","iopub.status.idle":"2026-02-21T17:24:43.726612Z","shell.execute_reply.started":"2026-02-21T17:24:43.702306Z","shell.execute_reply":"2026-02-21T17:24:43.725782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MAX_FRAMES = 160\nRIGHT_HAND_COLS = None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.068666Z","iopub.execute_input":"2026-02-16T08:44:54.068931Z","iopub.status.idle":"2026-02-16T08:44:54.07231Z","shell.execute_reply.started":"2026-02-16T08:44:54.068902Z","shell.execute_reply":"2026-02-16T08:44:54.071623Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def read_right_hand_sequence(file_id, sequence_id):\n    global RIGHT_HAND_COLS\n\n    path = f\"/kaggle/input/asl-fingerspelling/train_landmarks/{file_id}.parquet\"\n    pq_file = pq.ParquetFile(path)\n\n    # Detectamos columnas una sola vez\n    if RIGHT_HAND_COLS is None:\n        RIGHT_HAND_COLS = [c for c in pq_file.schema.names if \"right_hand\" in c]\n\n    table = pq.read_table(\n        path,\n        filters=[(\"sequence_id\", \"=\", sequence_id)],\n        columns=RIGHT_HAND_COLS\n    )\n\n    X = table.to_pandas().values.astype(np.float32)\n    return X\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.074788Z","iopub.execute_input":"2026-02-16T08:44:54.075036Z","iopub.status.idle":"2026-02-16T08:44:54.084821Z","shell.execute_reply.started":"2026-02-16T08:44:54.075007Z","shell.execute_reply":"2026-02-16T08:44:54.084068Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize_frames(X, max_frames=MAX_FRAMES):\n    T, D = X.shape\n\n    if T > max_frames:\n        return X[:max_frames]\n\n    if T < max_frames:\n        pad = np.zeros((max_frames - T, D), dtype=np.float32)\n        return np.vstack([X, pad])\n\n    return X","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.08555Z","iopub.execute_input":"2026-02-16T08:44:54.085813Z","iopub.status.idle":"2026-02-16T08:44:54.095775Z","shell.execute_reply.started":"2026-02-16T08:44:54.08578Z","shell.execute_reply":"2026-02-16T08:44:54.09503Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"row = train_df_split.iloc[0]\n\nX = read_right_hand_sequence(row[\"file_id\"], row[\"sequence_id\"])\nXn = normalize_frames(X)\n\nprint(\"Original:\", X.shape)\nprint(\"Normalizado:\", Xn.shape)\nprint(Xn[:2])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.096642Z","iopub.execute_input":"2026-02-16T08:44:54.096902Z","iopub.status.idle":"2026-02-16T08:44:54.446769Z","shell.execute_reply.started":"2026-02-16T08:44:54.09687Z","shell.execute_reply":"2026-02-16T08:44:54.446043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def count_valid_frames(X):\n    # Un frame es válido si NO todo es NaN\n    return np.sum(~np.all(np.isnan(X), axis=1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.447817Z","iopub.execute_input":"2026-02-16T08:44:54.448194Z","iopub.status.idle":"2026-02-16T08:44:54.45196Z","shell.execute_reply.started":"2026-02-16T08:44:54.44816Z","shell.execute_reply":"2026-02-16T08:44:54.451063Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ASLRightHandDataset(Dataset):\n    def __init__(self, df, max_frames=160):\n        self.df = df.reset_index(drop=True)\n        self.max_frames = max_frames\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n    \n        X_raw = read_right_hand_sequence(\n            row[\"file_id\"],\n            row[\"sequence_id\"]\n        )  \n    \n        input_len = count_valid_frames(X_raw)\n    \n        Y = torch.tensor(row[\"encoded\"], dtype=torch.long)\n        target_len = len(Y)\n\n        if input_len < target_len:\n            return None\n\n    \n        X = normalize_frames(X_raw, self.max_frames)\n    \n        X = np.nan_to_num(X, nan=0.0)\n    \n        X = torch.tensor(X, dtype=torch.float32)\n    \n        input_len = min(input_len, self.max_frames)\n    \n        return X, Y, input_len, target_len\nprint(\"INFO: ASLRightHandDataset defined\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.452814Z","iopub.execute_input":"2026-02-16T08:44:54.453027Z","iopub.status.idle":"2026-02-16T08:44:54.466934Z","shell.execute_reply.started":"2026-02-16T08:44:54.453007Z","shell.execute_reply":"2026-02-16T08:44:54.46629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def collate_fn(batch):\n    # Quitamos ejemplos inválidos (None)\n    batch = [b for b in batch if b is not None]\n\n    # Si todo el batch era inválido, devolvemos None\n    if len(batch) == 0:\n        return None\n\n    Xs = []\n    Ys = []\n    in_lens = []\n    tar_lens = []\n\n    for X, Y, in_len, tar_len in batch:\n        Xs.append(X)\n        Ys.append(Y)\n        in_lens.append(in_len)\n        tar_lens.append(tar_len)\n\n    Xs = torch.stack(Xs)\n    Ys = torch.cat(Ys)\n    in_lens = torch.tensor(in_lens, dtype=torch.long)\n    tar_lens = torch.tensor(tar_lens, dtype=torch.long)\n\n    return Xs, Ys, in_lens, tar_lens\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.467862Z","iopub.execute_input":"2026-02-16T08:44:54.468103Z","iopub.status.idle":"2026-02-16T08:44:54.477498Z","shell.execute_reply.started":"2026-02-16T08:44:54.468083Z","shell.execute_reply":"2026-02-16T08:44:54.476924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df_tiny = train_df_split.sample(200, random_state=0)\ntrain_dataset_tiny = ASLRightHandDataset(train_df_tiny)\n\nprint(\"INFO: Creating data loader with TINY size (200)\")\n\ntrain_loader_tiny = DataLoader(\n    train_dataset_tiny,\n    batch_size=4,\n    shuffle=True,\n    collate_fn=collate_fn,\n    num_workers=0\n)\n\nprint(\"INFO: Checking sizes\")\n\nX, Y, in_len, tar_len = next(iter(train_loader_tiny))\n\nprint(\"X:\", X.shape)          # (B, 160, 63)\nprint(\"Y:\", Y.shape)          # (sum of target lengths)\nprint(\"in_len:\", in_len)\nprint(\"tar_len:\", tar_len)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.478429Z","iopub.execute_input":"2026-02-16T08:44:54.478728Z","iopub.status.idle":"2026-02-16T08:44:54.887612Z","shell.execute_reply.started":"2026-02-16T08:44:54.478698Z","shell.execute_reply":"2026-02-16T08:44:54.886792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SimpleRNN(nn.Module):\n    def __init__(self, input_size, hidden_size, num_classes):\n        super().__init__()\n\n        self.rnn = nn.RNN(\n            input_size=input_size,\n            hidden_size=hidden_size,\n            batch_first=True\n        )\n\n        self.classifier = nn.Linear(hidden_size, num_classes)\n        self.log_softmax = nn.LogSoftmax(dim=-1)\n\n    def forward(self, x):\n        out, _ = self.rnn(x)        # (B, T, H)\n        out = self.classifier(out)  # (B, T, C)\n        out = self.log_softmax(out)\n        out = out.permute(1, 0, 2)  # (T, B, C) para CTC\n        return out\nprint(\"INFO: SimpleRNN defined\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.888831Z","iopub.execute_input":"2026-02-16T08:44:54.889484Z","iopub.status.idle":"2026-02-16T08:44:54.895148Z","shell.execute_reply.started":"2026-02-16T08:44:54.889454Z","shell.execute_reply":"2026-02-16T08:44:54.894303Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EmbeddedRNN(nn.Module):\n    def __init__(self, input_size, embed_size, hidden_size, num_classes):\n        super().__init__()\n\n        self.embedding = nn.Linear(input_size, embed_size)\n\n        self.rnn = nn.RNN(\n            input_size=embed_size,\n            hidden_size=hidden_size,\n            batch_first=True\n        )\n\n        self.classifier = nn.Linear(hidden_size, num_classes)\n        self.log_softmax = nn.LogSoftmax(dim=-1)\n\n    def forward(self, x):\n\n        x = self.embedding(x)       \n        out, _ = self.rnn(x)      \n        out = self.classifier(out)  \n        out = self.log_softmax(out)\n        out = out.permute(1, 0, 2)  \n\n        return out\nprint(\"INFO: EmbeddedRNN defined\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.896028Z","iopub.execute_input":"2026-02-16T08:44:54.896311Z","iopub.status.idle":"2026-02-16T08:44:54.908434Z","shell.execute_reply.started":"2026-02-16T08:44:54.896237Z","shell.execute_reply":"2026-02-16T08:44:54.907888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_classes = len(letter_to_int)\n\nmodel = SimpleRNN(\n    input_size=63,\n    hidden_size=128,\n    num_classes=num_classes\n).to(DEVICE)\n\nprint(f\"Model: SimpleRNN / Parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.909233Z","iopub.execute_input":"2026-02-16T08:44:54.909456Z","iopub.status.idle":"2026-02-16T08:44:54.922051Z","shell.execute_reply.started":"2026-02-16T08:44:54.909428Z","shell.execute_reply":"2026-02-16T08:44:54.921474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = EmbeddedRNN(\n    input_size=63,         \n    embed_size=128,         \n    hidden_size=128,\n    num_classes=len(letter_to_int)\n).to(DEVICE)\n\nprint(f\"Model: EmbeddedRNN / Parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.92288Z","iopub.execute_input":"2026-02-16T08:44:54.923125Z","iopub.status.idle":"2026-02-16T08:44:54.936994Z","shell.execute_reply.started":"2026-02-16T08:44:54.923105Z","shell.execute_reply":"2026-02-16T08:44:54.936284Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\nwith torch.no_grad():\n    outputs = model(X.to(DEVICE))\n\nprint(outputs.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.93784Z","iopub.execute_input":"2026-02-16T08:44:54.938129Z","iopub.status.idle":"2026-02-16T08:44:54.948705Z","shell.execute_reply.started":"2026-02-16T08:44:54.938097Z","shell.execute_reply":"2026-02-16T08:44:54.948034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CTCLoss(\n    blank=letter_to_int[\"<blank>\"],\n    zero_infinity=True,\n    reduction='mean'  # ensure proper reduction\n)\n\nloss = criterion(\n    outputs,    # (T, B, C)\n    Y.to(DEVICE),\n    in_len,\n    tar_len\n)\n\nprint(\"CTC loss:\", loss.item())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.949507Z","iopub.execute_input":"2026-02-16T08:44:54.949768Z","iopub.status.idle":"2026-02-16T08:44:54.960952Z","shell.execute_reply.started":"2026-02-16T08:44:54.949747Z","shell.execute_reply":"2026-02-16T08:44:54.960355Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader, optimizer, criterion, device):\n    model.train()\n    total_loss = 0.0\n    num_batches = 0\n\n    for batch in loader:\n\n        if batch is None:\n            continue\n\n        X, Y, input_lens, target_lens = batch\n\n        X = X.to(device)\n        Y = Y.to(device)\n        input_lens = input_lens.to(device)\n        target_lens = target_lens.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(X)  # (T, B, C)\n\n        loss = criterion(\n            outputs,\n            Y,\n            input_lens,\n            target_lens\n        )\n\n        loss.backward()\n\n\n        optimizer.step()\n\n        total_loss += loss.item()\n        num_batches += 1\n\n    avg_loss = total_loss / num_batches\n\n    return avg_loss\nprint(\"INFO: train_one_epoch method defined\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.961837Z","iopub.execute_input":"2026-02-16T08:44:54.962055Z","iopub.status.idle":"2026-02-16T08:44:54.971245Z","shell.execute_reply.started":"2026-02-16T08:44:54.962036Z","shell.execute_reply":"2026-02-16T08:44:54.970729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\nstart_time = time.time()\n\nnum_epochs = 5\ntrain_losses = []\ngrad_norms = []\nlrate = 1e-3\noptimizer = optim.Adam(model.parameters(), lr=lrate)\n\nprint(\"=\"*60)\nprint(f\"INITIAL TRAINING\")\nprint(\"=\"*60)\n\nprint(f\"[Very first training with TINY / lr={lrate}]\")\nfor epoch in range(num_epochs):\n    int_time = time.time()\n    loss = train_one_epoch(\n        model,\n        train_loader_tiny,\n        optimizer,\n        criterion,\n        DEVICE\n    )\n    train_losses.append(loss)\n    print(f\"Epoch {epoch+1} -> LOSS = {loss:.2f}\")\n    end_time = time.time()\n    print(f\"Elapsed time: {end_time - int_time:.2f} secs\")\n  \n\nfinal_time = time.time()\nprint(\"-\"*60)\nprint(f\"TOTAL time: {(final_time - start_time)/60:.2f} mins\")\nprint(\"-\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:44:54.972006Z","iopub.execute_input":"2026-02-16T08:44:54.972357Z","iopub.status.idle":"2026-02-16T08:46:36.415999Z","shell.execute_reply.started":"2026-02-16T08:44:54.972313Z","shell.execute_reply":"2026-02-16T08:46:36.415285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.figure(figsize=(6,4))\nplt.plot(train_losses, marker=\"o\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"CTC Loss\")\nplt.title(f\"Training Loss over {num_epochs} Epochs / lr={lrate}\")\nplt.grid(True)\nplt.show()\n\nprint(\"-\"*60)\nprint(f\"LOSS TREND\")\nprint(f\"First loss: {train_losses[0]:.2f}\")\nprint(f\"Last loss: {train_losses[-1]:.2f}\")\nprint(f\"Loss decreased: {train_losses[0] - train_losses[-1]:.2f}\")\nprint(\"-\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:36.416911Z","iopub.execute_input":"2026-02-16T08:46:36.417155Z","iopub.status.idle":"2026-02-16T08:46:36.54565Z","shell.execute_reply.started":"2026-02-16T08:46:36.417132Z","shell.execute_reply":"2026-02-16T08:46:36.544863Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"creating data loader with MICRO size (10)\")\ntrain_df_micro = train_df_split.sample(10, random_state=42)\ntrain_dataset_micro = ASLRightHandDataset(train_df_micro)\ntrain_loader_micro = DataLoader(\n  train_dataset_micro,\n  batch_size=2,\n  shuffle=True,\n  collate_fn=collate_fn,\n  num_workers=0\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:36.546679Z","iopub.execute_input":"2026-02-16T08:46:36.546976Z","iopub.status.idle":"2026-02-16T08:46:36.553717Z","shell.execute_reply.started":"2026-02-16T08:46:36.546945Z","shell.execute_reply":"2026-02-16T08:46:36.553011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # reset model\n# model = EmbeddedRNN(\n#   input_size=63,\n#   embed_size=128,\n#   hidden_size=128,\n#   num_classes=len(letter_to_int)\n# ).to(DEVICE)\n\n# optimizer = optim.Adam(model.parameters(), lr=1e-3)\n\n# print(\"INFO: Model reset completed\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:36.554727Z","iopub.execute_input":"2026-02-16T08:46:36.55497Z","iopub.status.idle":"2026-02-16T08:46:36.564256Z","shell.execute_reply.started":"2026-02-16T08:46:36.554949Z","shell.execute_reply":"2026-02-16T08:46:36.563515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# start_time = time.time()\n\n# num_epochs = 5\n# train_losses = []\n# grad_norms = []\n# lrate=1e-4\n# optimizer = optim.Adam(model.parameters(), lr=lrate)\n\n# print(\"=\"*60)\n# print(f\"NEXT TRAINING\")\n# print(\"=\"*60)\n\n# print(f\"[Now training with TINY / lr={lrate}]\")\n# for epoch in range(num_epochs):\n#     int_time = time.time()\n#     loss = train_one_epoch(\n#         model,\n#         train_loader_tiny,\n#         optimizer,\n#         criterion,\n#         DEVICE\n#     )\n#     train_losses.append(loss)\n#     print(f\"Epoch {epoch+1} -> LOSS = {loss:.2f}\")\n#     end_time = time.time()\n#     print(f\"Elapsed time: {end_time - int_time:.2f} secs\")\n\n\n# final_time = time.time()\n# print(\"-\"*60)\n# print(f\"TOTAL time: {(final_time - start_time)/60:.2f} mins\")\n# print(\"-\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:36.567696Z","iopub.execute_input":"2026-02-16T08:46:36.567981Z","iopub.status.idle":"2026-02-16T08:46:36.576503Z","shell.execute_reply.started":"2026-02-16T08:46:36.56796Z","shell.execute_reply":"2026-02-16T08:46:36.575848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# plt.figure(figsize=(6,4))\n# plt.plot(train_losses, marker=\"o\")\n# plt.xlabel(\"Epoch\")\n# plt.ylabel(\"CTC Loss\")\n# plt.title(f\"Training Loss over {num_epochs} Epochs / lr={lrate}\")\n# plt.grid(True)\n# plt.show()\n\n# print(\"-\"*60)\n# print(f\"LOSS TREND\")\n# print(f\"first loss: {train_losses[0]:.2f}\")\n# print(f\"Last loss: {train_losses[-1]:.2f}\")\n# print(f\"Loss decreased: {train_losses[0] - train_losses[-1]:.2f}\")\n# print(\"-\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:36.577385Z","iopub.execute_input":"2026-02-16T08:46:36.577675Z","iopub.status.idle":"2026-02-16T08:46:36.590106Z","shell.execute_reply.started":"2026-02-16T08:46:36.577644Z","shell.execute_reply":"2026-02-16T08:46:36.589457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =================================\n# CER IMPLEMENTATION using TorchMetrics\n# =================================\nfrom torchmetrics.text import CharErrorRate\n\ndef ctc_decode(log_probs, int_to_letter, blank_id=0):\n  \"\"\"Simple CTC greedy decoding\"\"\"\n  pred_indices = torch.argmax(log_probs, dim=-1).cpu().numpy()\n  decoded = []\n  previous = None\n\n  for idx in pred_indices:\n      if idx == previous or idx == blank_id:\n          previous = idx\n          continue\n      decoded.append(int_to_letter[idx])\n      previous = idx\n\n  return ''.join(decoded)\n\n\ndef evaluate_cer(model, dataloader, int_to_letter, device, blank_id=0):\n  \"\"\"Evaluate model and return average CER using TorchMetrics\"\"\"\n  model.eval()\n  cer_metric = CharErrorRate()\n\n  all_preds = []\n  all_targets = []\n\n  with torch.no_grad():\n      for batch in dataloader:\n          if batch is None:\n              continue\n\n          X, Y, input_lens, target_lens = batch\n          X = X.to(device)\n\n          # Get predictions\n          outputs = model(X)  # (T, B, C)\n\n          # Decode each sequence\n          batch_size = outputs.shape[1]\n          Y_list = Y.cpu().numpy().tolist()\n          start_idx = 0\n\n          for i in range(batch_size):\n              # Decode prediction\n              pred_text = ctc_decode(outputs[:, i, :], int_to_letter, blank_id)\n              all_preds.append(pred_text)\n\n              # Decode target\n              target_len = target_lens[i]\n              target_indices = Y_list[start_idx:start_idx + target_len]\n              target_text = ''.join([int_to_letter[idx] for idx in target_indices])\n              all_targets.append(target_text)\n              start_idx += target_len\n\n  # Calculate CER using TorchMetrics\n  cer = cer_metric(all_preds, all_targets)\n  return cer.item()\n\nprint(\"INFO: CER implemented\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:36.590991Z","iopub.execute_input":"2026-02-16T08:46:36.591255Z","iopub.status.idle":"2026-02-16T08:46:36.603235Z","shell.execute_reply.started":"2026-02-16T08:46:36.591226Z","shell.execute_reply":"2026-02-16T08:46:36.602585Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# print(\"=\"*60)\n# print(\"QUICK DEBUG\")\n# print(\"=\"*60)\n\n# # quick diagnostics and recommendations\n\n# # check data\n# X, Y, input_lens, target_lens = next(iter(train_loader_tiny))\n# print(f\"-data check:\")\n# print(f\"   NaN in X: {torch.isnan(X).any().item()}\")\n# print(f\"   Input lenghts are valid: {(input_lens >= target_lens).all().item()}\")\n\n# # check loss trend\n# print(f\"--LOSS trend:\")\n# print(f\"   first LOSS: {train_losses[0]:.2f}\")\n# print(f\"   last LOSS: {train_losses[-1]:.2f}\")\n# print(f\"   delta decrease: {train_losses[0] > train_losses[-1]}\")\n\n# # check model output\n# model.eval()\n# with torch.no_grad():\n#   X_test = X[:1].to(DEVICE)\n#   out = model(X_test)\n#   pred_classes = torch.argmax(out[:, 0, :], dim=-1)\n#   unique = torch.unique(pred_classes)\n#   print(f\"---Model output:\")\n#   print(f\"   Unique predictions: {len(unique)} / {len(letter_to_int)}\")\n\n# print(\"--------------\")\n# print(\"Conclussions\")\n# print(\"--------------\")\n\n# if not train_losses[0] > train_losses[-1]:\n#   print(\" (BAD) LOSS not decreasing\")\n# elif len(unique) < 5:\n#   print(\" (BAD) model stuck on few classes (<5)\")\n# else:\n#   print(\"  [WARNING] model is learning but very slow!\")\n#   print(\"next step: train longer or more data?)\")\n\n# print(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:36.604039Z","iopub.execute_input":"2026-02-16T08:46:36.604666Z","iopub.status.idle":"2026-02-16T08:46:37.018247Z","shell.execute_reply.started":"2026-02-16T08:46:36.604622Z","shell.execute_reply":"2026-02-16T08:46:37.017534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# print(\"=\"*60)\n# print(\"VERIFY DATA QUALITY\")\n# print(\"=\"*60)\n\n# # check only one batch\n# X, Y, input_lens, target_lens = next(iter(train_loader_tiny))\n\n# print(\"Data quality CHECK:\")\n# print(f\"  X shape: {X.shape}\")\n# print(f\"  X has NaN: {torch.isnan(X).any().item()}\")\n# print(f\"  X has Inf: {torch.isinf(X).any().item()}\")\n# print(f\"  X min: {X.min().item():.3f}, max: {X.max().item():.3f}\")\n# print(f\"  Y shape: {Y.shape}\")\n# print(f\"  Input lengths: {input_lens}\")\n# print(f\"  Target lengths: {target_lens}\")\n\n# # check CTC req: input_len >= target_len\n# print(\"\\nCTC Length Check:\")\n# for i in range(len(input_lens)):\n#   in_len = input_lens[i].item()\n#   tar_len = target_lens[i].item()\n#   status = \"**GOOD**\" if in_len >= tar_len else \"//BAD//\"\n#   print(f\"  Sample {i}: input={in_len}, target={tar_len} [{status}]\")\n\n# # what to look for:\n# # GOOD: No NaN or Inf values\n# # GOOD: All input_lens >= target_lens\n# # BAD: If input < target, CTC will fail silently","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:37.019177Z","iopub.execute_input":"2026-02-16T08:46:37.019401Z","iopub.status.idle":"2026-02-16T08:46:37.418561Z","shell.execute_reply.started":"2026-02-16T08:46:37.019379Z","shell.execute_reply":"2026-02-16T08:46:37.41782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=\"*60)\nprint(\"MONITOR GRADIENTS\")\nprint(\"=\"*60)\n\ndef train_one_epoch_with_gradients(model, loader, optimizer, criterion, device):\n  model.train()\n  total_loss = 0.0\n  grad_norms = []\n\n  for batch in loader:\n      if batch is None:\n          continue\n\n      X, Y, input_lens, target_lens = batch\n      X, Y = X.to(device), Y.to(device)\n\n      optimizer.zero_grad()\n      outputs = model(X)\n      loss = criterion(outputs, Y, input_lens, target_lens)\n      loss.backward()\n\n      # calculate gradient norm\n      total_norm = 0.0\n      for p in model.parameters():\n          if p.grad is not None:\n              total_norm += p.grad.data.norm(2).item() ** 2\n      total_norm = total_norm ** 0.5\n      grad_norms.append(total_norm)\n\n      optimizer.step()\n      total_loss += loss.item()\n\n  avg_loss = total_loss / len(loader)\n  avg_grad = sum(grad_norms) / len(grad_norms)\n\n  return avg_loss, avg_grad\n\n# train just one epoch and check\nloss, grad_norm = train_one_epoch_with_gradients(\n  model, train_loader_tiny, optimizer, criterion, DEVICE\n)\n\nprint(f\"LOSS: {loss:.3f}\")\n\nstatus = \"**GOOD**\" if grad_norm > 0.0001 or grad_norm < 10 else \"//BAD//\"\nprint(f\"Average Gradient Norm: {grad_norm:.6f} [{status}]\")\n\n# what to look for:\n# GOOD: gradient norm between 0.01 - 10.0\n# BAD: gradient norm < 0.0001 (vanishing case is very possible)\n# BAD: gradient norm > 10 (exploding case is very possible)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:37.419568Z","iopub.execute_input":"2026-02-16T08:46:37.420083Z","iopub.status.idle":"2026-02-16T08:46:57.332326Z","shell.execute_reply.started":"2026-02-16T08:46:37.420048Z","shell.execute_reply":"2026-02-16T08:46:57.33159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# print(\"=\"*60)\n# print(\"LEARNING_RATES TEST\")\n# print(\"=\"*60)\n\n# learning_rates = [1e-2, 1e-3, 1e-4]\n# results = {}\n\n# for lr in learning_rates:\n#   print(f\"\\nTesting lr={lr}\")\n\n#   # reset model\n#   model = EmbeddedRNN(\n#       input_size=63, embed_size=128, hidden_size=128,\n#       num_classes=len(letter_to_int)\n#   ).to(DEVICE)\n\n#   optimizer = optim.Adam(model.parameters(), lr=lr)\n\n#   # train for 10 epochs\n#   losses = []\n#   for epoch in range(10):\n#       loss, _ = train_one_epoch_with_gradients(\n#           model, train_loader_micro, optimizer, criterion, DEVICE\n#       )\n#       losses.append(loss)\n\n#   results[lr] = losses\n#   print(f\"  start: {losses[0]:.3f}, end: {losses[-1]:.3f}, delta: {losses[0]-losses[-1]:.3f}\")\n\n# # comparison\n# plt.figure(figsize=(10, 5))\n# for lr, losses in results.items():\n#   plt.plot(losses, marker='o', label=f'LR={lr}')\n# plt.xlabel('Epoch')\n# plt.ylabel('Loss')\n# plt.title('Learning Rate Comparison')\n# plt.legend()\n# plt.grid(True)\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:46:57.33335Z","iopub.execute_input":"2026-02-16T08:46:57.33369Z","iopub.status.idle":"2026-02-16T08:47:28.512856Z","shell.execute_reply.started":"2026-02-16T08:46:57.333656Z","shell.execute_reply":"2026-02-16T08:47:28.512232Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# print(\"=\"*60)\n# print(\"LABELS TEST\")\n# print(\"=\"*60)\n\n# # Check random samples\n# samples = [1,12,25,45,75,100]\n# for ix in samples:\n#     row = train_df_split.iloc[ix]\n#     print(\"--------------------\")\n#     print(f\"Label Verification for sample #{ix}\")\n#     print(f\"  Original phrase: '{row['phrase']}'\")\n#     print(f\"  Encoded: {row['encoded']}\")\n    \n#     # Decode it back\n#     decoded = ''.join([int_to_letter[idx] for idx in row['encoded']])\n#     print(f\"  Decoded back: '{decoded}'\")\n#     print(f\"  Match: {decoded == row['phrase']}\")\n#     print(\"  -------------\")\n#     # Check vocabulary coverage\n#     all_chars = set(''.join(train_df_split['phrase'].tolist()))\n#     vocab_chars = set(int_to_letter.values()) - {'<blank>'}\n    \n#     print(f\"\\n  Characters in data: {len(all_chars)}\")\n#     print(f\"  Characters in vocab: {len(vocab_chars)}\")\n#     print(f\"  Missing from vocab: {all_chars - vocab_chars}\")\n\n# # What to look for:\n# # GOOD: Decoded matches original\n# # GOOD: No missing characters from vocab","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:47:28.724277Z","iopub.execute_input":"2026-02-16T08:47:28.724472Z","iopub.status.idle":"2026-02-16T08:47:28.817178Z","shell.execute_reply.started":"2026-02-16T08:47:28.724452Z","shell.execute_reply":"2026-02-16T08:47:28.816366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"creating data loader with MINI size (100)\")\ntrain_df_mini = train_df_split.sample(100, random_state=42)\ntrain_dataset_mini = ASLRightHandDataset(train_df_mini)\ntrain_loader_mini = DataLoader(\n  train_dataset_micro,\n  batch_size=2,\n  shuffle=True,\n  collate_fn=collate_fn,\n  num_workers=0\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:47:28.818901Z","iopub.execute_input":"2026-02-16T08:47:28.819149Z","iopub.status.idle":"2026-02-16T08:47:28.834712Z","shell.execute_reply.started":"2026-02-16T08:47:28.819128Z","shell.execute_reply":"2026-02-16T08:47:28.833984Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=\"*60)\nprint(\"OVERFITING TEST\")\nprint(\"=\"*60)\n\nlrate=1e-3\n\n# reset model\nmodel = EmbeddedRNN(\n  input_size=63,\n  embed_size=128,\n  hidden_size=256,\n  num_classes=len(letter_to_int)\n).to(DEVICE)\n\noptimizer = optim.Adam(model.parameters(), lr=lrate)\n\nnum_epochs=300\ntrain_losses = []\n# train for many epochs on MINI\nstart_time = time.time()\ninit_time = time.time()\nfor epoch in range(num_epochs):\n  loss, _ = train_one_epoch_with_gradients(\n      model, train_loader_mini, optimizer, criterion, DEVICE\n  )\n  if (epoch + 1) % 20 == 0:\n      train_losses.append(loss)\n      # check CER on same training data\n      train_cer = evaluate_cer(model, train_loader_mini, int_to_letter, DEVICE)\n      print(f\"Epoch {epoch+1}: LOSS={loss:.3f}, training CER={train_cer:.3f}\")\n      last_time = time.time()\n      print(f\"Elapsed time: { last_time - start_time:.2f} secs\")\n      start_time = time.time()\n      \nfinal_time = time.time()\nprint(\"-\"*60)\nprint(f\"TOTAL time: {(final_time - init_time)/60:.2f} mins\")\nprint(\"-\"*60)\n\n# what to expect:\n# GOOD (model can memorize): LOSS drops to <1, CER drops to <0.1\n# BAD (model is not learning at all): LOSS stays ~3, CER stays > 0.8 → possible problem with model architecture or data pipeline (?)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:47:28.836083Z","iopub.execute_input":"2026-02-16T08:47:28.836325Z","iopub.status.idle":"2026-02-16T09:05:17.567267Z","shell.execute_reply.started":"2026-02-16T08:47:28.836304Z","shell.execute_reply":"2026-02-16T09:05:17.566576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save({\n    'model_state_dict': model.state_dict(),\n    'model_config': {\n        'input_size': 63,\n        'hidden_size': 128,\n        'num_classes': len(letter_to_int),\n        'num_layers': 3\n    },\n    'vocab': {\n        'letter_to_int': letter_to_int,\n        'int_to_letter': int_to_letter\n    },\n    'training_config': {\n        'max_frames': MAX_FRAMES,\n        'learning_rate': lrate,\n        'batch_size': 2\n    }\n}, 'asl_model_complete_overfit_large.pt')\n\nprint(\"✓ Complete model saved to: asl_model_complete_overfit_large.pt\")\nprint(\"\\nThis file contains:\")\nprint(\"  - Model weights\")\nprint(\"  - Architecture configuration\")\nprint(\"  - Vocabulary mappings\")\nprint(\"  - All hyperparameters\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:05:17.568146Z","iopub.execute_input":"2026-02-16T09:05:17.568426Z","iopub.status.idle":"2026-02-16T09:05:17.575876Z","shell.execute_reply.started":"2026-02-16T09:05:17.568402Z","shell.execute_reply":"2026-02-16T09:05:17.575313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=\"*60)\nprint(\"MODEL OUTPUTS TEST\")\nprint(\"=\"*60)\n\nmodel.eval()\nwith torch.no_grad():\n  X, Y, input_lens, target_lens = next(iter(train_loader_micro))\n  X = X.to(DEVICE)\n\n  outputs = model(X)  # (T, B, C)\n\n  # check first sample\n  probs = torch.exp(outputs[:, 0, :])  # Convert log_probs to probs\n\n  # check if model is predicting same class everywhere\n  pred_classes = torch.argmax(outputs[:, 0, :], dim=-1)\n  unique_preds = torch.unique(pred_classes)\n\n  print(\"Output Analysis (MICRO) (first sequence):\")\n  print(f\"  Unique predicted classes: {len(unique_preds)} / {len(letter_to_int)}\")\n  print(f\"  Most common prediction: {pred_classes.mode().values.item()}\")\n  print(f\"  Max probability: {probs.max().item():.3f}\")\n  print(f\"  Min probability: {probs.min().item():.6f}\")\n\n  # Decode prediction\n  pred_text = ctc_decode(outputs[:, 0, :], int_to_letter, blank_id=0)\n  print(f\"  Decoded: '{pred_text}'\")\n\n# What to look for:\n# BAD: Only 1-2 unique classes (model stuck)\n# BAD: Empty decoded text (model only outputs blank)\n# GOOD: Multiple classes, some characters decoded","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T08:47:28.51374Z","iopub.execute_input":"2026-02-16T08:47:28.513974Z","iopub.status.idle":"2026-02-16T08:47:28.723511Z","shell.execute_reply.started":"2026-02-16T08:47:28.513952Z","shell.execute_reply":"2026-02-16T08:47:28.722843Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(6,4))\nplt.plot(train_losses, marker=\"o\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"CTC Loss\")\nplt.title(f\"Training Loss over {num_epochs} Epochs / lr={lrate}\")\nplt.grid(True)\nplt.show()\n\nprint(\"-\"*60)\nprint(f\"LOSS TREND\")\nprint(f\"first loss: {train_losses[0]:.2f}\")\nprint(f\"Last loss: {train_losses[-1]:.2f}\")\nprint(f\"Loss decreased: {train_losses[0] - train_losses[-1]:.2f}\")\nprint(\"-\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:05:17.576717Z","iopub.execute_input":"2026-02-16T09:05:17.576971Z","iopub.status.idle":"2026-02-16T09:05:17.698014Z","shell.execute_reply.started":"2026-02-16T09:05:17.576951Z","shell.execute_reply":"2026-02-16T09:05:17.697324Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"  print(\"INFO: Evaluating CER on training data\")\n  cer = evaluate_cer(model, train_loader_tiny, int_to_letter, DEVICE, blank_id=0)\n  print(f\"Training CER: {cer:.4f} ({cer*100:.2f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:05:17.698937Z","iopub.execute_input":"2026-02-16T09:05:17.699208Z","iopub.status.idle":"2026-02-16T09:05:37.845619Z","shell.execute_reply.started":"2026-02-16T09:05:17.699186Z","shell.execute_reply":"2026-02-16T09:05:37.844876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"INFO: Creating DataLoader for validation (small->1000)\")\nval_df_small = val_df_split.sample(1000, random_state=SEED)               \nval_dataset = ASLRightHandDataset(val_df_small)\n\nval_loader = DataLoader(\n  val_dataset,\n  batch_size=4,\n  shuffle=False,\n  collate_fn=collate_fn,\n  num_workers=0\n)\n\nprint(f\" -> Validation loader created with {len(val_dataset)} samples...\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:05:37.846673Z","iopub.execute_input":"2026-02-16T09:05:37.846985Z","iopub.status.idle":"2026-02-16T09:05:37.854446Z","shell.execute_reply.started":"2026-02-16T09:05:37.846951Z","shell.execute_reply":"2026-02-16T09:05:37.853692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"INFO: Evaluating CER on validation data\")\ncer = evaluate_cer(model, val_loader, int_to_letter, DEVICE, blank_id=0)\nprint(f\" ->Validation CER: {cer:.4f} ({cer*100:.2f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:05:37.856063Z","iopub.execute_input":"2026-02-16T09:05:37.856517Z","iopub.status.idle":"2026-02-16T09:07:18.661838Z","shell.execute_reply.started":"2026-02-16T09:05:37.856494Z","shell.execute_reply":"2026-02-16T09:07:18.66102Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=\"*60)                                                                                                                                                              \nprint(\"VISUALIZE PREDICTIONS vs TARGETS\")\nprint(\"=\"*60)\n\ndef visualize_predictions(model, dataloader, int_to_letter, device, num_samples=10, blank_id=0):\n  \"\"\"\n  Visualize decoded predictions vs actual targets for validation samples\n  \"\"\"\n  model.eval()\n  cer_metric = CharErrorRate()\n\n  samples_shown = 0\n  print(\"Calculating validation predictions:\")\n\n  with torch.no_grad():\n      for batch in dataloader:\n          if batch is None:\n              continue\n\n          X, Y, input_lens, target_lens = batch\n          X = X.to(device)\n\n          # Get predictions\n          outputs = model(X)  # (T, B, C)\n\n          # Decode each sequence in batch\n          batch_size = outputs.shape[1]\n          Y_list = Y.cpu().numpy().tolist()\n          start_idx = 0\n\n          for i in range(batch_size):\n              if samples_shown >= num_samples:\n                  return\n\n              # Decode prediction\n              pred_text = ctc_decode(outputs[:, i, :], int_to_letter, blank_id)\n\n              # Decode target\n              target_len = target_lens[i]\n              target_indices = Y_list[start_idx:start_idx + target_len]\n              target_text = ''.join([int_to_letter[idx] for idx in target_indices])\n              start_idx += target_len\n\n              # Calculate individual CER\n              individual_cer = cer_metric([pred_text], [target_text]).item()\n\n              # Print comparison\n              print(f\"\\n{'='*60}\")\n              print(f\"Sample #{samples_shown + 1}:\")\n              print(f\"  Target:     '{target_text}'\")\n              print(f\"  Prediction: '{pred_text}'\")\n              print(f\"  CER:        {individual_cer:.4f} ({individual_cer*100:.2f}%)\")\n\n              # Visual indicator\n              if individual_cer < 0.2:\n                  print(f\"  Status:     ✓ EXCELLENT\")\n              elif individual_cer < 0.5:\n                  print(f\"  Status:     ~ GOOD\")\n              elif individual_cer < 0.8:\n                  print(f\"  Status:     ⚠ NEEDS IMPROVEMENT\")\n              else:\n                  print(f\"  Status:     ✗ POOR\")\n\n              samples_shown += 1\n\nprint(\"\\nShowing validation predictions:\")\nvisualize_predictions(model, val_loader, int_to_letter, DEVICE, num_samples=30, blank_id=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:10:00.734515Z","iopub.execute_input":"2026-02-16T09:10:00.73521Z","iopub.status.idle":"2026-02-16T09:10:04.133391Z","shell.execute_reply.started":"2026-02-16T09:10:00.735178Z","shell.execute_reply":"2026-02-16T09:10:04.132558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CER > 0.9 is very low, let's train on more data!\n# print(\"=\"*60)\n# print(f\"ONE BIG TRAINING\")\n# print(\"=\"*60)\n\n# start_time = time.time()\n\n# # more training samples\n# size=500\n# train_df_medium = train_df_split.sample(size, random_state=0)\n# train_dataset_medium = ASLRightHandDataset(train_df_medium)\n\n# print(\"Creating data loader with MEDIUM size (500)\")\n# train_loader_medium = DataLoader(\n#   train_dataset_medium,\n#   batch_size=8,\n#   shuffle=True,\n#   collate_fn=collate_fn,\n#   num_workers=0\n# )\n\n# # and more epochs\n# num_epochs = 3\n# lrate=1e-4\n# optimizer = optim.Adam(model.parameters(), lr=lrate)\n# train_losses = []\n\n# print(f\"Start training with {size} samples and lr={lrate}\")\n\n# for epoch in range(num_epochs):\n#   loss = train_one_epoch(model, train_loader_medium, optimizer, criterion, DEVICE)\n#   train_losses.append(loss)\n#   print(f\"Epoch {epoch+1}: loss = {loss:.2f}\")\n#   end_time = time.time()\n#   print(f\"Elapsed time: {end_time - start_time:.2f} seconds\")\n  \n\n# final_time = time.time()\n# print(\"-\"*60)\n# print(f\"TOTAL time: {(final_time - start_time)/60:.2f} minutes\")\n# print(\"-\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:07:18.662669Z","iopub.execute_input":"2026-02-16T09:07:18.662932Z","iopub.status.idle":"2026-02-16T09:07:18.667056Z","shell.execute_reply.started":"2026-02-16T09:07:18.662901Z","shell.execute_reply":"2026-02-16T09:07:18.666433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"  # print(\"INFO: Evaluating CER with more training data\")\n  # cer = evaluate_cer(model, train_loader_medium, int_to_letter, DEVICE, blank_id=0)\n  # print(f\"Training CER: {cer:.4f} ({cer*100:.2f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:07:18.667842Z","iopub.execute_input":"2026-02-16T09:07:18.668185Z","iopub.status.idle":"2026-02-16T09:07:18.681625Z","shell.execute_reply.started":"2026-02-16T09:07:18.668147Z","shell.execute_reply":"2026-02-16T09:07:18.681045Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# plt.figure(figsize=(6,4))\n# plt.plot(train_losses, marker=\"o\")\n# plt.xlabel(\"Epoch\")\n# plt.ylabel(\"CTC Loss\")\n# plt.title(f\"Training Loss over {num_epochs} Epochs / lr={lrate}\")\n# plt.grid(True)\n# plt.show()\n\n# print(\"-\"*60)\n# print(f\"LOSS TREND\")\n# print(f\"first LOSS: {train_losses[0]:.2f}\")\n# print(f\"last LOSS: {train_losses[-1]:.2f}\")\n# print(f\"delta: {train_losses[0] - train_losses[-1]:.2f}\")\n# print(\"-\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:07:18.682612Z","iopub.execute_input":"2026-02-16T09:07:18.68289Z","iopub.status.idle":"2026-02-16T09:07:18.693041Z","shell.execute_reply.started":"2026-02-16T09:07:18.682859Z","shell.execute_reply":"2026-02-16T09:07:18.692457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"INFO: [END_OF_NOTEBOOK]\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T09:07:18.693962Z","iopub.execute_input":"2026-02-16T09:07:18.694313Z","iopub.status.idle":"2026-02-16T09:07:18.704441Z","shell.execute_reply.started":"2026-02-16T09:07:18.694285Z","shell.execute_reply":"2026-02-16T09:07:18.703839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}