Dralkh commited on
Commit
b5eef15
·
verified ·
1 Parent(s): 19bfe36

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +65 -60
README.md CHANGED
@@ -1,53 +1,44 @@
1
- ---
2
- license: apache-2.0
3
- library_name: pytorch
4
- base_model: google/siglip2-base-patch16-224
5
- tags:
6
- - nutrition-estimation
7
- - calorie-estimation
8
- - food
9
- - nutrition5k
10
- - image-regression
11
- datasets:
12
- - Nutrition5k
13
- language:
14
- - en
15
- metrics:
16
- - mae
17
- ---
18
-
19
  # CalorieK — food-photo calorie & macro estimation
20
 
21
  A single RGB food photo in, five numbers out: **calories (kcal), mass (g), protein (g), carbohydrate (g), fat (g)**.
22
  CalorieK is a SigLIP2-base vision tower with a small regression head, fine-tuned end-to-end and evaluated
23
  under a strict, leakage-audited Nutrition5k protocol.
24
 
25
- ![Comparison on the locked Nutrition5k test set](comparison.png)
26
 
27
  ## Headline result — locked Nutrition5k test set (608 dishes, official test split, never seen)
28
 
29
  | Metric | CalorieK | CalorieCLIP (same 608 dishes) |
30
- |---|---|---|
31
- | Calorie MAE | **51.4 kcal** (95% CI 35.8–78.9) | 181.2 kcal |
32
- | Median absolute error | **25.9 kcal** | 137.0 kcal |
33
- | Within ±50 kcal | **76.8%** | 21.4% |
34
- | Within ±100 kcal | **92.4%** | 40.6% |
35
- | Bias | **−0.7 kcal** | +134.8 kcal |
36
  | Calibrated 90% interval | **±105.9 kcal** (actual coverage 93.6%) | — |
37
  | Invalid predictions | 0 / 608 | 0 / 608 |
38
 
39
  Every test image is a deterministic central frame from the official Nutrition5k camera-A side-angle
40
- video of an official *test-split* dish — selected by dish ID, never by image quality. Evaluation is
41
  dish-level (`physical_meal_id`).
42
 
 
 
 
 
 
 
 
 
 
43
  ## Comparison with open VLMs (paired 304-dish subset, seed 17)
44
 
45
  Each model answered the same calorie question on the same dishes
46
- (prompt: *"Estimate the total calories in this food image. Return exactly one integer number in kcal…"*).
47
 
48
  | Model | n | MAE (kcal) | Median AE | Within ±100 kcal | Bias |
49
- |---|---|---|---|---|---|
50
- | **CalorieK (ours)** | 304 | **64.4** | **23.5** | **92.4%** | −13.6 |
51
  | Qwen3.5-2B | 304 | 116.4 | 65.0 | 69.4% | −48.1 |
52
  | Qwen3.5-9B (Q8_0, thinking off) ‡ | 304 | 117.2 | 64.5 | 65.8% | +6.1 |
53
  | MiniCPM-V 4.6 (1.3B) | 304 | 163.3 | 97.0 | 52.6% | +75.5 |
@@ -58,8 +49,6 @@ Each model answered the same calorie question on the same dishes
58
  † Gemma-4-12B was scored on a seeded 100-dish paired subset of the same 304 dishes (compute budget);
59
  all parse rates were 100%.
60
  ‡ Qwen3.5-9B ran as an 8-bit GGUF (Q8_0) via llama.cpp with 4k context and thinking disabled.
61
- Notably it matches the 2B (117.2 vs 116.4 MAE) — 4.5× more parameters bought no accuracy on this task,
62
- suggesting the zero-shot VLM error floor is portion-mass ambiguity, not model capacity.
63
  MiniMax M3 (a hosted reasoning model, chain-of-thought enabled) reinforces the point from the other
64
  direction: extensive per-dish reasoning did not help — it lands second-to-last with a strong +104 kcal
65
  systematic overestimate. Reasoning does not fix calibration.
@@ -67,53 +56,58 @@ systematic overestimate. Reasoning does not fix calibration.
67
  ## Out-of-distribution evaluation (MM-Food-100K, 500 wild food photos)
68
 
69
  To measure generalization beyond Nutrition5k plating, both regression models were scored on a seeded
