{"spec_id":"learning-curve-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# learning-curve-basic: Model Learning Curve\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 84/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\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 TRAIN_COLOR = IMPRINT_PALETTE[1]  # brand green — always first series\nconst VAL_COLOR   = IMPRINT_PALETTE[2]  # lavender — second series\n\n# --- Data ---------------------------------------------------------------\n# Digit classifier learning curve: accuracy vs training set size, across\n# 8 cross-validation folds. Training accuracy starts near-perfect and eases\n# down as the model sees more (harder) examples; validation accuracy starts\n# low (underfit on tiny samples) and climbs toward the training curve as\n# more data narrows the generalization gap — the classic bias/variance\n# diagnostic shape.\nn_folds = 8\ntrain_sizes = [80, 160, 240, 360, 480, 640, 800, 1000, 1250, 1500]\nn_sizes = length(train_sizes)\n\ntrain_mean_target = 0.995 .- 0.055 .* (1 .- exp.(-train_sizes ./ 900))\nval_mean_target = 0.965 .- 0.28 .* exp.(-train_sizes ./ 500)\n\ntrain_std_target = 0.05 .* exp.(-train_sizes ./ 500) .+ 0.004\nval_std_target = 0.09 .* exp.(-train_sizes ./ 600) .+ 0.008\n\ntrain_scores = Matrix{Float64}(undef, n_folds, n_sizes)\nvalidation_scores = Matrix{Float64}(undef, n_folds, n_sizes)\nfor j in 1:n_sizes\n    train_scores[:, j] = clamp.(train_mean_target[j] .+ train_std_target[j] .* randn(n_folds), 0.0, 1.0)\n    validation_scores[:, j] = clamp.(val_mean_target[j] .+ val_std_target[j] .* randn(n_folds), 0.0, 1.0)\nend\n\ntrain_mean = vec(mean(train_scores; dims = 1))\ntrain_std = vec(std(train_scores; dims = 1))\nval_mean = vec(mean(validation_scores; dims = 1))\nval_std = vec(std(validation_scores; dims = 1))\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              = \"learning-curve-basic · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Training Set Size\",\n    ylabel             = \"Accuracy\",\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    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    topspinevisible     = false,\n    rightspinevisible   = false,\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xgridvisible       = false,\n    yminorgridvisible  = false,\n)\n\nband!(ax, train_sizes, train_mean .- train_std, train_mean .+ train_std;\n      color = (TRAIN_COLOR, 0.18))\nband!(ax, train_sizes, val_mean .- val_std, val_mean .+ val_std;\n      color = (VAL_COLOR, 0.18))\n\nlines!(ax, train_sizes, train_mean; color = TRAIN_COLOR, linewidth = 3, label = \"Training score\")\nscatter!(ax, train_sizes, train_mean; color = TRAIN_COLOR, markersize = 11, strokewidth = 1.5, strokecolor = PAGE_BG)\n\nlines!(ax, train_sizes, val_mean; color = VAL_COLOR, linewidth = 3, label = \"Validation score\")\nscatter!(ax, train_sizes, val_mean; color = VAL_COLOR, markersize = 11, strokewidth = 1.5, strokecolor = PAGE_BG)\n\nylims!(ax, 0.6, 1.02)\n\n# --- Insight annotation: highlight where the bias/variance gap narrows ----\ngap = train_mean .- val_mean\nconverge_idx = findfirst(<=(0.05), gap)\nx_converge = converge_idx === nothing ? train_sizes[end] : train_sizes[converge_idx]\nx_max = train_sizes[end]\n\nvspan!(ax, x_converge, x_max; color = RGBAf(INK.r, INK.g, INK.b, 0.05))\nvlines!(ax, [x_converge]; color = INK_SOFT, linestyle = :dash, linewidth = 1, ymax = 0.85)\ntext!(ax, (x_converge + x_max) / 2, 1.0; text = \"Diminishing returns\",\n      color = INK_SOFT, fontsize = 12, align = (:center, :top))\n\naxislegend(ax; position = :rb, labelcolor = INK_SOFT, framevisible = false, labelsize = 12)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}