{"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":"none","dataSources":[{"sourceId":52950,"databundleVersionId":5973250,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"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-11T15:48:50.858835Z","iopub.execute_input":"2026-02-11T15:48:50.859858Z","iopub.status.idle":"2026-02-11T15:48:51.645502Z","shell.execute_reply.started":"2026-02-11T15:48:50.859817Z","shell.execute_reply":"2026-02-11T15:48:51.644293Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport os\nimport json\nimport numpy as np\nimport pandas as pd\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-11T15:48:55.707293Z","iopub.execute_input":"2026-02-11T15:48:55.707914Z","iopub.status.idle":"2026-02-11T15:49:02.092913Z","shell.execute_reply.started":"2026-02-11T15:48:55.707881Z","shell.execute_reply":"2026-02-11T15:49:02.091874Z"}},"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-11T15:49:17.451283Z","iopub.execute_input":"2026-02-11T15:49:17.452578Z","iopub.status.idle":"2026-02-11T15:49:17.584359Z","shell.execute_reply.started":"2026-02-11T15:49:17.45252Z","shell.execute_reply":"2026-02-11T15:49:17.583098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#división por participantes, trian, evalu i test no deberían compartir los mismos participantes\nparticipants = train_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 = train_df[train_df[\"participant_id\"].isin(train_ids)]\nval_df_split   = train_df[train_df[\"participant_id\"].isin(val_ids)]\ntest_df_split  = train_df[train_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-11T16:13:33.072717Z","iopub.execute_input":"2026-02-11T16:13:33.073764Z","iopub.status.idle":"2026-02-11T16:13:33.096677Z","shell.execute_reply.started":"2026-02-11T16:13:33.073725Z","shell.execute_reply":"2026-02-11T16:13:33.095688Z"}},"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-11T16:13:35.0954Z","iopub.execute_input":"2026-02-11T16:13:35.09663Z","iopub.status.idle":"2026-02-11T16:13:35.104834Z","shell.execute_reply.started":"2026-02-11T16:13:35.09658Z","shell.execute_reply":"2026-02-11T16:13:35.103371Z"}},"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-11T16:13:36.563822Z","iopub.execute_input":"2026-02-11T16:13:36.564643Z","iopub.status.idle":"2026-02-11T16:13:36.5716Z","shell.execute_reply.started":"2026-02-11T16:13:36.564609Z","shell.execute_reply":"2026-02-11T16:13:36.570316Z"}},"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-11T16:13:38.690795Z","iopub.execute_input":"2026-02-11T16:13:38.691391Z","iopub.status.idle":"2026-02-11T16:13:38.696715Z","shell.execute_reply.started":"2026-02-11T16:13:38.691355Z","shell.execute_reply":"2026-02-11T16:13:38.695419Z"}},"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-11T16:13:40.259208Z","iopub.execute_input":"2026-02-11T16:13:40.260344Z","iopub.status.idle":"2026-02-11T16:13:40.39334Z","shell.execute_reply.started":"2026-02-11T16:13:40.260196Z","shell.execute_reply":"2026-02-11T16:13:40.392367Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MAX_FRAMES = 160\nRIGHT_HAND_COLS = None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T16:13:42.227283Z","iopub.execute_input":"2026-02-11T16:13:42.228394Z","iopub.status.idle":"2026-02-11T16:13:42.233402Z","shell.execute_reply.started":"2026-02-11T16:13:42.228354Z","shell.execute_reply":"2026-02-11T16:13:42.232023Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"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-11T16:13:43.81789Z","iopub.execute_input":"2026-02-11T16:13:43.819064Z","iopub.status.idle":"2026-02-11T16:13:43.825804Z","shell.execute_reply.started":"2026-02-11T16:13:43.81901Z","shell.execute_reply":"2026-02-11T16:13:43.824506Z"}},"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-11T16:13:45.636209Z","iopub.execute_input":"2026-02-11T16:13:45.637653Z","iopub.status.idle":"2026-02-11T16:13:45.643887Z","shell.execute_reply.started":"2026-02-11T16:13:45.637587Z","shell.execute_reply":"2026-02-11T16:13:45.642425Z"}},"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-11T16:13:47.560908Z","iopub.execute_input":"2026-02-11T16:13:47.562364Z","iopub.status.idle":"2026-02-11T16:13:47.703548Z","shell.execute_reply.started":"2026-02-11T16:13:47.562305Z","shell.execute_reply":"2026-02-11T16:13:47.702436Z"}},"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-11T16:13:49.910354Z","iopub.execute_input":"2026-02-11T16:13:49.910722Z","iopub.status.idle":"2026-02-11T16:13:49.916977Z","shell.execute_reply.started":"2026-02-11T16:13:49.910694Z","shell.execute_reply":"2026-02-11T16:13:49.915568Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nclass 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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T16:13:52.414028Z","iopub.execute_input":"2026-02-11T16:13:52.41506Z","iopub.status.idle":"2026-02-11T16:13:52.423758Z","shell.execute_reply.started":"2026-02-11T16:13:52.415006Z","shell.execute_reply":"2026-02-11T16:13:52.422442Z"}},"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-11T16:14:02.055151Z","iopub.execute_input":"2026-02-11T16:14:02.055537Z","iopub.status.idle":"2026-02-11T16:14:02.063514Z","shell.execute_reply.started":"2026-02-11T16:14:02.055508Z","shell.execute_reply":"2026-02-11T16:14:02.062497Z"}},"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\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\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-11T16:15:27.132551Z","iopub.execute_input":"2026-02-11T16:15:27.133045Z","iopub.status.idle":"2026-02-11T16:15:28.11191Z","shell.execute_reply.started":"2026-02-11T16:15:27.132993Z","shell.execute_reply":"2026-02-11T16:15:28.110737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nclass 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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T15:49:55.081414Z","iopub.execute_input":"2026-02-11T15:49:55.082244Z","iopub.status.idle":"2026-02-11T15:49:55.089333Z","shell.execute_reply.started":"2026-02-11T15:49:55.082186Z","shell.execute_reply":"2026-02-11T15:49:55.087919Z"}},"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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T16:15:32.752053Z","iopub.execute_input":"2026-02-11T16:15:32.75257Z","iopub.status.idle":"2026-02-11T16:15:32.760583Z","shell.execute_reply.started":"2026-02-11T16:15:32.752535Z","shell.execute_reply":"2026-02-11T16:15:32.759195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_classes = len(letter_to_int)\n\n\nmodel = SimpleRNN(\n    input_size=63,\n    hidden_size=128,\n    num_classes=num_classes\n).to(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T15:50:00.120774Z","iopub.execute_input":"2026-02-11T15:50:00.121349Z","iopub.status.idle":"2026-02-11T15:50:00.133398Z","shell.execute_reply.started":"2026-02-11T15:50:00.121307Z","shell.execute_reply":"2026-02-11T15:50:00.131523Z"}},"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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T16:15:35.500981Z","iopub.execute_input":"2026-02-11T16:15:35.501514Z","iopub.status.idle":"2026-02-11T16:15:35.510663Z","shell.execute_reply.started":"2026-02-11T16:15:35.501481Z","shell.execute_reply":"2026-02-11T16:15:35.509445Z"}},"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)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T16:15:41.062957Z","iopub.execute_input":"2026-02-11T16:15:41.063499Z","iopub.status.idle":"2026-02-11T16:15:41.076939Z","shell.execute_reply.started":"2026-02-11T16:15:41.063465Z","shell.execute_reply":"2026-02-11T16:15:41.075966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ncriterion = nn.CTCLoss(\n    blank=letter_to_int[\"<blank>\"],\n    zero_infinity=True\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-11T16:15:42.679314Z","iopub.execute_input":"2026-02-11T16:15:42.6799Z","iopub.status.idle":"2026-02-11T16:15:42.689706Z","shell.execute_reply.started":"2026-02-11T16:15:42.679841Z","shell.execute_reply":"2026-02-11T16:15:42.68804Z"}},"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\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T16:15:45.02343Z","iopub.execute_input":"2026-02-11T16:15:45.02394Z","iopub.status.idle":"2026-02-11T16:15:45.031998Z","shell.execute_reply.started":"2026-02-11T16:15:45.023902Z","shell.execute_reply":"2026-02-11T16:15:45.030385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 20\ntrain_losses = []\ngrad_norms = []\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\n\n\nfor epoch in range(num_epochs):\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}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-11T16:15:47.355751Z","iopub.execute_input":"2026-02-11T16:15:47.356583Z","iopub.status.idle":"2026-02-11T16:25:06.137416Z","shell.execute_reply.started":"2026-02-11T16:15:47.356541Z","shell.execute_reply":"2026-02-11T16:25:06.135908Z"}},"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(\"Training Loss over Epochs\")\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T06:40:50.776234Z","iopub.execute_input":"2026-02-09T06:40:50.776603Z","iopub.status.idle":"2026-02-09T06:40:50.946177Z","shell.execute_reply.started":"2026-02-09T06:40:50.776583Z","shell.execute_reply":"2026-02-09T06:40:50.945439Z"}},"outputs":[],"execution_count":null}]}