{"spec_id":"line-loss-training","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nline-loss-training: Training Loss Curve\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\nimport shutil\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_point,\n    geom_vline,\n    ggplot,\n    ggsize,\n    labs,\n    scale_color_manual,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito palette\nBRAND = \"#009E73\"  # Training loss (first series)\nACCENT = \"#C475FD\"  # Validation loss (second series)\nOPTIMAL = \"#DC2626\"  # Optimal epoch marker\n\n# Data - Simulated neural network training loss over 100 epochs\nnp.random.seed(42)\nepochs = np.arange(1, 101)\n\n# Training loss: starts high, decreases with noise - continues to decrease throughout\ntrain_loss = 2.5 * np.exp(-0.05 * epochs) + 0.08 + np.random.normal(0, 0.015, len(epochs))\n\n# Validation loss: decreases then increases (overfitting after ~50 epochs)\nval_loss_base = 2.5 * np.exp(-0.045 * epochs) + 0.2\nnoise = np.random.normal(0, 0.02, len(epochs))\nval_loss = val_loss_base + noise\n# Add overfitting effect - validation loss increases after epoch 50\noverfitting_start = 50\nval_loss[overfitting_start:] = val_loss[overfitting_start:] + 0.008 * (epochs[overfitting_start:] - overfitting_start)\n\n# Find optimal epoch (minimum validation loss)\noptimal_epoch = int(epochs[np.argmin(val_loss)])\noptimal_loss = float(val_loss.min())\n\n# Create DataFrame for plotting\ndf = pd.DataFrame(\n    {\n        \"Epoch\": np.tile(epochs, 2),\n        \"Loss\": np.concatenate([train_loss, val_loss]),\n        \"Type\": [\"Training Loss\"] * len(epochs) + [\"Validation Loss\"] * len(epochs),\n    }\n)\n\n# Optimal point marker\noptimal_df = pd.DataFrame({\"Epoch\": [optimal_epoch], \"Loss\": [optimal_loss]})\n\n# Custom theme\ncustom_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_border=element_blank(),\n    panel_grid_major=element_line(color=INK_SOFT, size=0.3, linetype=\"solid\"),\n    panel_grid_minor=element_blank(),\n    axis_line_x=element_line(color=INK_SOFT, size=0.5),\n    axis_line_y=element_line(color=INK_SOFT, size=0.5),\n    axis_title=element_text(size=20, color=INK),\n    axis_text=element_text(size=16, color=INK_SOFT),\n    plot_title=element_text(size=24, color=INK),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(size=16, color=INK_SOFT),\n    legend_title=element_text(size=16, color=INK),\n    legend_position=\"top\",\n)\n\n# Create plot\nplot = (\n    ggplot(df, aes(x=\"Epoch\", y=\"Loss\", color=\"Type\"))\n    + geom_line(size=1.5, alpha=0.9)\n    + geom_point(data=optimal_df, mapping=aes(x=\"Epoch\", y=\"Loss\"), color=OPTIMAL, size=6, shape=18, inherit_aes=False)\n    + geom_vline(xintercept=optimal_epoch, color=OPTIMAL, size=0.8, linetype=\"dashed\", alpha=0.7)\n    + scale_color_manual(values=[BRAND, ACCENT])\n    + labs(title=\"line-loss-training · letsplot · anyplot.ai\", x=\"Epoch\", y=\"Cross-Entropy Loss\", color=\"\")\n    + theme_minimal()\n    + custom_theme\n    + ggsize(1600, 900)\n)\n\n# Save as PNG (scale 3x for 4800x2700)\nggsave(plot, f\"plot-{THEME}.png\", scale=3)\nggsave(plot, f\"plot-{THEME}.html\")\n\n# Move files from lets-plot-images directory to current directory\nimages_dir = Path(\"lets-plot-images\")\nif images_dir.exists():\n    for file in images_dir.glob(f\"plot-{THEME}.*\"):\n        shutil.move(str(file), str(file.name))\n"}