544 lines
23 KiB
Plaintext
544 lines
23 KiB
Plaintext
{
|
|
"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
|
|
}
|