{"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":[{"sourceId":46105,"databundleVersionId":5087314,"sourceType":"competition"},{"sourceId":14760012,"sourceType":"datasetVersion","datasetId":9434149}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# import os\n# import pandas as pd\n# import numpy as np\n# from tqdm import tqdm\n# from sklearn.preprocessing import LabelEncoder, StandardScaler\n# from sklearn.cluster import KMeans\n# from sklearn.model_selection import train_test_split\n# from sklearn.metrics import accuracy_score\n# import torch\n# import torch.nn as nn\n# from torch.utils.data import Dataset, DataLoader\n\n# # ==============================\n# # 1. LOAD CSV\n# # ==============================\n\n# CSV_PATH = \"/kaggle/input/train-dynamic-csv/train_dynamic.csv\"\n# df = pd.read_csv(CSV_PATH)\n\n\n# print(\"Total samples:\", len(df))\n# def find_parquet_root():\n#     for root, dirs, files in os.walk(\"/kaggle/input\"):\n#         if \"train_landmark_files\" in root:\n#             return root.split(\"train_landmark_files\")[0]\n#     return None\n\n# PARQUET_BASE = find_parquet_root()\n# print(\"Detected parquet base:\", PARQUET_BASE)\n\n# # Encode labels\n# label_encoder = LabelEncoder()\n# df[\"label\"] = label_encoder.fit_transform(df[\"sign\"])\n\n# num_classes = len(label_encoder.classes_)\n# print(\"Number of classes:\", num_classes)\n\n# # ==============================\n# # 2. FEATURE EXTRACTION FROM PARQUET\n# # ==============================\n\n\n# def extract_features(parquet_relative_path):\n#     full_path = os.path.join(PARQUET_BASE, parquet_relative_path)\n\n#     data = pd.read_parquet(full_path)\n\n#     # 🔥 Keep only numeric columns\n#     data = data.select_dtypes(include=[np.number])\n\n#     # Fill NaN\n#     data = data.fillna(0)\n\n#     arr = data.to_numpy(dtype=np.float32)\n\n#     # Statistical features\n#     mean = arr.mean(axis=0)\n#     std = arr.std(axis=0)\n#     max_val = arr.max(axis=0)\n#     min_val = arr.min(axis=0)\n\n#     # Velocity features\n#     if len(arr) > 1:\n#         velocity = np.diff(arr, axis=0)\n#         vel_mean = velocity.mean(axis=0)\n#         vel_std = velocity.std(axis=0)\n#     else:\n#         vel_mean = np.zeros(arr.shape[1])\n#         vel_std = np.zeros(arr.shape[1])\n\n#     features = np.concatenate([mean, std, max_val, min_val, vel_mean, vel_std])\n#     return features\n\n\n\n# print(\"Extracting features...\")\n\n# feature_list = []\n# valid_indices = []\n\n# for idx, path in enumerate(tqdm(df[\"path\"].values)):\n#     feat = extract_features(path)\n#     if feat is not None:\n#         feature_list.append(feat)\n#         valid_indices.append(idx)\n\n# # Keep only valid rows\n# df = df.iloc[valid_indices].reset_index(drop=True)\n# X = np.array(feature_list)\n\n# print(\"Final feature shape:\", X.shape)\n\n# # ==============================\n# # 3. CLUSTERING\n# # ==============================\n\n# # Scale features\n# scaler = StandardScaler()\n# X_scaled = scaler.fit_transform(X)\n\n# # Choose number of clusters\n# n_clusters = 3\n\n# kmeans = KMeans(n_clusters=n_clusters, random_state=42)\n# clusters = kmeans.fit_predict(X_scaled)\n\n# df[\"cluster\"] = clusters\n\n# print(\"Cluster distribution:\")\n# print(df[\"cluster\"].value_counts())\n\n# # ==============================\n# # 4. PYTORCH DATASET\n# # ==============================\n\n# class FeatureDataset(Dataset):\n#     def __init__(self, X, y):\n#         self.X = torch.tensor(X, dtype=torch.float32)\n#         self.y = torch.tensor(y, dtype=torch.long)\n\n#     def __len__(self):\n#         return len(self.X)\n\n#     def __getitem__(self, idx):\n#         return self.X[idx], self.y[idx]\n\n\n# # ==============================\n# # 5. SIMPLE CLASSIFIER\n# # ==============================\n\n# class Classifier(nn.Module):\n#     def __init__(self, input_dim, num_classes):\n#         super().__init__()\n#         self.model = nn.Sequential(\n#             nn.Linear(input_dim, 512),\n#             nn.ReLU(),\n#             nn.BatchNorm1d(512),\n#             nn.Dropout(0.3),\n\n#             nn.Linear(512, 256),\n#             nn.ReLU(),\n#             nn.BatchNorm1d(256),\n#             nn.Dropout(0.3),\n\n#             nn.Linear(256, num_classes)\n#         )\n\n#     def forward(self, x):\n#         return self.model(x)\n\n\n# # ==============================\n# # 6. TRAIN FUNCTION\n# # ==============================\n\n# def train_model(model, train_loader, val_loader, epochs=10):\n#     device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n#     model.to(device)\n\n#     criterion = nn.CrossEntropyLoss()\n#     optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\n\n#     for epoch in range(epochs):\n#         model.train()\n#         total_loss = 0\n\n#         for xb, yb in train_loader:\n#             xb, yb = xb.to(device), yb.to(device)\n\n#             optimizer.zero_grad()\n#             outputs = model(xb)\n#             loss = criterion(outputs, yb)\n#             loss.backward()\n#             optimizer.step()\n\n#             total_loss += loss.item()\n\n#         # Validation\n#         model.eval()\n#         preds = []\n#         actuals = []\n\n#         with torch.no_grad():\n#             for xb, yb in val_loader:\n#                 xb = xb.to(device)\n#                 outputs = model(xb)\n#                 predicted = torch.argmax(outputs, dim=1).cpu().numpy()\n\n#                 preds.extend(predicted)\n#                 actuals.extend(yb.numpy())\n\n#         acc = accuracy_score(actuals, preds)\n#         print(f\"Epoch {epoch+1}, Loss: {total_loss:.4f}, Val Accuracy: {acc:.4f}\")\n\n#     return model\n\n\n# # ==============================\n# # 7. TRAIN MODEL PER CLUSTER\n# # ==============================\n\n# models = {}\n\n# for cluster_id in range(n_clusters):\n#     print(f\"\\n===== Training for Cluster {cluster_id} =====\")\n\n#     cluster_df = df[df[\"cluster\"] == cluster_id]\n\n#     if len(cluster_df) < 50:\n#         print(\"Too few samples, skipping...\")\n#         continue\n\n#     X_cluster = X_scaled[cluster_df.index]\n#     y_cluster = cluster_df[\"label\"].values\n\n#     X_train, X_val, y_train, y_val = train_test_split(\n#         X_cluster, y_cluster, test_size=0.2, random_state=42\n#     )\n\n#     train_dataset = FeatureDataset(X_train, y_train)\n#     val_dataset = FeatureDataset(X_val, y_val)\n\n#     train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\n#     val_loader = DataLoader(val_dataset, batch_size=64)\n\n#     model = Classifier(X_cluster.shape[1], num_classes)\n#     model = train_model(model, train_loader, val_loader, epochs=10)\n\n#     models[cluster_id] = model\n\n# print(\"\\nTraining complete for all clusters.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T14:25:30.657198Z","iopub.execute_input":"2026-02-14T14:25:30.657986Z","iopub.status.idle":"2026-02-14T14:44:17.909174Z","shell.execute_reply.started":"2026-02-14T14:25:30.657953Z","shell.execute_reply":"2026-02-14T14:44:17.908385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.preprocessing import LabelEncoder\nfrom tqdm import tqdm\n\nCSV_PATH = \"/kaggle/input/train-dynamic-csv/train_dynamic.csv\"\nPARQUET_BASE = \"/kaggle/input/competitions/asl-signs/\"\n\ndf = pd.read_csv(CSV_PATH)\n\nlabel_encoder = LabelEncoder()\ndf[\"label\"] = label_encoder.fit_transform(df[\"sign\"])\nnum_classes = len(label_encoder.classes_)\n\nMAX_LEN = 200  # number of frames\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T15:28:20.961034Z","iopub.execute_input":"2026-02-14T15:28:20.961641Z","iopub.status.idle":"2026-02-14T15:28:24.258805Z","shell.execute_reply.started":"2026-02-14T15:28:20.961609Z","shell.execute_reply":"2026-02-14T15:28:24.258181Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_sequence(parquet_relative_path):\n    full_path = os.path.join(PARQUET_BASE, parquet_relative_path)\n    data = pd.read_parquet(full_path)\n\n    data = data.fillna(0)\n    data = data[[\"frame\", \"type\", \"landmark_index\", \"x\", \"y\", \"z\"]]\n\n    # Keep only hands\n    data = data[data[\"type\"].isin([\"left_hand\", \"right_hand\"])]\n\n    frames = data[\"frame\"].unique()\n    seq = []\n\n    for f in frames:\n        frame_data = data[data[\"frame\"] == f]\n\n        # Sort inside frame\n        frame_data = frame_data.sort_values([\"type\", \"landmark_index\"])\n\n        coords = frame_data[[\"x\", \"y\", \"z\"]].values\n\n        # If missing landmarks, pad inside frame\n        if coords.shape[0] < 42:   # 21 left + 21 right\n            pad = np.zeros((42 - coords.shape[0], 3))\n            coords = np.vstack([coords, pad])\n\n        coords = coords.flatten()\n        seq.append(coords)\n\n    if len(seq) == 0:\n        # Completely empty sequence safeguard\n        return np.zeros((MAX_LEN, 42 * 3), dtype=np.float32)\n\n    seq = np.array(seq)\n\n    # Pad / truncate time dimension\n    if len(seq) > MAX_LEN:\n        seq = seq[:MAX_LEN]\n    else:\n        pad = np.zeros((MAX_LEN - len(seq), seq.shape[1]))\n        seq = np.vstack([seq, pad])\n\n    # Normalize per sequence\n    mean = seq.mean(axis=0)\n    std = seq.std(axis=0) + 1e-6\n    seq = (seq - mean) / std\n\n    return seq.astype(np.float32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T15:28:27.021293Z","iopub.execute_input":"2026-02-14T15:28:27.021989Z","iopub.status.idle":"2026-02-14T15:28:27.029127Z","shell.execute_reply.started":"2026-02-14T15:28:27.021958Z","shell.execute_reply":"2026-02-14T15:28:27.028316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ASLDataset(Dataset):\n    def __init__(self, df):\n        self.df = df.reset_index(drop=True)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        seq = load_sequence(row[\"path\"])\n        label = row[\"label\"]\n\n        return torch.tensor(seq), torch.tensor(label)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T15:28:29.524681Z","iopub.execute_input":"2026-02-14T15:28:29.525033Z","iopub.status.idle":"2026-02-14T15:28:29.52979Z","shell.execute_reply.started":"2026-02-14T15:28:29.525004Z","shell.execute_reply":"2026-02-14T15:28:29.529134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\n\nclass TemporalCNN(nn.Module):\n    def __init__(self, input_dim, num_classes):\n        super().__init__()\n\n        self.conv1 = nn.Conv1d(input_dim, 512, kernel_size=5, padding=2)\n        self.bn1 = nn.BatchNorm1d(512)\n\n        self.conv2 = nn.Conv1d(512, 256, kernel_size=5, padding=2)\n        self.bn2 = nn.BatchNorm1d(256)\n\n        self.conv3 = nn.Conv1d(256, 128, kernel_size=3, padding=1)\n        self.bn3 = nn.BatchNorm1d(128)\n\n        self.pool = nn.AdaptiveAvgPool1d(1)\n\n        self.fc = nn.Linear(128, num_classes)\n\n    def forward(self, x):\n        # x shape: (batch, frames, features)\n        x = x.permute(0, 2, 1)  # -> (batch, features, frames)\n\n        x = torch.relu(self.bn1(self.conv1(x)))\n        x = torch.relu(self.bn2(self.conv2(x)))\n        x = torch.relu(self.bn3(self.conv3(x)))\n\n        x = self.pool(x).squeeze(-1)\n\n        return self.fc(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T15:28:31.357788Z","iopub.execute_input":"2026-02-14T15:28:31.358473Z","iopub.status.idle":"2026-02-14T15:28:31.364356Z","shell.execute_reply.started":"2026-02-14T15:28:31.358446Z","shell.execute_reply":"2026-02-14T15:28:31.363664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_classes = df[\"label\"].nunique()\nprint(\"Num classes:\", num_classes)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T16:25:30.76988Z","iopub.execute_input":"2026-02-14T16:25:30.770214Z","iopub.status.idle":"2026-02-14T16:25:30.775193Z","shell.execute_reply.started":"2026-02-14T16:25:30.770187Z","shell.execute_reply":"2026-02-14T16:25:30.774668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score\n\ndf_full = df.reset_index(drop=True)\ntrain_df, val_df = train_test_split(df_full, test_size=0.2, random_state=42,stratify=df_full[\"label\"])\n\n\ntrain_dataset = ASLDataset(train_df)\nval_dataset = ASLDataset(val_df)\n\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=16)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Determine input dim dynamically\nsample_seq = load_sequence(df.iloc[0][\"path\"])\ninput_dim = sample_seq.shape[1]\n\nmodel = TemporalCNN(input_dim, num_classes).to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\ncriterion = nn.CrossEntropyLoss()\n\nEPOCHS = 10\n\nfor epoch in range(EPOCHS):\n    model.train()\n    total_loss = 0\n\n    for xb, yb in tqdm(train_loader):\n        xb, yb = xb.to(device), yb.to(device)\n\n        optimizer.zero_grad()\n        out = model(xb)\n        loss = criterion(out, yb)\n        loss.backward()\n        optimizer.step()\n\n        total_loss += loss.item()\n\n    # Validation\n    model.eval()\n    preds, actuals = [], []\n\n    with torch.no_grad():\n        for xb, yb in val_loader:\n            xb = xb.to(device)\n            out = model(xb)\n            pred = torch.argmax(out, dim=1).cpu().numpy()\n            preds.extend(pred)\n            actuals.extend(yb.numpy())\n\n    acc = accuracy_score(actuals, preds)\n    print(f\"Epoch {epoch+1}, Loss: {total_loss:.2f}, Val Acc: {acc:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T16:27:34.493224Z","iopub.execute_input":"2026-02-14T16:27:34.493789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Num classes:\", num_classes)\nprint(df_small[\"label\"].nunique())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T15:42:17.410156Z","iopub.execute_input":"2026-02-14T15:42:17.410629Z","iopub.status.idle":"2026-02-14T15:42:17.415297Z","shell.execute_reply.started":"2026-02-14T15:42:17.410598Z","shell.execute_reply":"2026-02-14T15:42:17.414684Z"}},"outputs":[],"execution_count":null}]}