mirror of
https://git.victorphan.net/basketballcantho/CSIROBoeingPhase5-Vietnam.git
synced 2026-08-05 13:43:11 +07:00
572 lines
18 KiB
Plaintext
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
|
|
}
|