Skip to content

Broaden the memorizing aroma heads, and measure whether it worked - #258

Merged
rvnminers-A-and-N merged 2 commits into
mainfrom
feat/aroma-broaden-memorizing-heads
Jul 29, 2026
Merged

rvnminers-A-and-N merged 2 commits into
mainfrom
feat/aroma-broaden-memorizing-heads

Conversation

@rvnminers-A-and-N

Copy link
Copy Markdown
Collaborator

Closes #256.

Two things here: a measurement instrument, and the data work it made possible.

The instrument

training/audit_generalization.py fires every head over the whole corpus and counts the hits that were not in its training set. That count is the head's discovery power, and a cross-validated AUROC cannot substitute for it — CV only ever asks about molecules inside the labelled set, so a head fit on twelve molecules sharing one scaffold scores 0.99 while discovering nothing.

Scoring at one threshold turned out to be wrong, and this is the part worth reading. A head with 13 positives against 2400 negatives is calibrated conservatively even with balanced class weights, so novel@0.5 = 0 conflates two opposite problems:

  • learned nothing — the real failure, fixed with structurally diverse positives
  • learned the class but is shy about saying so — fixed with more positives, or nothing at all

The audit now probes at 0.35 as well and returns a three-way verdict. The distinction changed the answer materially:

verdict count
generalizes at 0.5 153 median 6 novel discoveries
under-confident 12 found real unlabelled molecules just below the bar
memorizing 2 turmeric, celery — nothing at any threshold

Read at 0.5 alone, all 14 looked like the same problem. pine finds 8 unlabelled molecules at 0.35, rosemary 9, freesia 9 — those heads had learned their class all along.

The data

All twelve heads named in #256 had no entry in CURATED at all — their positives came solely from the weak-labelled HSDB odor corpus, which is exactly why they were thin enough to memorize. Added well-established character-impact chemistry for each, chosen for structural spread rather than volume (the #247 method): pinenes/camphene/carene/bornyl esters for pine, cineole/terpinen-4-ol/aromadendrene for eucalyptus, phthalides for celery, indole/cresol/benzyl esters/cinnamyl alcohol for narcissus.

Result, stated honestly:

  • five moved off zero at 0.5narcissus 0→4, frankincense 0→2, costus 0→2, fennel 0→1, allspice 0→1
  • five were under-confident, and sharpenedpine, eucalyptus, rosemary, freesia, elemi
  • two did not move at any thresholdturmeric, celery. Both are genuinely narrow single-scaffold classes (bisabolanes, phthalides). More molecules will not fix that; it needs a better model, not a longer list.

AUROC fell for several heads (pine 0.965→0.905, frankincense 0.916→0.829). That is the right direction — a broader class is harder to fit, and a score dropping while discovery rises is what learning instead of memorizing looks like.

Corpus now trains 167 aroma heads, up from 164; no head was dropped.

Also

docs/METHODS.md still described the aroma model as deferred to licensed odor data, several hundred heads after it shipped. Corrected — what remains gated is depth, not vocabulary — and documented the two-threshold method alongside it.

Provenance

All added associations are public-domain flavour/essential-oil chemistry resolved to canonical SMILES via PubChem, consistent with the existing supplement. No restricted compilation was consulted; the excluded-sources list in docs/DATA-SOURCES.md is unchanged.

Verification

  • ruff check training/ tests/ clean
  • full suite: 43 passed
  • downstream artifacts rebuilt against the new heads: master_enrichment (8,855 rows), profile_index (8,836 × 178 dims — 167 aroma + 6 taste + 5 mouthfeel), flavor_map (8,836 points)
  • needs sudo systemctl restart flavormancer to deploy — the running app still holds the old 164 heads in memory

Filed #257 separately: reading pine's raw positives surfaced chlorinated pesticides, and 105 industrial organohalogens are training flavor heads corpus-wide. Not touched here.

A cross-validated AUROC only ever asks about molecules inside the labelled set, so it cannot
see the failure that matters most for the thin heads: a head fit on twelve molecules sharing
one scaffold scores 0.99 by recognising that scaffold, fires on exactly its own training
molecules, and discovers nothing. audit_generalization.py fires every head over the whole
corpus and counts the hits that were NOT trained on.

Scoring at a single threshold turned out to conflate two opposite problems. A head with 13
positives against 2400 negatives is calibrated conservatively even with balanced class
weights, so "0 novel at 0.5" covers both "learned nothing" and "learned the class but is shy
about saying so" — and those want opposite fixes. The audit therefore also probes at 0.35 and
returns a three-way verdict: generalizes / under-confident / memorizing.

