Ingredient Parser
repository·master·Indexed 41 days ago
https://github.com/strangetom/ingredient-parserA Python package that uses a sequence labelling NLP model to extract structured information—such as amounts, units, names, and preparation steps—from unstructured recipe ingredient sentences. It includes the `parse_ingredient` function for converting sentences into `ParsedIngredient` objects and provides a regex-based alternative via `parse_ingredient_regex`.
What's inside Ingredient Parser
- The All Recipes dataset is a collection of 87,730 recipes scraped from allrecipes.com between June 2017 and July 2017. It is used as a data source for training or evaluating ingredient parsing models. The original source data is hosted on Internet Archive.
Overview of the Ingredient Parser package
masterTheingredient_parser_nlppackage is a Python-based library designed to parse structured information from natural language recipe ingredient sentences. It is intended for developers who need to extract specific data points (such as quantities, units, and ingredient names) from unstructured text.Capabilities of the Webtools interface
masterThe webtools interface provides several key workflows for interacting with the ingredient parser library:
- Parser Testing: Directly use the trained parser (equivalent to calling
parse_ingredient). - Data Management: Browse the ingredient sentence training database and edit labels for training entries.
- Model Training & Tuning:
train_single: Trains a model with specific hyper-parameters and outputs accuracy statistics.train_multiple: Trains multiple models using different random seed values and outputs aggregated accuracy statistics.grid_search: Trains multiple models across different hyper-parameter sets and outputs accuracy statistics for each set.
- Parser Testing: Directly use the trained parser (equivalent to calling
Understand the BBC dataset structure
masterThe BBC dataset used for training is derived from 10,599 recipes scraped frombbc.co.uk/food. The processed dataset is contained inbbc-ingredients-snapshot-2017.csv, which consists of 63,195 unique ingredient sentences extracted from the original source.How features work in the Ingredient Parser
masterFeatures are properties of a token that are deterministically calculated without the parser model. The model learns weights for these features to determine the likelihood of a token having a specific label.
Key characteristics of features:
- Data Types: Features are always primitive types (
str,bool). Numeric types (int,float) are converted tostr. - Contextual Window: To provide context, features are calculated not just for the target token, but also for up to three surrounding tokens.
- Contextual Naming: Features for surrounding tokens are distinguished from the current token's features by a prefix indicating their distance from the target token.
- Token Normalization: To reduce complexity, all numeric tokens are replaced with the placeholder
!num.
- Data Types: Features are always primitive types (
How approximate amounts and the APPROXIMATE flag work
masterIf an ingredient sentence uses words like
approximately,about, orroughly, the parser sets theAPPROXIMATEflag toTruewithin the correspondingIngredientAmountobject.>>> parse_ingredient("4 pork chops, about 400 g each") ParsedIngredient(name=[IngredientText(text='pork chops', confidence=0.9961, starting_index=1)], size=None, amount=[IngredientAmount(quantity=Fraction(4, 1), quantity_max=Fraction(4, 1), unit='', text='4', confidence=0.999876, starting_index=0, unit_system=<UnitSystem.NONE: 'none'>, APPROXIMATE=False, SINGULAR=False, RANGE=False, MULTIPLIER=False, PREPARED_INGREDIENT=False), IngredientAmount(quantity=Fraction(400, 1), quantity_max=Fraction(400, 1), unit=<Unit('gram')>, text='400 g', confidence=0.995449, starting_index=5, unit_system=<UnitSystem.METRIC: 'metric'>, APPROXIMATE=True, SINGULAR=True, RANGE=False, MULTIPLIER=False, PREPARED_INGREDIENT=False)], preparation=None, comment=None, purpose=None, foundation_foods=[], sentence='4 pork chops, about 400 g each')Navigate the Web Tool code hierarchy
masterThe project follows a WYSIWYG directory structure organized by feature and domain:
components/: Contains UI components grouped by feature:labeller: "browse & label" UI.parser: "try the parser" UI.train: "train the model" UI.shared: Reusable components.
domain/: Core logic and data:api: Endpoint resolvers.collections: Static data lists or JSON files.store: Zustand store for application state.types: TypeScript interfaces.
styles/: Custom CSS and Mantine-specific styling.assets/: Images and static assets.- Root Files:
app.py: The basic API server entry point.app.sockets.py: The web socket server entry point.
Understand the Ingredient Parser model type and workflow
masterThe ingredient parser uses a Conditional Random Fields (CRF) model implemented via
pycrfsuiteto label tokens in English recipe ingredient sentences.To use the model correctly, you must follow a three-step pipeline:
- Pre-processing: Perform text normalization on the raw sentence (see
preprocess.pyin theingredient_parser/en/directory). - Model Inference: Pass the normalized sentence to the CRF model (
model.en.crfsuite) to obtain token labels. - Post-processing: Interpret the model's token and label output to generate a structured representation of the ingredient.
- Pre-processing: Perform text normalization on the raw sentence (see
How the Ingredient Parser model works
masterThe library uses a sequence tagging natural language model to parse ingredients. The process follows a specific pipeline that transforms raw text into a structured
ParsedIngredientobject.Parsing Pipeline
When parsing a sentence, the system follows these four steps:
- Sentence Normalisation: Transforms the input sentence into a standardized form to assist the model and post-processing.
- Feature Generation: Splits the sentence into tokens and generates a set of features for each token (based on the token itself or its context).
- Model Labeling: The model assigns a label to each token in the sequence.
- Post-processing: Interprets the sequence of tokens and labels to determine the different parts of the ingredient (e.g., quantity, unit, name) by grouping tokens with the same label or identifying specific patterns.
Training Pipeline
When training the model, the process involves:
- Sentence Normalisation
- Feature Generation
- Training: Using a large dataset of example sentences with known correct label sequences to train the model.
- Evaluation: Testing the trained model against example sentences with known labels that were not used during training.
The mechanism for improving NLTK part of speech tags
masterTo improve the accuracy of NLTK's part of speech tagging for ingredient sentences, the library employs a two-step augmentation strategy:
- Automated Augmentation: The library uses Spacy's
English transformer modelto identify high-confidence tags. Any token that appears at least 20 times in the training data with the same tag at least 90% of the time is added to a newingredient_tagdict. - Manual Augmentation: The library's distributed
tagdictis further refined by manually inserting entries for unambiguous words that the automated process or standard NLTK fails to tag correctly in real-world ingredient sentences.
When the
pos_tagfunction is called, it loads the NLTK tagger and updates its internaltagdictwith this specializedingredient_tagdictbefore processing tokens.def pos_tag(tokens: list[str]) -> list[tuple[str, str]]: tagger = _get_tagger("eng") ingredient_tagdict = load_ingredient_tagdict() tagger.tagdict.update(ingredient_tagdict) return _pos_tag(tokens=tokens, tagset=None, tagger=tagger, lang="eng")- Automated Augmentation: The library uses Spacy's
Handling ingredients with different amounts (Limitation)
masterThe library assumes an ingredient sentence does not contain different ingredients with different amounts. If a sentence contains multiple phrases specifying different ingredients in different amounts, the parser will return the first identified ingredient and place everything following the first phrase into the
commentfield.>>> parse_ingredient("1 cup peeled and cooked fresh chestnuts (about 20), or 1 cup canned, unsweetened chestnuts") ParsedIngredient(name=[IngredientText(text='fresh chestnuts', confidence=0.977679, starting_index=5)], size=None, amount=[IngredientAmount(quantity=Fraction(1, 1), quantity_max=Fraction(1, 1), unit=<Unit('cup')>, text='1 cup', confidence=0.999549, starting_index=0, unit_system=<UnitSystem.US_CUSTOMARY: 'us_customary'>, APPROXIMATE=False, SINGULAR=False, RANGE=False, MULTIPLIER=False, PREPARED_INGREDIENT=True), IngredientAmount(quantity=Fraction(20, 1), quantity_max=Fraction(20, 1), unit='', text='20', confidence=0.887524, starting_index=9, unit_system=<UnitSystem.NONE: 'none'>, APPROXIMATE=True, SINGULAR=False, RANGE=False, MULTIPLIER=False, PREPARED_INGREDIENT=False)], preparation=IngredientText(text='peeled and cooked', confidence=0.999523, starting_index=2), comment=IngredientText(text='or 1 cup canned, unsweetened chestnuts', confidence=0.925894, starting_index=12), purpose=None, foundation_foods=[], sentence='1 cup peeled and cooked fresh chestnuts (about 20), or 1 cup canned, unsweetened chestnuts')Understand implicit quantities
masterThe parser identifies implicit quantities where the amount is not explicitly stated but implied by the unit or context. For example:
- '15 oz can black beans': Implies 1 can.
- 'Rosemary sprig': Implies a quantity of 1 because 'sprig' is singular.
In these cases, the
quantityin theIngredientAmountobject is explicitly set to1. To prevent errors, the parser checks that the unit is not plural and that no indefinite quantifiers (like 'few' or 'some') precede it.