Food Nutrition Model

Predicts eight nutrition values (energy, carbs, protein, fat, free sugar, fibre, cholesterol, calcium) from a food name. It is a small scikit-learn model trained on my Foods Nutrition Dataset, meant as a simple, honest baseline for that dataset rather than something to base diet decisions on.

Author Adarsh Zolekar
Task Multi-output regression: food name -> 8 nutrition values
Model TF-IDF (word + character n-grams) + Ridge regression
Input A food name, e.g. "Mango lassi"
Output energy_kcal, carbs_g, protein_g, fat_g, free_sugar_g, fibre_g, cholesterol_mg, calcium_mg
Training data data/Foods-Nutrition-Dataset.csv (1,028 foods)
License CC BY 4.0

Usage

pip install -r requirements.txt
from food_nutrition_model import FoodNutritionModel

model = FoodNutritionModel.from_pretrained("model")
print(model.predict("Mango lassi"))              # one name -> dict
print(model.predict(["Plain dosa", "Fruit cake"]))   # list -> list of dicts

From the command line: python inference.py "Mango lassi". There is a longer example in examples/example_inference.py.

To use it straight from the Hub:

import sys
from huggingface_hub import snapshot_download

path = snapshot_download("adarshzolekar/food-nutrition-model")
sys.path.insert(0, path)
from food_nutrition_model import FoodNutritionModel

model = FoodNutritionModel.from_pretrained(f"{path}/model")

model/model.joblib is a pickle file, so only load it from a source you trust. It was saved with scikit-learn 1.8.0; other versions may warn or fail to load it.

Dataset

Training used the uploaded Foods-Nutrition-Dataset.csv, which is the same data as adarshzolekar/foods-nutrition-dataset (CC BY 4.0). A copy is in data/ so the project runs offline.

Checked against the Hub copy (foods.csv): same size (70.3 KB), same header, and the start, one range in the middle and the end of the file are identical. This was a spot check, not a full byte-for-byte diff.

What the checks found (full numbers in results/data_report.json):

  • 1028 rows, 1028 unique food names, no missing values, no negative values, no fully duplicated rows.
  • 46 names have trailing spaces (stripped before training). 8 groups of near-duplicate names (17 rows), e.g. "Chicken sandwich" and "Chicken sandwich (toasted)".
  • Many zeros in some columns: cholesterol 370 rows, fibre 51 rows. Cholesterol zeros are expected for plant foods.
  • Heavy right tails: for example 129 fat values and 69 energy values fall outside 1.5 x IQR. They look like real foods (fried snacks such as mathri and bhatura, mayonnaise, spice blends with 500+ mg calcium), so they were kept.
  • Energy vs macros: 4 x carbs + 4 x protein + 9 x fat correlates 0.994 with the energy column, but 35 rows are more than 25% off, 28 of them soups or consommes. 22 rows have more fibre than carbs and 1 has more free sugar than carbs.
  • The first rows of the file have round numbers (300, 50, 7...) while the rest have two decimals, so the values probably come from more than one source.
  • The Hub dataset card describes the columns as Fiber / Cholesterol / Free sugar, the CSV headers are Fibre(g), Cholestrol(mg), Freesugar(g). The model uses cleaned names; the units for Energy kcal and Carbs are assumed to be kcal and g.

The CSV does not say whether values are per 100 g or per serving, and neither the CSV nor the dataset card names where the values were originally taken from. This repository does not claim to know either.

Why this task

The dataset has no target column, only a name and eight numbers. Predicting energy from carbs/protein/fat would be almost a formula (the 4/4/9 estimate above already correlates 0.99), so it would mostly re-learn Atwater factors. Predicting nutrition from the name is a more useful question: a rough estimate when you only know what the food is called. It is also hard with only 1,028 rows, which is why the results below are modest.

Training

  • Features: the name is lowercased and stripped of punctuation, then turned into TF-IDF word 1-2-grams plus character 2-5-grams.
  • Model: one Ridge regression (alpha = 1.0) predicting all eight targets (they are fitted independently, a multi-output Ridge).
  • Split: 15% of foods held out as a test set (154 foods), the other 874 used for training and validation. The split is grouped: names that only differ by a bracketed note (e.g. "(toasted)") stay on the same side so near-duplicates can't leak into the test set. The test foods are listed in results/split.json.
  • Validation: 5-fold grouped cross-validation on the 874 non-test foods. The model with the best mean out-of-fold R2 was refitted on all 874 foods.
  • Seed: 42.
  • Hardware: CPU only, 1 core(s), Linux x86_64; the whole script, including the model comparison, took about 39 seconds (most of it the random forest).

