Skip to content
Alp Yalay
HomeWorkCase StudiesAboutResumeContact

Alp Yalay

Portfolio of products, websites, and bilingual systems from Istanbul.

Pages

HomeWorkCase StudiesResumeAboutContactPrivacyDevelopers & API

Developers & Agents

Developer Portal (/developers)OpenAPI 3.0.3 SpecLLMs Context (/llms.txt)MCP Server Endpoint (/api/mcp)Agent Skills Catalog

© 2026 Alp Yalay. All rights reserved

Back to Case Studies
Case Study
Model Training
Edge AI
Offline-First

Training FieldNet 1: A 5,077-Species Wildlife Classifier That Fits in 39 MB

August 2026
22 min
Alp Yalay
XLinkedIn
Role
Creator, Product Architect & Model Trainer
Tech Stack
PyTorch, TensorFlow Lite, MobileNetV4, React Native CLI, RTX 4080
Platform
iOS & Android (Offline-First)
Model Scale
5,077 species · 39 MB INT8 · 61.3% top-1

RealDex shipped with BioCLIP, a scientific vision transformer bolted onto a phone. Then I replaced it. FieldNet 1 is a MobileNetV4 classifier I trained on 5,077 animal species — it raised top-1 accuracy from 46.8% to 61.3%, cut the first-run download by more than half, and survived INT8 quantization almost untouched. This is the story of the failed run, the rigged benchmark, and the USB drive that nearly cost three days.

1. The Question After the First Model

The first version of RealDex was a packaging problem. BioCLIP is a vision transformer built for scientific-scale biological representation learning, not for a React Native camera app. To get it onto an iPhone and an Android foldable, I converted the graph, replaced operators that mobile runtimes reject, quantized the weights to INT8, and shipped a separate database of species vectors beside it. It worked. That felt like the achievement at the time.

But shipping a model and choosing the right model are two different things. BioCLIP identified species by comparison, not by decision: it turned a photo into a 512-dimensional vector, then measured cosine similarity against one stored prototype per species. The encoder was frozen. It had never been trained to separate the exact 5,077 species RealDex cares about. Confidence was often weak, the transformer graph stayed on the CPU, and the complete first-run AI download reached about 105 MB. So I asked the uncomfortable question: instead of optimizing BioCLIP further, should RealDex replace it?

The answer was yes, and the reasoning was structural. A frozen encoder with nearest-prototype matching leaves accuracy on the table by design, because nothing in the pipeline is ever trained on the actual label space. A trained classifier decides directly. It also maps onto mobile accelerators far better, because a convolutional graph is what phone GPU delegates are built to run. The plan was to keep RealDex's existing YOLO detector as a gate and its cloud verification as a fallback, and to replace only the part in the middle: the thing that names the animal.

2. Three Problems That Nearly Ended the Project

Training the model was not the hard part. Three failures were, and each one stayed invisible until it had already done damage:

The Benchmark Was Rigged

My first head-to-head comparison favoured BioCLIP, and the comparison was invalid. Some of the images used to build BioCLIP's species prototypes also appeared in the evaluation pool. BioCLIP was being tested on pictures it had effectively already memorized, while my new model was tested cleanly. This was not a bug in the shipped app. It was a bug in the experiment, which is worse, because the experiment is what decisions get made from. I rebuilt every BioCLIP prototype from training images only and ran the comparison again.

INT8 Quantization Collapsed the Model

The first trained model scored 33.0% top-1 in full precision. Compressed to INT8 for the phone, it fell to roughly 21%. Twelve percentage points disappeared into the conversion. The cause was flat class scores: the model often ranked the right species near the top, but the numerical gaps between candidates were so small that quantization noise reordered them. An accurate model that cannot be compressed is not a mobile model.

A USB Drive Starved the GPU

The RTX 4080 sat mostly idle while training crawled. The dataset was still on an external USB hard drive, which fed images at about 31 per second. The same working set on an internal SSD fed about 301 per second. At USB speed the remaining epochs were projected to take more than 70 hours. Copying the data took 11.7 minutes. The lesson is unglamorous and worth stating plainly: on a single-GPU training box, the bottleneck is usually not the GPU.

3. Spotlight: How the Failures Actually Looked

None of this happened cleanly, and the specific shape of each failure is the part worth keeping:

The First Run
It Lost to BioCLIP
The first serious attempt ran on an M4 Max with 36 GB of memory. It died at epoch 6 when the training process lost access to the dataset, and the partial model scored 33.0% against BioCLIP's 44.9%. I did not treat this as evidence about the architecture. The model was visibly undertrained and the run had ended early, so the honest read was that the run had failed, not the idea. I moved to the RTX 4080 desktop and started again.
The Stall
Destiny 2 Was Holding the VRAM
The restarted run wedged at the exact moment it unfroze the backbone, twice, after up to 1 hour 46 minutes each time. The cause was memory pressure: I had left Destiny 2 running on the same GPU. Closing the game and dropping the batch size from 256 to 128 fixed it, and the remaining 38 epochs then ran without a single stall.
The Near Miss
Two Label Files, Both With 5,077 Names
FieldNet 1 ships its own label file, and 4,798 of its 5,077 indices sit in a different order than BioCLIP's. Both files hold the same species and the same count, so a class-count check passes while every prediction gets the wrong name. This is the same class of bug that once made RealDex identify my cat Mavi as an insect. The fix is to treat model and labels as one versioned artifact, verified together by SHA-256 digest rather than by counting rows.

4. How FieldNet 1 Was Built

Four phases, each of which had to be right before the next one meant anything:

01. Phase 1 — Building the Dataset

The source was the iNaturalist 2021 train_mini archive, about 44 GB. A conversion script kept only classes that map to animal categories and discarded plants, fungi, protozoa, and other kingdoms. That left 5,077 animal species: 2,526 insects, 1,486 birds, 313 reptiles, 246 mammals, 183 fish, 170 amphibians, and 153 arachnids. I capped each species at 50 images rather than forcing a perfectly balanced set, and I removed 5 images per species before training started to serve as an untouched final holdout.

02. Phase 2 — Training the Classifier

FieldNet 1 started from an ImageNet-pretrained MobileNetV4-Conv-L backbone, so it was fine-tuned rather than trained from scratch. Two warm-up epochs trained the new classifier head alone, which protects the pretrained weights while a randomly initialized head finds its footing. The full backbone then unfroze for 38 more epochs at 256x256 input, with RandAugment, Mixup, CutMix, random erasing, label smoothing, stochastic depth, a cosine learning-rate schedule, and an exponential moving average of the weights. The productive run took 7 hours 20 minutes.

03. Phase 3 — Sharpening, Then Export

To fix the quantization collapse I added six final epochs with Mixup disabled and label smoothing set to zero, training the whole network against hard one-hot targets. This widens the numerical gap between the winning score and its rivals, which is exactly what quantization needs. Training loss fell from about 3.7 to 0.75, but that number proves little on its own — removing softened targets lowers the loss mechanically. The real evidence came after export, when FP16 and INT8 landed within 0.14 points of each other.

04. Phase 4 — Shipping It Into the App

FieldNet 1 replaced the retrieval path rather than running beside it, which removed the 10.5 MB prototype database entirely. The app downloads one 39 MB INT8 file after onboarding and verifies its SHA-256 digest before activation, alongside a separately digested label file and geographic prior. The rollout sits behind a Firebase Remote Config flag, so the model could ship dark and be enabled per platform and per version, and every prediction passes a confidence gate before the app is willing to assert a species.

5. What Changed

Measured Results

FieldNet 1 INT8 reaches 61.3% top-1 and 81.7% top-5 across 5,077 species, against a leakage-free BioCLIP baseline of 46.8% top-1 and 73.4% top-5. That is a gain of 14.5 percentage points, or roughly 31% relative. INT8 costs only 0.14 points against FP16 while cutting the file to 39 MB. The complete first-run AI download fell from about 105 MB to about 46 MB. Random guessing in this label space would score about 0.02%.

