361 lines
15 KiB
Plaintext
361 lines
15 KiB
Plaintext
{
|
||
"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
|
||
}
|