Model comparison (grouped 5-fold CV on the 874 non-test foods):

Model Mean R2 over 8 targets Energy MAE (kcal)
Mean baseline (predict the training average) -0.003 146.4
TF-IDF + Ridge, alpha 0.5 0.433 97.2
TF-IDF + Ridge, alpha 1.0 (selected) 0.441 97.6
TF-IDF + Ridge, alpha 2.0 0.435 99.8
TF-IDF + k-nearest neighbours (k=10, cosine) 0.398 104.0
Random forest (200 trees, word TF-IDF only) 0.357 102.3

Ridge was picked because it scored best, and it is also fast and easy to inspect. In quick exploratory runs with the same split and CV, a small neural network (MLP) and extra trees scored lower (mean R2 around 0.40 and 0.31); those two are not in train.py, so treat those numbers as informal. A Transformer was not tried: with about a thousand short names there is little for it to learn beyond what TF-IDF already captures, and that is an untested assumption.

Evaluation

Scores on the 154 held-out test foods, which were not used for training or model selection:

Target MAE RMSE R2 Baseline R2
energy_kcal 102.34 145.79 0.320 -0.000
carbs_g 8.51 12.30 0.520 -0.038
protein_g 1.81 2.35 0.433 -0.009
fat_g 10.59 16.09 0.287 -0.010
free_sugar_g 5.25 7.58 0.640 -0.014
fibre_g 1.09 1.74 0.135 -0.008
cholesterol_mg 16.15 24.54 0.621 -0.000
calcium_mg 28.98 40.07 0.413 -0.008
mean over targets 0.421 -0.011

The baseline predicts the training average for every food. Energy MAE is 102.3 kcal for the model against 141.0 for the baseline.

With only 154 test foods these numbers are noisy. Bootstrapping the test set gives a 95% interval of 0.34 to 0.49 for the mean R2 and 0.17 to 0.46 for energy R2. Fibre is barely predictable from the name. Plots are in results/plots/; they show that the model pulls predictions toward the average, so very fatty or very high-energy foods are underestimated.

Reproduce everything with:

python train.py       # compares models, saves model/, config.json and results/
python evaluate.py    # scores the saved model on the test foods, writes results/metrics.json

What the model uses

results/top_terms.json lists the words with the largest Ridge weights for each target. For energy, words like "poori", "vada" and "bhatura" push the estimate up and "soup" and "kheer" push it down, which matches ordinary expectations. These weights describe what the model associates with the data; they do not show that a word causes anything, and the character n-grams add to the picture, so the word list is only part of it.

Limitations

  • Small data. 1,028 foods, and the test set is only 154. Expect rough estimates (R2 around 0.4 on average), not precise values.
  • Name only. Two dishes with the same name can differ a lot in recipe, and the model knows nothing about portion size or ingredients. Predictions for names unlike anything in the data are unreliable. In this dataset "masala" also appears in spice blends with very high fibre and calcium (garam masala, chat masala), so a dish called "... masala" can get inflated fibre and calcium estimates.
  • Targets are predicted separately. The eight outputs can disagree with each other (for example energy that doesn't match fat and carbs), partly because the training data has rows like that.
  • Data quality. Values appear to come from mixed sources, the basis (per 100 g or per serving) is not stated, and the original source is not documented. Foods are mostly Indian and some Western dishes; other cuisines are barely covered.
  • Not medical or dietary advice. The model has not been validated against lab data or by a nutrition professional.

Attribution

Code, model and docs: CC BY 4.0, Adarsh Zolekar. Training data: Foods Nutrition Dataset, CC BY 4.0. If you use this, please also credit the dataset:

@dataset{foods_nutrition_dataset,
  author = {Adarsh Zolekar},
  title = {Foods Nutrition Dataset},
  year = {2026},
  url = {https://huggingface.co/datasets/adarshzolekar/foods-nutrition-dataset}
}
Downloads last month
11
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train adarshzolekar/food-nutrition-model