FoodVision Mini

A small, fast image classifier that tells whether a food photo shows pizza, steak or sushi. It uses transfer learning: torchvision's EfficientNet-B2, pretrained on ImageNet, is kept frozen as a feature extractor and only a new 3-class head is trained, on 450 Food-101 photos.

Model

Architecture torchvision efficientnet_b2 (backbone initialised from EfficientNet_B2_Weights.IMAGENET1K_V1), classifier replaced by Dropout(0.3) -> Linear(1408, 3)
Parameters 7,705,221 total, of which 4,227 (the head) were trained
Input an RGB image of any size
Preprocessing model.get_transform(): resize to 288 (bicubic), center-crop 288x288, ImageNet mean/std
Output softmax probabilities for all 3 classes: {"pizza": p, "steak": p, "sushi": p}
Files model.safetensors (weights), config.json (class_names, dropout, pretrained), model.py (architecture + load() + Predictor), handler.py + requirements.txt (Inference Endpoint), metrics.json, assets/ (experiment graphs)

config.json records pretrained: true because the backbone was initialised from ImageNet weights before training. model.load() always rebuilds the network with pretrained=False and then loads model.safetensors, so inference never downloads the torchvision weights.

Usage

Python

from huggingface_hub import hf_hub_download, snapshot_download
import sys
path = snapshot_download("shalev396/foodvision-mini")
sys.path.insert(0, path)
import model
predictor = model.load(path, device="cpu")   # or "cuda"
image = hf_hub_download("shalev396/foodvision-mini", "examples/2582289.jpg", repo_type="space")
print(predictor.predict(image))              # a PIL image works too
# {'pizza': 0.964, 'steak': 0.017, 'sushi': 0.019}

Requirements (requirements.txt): torch, torchvision, pillow, huggingface_hub, safetensors.

Space API (free): the Space exposes /predict (image in, [label, seconds, device] out). curl and @gradio/client examples are in its README.

Inference Endpoint: deploy this repo from its page (Deploy -> Inference Endpoints, CPU or GPU). handler.py loads the model once (on cuda when the endpoint has a GPU) and accepts an image sent as raw bytes (Content-Type: image/jpeg) or as JSON {"inputs": "<base64 image>"}. It returns {"pizza": p, "steak": p, "sushi": p}.

curl $ENDPOINT_URL -H "Authorization: Bearer $HF_TOKEN" -H "Content-Type: image/jpeg" --data-binary @pizza.jpg

Training

  • Data: the pizza_steak_sushi_20_percent subset of Food-101, prepared for the PyTorch Deep Learning course (zip). It holds 450 train images (pizza 154, steak 146, sushi 150) and 150 test images (46 / 58 / 46).
  • Recipe: EfficientNet-B2 feature extractor. All backbone weights are frozen and only the new head trains. Loss is plain cross-entropy, optimizer is Adam(lr=1e-3), batch size 32, 10 epochs, seed 42. Train and test images get the same preprocessing (no data augmentation). BatchNorm layers stay in train mode, so their running statistics adapt to the food photos. The last epoch is kept (no early stopping).
  • This checkpoint: converted from the original training run (PyTorch Deep Learning bootcamp, notebook 09, trained on Apple MPS with torch 2.11 and published on 2026-05-08). The original .pth state_dict was mapped 1:1 into FoodVisionNet and saved as safetensors. On the 3 example images, the converted model's probabilities match the original's exactly (max |diff| 0.0). Training time was not recorded.
  • Correction: an earlier version of the Space README said the model was trained on 225/75 images with label smoothing and TrivialAugmentWide. That was wrong: the notebook that produced these weights used the 20% split (450/150), plain cross-entropy and no augmentation.

Full code: training/ · Colab. The notebook retrains the recipe, evaluates the new run next to this checkpoint, and exports whichever is better (the deployed one wins ties).

Experiments

Every variant evaluated on the same 150 test images (from metrics.json -> comparison). The deployed one is in bold. accuracy_<class> is the share of that class's photos classified correctly.

variant accuracy f1_macro accuracy_pizza accuracy_steak accuracy_sushi
original bootcamp checkpoint (deployed) 0.960 0.961 0.957 0.948 0.978
retrain with training/ on cpu, 2026-09-25 0.940 0.941 0.978 0.983 0.848
  • original bootcamp checkpoint: the weights in this repo (see Training).
  • retrain with training/: the same recipe run by the notebook on a desktop CPU on 2026-09-25 (857 s of training, on a CPU shared with other jobs). An earlier retrain on 2026-09-24 (520 s) gave exactly the same 0.940, so the CPU run is reproducible. It lands 3 images lower (141/150), which is within the noise of a 150-image test set, so the original weights stay deployed. The two make different mistakes: the retrain misses more sushi (7 of 46), the original misses more steak (3 of 58).

Test metrics of every variant

Training curves of the retrain (the original run's curves were not recorded). The loss is still falling at epoch 10. Test accuracy touches 0.960 at epoch 5 and ends at 0.940. With 150 test images, one epoch's swing is 1-3 images, and the recipe does not pick epochs on the test split.

Training curves of the retrain

Evaluation

metric (test) value
accuracy 0.9600
f1_macro 0.9606
accuracy_pizza 0.9565
accuracy_steak 0.9483
accuracy_sushi 0.9783

The deployed checkpoint on the 150 test images, evaluated locally on CPU with this repo's training/src code.

Confusion matrix on the test split

144 of 150 test images are correct. 3 of the 6 errors are steak photos predicted as sushi. The bootcamp notebook printed 96.25% for this model. That figure is a mean of per-batch accuracies (the last batch holds only 22 images). The per-image accuracy on the same 150 images is 96.0%, and that is what is reported here.

Limitations

  • Three classes only. Every image gets a pizza/steak/sushi label, even a photo of a dog. There is no "other" class or rejection threshold.
  • Small data. 450 training images and 150 test images. The accuracy estimate has roughly a +/-1.6 percentage point standard error.
  • No separate validation split. The recipe monitors the test split every epoch but keeps the last epoch. The choice between variants is also made on the test split (two candidates, so the optimism is small but not zero).
  • Domain. Food-101 photos are user-uploaded restaurant/home photos with some label noise. Performance on other styles (packaged food, drawings, unusual cuisines) is unknown.
Downloads last month
-
Safetensors
Model size
7.77M params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train shalev396/foodvision-mini

Space using shalev396/foodvision-mini 1

Evaluation results