Skip to content

Stop class masking from repeating an identification when the species does not change#1377

Open
mihow wants to merge 1 commit into
mainfrom
fix/class-masking-skip-unchanged-taxon
Open

Stop class masking from repeating an identification when the species does not change#1377
mihow wants to merge 1 commit into
mainfrom
fix/class-masking-skip-unchanged-taxon

Conversation

@mihow

@mihow mihow commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Class masking was recording a new identification for nearly every prediction it looked at, and most of those identifications named the species that was already there. Opening an occurrence showed the same species twice — once as the masked result and once as the intermediate prediction it replaced — each with its own Confirm button, which reads as a re-identification that never happened. In one run against real data, 948 of the 3,814 recorded results (24.9%) named the same taxon as the prediction they replaced.

This happens because masking moves probability around whenever a species the taxa list drops held any at all, and the existing check only skipped a prediction when the scores came back completely unchanged. Whether scores are re-weighted or not makes no difference; both settings produce the repeats.

Class masking now takes an option, on by default and exposed as a checkbox in the admin, to record a result only when the winning species actually changes. Runs write far fewer rows, and an occurrence's identification history only gains an entry when masking genuinely changed the answer. Unchecking it restores the previous behaviour, which is what you want when comparing confidence scores across taxa lists.

List of Changes

# What changes for the operator How
1 Masking no longer adds an identification that repeats the species already shown The skip check now also covers the case where the winning taxon is unchanged
2 The behaviour is selectable when triggering a run New only_when_taxon_changes config field (default True) with an admin checkbox
3 Recording every re-score is still available for score comparisons Unchecking the box passes only_when_taxon_changes=False through to the task

Detailed Description

Stacked on #1376; review that one first. This branch's own diff is the last commit.

Why the previous check did not catch these

The existing short-circuit compared the masked probability vector against the unmasked softmax and skipped only if they matched. That fires when every dropped class already scored about zero, which is rare — any dropped class holding real probability changes the vector, so a result was recorded even though the top species was untouched. The new check adds the condition that matters to a reader: did the winning species move?

Two decisions worth a look

The comparison uses taxon_id, not the related object. The scope query does not select_related the taxon, so reading classification.taxon would fetch one row per classification — an extra query for each of the tens of thousands of rows in a large run. Comparing ids reads a column already present on the row.

The setting is deliberately not part of the masking algorithm's identity. The re-weight mode is part of it, because the two modes persist different score semantics and a result's provenance has to be able to distinguish them. This setting only decides how many results get recorded; a result recorded under either setting means the same thing, so both resolve to the same Algorithm row. test_both_skip_settings_share_one_masking_algorithm pins that.

On existing data

Results already written are left alone; this only affects future runs. If the repeated entries on already-masked occurrences should be cleaned up, that is a separate data-management pass and I am happy to write it — worth deciding before this ships, since it determines whether the option's default matters for what is already in the database.

Testing

  • 137 tests pass across ami.ml.post_processing and ami.ml, including five new ones.
  • black, isort and flake8 clean.
  • The new tests cover both directions, not just the skip: test_new_classification_when_the_taxon_changes pins the transition masking exists for, so a guard that always skipped would fail. test_occurrences_updated_counts_only_changed_determinations now runs with the option off, because it is testing the metric rather than the option, and needs the re-scored occurrence to reach the counting step.

Summary by CodeRabbit

  • New Features

    • Added an option to record a new identification only when the predicted taxon changes.
    • The option is enabled by default and can be disabled when running class masking.
    • Re-scores are now skipped when the winning taxon remains unchanged, unless explicitly configured otherwise.
  • Bug Fixes

    • Improved occurrence update counts for cases where determinations change during masking.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c68751f0-da3a-450c-b0e3-f7f3bdc613a4

📥 Commits

Reviewing files that changed from the base of the PR and between 06791c4 and 92d8318.

📒 Files selected for processing (4)
  • ami/ml/post_processing/admin/class_masking_form.py
  • ami/ml/post_processing/class_masking.py
  • ami/ml/post_processing/tests/test_class_masking.py
  • ami/ml/post_processing/tests/test_class_masking_admin.py

📝 Walkthrough

Walkthrough

Class masking gains an only_when_taxon_changes setting, enabled by default, to suppress re-score records when the winning taxon is unchanged. The option flows from the admin form through job configuration and task execution, with tests covering both settings and algorithm identity.

Changes

Class masking taxon-change control

Layer / File(s) Summary
Configuration and admin wiring
ami/ml/post_processing/admin/class_masking_form.py, ami/ml/post_processing/class_masking.py
Adds the default-enabled configuration and form field, serializes it into job parameters, and passes it into the masking helper.
Conditional re-score execution
ami/ml/post_processing/class_masking.py
Skips re-scores whose winning taxon remains unchanged when enabled, while preserving algorithm identity across setting variants.
Behavior and admin validation
ami/ml/post_processing/tests/test_class_masking.py, ami/ml/post_processing/tests/test_class_masking_admin.py
Tests unchanged and changed taxa, disabled filtering, occurrence counting, shared algorithm identity, configuration defaults, and form-to-job propagation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: post-processing

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main behavioral change: class masking no longer repeats an identification when the species stays the same.
Description check ✅ Passed The PR description is detailed and mostly matches the template, including summary, changes, details, and testing; only optional sections are omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/class-masking-skip-unchanged-taxon

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@netlify

netlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploy Preview for antenna-preview canceled.

Name Link
🔨 Latest commit 92d8318
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/6a6151faab0d9900083d452d

@mihow
mihow force-pushed the fix/class-masking-skip-unchanged-taxon branch 3 times, most recently from 64979a9 to c5ab976 Compare July 22, 2026 22:33
Base automatically changed from fix/class-masking-scope-distinct to main July 22, 2026 23:25
Masking shifts probability whenever a class the taxa list drops held any, so
the run recorded a new terminal classification for nearly every prediction it
examined. In one run on a deployment with real data, 948 of 3,814 recorded
predictions (24.9%) named the same taxon as the prediction they replaced.
Those appear in an occurrence's identification history as a repeat of the
taxon already shown, each with its own Confirm control, and each stores
another copy of the logits and scores arrays.

`only_when_taxon_changes` (default True, exposed as an admin checkbox) records
a re-score only when the winning taxon actually moves. The old behaviour is
still available by unchecking it, which is what comparing confidence across
taxa lists needs.

Two details worth noting for review. The taxon comparison uses `taxon_id`
rather than the related instance, because the scope query does not fetch the
taxon and touching it would load one per row. The setting is deliberately not
part of the masking algorithm's identity, unlike the reweight mode: it changes
how many predictions are recorded, not what a recorded prediction means.

Co-Authored-By: Claude <noreply@anthropic.com>
@mihow
mihow force-pushed the fix/class-masking-skip-unchanged-taxon branch from c5ab976 to 92d8318 Compare July 22, 2026 23:27
@netlify

netlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploy Preview for antenna-ssec canceled.

Name Link
🔨 Latest commit 92d8318
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/6a6151fa4defb0000832e724

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant