{"spec_id":"line-loss-training","library":"makie","language":"julia","code":"# anyplot.ai\n# line-loss-training: Training Loss Curve\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 92/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\nconst ANYPLOT_AMBER = colorant\"#DDCC77\"  # warning / caution — early-stopping marker\n\n# --- Data ---------------------------------------------------------------\n# Synthetic training history: training loss decays smoothly, validation loss\n# decays alongside it until the model starts overfitting past epoch 45.\nepochs = collect(1:80)\nn = length(epochs)\n\ntrain_loss_base = 2.0 .* exp.(-0.065 .* epochs) .+ 0.03\ntrain_loss = train_loss_base .* (1 .+ 0.04 .* randn(n))\ntrain_loss = clamp.(train_loss, 0.02, Inf)\n\noverfit_start = 45\nval_loss = zeros(n)\nfor (i, e) in enumerate(epochs)\n    base = 2.2 * exp(-0.058 * e) + 0.16\n    if e > overfit_start\n        base += 0.0035 * (e - overfit_start)^1.3\n    end\n    val_loss[i] = clamp(base + 0.035 * randn(), 0.05, Inf)\nend\n\nbest_epoch = epochs[argmin(val_loss)]\nbest_val_loss = minimum(val_loss)\n\n# --- Plot -----------------------------------------------------------------\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"line-loss-training · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Epoch\",\n    ylabel             = \"Cross-Entropy Loss (log scale)\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    yscale             = log10,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n)\n\nvlines!(ax, [best_epoch]; color = ANYPLOT_AMBER, linewidth = 1.5, linestyle = :dash)\n\nlines!(ax, epochs, train_loss; color = IMPRINT_PALETTE[1], linewidth = 3.0, label = \"Training loss\")\nlines!(ax, epochs, val_loss; color = IMPRINT_PALETTE[2], linewidth = 3.0, label = \"Validation loss\")\n\nscatter!(ax, [best_epoch], [best_val_loss];\n    color = ANYPLOT_AMBER, markersize = 18, strokewidth = 1.5, strokecolor = PAGE_BG,\n    label = \"Early-stopping epoch\")\n\ntext!(ax, best_epoch + 3, best_val_loss * 1.9;\n    text     = \"Best val loss @ epoch $(best_epoch)\",\n    color    = INK_SOFT,\n    fontsize = 13,\n    align    = (:left, :baseline),\n)\n\naxislegend(ax;\n    position      = :rt,\n    labelcolor    = INK_SOFT,\n    framevisible  = false,\n    backgroundcolor = PAGE_BG,\n)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}