{ "cells": [ { "cell_type": "code", "execution_count": 6, "id": "912ed572-1658-406b-976c-cd6de2d4e89e", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " XGBoost version: 3.1.2\n", "✅ All modules loaded successfully\n", "CPU times: user 975 μs, sys: 0 ns, total: 975 μs\n", "Wall time: 948 μs\n" ] } ], "source": [ "%%time\n", "%matplotlib inline\n", "\n", "# Import Microsoft Planetary Computer libraries\n", "import planetary_computer\n", "from pystac_client import Client\n", "from odc.stac import load as stac_load\n", "\n", "# Standard imports\n", "import xarray as xr\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from sklearn.model_selection import train_test_split\n", "from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, ConfusionMatrixDisplay\n", "import geopandas as gpd\n", "\n", "# XGBoost for GPU training\n", "import xgboost as xgb\n", "\n", "from xgboost import XGBClassifier\n", "\n", "print(f\" XGBoost version: {xgb.__version__}\")\n", "\n", "print(\"✅ All modules loaded successfully\")" ] }, { "cell_type": "code", "execution_count": 7, "id": "d824dc4f-994b-4d1c-8d24-ce6674da141c", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "✅ Connected to Microsoft Planetary Computer\n", "\n", "======================================================================\n", "CPU times: user 40.4 ms, sys: 4.03 ms, total: 44.5 ms\n", "Wall time: 776 ms\n" ] } ], "source": [ "%%time\n", "# Kết nối tới Microsoft Planetary Computer STAC\n", "from pystac_client import Client\n", "\n", "# KHÔNG dùng modifier ở catalog level để tránh items bị convert thành dict\n", "catalog = Client.open(\n", " \"https://planetarycomputer.microsoft.com/api/stac/v1\"\n", ")\n", "print(\"✅ Connected to Microsoft Planetary Computer\")\n", "\n", "print(\"\\n\" + \"=\"*70)" ] }, { "cell_type": "code", "execution_count": 8, "id": "1e113730", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "CONFIGURATION\n", "======================================================================\n", "\n", "📍 Area of Interest:\n", " Longitude: 105.6 to 106.2\n", " Latitude: 9.3 to 9.8\n", "\n", "📅 Time Range: 2023-03-01/2023-05-31\n", " ⚠️ Optimized for personal computer (3 months, reduced area)\n", "\n", "🗺️ CRS: EPSG:32648\n", " Resolution: 20m (reduced from 10m for smaller data size)\n", "======================================================================\n", "CPU times: user 258 μs, sys: 0 ns, total: 258 μs\n", "Wall time: 246 μs\n" ] } ], "source": [ "%%time\n", "# 🌍 Định nghĩa khu vực và thời gian\n", "print(\"=\"*70)\n", "print(\"CONFIGURATION\")\n", "print(\"=\"*70)\n", "\n", "# Khu vực quan tâm (Vietnam - Mekong Delta) - GIẢM DIỆN TÍCH ~40%\n", "bbox = [105.6, 9.3, 106.2, 9.8] # [min_lon, min_lat, max_lon, max_lat]\n", "\n", "# GIẢM THỜI GIAN xuống 3 tháng để giảm kích thước dữ liệu cho PC\n", "time_range = \"2023-03-01/2023-05-31\" # 3 tháng (mùa khô)\n", "\n", "print(f\"\\n📍 Area of Interest:\")\n", "print(f\" Longitude: {bbox[0]} to {bbox[2]}\")\n", "print(f\" Latitude: {bbox[1]} to {bbox[3]}\")\n", "print(f\"\\n📅 Time Range: {time_range}\")\n", "print(f\" ⚠️ Optimized for personal computer (3 months, reduced area)\")\n", "print(f\"\\n🗺️ CRS: EPSG:32648\")\n", "print(f\" Resolution: 20m (reduced from 10m for smaller data size)\")\n", "\n", "print(\"=\"*70)" ] }, { "cell_type": "code", "execution_count": 9, "id": "3cd69645", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "LOADING SENTINEL-2 L2A\n", "======================================================================\n", "\n", "🔍 Searching for Sentinel-2 scenes...\n", "✅ Found 22 Sentinel-2 scenes\n", "⚠️ Limiting to 12 scenes for personal computer\n", " Selected 12 scenes evenly distributed\n", "\n", "📋 Sample scenes:\n", " [1] 2023-05-17 - Cloud: 27.73279%\n", " [2] 2023-05-15 - Cloud: 21.273129%\n", " [3] 2023-05-02 - Cloud: 20.527479%\n", " [4] 2023-05-02 - Cloud: 20.524253%\n", " [5] 2023-04-15 - Cloud: 29.471546%\n", "\n", "🔑 Signing STAC items...\n", "\n", "⏳ Loading Sentinel-2 data...\n", "\n", "✅ Sentinel-2 loaded!\n", " Shape: {'y': 2774, 'x': 3301, 'time': 7}\n", " Variables: ['red', 'nir', 'scl']\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ ":54: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset> Size: 769MB\n",
       "Dimensions:      (y: 2774, x: 3301, time: 7)\n",
       "Coordinates:\n",
       "  * y            (y) float64 22kB 1.084e+06 1.084e+06 ... 1.028e+06 1.028e+06\n",
       "  * x            (x) float64 26kB 5.658e+05 5.658e+05 ... 6.318e+05 6.318e+05\n",
       "    spatial_ref  int32 4B 32648\n",
       "  * time         (time) datetime64[ns] 56B 2023-03-23T03:05:29.024000 ... 202...\n",
       "Data variables:\n",
       "    red          (time, y, x) float32 256MB nan nan nan ... 1.917e+03 1.926e+03\n",
       "    nir          (time, y, x) float32 256MB nan nan nan ... 1.374e+03 1.386e+03\n",
       "    scl          (time, y, x) float32 256MB nan nan nan nan ... 6.0 6.0 6.0 6.0
" ], "text/plain": [ " Size: 769MB\n", "Dimensions: (y: 2774, x: 3301, time: 7)\n", "Coordinates:\n", " * y (y) float64 22kB 1.084e+06 1.084e+06 ... 1.028e+06 1.028e+06\n", " * x (x) float64 26kB 5.658e+05 5.658e+05 ... 6.318e+05 6.318e+05\n", " spatial_ref int32 4B 32648\n", " * time (time) datetime64[ns] 56B 2023-03-23T03:05:29.024000 ... 202...\n", "Data variables:\n", " red (time, y, x) float32 256MB nan nan nan ... 1.917e+03 1.926e+03\n", " nir (time, y, x) float32 256MB nan nan nan ... 1.374e+03 1.386e+03\n", " scl (time, y, x) float32 256MB nan nan nan nan ... 6.0 6.0 6.0 6.0" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 12.5 s, sys: 974 ms, total: 13.5 s\n", "Wall time: 2min 25s\n" ] } ], "source": [ "%%time\n", "# 📡 LOAD SENTINEL-2 FROM MICROSOFT PLANETARY COMPUTER\n", "print(\"=\"*70)\n", "print(\"LOADING SENTINEL-2 L2A\")\n", "print(\"=\"*70)\n", "\n", "print(\"\\n🔍 Searching for Sentinel-2 scenes...\")\n", "query_s2 = catalog.search(\n", " collections=[\"sentinel-2-l2a\"],\n", " bbox=bbox,\n", " datetime=time_range,\n", " query={\"eo:cloud_cover\": {\"lt\": 30}} # Cloud cover < 30% (giảm từ 50%)\n", ")\n", "\n", "items_s2 = list(query_s2.item_collection())\n", "print(f\"✅ Found {len(items_s2)} Sentinel-2 scenes\")\n", "\n", "# GIỚI HẠN SỐ LƯỢNG SCENES cho PC cá nhân\n", "max_scenes = 12 # Giảm xuống 12 scenes để tối ưu cho PC\n", "if len(items_s2) > max_scenes:\n", " print(f\"⚠️ Limiting to {max_scenes} scenes for personal computer\")\n", " # Chọn scenes đều đặn trong khoảng thời gian\n", " step = len(items_s2) // max_scenes\n", " items_s2 = items_s2[::step][:max_scenes]\n", " print(f\" Selected {len(items_s2)} scenes evenly distributed\")\n", "\n", "if len(items_s2) > 0:\n", " # Show first few scenes\n", " print(f\"\\n📋 Sample scenes:\")\n", " for i, item in enumerate(items_s2[:5]):\n", " date = item.datetime.strftime(\"%Y-%m-%d\")\n", " cloud = item.properties.get(\"eo:cloud_cover\", \"N/A\")\n", " print(f\" [{i+1}] {date} - Cloud: {cloud}%\")\n", " \n", " # Re-sign items to ensure fresh URLs (keep as pystac objects)\n", " print(f\"\\n🔑 Signing STAC items...\")\n", " items_s2 = [planetary_computer.sign(item) for item in items_s2]\n", " \n", " # Load Sentinel-2 data (without Dask chunks)\n", " print(f\"\\n⏳ Loading Sentinel-2 data...\")\n", " ds_s2 = stac_load(\n", " items_s2,\n", " bands=[\"B04\", \"B08\", \"SCL\"], # Red (B04), NIR (B08), Scene Classification (SCL)\n", " crs=\"EPSG:32648\",\n", " resolution=20, # 20m resolution (4x smaller data than 10m)\n", " bbox=bbox,\n", " patch_url=planetary_computer.sign, # Re-sign URLs during loading\n", " fail_on_error=False, # Skip problematic tiles instead of crashing\n", " )\n", " \n", " # Rename bands to simpler names\n", " ds_s2 = ds_s2.rename({\"B04\": \"red\", \"B08\": \"nir\", \"SCL\": \"scl\"})\n", " \n", " print(f\"\\n✅ Sentinel-2 loaded!\")\n", " print(f\" Shape: {dict(ds_s2.dims)}\")\n", " print(f\" Variables: {list(ds_s2.data_vars)}\")\n", " display(ds_s2)\n", "else:\n", " print(f\"❌ No Sentinel-2 scenes found\")\n", "\n", " ds_s2 = Noneprint(\"=\"*70)\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "435f9f78-a9a4-4226-86ca-d4bec42d454e", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "LOADING SENTINEL-1 RTC\n", "======================================================================\n", "\n", "🔍 Searching for Sentinel-1 scenes...\n", "✅ Found 22 Sentinel-1 scenes\n", "⚠️ Limiting to 12 scenes for personal computer\n", " Selected 12 scenes evenly distributed\n", "\n", "📋 Sample scenes:\n", " [1] 2023-05-29 - Orbit: ascending\n", " [2] 2023-05-29 - Orbit: ascending\n", " [3] 2023-05-17 - Orbit: ascending\n", " [4] 2023-05-17 - Orbit: ascending\n", " [5] 2023-05-05 - Orbit: ascending\n", "\n", "🔑 Signing STAC items...\n", "\n", "⏳ Loading Sentinel-1 data...\n", "\n", "🔄 Converting to dB...\n", "\n", "✅ Sentinel-1 loaded!\n", " Shape: {'y': 2774, 'x': 3301, 'time': 12}\n", " Variables: ['vv', 'vh', 'vv_db', 'vh_db']\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ ":55: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset> Size: 2GB\n",
       "Dimensions:      (y: 2774, x: 3301, time: 12)\n",
       "Coordinates:\n",
       "  * y            (y) float64 22kB 1.084e+06 1.084e+06 ... 1.028e+06 1.028e+06\n",
       "  * x            (x) float64 26kB 5.658e+05 5.658e+05 ... 6.318e+05 6.318e+05\n",
       "    spatial_ref  int32 4B 32648\n",
       "  * time         (time) datetime64[ns] 96B 2023-04-11T11:11:31.308452 ... 202...\n",
       "Data variables:\n",
       "    vv           (time, y, x) float32 440MB -3.277e+04 -3.277e+04 ... -3.277e+04\n",
       "    vh           (time, y, x) float32 440MB -3.277e+04 -3.277e+04 ... -3.277e+04\n",
       "    vv_db        (time, y, x) float32 440MB nan nan nan nan ... nan nan nan nan\n",
       "    vh_db        (time, y, x) float32 440MB nan nan nan nan ... nan nan nan nan
" ], "text/plain": [ " Size: 2GB\n", "Dimensions: (y: 2774, x: 3301, time: 12)\n", "Coordinates:\n", " * y (y) float64 22kB 1.084e+06 1.084e+06 ... 1.028e+06 1.028e+06\n", " * x (x) float64 26kB 5.658e+05 5.658e+05 ... 6.318e+05 6.318e+05\n", " spatial_ref int32 4B 32648\n", " * time (time) datetime64[ns] 96B 2023-04-11T11:11:31.308452 ... 202...\n", "Data variables:\n", " vv (time, y, x) float32 440MB -3.277e+04 -3.277e+04 ... -3.277e+04\n", " vh (time, y, x) float32 440MB -3.277e+04 -3.277e+04 ... -3.277e+04\n", " vv_db (time, y, x) float32 440MB nan nan nan nan ... nan nan nan nan\n", " vh_db (time, y, x) float32 440MB nan nan nan nan ... nan nan nan nan" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 17.3 s, sys: 3.14 s, total: 20.4 s\n", "Wall time: 3min 44s\n" ] } ], "source": [ "%%time\n", "# 📡 LOAD SENTINEL-1 FROM MICROSOFT PLANETARY COMPUTER\n", "print(\"=\"*70)\n", "print(\"LOADING SENTINEL-1 RTC\")\n", "print(\"=\"*70)\n", "\n", "print(\"\\n🔍 Searching for Sentinel-1 scenes...\")\n", "query_s1 = catalog.search(\n", " collections=[\"sentinel-1-rtc\"],\n", " bbox=bbox,\n", " datetime=time_range,\n", ")\n", "\n", "items_s1 = list(query_s1.item_collection())\n", "print(f\"✅ Found {len(items_s1)} Sentinel-1 scenes\")\n", "\n", "# GIỚI HẠN SỐ LƯỢNG SCENES cho PC cá nhân\n", "max_scenes = 12 # Giảm xuống 12 scenes để tối ưu cho PC\n", "if len(items_s1) > max_scenes:\n", " print(f\"⚠️ Limiting to {max_scenes} scenes for personal computer\")\n", " # Chọn scenes đều đặn trong khoảng thời gian\n", " step = len(items_s1) // max_scenes\n", " items_s1 = items_s1[::step][:max_scenes]\n", " print(f\" Selected {len(items_s1)} scenes evenly distributed\")\n", "\n", "if len(items_s1) > 0:\n", " # Show first few scenes\n", " print(f\"\\n📋 Sample scenes:\")\n", " for i, item in enumerate(items_s1[:5]):\n", " date = item.datetime.strftime(\"%Y-%m-%d\")\n", " orbit = item.properties.get(\"sat:orbit_state\", \"N/A\")\n", " print(f\" [{i+1}] {date} - Orbit: {orbit}\")\n", " \n", " # Re-sign items to ensure fresh URLs (keep as pystac objects)\n", " print(f\"\\n🔑 Signing STAC items...\")\n", " items_s1 = [planetary_computer.sign(item) for item in items_s1]\n", " \n", " # Load Sentinel-1 data (without Dask chunks)\n", " print(f\"\\n⏳ Loading Sentinel-1 data...\")\n", " ds_s1 = stac_load(\n", " items_s1,\n", " bands=[\"vv\", \"vh\"], # VV and VH polarizations\n", " crs=\"EPSG:32648\",\n", " resolution=20, # 20m resolution (4x smaller data than 10m)\n", " bbox=bbox,\n", " patch_url=planetary_computer.sign, # Re-sign URLs during loading\n", " fail_on_error=False, # Skip problematic tiles instead of crashing\n", " )\n", " \n", " # Convert to dB (Microsoft S1 is in linear power)\n", " print(f\"\\n🔄 Converting to dB...\")\n", " ds_s1['vv_db'] = 10 * np.log10(ds_s1['vv'].where(ds_s1['vv'] > 0))\n", " ds_s1['vh_db'] = 10 * np.log10(ds_s1['vh'].where(ds_s1['vh'] > 0))\n", " \n", " print(f\"\\n✅ Sentinel-1 loaded!\")\n", " print(f\" Shape: {dict(ds_s1.dims)}\")\n", " print(f\" Variables: {list(ds_s1.data_vars)}\")\n", " display(ds_s1)\n", "else:\n", " print(f\"❌ No Sentinel-1 scenes found\")\n", "\n", " ds_s1 = Noneprint(\"=\"*70)\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "d2585562-88aa-4c7d-bf70-1f6affcf65d4", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "DATA PROCESSING\n", "======================================================================\n", "\n", "[1] Calculating NDVI...\n", "✅ NDVI calculated\n", " Shape: (7, 2774, 3301)\n", " Time steps: 7\n", "\n", "[2] Applying cloud mask...\n", "✅ Cloud mask applied\n", "\n", "[3] Computing mean NDVI across time...\n", "✅ Mean NDVI computed\n", " Shape: (2774, 3301)\n", "======================================================================\n", "CPU times: user 735 ms, sys: 538 ms, total: 1.27 s\n", "Wall time: 1.26 s\n" ] } ], "source": [ "%%time\n", "# 🌿 CALCULATE NDVI AND PROCESS DATA\n", "print(\"=\"*70)\n", "print(\"DATA PROCESSING\")\n", "print(\"=\"*70)\n", "\n", "if ds_s2 is not None:\n", " print(\"\\n[1] Calculating NDVI...\")\n", " # NDVI = (NIR - Red) / (NIR + Red)\n", " ndvi = (ds_s2['nir'] - ds_s2['red']) / (ds_s2['nir'] + ds_s2['red'] + 1e-8)\n", " \n", " print(f\"✅ NDVI calculated\")\n", " print(f\" Shape: {ndvi.shape}\")\n", " print(f\" Time steps: {len(ndvi.time)}\")\n", " \n", " # Cloud masking using SCL band\n", " print(f\"\\n[2] Applying cloud mask...\")\n", " # SCL values: 1=defective, 3=cloud shadow, 8=cloud medium, 9=cloud high, 10=cirrus\n", " cloud_mask = ds_s2['scl'].isin([1, 3, 8, 9, 10])\n", " ndvi_masked = ndvi.where(~cloud_mask)\n", " \n", " print(f\"✅ Cloud mask applied\")\n", " \n", " # Temporal aggregation (mean over time)\n", " print(f\"\\n[3] Computing mean NDVI across time...\")\n", " ndvi_mean = ndvi_masked.mean(dim='time')\n", " \n", " # Data already in memory, no need to compute() again\n", " print(f\"✅ Mean NDVI computed\")\n", " print(f\" Shape: {ndvi_mean.shape}\")\n", " \n", "else:\n", " print(\"❌ No Sentinel-2 data to process\")\n", " ndvi_mean = None\n", "\n", "print(\"=\"*70)" ] }, { "cell_type": "code", "execution_count": null, "id": "2e955884-d4af-422d-a8e6-d436199540e0", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "FEATURE EXTRACTION\n", "======================================================================\n", "\n", "[1] Loading training data from: train/ST_training_data_updated_1130points_new.shp\n", "✅ Loaded 1130 training points\n", " Available columns: ['No', 'X', 'Y', 'LU2022', 'Hientrang', 'HT_code', 'geometry']\n", " Using label column: 'HT_code'\n", " Classes: [0, 1, 2, 3, 4, 5, 6, 7]\n", "\n", "[2] Extracting features at training points...\n", "✅ Extracted features for 638 valid points\n", " Skipped 492 points (outside extent or NaN values)\n", " Feature shape: (638, 3)\n", " Feature names: ['NDVI_mean', 'VH_dB_mean', 'VV_dB_mean']\n", "\n", " Class distribution:\n", " Class 0: 62 samples (9.7%)\n", " Class 1: 114 samples (17.9%)\n", " Class 3: 112 samples (17.6%)\n", " Class 4: 104 samples (16.3%)\n", " Class 5: 43 samples (6.7%)\n", " Class 6: 150 samples (23.5%)\n", " Class 7: 53 samples (8.3%)\n", "======================================================================\n", "CPU times: user 4.08 s, sys: 117 ms, total: 4.19 s\n", "Wall time: 4.06 s\n" ] } ], "source": [ "%%time\n", "# 🎯 EXTRACT TRAINING DATA FEATURES\n", "print(\"=\"*70)\n", "print(\"FEATURE EXTRACTION\")\n", "print(\"=\"*70)\n", "\n", "# Check if required data is available\n", "if 'ndvi_mean' not in globals() or 'ds_s1' not in globals():\n", " print(\"❌ Error: Please run Cell 6 (DATA PROCESSING) first!\")\n", " print(\" Required variables: ndvi_mean, ds_s1\")\n", " raise RuntimeError(\"Missing required data. Run cells in order: Cell 4 → Cell 5 → Cell 6 → Cell 7\")\n", "\n", "# Load training shapefile\n", "import geopandas as gpd\n", "\n", "train_path = 'train/ST_training_data_updated_1130points_new.shp'\n", "print(f\"\\n[1] Loading training data from: {train_path}\")\n", "train_gdf = gpd.read_file(train_path)\n", "\n", "# Ensure CRS matches\n", "if train_gdf.crs != 'EPSG:32648':\n", " print(f\" Reprojecting from {train_gdf.crs} to EPSG:32648...\")\n", " train_gdf = train_gdf.to_crs('EPSG:32648')\n", "\n", "print(f\"✅ Loaded {len(train_gdf)} training points\")\n", "print(f\" Available columns: {list(train_gdf.columns)}\")\n", "\n", "# Auto-detect label column (look for common names)\n", "label_column = None\n", "for col in ['HT_code', 'Ma_LU', 'LU2022', 'class', 'Class', 'CLASS', 'label', 'Label', 'LABEL', 'LU_CODE', 'LU_code']:\n", " if col in train_gdf.columns:\n", " label_column = col\n", " break\n", "\n", "if label_column is None:\n", " print(f\"❌ Cannot find label column. Available columns: {list(train_gdf.columns)}\")\n", " print(f\" Please check your shapefile and update the code.\")\n", "else:\n", " print(f\" Using label column: '{label_column}'\")\n", " print(f\" Classes: {sorted(train_gdf[label_column].unique())}\")\n", " \n", " # Extract features at each training point\n", " print(f\"\\n[2] Extracting features at training points...\")\n", " \n", " features = []\n", " labels = []\n", " skipped = 0\n", " \n", " for idx, row in train_gdf.iterrows():\n", " point = row.geometrychro\n", " x_coord = point.x\n", " y_coord = point.y\n", " label = row[label_column]\n", " \n", " # Extract NDVI at this location\n", " if ndvi_mean is not None and ds_s1 is not None:\n", " try:\n", " ndvi_val = ndvi_mean.sel(x=x_coord, y=y_coord, method='nearest').values\n", " \n", " # Extract Sentinel-1 VH/VV at this location (mean across time)\n", " # Data already in memory, no need to compute()\n", " vh_val = ds_s1['vh_db'].sel(x=x_coord, y=y_coord, method='nearest').mean(dim='time').values\n", " vv_val = ds_s1['vv_db'].sel(x=x_coord, y=y_coord, method='nearest').mean(dim='time').values\n", " \n", " # Create feature vector: [NDVI, VH_dB, VV_dB]\n", " feature_vec = [ndvi_val, vh_val, vv_val]\n", " \n", " # Only add if all features are valid (not NaN)\n", " if not np.isnan(feature_vec).any():\n", " features.append(feature_vec)\n", " labels.append(label)\n", " else:\n", " skipped += 1\n", " except Exception as e:\n", " # Skip points outside the data extent\n", " skipped += 1\n", " continue\n", " \n", " features = np.array(features)\n", " labels = np.array(labels)\n", " \n", " print(f\"✅ Extracted features for {len(features)} valid points\")\n", " print(f\" Skipped {skipped} points (outside extent or NaN values)\")\n", " print(f\" Feature shape: {features.shape}\")\n", " print(f\" Feature names: ['NDVI_mean', 'VH_dB_mean', 'VV_dB_mean']\")\n", " print(f\"\\n Class distribution:\")\n", " unique, counts = np.unique(labels, return_counts=True)\n", " for cls, cnt in zip(unique, counts):\n", " print(f\" Class {cls}: {cnt} samples ({cnt/len(labels)*100:.1f}%)\")\n", "\n", "print(\"=\"*70)" ] }, { "cell_type": "code", "execution_count": 21, "id": "f1a14379-ed6e-4897-9ca4-2669743fab40", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "MODEL TRAINING - GPU ACCELERATED\n", "======================================================================\n", "\n", "[1] Encoding labels...\n", "✅ Original classes: [0 1 3 4 5 6 7]\n", " Encoded as: [0 1 2 3 4 5 6]\n", "\n", "[2] Splitting data (80% train, 20% test)...\n", "✅ Training samples: 510\n", " Testing samples: 128\n", "\n", "[3] Training XGBoost classifier on RTX 4060 GPU...\n", " GPU Settings: device='cuda:0'\n", "✅ Model trained on GPU\n", "\n", "[4] Evaluating model...\n", "✅ Training accuracy: 1.0000\n", " Testing accuracy: 0.5781\n", "\n", "[5] Classification Report:\n", " precision recall f1-score support\n", "\n", " 0 0.27 0.25 0.26 12\n", " 1 0.54 0.65 0.59 23\n", " 3 0.57 0.55 0.56 22\n", " 4 0.52 0.57 0.55 21\n", " 5 0.80 0.44 0.57 9\n", " 6 0.85 0.73 0.79 30\n", " 7 0.43 0.55 0.48 11\n", "\n", " accuracy 0.58 128\n", " macro avg 0.57 0.53 0.54 128\n", "weighted avg 0.60 0.58 0.58 128\n", "\n", "\n", "[6] Confusion Matrix:\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3cAAAMWCAYAAABSm3/pAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjUsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvWftoOwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAih9JREFUeJzs3Xd8FOXaxvFr0jYJJKGGJBAg9F6kBl6qCkbloIKKihSxgoWDFRvoUYLoUYoCFhCUI3CUIhYUkKYC0gSBIwiCEEooQRIIkECY9w/IypIACS47w+zvy2c+urMzu9dmssneuZ95xjBN0xQAAAAA4IoWYHUAAAAAAMDfR3EHAAAAAA5AcQcAAAAADkBxBwAAAAAOQHEHAAAAAA5AcQcAAAAADkBxBwAAAAAOQHEHAAAAAA5AcQcAAAAADkBxBwAAAAAOQHEHv/fLL7+od+/eSkhIUGhoqIoWLaqrrrpKw4YN08GDBy/rc//8889q06aNoqKiZBiGhg8f7vXnMAxDgwcP9vrjXsyECRNkGIYMw9DChQvz3G+apqpUqSLDMNS2bdtLeo7Ro0drwoQJhdpn4cKF5810uS1atEgBAQF69tln89z3+++/q2jRouratWue+7788kt17txZcXFxCgkJUUREhBo2bKhBgwZpx44dHtu2bdvW/XU3DEPBwcGqWLGi+vTpo+3bt1+211ZQS5Ys0eDBg3Xo0KFC7ffDDz/ojjvuUPny5eVyuVSkSBHVrl1bjz/+uDZu3Oixba9evTy+Bi6XS9WrV9egQYN0/Phxj+2KFi163ucsWrSoevXqddFsuc9zvm1ffvll9zZ//PFHQV5ugfTq1UsVK1a8pH3btm1b4PfdiRMnVKNGDQ0dOtS97uz3t2EYCgoKUmxsrLp166bNmze78529zfmWXr166cCBAypTpoxatWqlU6dOeTx/dna26tevr4SEBB0+fLjAr/F///ufXC6XDMPQypUr89y/b98+9erVS6VKlVJ4eLgSExP13Xff5ftYmZmZevHFF1WtWjW5XC6VLFlS7dq1c7/Ws79WL730kipWrCiXy6UaNWpo1KhReR7v7rvv1k033VTg1wIABWYCfuy9994zg4KCzNq1a5vvvPOOuWDBAnPOnDnmkCFDzISEBPOmm266rM/foEEDs2rVqubXX39tLl261NyzZ4/Xn2Pp0qVmSkqK1x/3Yj788ENTkhkREWF27949z/0LFixw39+mTZtLeo7atWsXet/09HRz6dKlZnp6+iU959/12GOPmYGBgeZPP/3kXpeTk2O2bNnSLF26tLlv3z6P9T169DAlmUlJSeaECRPMhQsXmrNnzzZffvllMyEhwSxXrpzH47dp08asVKmSuXTpUnPp0qXmokWLzHfeeceMjo424+PjzczMTJ+91vy8/vrrpiRz27ZtBd7nueeeMyWZiYmJ5rvvvmvOnz/fnDNnjvnGG2+YdevWNSWZJ0+edG/fs2dPMywszP01+Prrr827777blGTedtttHtsVKVLkvM9bpEgRs2fPnhfNl/t9HB4ebmZkZHjcd+rUKTMhIcGMjIws9Ou+mJ49e5oVKlS4pH3btGlT4PfO8OHDzejoaPPIkSPudbnv7w8//NBcunSpuWDBAvOVV14xw8LCzOjoaPPgwYPmli1b3Mdg6dKl5jvvvGNKMocMGeKxfsuWLaZpmuaMGTNMSeYbb7zh8fzPPPOMaRiGOX/+/AK/vpMnT5rNmjUz4+LiTEnmihUrPO4/fvy4WadOHbNcuXLmpEmTzDlz5pidO3c2g4KCzIULF3pse/jwYbNx48ZmXFycOXLkSHPhwoXm559/bj799NPmmjVrPLa99957TZfLZQ4bNsxcsGCBO/urr77qsd2WLVvMoKAg87vvvivwawKAgqC4g99asmSJGRgYaF533XXm8ePH89yflZVlfv7555c1Q1BQkPnQQw9d1uewSu6Hv3vvvdcMCwvLU0x1797dTExMvKQCLVdh9s3OzjZPnDhxSc/jTUePHjWrVatm1qhRwzx27Jhpmqb52muvmZLMadOmeWw7ZMgQU5KZnJyc72OdOHHCfPvttz3WtWnTxqxdu3aebceNG2dKMr/99lsvvZJLU9ji7pNPPjElmQ8++KB56tSpPPefOnXKfPvtt/MUd/kVba1atTIlmTt37rzgdrkKU9x1797dDAsLM9977z2P++bNm2dKMu+7774rsrg7ceKEWbZsWfOZZ57xWJ/7/j63aHrppZdMSeb48ePzPFbuH3Q+/fTT8z5f9+7dzdDQUPN///ufaZp//Zx+5JFHCvCq/vL666+bZcuWNUeMGJFvztxCc8mSJR6vtVatWmbTpk09tn3sscfMIkWKmL///vsFn3P9+vWmYRjmkCFDPNbfd999ZlhYmJmWluax/sYbbzSvvfbaQr0uALgYhmXCbw0ZMkSGYei9996Ty+XKc39ISIj+8Y9/uG+fOnVKw4YNU40aNeRyuRQdHa0ePXpo586dHvu1bdtWderU0YoVK9SqVSuFh4erUqVKGjp0qHu4Ue6QppMnT2rMmDHu4UmSNHjwYPf/ny13n7OHdc2fP19t27ZVyZIlFRYWpvLly6tLly46evSoe5v8hmWuX79enTt3VvHixRUaGqoGDRpo4sSJHtvkDl+cPHmynnvuOcXFxSkyMlLXXHONNm3aVLAvsqQ77rhDkjR58mT3uvT0dE2bNk333HNPvvu89NJLatasmUqUKKHIyEhdddVVGjdunEzTdG9TsWJFbdiwQYsWLXJ//XKHqOVm//jjj/X444+rbNmycrlc2rJlS55hmQcOHFB8fLxatGihEydOuB//f//7n4oUKaK77767wK+1IMLCwjRhwgT99ttvevbZZ7V+/Xq9+OKLuuuuu3TLLbe4t8vOztawYcNUp04dPfPMM/k+VlBQkPr161eg542KipIkBQcHe6z/4YcfdPXVVysiIkLh4eFq0aKFvvrqqzz7F+R75tSpU3rllVdUvXp1hYWFqVixYqpXr55GjBgh6fT39pNPPilJSkhIuOCw3VyvvPKKSpUqpbfeeivf94VhGOrXr58CAwMv+jVo3ry5JF2W4alRUVG6+eabNX78eI/148ePV8uWLVWtWrV89xs/frzq16+v0NBQlShRQjfffLN+/fXXPNtNmDBB1atXl8vlUs2aNfXRRx/l+3jZ2dl65ZVX3D+nSpcurd69e2v//v2X9LpmzZqlXbt2Ffh90LhxY0nS3r17L+n5Ro4cqRIlSqhnz57KyMhQz5493T8/C2rz5s168cUXNXr0aEVGRua7zYwZM1S9enUlJia61wUFBal79+5avny5du3aJUk6evSoPvjgA916662qVKnSBZ935syZMk1TvXv39ljfu3dvHTt2TN98843H+rvvvlvz5s3T77//XuDXBgAXQ3EHv5STk6P58+erUaNGio+PL9A+Dz30kJ5++mlde+21mjVrlv71r3/pm2++UYsWLXTgwAGPbVNTU3XXXXepe/fumjVrlpKSkjRw4EBNmjRJknTDDTdo6dKlkqSuXbtq6dKl7tsF9ccff+iGG25QSEiIxo8fr2+++UZDhw5VkSJF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i4+P1zXXXKOXXnpJ1atXz9Ndy3X2z5Rznf0zJXe71NTUPNudu65UqVIqWbJknok7cp1dnBRUqVKlLni9z5o1a7q/zu3atVNOTo4++OADffbZZ/les/FiXn75Zf3yyy+aN2+e2rdvrwcffFBDhw7VzTfffN5zM3OtX79e27dvV/HixfPc165dO0VFRbnP/6xbt67WrVuXZ7vcdXXq1JGkfM/Ly2WapgIC/vobed26dTVlyhSlpqZ6FN7nPubZDh48eMnXKgSA/NC5g98aOHCgTNPUfffdl++wnxMnTuiLL76QJLVv316S3BOi5FqxYoV+/fVXXX311V7LlfuL/pdffvFYn5slP4GBgWrWrJneeecdSfL4a/K5rr76as2fP9/9wTzXRx99pPDwcPeMgt5WtmxZPfnkk+rUqZN69ux53u1yL4h89uyHx44d08cff5xnW291Q3NycnTHHXfIMAzNnj1bycnJGjVqlKZPn37B/XInVjh7udgH6BEjRmjx4sUaM2aMoqOjFRwcrAkTJmj37t3umSRzH/vJJ5/U+vXr9dprr/3t17hz504dOHDAPcSuSJEiatasmaZPn+7xNTx16pQmTZqkcuXKuSfFuJTvmWLFiqlr167q16+fDh486J7ltbCd3+eee04HDhzQgAEDCjzktaCuueYaSfKYPCbXf//7X49tCuPxxx9Xp06d9MILL5x3m8TERIWFheX5mbJz5073MFjp9B8LYmNjNXnyZI/Xv337di1ZssRj3xtvvFFpaWnKycnJ833ZuHHji/7hIT81atQo1GyOw4YNU/HixfXiiy/muRj5xaxcuVJDhw5V37593T9zhw0bpnLlyqlXr14XHZ45ZcoULViwwGPJnSRp7Nix+vLLL93b3nzzzdq4caN++ukn97qTJ09q0qRJatasmbsrHRsbq8TERP34448ew4yPHj2qRYsWeXzvd+7cWYZh5JlFdsKECQoLC9N1113nsf7kyZNKSUkp8GQ1AFAgFp7vB1gu9yLmderUMd955x1z4cKF5ty5c81hw4aZVapU8biI+f33328ahmH279/f/Pbbb813333XfWHoAwcOuLc733XG8rsmlfKZUCU9Pd0sUaKEWbduXXPGjBnmF198YXbp0sVMSEjwmJBhzJgx5q233mpOmDDBnD9/vvn111+bXbt2zXMtM50zocrGjRvNiIgI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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "CPU times: user 6.04 s, sys: 889 ms, total: 6.93 s\n", "Wall time: 6.3 s\n" ] } ], "source": [ "%%time\n", "# 🤖 TRAIN XGBOOST MODEL ON GPU (RTX 4060)\n", "print(\"=\"*70)\n", "print(\"MODEL TRAINING - GPU ACCELERATED\")\n", "print(\"=\"*70)\n", "\n", "from xgboost import XGBClassifier\n", "from sklearn.model_selection import train_test_split\n", "from sklearn.preprocessing import LabelEncoder\n", "from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay\n", "import matplotlib.pyplot as plt\n", "\n", "# Encode labels to ensure they are 0, 1, 2, ... n-1\n", "print(\"\\n[1] Encoding labels...\")\n", "label_encoder = LabelEncoder()\n", "labels_encoded = label_encoder.fit_transform(labels)\n", "print(f\"✅ Original classes: {label_encoder.classes_}\")\n", "print(f\" Encoded as: {np.unique(labels_encoded)}\")\n", "\n", "# Split data\n", "print(\"\\n[2] Splitting data (80% train, 20% test)...\")\n", "X_train, X_test, y_train, y_test = train_test_split(\n", " features, labels_encoded, test_size=0.2, random_state=42, stratify=labels_encoded\n", ")\n", "print(f\"✅ Training samples: {len(X_train)}\")\n", "print(f\" Testing samples: {len(X_test)}\")\n", "\n", "# Train XGBoost on GPU\n", "print(\"\\n[3] Training XGBoost classifier on RTX 4060 GPU...\")\n", "print(\" GPU Settings: device='cuda:0'\")\n", "\n", "xgb_model = XGBClassifier(\n", " n_estimators=100,\n", " max_depth=20,\n", " learning_rate=0.1,\n", " device='cuda:0', # Use GPU (updated from deprecated gpu_id)\n", " tree_method='hist', # Use hist with device for GPU training\n", " random_state=42,\n", " eval_metric='mlogloss', # Multi-class log loss\n", " verbosity=1 # Show GPU training progress\n", ")\n", "\n", "xgb_model.fit(X_train, y_train)\n", "print(f\"✅ Model trained on GPU\")\n", "\n", "# Evaluate\n", "print(\"\\n[4] Evaluating model...\")\n", "train_score = xgb_model.score(X_train, y_train)\n", "test_score = xgb_model.score(X_test, y_test)\n", "print(f\"✅ Training accuracy: {train_score:.4f}\")\n", "print(f\" Testing accuracy: {test_score:.4f}\")\n", "\n", "# Classification report\n", "print(\"\\n[5] Classification Report:\")\n", "y_pred = xgb_model.predict(X_test)\n", "print(classification_report(y_test, y_pred, target_names=[str(c) for c in label_encoder.classes_]))\n", "\n", "# Confusion matrix\n", "print(\"\\n[6] Confusion Matrix:\")\n", "fig, ax = plt.subplots(figsize=(10, 8))\n", "cm = confusion_matrix(y_test, y_pred)\n", "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=label_encoder.classes_)\n", "disp.plot(ax=ax, cmap='Blues', values_format='d')\n", "plt.title('Confusion Matrix - XGBoost GPU Model (RTX 4060)')\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "print(\"=\"*70)" ] }, { "cell_type": "code", "execution_count": 23, "id": "33dd516d-9824-499e-96b9-5cd9224c194c", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "======================================================================\n", "SAVING MODEL & CLEANUP\n", "======================================================================\n", "\n", "[1] Saving model to: model_train/model_xgboost_gpu_20251212_125754.joblib\n", "✅ Model and label encoder saved\n", "✅ Model info saved to: model_train/model_xgboost_gpu_20251212_125754_info.json\n", "\n", "[2] Cleanup complete\n", "======================================================================\n", "\n", "======================================================================\n", "🎉 TRAINING COMPLETE!\n", "CPU times: user 104 ms, sys: 3.86 ms, total: 108 ms\n", "Wall time: 14.3 ms\n" ] } ], "source": [ "%%time\n", "# 💾 SAVE MODEL AND CLEANUP\n", "print(\"=\"*70)\n", "print(\"SAVING MODEL & CLEANUP\")\n", "print(\"=\"*70)\n", "\n", "import joblib\n", "from datetime import datetime\n", "\n", "# Save model and label encoder\n", "model_filename = f\"model_train/model_xgboost_gpu_{datetime.now().strftime('%Y%m%d_%H%M%S')}.joblib\"\n", "print(f\"\\n[1] Saving model to: {model_filename}\")\n", "joblib.dump({'model': xgb_model, 'label_encoder': label_encoder}, model_filename)\n", "print(f\"✅ Model and label encoder saved\")\n", "\n", "# Save model info\n", "info = {\n", " \"timestamp\": datetime.now().isoformat(),\n", " \"data_source\": \"Microsoft Planetary Computer STAC\",\n", " \"collections\": [\"sentinel-2-l2a\", \"sentinel-1-rtc\"],\n", " \"features\": [\"NDVI_mean\", \"VH_dB_mean\", \"VV_dB_mean\"],\n", " \"training_samples\": len(X_train),\n", " \"testing_samples\": len(X_test),\n", " \"train_accuracy\": float(train_score),\n", " \"test_accuracy\": float(test_score),\n", " \"model_type\": \"XGBClassifier\",\n", " \"device\": \"cuda:0\",\n", " \"gpu_device\": \"RTX 4060\",\n", " \"tree_method\": \"hist\",\n", " \"n_estimators\": 100,\n", " \"max_depth\": 20,\n", " \"learning_rate\": 0.1\n", "}\n", "\n", "import json\n", "info_filename = model_filename.replace('.joblib', '_info.json')\n", "with open(info_filename, 'w') as f:\n", " json.dump(info, f, indent=2)\n", "print(f\"✅ Model info saved to: {info_filename}\")\n", "\n", "# No cleanup needed (Dask removed)\n", "print(\"\\n[2] Cleanup complete\")\n", "\n", "print(\"=\"*70)\n", "\n", "print(\"\\n\" + \"=\"*70)\n", "\n", "print(\"🎉 TRAINING COMPLETE!\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }