Repository navigation
Conversation
Optimal threshold for values at limit=1 will always return 0.0, overriding actual optima. Calculate from 2+ instead.
- add OptimizeEvaluator() to parallel.py
Fix a bug in pareto_front where pairs already seen were only recorded after the dominance check
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #967 +/- ##
==========================================
+ Coverage 99.63% 99.64% +0.01%
==========================================
Files 103 104 +1
Lines 8307 8549 +242
==========================================
+ Hits 8277 8519 +242
Misses 30 30 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟢 Approval recommended
Static review found no blocking issues in parallel evaluation, Pareto selection, or results export; project tests were not executed.
No open issues 🎉
What changed in this PR
This PR extends Annif’s optimize CLI with parallel metric evaluation and richer output for choosing limit and threshold settings.
Changes:
- Uses per-worker evaluators to run parameter evaluations with the existing
--jobsoption. - Reports a recall-sorted Pareto front while retaining the best-F1 summary.
- Adds TSV results export with Pareto membership, plus CLI tests for export and multiprocessing.
| File | Description |
|---|---|
tests/test_cli.py |
Updates summary assertions and adds export, error-handling, and multiprocessing tests. |
annif/parallel.py |
Adds OptimizeEvaluator for per-worker parameter evaluation. |
annif/cli.py |
Integrates parallel evaluation, Pareto reporting, and TSV export. |
annif/cli_util.py |
Adds Pareto-front selection with deduplication and recall ordering. |
Review effort: Balanced
|
Just reporting about my tests for the performance optimization, that I did by running on our server (yso-ensemble-en project is the simple ensemble version of the one used in FintoAI at the moment, and the dataset (1865 documents) is the one we use to test the YSO models). With -j 6BeforeAfterA second runs produced very similar results. So the optimization cut some 20% of the wall time! An did not affect the results or the memory consumption. With -j 1BeforeAfterWith one job the PR changes have no effect as expected. |
|
Here are some plots of the optimize command file output (the dataset is the same as for the performance evaluation above). Credit goes to Qwen 3.8. Jupyter notebook of the plots as a GH gist here.
|
juhoinkinen
left a comment
There was a problem hiding this comment.
I think this could be merged 👍
The stdout output after this PR is like this:
Limit Thresh. Prec. Rec. F1
1 0.00 0.7512 0.1338 0.2141
1 0.05 0.7496 0.1336 0.2138
1 0.10 0.7453 0.1329 0.2127
[...]
15 0.90 0.0000 0.0000 0.0000
15 0.95 0.0000 0.0000 0.0000
Pareto front: best precision at each recall level
Limit Thresh. Prec. Rec. F1
1 0.00 0.7512 0.1338 0.2141
2 0.00 0.7029 0.2397 0.3328
3 0.10 0.6557 0.3160 0.3966
[...]
14 0.00 0.3404 0.6917 0.4285
15 0.00 0.3254 0.7050 0.4187
Best F1 score (doc avg): 0.4825 Limit: 9 Threshold: 0.1000
Documents evaluated: 1865
The results file (TSV) output is like this:
Limit Threshold Precision (doc avg) Recall (doc avg) F1 score (doc avg) Pareto front
1 0 0.751206 0.133784 0.214122 1
1 0.05 0.749598 0.133593 0.213785 0
1 0.1 0.745308 0.132949 0.212698 0
1 0.15 0.735657 0.131129 0.209767 0
1 0.2 0.720643 0.127940 0.204853 0
1 0.25 0.699196 0.122567 0.197261 0
[...]
I tried to think if the outputs could be simplified somehow (it's already quite a long output to stdout, we could later think about having an option to silent the limit-threshold loop output), but could not see a good way for it.
Overall, the only alternative that comes to mind is to have an option to that would append the Pareto-front column to the stdout output, so that the results file output would not be needed, but with the existing results file output in the hyperopt command that makes no sense.
| "\t".join( | ||
| ( | ||
| str(limit), | ||
| f"{threshold:g}", |
There was a problem hiding this comment.
Formatting threshold using :g for the results file is different than for stdout output. The threshold values vary by two digits, so it could be more naturaly to use :.2f.
There was a problem hiding this comment.
Good catch -- I've used :.4f because that's the standard in eg. optimize and it allows rounding to 3 places.
|
The Pareto-front optimization functionality would benefit from documentation, probably in Achieving good results page. Do you have something in mind that you could write, @mjsuhonos? |
|
I'm a bit confused - shall we merge this now that Juho has approved it or is there still something to be done? @juhoinkinen @mjsuhonos |
|
Yes, @juhoinkinen’s comments from yesterday are worth a commit, I think. Will do shortly. |
osma
left a comment
There was a problem hiding this comment.
Thanks again @mjsuhonos for the fixes! I'll leave it to @juhoinkinen to merge this.
|
@mjsuhonos It looks like your latest commit broke the |
|






This PR brings some new functionality to the
optimizeCLI tool:New class:
OptimizeEvaluator(): enables per-worker evaluation for parallel optimization.run_optimize()now uses this class to run optimization metrics in parallel (as specified by the existing-jparameter).Pareto front calculation: the
optimizeoutput remains identical, but the summary now provides a list of Pareto points (by ascendingRecall) rather than "Best Precision" and "Best Recall"; "Best F1" is kept. This provides a choice of operational points depending on the shape of the Precision-Recall curve.Results file output:
-r --results-fileparameter added to mirror the implementation inhyperopt. Output of the file is a TSV that contains the same rows as theoptimizestdout, with Pareto points in an additional column.The results file is especially useful for cannif because it allows generating charts like these:
Some of the code was provided by GLM-5.3 (Mistral Hosted) but every line of code was reviewed by myself.