Dashboard Part 6 Custom Vision Models
🎯 Part 6 · Topic 2 of 3

Custom Vision Models

Azure Custom Vision lets you train your own computer vision model using your own photographs — without writing machine learning code. You upload labelled images, click Train, and Azure builds a model that can classify images or detect objects with bounding boxes. This topic covers the complete workflow from labelling to publishing to evaluation metrics.

🐍 Python — every line explained ⏱️ ~45 min 🔥 High exam weight — metrics are a trap area
⚠️
Important: Custom Vision is being retired

Azure Custom Vision is transitioning to Azure AI Foundry / Azure Machine Learning AutoML for Vision. New model creation will be disabled from 2026 onwards. However, the exam still tests Custom Vision — including its workflow, metrics, and Python SDK. Learn it for the exam. For new production projects, prefer Azure ML AutoML for Vision. The retirement applies to new projects, not knowledge of the service.

Custom Vision terms — defined before code

Image Classification

Answering the question: "What is in this image overall?" — assigning one or more category labels to the whole image. Example: a photo of a cat → label: "Cat". A photo of a beach → label: "Beach". The model doesn't tell you WHERE in the image the thing is — just what the image shows. Two sub-types: multi-class (exactly one label) and multi-label (multiple labels allowed per image).

Object Detection

Answering the question: "What objects are in this image and WHERE are they?" — identifies specific objects and draws bounding boxes around each one. Example: a photo of a street → detects: Car at [top-left], Person at [centre], Bicycle at [right]. Use when you need to locate and count specific objects, not just classify the whole image.

Tag (Custom Vision)

A category label applied to a training image. For classification: you add tags to whole images ("Cat", "Dog"). For object detection: you add tags to bounding box regions you draw on images. Tags are the categories your model learns to recognise. You need at least 5 images per tag minimum (15+ recommended for good accuracy).

Multi-Class Classification

Each image gets exactly one tag — the image belongs to one and only one category. The model outputs the single most likely class. Example: classify photos as either "Healthy" or "Diseased" plant — no image can be both. Use when categories are mutually exclusive.

Multi-Label Classification

Each image can have multiple tags — an image can belong to several categories simultaneously. Example: a food photo can be tagged as both "Vegetarian" and "Spicy" and "Main Course". The model outputs confidence scores for ALL tags, not just the top one. Use when categories are not mutually exclusive.

Iteration

A trained version of your custom model. Each time you train in Custom Vision, a new Iteration is created — the previous iterations are kept. This gives you a version history. You can compare iterations' metrics side by side. Only one iteration can be published (active) at a time. If training with new data improves metrics, you publish the new iteration to replace the old one.

Precision

Of all the times the model said "Yes, this is a Cat" — what percentage was it actually right? Precision = True Positives / (True Positives + False Positives). High precision means: when the model fires, it's reliable. Low precision means: it fires too eagerly and generates many false alarms. Important when false positives are costly.

Recall

Of all the actual Cats in the test set — what percentage did the model correctly find? Recall = True Positives / (True Positives + False Negatives). High recall means: the model finds most of the real examples. Low recall means: it misses many real examples. Important when missing true positives is costly (e.g. disease detection).

mAP (Mean Average Precision)

The single summary metric for object detection models. Combines precision and recall across all object classes into one number. Higher is better — 100% mAP is perfect. Used because detecting objects requires both finding them (recall) AND putting the bounding box in the right place (precision). mAP is the standard metric for object detection; Precision and Recall are used for classification.

Probability Threshold

A confidence cutoff — the model only reports a detection/classification if its confidence score is above this threshold. Default: 50% (0.5). Raising the threshold (e.g. to 80%) → higher precision (fewer false positives) but lower recall (misses more). Lowering the threshold (e.g. to 30%) → higher recall (catches more) but lower precision (more false positives). The exam tests this tradeoff directly.

Quick Training vs Advanced Training

Quick Training: fast (minutes), uses a lightweight model, good for initial experiments and small datasets. Lower accuracy potential. No configuration needed. Advanced Training: slower (hours), uses a more powerful model, configures a training budget (how many hours of compute to use). Better accuracy, especially with more training data. The exam may ask when to use each.

Prediction Endpoint

The REST API endpoint for making predictions with your published Custom Vision model. After publishing an iteration, you get a prediction URL your application calls with new images. Two prediction methods: classify_image() for classification, detect_image() for object detection. The prediction URL and prediction key are separate from the training endpoint and key.

Suggested Tags (Active Learning)

Custom Vision automatically suggests tags for newly uploaded untagged images based on the current model's predictions. You review and confirm or correct the suggestions rather than manually tagging every image from scratch. This speeds up the labelling process significantly as your dataset grows. The model learns from your corrections to improve future suggestions.

Choosing the right project type — the most common exam decision

📚
Analogy — A stamp vs a spotlight

Image Classification is like stamping a label on the whole photo envelope: "This envelope contains a cat photo." You know what's in the photo, but not where.

Object Detection is like shining a spotlight inside the photo: "There's a cat in the top-left corner, a dog in the centre, and a bowl in the bottom-right." You know both what and where.

If the scenario only needs to categorise images (spam vs not spam, defective vs normal), use classification. If the scenario needs to find and locate specific things (count cars, detect defects in a specific part), use detection.

Decision guide — classification vs detection

ScenarioProject TypeWhy
Classify product images as "Defective" or "Normal"Multi-class ClassificationEach image is either one or the other — mutually exclusive categories
Tag food photos with all applicable dietary labels (Vegan, Spicy, Gluten-free)Multi-label ClassificationOne photo can have multiple labels simultaneously
Detect and count cars in parking lot camera imagesObject DetectionNeed to find WHERE each car is and how many there are, not just "cars are present"
Locate and highlight specific defects in circuit board imagesObject DetectionNeed to know WHERE the defect is on the board — bounding box required
Identify which type of fruit is in a photoMulti-class ClassificationImage shows one type of fruit — one label per image
Detect and locate faces, cars, and pedestrians in dashcam footageObject DetectionMultiple distinct object types need to be located with bounding boxes

Labelling differences

Classification Labelling

1. Upload images to your project
2. Select each image (or batch)
3. Assign one tag (multi-class) or multiple tags (multi-label)
4. Repeat until all images are tagged

No drawing required — you just select the label for the whole image.

Object Detection Labelling

1. Upload images to your project
2. Open each image in the labelling tool
3. Draw a bounding box around each object
4. Assign a tag to each drawn box
5. Every object in the image must be boxed

More effort per image — but gives the model location data.

The complete Custom Vision pipeline

1
Create a Custom Vision project

In customvision.ai (or via Python SDK), create a project specifying: project type (Classification or Object Detection), classification type (Multi-class or Multi-label), and domain (General, Food, Retail, Landmarks, etc. — each domain specialises the base model). Domains affect the underlying base model used for transfer learning. Choose "General" if none of the specific domains match.

2
Upload and label images

Upload at least 5 images per tag (minimum). 15+ per tag recommended for reasonable accuracy. 50+ per tag for good production accuracy. Images should be varied — different angles, lighting conditions, backgrounds, sizes. Similar images don't help the model generalise. For object detection: draw bounding boxes around every instance of every object in each image.

3
Train the model

Click "Train" — choose Quick Training (faster, less accurate) or Advanced Training (specify a compute budget in hours). Training performs transfer learning: it takes a powerful pre-trained vision model and fine-tunes it on your tagged images. A new Iteration is created. You can train multiple times — each creates a new Iteration.

4
Evaluate metrics

After training, Custom Vision shows Precision, Recall, and mAP for the new Iteration. Review these metrics at different probability thresholds using the threshold slider. Identify which tags have low precision or recall — these need more training images or better image diversity. Compare against previous iterations to confirm improvement.

5
Test with new images

Use the "Quick Test" feature in the portal to upload images that were NOT part of training. Review the model's predictions — this is a real-world sanity check beyond the training metrics. If the model performs well on Quick Test images, you can be more confident in its real-world accuracy.

6
Publish the Iteration

Click "Publish" on the Iteration you want to deploy. Give it a publication name. This exposes the prediction endpoint URL and prediction key that your application will use to call the model. Until published, the model is only accessible for testing in the portal — it has no REST endpoint for external applications.

7
Consume via prediction endpoint

Call the prediction endpoint from your application using the azure-cognitiveservices-vision-customvision Python SDK (or REST). For classification: call classify_image() — returns predictions with tag names and probabilities. For detection: call detect_image() — returns predictions with bounding boxes, tag names, and probabilities. Filter by probability threshold.

Precision, Recall, mAP — and the probability threshold tradeoff