70
- 500-image sample of [MM-Food-100K](https://huggingface.co/datasets/Humanbased-AI/MM-Food-100K)
71
- (restaurant/home photos; `food_prob ≥ 0.9`). **Caveat: these labels are crowd/AI estimates, not weighed
72
- measurements** — absolute errors include label noise no model can beat, so the relative comparison is
73
  the meaningful signal.
74
 
75
- | | CalorieK | CalorieCLIP | |
76
- |---|---|---|---|
77
- | MAE | **164.9 kcal** | 206.4 kcal | 1.25× better |
78
- | Median error | **139.1 kcal** | 179.9 kcal | 1.3× better |
79
- | Within ±100 kcal | **36.0%** | 25.6% | |
80
- | Bias | **−4.8 kcal** | +98.3 kcal | |
81
- | R² | **+0.24** | −0.08 | |
82
 
83
  Findings, stated honestly:
 
84
  - CalorieK beats CalorieCLIP out-of-distribution on every metric, keeps **near-zero bias** on food it
85
- has never seen, and retains real signal (R² +0.24) where CalorieCLIP is worse than predicting the
86
- mean (R² −0.08).
87
- - But CalorieK degrades ~3× versus in-distribution (51.4 → 164.9 kcal MAE): it is substantially a
88
- specialist in Nutrition5k-style plated dishes. For wild-photo deployment, mixing diverse weakly
89
- labeled data into training is the indicated next step.
90
 
91
- ## Training method
92
 
93
  - **Architecture**: `google/siglip2-base-patch16-224` vision tower (92M params) → LayerNorm →
94
- MLP head (768→768→256→5) with softplus outputs. Trained with Huber loss, per-task, quality-weighted.
95
- - **Schedule**: 15 epochs × 20,000 samples, batch 256 (bf16), head LR 1e-4 / trunk LR 2e-5,
96
- cosine schedule with 5% warmup, first 5% head-only, last 4 vision blocks unfrozen, weight decay 0.05, seed 17.
97
- - **Checkpoint selection**: best validation MAE on 80 held-out camera-A dishes (validation MAE 39.2 kcal).
98
- Training ran ~34 minutes on one RTX PRO 6000 Blackwell (CUDA 13).
 
 
 
99
  - **Calibration**: a conformal radius fitted on 86 held-out calibration dishes gives 90% prediction
100
- intervals of ±105.9 kcal, with 93.6% actual coverage on the locked test set (`calibration.json`).
101
 
102
- ![Training loss](loss_curve.png)
103
 
104
  ## Training data (20,000 samples, leakage-audited)
105
 
106
  | Source | Samples | Notes |
107
- |---|---|---|
108
  | [`pinkieseb/nutrition_dataset`](https://huggingface.co/datasets/pinkieseb/nutrition_dataset) (Nutrition5k video frames) | 19,734 | only dishes provably in the **official Nutrition5k train split** (matched by exact nutrition signature), ≤16 frames per dish |
109
  | [`aryachakraborty/Food_Calorie_Dataset`](https://huggingface.co/datasets/aryachakraborty/Food_Calorie_Dataset) | 266 | human-estimated labels, down-weighted (0.4) |
110
 
111
  Leakage controls, all enforced programmatically before training:
 
112
  - **Group separation**: splits partition by physical dish (`physical_meal_id`); no dish appears in two splits.
113
  - **Exact-media**: SHA-256 disjointness across fit / validation / calibration / test.
114
  - **Near-duplicate**: perceptual-hash disjointness; 2 fit images visually colliding with held-out frames were dropped and backfilled.
115
  - **Official-split proof**: a Nutrition5k registry maps nutrition signatures to official train/test IDs;
116
- ambiguous signatures are excluded. The audit trail (SHA-256 of every manifest and the source tree) ships in the results.
117
 
118
  ## Usage
119
 
@@ -151,9 +145,20 @@ print(f"{kcal:.0f} kcal ± 106 (90% interval), {mass_g:.0f} g, P{protein_g:.0f}/
151
  ## Limitations
152
 
153
  - **Domain**: trained and evaluated on Nutrition5k-style plated single dishes photographed from the side;
154
- accuracy on multi-plate scenes, packaged food, or drinks is untested.
155
- - **Heavy tail**: RMSE (327) is far above MAE (51) — very high-calorie dishes are occasionally missed badly.
156
- Use the calibrated interval, not the point estimate, for anything that matters.
157
  - **Frames, not meals**: training frames come from videos of ~1,250 distinct dishes; visual diversity is
158
- narrower than the sample count suggests.
159
  - The VLM baselines answered a zero-shot prompt; instruction-tuned prompting or few-shot could improve them.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # CalorieK — food-photo calorie & macro estimation
2
 
3
  A single RGB food photo in, five numbers out: **calories (kcal), mass (g), protein (g), carbohydrate (g), fat (g)**.
4
  CalorieK is a SigLIP2-base vision tower with a small regression head, fine-tuned end-to-end and evaluated
5
  under a strict, leakage-audited Nutrition5k protocol.
6
 
7
+ [![Comparison on the locked Nutrition5k test set](https://huggingface.co/Dralkh/CalorieK/resolve/main/comparison.png)](https://huggingface.co/Dralkh/CalorieK/blob/main/comparison.png)
8
 
9
  ## Headline result — locked Nutrition5k test set (608 dishes, official test split, never seen)
10
 
11
  | Metric | CalorieK | CalorieCLIP (same 608 dishes) |
12
+ | --- | --- | --- |
13
+ | Calorie MAE | **49.8 kcal** (95% CI 34.4–78.0) | 181.2 kcal |
14
+ | Median absolute error | **24.5 kcal** | 137.0 kcal |
15
+ | Within ±50 kcal | **78.5%** | 21.4% |
16
+ | Within ±100 kcal | **94.2%** | 40.6% |
17
+ | Bias | **−6.6 kcal** | +134.8 kcal |
18
  | Calibrated 90% interval | **±105.9 kcal** (actual coverage 93.6%) | — |
19
  | Invalid predictions | 0 / 608 | 0 / 608 |
20
 
21
  Every test image is a deterministic central frame from the official Nutrition5k camera-A side-angle
22
+ video of an official _test-split_ dish — selected by dish ID, never by image quality. Evaluation is
23
  dish-level (`physical_meal_id`).
24
 
25
+ ### Macronutrient accuracy (same 608 dishes)
26
+
27
+ | Task | MAE | Median AE | Bias |
28
+ | --- | --- | --- | --- |
29
+ | Mass | **90.4 g** | 34.5 g | −47.3 g |
30
+ | Protein | **7.8 g** | 3.9 g | −1.9 g |
31
+ | Carbohydrate | **5.8 g** | 2.8 g | −1.2 g |
32
+ | Fat | **7.2 g** | 2.2 g | −3.1 g |
33
+
34
  ## Comparison with open VLMs (paired 304-dish subset, seed 17)
35
 
36
  Each model answered the same calorie question on the same dishes
37
+ (prompt: _"Estimate the total calories in this food image. Return exactly one integer number in kcal…"_).
38
 
39
  | Model | n | MAE (kcal) | Median AE | Within ±100 kcal | Bias |
40
+ | --- | --- | --- | --- | --- | --- |
41
+ | **CalorieK (ours)** | 304 | **62.7** | **24.0** | **94.1%** | −11.0 |
42
  | Qwen3.5-2B | 304 | 116.4 | 65.0 | 69.4% | −48.1 |
43
  | Qwen3.5-9B (Q8_0, thinking off) ‡ | 304 | 117.2 | 64.5 | 65.8% | +6.1 |
44
  | MiniCPM-V 4.6 (1.3B) | 304 | 163.3 | 97.0 | 52.6% | +75.5 |
 
49
  † Gemma-4-12B was scored on a seeded 100-dish paired subset of the same 304 dishes (compute budget);
50
  all parse rates were 100%.
51
  ‡ Qwen3.5-9B ran as an 8-bit GGUF (Q8_0) via llama.cpp with 4k context and thinking disabled.
 
 
52
  MiniMax M3 (a hosted reasoning model, chain-of-thought enabled) reinforces the point from the other
53
  direction: extensive per-dish reasoning did not help — it lands second-to-last with a strong +104 kcal
54
  systematic overestimate. Reasoning does not fix calibration.
 
56
  ## Out-of-distribution evaluation (MM-Food-100K, 500 wild food photos)
57
 
58
  To measure generalization beyond Nutrition5k plating, both regression models were scored on a seeded
59
+ 500-image sample of [MM-Food-100K](https://huggingface.co/datasets/Codatta/MM-Food-100K)
60
+ (restaurant/home photos; `food_prob ≥ 0.9`). **Caveat: these labels are crowd/AI estimates, not weighed**
61
+ **measurements** — absolute errors include label noise no model can beat, so the relative comparison is
62
  the meaningful signal.
63
 
64
+ | | CalorieK | CalorieCLIP |
65
+ | --- | --- | --- |
66
+ | MAE | **162.5 kcal** | 206.4 kcal |
67
+ | Median error | **135.0 kcal** | 179.9 kcal |
68
+ | Within ±100 kcal | **38.8%** | 25.6% |
69
+ | Bias | **−8.1 kcal** | +98.3 kcal |
70
+ | R² | **+0.24** | −0.08 |
71
 
72
  Findings, stated honestly:
73
+
74
  - CalorieK beats CalorieCLIP out-of-distribution on every metric, keeps **near-zero bias** on food it
75
+ has never seen, and retains real signal (R² +0.24) where CalorieCLIP is worse than predicting the
76
+ mean (R² −0.08).
77
+ - But CalorieK degrades ~3× versus in-distribution (49.8 → 162.5 kcal MAE): it is substantially a
78
+ specialist in Nutrition5k-style plated dishes. For wild-photo deployment, mixing diverse weakly
79
+ labeled data into training is the indicated next step.
80
 
81
+ ## Training method (improved)
82
 
83
  - **Architecture**: `google/siglip2-base-patch16-224` vision tower (92M params) → LayerNorm →
84
+ MLP head (768→768→256→5) with softplus outputs. Trained with composite Huber loss, per-task weighted.
85
+ - **Augmentation**: MixUp + CutMix (both α=0.4, 50% probability per batch), plus standard
86
+ RandomResizedCrop, HorizontalFlip, Rotation, ColorJitter.
87
+ - **Schedule**: 20 epochs × 20,000 samples, effective batch 256 (bf16), head LR 1e-4 / trunk LR 2e-5,
88
+ cosine schedule with restarts, 5% warmup, first 5% head-only, last 4 vision blocks unfrozen,
89
+ weight decay 0.05, seed 17.
90
+ - **Checkpoint selection**: best validation MAE on 80 held-out camera-A dishes (validation MAE 39.3 kcal).
91
+ Training ran ~12 minutes on one RTX 4080 (16 GB).
92
  - **Calibration**: a conformal radius fitted on 86 held-out calibration dishes gives 90% prediction
93
+ intervals of ±105.9 kcal, with 93.6% actual coverage on the locked test set (`calibration.json`).
94
 
95
+ [![Training loss](https://huggingface.co/Dralkh/CalorieK/resolve/main/loss_curve.png)](https://huggingface.co/Dralkh/CalorieK/blob/main/loss_curve.png)
96
 
97
  ## Training data (20,000 samples, leakage-audited)
98
 
99
  | Source | Samples | Notes |
100
+ | --- | --- | --- |
101
  | [`pinkieseb/nutrition_dataset`](https://huggingface.co/datasets/pinkieseb/nutrition_dataset) (Nutrition5k video frames) | 19,734 | only dishes provably in the **official Nutrition5k train split** (matched by exact nutrition signature), ≤16 frames per dish |
102
  | [`aryachakraborty/Food_Calorie_Dataset`](https://huggingface.co/datasets/aryachakraborty/Food_Calorie_Dataset) | 266 | human-estimated labels, down-weighted (0.4) |
103
 
104
  Leakage controls, all enforced programmatically before training:
105
+
106
  - **Group separation**: splits partition by physical dish (`physical_meal_id`); no dish appears in two splits.
107
  - **Exact-media**: SHA-256 disjointness across fit / validation / calibration / test.
108
  - **Near-duplicate**: perceptual-hash disjointness; 2 fit images visually colliding with held-out frames were dropped and backfilled.
109
  - **Official-split proof**: a Nutrition5k registry maps nutrition signatures to official train/test IDs;
110
+ ambiguous signatures are excluded. The audit trail (SHA-256 of every manifest and the source tree) ships in the results.
111
 
112
  ## Usage
113
 
 
145
  ## Limitations
146
 
147
  - **Domain**: trained and evaluated on Nutrition5k-style plated single dishes photographed from the side;
148
+ accuracy on multi-plate scenes, packaged food, or drinks is untested.
149
+ - **Heavy tail**: RMSE (326) is far above MAE (50) — very high-calorie dishes are occasionally missed badly.
150
+ Use the calibrated interval, not the point estimate, for anything that matters.
151
  - **Frames, not meals**: training frames come from videos of ~1,250 distinct dishes; visual diversity is
152
+ narrower than the sample count suggests.
153
  - The VLM baselines answered a zero-shot prompt; instruction-tuned prompting or few-shot could improve them.
154
+
155
+ ## Citation
156
+
157
+ ```bibtex
158
+ @misc{caloriek2025,
159
+ title={CalorieK: Accurate Food Calorie Estimation from a Single RGB Image},
160
+ author={Dralkh},
161
+ year={2025},
162
+ url={https://huggingface.co/Dralkh/CalorieK}
163
+ }
164
+ ```