61.3%
Top-1 Accuracy
39 MB
Shipping Model

The Honest Developer Feeling

"61% does not sound impressive until you know what it is 61% of. This is 5,077 fine-grained wildlife classes, judged on crops from the same YOLO detector the app actually ships, from a 39 MB file running offline on a phone. I did not feel anything when training finished. I felt it when the comparison table rendered and the fair BioCLIP column was clearly, unambiguously lower."

6. The Technical Record

The charts, artifact manifests, calibration thresholds, and the measurements I am not willing to overstate.

The benchmark that decides everything

Every number below comes from one protocol. I removed 5 images per species from the dataset before training started, which produced an untouched holdout. I passed those frames through the shipping YOLOv8n detector and kept the 10,918 crops where it found an animal. Every exported model then saw those same 10,918 crops, and I checked each model's class indices against its own matching label file.

The BioCLIP baseline here is the rebuilt one. Its species prototypes were regenerated from training images only, after I found that the original prototypes had absorbed images from the evaluation pool.

Final benchmark across 5,077 species

10,918 YOLO crops from an untouched holdout

Top-1Top-5
FieldNet 1 — FP16 (75.6 MB)
61.4%
81.7%
FieldNet 1 — INT8 (39.0 MB)ships
61.3%
81.7%
BioCLIP — INT8, leakage-free (88.0 MB + prototypes)
46.8%
73.4%

FieldNet 1 gains 14.5 points of top-1 accuracy over the leakage-free BioCLIP baseline. The INT8 model is the one that ships; it costs 0.14 points against FP16 at half the file size.

+14.5
Points of top-1
46.8% to 61.3%, about 31% relative
0.14
Points lost to INT8
The failed run lost about 12
56%
Smaller first download
About 105 MB to about 46 MB
0.02%
Random-guess baseline
One correct answer in 5,077

I have deliberately not published an FP32 accuracy figure for the final model. I evaluated FP16 and INT8, which are the artifacts that could actually ship. Quoting a third number I did not measure would be padding.

Why sharpening saved the deployment

The failed run showed a pattern worth understanding, because it only appears after export. The full-precision model was mediocre but functional. Both compressed versions were unusable.

Quantization: collapse, then recovery

Top-1 accuracy by numeric format

Failed run — FP3233%
Undertrained, stopped at epoch 6
Failed run — INT821%
About 12 points destroyed by compression
FieldNet 1 — FP1661.4%
FieldNet 1 — INT861.3%
Ships

Left: the abandoned first run, where compression destroyed 12 points. Right: after six sharpening epochs, both shipping formats agree within 0.14 points. Same architecture, different training endgame.

The mechanism is straightforward once you see it. Mixup and label smoothing train a model to spread probability deliberately, because soft targets reduce overconfidence and improve generalization. They also compress the distance between the top candidates. INT8 quantization then rounds those already-narrow gaps, and the ranking scrambles.

Six sharpening epochs reversed it. Mixup off, label smoothing at zero, whole network trainable, hard one-hot targets. The model learned to commit. Training loss dropped from roughly 3.7 to 0.75, which I want to explicitly not present as a breakthrough — removing softened targets lowers the loss arithmetically, whether or not the model improved. The result that mattered was that FP16 and INT8 converged.

Headline technical result

INT8 costs 0.14 percentage points against FP16 while reducing the model file by about 48%, from 75.6 MB to 39.0 MB. Deployment constraints had to enter the training loop to make that true.

Accuracy against size

This is the thesis of the whole project in one figure. Better is up and to the left.

Accuracy versus shipping file size
40%55%70%080160Identification model size (MB)Top-1 accuracyBioCLIP INT8 — 88 MBFieldNet 1 FP16 — 75.6 MBFieldNet 1 INT8 — 39 MB

BioCLIP also needs a 10.5 MB prototype database beside its 88 MB encoder, so its real footprint sits further right than plotted. FieldNet 1 INT8 needs a 0.5 MB label file.

First-run AI downloadBioCLIPFieldNet 1
YOLOv8n detector6.4 MB6.4 MB
Identification model88.0 MB39.0 MB
Species prototypes10.5 MBnone
Labels and priors1.6 MBabout 2.1 MB
Approximate totalabout 105 MBabout 46 MB

Removing retrieval removed a whole asset class. FieldNet 1 carries a geographic prior instead, which is smaller than the prototype database it replaced.

The storage bottleneck

The most expensive mistake in the project had nothing to do with machine learning.

Input throughput by storage device

Images per second reaching the GPU

External USB hard drive31 img/s
Projected remaining time: over 70 hours
Internal SSD301 img/s
Actual remaining time: 7 h 20 m

An 11.7-minute file copy converted a projected 70-plus hours of remaining training into 7 hours 20 minutes. The GPU had never been the constraint.

Training timeline

Runs and wall clock

Batch 256 attempt, epochs 1–2
up to 1 h 46 m, wedged at unfreeze
Batch 128 resumed run, epochs 3–40
7 h 19 m 53 s, no stalls
Total training wall clock
about 9 hours
Exports and evaluation
about 2 h 45 m
Hardware
RTX 4080, Core i5-13600K, 32 GB DDR5
Direct project cost
$100 Claude subscription plus electricity

Every one of those failures was recoverable for one reason: the pipeline checkpointed after every epoch. A corrupted archive, case-insensitive globbing that duplicated files, a missing serving signature on export, a Unicode character that crashed a log path, and three libraries fighting over one Python environment — each cost time, none cost the run.

The training recipe

FieldNet 1 configuration

Backbone
MobileNetV4-Conv-L, ImageNet-pretrained
Input resolution
256 x 256
Classes
5,077 animal species
Warm-up
2 epochs, classifier head only
Fine-tuning
38 epochs, full backbone
Sharpening
6 epochs, hard targets
RandAugment
2 operations, magnitude 9
Mixup / CutMix
alpha 0.2 / enabled
Random erasing
enabled
Label smoothing
on while fine-tuning, zero while sharpening
Stochastic depth
drop_path enabled
EMA decay
0.9998
Schedule
cosine, checkpointed every epoch

Two of those entries carried more weight than the rest. The head-only warm-up exists because a randomly initialized 5,077-way classifier produces large, badly aimed gradients at first, and letting those reach a pretrained backbone damages features that took a lot of compute to learn. The exponential moving average keeps a smoothed copy of the weights across noisy updates, so the exported model reflects the trend of training rather than wherever the last step happened to land.

Dataset composition

Species per animal category

5,077 classes retained from iNaturalist 2021 train_mini

Insects2526 classes
Birds1486 classes
Reptiles313 classes
Mammals246 classes
Fish183 classes
Amphibians170 classes
Arachnids153 classes

The distribution skews heavily toward insects, and that is a real property of the problem rather than a flaw in the data. Insects genuinely dominate animal biodiversity. It does mean the headline accuracy figure is dominated by the hardest and most visually similar classes in the set, which I consider a fair way to be judged.

Derived, not measured

The training pool and holdout sizes usually quoted for this dataset — roughly 228,000 training images and 25,385 holdout frames — follow arithmetically from 5,077 classes, a 50-image cap, and 5 held-out images per species. Sparse classes push the real holdout below that ceiling. The number I actually measured is the one the benchmark used: 10,918 crops where the detector found an animal.

Integration, and the guard that prevents silent nonsense

FieldNet 1 downloads after onboarding and before the camera first opens. Three files arrive as one versioned pack: the INT8 model, its label file, and a geographic prior. Each is verified by SHA-256 digest, and a mismatch causes rejection rather than a load attempt.

The digest check on the label file is not routine hygiene. It is the load-bearing part.

Two label files, both correct-looking