P
Precision

"When the model fires, how often is it right?"

TP ÷ (TP + FP)

High precision = low false alarm rate. Relevant when false positives are expensive (e.g. flagging innocent products for disposal).

R
Recall

"Of all real examples, how many did it find?"

TP ÷ (TP + FN)

High recall = catches most real cases. Relevant when missing true positives is dangerous (e.g. missing a defect that could cause product failure).

mAP
Mean Avg. Precision

Object detection summary metric. Combines precision + recall + bounding box accuracy across ALL classes into one score. Higher is better. Used for detection; Precision + Recall used for classification.

Probability threshold — the precision/recall tradeoff

Every prediction has a probability score (0–100%). The probability threshold is your cutoff — below it, the model stays silent. Adjusting it changes the precision/recall balance:

ThresholdEffect on PrecisionEffect on RecallUse when…
High (e.g. 80%) ⬆️ Higher (fewer false positives) ⬇️ Lower (misses more real cases) False alarms are very costly (e.g. triggering expensive automated responses)
Default (50%) Balanced Balanced General purpose scenarios where both matter equally
Low (e.g. 30%) ⬇️ Lower (more false positives) ⬆️ Higher (catches more real cases) Missing a real case is dangerous (e.g. missing a defect in safety-critical manufacturing)
🎯
Exam Tip — Precision/Recall scenario questions

"The model is generating too many false alarms — legitimate products are being rejected."
→ This is a low Precision problem (too many false positives). Fix: raise the probability threshold.

"The model is missing too many actual defects — defective products are passing through."
→ This is a low Recall problem (too many false negatives). Fix: lower the probability threshold.

"The company needs to balance catching defects vs. not flagging good products."
→ Use the default 50% threshold or adjust based on the cost comparison.

Custom Vision end-to-end — train, publish, and predict

Install: pip install azure-cognitiveservices-vision-customvision

from azure.cognitiveservices.vision.customvision.training import CustomVisionTrainingClient
from azure.cognitiveservices.vision.customvision.training.models import (
    ImageFileCreateBatch,  # Wraps a batch of images with their tags for upload
    ImageFileCreateEntry,  # One image + its tag(s) in a batch
    Region,                # Bounding box for object detection (normalised 0.0–1.0)
)
from azure.cognitiveservices.vision.customvision.prediction import CustomVisionPredictionClient
from msrest.authentication import ApiKeyCredentials
# CustomVision uses msrest credentials, not azure-core credentials
import os
import time  # For polling the training status

# ── Connection details ─────────────────────────────────────────────────
# Custom Vision has SEPARATE endpoints and keys for Training and Prediction
# Find them in customvision.ai → Settings (gear icon)
TRAINING_ENDPOINT = os.environ.get("CUSTOMVISION_TRAINING_ENDPOINT")
TRAINING_KEY      = os.environ.get("CUSTOMVISION_TRAINING_KEY")
PREDICTION_ENDPOINT = os.environ.get("CUSTOMVISION_PREDICTION_ENDPOINT")
PREDICTION_KEY      = os.environ.get("CUSTOMVISION_PREDICTION_KEY")
PREDICTION_RESOURCE_ID = os.environ.get("CUSTOMVISION_PREDICTION_RESOURCE_ID")
# Resource ID format: /subscriptions/xxx/resourceGroups/xxx/providers/...
# Needed when publishing an Iteration — links the model to the prediction resource

# msrest credential wraps the key — different from AzureKeyCredential used elsewhere
training_credentials   = ApiKeyCredentials(in_headers={"Training-key": TRAINING_KEY})
prediction_credentials = ApiKeyCredentials(in_headers={"Prediction-key": PREDICTION_KEY})

# Training client: create projects, upload images, train, evaluate
trainer = CustomVisionTrainingClient(TRAINING_ENDPOINT, training_credentials)
# Prediction client: make predictions against published models
predictor = CustomVisionPredictionClient(PREDICTION_ENDPOINT, prediction_credentials)


# ══════════════════════════════════════════════════════════════════════
# PART 1 — TRAINING: Create project, add tags, upload images, train
# ══════════════════════════════════════════════════════════════════════

# Get the list of available domains — you must use a domain ID, not a name
# Domains: General, Food, Landmarks, Retail, General (compact), etc.
domains = trainer.get_domains()
# Find the General classification domain
general_domain = next(d for d in domains if d.name == "General" and d.type == "Classification")

