{"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":[{"sourceType":"competition","sourceId":46105,"databundleVersionId":5087314},{"sourceType":"datasetVersion","sourceId":16027833,"datasetId":10280176,"databundleVersionId":16993198}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"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\nimport pandas as pd\nimport numpy as np\nimport os\nimport random\nimport json\n\n# --- 1. البحث التلقائي عن المسارات (CSV, JSON, BASE_DIR) ---\nBASE_DIR = \"/kaggle/input/competitions/asl-signs\"\nCSV_PATH = \"/kaggle/input/competitions/asl-signs/train.csv\"\nJSON_PATH =\"/kaggle/input/competitions/asl-signs/sign_to_prediction_index_map.json\"\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        if filename == 'train.csv':\n            CSV_PATH = os.path.join(dirname, filename)\n            BASE_DIR = os.path.dirname(CSV_PATH)\n        if filename.endswith('.json'):\n            JSON_PATH = os.path.join(dirname, filename)\n\nif not CSV_PATH or not JSON_PATH:\n    raise FileNotFoundError(\"تأكدي من إضافة ملفات train.csv وملف الـ JSON للبيانات.\")\n\n# إعداد مجلد الكاش (Cache) لتسريع التدريب\nCACHE_DIR = \"/kaggle/working/npy_cache\"\nos.makedirs(CACHE_DIR, exist_ok=True)\n\n# --- 2. كلاس Dataset مع الالتزام بترتيب الـ JSON والـ Augmentation ---\nclass GoogleSignDataset(Dataset):\n    def __init__(self, df, base_dir, json_path, n_frames=30, augment=False):\n        self.df = df\n        self.base_dir = base_dir\n        self.n_frames = n_frames\n        self.augment = augment\n        \n        # تحميل الـ Label Map من الجيسون لضمان تطابق الأرقام\n        with open(json_path, 'r') as f:\n            self.label_map = json.load(f)\n\n    def __len__(self): return len(self.df)\n\n    def temporal_augmentation(self, x):\n        if len(x) > self.n_frames:\n            start = random.randint(0, len(x) - self.n_frames)\n            return x[start : start + self.n_frames]\n        return x\n\n    def spatial_augmentation(self, x):\n        # Noise\n        noise = np.random.normal(0, 0.002, x.shape)\n        x = x + noise\n        # Scale (90% - 110%)\n        scale = random.uniform(0.9, 1.1)\n        x = x * scale\n        # Shift\n        shift = random.uniform(-0.05, 0.05)\n        x = x + shift\n        return x\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            # نقرأ الملف مباشرة ونعالجه\n            data = pd.read_parquet(full_path)\n            # استخراج الإحداثيات وإعادة تشكيلها\n            xyz = data[['x', 'y', 'z']].values.reshape(-1, 543, 3)\n            # تحويل القيم المفقودة (NaN) إلى أصفار فوراً\n            return np.nan_to_num(xyz, nan=0.0).astype(np.float32)\n        except Exception as e:\n            # في حال وجود ملف تالف، نرجع مصفوفة فارغة بنفس الشكل\n            return np.zeros((self.n_frames, 543, 3), dtype=np.float32)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        landmarks = self.load_video(row['path'])\n        \n        if self.augment:\n            landmarks = self.temporal_augmentation(landmarks)\n            landmarks = self.spatial_augmentation(landmarks)\n\n        face = landmarks[:, 0:468, :].reshape(len(landmarks), -1)\n        body = landmarks[:, 468:, :].reshape(len(landmarks), -1)\n        \n        face = self.resample(face, self.n_frames)\n        body = self.resample(body, self.n_frames)\n        \n        # Standardize\n        face = (face - face.mean()) / (face.std() + 1e-6)\n        body = (body - body.mean()) / (body.std() + 1e-6)\n        \n        # الحصول على الـ Label من الـ JSON Map\n        label_idx = self.label_map[row['sign']]\n        \n        return torch.tensor(face, dtype=torch.float32), \\\n               torch.tensor(body, dtype=torch.float32), \\\n               torch.tensor(label_idx, dtype=torch.long)\n\n# --- 3. تجهيز الداتا ---\nfull_df = pd.read_csv(CSV_PATH)\ntrain_df, val_df = train_test_split(full_df, test_size=0.10, stratify=full_df['sign'], random_state=42)\n\ntrain_loader = DataLoader(GoogleSignDataset(train_df, BASE_DIR, JSON_PATH, augment=True), \n                          batch_size=64, shuffle=True, num_workers=4, pin_memory=True)\nval_loader = DataLoader(GoogleSignDataset(val_df, BASE_DIR, JSON_PATH, augment=False), \n                        batch_size=64, shuffle=False, num_workers=4, pin_memory=True)\n\nn_classes = len(pd.read_csv(CSV_PATH)['sign'].unique())\n\n# --- 4. الموديل (Average Pooling) ---\nclass DualStreamLSTM(nn.Module):\n    def __init__(self, n_classes):\n        super().__init__()\n        self.face_fc = nn.Linear(1404, 256)\n        self.face_lstm = nn.LSTM(256, 256, batch_first=True, num_layers=2, dropout=0.3)\n        self.body_fc = nn.Linear(225, 256)\n        self.body_lstm = nn.LSTM(256, 256, batch_first=True, num_layers=2, dropout=0.3)\n        self.classifier = nn.Sequential(\n            nn.BatchNorm1d(256),\n            nn.Linear(256, 512), nn.ReLU(), nn.Dropout(0.5),\n            nn.Linear(512, n_classes)\n        )\n\n    def forward(self, face, body):\n        f = torch.relu(self.face_fc(face))\n        _, (h_f, _) = self.face_lstm(f)\n        b = torch.relu(self.body_fc(body))\n        _, (h_b, _) = self.body_lstm(b)\n        combined = (h_f[-1] + h_b[-1]) / 2\n        return self.classifier(combined)\n\n# --- 5. الإعداد والتدريب ---\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = DualStreamLSTM(n_classes).to(device)\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\ncriterion = nn.CrossEntropyLoss()\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=3)\n# المسار الجديد بعد ما رفعتيه كـ Dataset\nMODEL_PATH = \"/kaggle/input/datasets/haidy112ibrahim/model-weights/best_asl_model_avg_66_modified.pt\"\n\n# تحميل الأوزان\nmodel.load_state_dict(torch.load(MODEL_PATH))\nmodel.to(device)\nprint(\"تم استعادة الموديل من الـ Dataset بنجاح!\")\nprint(f\" بدء التدريب مع مطابقة الـ JSON وتفعيل الـ Augmentation...\")\nbest_val_acc =  0.6781\n\nfor epoch in range(1, 101):\n    model.train()\n    t_loss, t_acc = 0, 0\n    for f, b, l in train_loader:\n        f, b, l = f.to(device), b.to(device), l.to(device)\n        optimizer.zero_grad()\n        out = model(f, b)\n        loss = criterion(out, l)\n        loss.backward()\n        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        optimizer.step()\n        t_loss += loss.item()\n        t_acc += (out.argmax(1) == l).sum().item()\n\n    model.eval()\n    v_loss, v_acc =  0.0\n    with torch.no_grad():\n        for f, b, l in val_loader:\n            f, b, l = f.to(device), b.to(device), l.to(device)\n            out = model(f, b)\n            v_loss += criterion(out, l).item()\n            v_acc += (out.argmax(1) == l).sum().item()\n\n    avg_v_acc = v_acc / len(val_df)\n    avg_train_loss = t_loss / len(train_loader)\n    avg_val_loss = v_loss / len(val_loader)\n    avg_train_acc = t_acc / len(train_df)\n    avg_val_acc = v_acc / len(val_df)\n    \n    scheduler.step(avg_val_loss)\n    if avg_v_acc > best_val_acc:\n        best_val_acc = avg_v_acc\n        \n        torch.save(model.state_dict(), \"best_asl_model_avg_66_modified.pt\")\n        print(f\"New best model saved with Acc: {best_val_acc:.4f}\")\n    \n    # السطر المعدل لإظهار الـ Loss والـ Accuracy\n    print(f\"Epoch {epoch:03d} | Train Loss: {avg_train_loss:.4f} Acc: {avg_train_acc:.4f} | Val Loss: {avg_val_loss:.4f} Acc: {avg_val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-30T12:02:05.229274Z","iopub.execute_input":"2026-04-30T12:02:05.230158Z"}},"outputs":[],"execution_count":null}]}