OrchidCLIP

A long-tail-aware CLIP model for fine-grained orchid identification across 5,124 species.

TL;DR. orchid-clip-v8 is a long-tail-aware orchid CLIP model: +7.6 pp top-1 averaged per genus (0.844 vs BioCLIP 2's 0.768), with gains concentrated on the rarest. Averaged per image it reads 0.911, but that holdout is 70% Ophrys — a genus both models already handle — so the per-image figure understates the lift. It also ran into a wall. Across six independent extension attempts, genus structure transfers but within-genus species identity stays locked. The live demo therefore serves a calibrated genus, naming a species only when the top-1/top-2 margin earns it.
🌿 Try the live demo 🤗 Model on HF 💻 Code on GitHub
On this page: The long tail · What lifted it · Taxonomy-shaped errors · The embedding · Six attempts, one wall · Try the demo · What moved the tail · Status & usage

orchid-clip-v8 is a CLIP model fine-tuned from BioCLIP 2 for fine-grained orchid identification. Orchidaceae is one of the largest plant families and one of the most heavily long-tailed domains in biological vision: a handful of cultivated genera dominate every public image source, while thousands of tropical-epiphyte species have fewer than 30 labeled images on the entire internet. v8 lifts top-1 accuracy from 0.873 (BioCLIP 2 baseline) to 0.911 on a 4,000-image holdout. The gains fall where the training was aimed, on long-tail Pleurothallidinae genera, which gain +14 to +28 pp.

Headline

model top-1 per image top-1 per genus top-5 genus-top-1
BioCLIP 2 0.873 0.768 0.978 0.992
orchid-clip-v8 0.911 0.844 0.986 0.991

Read the per-genus column. The holdout is a hash-partitioned random 2% of the corpus, so it inherits the corpus’s skew: Ophrys alone is 2,754 of the 4,000 images, and it is a genus both models already do well on (0.933 vs 0.905). Averaging per image therefore weights the result 70% toward the case with the least to gain, and the lift reads +3.8 pp. Averaging per genus, it is +7.6 pp — double. On a page about the long tail, the per-genus figure is the one that answers the question being asked; the per-image figure answers a different one, how often the model is right on a random photo from this distribution.

Either way the lift is real species discrimination, not a coarsened boundary: genus-top-1 is unchanged (0.991 vs 0.992, within noise).

Per-genus figures average the 14 genera with ≥20 holdout images, which is 3,937 of the 4,000. The genera below that cutoff are the rarest, so the per-genus number is if anything the optimistic one.

† Which number is the product? This is a closed-set benchmark: each holdout image is ranked (image→text) against only the 547 species that appear in the 4,000-image holdout. The deployed demo faces the full open set of all 18,858 named species, a 34× larger candidate pool, so its species top-1 starts at 0.71 before the abstain buys it back to 0.90; genus stays reliable (~0.94) on the open set too. (The live demo now ranks each photo against per-species image centroids rather than the text table. The open-set rates are essentially unchanged, but a source-expansion pass has since lifted the starved tail well above them; see below.) How the two compare →

The long tail is the point

Per-genus top-1 accuracy of orchid-clip-v8 vs BioCLIP 2, sorted by long-tail Δ. Hover any bar for the exact accuracies and Δ. Lift concentrates on the smallest, longest-tailed Pleurothallidinae genera.
genus n v8 BioCLIP 2 Δ
Stelis 25 0.640 0.400 +24.0 pp
Lepanthes 40 0.800 0.525 +27.5 pp
Bulbophyllum 41 0.732 0.585 +14.6 pp
Maxillaria 94 0.787 0.649 +13.8 pp
Pleurothallis 100 0.800 0.690 +11.0 pp

The biggest lifts come on the smallest, longest-tailed genera. Stelis (~1,300 species worldwide, n=25 in the holdout) gains +24 pp; Lepanthes (~1,200 species, n=40) gains +27.5 pp. Head genera like Ophrys (n=2,754) gain modestly but never regress.

A two-by-two grid of line charts comparing orchid-CLIP v8 (left column) with BioCLIP 2 (right), plotting top-1 and macro-genus accuracy against labels per species for three probes. v8's curves start high and stay near a dashed zero-shot ceiling; BioCLIP 2's start far lower and climb steeply.
Few-shot adaptation on 190 rare seen species, three probes over frozen v8 (left) vs BioCLIP 2 (right). v8’s zero-shot (dashed, 0.97) already sits at the ceiling, so extra labels barely help; BioCLIP 2 needs ~25 labels/species to reach where v8 starts.

What lifted the long tail (and what didn’t)

The training pool is 1.14M images covering 5,124 species after WCVP synonym dedup, a previous-generation cosine quality filter, and a min_species ≥ 3 threshold. The class-frequency distribution is heavily skewed:

Log-log species-frequency of the 1.14M-image v8 training pool — every point is one of 5,124 species; hover for its exact image count. Median 23 rows, floor 3; the orange line is the 2,000-per-species sampler cap that the 101 head species exceed.

The recipe that worked:

  1. Inverse-square-root long-tail sampler. Sample each (binomial, image) pair with weight ∝ 1/√n_rows_in_class, with a per-species cap of 2,000. Less aggressive than uniform-by-class (which over-corrects and hurts head accuracy), much more tail-friendly than uniform-by-row.
  2. WCVP 2026 synonym dedup. Of 26,928 unique binomials in the raw label space, 4,504 binomials covering 69,467 image rows resolved to a different accepted name. The largest single confusion in the previous-generation v7 model was Ophrys fucifloraOphrys holosericea (29% of all v7 errors), which collapsed entirely under WCVP because they are the same accepted species.
  3. Previous-generation cosine filter. Drop the bottom percentile of training rows by previous-generation image↔binomial cosine. Rows that score poorly against their claimed binomial under a previous orchid-specific model are most likely label errors or off-target images.
A horizontal bar chart of the most frequent v7 intra-genus confusion pairs. The top bar, Ophrys fuciflora versus holosericea, is highlighted dark red at 149 errors and dwarfs every other pair, the next largest being 20.
The dominant v7 confusion pairs on the 4,000-row holdout. Ophrys fucifloraholosericea (149 errors) is a WCVP synonym — the same accepted species — so it collapses entirely under the 2026 dedup; resolving synonyms removes the single largest error source before a single training step.

The dedup holds up both ways. Re-running the holdout in WCVP-_accepted_ label space scores top-1 0.9145, essentially identical to the raw-label 0.911, because collapsing fucifloraholosericea just surfaces the next cryptic pair (Ophrys argolicasphegodes) beneath it. The residual within-genus confusion therefore reflects real morphology.

Three substantial ablations against this recipe each underperformed:

  • v9, a backbone swap to BioCLIP 2.5-H ViT-H/14: regressed −2.5 pp top-1.
  • v10, a hierarchical genus-species sampler: regressed on macro-genus (cardinality-blind across genera).
  • v11, an auxiliary genus classification head: lifted genus-top-1 by +0.6 pp but regressed top-1 by −0.7 pp.

At this scale, in this domain, the dominant variable is the label distribution. Architectural and auxiliary-objective changes that would help on a balanced dataset can actively hurt when the underlying label space is heavily skewed and noisy.

Errors are taxonomy-shaped

A grouped bar chart of error fraction against taxonomic distance, comparing observed v8 errors with a uniform-random null. Same-genus errors are 90 percent observed versus near zero under the null, and the pattern inverts at greater distances.
Observed v8 error distribution across WCVP rank distances vs. a uniform-random null over the 18,858-species candidate space (the full text-ranking vocabulary). Same-genus errors are 58× more common than chance.

When v8 is wrong, it’s wrong in a structured way. Bucketing errors by WCVP rank distance between the true and predicted class:

rank distance observed null lift over null
same genus (d=1) 90.1% 1.6% 58×
same tribe (d=2) 4.8% 14.8% 0.32×
same subfamily (d=3) 2.3% 20.4% 0.11×
diff subfamily (d=4) 0.3% 51.9% 0.01×

(The observed column sums to 97.5% — the remaining 9 of 355 errors fall on predictions with no resolvable WCVP rank distance to the true class.)

Errors at d=1 are 58× more common than chance; cross-subfamily mistakes are essentially absent. Downstream consumers should read v8’s top-1 prediction as “this genus, probably this species” rather than as a hard species label. The model’s effective competence is at the genus level, with a residual species-level disambiguation problem in cryptic-species complexes.

Which species does v8 actually mix up? Pulling its 355 holdout errors apart, 320 (90%) are within-genus and only 35 cross a genus boundary. The within-genus mistakes cluster in the cryptic, long-tailed genera the sampler targets, sister species that even specialists separate on subtle floral-segment detail:

genus within-genus errors rate an illustrative cryptic pair
Maxillaria 19 / 94 20% hematoglossameleagris
Lepanthes 8 / 40 20% tachirensisscopula
Pleurothallis 17 / 100 17% cordataerymnochila
Encyclia 15 / 101 15% tampensisadenocarpos
Oncidium 7 / 57 12% sphacelatumobryzatum
Masdevallia 7 / 64 11% bonplandiifloribunda

Every pair is two species of the same genus, so the mistake stays inside the genus knot. (The single largest raw within-genus count, Ophrys fucifloraholosericea at 82, is deliberately left out: it’s the WCVP synonym from “What lifted the long tail”, the same accepted species mislabeled in the holdout, a labeling artifact rather than a real confusion.)

The embedding, up close

That taxonomy-shaped error structure is something you can see. Below is the same v8 prototype space as the cover figure, made explorable: every point is one of 18,601 species (its mean v8 image embedding) projected to 2D with UMAP and colored by WCVP subfamily. Hover any point to read off its species, genus, and how many images built the prototype; drag to zoom into a clade.

UMAP of all 18,601 v8 species prototypes (per-binomial mean image embedding), colored by WCVP subfamily. Use the color dropdown to recolor by tribe — the finer the level, the tighter the knots.

Three species counts, three scopes. The page carries three numbers because three things are being measured: 5,124 species have ≥3 training images (the holdout-eval space); 18,858 is every species in the shipped gallery the live demo ranks each photo against, now one per-species v8 image centroid each (each built from ≥1 photo); 18,601 of those also carry a known orchid subfamily, and those are the points plotted above (a few hundred centroids whose subfamily is empty or non-orchid are dropped from that plot).

Zoom into almost any neighborhood and the points resolve into tight, same-genus knots. The genus level is what v8 has learned to separate. The within-genus species detail that the six extension attempts below all chase is the residual spread inside those knots, and the projection never cleanly pulls it apart.

Can the species gap be closed? Six attempts, one wall

That “this genus, probably this species” framing raises the obvious question: can the residual species-disambiguation problem be fixed? v8 already shows the embedding organizes the domain hierarchically, so the species detail ought to be reachable with the right extra lever. I ran six independent extension attempts, each from a different mechanism class, each with its own kill-gate. All six landed in the same place: genus structure transfers and stays decodable; species identity does not.

extension lever genus species
a second modality: herbarium scans / written descriptions 0.81–0.93 0.005 → 0.686, then plateaus
more capacity: 2× ViT-H backbone, clade mixture-of-experts n/a no lever found
interpretability: sparse autoencoder over frozen features partial 0 of 13 morphology axes
open-set recognition: reject never-seen species card holds novel-rejection 0.155
generative augmentation: synthesize tail species n/a no lift past 2–3 real photos
model-free control: classical CV morphology, no v8 (within-photo only) cross-modal corr ≈ 0

Two are worth spelling out. A second modality is the most direct lever: give the model a dried herbarium specimen or a written description per species. It recovers genus cheaply, but within-genus species climbs only from 0.005 to 0.686 as the alignment improves, and there it sticks; neither more capacity nor more data moves it. Each modality separates species from itself (herbarium→herbarium 0.88, photo→photo 0.99), but those axes don’t line up across the gap between them.

The model-free control is the cleanest. Setting v8 aside entirely, I measured fourteen classical computer-vision features (color clusters, texture, symmetry, aspect ratio) straight off the pixels. Within photos they tell congeneric species apart above chance across all 52 genera tested; across the photo-to-herbarium gap the per-species values correlate at essentially zero on every axis, even for the best-measured species. That locates the wall in the data itself.

A line chart across three training stages with two series. Genus top-1 stays roughly flat near 0.81 to 0.93, while species top-1 climbs from 0.005 through 0.077 to 0.686 and then stops short of 0.69.
The most direct lever, spelled out. Across three alignment stages the genus signal stays flat near the top while within-genus species top-1 climbs two orders of magnitude — 0.005 → 0.077 → 0.686 — and then stalls below 0.69. The mechanism keeps improving; the species ceiling holds.

A single failed extension would be a tuning anecdote. Six independent failures, each with its own gate, all landing on the identical genus-survives / species-locked split is evidence about the embedding itself. It also generalizes: this is the fine-grained-taxonomy face of the modality gap that contrastive image-text models are known to exhibit.

Building around the boundary

If the species gap is structural, the useful response is to serve predictions at the granularity the embedding actually earns. The deployed Orchid Photo → ID card does that: a zero-training layer reads the margin between the top-1 and top-2 species scores and, when it’s too thin, abstains to “Genus X (species uncertain)” rather than committing to a confident wrong binomial. That one rule lifts shown-species precision from 0.71 to 0.90 while still naming a species on 60% of photos.

That 0.71 starting point is lower than the 0.911 per-image figure in the table above because the two measure different things. That figure is a closed-set benchmark, with each image ranked against only the 547 species present in the holdout. The card faces the full open set of all 18,858 named species, a 34× larger candidate pool, so it can confuse a photo with any orchid on Earth rather than with the few hundred in a test split. (The card now ranks against per-species image centroids rather than the text table; the open-set species rate is essentially unchanged at ~0.71, while the starved tail is much improved. See what moved the tail, below.) Genus stays reliable either way (~0.94 here); the abstain is what buys species precision back.

The trade-off is adjustable: every point below is one threshold on the top1−top2 margin, sweeping how often the card commits to a species against how often it’s right when it does.

Risk–coverage for the species-abstain, from the deployed calibration (n=7,137 leakage-safe holdout, image→image-centroid path). The orange star is the live operating point — τ=0.0149, precision 0.90 at 60% coverage; grey at far right is the no-abstain baseline (0.71).

Try it live → Upload an orchid photo; the card names a species when the margin is confident and falls back to the genus when it isn’t.

The live genus-ID Space, embedded. The first request wakes the free CPU Space and loads the ViT-L/14 tower (a few seconds); after that, each photo embeds and ranks against 18,858 species in real time.

What finally moved the tail

The first lever that moved the species wall was the data, not the model. Folding the missing photos into the starved tail lifts species top-1 from 0.16 to 0.50 and genus from 0.64 to 0.81 on those species, a 3× species gain, while overall accuracy holds flat. The live card now ranks against that expanded image-centroid gallery.
Photos per species for the 3,597 starved-tail species, before and after the source-expansion. Mass drains out of the 1–2-photo bin into 3–30 — the coverage the within-genus signal was starved of (median 2 → 5).
Where the 27,770 net-new images came from. Three homogeneous catalogs (New Guinea, Epidendra, OrchidWeb) were scraped but discarded: standardized rendering drags centroids off the field-photo manifold and hurts accuracy.

Six embedding-side levers couldn’t move within-genus species identity, but part of the wall sits in the inputs. The deepest-tail species are starved: the corpus carries a median of ~2 photos for them, too few to pin a stable centroid. A targeted source-expansion pass, drawing on vendor, captive, and curated photo sources the iNaturalist-dominated corpus misses, feeds those species the views they lacked, and the deployed card ranks against the expanded image-centroid gallery.

This cuts against a purely architectural reading of the wall. Where the embedding is finally fed enough views of a rare species, it can separate it; the six levers stalled because they re-tuned the model rather than its inputs. Two caveats. Blending helps only the starved species: pooling the same sources into already-well-photographed species regresses them (catalog-style images drag a healthy centroid off the field-photo manifold), so the deploy blends the thin tail and leaves the rest untouched. And the genuinely-unphotographed deep tail still resists, which is a data-collection problem rather than a modeling one.

Status

The frozen v8 image encoder is released on HuggingFace as musharna/orchid-clip-v8 (MIT), a foundation embedding for downstream orchid tasks, and the abstain-gated genus-ID card runs as a live Space at musharna/orchid-genus-id. The full extension program above, six mechanism classes with their kill-gates plus the v9–v11 ablations, is written up as a negative-results manuscript, “Genus Transfers, Species Doesn’t: A Mechanism-Invariant Boundary in a Fine-Grained Taxonomic Embedding.” The interactive UMAP above projects those v8 species centroids colored by WCVP subfamily. Cypripedioideae and Vanilloideae form clean islands while the two megadiverse subfamilies (Epidendroideae and Orchidoideae) partially overlap, and that overlap is where the within-genus species ceiling lives.

Where it works, and where it doesn’t

Every benchmark here is on an iNaturalist-dominated holdout, and v8 inherits that distribution. On other in-situ photo sources it degrades only mildly: OrchidRoots, Tree-of-Life, and Flickr cohorts lose −0.10 to −0.11 top-1, with genus largely intact. But on botanically-curated archives heavy with herbarium specimens and illustrations (IOSPE, POWO) it collapses: top-1 falls to 0.14–0.19 and even genus drops to ~0.55. The within-genus species wall documented above is a property of field photographs; herbarium and illustration imagery is a separate, larger modality gap, and exactly the second-modality lever in the six-attempt table. Heads built on v8 inherit this: deploy it on field photos, not on scanned plates.

Using v8 as an embedding

orchid-clip-v8 is an open_clip checkpoint (ViT-L/14, fine-tuned on top of BioCLIP 2). Loading it and embedding a photo is a few lines:

# pip install open_clip_torch huggingface_hub torch pillow
import torch, open_clip
from huggingface_hub import snapshot_download
from PIL import Image

ckpt = snapshot_download("musharna/orchid-clip-v8")          # model_config.json + open_clip_pytorch_model.bin
model, _, preprocess = open_clip.create_model_and_transforms("ViT-L-14", pretrained=None)
state = torch.load(f"{ckpt}/open_clip_pytorch_model.bin", map_location="cpu", weights_only=False)
model.load_state_dict(state["state_dict"]); model.eval()     # weights live under state["state_dict"]

img = preprocess(Image.open("orchid.jpg").convert("RGB")).unsqueeze(0)
with torch.no_grad():
    feat = model.encode_image(img)
feat = feat / feat.norm(dim=-1, keepdim=True)                # 768-d, L2-normalized

That 768-d feature is the foundation embedding. Cosine-rank it against per-species image centroids or text embeddings for ID, or use it directly for retrieval and downstream heads (bloom-stage, disease, mounting-style). The repo ships embed_example.py (with zero-shot scoring against arbitrary species names) and a sanity_check.py.


Last updated June 2026 · orchid-clip-v8 (MIT) · live demo orchid-genus-id · code github.com/musharna/orchid-clip. All accuracies are on a hash-partitioned random 2% holdout, iNaturalist-dominated; closed-set unless noted.