# Create a new Classification project
# classification_type: "Multiclass" (one tag per image) or "Multilabel" (many tags per image)
project = trainer.create_project(
    name                = "plant-disease-classifier",
    description         = "Classifies plant leaves as Healthy or Diseased",
    domain_id           = general_domain.id,
    classification_type = "Multiclass"   # Each plant is either Healthy OR Diseased
)
project_id = project.id
print(f"Created project: {project.name} (ID: {project_id})")


# ── Create Tags (categories) ───────────────────────────────────────────
# Tags are the categories the model will learn to recognise
# You must create tags BEFORE uploading images
healthy_tag  = trainer.create_tag(project_id, "Healthy")
diseased_tag = trainer.create_tag(project_id, "Diseased")
# Each tag gets a unique ID — use the ID when uploading images
print(f"Created tags: {healthy_tag.name} (ID: {healthy_tag.id}), {diseased_tag.name}")


# ── Upload images with tags ────────────────────────────────────────────
# Build a batch — more efficient than uploading one image at a time
# Batch size limit: 64 images per call
upload_batch = []

healthy_images = ["healthy_leaf_1.jpg", "healthy_leaf_2.jpg", "healthy_leaf_3.jpg"]
for image_path in healthy_images:
    with open(image_path, "rb") as f:
        image_bytes = f.read()  # Read the image as binary
    # ImageFileCreateEntry: wraps one image + its tags for the batch
    # tag_ids: a list of tag IDs — for multiclass, use exactly one tag
    upload_batch.append(ImageFileCreateEntry(
        name    = image_path,
        contents= image_bytes,
        tag_ids = [healthy_tag.id]   # This image belongs to the Healthy category
    ))

diseased_images = ["diseased_leaf_1.jpg", "diseased_leaf_2.jpg", "diseased_leaf_3.jpg"]
for image_path in diseased_images:
    with open(image_path, "rb") as f:
        image_bytes = f.read()
    upload_batch.append(ImageFileCreateEntry(
        name    = image_path,
        contents= image_bytes,
        tag_ids = [diseased_tag.id]  # This image belongs to the Diseased category
    ))

# Upload the entire batch at once — more efficient than one-by-one
upload_result = trainer.create_images_from_files(
    project_id,
    ImageFileCreateBatch(images=upload_batch)  # Wrap the list in a batch object
)

# Check if all images were successfully uploaded
if not upload_result.is_batch_successful:
    # Some images may have failed (unsupported format, duplicate, too small)
    for img in upload_result.images:
        if img.status != "OK":
            print(f"Failed: {img.source_url} — {img.status}")
else:
    print(f"Uploaded {len(upload_batch)} images successfully.")


# ── Train the model ────────────────────────────────────────────────────
# train_project() starts training and returns an Iteration object
# training_type: "Regular" (Quick Training) — for faster/smaller models
# For Advanced Training: training_type="Advanced", budget_in_hours=1 (minimum: 1 hour)
iteration = trainer.train_project(project_id)
print(f"\nTraining started — Iteration: {iteration.id}")

# Training is async — poll until status changes from "Training" to "Completed"
while iteration.status == "Training":
    print("  Still training...")
    time.sleep(10)  # Wait 10 seconds before checking again
    # get_iteration() refreshes the iteration status from the server
    iteration = trainer.get_iteration(project_id, iteration.id)

print(f"Training complete! Status: {iteration.status}")


# ── Evaluate metrics ───────────────────────────────────────────────────
# get_iteration_performance() returns the evaluation metrics for a trained iteration
performance = trainer.get_iteration_performance(
    project_id,
    iteration.id,
    threshold = 0.5   # Probability threshold: only count predictions above 50%
)

# Overall model metrics
print(f"\n=== Model Performance (threshold=50%) ===")
print(f"  Precision: {performance.precision:.1%}")   # :.1% formats 0.923 as "92.3%"
print(f"  Recall:    {performance.recall:.1%}")
print(f"  mAP:       {performance.average_precision:.1%}")  # Mean Average Precision

# Per-tag metrics — see which tags are weak
print("\nPer-tag metrics:")
for tag_perf in performance.per_tag_performance:
    print(f"  {tag_perf.name}: precision={tag_perf.precision:.1%}  recall={tag_perf.recall:.1%}")
    # If Healthy is 95% precision but Diseased is only 60% precision,
    # you need more / better Diseased training images