RealDex's older BioCLIP label file also contains 5,077 species names. But 4,798 of those indices sit in a different order than FieldNet 1's. Cross the two artifacts and every count check passes while nearly every prediction is assigned the wrong species name — a failure that looks like a catastrophically bad model rather than a mismatched file. The app enforces a three-way class-count check plus digest verification, which makes a crossed pack unloadable instead of quietly wrong.

Confidence is a gate, not decoration

Replacing retrieval with classification introduced a genuine regression, and it needs stating plainly. BioCLIP could compare a crop against newly generated prototypes, so extending it did not require retraining. FieldNet 1 has exactly 5,077 outputs. Shown an animal outside that set, it must still distribute probability among species it knows.

RealDex therefore refuses to assert a species unless three conditions hold together.

Calibration thresholds

Top-1 probability
at least 0.60
Margin over runner-up
at least 0.05
Posterior entropy
at most 3.58 nats
Softmax temperature
1.0

Below those thresholds the app presents ranked local candidates and an explicit option to verify with the cloud. Low confidence never silently spends a cloud identification credit.

Honest limitation

Entropy and margin gates reduce confident mistakes. They do not make a closed-set classifier genuinely open-set. Photograph an animal outside the 5,077, and the honest best case is that RealDex declines to name it — not that it knows what it is.

Latency: what I measured, and what I will not claim

This is the section where my own first draft overreached, so it is worth being precise about the limits of the evidence.

Old capture pipeline, single instrumented run

BioCLIP era, capture to visible result sheet

Capture to frozen frame
660 ms
Image decoding
3.7 s
YOLOv8n crops, 3 candidates
284 ms
BioCLIP matching, 3 candidates
1.6 s
Result sheet visible: about 11.1 s total

Individually measured stages account for about 6.2 of the 11.1 seconds. The rest sat in orchestration, image preparation, state changes, and UI work. These are single-run figures from my own instrumentation, not an averaged benchmark.

On a Galaxy Z Fold5, FieldNet 1 classified through the Android GPU delegate in 44, 50, and 67 milliseconds across three runs, averaging 53.7 ms. It identified the test cat correctly as Felis catus at 43% confidence, with a long-tailed weasel and a mountain lion trailing well behind — wrong answers, but wrong within visually related mammals, which is the failure mode you want.

I am not going to multiply 1,600 ms by anything and call it a speedup. The BioCLIP figure came from an iPhone 13 mini on CPU and the FieldNet figure from a Fold5 on GPU: different silicon, different operating system, different delegate. Two numbers measured that differently do not divide into an honest ratio.

What the iPhone actually does

The migration's architectural claim was that a convolutional graph can reach accelerators a transformer cannot. On Android that held. On iOS it did not. TensorFlow Lite's Core ML delegate accepted 0 of the classifier's 135 nodes across 29 rejected partitions, so RealDex now requests plain CPU there deliberately — the delegate log line claims Core ML and is misleading. The detector still uses Core ML successfully, at 214 delegated nodes. A same-device iOS latency benchmark is outstanding work, not a result I am holding back.

How this was built, and by whom

I want the attribution here to be accurate rather than flattering in either direction.

I set the product objective and accepted the architecture change. I set the fairness standard that exposed the benchmark leakage, and I decided the leaky comparison could not be published even though it was the one that had already run. I chose to restart the failed run rather than salvage it. I accepted the closed-set trade-off and specified that confidence had to gate assertion. I decided which numbers in this case study were strong enough to publish.

The instrument was AI throughout. Kimi K3 framed the initial architecture brief and training guidelines. The deeper architecture comparison ran through Claude Code as a multi-agent research pass across BioCLIP, BioCLIP 2, MobileNetV4, EfficientNet, MobileCLIP, and DINOv2, where 22 claims survived three-vote adversarial checking. Claude Opus 4.8 then implemented the training, recovery, export, and benchmark pipeline.

What that cost

FieldNet 1 was trained on hardware I already owned, not rented cloud GPUs. Direct cost: a $100 Claude subscription plus electricity. That does not make the work free — the desktop, the storage, the failed runs, and the attention were real costs. It does show that a useful fine-grained mobile classifier no longer requires turning every experiment into a compute bill.

