{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "0cb933a1", "metadata": {}, "outputs": [], "source": [ "%%time\n", "%matplotlib inline\n", "\n", "import importlib\n", "import new_import_ODC\n", "\n", "importlib.reload(new_import_ODC)\n", "from new_import_ODC import *\n", "\n", "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "import torch.optim as optim\n", "from torch.utils.data import Dataset, DataLoader, TensorDataset\n", "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n", "\n", "# ── Hyperparameters & constants ───────────────────────────────────────────────\n", "N_VARS = 3 # số kênh mỗi bước thời gian (ndvi, vh, vv)\n", "EMBED_DIM = 128 # kích thước embedding Swin blocks\n", "NUM_HEADS = 4 # heads cho MultiheadAttention\n", "NUM_CLASSES = 8 # số lớp phân loại\n", "EPOCHS = 150 # số epoch tối đa\n", "PATIENCE = 20 # early stopping patience\n", "BATCH_SIZE = 32\n", "LR = 1e-3\n", "WEIGHT_DECAY = 0.01\n", "\n", "DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "print(f\"Device: {DEVICE}\")\n", "print(f\"PyTorch version: {torch.__version__}\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "46b8f479", "metadata": {}, "outputs": [], "source": [ "%%time\n", "# ── Kết nối Dask + ODC + S3 ──────────────────────────────────────────────────\n", "cluster, client = notebook_utils.initialize_dask(use_gateway=True, workers=(1, 10))\n", "dc = datacube.Datacube()\n", "configure_s3_access(aws_unsigned=False, requester_pays=True, client=client)\n", "client\n" ] }, { "cell_type": "code", "execution_count": null, "id": "cb9a8d0c", "metadata": {}, "outputs": [], "source": [ "## cấu hình thời gian và tọa độ\n", "date_range = (\"2022-09-01\", \"2023-10-01\")\n", "longtitude_range = (105.5, 106.4)\n", "latitude_range = (9.2, 10.0)\n", "coordinates = (longtitude_range, latitude_range)\n", "\n", "## truy vấn ảnh Sentinel-2\n", "data = load_data(dc, date_range, longtitude_range, latitude_range)\n", "notebook_utils.heading(notebook_utils.xarray_object_size(data))\n", "display(data)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "cf2647d3", "metadata": {}, "outputs": [], "source": [ "%%time\n", "# ── Loại bỏ mây + tính NDVI ───────────────────────────────────────────────────\n", "result = mask_clean(data)\n", "progress(result)\n", "\n", "ds1 = calculate_indices(result, index=\"NDVI\", satellite_mission=\"s2\")\n", "ndvi = ds1[\"NDVI\"]\n", "display(ndvi)\n", "\n", "## Hiển thị ảnh NDVI trước khi fill mây\n", "plt.imshow(ndvi.isel(time=6))\n", "plt.title(\"NDVI (before cloud fill)\")\n", "plt.colorbar()\n", "plt.show()\n", "\n", "# ── Fill nan theo mùa vụ ──────────────────────────────────────────────────────\n", "time_split = [\n", " slice(\"2022-09-01\", \"2023-01-01\"),\n", " slice(\"2023-01-01\", \"2023-05-01\"),\n", " slice(\"2023-05-01\", \"2023-07-01\"),\n", " slice(\"2023-07-01\", \"2023-10-01\"),\n", "]\n", "fill_nan_ndvi = fill_nan(ndvi, time_split)\n", "\n", "plt.imshow(fill_nan_ndvi.isel(time=6))\n", "plt.title(\"NDVI (after cloud fill)\")\n", "plt.colorbar()\n", "plt.show()\n", "\n", "# ── Resample về trung bình tháng ─────────────────────────────────────────────\n", "average_ndvi = fill_nan_ndvi.resample(time=\"1M\").mean().persist()\n", "progress(average_ndvi)\n", "average_ndvi = average_ndvi.compute()\n", "print(f\"NDVI monthly shape: {average_ndvi.shape}\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7cae5302", "metadata": {}, "outputs": [], "source": [ "# ── Load Sentinel-1 (VH, VV) và tính trung bình tháng ────────────────────────\n", "dsvh, dsvv = load_data_sen1(dc, date_range, coordinates)\n", "average_vv = calculate_average(dsvv, time_pattern=\"1M\")\n", "average_vh = calculate_average(dsvh, time_pattern=\"1M\")\n", "\n", "print(f\"VV monthly shape: {average_vv.shape}\")\n", "print(f\"VH monthly shape: {average_vh.shape}\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "8901a611", "metadata": {}, "outputs": [], "source": [ "## ── Chuẩn bị dữ liệu train ───────────────────────────────────────────────────\n", "train_path = \"train/ST_training data_updated_1130points_new.shp\"\n", "\n", "train = load_train_data(train_path)\n", "train.head()\n", "\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", "# Xây dựng dataset gồm VH, VV, NDVI\n", "datasets = get_data_sen1_and_sen2(train, average_ndvi, average_vh, average_vv)\n", "\n", "# Chia 80-20-20\n", "X_train, X_val, X_test, y_train, y_val, y_test = split_train_data(\n", " train, label_mapping, datasets\n", ")\n", "\n", "print(f\"X_train: {np.asarray(X_train).shape} y_train: {np.asarray(y_train).shape}\")\n", "print(f\"X_val : {np.asarray(X_val).shape} y_val : {np.asarray(y_val).shape}\")\n", "print(f\"X_test : {np.asarray(X_test).shape} y_test : {np.asarray(y_test).shape}\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "64492335", "metadata": {}, "outputs": [], "source": [ "%%time\n", "# ── Kiến trúc Swin-UNet (giống train_module.py / api_server) ─────────────────\n", "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "import torch.optim as optim\n", "from torch.utils.data import TensorDataset, DataLoader\n", "\n", "class SwinUNetClassifier(nn.Module):\n", " \"\"\"\n", " Swin Transformer U-Net style architecture adapted for feature vector classification.\n", " Combines hierarchical Swin Transformer blocks with skip connections.\n", " Identical to SwinUNetClassifier in train_module.py.\n", " \"\"\"\n", " def __init__(self, n_features, n_classes, embed_dim=128):\n", " super().__init__()\n", " self.n_features = n_features\n", " self.n_classes = n_classes\n", " self.embed_dim = embed_dim\n", "\n", " # Feature adapter\n", " self.adapter = nn.Sequential(\n", " nn.Linear(n_features, embed_dim * 2),\n", " nn.ReLU(),\n", " nn.Dropout(0.1),\n", " nn.Linear(embed_dim * 2, embed_dim),\n", " )\n", "\n", " # Encoder\n", " self.encoder1 = nn.Sequential(\n", " nn.Linear(embed_dim, embed_dim), nn.LayerNorm(embed_dim), nn.GELU(), nn.Dropout(0.1)\n", " )\n", " self.down1 = nn.Linear(embed_dim, embed_dim * 2)\n", "\n", " self.encoder2 = nn.Sequential(\n", " nn.Linear(embed_dim * 2, embed_dim * 2), nn.LayerNorm(embed_dim * 2), nn.GELU(), nn.Dropout(0.1)\n", " )\n", " self.down2 = nn.Linear(embed_dim * 2, embed_dim * 4)\n", "\n", " self.encoder3 = nn.Sequential(\n", " nn.Linear(embed_dim * 4, embed_dim * 4), nn.LayerNorm(embed_dim * 4), nn.GELU(), nn.Dropout(0.1)\n", " )\n", "\n", " # Decoder with skip connections\n", " self.up2 = nn.Linear(embed_dim * 4, embed_dim * 2)\n", " self.decoder2 = nn.Sequential(\n", " nn.Linear(embed_dim * 4, embed_dim * 2), nn.LayerNorm(embed_dim * 2), nn.GELU(), nn.Dropout(0.1)\n", " )\n", "\n", " self.up1 = nn.Linear(embed_dim * 2, embed_dim)\n", " self.decoder1 = nn.Sequential(\n", " nn.Linear(embed_dim * 2, embed_dim), nn.LayerNorm(embed_dim), nn.GELU(), nn.Dropout(0.1)\n", " )\n", "\n", " # Attention for better aggregation\n", " self.attention = nn.MultiheadAttention(embed_dim, num_heads=4, batch_first=True)\n", "\n", " # Classification head\n", " self.classifier = nn.Sequential(\n", " nn.Linear(embed_dim, embed_dim // 2),\n", " nn.GELU(),\n", " nn.Dropout(0.3),\n", " nn.Linear(embed_dim // 2, n_classes),\n", " )\n", "\n", " def forward(self, x):\n", " if len(x.shape) == 3:\n", " x = x.squeeze(1)\n", "\n", " # Adapter\n", " x = self.adapter(x) # (B, embed_dim)\n", " x_seq = x.unsqueeze(1) # (B, 1, embed_dim)\n", "\n", " # Encoder\n", " x1 = self.encoder1(x_seq) # (B, 1, embed_dim)\n", " x_d1 = self.down1(x1.squeeze(1)) # (B, embed_dim*2)\n", "\n", " x2 = self.encoder2(x_d1.unsqueeze(1)) # (B, 1, embed_dim*2)\n", " x_d2 = self.down2(x2.squeeze(1)) # (B, embed_dim*4)\n", "\n", " x3 = self.encoder3(x_d2.unsqueeze(1)) # (B, 1, embed_dim*4)\n", "\n", " # Decoder\n", " x_u2 = self.up2(x3.squeeze(1)) # (B, embed_dim*2)\n", " x_cat2 = torch.cat([x_u2, x_d1], dim=1) # (B, embed_dim*4)\n", " x_dec2 = self.decoder2(x_cat2) # (B, embed_dim*2)\n", "\n", " x_u1 = self.up1(x_dec2) # (B, embed_dim)\n", " x_cat1 = torch.cat([x_u1, x.squeeze(1) if len(x.shape)==3 else x], dim=1) # (B, embed_dim*2)\n", " x_dec1 = self.decoder1(x_cat1) # (B, embed_dim)\n", "\n", " # Attention\n", " x_seq2 = x_dec1.unsqueeze(1)\n", " attn, _ = self.attention(x_seq2, x_seq2, x_seq2)\n", "\n", " return self.classifier(attn.squeeze(1))\n", "\n", " def predict(self, X):\n", " \"\"\"Scikit-learn style predict.\"\"\"\n", " self.eval()\n", " with torch.no_grad():\n", " if isinstance(X, np.ndarray):\n", " X = torch.FloatTensor(X)\n", " outputs = self(X)\n", " return outputs.argmax(1).cpu().numpy()\n", "\n", " def score(self, X, y):\n", " preds = self.predict(X)\n", " if isinstance(y, torch.Tensor):\n", " y = y.cpu().numpy()\n", " return float(np.mean(preds == y))\n", "\n", "\n", "# ── Chuẩn bị tensor & DataLoader ─────────────────────────────────────────────\n", "X_train_np = np.asarray(X_train, dtype=np.float32)\n", "X_val_np = np.asarray(X_val, dtype=np.float32)\n", "y_train_np = np.asarray(y_train, dtype=np.int64)\n", "y_val_np = np.asarray(y_val, dtype=np.int64)\n", "\n", "n_features = X_train_np.shape[1]\n", "NUM_CLASSES = len(label_mapping)\n", "\n", "X_train_t = torch.from_numpy(X_train_np)\n", "X_val_t = torch.from_numpy(X_val_np)\n", "y_train_t = torch.from_numpy(y_train_np)\n", "y_val_t = torch.from_numpy(y_val_np)\n", "\n", "train_loader = DataLoader(TensorDataset(X_train_t, y_train_t), batch_size=BATCH_SIZE, shuffle=True)\n", "val_loader = DataLoader(TensorDataset(X_val_t, y_val_t), batch_size=64, shuffle=False)\n", "\n", "# ── Khởi tạo mô hình ─────────────────────────────────────────────────────────\n", "model = SwinUNetClassifier(n_features, NUM_CLASSES, embed_dim=EMBED_DIM).to(DEVICE)\n", "print(model)\n", "total_params = sum(p.numel() for p in model.parameters())\n", "print(f\"\\nTotal parameters: {total_params:,}\")\n", "print(f\"n_features={n_features} n_classes={NUM_CLASSES} embed_dim={EMBED_DIM}\\n\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "d26a3308", "metadata": {}, "outputs": [], "source": [ "%%time\n", "# ── Train Swin-UNet ───────────────────────────────────────────────────────────\n", "\n", "# Class weights để xử lý mất cân bằng dữ liệu (giống api_server)\n", "class_counts = np.bincount(y_train_np)\n", "class_weights = 1.0 / (class_counts + 1e-6)\n", "class_weights = class_weights / class_weights.sum() * len(class_counts)\n", "class_weights_t = torch.FloatTensor(class_weights).to(DEVICE)\n", "\n", "print(f\"Class distribution : {class_counts}\")\n", "print(f\"Class weights : {np.round(class_weights, 3)}\\n\")\n", "\n", "criterion = nn.CrossEntropyLoss(weight=class_weights_t)\n", "optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n", "scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)\n", "\n", "best_val_acc = 0.0\n", "best_state = None\n", "no_improve = 0\n", "history = {\"train_loss\": [], \"train_acc\": [], \"val_loss\": [], \"val_acc\": []}\n", "\n", "def evaluate(loader):\n", " model.eval()\n", " total_loss, correct, n = 0.0, 0, 0\n", " with torch.no_grad():\n", " for xb, yb in loader:\n", " xb, yb = xb.to(DEVICE), yb.to(DEVICE)\n", " logits = model(xb)\n", " total_loss += criterion(logits, yb).item() * len(yb)\n", " correct += (logits.argmax(1) == yb).sum().item()\n", " n += len(yb)\n", " return total_loss / n, correct / n\n", "\n", "print(\"🚀 Training Swin-UNet model (PyTorch)...\")\n", "for epoch in range(1, EPOCHS + 1):\n", " model.train()\n", " t_loss, t_correct, t_n = 0.0, 0, 0\n", " for xb, yb in train_loader:\n", " xb, yb = xb.to(DEVICE), yb.to(DEVICE)\n", " optimizer.zero_grad()\n", " logits = model(xb)\n", " loss = criterion(logits, yb)\n", " loss.backward()\n", " # Gradient clipping — quan trọng cho Swin blocks\n", " torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n", " optimizer.step()\n", " t_loss += loss.item() * len(yb)\n", " t_correct += (logits.argmax(1) == yb).sum().item()\n", " t_n += len(yb)\n", "\n", " scheduler.step()\n", " train_loss, train_acc = t_loss / t_n, t_correct / t_n\n", " val_loss, val_acc = evaluate(val_loader)\n", " lr_now = optimizer.param_groups[0][\"lr\"]\n", "\n", " history[\"train_loss\"].append(train_loss)\n", " history[\"train_acc\"].append(train_acc)\n", " history[\"val_loss\"].append(val_loss)\n", " history[\"val_acc\"].append(val_acc)\n", "\n", " if val_acc > best_val_acc:\n", " best_val_acc = val_acc\n", " best_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}\n", " no_improve = 0\n", " else:\n", " no_improve += 1\n", "\n", " if epoch % 10 == 0 or epoch == 1:\n", " print(f\"Epoch {epoch:3d}/{EPOCHS} \"\n", " f\"train_loss={train_loss:.4f} train_acc={train_acc:.4f} \"\n", " f\"val_loss={val_loss:.4f} val_acc={val_acc:.4f} lr={lr_now:.6f}\")\n", "\n", " if no_improve >= PATIENCE:\n", " print(f\"\\nEarly stopping tại epoch {epoch} (không cải thiện {PATIENCE} epochs liên tiếp)\")\n", " break\n", "\n", "model.load_state_dict(best_state)\n", "print(f\"\\n✅ Training hoàn tất! Best val accuracy: {best_val_acc:.4f} ({best_val_acc*100:.2f}%)\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4a806eaf", "metadata": {}, "outputs": [], "source": [ "%%time\n", "import matplotlib.pyplot as plt\n", "from sklearn.metrics import (\n", " accuracy_score, precision_score, recall_score, f1_score,\n", " confusion_matrix, ConfusionMatrixDisplay,\n", ")\n", "\n", "# ── 1. Đánh giá trên tập test ────────────────────────────────────────────────\n", "X_test_np = np.asarray(X_test, dtype=np.float32)\n", "y_test_np = np.asarray(y_test, dtype=np.int64)\n", "X_test_t = torch.from_numpy(X_test_np)\n", "\n", "print(\"📊 Evaluating Swin-UNet on test set...\\n\")\n", "\n", "model.eval()\n", "all_preds = []\n", "with torch.no_grad():\n", " for i in range(0, len(X_test_t), 64):\n", " xb = X_test_t[i:i+64].to(DEVICE)\n", " preds = model(xb).argmax(1).cpu().numpy()\n", " all_preds.append(preds)\n", "\n", "y_pred_test = np.concatenate(all_preds)\n", "\n", "test_accuracy = accuracy_score(y_test_np, y_pred_test)\n", "precision = precision_score(y_test_np, y_pred_test, average=\"weighted\", zero_division=0)\n", "recall = recall_score(y_test_np, y_pred_test, average=\"weighted\", zero_division=0)\n", "f1 = f1_score(y_test_np, y_pred_test, average=\"weighted\", zero_division=0)\n", "\n", "print(f\"📈 Test Results:\")\n", "print(f\" Accuracy : {test_accuracy:.4f} ({test_accuracy*100:.2f}%)\")\n", "print(f\" Precision: {precision:.4f}\")\n", "print(f\" Recall : {recall:.4f}\")\n", "print(f\" F1-Score : {f1:.4f}\\n\")\n", "\n", "# ── 2. Learning curves ───────────────────────────────────────────────────────\n", "fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n", "\n", "axes[0].plot(history[\"train_loss\"], label=\"Train loss\")\n", "axes[0].plot(history[\"val_loss\"], label=\"Val loss\")\n", "axes[0].set_title(\"Loss over epochs\"); axes[0].set_xlabel(\"Epoch\"); axes[0].legend()\n", "\n", "axes[1].plot(history[\"train_acc\"], label=\"Train accuracy\")\n", "axes[1].plot(history[\"val_acc\"], label=\"Val accuracy\")\n", "axes[1].set_title(\"Accuracy over epochs\"); axes[1].set_xlabel(\"Epoch\"); axes[1].legend()\n", "\n", "plt.tight_layout(); plt.show()\n", "\n", "# ── 3. Confusion matrix ──────────────────────────────────────────────────────\n", "class_names = list(label_mapping.keys())\n", "cm = confusion_matrix(y_test_np, y_pred_test)\n", "disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names)\n", "\n", "fig, ax = plt.subplots(figsize=(10, 8))\n", "disp.plot(cmap=\"Blues\", ax=ax)\n", "plt.xticks(rotation=45, ha=\"right\")\n", "plt.title(\"Swin-UNet Confusion Matrix — Test Set\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "code", "execution_count": null, "id": "cf1b5923", "metadata": {}, "outputs": [], "source": [ "import json\n", "\n", "# ── Lưu weights mô hình Swin-UNet ────────────────────────────────────────────\n", "model_path = \"model_swinunet_land_use.pth\"\n", "torch.save({\n", " \"model_state_dict\": model.state_dict(),\n", " \"n_features\": n_features,\n", " \"num_classes\": NUM_CLASSES,\n", " \"embed_dim\": EMBED_DIM,\n", " \"label_mapping\": label_mapping,\n", "}, model_path)\n", "print(f\"✅ Model saved to {model_path}\")\n", "\n", "# ── Lưu metadata ─────────────────────────────────────────────────────────────\n", "info = {\n", " \"model_type\": \"Swin-UNet (PyTorch)\",\n", " \"input_shape\": [n_features],\n", " \"embed_dim\": EMBED_DIM,\n", " \"num_classes\": NUM_CLASSES,\n", " \"classes\": list(label_mapping.keys()),\n", " \"label_mapping\": label_mapping,\n", " \"num_parameters\": sum(p.numel() for p in model.parameters()),\n", " \"accuracy\": float(test_accuracy),\n", " \"precision\": float(precision),\n", " \"recall\": float(recall),\n", " \"f1_score\": float(f1),\n", "}\n", "\n", "info_path = \"model_swinunet_land_use_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 to {info_path}\")\n", "print(f\"\\nSummary:\")\n", "print(f\" n_features : {n_features}\")\n", "print(f\" Parameters : {info['num_parameters']:,}\")\n", "print(f\" Test accuracy: {test_accuracy*100:.2f}%\")\n", "\n", "# ── Ví dụ load lại mô hình ──────────────────────────────────────────────────\n", "# ck = torch.load(\"model_swinunet_land_use.pth\")\n", "# model_loaded = SwinUNetClassifier(ck[\"n_features\"], ck[\"num_classes\"], ck[\"embed_dim\"])\n", "# model_loaded.load_state_dict(ck[\"model_state_dict\"])\n", "# model_loaded.eval()\n" ] }, { "cell_type": "code", "execution_count": null, "id": "93c9d96e", "metadata": {}, "outputs": [], "source": [ "# đóng client, cluster\n", "client.close()\n", "cluster.close()\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }