diff --git a/01.train_ODC.ipynb b/01.train_ODC.ipynb index 96cf2b0..55299ac 100755 --- a/01.train_ODC.ipynb +++ b/01.train_ODC.ipynb @@ -726,7 +726,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "2e955884-d4af-422d-a8e6-d436199540e0", "metadata": { "tags": [] @@ -772,57 +772,100 @@ " print(\" (Sử dụng nhiều spectral indices để cải thiện accuracy)\")\n", " \n", " try:\n", + " # ── FIX 1: Reproject training points to match raster CRS ──────────────\n", + " # Shapefile thường ở WGS84 (EPSG:4326, lon/lat ~105-106)\n", + " # Raster data ở UTM (EPSG:32648, easting ~500000+)\n", + " # Nếu không reproject → .sel() sẽ chọn sai pixel hoàn toàn!\n", + " raster_crs = None\n", + " try:\n", + " # Ưu tiên lấy CRS từ rioxarray\n", + " raster_crs = data.rio.crs\n", + " except Exception:\n", + " pass\n", + " if raster_crs is None:\n", + " # Fallback: dùng biến native_crs đã định nghĩa ở cell load data\n", + " raster_crs = native_crs if 'native_crs' in dir() else 'EPSG:32648'\n", + "\n", + " train_src_crs = train.crs if train.crs is not None else 'EPSG:4326'\n", + " print(f\" Training CRS : {train_src_crs}\")\n", + " print(f\" Raster CRS : {raster_crs}\")\n", + "\n", + " if str(train_src_crs).upper() != str(raster_crs).upper():\n", + " train_proj = train.to_crs(raster_crs)\n", + " print(f\" ✅ Reprojected training points → {raster_crs}\")\n", + " else:\n", + " train_proj = train\n", + " print(f\" ✅ CRS already match, no reprojection needed\")\n", + "\n", " # Extract features at training point locations\n", " X = []\n", " y = []\n", - " \n", + " skipped_nan = 0\n", + " skipped_label = 0\n", + " skipped_other = 0\n", + "\n", " # Available features from data\n", " available_features = ['ndvi_mean', 'ndvi_min', 'ndvi_max', 'ndvi_std', 'ndvi_range',\n", " 'ndwi_mean', 'ndbi_mean', 'evi_mean']\n", - " \n", + "\n", " # Check which features are actually available\n", " features_to_use = [f for f in available_features if f in data.data_vars]\n", - " \n", + "\n", " if not features_to_use:\n", " print(\" ❌ No spectral features found in dataset!\")\n", " print(\" Available variables:\", list(data.data_vars))\n", " model = None\n", " else:\n", " print(f\" Using {len(features_to_use)} features: {features_to_use}\")\n", - " \n", - " for idx, point in train.iterrows():\n", + "\n", + " for idx, point in train_proj.iterrows():\n", " try:\n", + " # ── FIX 2: Dùng tọa độ đã reproject ──────────────────────\n", + " px, py = point.geometry.x, point.geometry.y\n", + "\n", " # Extract all available features at this point\n", " feature_vec = []\n", " for feat_name in features_to_use:\n", " feat_val = float(data[feat_name].sel(\n", - " x=point.geometry.x, \n", - " y=point.geometry.y, \n", - " method='nearest'\n", + " x=px, y=py, method='nearest'\n", " ).values)\n", " feature_vec.append(feat_val)\n", - " \n", + "\n", " # Get label\n", - " label = label_mapping[point.Hientrang]\n", - " \n", - " # Only add if no NaN values\n", - " if not np.isnan(feature_vec).any():\n", - " X.append(feature_vec)\n", - " y.append(int(label))\n", + " try:\n", + " label = label_mapping[point.Hientrang]\n", + " except KeyError:\n", + " skipped_label += 1\n", + " continue\n", + "\n", + " # ── FIX 3: np.isnan phải nhận numpy array, không phải list ─\n", + " feature_arr = np.array(feature_vec, dtype=np.float32)\n", + " if np.isnan(feature_arr).any():\n", + " skipped_nan += 1\n", + " continue\n", + "\n", + " X.append(feature_vec)\n", + " y.append(int(label))\n", + "\n", " except Exception as e:\n", - " # Skip points with errors\n", + " skipped_other += 1\n", " continue\n", - " \n", + "\n", + " print(f\"\\n Points extracted : {len(X)}\")\n", + " print(f\" Skipped (NaN) : {skipped_nan}\")\n", + " print(f\" Skipped (label) : {skipped_label}\")\n", + " print(f\" Skipped (other) : {skipped_other}\")\n", + "\n", " if len(X) > 0:\n", " X = np.array(X)\n", " y = np.array(y)\n", - " print(f\" ✅ Extracted {len(X)} samples with {X.shape[1]} features each\")\n", - " \n", + " print(f\" ✅ {len(X)} samples × {X.shape[1]} features\")\n", + "\n", " # Show feature statistics\n", " print(f\"\\n Feature statistics:\")\n", " for i, feat_name in enumerate(features_to_use):\n", " print(f\" {feat_name:15s}: mean={X[:,i].mean():.3f}, std={X[:,i].std():.3f}\")\n", - " \n", + "\n", " # Split data\n", " print(f\"\\n[2] Splitting data (80-20)...\")\n", " from sklearn.model_selection import train_test_split\n", @@ -830,21 +873,21 @@ " X, y, test_size=0.2, random_state=42, stratify=y\n", " )\n", " print(f\" Train: {len(X_train)}, Test: {len(X_test)}\")\n", - " \n", + "\n", " # Show class distribution\n", " unique, counts = np.unique(y_train, return_counts=True)\n", " print(f\"\\n Class distribution in training set:\")\n", " for cls, count in zip(unique, counts):\n", " cls_name = [k for k, v in label_mapping.items() if v == str(cls)][0]\n", " print(f\" {cls}: {cls_name:15s} - {count:4d} samples ({count/len(y_train)*100:.1f}%)\")\n", - " \n", + "\n", " # Train model\n", " print(f\"\\n[3] Training Random Forest for LAND USE CLASSIFICATION...\")\n", " from sklearn.ensemble import RandomForestClassifier\n", " from sklearn.metrics import accuracy_score, classification_report\n", - " \n", + "\n", " model = RandomForestClassifier(\n", - " n_estimators=200, # More trees for better accuracy\n", + " n_estimators=200,\n", " max_depth=30,\n", " min_samples_split=5,\n", " random_state=42,\n", @@ -852,31 +895,31 @@ " verbose=1\n", " )\n", " model.fit(X_train, y_train)\n", - " \n", + "\n", " # Evaluate\n", " y_pred = model.predict(X_test)\n", " accuracy = accuracy_score(y_test, y_pred)\n", - " \n", + "\n", " print(f\"\\n ✅ Model trained!\")\n", " print(f\" Training accuracy: {model.score(X_train, y_train)*100:.2f}%\")\n", - " print(f\" Testing accuracy: {accuracy*100:.2f}%\")\n", - " \n", + " print(f\" Testing accuracy : {accuracy*100:.2f}%\")\n", + "\n", " # Show feature importance\n", " print(f\"\\n Feature importance:\")\n", " importances = model.feature_importances_\n", " indices = np.argsort(importances)[::-1]\n", - " for i, idx in enumerate(indices):\n", - " print(f\" {i+1}. {features_to_use[idx]:15s}: {importances[idx]:.4f}\")\n", - " \n", + " for i, idx_fi in enumerate(indices):\n", + " print(f\" {i+1}. {features_to_use[idx_fi]:15s}: {importances[idx_fi]:.4f}\")\n", + "\n", " # Classification report\n", " print(f\"\\n[4] Classification Report:\")\n", " class_names = [k for k, v in sorted(label_mapping.items(), key=lambda x: x[1])]\n", " print(classification_report(y_test, y_pred, target_names=class_names, zero_division=0))\n", - " \n", + "\n", " else:\n", - " print(f\" ❌ No samples extracted\")\n", + " print(f\" ❌ No valid samples extracted — kiểm tra CRS và tọa độ!\")\n", " model = None\n", - " \n", + "\n", " except Exception as e:\n", " print(f\" ❌ Error: {e}\")\n", " import traceback\n", @@ -890,7 +933,7 @@ "print(\"📝 NOTE: Model này dự đoán PHÂN LOẠI SỬ DỤNG ĐẤT (8 lớp)\")\n", "print(\" NDVI là một trong các features đầu vào, không phải mục tiêu dự đoán\")\n", "print(\" Sau khi predict → có thể hiển thị NDVI map như chỉ số phụ\")\n", - "print(\"=\"*70)" + "print(\"=\"*70)\n" ] }, { @@ -1094,14 +1137,6 @@ "print(\"✅ PIPELINE COMPLETE\")\n", "print(\"=\"*70)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ef65aa25-9032-4e1c-8808-64747e8005c6", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": {