Improve PREPARED_INGREDIENT accuracy for irreversible-prep verbs at volumetric amounts#53
Conversation
The syntactic rule in ParsedIngredient.set_prepared_flag produces False
for sentences like "1 cup carrots, diced" because the preparation
trails the name. But pre-prep volumetric measurement is physically
impossible for irreversible action verbs (chopped, sliced, diced,
minced, grated, shredded, etc.) - you cannot cup-measure unchopped
onion or unshredded cheese, so the cup must be measuring the prepared
form.
Adds an override that fires when:
- any PREP-labelled token in the sentence contains an irreversible
action verb (IRREVERSIBLE_PREP_VERBS, 24 entries)
- AND the amount unit is volumetric (pint dimensionality matches cup)
- AND the unit is not strictly liquid-only (ml, cl, dl, l, fl_oz),
where volume is preserved through prep so the syntactic rule's
False is the natural reading
The override only flips False to True; it never overrides an existing
True (positional or to-yield/to-make). It does not fire for count
units (the count of whole ingredients is invariant under prep), mass
units (mass is preserved), or strict-liquid units. Reversible-order
verbs like sifted and packed are deliberately excluded - the syntactic
rule remains correct for the documented flour/sifted archetype.
Implementation lives in PostProcessor (one site in _fallback_pattern,
one in _composite_amounts_pattern) so it runs before
_distribute_related_flags - paired metric/imperial amounts therefore
propagate correctly. The is_volumetric_unit and is_liquid_only_unit
helpers delegate to pint dimensionality with a small named exclusion
set for liquid-only units.
|
Hi @MaximusCub I need to have a think about this one because I'm not sure I agree. As I understand it, the problem you are proposing to fix is as follows:
The thing that makes me hesitant about this is I can think of counter examples where this isn't true. In the
|
|
Hey @strangetom -- fair point on those examples. Spent some time digging in and I'm less confident this is "the best" approach than I started. It's an improvement, but like the existing code it carries its own trade-offs. A few high-level thoughts before the verb list. The fundamental issue is that whether the amount refers to the prepared form depends on (verb, ingredient, unit_size) jointly, and sometimes there's no canonical answer even with all three -- the "right" answer often requires context the parser doesn't have. Honestly, a confidence/probability score is probably the better long-term shape than a bool, but that's an interface change and would need either CRF modifications or a separate classifier feeding it. The classifier route adds architectural complexity and inference latency. The CRF route avoids the latency but needs relabeling effort. Both are bigger than this PR's scope. A middle-ground I considered is an ingredient-class lookup table -- could capture the missing ingredient dimension without ML. I'd be happy to look into implementation approaches for any of these if you have the appetite for it. If you'd still like to ship something low-tech in the meantime, here's a tighter verb list. One framing note first: "non-reversible" was a poor naming choice on my part -- it's not that the action is non-reversible, it's that the heuristic odds (not certainty) favor prep-before-measurement for certain verbs. Drop:
Keep:
Let me know if you'd want to move ahead with something like this, or if a different approach (CRF, classifier, ingredient table) would be a better use of effort. Happy to adjust the naming/doc conventions either way. Thanks again for the thoughtful review. |
Modification of the CRF sounds like it could be worth investigating but, as you say, it would be a lot of effort. I'm imagining changes the current label set to split the PREP label into two variants: one for where for the prep happens after measuring the amount, one where it happens before. In theory, training the model like should result in it learning that certain verbs and ingredient names and combinations thereof imply the prep happens before/after measurement. One issue with introducing new labels is that it tends to reduce model accuracy, but if that happens it might be offset by including new features based on your verb list. You are welcome to investigate this further, and if you do I would suggest doing so on a subset of the training data to avoid spending ages relabelling the data only to find it doesn't work well enough. I'll add this to my backlog of things to investigate but I doubt I'll get to it any time soon.
I can see the challenge with this being generating a sufficiently large table that covers the majority of cases. |
Summary
Fixes
PREPARED_INGREDIENTreturning the wrong value for sentences where an irreversible action verb (chopped, sliced, diced, etc.) trails a comma at a volumetric amount.Both sentences describe the same thing -- 1 cup of diced carrots -- but the flag flips on word order alone. That behavior is correct for genuinely two-way verbs like
sifted(where1 cup flour, siftedand1 cup sifted flourare physically different quantities), but produces the wrong answer for verbs where pre-prep volumetric measurement is physically impossible.Root cause
PREPARED_INGREDIENTis set by syntactic position inParsedIngredient.set_prepared_flag: prep-between-amount-and-name -> True, prep-after-name -> False, plus theto yield/to makeheuristic. The flour/sifted contrast that motivates this rule is genuinely two-way, but it's a corner case in real recipes. For most prep verbs (chopped, sliced, diced, minced, grated, shredded, etc.) you cannot cup-measure the unprepped form -- there is no way to put unchopped onion or unshredded cheese into a cup measure -- so the post-comma form must measure the prepared form regardless of word order.Approach
A postprocessor fix, no CRF retrain. The CRF correctly tags the verbs as
PREP; only the post-CRF flag interpretation needs to change.IRREVERSIBLE_PREP_VERBS)LIQUID_ONLY_UNIT_NAMES = {centiliter, deciliter, fluid_ounce, liter, milliliter}excluded because pre-prep liquid measurement is physically possible and volume is preserved through prepsifted,packed) and state adjectives (frozen,dried) remain excludedChanges
ingredient_parser/en/_constants.py--IRREVERSIBLE_PREP_VERBS(24) andLIQUID_ONLY_UNIT_NAMES(5)ingredient_parser/en/_utils.py-- public predicatesis_volumetric_unit(unit)andis_liquid_only_unit(unit)delegated to pint dimensionality, plus a shared_coerce_unit_to_pinthelperingredient_parser/en/postprocess.py--_has_irreversible_prep_verb()method, override sites in_fallback_patternand_composite_amounts_pattern. Runs before_distribute_related_flagsso paired metric/imperial amounts (1 cup (140 g) onion, finely chopped) propagate correctly.tests/parser/test_prepared_ingredient.py-- 9 new tests covering canonical positive, paired propagation, count/mass/strict-liquid gates, hyphenated PREP token, composite path, and a negative case (1 cup rice, cookedstays False) pinning the exclusion of adjacent verbsdocs/source/explanation/postprocessing.rst-- replaces the existing1 tbsp chopped nuts/1 tbsp nuts, choppedworked example with prose covering both the syntactic rule and the verb-class overridedocs/source/tutorials/examples.rst-- adds a paragraph after the flour/sifted archetype contrasting volumetric/count/mass behavior under the overrideCHANGELOG.md-- entry under### develop7 files, 339 insertions / 2 deletions.
Validation
Full test suite green (466 passed). The 5 existing prepared-ingredient tests pass unchanged.
Corpus measurement: parsed every sentence in
train/data/training.sqlite3matching the SQL pattern below across all 24 verbs (1086 unique sentences after dedup; 1130 raw matches), once with the override and once with the override reverted to origin/develop:PREPARED_INGREDIENT=Truecount10 random rows sampled from the 850 flipped sentences:
In most sampled cases the natural reading appears to be form-at-measurement (1/3 cup of chopped onions, 1 cup of chopped fresh basil leaves, etc.). The hypothetical "measure first, then prep as a workflow step" reading isn't expressed in this syntactic shape -- when writers want that they use a different form entirely.
A subset is genuinely borderline: herbs in particular (
1 cup fresh basil leaves, chopped,1 tablespoon thyme leaves, chopped) can reasonably mean either "measure whole leaves, then chop" or "chop, then measure" -- whole leaves are themselves cup-measurable, unlike whole onions or unshredded cheese, so the workflow reading isn't physically excluded the way it is for the canonical cases. This is accepted as a minor cost. For a use case that may commonly be served by the flag -- volumetric-to-mass density conversion for nutrition / calorie mapping -- a misclassification on a tablespoon of herbs has small mass and calorie consequences relative to correctly resolving the bulk-ingredient cases (chopped onions, diced carrots, shredded cheese, halved cherry tomatoes, etc.) where the unprepped and prepped forms differ in mass by 25-100%.Why the verb list is shaped this way
Pre-comma vs post-comma frequency at volumetric units across the 24 verbs (same SQL pattern, swapping
'*[0-9]* {unit}*, {verb}*'for'*[0-9]* {unit}* {verb} *'for the pre-comma count):For pre-comma-dominant verbs (chopped, ground, grated, minced), writers prefer the explicit
cup VERB Xsyntax when meaning form-at-measurement; the post-comma form is rarer but means the same thing. For near-equal pre/post ratios (halved, quartered, cored, drained, seeded), writers use both forms interchangeably.Deliberately excluded from the verb list: state adjectives (
frozen,dried,chilled,refrigerated), reversible-order preps (sifted,packed,melted,softened), and time-process verbs (cooked,boiled,roasted,toasted) where the syntactic rule remains correct or where corpus inspection shows the post-comma form is genuinely ambiguous.Notes
A CRF-shaped solution is possible -- splitting
PREPinto sub-labels for irreversible vs reversible-order verbs in the corpus, then keying the flag off the new label -- but it felt like meaningful added complexity (re-labelling and retraining for a property that's a flat per-verb attribute) for little practical gain over the postprocessor approach. The postprocessor route seemed the most elegant fit: the CRF is already correctly identifying these tokens asPREP, the bug is purely in how that signal is interpreted into the flag, and a closed verb-class set captures it directly. Open to redoing as a CRF change if you prefer; the verb list and corpus survey above would feed the labelling pass.