Skip to content

Commit a8ae1d4

Browse files
committed
feat(julia): add eval_held_out.jl computing MRR/top-k on validation split
Loads a trained NeuralSolver from models/neural/ (same candidate-dir order as load_gnn_model), runs compute_metrics on the 20% held-out split, and appends a JSONL row to training_data/eval_results.jsonl. Exits non-zero if MRR < 0.66 (below the cosine baseline) so CI can gate on it. Invoked by `just eval`. https://claude.ai/code/session_01YPqu7gti4azBach6ZvpRFJ
1 parent 7b90a65 commit a8ae1d4

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

src/julia/eval_held_out.jl

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
#
4+
# eval_held_out.jl — Evaluate a trained GNN model on the held-out validation split.
5+
#
6+
# Measures MRR, top-1, top-5, top-10 and appends one JSONL row to
7+
# training_data/eval_results.jsonl. Compares against the 0.66 cosine baseline.
8+
#
9+
# Usage:
10+
# just eval
11+
# or:
12+
# cd src/julia && julia --project=. eval_held_out.jl
13+
14+
using Pkg
15+
Pkg.activate(joinpath(@__DIR__))
16+
17+
using EchidnaML
18+
using JSON3, Dates, Statistics, BSON
19+
20+
function main()
21+
repo_root = abspath(joinpath(@__DIR__, "..", ".."))
22+
data_dir = joinpath(repo_root, "training_data")
23+
24+
# Try candidate model directories in the same order as load_gnn_model.
25+
candidate_dirs = [
26+
joinpath(repo_root, "models", "neural", "gnn_ranker"),
27+
joinpath(repo_root, "models", "neural", "best_model"),
28+
joinpath(repo_root, "models", "neural", "final_model"),
29+
]
30+
31+
models_dir = nothing
32+
for d in candidate_dirs
33+
if isdir(d)
34+
models_dir = d
35+
break
36+
end
37+
end
38+
39+
if models_dir === nothing
40+
error("No trained model found — run 'just train-cpu' first. " *
41+
"Checked: $(join(candidate_dirs, ", "))")
42+
end
43+
44+
@info "Loading solver from $models_dir"
45+
solver = load_solver(models_dir)
46+
47+
@info "Loading validation data from $data_dir"
48+
t_start = time()
49+
(_train_ds, val_ds, vocab) = load_training_data(data_dir; train_split=0.8f0)
50+
@info "Validation split size: $(length(val_ds.examples)) vocab: $(vocab.vocab_size)"
51+
52+
if isempty(val_ds.examples)
53+
error("Validation dataset is empty — check training_data/ has proof_states and premises files.")
54+
end
55+
56+
# Evaluate using the same scoring path as train.jl::compute_metrics.
57+
# compute_metrics returns (precision, recall, mrr) using cosine over text embeddings;
58+
# when a trained model is loaded, the solver's text_encoder reflects learned weights.
59+
val_metrics = compute_metrics(solver, val_ds; k=10)
60+
mrr = val_metrics.mrr
61+
top1 = val_metrics.precision # precision@1 is top-1 accuracy at k=1
62+
top5 = val_metrics.recall # secondary; full top-k breakdown below
63+
top10 = val_metrics.precision # reuse — compute_metrics gives precision@k
64+
65+
# Finer breakdown: compute_metrics at k=1 and k=5.
66+
val_k1 = compute_metrics(solver, val_ds; k=1)
67+
val_k5 = compute_metrics(solver, val_ds; k=5)
68+
top1 = val_k1.precision
69+
top5 = val_k5.precision
70+
top10 = val_metrics.precision
71+
72+
duration = time() - t_start
73+
74+
git_sha = try
75+
strip(read(`git -C $repo_root rev-parse HEAD`, String))
76+
catch
77+
"unknown"
78+
end
79+
80+
metrics = Dict(
81+
"timestamp" => string(now()),
82+
"git_sha" => git_sha,
83+
"model_path" => models_dir,
84+
"val_size" => length(val_ds.examples),
85+
"vocab_size" => vocab.vocab_size,
86+
"mrr" => mrr,
87+
"top1" => top1,
88+
"top5" => top5,
89+
"top10" => top10,
90+
"cosine_baseline_mrr" => 0.66,
91+
"eval_duration_seconds" => duration,
92+
)
93+
94+
out_path = joinpath(data_dir, "eval_results.jsonl")
95+
open(out_path, "a") do io
96+
println(io, JSON3.write(metrics))
97+
end
98+
99+
@info "Eval complete: MRR=$mrr top1=$top1 top5=$top5 top10=$top10"
100+
@info "Results appended to $out_path"
101+
102+
if mrr >= 0.66f0
103+
@info "PASS: MRR=$mrr ≥ 0.66 cosine baseline — weights are improving retrieval."
104+
else
105+
@warn "FAIL: MRR=$mrr < 0.66 cosine baseline — stop, file a bug before proceeding."
106+
exit(1)
107+
end
108+
end
109+
110+
main()

0 commit comments

Comments
 (0)