# ── Publish the Iteration ──────────────────────────────────────────────
# After evaluating, publish the iteration so external apps can use it
# publish_name: a human-readable name for this published model
# prediction_id: the Azure resource ID of your prediction resource
trainer.publish_iteration(
    project_id,
    iteration.id,
    publish_name      = "plant-classifier-v1",
    prediction_id     = PREDICTION_RESOURCE_ID
)
print("\nIteration published as 'plant-classifier-v1'")


# ══════════════════════════════════════════════════════════════════════
# PART 2 — PREDICTION: Call the published model with new images
# ══════════════════════════════════════════════════════════════════════

# ── Classification prediction ──────────────────────────────────────────
with open("new_leaf.jpg", "rb") as f:
    test_image = f.read()

# classify_image(): makes a classification prediction with a local image
# project_id: which project this image belongs to
# published_name: the name you gave when publishing the iteration above
# image_data: the image bytes
results = predictor.classify_image(
    project_id     = project_id,
    published_name = "plant-classifier-v1",  # Must match what you used in publish_iteration()
    image_data     = test_image
)

# results.predictions: list of all tags with their probability scores
# Sorted by probability descending — first item is the most likely prediction
print("\n=== Classification Predictions ===")
for prediction in results.predictions:
    # prediction.tag_name: the label — prediction.probability: confidence (0.0 to 1.0)
    print(f"  {prediction.tag_name}: {prediction.probability:.1%}")

# Best prediction: use the first result (highest probability)
best = results.predictions[0]
if best.probability >= 0.7:       # Our custom threshold: only act if 70%+ confident
    print(f"\nDiagnosis: {best.tag_name} ({best.probability:.1%} confidence)")
else:
    print("\nLow confidence — flag for human review")


# ── URL-based prediction (no local file needed) ─────────────────────────
# classify_image_with_no_store() for predictions that don't store the image
# classify_image_url() for URL-based images
from azure.cognitiveservices.vision.customvision.prediction.models import ImageUrl

url_results = predictor.classify_image_url(
    project_id     = project_id,
    published_name = "plant-classifier-v1",
    image_url      = ImageUrl(url="https://example.com/leaf-photo.jpg")
)
print(f"\nURL prediction: {url_results.predictions[0].tag_name} — {url_results.predictions[0].probability:.1%}")

Python — Object Detection prediction (with bounding boxes)

# Object detection prediction uses detect_image() instead of classify_image()
# Returns both tag names AND bounding box coordinates for each detected object

# Assume you have an object detection project and published iteration
with open("warehouse_photo.jpg", "rb") as f:
    detect_image = f.read()

# detect_image(): same structure as classify_image() but returns bounding boxes
detect_results = predictor.detect_image(
    project_id     = project_id,
    published_name = "defect-detector-v1",
    image_data     = detect_image
)

THRESHOLD = 0.6  # Only report detections above 60% confidence

print("\n=== Object Detection Results ===")
for prediction in detect_results.predictions:
    # Filter out low-confidence predictions using our threshold
    if prediction.probability < THRESHOLD:
        continue   # skip this prediction — not confident enough

    # prediction.bounding_box: NORMALISED coordinates (0.0 to 1.0, not pixels)
    # Multiply by image width/height to get pixel coordinates
    bb = prediction.bounding_box
    # bb.left: x position of left edge as a fraction of image width
    # bb.top:  y position of top edge as a fraction of image height
    # bb.width: width as a fraction of total image width
    # bb.height: height as a fraction of total image height
    print(f"  {prediction.tag_name}: {prediction.probability:.1%}")
    print(f"    Box: left={bb.left:.2f} top={bb.top:.2f} w={bb.width:.2f} h={bb.height:.2f}")
    # Example to convert to pixel coordinates (assuming 1920x1080 image):
    # pixel_x = int(bb.left * 1920)  → left edge in pixels
    # pixel_y = int(bb.top  * 1080)  → top edge in pixels

Custom Vision — what the exam tests

⚠️
Top 6 Tested Facts

1. Classification vs Object Detection decision — "Categorise the whole image" → Classification. "Find WHERE objects are + draw boxes" → Object Detection. This is the most common project type selection question.

2. Precision/Recall/threshold tradeoff — Raising threshold improves precision but hurts recall. Lowering threshold improves recall but hurts precision. Know which direction each scenario needs.

3. mAP is the object detection metric; Precision+Recall for classification — Don't mix them up. mAP = Mean Average Precision, specifically for detection. Classification uses Precision and Recall directly.

4. Minimum 5 images per tag — The minimum to even start training. 15+ is recommended. Always check if a scenario mentions insufficient training data.

5. Training vs Prediction: separate endpoints and keys — Custom Vision has TWO endpoints (training and prediction) with different keys. Training creates/manages the model; Prediction makes predictions. A common code question tests if you know which client and key to use for predictions.

6. Object detection bounding boxes are normalised (0.0–1.0) — Not in pixels. Multiply by the image dimensions to convert to pixels. Classification doesn't use bounding boxes at all.

Test your understanding

Topic 2 — Custom Vision Models 1 / 5
Scenario: A supermarket wants to build a system that analyses images taken at checkout to identify all the products on the conveyor belt — including their positions in the image — so they can automatically scan and charge for each item.

Which Custom Vision project type should be used and why?

💡 Explanation

C is correct. The key phrase is "identify products including their positions in the image." Object Detection provides bounding boxes for each detected product — telling the system exactly where each item is, what it is, and how many there are. Multi-label Classification (B/D) could assign multiple product labels to the whole image but would not differentiate between two items of the same product type or provide locations. Without bounding boxes, you can't count individual items reliably or distinguish which product is where.

Scenario: A pharmaceutical manufacturing company uses Custom Vision to detect defective pills on a production line. The model currently has Precision: 92% and Recall: 65%. The quality team receives many rejected pills that were actually fine (false positives are acceptable) but is extremely concerned that some defective pills are being missed (false negatives must be minimised).

What adjustment should be made to improve this situation?

💡 Explanation

B is correct. The problem is 65% Recall — 35% of defective pills are being missed. In a safety-critical context, missing defects (false negatives) is far worse than false alarms (false positives). Lowering the probability threshold means the model reports a defect even when it's less certain — catching more real defects (higher recall) at the cost of more false alarms (lower precision). The quality team can manually review the additional rejected pills (false positives are acceptable per the scenario). Raising the threshold (A) would do the opposite — fewer detections, even lower recall. Advanced Training (C) may improve overall accuracy but doesn't specifically target recall.

In Azure Custom Vision, what is the difference between a Training Key and a Prediction Key, and when is each used?

💡 Explanation

B is correct. Custom Vision has completely separate endpoints and keys for training and prediction. The Training Key authenticates operations that modify the project: uploading images, creating tags, training models, publishing iterations, and viewing metrics — all done during development. The Prediction Key authenticates the production prediction calls made by your application (classify_image(), detect_image()) — used at runtime when actual images are analysed. Using the wrong key causes authentication errors. This separation is a security feature — your production app only needs the prediction key, not the ability to modify the training project.

After training, a Custom Vision object detection model shows the following metrics: Precision: 88%, Recall: 82%, mAP: 76%. A developer is confused about what mAP measures. Which statement correctly describes mAP in this context?

💡 Explanation

C is correct. mAP (Mean Average Precision) is the standard summary metric for object detection. It is NOT a simple average of the reported Precision and Recall. mAP calculates the area under the precision-recall curve across multiple probability thresholds, then averages this across all object classes, and also accounts for how accurately the bounding boxes overlap with the true object boundaries (IoU — Intersection over Union). This is why mAP (76%) can be lower than the per-point Precision and Recall — it's a more rigorous combined measure. Classification models use Precision and Recall; detection models use mAP.

Scenario: A team uploads 3 training images tagged as "Apple" and 3 images tagged as "Orange", then tries to train a Custom Vision classification model. Training fails with an error saying the project doesn't meet the training requirements.

What is the most likely cause and how should it be fixed?

💡 Explanation

C is correct. Azure Custom Vision requires a minimum of 5 training images per tag before training can proceed. With only 3 images tagged as "Apple" and 3 as "Orange", the project does not meet this minimum and training will fail. The fix is to upload at least 2 more images for each tag (reaching the minimum of 5). For better accuracy, 15+ images per tag is recommended. A negative tag (D) exists as a concept but is not a hard requirement — it's used when you want the model to learn what doesn't match any category. Image size constraints (A) are a separate issue. The tag count (B) is fine.

Mark this topic as complete