A learning curve visualizes model performance (training and validation scores) as a function of training set size. It is essential for diagnosing bias vs variance tradeoffs, determining whether collecting more data would improve model performance, and guiding model selection decisions. The plot typically shows two lines with shaded confidence bands representing variability across cross-validation folds.

# anyplot.ai
# learning-curve-basic: Model Learning Curve
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 84/100 | Created: 2026-09-05
using CairoMakie
using Colors
using Random
using Statistics
Random.seed!(42)
# --- Theme tokens -----------------------------------------------------------
const THEME = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
const IMPRINT_PALETTE = [
colorant"#009E73", colorant"#C475FD", colorant"#4467A3", colorant"#BD8233",
colorant"#AE3030", colorant"#2ABCCD", colorant"#954477", colorant"#99B314",
]
const TRAIN_COLOR = IMPRINT_PALETTE[1] # brand green — always first series
const VAL_COLOR = IMPRINT_PALETTE[2] # lavender — second series
# --- Data ---------------------------------------------------------------
# Digit classifier learning curve: accuracy vs training set size, across
# 8 cross-validation folds. Training accuracy starts near-perfect and eases
# down as the model sees more (harder) examples; validation accuracy starts
# low (underfit on tiny samples) and climbs toward the training curve as
# more data narrows the generalization gap — the classic bias/variance
# diagnostic shape.
n_folds = 8
train_sizes = [80, 160, 240, 360, 480, 640, 800, 1000, 1250, 1500]
n_sizes = length(train_sizes)
train_mean_target = 0.995 .- 0.055 .* (1 .- exp.(-train_sizes ./ 900))
val_mean_target = 0.965 .- 0.28 .* exp.(-train_sizes ./ 500)
train_std_target = 0.05 .* exp.(-train_sizes ./ 500) .+ 0.004
val_std_target = 0.09 .* exp.(-train_sizes ./ 600) .+ 0.008
train_scores = Matrix{Float64}(undef, n_folds, n_sizes)
validation_scores = Matrix{Float64}(undef, n_folds, n_sizes)
for j in 1:n_sizes
train_scores[:, j] = clamp.(train_mean_target[j] .+ train_std_target[j] .* randn(n_folds), 0.0, 1.0)
validation_scores[:, j] = clamp.(val_mean_target[j] .+ val_std_target[j] .* randn(n_folds), 0.0, 1.0)
end
train_mean = vec(mean(train_scores; dims = 1))
train_std = vec(std(train_scores; dims = 1))
val_mean = vec(mean(validation_scores; dims = 1))
val_std = vec(std(validation_scores; dims = 1))
# --- Plot -----------------------------------------------------------------
fig = Figure(
resolution = (1600, 900),
fontsize = 14,
backgroundcolor = PAGE_BG,
)
ax = Axis(
fig[1, 1];
title = "learning-curve-basic · julia · makie · anyplot.ai",
titlesize = 20,
titlecolor = INK,
xlabel = "Training Set Size",
ylabel = "Accuracy",
xlabelsize = 14,
ylabelsize = 14,
xlabelcolor = INK,
ylabelcolor = INK,
xticklabelsize = 12,
yticklabelsize = 12,
xticklabelcolor = INK_SOFT,
yticklabelcolor = INK_SOFT,
xtickcolor = INK_SOFT,
ytickcolor = INK_SOFT,
backgroundcolor = PAGE_BG,
leftspinecolor = INK_SOFT,
bottomspinecolor = INK_SOFT,
topspinevisible = false,
rightspinevisible = false,
ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),
xgridvisible = false,
yminorgridvisible = false,
)
band!(ax, train_sizes, train_mean .- train_std, train_mean .+ train_std;
color = (TRAIN_COLOR, 0.18))
band!(ax, train_sizes, val_mean .- val_std, val_mean .+ val_std;
color = (VAL_COLOR, 0.18))
lines!(ax, train_sizes, train_mean; color = TRAIN_COLOR, linewidth = 3, label = "Training score")
scatter!(ax, train_sizes, train_mean; color = TRAIN_COLOR, markersize = 11, strokewidth = 1.5, strokecolor = PAGE_BG)
lines!(ax, train_sizes, val_mean; color = VAL_COLOR, linewidth = 3, label = "Validation score")
scatter!(ax, train_sizes, val_mean; color = VAL_COLOR, markersize = 11, strokewidth = 1.5, strokecolor = PAGE_BG)
ylims!(ax, 0.6, 1.02)
# --- Insight annotation: highlight where the bias/variance gap narrows ----
gap = train_mean .- val_mean
converge_idx = findfirst(<=(0.05), gap)
x_converge = converge_idx === nothing ? train_sizes[end] : train_sizes[converge_idx]
x_max = train_sizes[end]
vspan!(ax, x_converge, x_max; color = RGBAf(INK.r, INK.g, INK.b, 0.05))
vlines!(ax, [x_converge]; color = INK_SOFT, linestyle = :dash, linewidth = 1, ymax = 0.85)
text!(ax, (x_converge + x_max) / 2, 1.0; text = "Diminishing returns",
color = INK_SOFT, fontsize = 12, align = (:center, :top))
axislegend(ax; position = :rb, labelcolor = INK_SOFT, framevisible = false, labelsize = 12)
# --- Save -------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/learning-curve-basic/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "learning-curve-basic",
"language": "julia",
"library": "makie",
"page": "https://anyplot.ai/learning-curve-basic/julia/makie",
"hub": "https://anyplot.ai/learning-curve-basic",
"code_json": "https://api.anyplot.ai/specs/learning-curve-basic/makie/code",
"spec_json": "https://api.anyplot.ai/specs/learning-curve-basic",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/learning-curve-basic/julia/makie/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/learning-curve-basic/julia/makie/plot-dark.png",
"quality_score": 84.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Model Learning Curve on anyplot.ai.