{"spec_id":"line-loss-training","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nline-loss-training: Training Loss Curve\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-14\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito colors\nTRAINING_COLOR = \"#009E73\"\nVALIDATION_COLOR = \"#C475FD\"\n\n# Data\nnp.random.seed(42)\nepochs = np.arange(1, 151)\n\n# Training loss: sigmoid saturation pattern (different from exponential)\n# Starts high (~2.2) and rapidly decays, then plateaus\ntrain_loss = 2.2 / (1 + np.exp((epochs - 25) / 8)) + 0.12 + np.random.randn(150) * 0.012\n\n# Validation loss: similar initial decay but with step-function overfitting\n# Decays to minimum around epoch 65, then increases due to overfitting\nval_base = 2.1 / (1 + np.exp((epochs - 30) / 9)) + 0.18\nval_loss = np.copy(val_base) + np.random.randn(150) * 0.015\n\n# Add step-function overfitting: sharp increase after epoch 65\noverfitting_start = 65\nval_loss[overfitting_start:] += (\n    np.linspace(0, 0.5, 150 - overfitting_start) + np.random.randn(150 - overfitting_start) * 0.012\n)\n\n# Find minimum validation loss epoch\nmin_val_epoch = np.argmin(val_loss) + 1\nmin_val_loss = val_loss[min_val_epoch - 1]\n\n# Plot\nfig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nax.plot(epochs, train_loss, linewidth=3, color=TRAINING_COLOR, label=\"Training Loss\", marker=\"o\", markersize=4)\nax.plot(epochs, val_loss, linewidth=3, color=VALIDATION_COLOR, label=\"Validation Loss\", marker=\"s\", markersize=4)\n\n# Mark minimum validation loss (optimal early stopping)\nax.scatter([min_val_epoch], [min_val_loss], s=250, color=TRAINING_COLOR, zorder=5, edgecolors=PAGE_BG, linewidth=1.5)\nax.annotate(\n    f\"Epoch {min_val_epoch}\",\n    xy=(min_val_epoch, min_val_loss),\n    xytext=(min_val_epoch + 20, min_val_loss - 0.25),\n    fontsize=14,\n    color=INK,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_SOFT, \"lw\": 1.5},\n    bbox={\"boxstyle\": \"round,pad=0.4\", \"facecolor\": PAGE_BG, \"edgecolor\": INK_SOFT, \"linewidth\": 1},\n)\n\n# Style\nax.set_xlabel(\"Epoch\", fontsize=20, color=INK)\nax.set_ylabel(\"Cross-Entropy Loss\", fontsize=20, color=INK)\nax.set_title(\"line-loss-training · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=16, colors=INK_SOFT)\n\n# Spines\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n\n# Grid (subtle, y-axis only)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\n# Legend (upper left to avoid overlap)\nleg = ax.legend(fontsize=16, loc=\"upper left\", framealpha=0.95)\nif leg:\n    leg.get_frame().set_facecolor(PAGE_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    leg.get_frame().set_linewidth(0.8)\n    for text in leg.get_texts():\n        text.set_color(INK_SOFT)\n\nax.set_xlim(0, 155)\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}