That distinction is not hypothetical. Of the aroma roster, 153 generalize, 12 are merely
under-confident (pine finds 8 unlabelled molecules at 0.35, rosemary 9, freesia 9), and only
turmeric and celery memorize at any threshold — both genuinely narrow single-scaffold classes
where more molecules will not help. Read at 0.5 alone, all 14 looked like the same problem.

Also corrects METHODS.md, which still described the aroma model as deferred to licensed data
several hundred heads after it shipped. The gated piece is depth, not vocabulary.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
…es (#256)

Each of these heads was drawing its positives solely from the weak-labelled HSDB odor corpus,
with no entry in CURATED at all — which is why they were thin and narrow enough to memorize.
Adds well-established character-impact chemistry for each, chosen for structural spread rather
than volume, the same way #247 moved tingling off zero: pinenes/camphene/carene/bornyl esters
for pine, cineole/terpinen-4-ol/aromadendrene for eucalyptus, phthalides for celery,
indole/cresol/benzyl esters/cinnamyl alcohol for narcissus.

Honest result: five moved off zero at the 0.5 threshold — narcissus 0 to 4 novel discoveries,
frankincense and costus to 2, fennel and allspice to 1. Another five (pine, eucalyptus,
rosemary, freesia, elemi) turned out to be under-confident rather than memorizing once probed
at 0.35, so the extra positives sharpened heads that had already learned their class. Turmeric
and celery did not move at any threshold and are tracked separately; a single-scaffold class
that thin is a model problem, not a data problem.

AUROC fell for several heads (pine 0.965 to 0.905, frankincense 0.916 to 0.829). That is the
right direction — a broader class is genuinely harder to fit, and a score dropping while
discovery rises is what learning instead of memorizing looks like.

Corpus now trains 167 aroma heads, up from 164.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
@rvnminers-A-and-N rvnminers-A-and-N added area:aroma OpenPOM aroma model + sidecar area:data Datasets, sources, column mapping area:training Python dataset build + model training labels Jul 29, 2026
@rvnminers-A-and-N
rvnminers-A-and-N merged commit 928a9f0 into main Jul 29, 2026
4 checks passed
@rvnminers-A-and-N
rvnminers-A-and-N deleted the feat/aroma-broaden-memorizing-heads branch July 29, 2026 12:47
rvnminers-A-and-N added a commit that referenced this pull request Jul 29, 2026
)

The notes picker groups heads into perceptual families and sweeps anything unlisted into an
Other bucket so no head is silently dropped. The aroma `sweet` head was never listed, so it
landed in Other alongside `odorless` — reading as an uncategorised leftover rather than the
deliberate note it is, and sitting oddly far from vanilla/caramel/honey where a flavorist
would look for it.

`odorless` stays in Other on purpose: it is the absence of a percept, so it has no perceptual
family to belong to, and Other is the honest home for it.

Found while reconciling the chip pickers against the head roster after the retrain in #258
(164 -> 167 heads). The three heads that newly cleared the AUROC bar — blackberry, fig,
magnolia — were already covered by the Fruity and Floral families, so nothing else moved.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
rvnminers-A-and-N added a commit that referenced this pull request Jul 29, 2026
The retrain in #258 took the aroma roster from 164 to 167, which moved three numbers that were
quoted in nineteen places across the README, five docs, the workbench UI, the MCP server, the
e2e conftest and the pipeline comments in predict.py and app.py. Several were already stale
before this — the profile vector had been described as 170-dimensional since before mouthfeel
shipped, and one workbench card claimed 167 heads in its title while still saying 164 survive
in its body.

The three canonical numbers, so future drift is easy to spot:

  190  trained heads in all — 6 taste + 167 aroma + 5 mouthfeel + 12 safety
  178  dimensions in the flavor-profile vector — taste + aroma + mouthfeel, tox deliberately
       excluded because safety is not a flavor-match dimension
  167  aroma descriptor heads clearing CV-AUROC >= 0.70

(The loader reports 191 artifacts: the 190 heads plus the sweetness-intensity regressor, which
is a regressor rather than a head and so is not counted as one.)

Also corrects the aroma training corpus size in AROMA.md, which still read 1994 molecules
against an actual 2403 after the supplement work.

Signed-off-by: Austin L. <86896075+rvnminers-A-and-N@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:aroma OpenPOM aroma model + sidecar area:data Datasets, sources, column mapping area:training Python dataset build + model training

Projects

None yet

Development

Successfully merging this pull request may close these issues.

model(aroma): 16 heads are memorizing, not generalizing — broaden their positives

1 participant