diff --git a/01.train_ODC_MobileNet.ipynb b/01.train_ODC_MobileNet.ipynb index b510dad..93b03fe 100644 --- a/01.train_ODC_MobileNet.ipynb +++ b/01.train_ODC_MobileNet.ipynb @@ -179,10 +179,12 @@ "import matplotlib.pyplot as plt\n", "\n", "# ── Hyper-parameters ───────────────────────────────────────────────────────────\n", - "LEARNING_RATE = 1e-3\n", - "BATCH_SIZE = 64\n", - "N_EPOCHS = 60\n", - "PATIENCE = 10\n", + "LEARNING_RATE = 1e-3\n", + "BATCH_SIZE = 64\n", + "N_EPOCHS = 60\n", + "PATIENCE = 10\n", + "CONV_THRESHOLD = 1e-4 # cải thiện val_loss < THRESHOLD → đánh dấu hội tụ\n", + "CONV_WINDOW = 3 # cần CONV_WINDOW bước liên tiếp thỏa mãn\n", "\n", "# ── Tensors & DataLoaders ──────────────────────────────────────────────────────\n", "X_tr_t = torch.FloatTensor(X_train_np)\n", @@ -203,12 +205,16 @@ "scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode=\"min\", factor=0.5, patience=5)\n", "\n", "# ── Training loop ──────────────────────────────────────────────────────────────\n", - "train_losses, val_losses, val_accs = [], [], []\n", - "best_val_loss = float(\"inf\")\n", - "best_state_dict = None\n", + "train_losses, val_losses, val_accs, lr_history = [], [], [], []\n", + "best_val_loss = float(\"inf\")\n", + "best_epoch = 1\n", + "best_state_dict = None\n", "patience_counter = 0\n", + "convergence_epoch = None # ← điểm hội tụ sẽ được ghi lại ở đây\n", + "early_stop_epoch = None\n", "\n", "print(\"🚀 Training MobileNetV3 + LR-ASPP...\")\n", + "print(f\" conv_threshold={CONV_THRESHOLD} conv_window={CONV_WINDOW} patience={PATIENCE}\")\n", "for epoch in range(1, N_EPOCHS + 1):\n", " # --- Train ---\n", " model.train()\n", @@ -239,40 +245,154 @@ "\n", " scheduler.step(avg_val_loss)\n", " lr = optimizer.param_groups[0][\"lr\"]\n", + " lr_history.append(lr)\n", "\n", " train_losses.append(avg_train_loss)\n", " val_losses.append(avg_val_loss)\n", " val_accs.append(val_acc)\n", "\n", - " if epoch % 5 == 0 or epoch == 1:\n", - " print(f\"Epoch {epoch:3d}/{N_EPOCHS} train_loss={avg_train_loss:.4f} \"\n", - " f\"val_loss={avg_val_loss:.4f} val_acc={val_acc:.4f} lr={lr:.2e}\")\n", + " # ── Phát hiện điểm hội tụ (sliding window trên val_loss) ──────────────\n", + " if convergence_epoch is None and epoch >= CONV_WINDOW + 1:\n", + " window = val_losses[-(CONV_WINDOW + 1):]\n", + " improvements = [abs(window[i] - window[i - 1]) for i in range(1, len(window))]\n", + " if all(imp < CONV_THRESHOLD for imp in improvements):\n", + " convergence_epoch = epoch - CONV_WINDOW + 1\n", + " print(f\" 📍 Hội tụ phát hiện tại epoch {convergence_epoch} \"\n", + " f\"(val_loss={val_losses[convergence_epoch-1]:.4f})\")\n", "\n", - " # Early stopping\n", + " if epoch % 5 == 0 or epoch == 1:\n", + " print(f\" Epoch {epoch:3d}/{N_EPOCHS} train={avg_train_loss:.4f} \"\n", + " f\"val={avg_val_loss:.4f} acc={val_acc:.4f} lr={lr:.2e}\")\n", + "\n", + " # ── Early stopping ─────────────────────────────────────────────────────\n", " if avg_val_loss < best_val_loss:\n", - " best_val_loss = avg_val_loss\n", + " best_val_loss = avg_val_loss\n", + " best_epoch = epoch\n", " best_state_dict = {k: v.clone() for k, v in model.state_dict().items()}\n", " patience_counter = 0\n", " else:\n", " patience_counter += 1\n", " if patience_counter >= PATIENCE:\n", - " print(f\"⏹ Early stopping at epoch {epoch}.\")\n", + " early_stop_epoch = epoch\n", + " print(f\"⏹ Early stopping tại epoch {epoch} (patience={PATIENCE})\")\n", " break\n", "\n", "# Restore best weights\n", "model.load_state_dict(best_state_dict)\n", - "print(f\"\\n✅ Training hoàn tất! Best val_loss={best_val_loss:.4f}\")\n", + "total_epochs = len(train_losses)\n", + "print(f\"\\n✅ Training hoàn tất! Best epoch={best_epoch} Best val_loss={best_val_loss:.4f}\")\n", + "if convergence_epoch:\n", + " print(f\" Điểm hội tụ : epoch {convergence_epoch}\")\n", "\n", - "# ── Learning curves ──────────────────────────────────────────────────────────\n", - "fig, axes = plt.subplots(1, 2, figsize=(13, 4))\n", - "axes[0].plot(train_losses, label=\"Train\")\n", - "axes[0].plot(val_losses, label=\"Val\")\n", - "axes[0].set_title(\"Loss\")\n", - "axes[0].legend()\n", - "axes[1].plot(val_accs)\n", - "axes[1].set_title(\"Val Accuracy\")\n", + "# ═══════════════════════════════════════════════════════════════════════════════\n", + "# PHÂN TÍCH ĐIỂM HỘI TỤ — MobileNetV3 + LR-ASPP\n", + "# EMA-smoothed curves + annotated convergence / best / early-stop markers\n", + "# ═══════════════════════════════════════════════════════════════════════════════\n", + "epochs_axis = list(range(1, total_epochs + 1))\n", + "\n", + "# Exponential Moving Average smoothing\n", + "def ema(values, alpha=0.2):\n", + " s = [values[0]]\n", + " for v in values[1:]:\n", + " s.append(alpha * v + (1 - alpha) * s[-1])\n", + " return s\n", + "\n", + "val_losses_ema = ema(val_losses, alpha=0.3)\n", + "train_losses_ema = ema(train_losses, alpha=0.3)\n", + "val_accs_ema = ema(val_accs, alpha=0.3)\n", + "\n", + "# ── Tính ΔVal-loss per epoch ──────────────────────────────────────────────────\n", + "delta_val = [abs(val_losses_ema[i] - val_losses_ema[i-1])\n", + " for i in range(1, len(val_losses_ema))]\n", + "\n", + "fig, axes = plt.subplots(2, 2, figsize=(16, 10))\n", + "\n", + "# --- Top-left: Loss curves ---\n", + "ax = axes[0, 0]\n", + "ax.plot(epochs_axis, train_losses, \"b-\", alpha=0.25, linewidth=0.8)\n", + "ax.plot(epochs_axis, train_losses_ema, \"b-\", linewidth=2, label=\"Train loss (EMA)\")\n", + "ax.plot(epochs_axis, val_losses, \"g-\", alpha=0.25, linewidth=0.8)\n", + "ax.plot(epochs_axis, val_losses_ema, \"g-\", linewidth=2, label=\"Val loss (EMA)\")\n", + "ax.axvline(x=best_epoch, color=\"red\", linestyle=\"--\", linewidth=1.5,\n", + " label=f\"Best epoch={best_epoch}\")\n", + "if convergence_epoch:\n", + " ax.axvline(x=convergence_epoch, color=\"orange\", linestyle=\":\", linewidth=1.5,\n", + " label=f\"HỘI TỤ epoch={convergence_epoch}\")\n", + "if early_stop_epoch:\n", + " ax.axvline(x=early_stop_epoch, color=\"gray\", linestyle=\"-.\", linewidth=1.5,\n", + " label=f\"Early stop epoch={early_stop_epoch}\")\n", + "ax.set_xlabel(\"Epoch\")\n", + "ax.set_ylabel(\"Loss\")\n", + "ax.set_title(\"Loss Curves (raw + EMA)\")\n", + "ax.legend(fontsize=8)\n", + "ax.grid(True, alpha=0.3)\n", + "\n", + "# --- Top-right: Val accuracy ---\n", + "ax = axes[0, 1]\n", + "ax.plot(epochs_axis, val_accs, \"g-\", alpha=0.3, linewidth=0.8)\n", + "ax.plot(epochs_axis, val_accs_ema, \"g-\", linewidth=2, label=\"Val acc (EMA)\")\n", + "ax.axvline(x=best_epoch, color=\"red\", linestyle=\"--\", linewidth=1.5,\n", + " label=f\"Best epoch={best_epoch} ({val_accs[best_epoch-1]*100:.2f}%)\")\n", + "if convergence_epoch:\n", + " ax.axvline(x=convergence_epoch, color=\"orange\", linestyle=\":\", linewidth=1.5,\n", + " label=f\"HỘI TỤ epoch={convergence_epoch} ({val_accs[convergence_epoch-1]*100:.2f}%)\")\n", + "if early_stop_epoch:\n", + " ax.axvline(x=early_stop_epoch, color=\"gray\", linestyle=\"-.\", linewidth=1.5,\n", + " label=f\"Early stop\")\n", + "ax.set_xlabel(\"Epoch\")\n", + "ax.set_ylabel(\"Accuracy\")\n", + "ax.set_title(\"Val Accuracy Curve\")\n", + "ax.legend(fontsize=8)\n", + "ax.grid(True, alpha=0.3)\n", + "\n", + "# --- Bottom-left: ΔVal-loss (marginal improvement) ---\n", + "ax = axes[1, 0]\n", + "ax.bar(epochs_axis[1:], delta_val,\n", + " color=[\"green\" if d > CONV_THRESHOLD else \"salmon\" for d in delta_val],\n", + " alpha=0.75)\n", + "ax.axhline(y=CONV_THRESHOLD, color=\"red\", linestyle=\"--\",\n", + " label=f\"Threshold = {CONV_THRESHOLD:.0e}\")\n", + "if convergence_epoch:\n", + " ax.axvline(x=convergence_epoch, color=\"orange\", linestyle=\":\", linewidth=1.5,\n", + " label=f\"HỘI TỤ epoch={convergence_epoch}\")\n", + "ax.set_xlabel(\"Epoch\")\n", + "ax.set_ylabel(\"|ΔVal Loss|\")\n", + "ax.set_title(\"Marginal Val-Loss Improvement per Epoch\")\n", + "ax.legend(fontsize=8)\n", + "ax.grid(True, alpha=0.3)\n", + "\n", + "# --- Bottom-right: Learning rate schedule ---\n", + "ax = axes[1, 1]\n", + "ax.semilogy(epochs_axis, lr_history, \"purple\", linewidth=2)\n", + "if convergence_epoch:\n", + " ax.axvline(x=convergence_epoch, color=\"orange\", linestyle=\":\", linewidth=1.5,\n", + " label=f\"HỘI TỤ epoch={convergence_epoch}\")\n", + "ax.axvline(x=best_epoch, color=\"red\", linestyle=\"--\", linewidth=1.5,\n", + " label=f\"Best epoch={best_epoch}\")\n", + "ax.set_xlabel(\"Epoch\")\n", + "ax.set_ylabel(\"Learning Rate (log)\")\n", + "ax.set_title(\"Learning Rate Schedule (ReduceLROnPlateau)\")\n", + "ax.legend(fontsize=8)\n", + "ax.grid(True, alpha=0.3)\n", + "\n", + "plt.suptitle(\"MobileNetV3 + LR-ASPP — Convergence Analysis\", fontsize=13, fontweight=\"bold\")\n", "plt.tight_layout()\n", - "plt.show()\n" + "plt.show()\n", + "\n", + "# ── Tổng kết ──────────────────────────────────────────────────────────────────\n", + "print(f\"\\n{'═'*60}\")\n", + "print(f\" Tổng số epoch : {total_epochs}\")\n", + "print(f\" Best epoch : {best_epoch} (val_loss={best_val_loss:.4f})\")\n", + "print(f\" Best val accuracy : {val_accs[best_epoch-1]*100:.4f}%\")\n", + "if convergence_epoch:\n", + " print(f\" Điểm HỘI TỤ : epoch {convergence_epoch} \"\n", + " f\"(val_acc={val_accs[convergence_epoch-1]*100:.2f}%)\")\n", + " wasted = total_epochs - convergence_epoch\n", + " print(f\" Epochs sau hội tụ : {wasted} \"\n", + " f\"(có thể giảm N_EPOCHS không ảnh hưởng nhiều đến kết quả)\")\n", + "if early_stop_epoch:\n", + " print(f\" Early stop tại epoch: {early_stop_epoch}\")\n", + "print(f\"{'═'*60}\")\n" ] }, {