{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":46105,"databundleVersionId":5087314,"isSourceIdPinned":false}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport pandas as pd\nimport numpy as np\nimport os\nimport json\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# --- الإعدادات ---\nBASE_DIR = \"/kaggle/input/competitions/asl-signs\"\nCSV_PATH = os.path.join(BASE_DIR, \"train.csv\")\nwith open(os.path.join(BASE_DIR, \"sign_to_prediction_index_map.json\"), 'r') as f:\n    sign_map = json.load(f)\n    # 1. تيار الوجه (Face Stream) - [120 رقم]\nL_EYE_IDXS = [33, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7]\nR_EYE_IDXS = [362, 382, 381, 380, 374, 373, 390, 249, 263, 387, 386, 385, 384, 398, 466]\nL_BROW_IDXS = [70, 63, 105, 66, 107]\nR_BROW_IDXS = [336, 296, 334, 293, 300]\nLIPS_IDXS = [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291, 146, 91, 181, 84, 17, 314, 405, 321, 375]\n\n# 2. تيار الحركة (Movement Stream) - [96 رقم]\n# اليدين (21 يمين + 21 شمال) = 42 نقطة = 84 رقم\n# الجسم (أكتاف، كوع، معصم) = 6 نقط = 12 رقم\n# المجموع = 96 رقم\nPOSE_IDXS = [11, 12, 13, 14, 15, 16]\n\nclass ASLFinalDataset(Dataset):\n    def __init__(self, df, base_dir, sign_map, n_frames=30, training=True):\n        self.df = df\n        self.base_dir = base_dir\n        self.n_frames = n_frames\n        self.label_map = sign_map\n        self.training = training\n\n    def __len__(self):\n        return len(self.df)\n    \n    def resample(self, x, size):\n        if len(x) >= size:\n            indices = np.linspace(0, len(x) - 1, size).astype(int)\n        else:\n            indices = np.pad(np.arange(len(x)), (0, max(0, size - len(x))), 'edge')\n        return x[indices]\n\n    def load_video(self, path):\n        full_path = os.path.join(self.base_dir, path)\n        try:\n            raw = pd.read_parquet(full_path)\n            face = raw[raw['type'] == 'face'][['x', 'y']].values.reshape(-1, 468, 2)\n            lh = raw[raw['type'] == 'left_hand'][['x', 'y']].values.reshape(-1, 21, 2)\n            rh = raw[raw['type'] == 'right_hand'][['x', 'y']].values.reshape(-1, 21, 2)\n            pose = raw[raw['type'] == 'pose'][['x', 'y']].values.reshape(-1, 33, 2)\n\n            # Normalization\n            nose = face[:, 1, :][:, np.newaxis, :]\n            width = np.linalg.norm(pose[:, 11, :] - pose[:, 12, :], axis=1, keepdims=True)\n            width = np.where(width < 1e-6, 1.0, width)[:, np.newaxis, :]\n            def norm(d): return (d - nose) / width\n\n            # تيار الوجه (120 رقم)\n            f_idxs = L_EYE_IDXS + R_EYE_IDXS + L_BROW_IDXS + R_BROW_IDXS + LIPS_IDXS\n            f_feat = norm(face[:, f_idxs, :]).reshape(len(face), -1)\n            \n            # تيار الحركة (96 رقم)\n            m_feat = np.concatenate([norm(lh), norm(rh), norm(pose[:, POSE_IDXS, :])], axis=1).reshape(len(lh), -1)\n\n            return np.nan_to_num(f_feat, 0.0), np.nan_to_num(m_feat, 0.0)\n        except:\n            return np.zeros((self.n_frames, 120)), np.zeros((self.n_frames, 96))\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        face_d, move_d = self.load_video(row['path'])\n        \n        if self.training:\n            # Time Augmentation\n            ts = np.random.uniform(0.8, 1.2)\n            face_d = self.resample(face_d, int(self.n_frames * ts))\n            move_d = self.resample(move_d, int(self.n_frames * ts))\n            # Spatial Augmentation\n            sc = np.random.uniform(0.9, 1.1)\n            face_d, move_d = face_d * sc, move_d * sc\n\n        return torch.tensor(self.resample(face_d, self.n_frames), dtype=torch.float32), \\\n               torch.tensor(self.resample(move_d, self.n_frames), dtype=torch.float32), \\\n               torch.tensor(self.label_map[row['sign']], dtype=torch.long)\n\n# --- الموديل المطور (DualStream) ---\nclass ASLModelV2(nn.Module):\n    def __init__(self, n_classes):\n        super().__init__()\n        self.face_fc = nn.Linear(120, 128)\n        self.face_lstm = nn.LSTM(128, 128, batch_first=True, num_layers=2, bidirectional=True, dropout=0.3)\n        self.move_fc = nn.Linear(96, 256)\n        self.move_lstm = nn.LSTM(256, 256, batch_first=True, num_layers=2, bidirectional=True, dropout=0.3)\n        self.classifier = nn.Sequential(\n            nn.BatchNorm1d(768),\n            nn.Linear(768, 512), nn.ReLU(), nn.Dropout(0.5),\n            nn.Linear(512, n_classes)\n        )\n\n    def forward(self, face, move):\n        f = torch.relu(self.face_fc(face))\n        _, (f_h, _) = self.face_lstm(f)\n        f_feat = torch.cat([f_h[-2], f_h[-1]], dim=1)\n        \n        m = torch.relu(self.move_fc(move))\n        _, (m_h, _) = self.move_lstm(m)\n        m_feat = torch.cat([m_h[-2], m_h[-1]], dim=1)\n        \n        return self.classifier(torch.cat([f_feat, m_feat], dim=1))\n\n# --- التقسيم والتدريب ---\nfull_df = pd.read_csv(CSV_PATH)\nt_v_df, test_df = train_test_split(full_df, test_size=0.1, stratify=full_df['sign'], random_state=42)\ntrain_df, val_df = train_test_split(t_v_df, test_size=0.11, stratify=t_v_df['sign'], random_state=42)\n\ntrain_loader = DataLoader(ASLFinalDataset(train_df, BASE_DIR, sign_map, training=True), batch_size=64, shuffle=True ,num_workers=4)\nval_loader = DataLoader(ASLFinalDataset(val_df, BASE_DIR, sign_map, training=False), batch_size=64,num_workers=4)\ntest_loader = DataLoader(ASLFinalDataset(test_df, BASE_DIR, sign_map, training=False), batch_size=64,num_workers=4)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = ASLModelV2(len(sign_map)).to(device)\noptimizer = optim.AdamW(model.parameters(), lr=1e-3)\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\n\n# --- قبل الحلقة ---\nhistory = {'t_loss': [], 'v_loss': [], 't_acc': [], 'v_acc': []}\nbest_acc = 0\n\nprint(f\"{'Epoch':<8} | {'Train Loss':<12} | {'Train Acc':<12} | {'Val Loss':<12} | {'Val Acc':<12}\")\nprint(\"-\" * 75)\n\ntry:\n    for epoch in range(1, 51):\n        # [نفس كود التدريب والتقييم اللي فات بالظبط]\n        model.train()\n        t_loss, t_correct, t_total = 0, 0, 0\n        for f, m, l in train_loader:\n            f, m, l = f.to(device), m.to(device), l.to(device)\n            optimizer.zero_grad(); out = model(f, m); loss = criterion(out, l); loss.backward(); optimizer.step()\n            t_loss += loss.item(); t_correct += (out.argmax(1) == l).sum().item(); t_total += l.size(0)\n\n        model.eval()\n        v_loss, v_correct, v_total = 0, 0, 0\n        with torch.no_grad():\n            for f, m, l in val_loader:\n                f, m, l = f.to(device), m.to(device), l.to(device)\n                out = model(f, m); loss = criterion(out, l)\n                v_loss += loss.item(); v_correct += (out.argmax(1) == l).sum().item(); v_total += l.size(0)\n\n        # حساب وتسجيل النتائج\n        train_loss, train_acc = t_loss/len(train_loader), t_correct/t_total\n        val_loss, val_acc = v_loss/len(val_loader), v_correct/v_total\n        \n        history['t_loss'].append(train_loss); history['v_loss'].append(val_loss)\n        history['t_acc'].append(train_acc); history['v_acc'].append(val_acc)\n\n        print(f\"{epoch:<8} | {train_loss:<12.4f} | {train_acc:<12.4f} | {val_loss:<12.4f} | {val_acc:<12.4f}\")\n\n        if val_acc > best_acc:\n            best_acc = val_acc\n            torch.save(model.state_dict(), \"best_model_v2_augmented_normalized.pt\")\n            print(f\" New Best Model Saved!\")\n\nexcept KeyboardInterrupt:\n    print(\"\\n Training interrupted by user. Generating results from best saved model...\")\n\n# --- جزء عرض النتائج (هيشتغل سواء خلص الـ 50 أو وقفتِ يدوي) ---\nprint(\"\\n\" + \"=\"*30)\nprint(\" Generating Final Evaluation\")\nprint(\"=\"*30)\n\n# 1. رسم منحنيات الأداء\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\nax1.plot(history['t_loss'], label='Train'); ax1.plot(history['v_loss'], label='Val'); ax1.set_title('Loss'); ax1.legend()\nax2.plot(history['t_acc'], label='Train'); ax2.plot(history['v_acc'], label='Val'); ax2.set_title('Accuracy'); ax2.legend()\nplt.show()\n\n# 2. تحميل أفضل أوزان وعرض الماتريكس\nif os.path.exists(\"best_model_v2_augmented_normalized.pt\"):\n    model.load_state_dict(torch.load(\"best_model_v2_augmented_normalized.pt\"))\n    model.eval()\n    y_true, y_pred = [], []\n    with torch.no_grad():\n        for f, m, l in test_loader:\n            out = model(f.to(device), m.to(device))\n            y_pred.extend(out.argmax(1).cpu().numpy())\n            y_true.extend(l.numpy())\n\n    # رسم الماتريكس (أول 25 إشارة)\n    plt.figure(figsize=(12, 10))\n    sns.heatmap(confusion_matrix(y_true, y_pred)[:25, :25], annot=True, fmt='d', cmap='Blues')\n    plt.title(\"Confusion Matrix for Best Model\")\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-26T12:30:25.284266Z","iopub.execute_input":"2026-04-26T12:30:25.284878Z","iopub.status.idle":"2026-04-26T18:12:16.134127Z","shell.execute_reply.started":"2026-04-26T12:30:25.284847Z","shell.execute_reply":"2026-04-26T18:12:16.133380Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom sklearn.metrics import accuracy_score, classification_report, confusion_matrix\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# 1. تحميل أفضل أوزان تم حفظها أثناء التدريب\n# تأكدي أن الاسم مطابق للاسم في كود التدريب بتاعك\nmodel_path = \"best_model_v2_augmented_normalized.pt\"\n\nif os.path.exists(model_path):\n    model.load_state_dict(torch.load(model_path))\n    model.eval()\n    \n    y_true = []\n    y_pred = []\n    test_loss = 0\n\n    print(\"🚀 Running Final Evaluation on Test Set (Unseen Data)...\")\n\n    with torch.no_grad():\n        for f, m, l in test_loader: # الكود بتاعك بيرجع 3 حاجات: Face, Movement, Label\n            # نقل البيانات للـ GPU\n            f, m, l = f.to(device), m.to(device), l.to(device)\n            \n            # التوقع\n            outputs = model(f, m)\n            loss = criterion(outputs, l)\n            \n            test_loss += loss.item()\n            preds = outputs.argmax(1)\n            \n            y_true.extend(l.cpu().numpy())\n            y_pred.extend(preds.cpu().numpy())\n\n    # 2. حساب النتائج النهائية\n    final_acc = accuracy_score(y_true, y_pred)\n    avg_loss = test_loss / len(test_loader)\n\n    print(\"\\n\" + \"=\"*30)\n    print(f\"✅ Final Test Accuracy: {final_acc*100:.2f}%\")\n    print(f\"📉 Final Test Loss: {avg_loss:.4f}\")\n    print(\"=\"*30)\n\n    # 3. رسم الـ Confusion Matrix للـ Test Set كاملة\n    plt.figure(figsize=(15, 12))\n    # هنعرض أول 25 كلاس عشان الزحمة، وممكن تشيلي [:25] لو عايزة تشوفي كله\n    cm = confusion_matrix(y_true, y_pred)\n    sns.heatmap(cm[:25, :25], annot=True, fmt='d', cmap='Blues')\n    plt.title(\"Final Test Set Confusion Matrix (First 25 Signs)\")\n    plt.xlabel(\"Predicted\")\n    plt.ylabel(\"True\")\n    plt.show()\n\nelse:\n    print(f\"Error: File {model_path} not found. Make sure you trained the model first!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-26T18:21:50.868414Z","iopub.execute_input":"2026-04-26T18:21:50.869114Z","iopub.status.idle":"2026-04-26T18:23:33.912121Z","shell.execute_reply.started":"2026-04-26T18:21:50.869080Z","shell.execute_reply":"2026-04-26T18:23:33.911121Z"}},"outputs":[],"execution_count":null}]}