Files
remote-sensing/01.train_ODC_DecisionTree.ipynb
T
2026-03-07 17:14:01 +07:00

361 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "b05aa740",
"metadata": {},
"outputs": [],
"source": [
"import importlib\n",
"import new_import_ODC as odc_tools\n",
"importlib.reload(odc_tools)\n",
"from new_import_ODC import *\n",
"print(\"✅ Import thành công\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b794d005",
"metadata": {},
"outputs": [],
"source": [
"# Khởi tạo Dask + Datacube + S3\n",
"cluster, client = initialize_dask(use_gateway=True)\n",
"dc = datacube.Datacube()\n",
"configure_s3_access(aws_unsigned=True)\n",
"print(\"✅ Dask + Datacube + S3 sẵn sàng\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5bc42a3c",
"metadata": {},
"outputs": [],
"source": [
"# Cấu hình vùng và thời gian\n",
"date_range = (\"2022-09-01\", \"2023-10-01\")\n",
"longtitude_range = (105.5, 106.4)\n",
"latitude_range = (9.2, 10.0)\n",
"\n",
"# Tải dữ liệu Sentinel-2\n",
"data_sen2 = load_data(\n",
" dc=dc,\n",
" date_range=date_range,\n",
" longtitude_range=longtitude_range,\n",
" latitude_range=latitude_range,\n",
")\n",
"print(f\"✅ Sentinel-2 raw: {data_sen2.dims}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ef51e7d",
"metadata": {},
"outputs": [],
"source": [
"# Tiền xử lý Sentinel-2: cloud mask + NDVI + resampling\n",
"data_clean = mask_clean(data_sen2)\n",
"data_ndvi = calculate_indices(data_clean, index=\"NDVI\")\n",
"data_fill = fill_nan(data_ndvi)\n",
"data_sen2_monthly = data_fill.resample(time=\"1MS\").mean().compute()\n",
"print(f\"✅ S2 monthly shape: {data_sen2_monthly.dims}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0da4f86d",
"metadata": {},
"outputs": [],
"source": [
"# Tải Sentinel-1 (SAR VV/VH)\n",
"data_sen1 = load_data_sen1(\n",
" dc=dc,\n",
" date_range=date_range,\n",
" longtitude_range=longtitude_range,\n",
" latitude_range=latitude_range,\n",
")\n",
"data_sen1_monthly = calculate_average(data_sen1, [\"VV\", \"VH\"], resample=\"1MS\").compute()\n",
"print(f\"✅ S1 monthly shape: {data_sen1_monthly.dims}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "412b3716",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"# Ánh xạ nhãn lớp đất\n",
"label_mapping = {\n",
" \"Lua tom\": \"0\", \"Lua\": \"1\", \"CHN\": \"2\", \"CLN\": \"3\",\n",
" \"TS\": \"4\", \"Song\": \"5\", \"Dat xay dung\": \"6\", \"Rung\": \"7\",\n",
"}\n",
"\n",
"# Tải và ghép dữ liệu train từ S1 + S2\n",
"train_data = load_train_data(label_mapping=label_mapping)\n",
"X, y = get_data_sen1_and_sen2(train_data, data_sen2_monthly, data_sen1_monthly)\n",
"\n",
"# Chia tập train / val / test\n",
"X_train, X_val, X_test, y_train, y_val, y_test = split_train_data(X, y, test_size=0.2, val_size=0.1)\n",
"\n",
"X_train_np = np.array(X_train, dtype=np.float32)\n",
"X_val_np = np.array(X_val, dtype=np.float32)\n",
"X_test_np = np.array(X_test, dtype=np.float32)\n",
"y_train_np = np.array(y_train, dtype=np.int64)\n",
"y_val_np = np.array(y_val, dtype=np.int64)\n",
"y_test_np = np.array(y_test, dtype=np.int64)\n",
"\n",
"# Gộp train + val cho sklearn\n",
"X_fit = np.concatenate([X_train_np, X_val_np], axis=0)\n",
"y_fit = np.concatenate([y_train_np, y_val_np], axis=0)\n",
"\n",
"print(f\"✅ X_fit: {X_fit.shape} | X_test: {X_test_np.shape}\")\n",
"print(f\" Classes: {sorted(set(y_fit.tolist()))}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "84a8a1d0",
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"from sklearn.tree import DecisionTreeClassifier\n",
"\n",
"# ── Xây dựng và train mô hình Decision Tree ─────────────────────────────────\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",
"print(\"🚀 Training Decision Tree...\")\n",
"model.fit(X_fit, y_fit)\n",
"\n",
"val_acc = model.score(X_val_np, y_val_np)\n",
"print(f\"✅ Training hoàn tất! Depth: {model.get_depth()} \"\n",
" f\"Leaves: {model.get_n_leaves()} Val accuracy: {val_acc:.4f}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8248d748",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"from sklearn.tree import DecisionTreeClassifier\n",
"\n",
"# ═══════════════════════════════════════════════════════════════════════════════\n",
"# PHÂN TÍCH ĐIỂM HỘI TỤ — Decision Tree\n",
"# Phương pháp: quét max_depth từ 1→50 và theo dõi train/val accuracy\n",
"# Điểm hội tụ = độ sâu tại đó val_acc đạt cực đại rồi bắt đầu giảm (overfitting)\n",
"# ═══════════════════════════════════════════════════════════════════════════════\n",
"DEPTH_RANGE = list(range(1, 51))\n",
"THRESHOLD = 0.001 # cải thiện val_acc < 0.1% → coi là hội tụ\n",
"\n",
"train_accs_d, val_accs_d = [], []\n",
"print(\"🔍 Phân tích hội tụ theo max_depth ...\")\n",
"for d in DEPTH_RANGE:\n",
" m = DecisionTreeClassifier(\n",
" min_samples_leaf=2, min_samples_split=5,\n",
" class_weight=\"balanced\", random_state=42, max_depth=d,\n",
" )\n",
" m.fit(X_fit, y_fit)\n",
" train_accs_d.append(m.score(X_fit, y_fit))\n",
" val_accs_d.append( m.score(X_val_np, y_val_np))\n",
"\n",
"train_accs_d = np.array(train_accs_d)\n",
"val_accs_d = np.array(val_accs_d)\n",
"improvements = np.diff(val_accs_d)\n",
"\n",
"# ── Tìm điểm hội tụ ───────────────────────────────────────────────────────────\n",
"best_depth = DEPTH_RANGE[int(np.argmax(val_accs_d))]\n",
"best_val_acc = float(np.max(val_accs_d))\n",
"\n",
"# Điểm hội tụ sớm: lần đầu cải thiện < threshold\n",
"conv_depth = None\n",
"for i, imp in enumerate(improvements):\n",
" if abs(imp) < THRESHOLD:\n",
" conv_depth = DEPTH_RANGE[i + 1]\n",
" break\n",
"\n",
"# Điểm overfit: val_acc bắt đầu giảm so với peak\n",
"overfit_depth = None\n",
"peak_idx = int(np.argmax(val_accs_d))\n",
"for i in range(peak_idx + 1, len(val_accs_d)):\n",
" if val_accs_d[i] < best_val_acc - 0.005: # giảm > 0.5%\n",
" overfit_depth = DEPTH_RANGE[i]\n",
" break\n",
"\n",
"# Khoảng cách train-val (generalization gap)\n",
"gap = train_accs_d - val_accs_d\n",
"\n",
"# ── Vẽ đồ thị ─────────────────────────────────────────────────────────────────\n",
"fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
"\n",
"# --- Trái: accuracy curves ---\n",
"axes[0].plot(DEPTH_RANGE, train_accs_d, \"b-o\", markersize=3, label=\"Train\")\n",
"axes[0].plot(DEPTH_RANGE, val_accs_d, \"g-o\", markersize=3, label=\"Val\")\n",
"axes[0].axvline(x=best_depth, color=\"red\", linestyle=\"--\", linewidth=1.5,\n",
" label=f\"Best depth={best_depth} ({best_val_acc*100:.2f}%)\")\n",
"if conv_depth:\n",
" axes[0].axvline(x=conv_depth, color=\"orange\", linestyle=\":\", linewidth=1.5,\n",
" label=f\"Hội tụ depth={conv_depth}\")\n",
"if overfit_depth:\n",
" axes[0].axvline(x=overfit_depth, color=\"purple\", linestyle=\"-.\", linewidth=1.5,\n",
" label=f\"Overfit depth={overfit_depth}\")\n",
"axes[0].set_xlabel(\"max_depth\")\n",
"axes[0].set_ylabel(\"Accuracy\")\n",
"axes[0].set_title(\"Train / Val Accuracy vs max_depth\")\n",
"axes[0].legend(fontsize=8)\n",
"axes[0].grid(True, alpha=0.3)\n",
"\n",
"# --- Giữa: marginal improvement ---\n",
"axes[1].bar(DEPTH_RANGE[1:], improvements * 100,\n",
" color=[\"green\" if v > 0 else \"red\" for v in improvements], alpha=0.7)\n",
"axes[1].axhline(y=0, color=\"black\", linewidth=0.8)\n",
"axes[1].axhline(y=THRESHOLD * 100, color=\"orange\", linestyle=\"--\",\n",
" label=f\"Threshold={THRESHOLD*100:.2f}%\")\n",
"if conv_depth:\n",
" axes[1].axvline(x=conv_depth, color=\"orange\", linestyle=\":\", linewidth=1.5,\n",
" label=f\"Hội tụ depth={conv_depth}\")\n",
"axes[1].set_xlabel(\"max_depth\")\n",
"axes[1].set_ylabel(\"ΔVal Accuracy (%)\")\n",
"axes[1].set_title(\"Marginal Val Improvement per Depth Step\")\n",
"axes[1].legend(fontsize=8)\n",
"axes[1].grid(True, alpha=0.3)\n",
"\n",
"# --- Phải: generalization gap ---\n",
"axes[2].fill_between(DEPTH_RANGE, gap * 100, alpha=0.5, color=\"tomato\", label=\"Gap = Train Val\")\n",
"axes[2].plot(DEPTH_RANGE, gap * 100, \"r-o\", markersize=3)\n",
"if best_depth:\n",
" axes[2].axvline(x=best_depth, color=\"red\", linestyle=\"--\", linewidth=1.5,\n",
" label=f\"Best depth={best_depth}\")\n",
"axes[2].set_xlabel(\"max_depth\")\n",
"axes[2].set_ylabel(\"Gap (%)\")\n",
"axes[2].set_title(\"Generalization Gap (Overfitting Risk)\")\n",
"axes[2].legend(fontsize=8)\n",
"axes[2].grid(True, alpha=0.3)\n",
"\n",
"plt.suptitle(\"Decision Tree — Convergence Analysis\", fontsize=13, fontweight=\"bold\")\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# ── Tổng kết ──────────────────────────────────────────────────────────────────\n",
"print(f\"\\n{'═'*58}\")\n",
"print(f\" Độ sâu TỐI ƯU (best val acc) : max_depth = {best_depth} ({best_val_acc*100:.4f}%)\")\n",
"if conv_depth:\n",
" print(f\" Điểm HỘI TỤ (Δacc < {THRESHOLD*100:.1f}%) : max_depth = {conv_depth}\")\n",
"if overfit_depth:\n",
" print(f\" Điểm OVERFIT bắt đầu : max_depth ≥ {overfit_depth}\")\n",
" print(f\" → Nên dùng max_depth ≤ {best_depth} để tránh overfit\")\n",
"print(f\"{'═'*58}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5660e2ec",
"metadata": {},
"outputs": [],
"source": [
"from sklearn.metrics import classification_report, confusion_matrix, accuracy_score\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"\n",
"# ── Đánh giá trên tập test ─────────────────────────────────────────────────────\n",
"y_pred = model.predict(X_test_np)\n",
"\n",
"acc = accuracy_score(y_test_np, y_pred)\n",
"print(f\"Test Accuracy : {acc:.4f} ({acc*100:.2f}%)\\n\")\n",
"print(classification_report(y_test_np, y_pred, digits=4))\n",
"\n",
"# ── Feature importance ──────────────────────────────────────────────────────────\n",
"feat_imp = model.feature_importances_\n",
"idx = feat_imp.argsort()[::-1][:20]\n",
"plt.figure(figsize=(12, 4))\n",
"plt.bar(range(len(idx)), feat_imp[idx])\n",
"plt.xticks(range(len(idx)), idx, rotation=45)\n",
"plt.title(\"Top-20 Feature Importances\")\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# ── Confusion matrix ────────────────────────────────────────────────────────────\n",
"class_names = list(label_mapping.keys())\n",
"cm = confusion_matrix(y_test_np, y_pred)\n",
"plt.figure(figsize=(9, 7))\n",
"sns.heatmap(cm, annot=True, fmt=\"d\", cmap=\"Greens\",\n",
" xticklabels=class_names, yticklabels=class_names)\n",
"plt.xlabel(\"Predicted\")\n",
"plt.ylabel(\"Actual\")\n",
"plt.title(\"Confusion Matrix — Decision Tree\")\n",
"plt.tight_layout()\n",
"plt.show()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e416c2aa",
"metadata": {},
"outputs": [],
"source": [
"import joblib, json\n",
"from datetime import datetime\n",
"\n",
"# ── Lưu mô hình ────────────────────────────────────────────────────────────────\n",
"model_path = \"model_decision_tree_land_use.joblib\"\n",
"joblib.dump(model, model_path)\n",
"print(f\"✅ Model saved → {model_path}\")\n",
"\n",
"# ── Lưu thông tin mô hình ──────────────────────────────────────────────────────\n",
"info = {\n",
" \"model_type\": \"DecisionTree\",\n",
" \"max_depth\": model.get_depth(),\n",
" \"n_leaves\": model.get_n_leaves(),\n",
" \"class_weight\": \"balanced\",\n",
" \"n_features\": int(X_fit.shape[1]),\n",
" \"label_mapping\": label_mapping,\n",
" \"test_accuracy\": float(acc),\n",
" \"train_samples\": int(len(X_fit)),\n",
" \"test_samples\": int(len(X_test_np)),\n",
" \"saved_at\": datetime.now().isoformat(),\n",
"}\n",
"info_path = \"model_decision_tree_land_use_info.json\"\n",
"with open(info_path, \"w\") as f:\n",
" json.dump(info, f, indent=2, ensure_ascii=False)\n",
"print(f\"✅ Info saved → {info_path}\")\n",
"print(json.dumps(info, indent=2, ensure_ascii=False))\n",
"\n",
"# ── Đóng kết nối Dask ──────────────────────────────────────────────────────────\n",
"try:\n",
" client.close()\n",
" cluster.close()\n",
" print(\"✅ Dask cluster closed.\")\n",
"except Exception:\n",
" pass\n"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}