655 lines
22 KiB
Plaintext
655 lines
22 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "bb54fa69",
|
|
"metadata": {},
|
|
"source": [
|
|
"# 🌍 Decision Tree Land Classification - ODC Database\n",
|
|
"\n",
|
|
"## 📌 Notebook này chạy trên:\n",
|
|
"- ✅ **Server ODC/JupyterHub** với Dask Gateway\n",
|
|
"- ✅ Load dữ liệu từ **ODC Database** (access nhanh trên server)\n",
|
|
"\n",
|
|
"## 🎯 Nguồn dữ liệu:\n",
|
|
"**ODC Database**\n",
|
|
"- Sentinel-2 L2A (optical) từ ODC\n",
|
|
"- Sentinel-1 RTC (SAR) từ ODC\n",
|
|
"\n",
|
|
"## 🚀 Infrastructure:\n",
|
|
"- **Dask Gateway**: Adaptive scaling (1-10 workers)\n",
|
|
"- **Datacube**: Load data từ ODC database\n",
|
|
"- **S3 Access**: Configured với requester_pays\n",
|
|
"\n",
|
|
"## 🔄 Workflow:\n",
|
|
"1. Load data từ ODC Database (nhanh trên server)\n",
|
|
"2. Preprocessing (cloud mask, NDVI, resampling)\n",
|
|
"3. Train Decision Tree model\n",
|
|
"4. Save model → dùng cho prediction trên Planetary Computer\n",
|
|
"\n",
|
|
"---"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "10e7e875",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%time\n",
|
|
"%matplotlib inline\n",
|
|
"\n",
|
|
"import sys\n",
|
|
"import os\n",
|
|
"sys.path.insert(0, '/home/jovyan/remote-sensing')\n",
|
|
"\n",
|
|
"# Import ODC modules\n",
|
|
"import datacube\n",
|
|
"from easi_tools import notebook_utils\n",
|
|
"from datacube.utils.rio import configure_s3_access\n",
|
|
"\n",
|
|
"# Import ODC data loading functions\n",
|
|
"import importlib\n",
|
|
"import new_import_ODC\n",
|
|
"importlib.reload(new_import_ODC)\n",
|
|
"from new_import_ODC import *\n",
|
|
"\n",
|
|
"# Standard imports\n",
|
|
"import numpy as np\n",
|
|
"import pandas as pd\n",
|
|
"import xarray as xr\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import seaborn as sns\n",
|
|
"sns.set_style('whitegrid')\n",
|
|
"\n",
|
|
"# ML imports\n",
|
|
"from sklearn.tree import DecisionTreeClassifier\n",
|
|
"from sklearn.model_selection import train_test_split\n",
|
|
"from sklearn.metrics import classification_report, confusion_matrix, accuracy_score\n",
|
|
"import joblib\n",
|
|
"import json\n",
|
|
"from datetime import datetime\n",
|
|
"\n",
|
|
"print(\"✅ All modules loaded successfully!\")\n",
|
|
"print(\"📡 Data source: ODC Database\")\n",
|
|
"print(\"🚀 Infrastructure: Dask Gateway + ODC\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0ad19235",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 🚀 Step 1: Initialize Dask Gateway + Datacube\n",
|
|
"\n",
|
|
"Khởi tạo Dask Gateway với adaptive scaling và kết nối tới ODC Database"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "5d894cbd",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%time\n",
|
|
"\n",
|
|
"print(\"🚀 Step 1: Dask Gateway + Datacube Initialization\")\n",
|
|
"print(\"=\" * 70)\n",
|
|
"\n",
|
|
"# Cấu hình Dask Gateway\n",
|
|
"cluster, client = notebook_utils.initialize_dask(use_gateway=True, workers=(1, 10))\n",
|
|
"\n",
|
|
"# Khai báo Datacube - kết nối tới ODC Database\n",
|
|
"dc = datacube.Datacube()\n",
|
|
"\n",
|
|
"# Cấu hình truy cập dịch vụ S3\n",
|
|
"configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)\n",
|
|
"\n",
|
|
"print(f\"\\n✅ Dask Gateway + Datacube + S3 ready!\")\n",
|
|
"print(f\" Dask dashboard: {client.dashboard_link}\")\n",
|
|
"print(f\" Workers: Adaptive scaling (1-10)\")\n",
|
|
"print(f\" ODC products available: {len(dc.list_products())}\")\n",
|
|
"print(\"=\" * 70)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d96f19e5",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 📍 Step 2: Define Area of Interest (AOI)\n",
|
|
"\n",
|
|
"Định nghĩa vùng nghiên cứu và khoảng thời gian"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "98046ea8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Cấu hình vùng và thời gian\n",
|
|
"date_range = (\"2022-09-01\", \"2023-10-01\")\n",
|
|
"\n",
|
|
"# Coordinates: (lon_min, lon_max), (lat_min, lat_max)\n",
|
|
"longitude_range = (105.5, 106.4) # Khu vực Mekong Delta\n",
|
|
"latitude_range = (9.2, 10.0)\n",
|
|
"\n",
|
|
"print(\"📍 Area of Interest:\")\n",
|
|
"print(f\" Longitude range: {longitude_range[0]} to {longitude_range[1]}\")\n",
|
|
"print(f\" Latitude range: {latitude_range[0]} to {latitude_range[1]}\")\n",
|
|
"print(f\" Date range: {date_range[0]} to {date_range[1]}\")\n",
|
|
"print(\"=\" * 70)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "79d37cff",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 📥 Step 3: Load Sentinel-2 Data from ODC\n",
|
|
"\n",
|
|
"Load dữ liệu Sentinel-2 L2A từ ODC Database"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "3e8b535d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%time\n",
|
|
"\n",
|
|
"print(\"📥 Loading Sentinel-2 data from ODC Database...\")\n",
|
|
"print(\"-\" * 70)\n",
|
|
"\n",
|
|
"# Load Sentinel-2 từ ODC\n",
|
|
"data_sen2 = load_data(dc, date_range, longitude_range, latitude_range)\n",
|
|
"\n",
|
|
"if data_sen2 is not None:\n",
|
|
" print(f\"\\n✅ Sentinel-2 data loaded from ODC!\")\n",
|
|
" print(f\" Dimensions: {dict(data_sen2.dims)}\")\n",
|
|
" print(f\" Variables: {list(data_sen2.data_vars)}\")\n",
|
|
" print(f\" Time steps: {len(data_sen2.time)}\")\n",
|
|
" \n",
|
|
" # Apply cloud masking\n",
|
|
" print(\"\\n🔧 Applying cloud mask...\")\n",
|
|
" data_sen2_clean = mask_clean(data_sen2)\n",
|
|
" \n",
|
|
" # Calculate NDVI\n",
|
|
" print(\"📊 Calculating NDVI...\")\n",
|
|
" ndvi = calculate_indices(\n",
|
|
" data_sen2_clean,\n",
|
|
" index='NDVI',\n",
|
|
" red='red',\n",
|
|
" nir='nir',\n",
|
|
" collection='s2'\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Resample to monthly\n",
|
|
" print(\"📅 Resampling to monthly averages...\")\n",
|
|
" data_sen2_monthly = calculate_average(ndvi, time_pattern='1M')\n",
|
|
" \n",
|
|
" # Compute to load into memory\n",
|
|
" data_sen2_monthly = data_sen2_monthly.compute()\n",
|
|
" print(f\"\\n✅ Sentinel-2 monthly data ready!\")\n",
|
|
" print(f\" ✓ Cloud masked\")\n",
|
|
" print(f\" ✓ NDVI calculated\")\n",
|
|
" print(f\" ✓ Monthly resampled\")\n",
|
|
" print(f\" ✓ Loaded into memory\")\n",
|
|
"else:\n",
|
|
" print(\"❌ Failed to load Sentinel-2 data\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d0c0f672",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 📥 Step 4: Load Sentinel-1 Data from ODC\n",
|
|
"\n",
|
|
"Load dữ liệu Sentinel-1 RTC (SAR) từ ODC Database"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "1e629839",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%time\n",
|
|
"\n",
|
|
"print(\"📥 Loading Sentinel-1 data from ODC Database...\")\n",
|
|
"print(\"-\" * 70)\n",
|
|
"\n",
|
|
"# Load Sentinel-1 từ ODC\n",
|
|
"# Note: load_data_sen1 returns (dsvh, dsvv) - 2 separate DataArrays\n",
|
|
"coordinates = (longitude_range, latitude_range)\n",
|
|
"\n",
|
|
"dsvh, dsvv = load_data_sen1(dc, date_range, coordinates)\n",
|
|
"\n",
|
|
"if dsvh is not None and dsvv is not None:\n",
|
|
" print(f\"\\n✅ Sentinel-1 data loaded from ODC!\")\n",
|
|
" print(f\" VH dimensions: {dict(dsvh.dims)}\")\n",
|
|
" print(f\" VV dimensions: {dict(dsvv.dims)}\")\n",
|
|
" print(f\" Time steps: {len(dsvh.time)}\")\n",
|
|
" \n",
|
|
" # Resample to monthly\n",
|
|
" print(\"\\n📅 Resampling to monthly averages...\")\n",
|
|
" dsvh_monthly = calculate_average(dsvh, time_pattern='1M')\n",
|
|
" dsvv_monthly = calculate_average(dsvv, time_pattern='1M')\n",
|
|
" \n",
|
|
" # Compute to load into memory\n",
|
|
" dsvh_monthly = dsvh_monthly.compute()\n",
|
|
" dsvv_monthly = dsvv_monthly.compute()\n",
|
|
" print(f\"\\n✅ Sentinel-1 monthly data ready!\")\n",
|
|
" print(f\" ✓ Monthly resampled\")\n",
|
|
" print(f\" ✓ Loaded into memory\")\n",
|
|
"else:\n",
|
|
" print(\"❌ Failed to load Sentinel-1 data\")\n",
|
|
" print(\"⚠ Note: Nếu không có S1 trong ODC, có thể load từ file local:\")\n",
|
|
" print(\" dsvh, dsvv = load_sen1('path/to/vh.tif', 'path/to/vv.tif')\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "40a2ffe6",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 🎯 Step 5: Load Training Data\n",
|
|
"\n",
|
|
"Load dữ liệu mẫu huấn luyện (training samples)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "9be070b8",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Ánh xạ nhãn lớp đất\n",
|
|
"label_mapping = {\n",
|
|
" \"Lua tom\": 0,\n",
|
|
" \"Lua\": 1,\n",
|
|
" \"CHN\": 2,\n",
|
|
" \"CLN\": 3,\n",
|
|
" \"TS\": 4,\n",
|
|
" \"Song\": 5,\n",
|
|
" \"Dat xay dung\": 6,\n",
|
|
" \"Rung\": 7,\n",
|
|
"}\n",
|
|
"\n",
|
|
"print(\"🎯 Label mapping:\")\n",
|
|
"for label, idx in label_mapping.items():\n",
|
|
" print(f\" {idx}: {label}\")\n",
|
|
"\n",
|
|
"# Load training data\n",
|
|
"train_path = \"train/ST_training_data_updated_1130points_new.shp\"\n",
|
|
"\n",
|
|
"print(f\"\\n📂 Loading training data from: {train_path}\")\n",
|
|
"train_data = load_train_data(train_path)\n",
|
|
"\n",
|
|
"if train_data is not None:\n",
|
|
" print(f\"\\n✅ Training data loaded successfully!\")\n",
|
|
" print(f\" Total points: {len(train_data)}\")\n",
|
|
" print(f\" CRS: {train_data.crs}\")\n",
|
|
" print(f\" Columns: {list(train_data.columns)}\")\n",
|
|
"else:\n",
|
|
" print(\"❌ Failed to load training data\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b371c97f",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 🔧 Step 6: Extract Features\n",
|
|
"\n",
|
|
"Trích xuất features từ Sentinel-1 và Sentinel-2 tại các điểm mẫu"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "7d6c117d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%time\n",
|
|
"\n",
|
|
"print(\"🔧 Extracting features from satellite data...\")\n",
|
|
"print(\"-\" * 70)\n",
|
|
"\n",
|
|
"if train_data is not None and \\\n",
|
|
" 'data_sen2_monthly' in locals() and \\\n",
|
|
" 'dsvh_monthly' in locals() and \\\n",
|
|
" 'dsvv_monthly' in locals():\n",
|
|
" \n",
|
|
" # Extract features using ODC function\n",
|
|
" # Note: get_data_sen1_and_sen2 expects average_ndvi, dsvh, dsvv\n",
|
|
" datasets = get_data_sen1_and_sen2(\n",
|
|
" train_data, \n",
|
|
" data_sen2_monthly, # NDVI monthly average\n",
|
|
" dsvh_monthly, # VH monthly average\n",
|
|
" dsvv_monthly # VV monthly average\n",
|
|
" )\n",
|
|
" \n",
|
|
" print(f\"\\n✅ Feature extraction complete!\")\n",
|
|
" print(f\" Total datasets: {len(datasets)}\")\n",
|
|
" \n",
|
|
" # Display statistics\n",
|
|
" valid_datasets = [v for v in datasets.values() if v is not None]\n",
|
|
" if valid_datasets:\n",
|
|
" sample_data = valid_datasets[0]['data']\n",
|
|
" print(f\" Feature dimension: {len(sample_data)}\")\n",
|
|
" print(f\" Valid samples: {len(valid_datasets)}\")\n",
|
|
" \n",
|
|
" # Count labels\n",
|
|
" labels = [d['label'] for d in valid_datasets]\n",
|
|
" label_counts = pd.Series(labels).value_counts().sort_index()\n",
|
|
" print(f\"\\n Label distribution:\")\n",
|
|
" for label_id, count in label_counts.items():\n",
|
|
" print(f\" {label_id}: {count} samples ({count/len(labels)*100:.1f}%)\")\n",
|
|
" \n",
|
|
"else:\n",
|
|
" print(\"❌ Missing data: Please check training data or satellite data\")\n",
|
|
" print(f\" train_data: {'✓' if train_data is not None else '✗'}\")\n",
|
|
" print(f\" data_sen2_monthly: {'✓' if 'data_sen2_monthly' in locals() else '✗'}\")\n",
|
|
" print(f\" dsvh_monthly: {'✓' if 'dsvh_monthly' in locals() else '✗'}\")\n",
|
|
" print(f\" dsvv_monthly: {'✓' if 'dsvv_monthly' in locals() else '✗'}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b485ba1b",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 📊 Step 7: Split Data\n",
|
|
"\n",
|
|
"Chia dữ liệu thành train/val/test sets"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "29726670",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if 'datasets' in locals():\n",
|
|
" # Split data using ODC function\n",
|
|
" X_train, X_val, X_test, y_train, y_val, y_test = split_train_data(\n",
|
|
" train_data, \n",
|
|
" label_mapping, \n",
|
|
" datasets\n",
|
|
" )\n",
|
|
" \n",
|
|
" # Combine train + val for final model training\n",
|
|
" X_fit = X_train + X_val\n",
|
|
" y_fit = y_train + y_val\n",
|
|
" \n",
|
|
" print(f\"\\n✅ Data split complete:\")\n",
|
|
" print(f\" Training samples: {len(X_train)}\")\n",
|
|
" print(f\" Validation samples: {len(X_val)}\")\n",
|
|
" print(f\" Test samples: {len(X_test)}\")\n",
|
|
" print(f\"\\n Combined train+val: {len(X_fit)} samples\")\n",
|
|
" \n",
|
|
"else:\n",
|
|
" print(\"❌ Features not extracted yet. Please run Step 6 first.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3966ba86",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 🌲 Step 8: Train Decision Tree Model\n",
|
|
"\n",
|
|
"Huấn luyện mô hình Decision Tree"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "2fc1c3da",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%time\n",
|
|
"\n",
|
|
"if 'X_fit' in locals() and 'y_fit' in locals():\n",
|
|
" \n",
|
|
" print(\"🚀 Training Decision Tree model...\")\n",
|
|
" print(\"-\" * 70)\n",
|
|
" \n",
|
|
" # Train Decision Tree with tuned hyperparameters\n",
|
|
" model = DecisionTreeClassifier(\n",
|
|
" max_depth=30,\n",
|
|
" min_samples_leaf=2,\n",
|
|
" min_samples_split=5,\n",
|
|
" class_weight=\"balanced\",\n",
|
|
" random_state=42,\n",
|
|
" )\n",
|
|
" \n",
|
|
" model.fit(X_fit, y_fit)\n",
|
|
" \n",
|
|
" # Evaluate on validation set\n",
|
|
" val_acc = model.score(X_val, y_val)\n",
|
|
" \n",
|
|
" print(f\"\\n✅ Training complete!\")\n",
|
|
" print(f\" Model: Decision Tree\")\n",
|
|
" print(f\" Tree depth: {model.get_depth()}\")\n",
|
|
" print(f\" Number of leaves: {model.get_n_leaves()}\")\n",
|
|
" print(f\" Training samples: {len(X_fit)}\")\n",
|
|
" print(f\" Validation accuracy: {val_acc:.4f} ({val_acc*100:.2f}%)\")\n",
|
|
" \n",
|
|
"else:\n",
|
|
" print(\"❌ Data not ready. Please run Step 7 first.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b244cc21",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 📊 Step 9: Evaluate Model\n",
|
|
"\n",
|
|
"Đánh giá mô hình trên tập test"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "4c50781e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if 'model' in locals() and 'X_test' in locals():\n",
|
|
" \n",
|
|
" print(\"📊 Evaluating model on test set...\")\n",
|
|
" print(\"-\" * 70)\n",
|
|
" \n",
|
|
" # Make predictions\n",
|
|
" y_pred = model.predict(X_test)\n",
|
|
" acc = accuracy_score(y_test, y_pred)\n",
|
|
" \n",
|
|
" print(f\"\\n🎯 Test Accuracy: {acc:.4f} ({acc*100:.2f}%)\\n\")\n",
|
|
" print(\"📋 Classification Report:\")\n",
|
|
" print(classification_report(y_test, y_pred, digits=4))\n",
|
|
" \n",
|
|
" # Confusion Matrix\n",
|
|
" class_names = list(label_mapping.keys())\n",
|
|
" cm = confusion_matrix(y_test, y_pred)\n",
|
|
" \n",
|
|
" plt.figure(figsize=(10, 8))\n",
|
|
" sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n",
|
|
" xticklabels=class_names, yticklabels=class_names,\n",
|
|
" cbar_kws={'label': 'Count'})\n",
|
|
" plt.xlabel('Predicted Label', fontsize=12)\n",
|
|
" plt.ylabel('True Label', fontsize=12)\n",
|
|
" plt.title('Confusion Matrix — Decision Tree (Test Set)', fontsize=14, fontweight='bold')\n",
|
|
" plt.tight_layout()\n",
|
|
" plt.show()\n",
|
|
" \n",
|
|
"else:\n",
|
|
" print(\"❌ Model not trained yet. Please run Step 8 first.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3db1ed05",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 💾 Step 10: Save Model\n",
|
|
"\n",
|
|
"Lưu mô hình để dùng cho prediction trên Planetary Computer"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "11e9ac2d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if 'model' in locals() and 'acc' in locals():\n",
|
|
" \n",
|
|
" print(\"💾 Saving model and metadata...\")\n",
|
|
" print(\"-\" * 70)\n",
|
|
" \n",
|
|
" # Save model\n",
|
|
" model_path = \"model_decision_tree_odc.joblib\"\n",
|
|
" joblib.dump(model, model_path)\n",
|
|
" print(f\"✅ Model saved → {model_path}\")\n",
|
|
" \n",
|
|
" # Create metadata\n",
|
|
" info = {\n",
|
|
" \"model_type\": \"DecisionTree\",\n",
|
|
" \"data_source\": \"ODC Database\",\n",
|
|
" \"training_data\": train_path,\n",
|
|
" \"max_depth\": int(model.get_depth()),\n",
|
|
" \"n_leaves\": int(model.get_n_leaves()),\n",
|
|
" \"n_features\": len(X_fit[0]) if X_fit else 0,\n",
|
|
" \"label_mapping\": label_mapping,\n",
|
|
" \"test_accuracy\": float(acc),\n",
|
|
" \"train_samples\": int(len(X_fit)),\n",
|
|
" \"val_samples\": int(len(X_val)),\n",
|
|
" \"test_samples\": int(len(X_test)),\n",
|
|
" \"longitude_range\": list(longitude_range),\n",
|
|
" \"latitude_range\": list(latitude_range),\n",
|
|
" \"date_range\": list(date_range),\n",
|
|
" \"saved_at\": datetime.now().isoformat(),\n",
|
|
" \"hyperparameters\": {\n",
|
|
" \"max_depth\": 30,\n",
|
|
" \"min_samples_leaf\": 2,\n",
|
|
" \"min_samples_split\": 5,\n",
|
|
" \"class_weight\": \"balanced\",\n",
|
|
" \"random_state\": 42\n",
|
|
" },\n",
|
|
" \"note\": \"Model trained on ODC data, can be used for prediction on Planetary Computer\"\n",
|
|
" }\n",
|
|
" \n",
|
|
" # Save metadata\n",
|
|
" info_path = \"model_decision_tree_odc_info.json\"\n",
|
|
" with open(info_path, \"w\") as f:\n",
|
|
" json.dump(info, f, indent=2, ensure_ascii=False)\n",
|
|
" \n",
|
|
" print(f\"✅ Metadata saved → {info_path}\")\n",
|
|
" print(\"\\n📋 Model Info:\")\n",
|
|
" print(json.dumps(info, indent=2, ensure_ascii=False))\n",
|
|
" print(\"\\n💡 Model này có thể dùng cho prediction trên Planetary Computer!\")\n",
|
|
" \n",
|
|
"else:\n",
|
|
" print(\"❌ Model not trained yet or evaluation not complete.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8280de27",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 🧹 Step 11: Cleanup\n",
|
|
"\n",
|
|
"Đóng Dask cluster"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "3ffe4f8a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Close Dask Gateway cluster\n",
|
|
"try:\n",
|
|
" client.close()\n",
|
|
" cluster.close()\n",
|
|
" print(\"✅ Dask Gateway cluster closed.\")\n",
|
|
"except Exception as e:\n",
|
|
" print(f\"⚠ Error closing cluster: {e}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "382428e1",
|
|
"metadata": {},
|
|
"source": [
|
|
"---\n",
|
|
"\n",
|
|
"## ✅ Summary\n",
|
|
"\n",
|
|
"### Notebook này:\n",
|
|
"- ✅ Chạy trên **Server ODC/JupyterHub** với **Dask Gateway**\n",
|
|
"- ✅ Load dữ liệu từ **ODC Database** (rất nhanh trên server)\n",
|
|
"- ✅ Train model với dữ liệu từ ODC\n",
|
|
"- ✅ Save model để dùng cho prediction\n",
|
|
"\n",
|
|
"### Ưu điểm của approach này:\n",
|
|
"1. **Speed**: ODC Database truy cập cực nhanh trên server\n",
|
|
"2. **Scalability**: Dask Gateway adaptive scaling (1-10 workers)\n",
|
|
"3. **Compatibility**: Model có thể dùng trên Planetary Computer\n",
|
|
"\n",
|
|
"### Workflow hoàn chỉnh:\n",
|
|
"```\n",
|
|
"┌─────────────────────────────────────┐\n",
|
|
"│ TRAINING (ODC Server) │\n",
|
|
"│ ✅ Load từ ODC Database (FAST) │\n",
|
|
"│ ✅ Dask Gateway (1-10 workers) │\n",
|
|
"│ ✅ Train Decision Tree │\n",
|
|
"│ → Model: .joblib │\n",
|
|
"└─────────────────────────────────────┘\n",
|
|
" ↓\n",
|
|
"┌─────────────────────────────────────┐\n",
|
|
"│ PREDICTION (Local Machine) │\n",
|
|
"│ ✅ Load từ Planetary Computer │\n",
|
|
"│ ✅ Use trained model │\n",
|
|
"│ ✅ No VPN/ODC access needed │\n",
|
|
"│ → GeoTIFF output │\n",
|
|
"└─────────────────────────────────────┘\n",
|
|
"```\n",
|
|
"\n",
|
|
"### Prediction API:\n",
|
|
"- Máy local không cần truy cập ODC\n",
|
|
"- Load data từ Planetary Computer (public)\n",
|
|
"- Sử dụng model đã train từ ODC\n",
|
|
"- Kết quả prediction giống nhau!\n",
|
|
"\n",
|
|
"---"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|