Files
CSIROBoeingPhase5-Vietnam/02.train_CNN_PyTorch_local.ipynb
2025-11-11 15:27:54 +07:00

572 lines
18 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "a58466bf",
"metadata": {},
"source": [
"# Train CNN Model on Local Machine\n",
"Huấn luyện mô hình CNN với PyTorch trên máy cá nhân\n",
"\n",
"**Yêu cầu**: Đã tải dữ liệu từ server vào thư mục `data_for_training/`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e5a5a39",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import numpy as np\n",
"import xarray as xr\n",
"import torch\n",
"import torch.nn as nn\n",
"import torch.optim as optim\n",
"from torch.utils.data import DataLoader, TensorDataset\n",
"import matplotlib.pyplot as plt\n",
"from sklearn.preprocessing import LabelEncoder\n",
"from sklearn.model_selection import train_test_split\n",
"import geopandas as gpd\n",
"import joblib\n",
"from utils import load_data_geo\n",
"\n",
"print(f\"PyTorch version: {torch.__version__}\")\n",
"print(f\"GPU available: {torch.cuda.is_available()}\")\n",
"if torch.cuda.is_available():\n",
" print(f\"GPU device: {torch.cuda.get_device_name(0)}\")"
]
},
{
"cell_type": "markdown",
"id": "ff3f92e3",
"metadata": {},
"source": [
"## Load dữ liệu từ server"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "880dba25",
"metadata": {},
"outputs": [],
"source": [
"# Đường dẫn dữ liệu\n",
"data_dir = \"data_for_training\"\n",
"\n",
"print(\"📂 Kiểm tra các file dữ liệu...\")\n",
"if not os.path.exists(data_dir):\n",
" raise FileNotFoundError(f\"❌ Thư mục '{data_dir}' không tồn tại. Hãy tải dữ liệu từ server trước.\")\n",
"\n",
"# Load dữ liệu\n",
"print(\"\\n📥 Load dữ liệu...\")\n",
"average_ndvi = xr.open_dataarray(os.path.join(data_dir, \"average_ndvi.nc\"))\n",
"average_vv = xr.open_dataarray(os.path.join(data_dir, \"average_vv.nc\"))\n",
"average_vh = xr.open_dataarray(os.path.join(data_dir, \"average_vh.nc\"))\n",
"\n",
"print(f\"✅ NDVI shape: {average_ndvi.shape}\")\n",
"print(f\"✅ VV shape: {average_vv.shape}\")\n",
"print(f\"✅ VH shape: {average_vh.shape}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2236b22d",
"metadata": {},
"outputs": [],
"source": [
"# Load training data\n",
"train_data_path = os.path.join(data_dir, \"train_data\", \"ST_training data_updated_1130points_new.shp\")\n",
"print(f\"\\n📋 Load training points...\")\n",
"train = load_data_geo(train_data_path)\n",
"print(f\"✅ Training points: {len(train)}\")\n",
"print(f\"✅ Columns: {train.columns.tolist()}\")\n",
"train.head()"
]
},
{
"cell_type": "markdown",
"id": "8b1800e2",
"metadata": {},
"source": [
"## Chuẩn bị dữ liệu huấn luyện"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "128d1492",
"metadata": {},
"outputs": [],
"source": [
"# Cấu hình nhãn dữ liệu\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",
"num_classes = len(label_mapping)\n",
"print(f\"🏷️ Số lớp: {num_classes}\")\n",
"for label, idx in label_mapping.items():\n",
" print(f\" {idx}: {label}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ec87566f",
"metadata": {},
"outputs": [],
"source": [
"# Trích xuất dữ liệu từ các điểm training\n",
"print(\"\\n🔧 Trích xuất dữ liệu từ các điểm...\")\n",
"X = []\n",
"y = []\n",
"\n",
"for idx, point in train.iterrows():\n",
" try:\n",
" # Lấy tọa độ\n",
" x_coord = point.geometry.x\n",
" y_coord = point.geometry.y\n",
" \n",
" # Trích xuất giá trị từ mỗi band\n",
" ndvi_data = average_ndvi.sel(x=x_coord, y=y_coord, method='nearest').values\n",
" vv_data = average_vv.sel(x=x_coord, y=y_coord, method='nearest').values\n",
" vh_data = average_vh.sel(x=x_coord, y=y_coord, method='nearest').values\n",
" \n",
" # Concatenate các band\n",
" data_point = np.concatenate((ndvi_data, vv_data, vh_data))\n",
" X.append(data_point)\n",
" \n",
" # Lấy nhãn\n",
" label_text = point.Hientrang\n",
" label_idx = label_mapping[label_text]\n",
" y.append(label_idx)\n",
" \n",
" except Exception as e:\n",
" print(f\" ⚠️ Point {idx}: {e}\")\n",
"\n",
"X = np.array(X)\n",
"y = np.array(y)\n",
"\n",
"print(f\"✅ Dữ liệu trích xuất: {X.shape}\")\n",
"print(f\"✅ Nhãn: {y.shape}\")\n",
"print(f\"✅ Phân phối nhãn: {np.bincount(y)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2587a67c",
"metadata": {},
"outputs": [],
"source": [
"# Chia dữ liệu\n",
"print(\"\\n📊 Chia dữ liệu (train/val/test = 60/20/20)...\")\n",
"X_train, X_temp, y_train, y_temp = train_test_split(\n",
" X, y, test_size=0.4, random_state=42, stratify=y\n",
")\n",
"X_val, X_test, y_val, y_test = train_test_split(\n",
" X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp\n",
")\n",
"\n",
"print(f\"✅ Train: {X_train.shape}\")\n",
"print(f\"✅ Val: {X_val.shape}\")\n",
"print(f\"✅ Test: {X_test.shape}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "359761e4",
"metadata": {},
"outputs": [],
"source": [
"# Normalize dữ liệu\n",
"print(\"\\n🔧 Normalize dữ liệu...\")\n",
"mean = X_train.mean()\n",
"std = X_train.std()\n",
"X_train = (X_train - mean) / (std + 1e-8)\n",
"X_val = (X_val - mean) / (std + 1e-8)\n",
"X_test = (X_test - mean) / (std + 1e-8)\n",
"\n",
"print(f\"✅ Mean: {mean:.4f}\")\n",
"print(f\"✅ Std: {std:.4f}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0667150b",
"metadata": {},
"outputs": [],
"source": [
"# Convert to PyTorch tensors\n",
"print(\"\\n🔧 Convert sang PyTorch tensors...\")\n",
"X_train_tensor = torch.FloatTensor(X_train).unsqueeze(1) # (N, 1, features)\n",
"X_val_tensor = torch.FloatTensor(X_val).unsqueeze(1)\n",
"X_test_tensor = torch.FloatTensor(X_test).unsqueeze(1)\n",
"\n",
"y_train_tensor = torch.LongTensor(y_train)\n",
"y_val_tensor = torch.LongTensor(y_val)\n",
"y_test_tensor = torch.LongTensor(y_test)\n",
"\n",
"print(f\"✅ X_train shape: {X_train_tensor.shape}\")\n",
"print(f\"✅ y_train shape: {y_train_tensor.shape}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be2a4179",
"metadata": {},
"outputs": [],
"source": [
"# Tạo DataLoaders\n",
"print(\"\\n🔧 Tạo DataLoaders...\")\n",
"batch_size = 32\n",
"\n",
"train_dataset = TensorDataset(X_train_tensor, y_train_tensor)\n",
"train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n",
"\n",
"val_dataset = TensorDataset(X_val_tensor, y_val_tensor)\n",
"val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\n",
"\n",
"test_dataset = TensorDataset(X_test_tensor, y_test_tensor)\n",
"test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n",
"\n",
"print(f\"✅ Train batches: {len(train_loader)}\")\n",
"print(f\"✅ Val batches: {len(val_loader)}\")\n",
"print(f\"✅ Test batches: {len(test_loader)}\")"
]
},
{
"cell_type": "markdown",
"id": "5231acf3",
"metadata": {},
"source": [
"## Xây dựng CNN Model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b279fe09",
"metadata": {},
"outputs": [],
"source": [
"# Định nghĩa CNN model\n",
"class CNNClassifier(nn.Module):\n",
" def __init__(self, input_size, num_classes=8):\n",
" super(CNNClassifier, self).__init__()\n",
" \n",
" # Conv blocks\n",
" self.conv1 = nn.Conv1d(1, 64, kernel_size=3, padding=1)\n",
" self.bn1 = nn.BatchNorm1d(64)\n",
" self.conv2 = nn.Conv1d(64, 64, kernel_size=3, padding=1)\n",
" self.bn2 = nn.BatchNorm1d(64)\n",
" self.pool1 = nn.MaxPool1d(2)\n",
" self.drop1 = nn.Dropout(0.25)\n",
" \n",
" self.conv3 = nn.Conv1d(64, 128, kernel_size=3, padding=1)\n",
" self.bn3 = nn.BatchNorm1d(128)\n",
" self.conv4 = nn.Conv1d(128, 128, kernel_size=3, padding=1)\n",
" self.bn4 = nn.BatchNorm1d(128)\n",
" self.pool2 = nn.MaxPool1d(2)\n",
" self.drop2 = nn.Dropout(0.25)\n",
" \n",
" self.conv5 = nn.Conv1d(128, 256, kernel_size=3, padding=1)\n",
" self.bn5 = nn.BatchNorm1d(256)\n",
" self.conv6 = nn.Conv1d(256, 256, kernel_size=3, padding=1)\n",
" self.bn6 = nn.BatchNorm1d(256)\n",
" self.global_avg_pool = nn.AdaptiveAvgPool1d(1)\n",
" self.drop3 = nn.Dropout(0.25)\n",
" \n",
" # FC layers\n",
" self.fc1 = nn.Linear(256, 128)\n",
" self.bn_fc1 = nn.BatchNorm1d(128)\n",
" self.drop_fc1 = nn.Dropout(0.5)\n",
" \n",
" self.fc2 = nn.Linear(128, 64)\n",
" self.bn_fc2 = nn.BatchNorm1d(64)\n",
" self.drop_fc2 = nn.Dropout(0.5)\n",
" \n",
" self.fc3 = nn.Linear(64, num_classes)\n",
" \n",
" self.relu = nn.ReLU()\n",
" \n",
" def forward(self, x):\n",
" # Block 1\n",
" x = self.relu(self.bn1(self.conv1(x)))\n",
" x = self.relu(self.bn2(self.conv2(x)))\n",
" x = self.pool1(x)\n",
" x = self.drop1(x)\n",
" \n",
" # Block 2\n",
" x = self.relu(self.bn3(self.conv3(x)))\n",
" x = self.relu(self.bn4(self.conv4(x)))\n",
" x = self.pool2(x)\n",
" x = self.drop2(x)\n",
" \n",
" # Block 3\n",
" x = self.relu(self.bn5(self.conv5(x)))\n",
" x = self.relu(self.bn6(self.conv6(x)))\n",
" x = self.global_avg_pool(x)\n",
" x = x.squeeze(-1)\n",
" x = self.drop3(x)\n",
" \n",
" # FC layers\n",
" x = self.relu(self.bn_fc1(self.fc1(x)))\n",
" x = self.drop_fc1(x)\n",
" x = self.relu(self.bn_fc2(self.fc2(x)))\n",
" x = self.drop_fc2(x)\n",
" x = self.fc3(x)\n",
" \n",
" return x\n",
"\n",
"# Khởi tạo model\n",
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
"model = CNNClassifier(input_size=X_train.shape[1], num_classes=num_classes)\n",
"model = model.to(device)\n",
"\n",
"print(f\"✅ Model created\")\n",
"print(f\"📍 Device: {device}\")\n",
"print(f\"\\n📋 Model architecture:\")\n",
"print(model)"
]
},
{
"cell_type": "markdown",
"id": "ff42829a",
"metadata": {},
"source": [
"## Huấn luyện Model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de90a544",
"metadata": {},
"outputs": [],
"source": [
"# Loss function và optimizer\n",
"criterion = nn.CrossEntropyLoss()\n",
"optimizer = optim.Adam(model.parameters(), lr=0.001)\n",
"scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n",
" optimizer, mode='min', factor=0.5, patience=5, verbose=True\n",
")\n",
"\n",
"epochs = 100\n",
"early_stop_patience = 15\n",
"best_val_loss = float('inf')\n",
"early_stop_counter = 0\n",
"\n",
"train_losses = []\n",
"val_losses = []\n",
"train_accs = []\n",
"val_accs = []\n",
"\n",
"print(\"🚀 Bắt đầu huấn luyện...\\n\")\n",
"\n",
"for epoch in range(epochs):\n",
" # Training\n",
" model.train()\n",
" train_loss = 0\n",
" train_correct = 0\n",
" train_total = 0\n",
" \n",
" for X_batch, y_batch in train_loader:\n",
" X_batch = X_batch.to(device)\n",
" y_batch = y_batch.to(device)\n",
" \n",
" optimizer.zero_grad()\n",
" outputs = model(X_batch)\n",
" loss = criterion(outputs, y_batch)\n",
" loss.backward()\n",
" optimizer.step()\n",
" \n",
" train_loss += loss.item()\n",
" _, predicted = torch.max(outputs.data, 1)\n",
" train_correct += (predicted == y_batch).sum().item()\n",
" train_total += y_batch.size(0)\n",
" \n",
" train_loss /= len(train_loader)\n",
" train_acc = 100 * train_correct / train_total\n",
" train_losses.append(train_loss)\n",
" train_accs.append(train_acc)\n",
" \n",
" # Validation\n",
" model.eval()\n",
" val_loss = 0\n",
" val_correct = 0\n",
" val_total = 0\n",
" \n",
" with torch.no_grad():\n",
" for X_batch, y_batch in val_loader:\n",
" X_batch = X_batch.to(device)\n",
" y_batch = y_batch.to(device)\n",
" \n",
" outputs = model(X_batch)\n",
" loss = criterion(outputs, y_batch)\n",
" val_loss += loss.item()\n",
" _, predicted = torch.max(outputs.data, 1)\n",
" val_correct += (predicted == y_batch).sum().item()\n",
" val_total += y_batch.size(0)\n",
" \n",
" val_loss /= len(val_loader)\n",
" val_acc = 100 * val_correct / val_total\n",
" val_losses.append(val_loss)\n",
" val_accs.append(val_acc)\n",
" \n",
" # Print progress\n",
" if (epoch + 1) % 10 == 0:\n",
" print(f'Epoch [{epoch+1}/{epochs}]')\n",
" print(f' Train Loss: {train_loss:.4f}, Acc: {train_acc:.2f}%')\n",
" print(f' Val Loss: {val_loss:.4f}, Acc: {val_acc:.2f}%')\n",
" \n",
" # Early stopping\n",
" scheduler.step(val_loss)\n",
" \n",
" if val_loss < best_val_loss:\n",
" best_val_loss = val_loss\n",
" early_stop_counter = 0\n",
" # Lưu best model\n",
" torch.save(model.state_dict(), 'model_cnn_pytorch_best.pt')\n",
" else:\n",
" early_stop_counter += 1\n",
" if early_stop_counter >= early_stop_patience:\n",
" print(f'\\n⛔ Early stopping at epoch {epoch+1}')\n",
" break\n",
"\n",
"print(f'\\n✅ Huấn luyện hoàn thành!')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0868ab12",
"metadata": {},
"outputs": [],
"source": [
"# Load best model\n",
"model.load_state_dict(torch.load('model_cnn_pytorch_best.pt'))\n",
"\n",
"# Evaluate on test set\n",
"model.eval()\n",
"test_correct = 0\n",
"test_total = 0\n",
"test_loss = 0\n",
"\n",
"with torch.no_grad():\n",
" for X_batch, y_batch in test_loader:\n",
" X_batch = X_batch.to(device)\n",
" y_batch = y_batch.to(device)\n",
" \n",
" outputs = model(X_batch)\n",
" loss = criterion(outputs, y_batch)\n",
" test_loss += loss.item()\n",
" _, predicted = torch.max(outputs.data, 1)\n",
" test_correct += (predicted == y_batch).sum().item()\n",
" test_total += y_batch.size(0)\n",
"\n",
"test_loss /= len(test_loader)\n",
"test_acc = 100 * test_correct / test_total\n",
"\n",
"print(f\"📊 Test Results:\")\n",
"print(f\" Test Loss: {test_loss:.4f}\")\n",
"print(f\" Test Accuracy: {test_acc:.2f}%\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2067227",
"metadata": {},
"outputs": [],
"source": [
"# Vẽ đồ thị huấn luyện\n",
"fig, axes = plt.subplots(1, 2, figsize=(15, 5))\n",
"\n",
"# Loss\n",
"axes[0].plot(train_losses, label='Train Loss')\n",
"axes[0].plot(val_losses, label='Val Loss')\n",
"axes[0].set_xlabel('Epoch')\n",
"axes[0].set_ylabel('Loss')\n",
"axes[0].set_title('Model Loss')\n",
"axes[0].legend()\n",
"axes[0].grid(True)\n",
"\n",
"# Accuracy\n",
"axes[1].plot(train_accs, label='Train Accuracy')\n",
"axes[1].plot(val_accs, label='Val Accuracy')\n",
"axes[1].set_xlabel('Epoch')\n",
"axes[1].set_ylabel('Accuracy (%)')\n",
"axes[1].set_title('Model Accuracy')\n",
"axes[1].legend()\n",
"axes[1].grid(True)\n",
"\n",
"plt.tight_layout()\n",
"plt.savefig('training_history.png', dpi=150, bbox_inches='tight')\n",
"plt.show()\n",
"\n",
"print(\"✅ Đồ thị đã lưu vào 'training_history.png'\")"
]
},
{
"cell_type": "markdown",
"id": "d8402a21",
"metadata": {},
"source": [
"## Lưu Model"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c12de183",
"metadata": {},
"outputs": [],
"source": [
"# Lưu model state dict\n",
"print(\"💾 Lưu model...\")\n",
"torch.save(model.state_dict(), 'model_cnn_pytorch.pt')\n",
"print(f\"✅ Model state dict đã lưu: model_cnn_pytorch.pt\")\n",
"\n",
"# Lưu thông tin model\n",
"model_info = {\n",
" 'state_dict': model.state_dict(),\n",
" 'num_classes': num_classes,\n",
" 'input_size': X_train.shape[1],\n",
" 'label_mapping': label_mapping,\n",
" 'mean': mean,\n",
" 'std': std,\n",
" 'test_accuracy': test_acc,\n",
" 'test_loss': test_loss\n",
"}\n",
"\n",
"torch.save(model_info, 'model_cnn_pytorch_full.pt')\n",
"print(f\"✅ Full model info đã lưu: model_cnn_pytorch_full.pt\")\n",
"\n",
"print(f\"\\n✅ Model ready for prediction!\")"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}