Current status

FieldNet 1 ships in RealDex on Google Play, enabled through a Firebase Remote Config flag scoped by platform and version so it can be switched off without a release. The iOS build is pending App Store release and uses the same INT8 artifact on CPU. The live in-camera classification path exists in the codebase but stays disabled: its latency is unvalidated, and shipping an unvalidated real-time path into a camera is how you turn a 54-millisecond model into a janky one.

What I would tell someone starting this

Train the smallest model that can plausibly work, and evaluate it the way it will ship — quantized, on detector crops, against a holdout you removed before you started. Every shortcut I took around that principle cost me more time than the shortcut saved. The benchmark leakage cost a full rebuild of the baseline. The soft-target training cost six extra epochs and a second export cycle. The USB drive nearly cost three days.

BioCLIP proved RealDex could run serious biological vision on a phone. FieldNet 1 proved the first model did not have to be the final architecture.

Key Lessons

A Reproducible Benchmark Can Still Answer the Wrong Question

My leaky benchmark was automated, deterministic, and numerically precise. It was also rigged, and every one of those good properties made it more convincing rather than less. Rigor is not a reporting step you perform at the end. It belongs in the implementation, next to the code that builds the evaluation set, because a benchmark's job is to be capable of telling you that you were wrong.

Do Not Get Attached to an Architecture Because It Shipped

BioCLIP was the right first decision and the wrong permanent one. It proved a serious biological vision model could run offline on a phone, which was the question that mattered in version one. Replacing it meant discarding work I was proud of, including the vector loaders and the embedding pipeline. The sunk cost was real. It was also irrelevant to the question of what the product should do next.

Deployment Constraints Belong in the Training Loop

The quantization collapse was not an export bug. It was a training decision surfacing three steps downstream, because heavy label smoothing and Mixup produce a model whose confidence is deliberately soft — and soft confidence is precisely what INT8 destroys. I could not fix it in the converter. I had to go back and change how the model was trained. If a model has to run in 8 bits on a phone, that constraint has to be present while it learns.

Summary

FieldNet 1 moved RealDex in two directions at once. The shipping INT8 model raised fair top-1 accuracy by 14.5 percentage points while cutting the first-run AI download by roughly 56%. It removed the separate embedding database, survived quantization with a 0.14-point loss, and reached the Android GPU delegate that the old transformer could never use. Products usually trade accuracy against size. This one improved both, because the dataset, training recipe, benchmark, export, and mobile integration were treated as one system rather than five stages.

The number I care about is not 61.3%, or 39 MB, or 54 ms. It is the combination. A model that is accurate but too large is a demo. A model that is fast but unreliable is a gimmick. And a model that beats its predecessor on a benchmark it was allowed to cheat on is neither — which is why the most valuable hour of this project was the one I spent proving my own result wrong before I published it.

See It Running

RealDex is a wildlife field journal for iOS and Android. You photograph real animals, the model names them offline, and your collection fills in like a Pokédex.

Previous case study4,586 Strings, 99 Players: The Heroes of Hammerwatch II Turkish PatchGame LocalizationNext case studyCase Study: Vibe Coding Prompt Template – 2,806 Stars for a Workflow I Wrote for MyselfOpen Source

Contents

  1. 1. The Question After the First Model
  2. 2. Three Problems That Nearly Ended the Project
  3. 3. Spotlight: How the Failures Actually Looked
  4. 4. How FieldNet 1 Was Built
  5. 5. What Changed
  6. 6. The Technical Record
  7. The benchmark that decides everything
  8. Why sharpening saved the deployment
  9. Accuracy against size
  10. The storage bottleneck
  11. Training timeline
  12. The training recipe
  13. Dataset composition
  14. Integration, and the guard that prevents silent nonsense
  15. Confidence is a gate, not decoration
  16. Latency: what I measured, and what I will not claim
  17. How this was built, and by whom
  18. Current status
  19. What I would tell someone starting this
  20. Key Lessons
  21. Summary