Ultralytics YOLO27: Встать в лист ожидания.
Get Started
За пределами детекции: сегментация, поза, OBB и классификация·Классифицируй, объединяй, отправляй·Урок 8/10
Уроксредний

Обучи классификатор изображений

Подготовь датасет в виде папок, обучи yolo26n-cls.pt на MNIST160, считай вероятности и точность top-1, а предсказания с низкой уверенностью направляй на проверку человеком.

Классификация изображений отвечает на один вопрос для каждого изображения: что на нём? Для неё нужны самые простые метки среди всех задач — названия папок, — и она работает быстрее всего. Поэтому это стандартный второй этап после детектора и подходящая первая модель для станции контроля, которая видит одну деталь в кадре. В датасете не нужен YAML: его роль выполняет структура папок. Классификатор всегда выдаёт ответ, даже для изображений, на которые он никогда раньше не смотрел. Понимание того, когда ему не стоит доверять, — половина задачи.

Результат

Обучи yolo26n-cls.pt на MNIST160, укажи точность top-1, считай вероятности классов из result.probs и выбери порог уверенности, при котором сомнительные изображения отправляются на проверку.

Ускоренное прохождение
Если ты уже освоился, вот краткая версия.
  1. Помести изображения в train/<class>/ и val/<class>/; названия папок станут названиями классов и будут отсортированы по алфавиту.

  2. yolo classify train data=mnist160 model=yolo26n-cls.pt epochs=1 imgsz=64.

  3. Ориентируйся на точность top-1 (metrics.top1); если классов не больше пяти, top-5 всегда равна 1.0.

  4. Считай result.probs.top1 и .top1conf и направляй предсказания с уверенностью ниже порога человеку.

Практические занятия

Структура папок датасета#

Классификация изображений Ultralytics YOLO: три листа с метками healthy, rust и powdery mildew; для каждого указана уверенность

Датасеты для классификации — это каталоги, а не YAML-файлы. Укажи в data= путь к корневой папке:

parts/
├── train/
│   ├── good/      img_0001.jpg ...
│   ├── scratch/   img_0412.jpg ...
│   └── dent/      img_0733.jpg ...
└── val/
    ├── good/
    ├── scratch/
    └── dent/

Что загрузчик с ней делает — согласно установленному исходному коду и руководству по датасетам для классификации:

СитуацияПоведение
Индексы классовназвания папок в train/, отсортированные по алфавиту → dent=0, good=1, scratch=2
Папка для валидацииval/, затем validation/, затем valid/; если они отсутствуют, используется test/ с предупреждением
Папки train/ нет, есть только папки классовавтоматически делит данные в пропорции 80/20 на train/ и val/

На платформе Ultralytics датасет для классификации содержит одну метку на уровне изображения для каждого изображения, а не фигуры:

Датасет для классификации на платформе Ultralytics: конвейер с белыми коробками и единственной меткой уровня изображения class2, выбранной на панели классов

Новая папка меняет нумерацию классов

Симптом: после добавления класса старая интеграция сообщает, что части good — это dent. Причина: добавление папки с именем bent ставит её первой при сортировке и сдвигает все последующие индексы. Решение: всегда сопоставляй индексы с именами с помощью model.names (или result.names); никогда не задавай индекс жёстко.

Обучение на MNIST160#

MNIST160 содержит 160 рукописных цифр: 80 в train/ и 80 в test/; они распределены по десяти папкам с цифрами — этого достаточно для быстрой проверки на CPU. Папки val/ нет, поэтому обрати внимание на предупреждение загрузчика и переход к test/:

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=1, imgsz=64, device="cpu")
metrics = model.val()
print(f"top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
WARNING ⚠️ Dataset 'split=val' not found, using 'split=test' instead.
val: .../datasets/mnist160/test... found 80 images in 10 classes ✅
                   all      0.162      0.463
top-1: 0.162  top-5: 0.463

Одна эпоха почти не меняет новую голову для десяти классов. В нашем запуске epochs=10 достигла top-1 0.55 и top-5 0.95 примерно за десять секунд на CPU ноутбука — цикл работает, просто ему нужно больше эпох. В руководстве по задаче классификации перечислены размеры предварительно обученных моделей.

Top-1 — доля изображений, для которых класс с наибольшей вероятностью указан правильно. Top-5 засчитывает правильный ответ, если нужный класс входит в пятёрку классов с наибольшими вероятностями. Top-5 полезна для ImageNet с 1 000 классов, от общих до очень узких категорий; при пяти классах или меньше её значение всегда равно 1.0, а при десяти она малоинформативна. Ориентируйся на точность top-1 и матрицу ошибок по классам.

Примеры изображений ImageNet, расположенные от общих до узких категорий, например от млекопитающего до хаски и от транспортного средства до тримарана

Считывание вероятностей в коде#

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")  # ImageNet, 1,000 classes
result = model("https://ultralytics.com/images/bus.jpg")[0]

probs = result.probs
print(f"top-1: {result.names[probs.top1]} ({float(probs.top1conf):.2f})")
for i, conf in zip(probs.top5, probs.top5conf.tolist()):
    print(f"  {result.names[i]:20s} {conf:.3f}")
top-1: minibus (0.52)
  minibus              0.516
  police_van           0.369
  trolleybus           0.096
  minivan              0.006
  ambulance            0.003

probs.top1 — это индекс, probs.top5 — список индексов, а probs.data — полный вектор вероятностей. Обрати внимание на уверенность top-1: значение 0.52 лишь немного выше шанса угадать, подбросив монету между minibus и police_van. Именно на основе этого числа ты выбираешь маршрут.

Направляй сомнительные предсказания человеку#

Классификатору в рабочей системе редко нужно правильно классифицировать каждое изображение; важно, чтобы он правильно классифицировал изображения, по которым принимает решение самостоятельно. Этот скрипт обучает модель 10 эпох, а затем оценивает, как разные пороги уверенности повлияют на 80 отложенных изображений цифр:

from pathlib import Path

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=10, imgsz=64, device="cpu", plots=False)  # model now holds best.pt
print(model.names)  # folder names, sorted, became class indices

test = Path(model.trainer.data["test"])  # mnist160/test, the split the loader validated on
images = sorted(test.rglob("*.png"))
results = model(images, imgsz=64, verbose=False)
truth = [p.parent.name for p in images]

for threshold in (0.0, 0.3, 0.6):
    kept = [(r, t) for r, t in zip(results, truth) if r.probs.top1conf >= threshold]
    correct = sum(r.names[r.probs.top1] == t for r, t in kept)
    print(f"threshold {threshold:.1f}: auto-accept {len(kept):2d}/{len(images)}, accuracy on accepted {correct / max(len(kept), 1):.0%}")
{0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'}
threshold 0.0: auto-accept 80/80, accuracy on accepted 55%
threshold 0.3: auto-accept 41/80, accuracy on accepted 68%
threshold 0.6: auto-accept 10/80, accuracy on accepted 80%

Каждая строка отражает компромисс: повышение порога увеличивает точность принятых предсказаний и отправляет больше изображений человеку. При пороге 0.6 эта модель самостоятельно обрабатывает только 10 из 80 цифр — она ещё не готова что-либо автоматизировать; поможет дополнительное обучение, а не более хитрый порог. Повторяй ту же проверку после каждого цикла обучения: она покажет и ожидаемую точность, и точный объём работы на проверке при каждом пороге.

Правило выбора

Выбери самый низкий порог, при котором точность на принимаемых изображениях соответствует твоим требованиям, измерив её на отложенных данных. Затем проверь, справится ли команда с объёмом проверки, который он создаёт; если нет — улучши модель.

Подводные камни#

Нет класса «ничего из перечисленного»

Симптом: пустая рабочая станция, неподходящая деталь или рука перед объективом получают уверенную метку класса. Причина: вероятности softmax всегда в сумме дают 1, поэтому классификатор неизменно выбирает один из своих классов. Решение: добавь папку other/, содержащую только такие изображения, и отправляй предсказания с низким top1conf на проверку.

Валидация предобученной головы на новых папках

Симптом: до обучения предобученная модель почти не набирает баллов на твоём датасете. Причина: yolo26n-cls.pt предсказывает 1 000 индексов ImageNet, а в твоих папках индексам 0–9 соответствуют другие значения: в MNIST160 папка 0 имеет индекс 0, которому предобученная голова присваивает значение tench. В таком случае мы измерили top-1 0.000. Решение: сначала выполни дообучение, затем проводи валидацию.

Утечка между папками

Симптом: top-1 при валидации близок к 1.0, а в рабочей среде результат намного хуже. Причина: почти одинаковые фотографии одной и той же детали есть и в train/, и в val/. Решение: раздели данные по деталям или сеансам, как описано в статье Как правильно разделить датасет.

Классификаторы предварительно обучены для размера imgsz=224, а не 640. Если на большом кадре есть маленькие объекты, лучше использовать детекцию с последующей классификацией (следующий урок), чем увеличивать imgsz.

Попробовать

Обучи yolo26n-cls.pt на mnist160 в течение 1 эпохи, а затем 10 эпох при imgsz=64, и запиши top-1 и top-5 для обоих запусков. Выведи model.names, чтобы проверить, как названия папок превратились в индексы классов. Затем запусти route.py и выбери порог, который ты использовал бы в рабочей системе при требовании не менее 75% точности на автоматически принятых изображениях.

Критерии готовности
Урок считается завершённым, если выполнены все условия ниже.
  • У тебя есть значения top-1 и top-5 для запусков на 1 и 10 эпох.

  • Ты вывел model.names и можешь объяснить правило присвоения индексов по алфавиту и почему top-5 бесполезна при пяти классах.

  • Ты указал порог, точность на принятых изображениях и количество из 80 изображений, отправляемых на проверку.

Показать решение
from ultralytics import YOLO

for epochs in (1, 10):
    model = YOLO("yolo26n-cls.pt")
    model.train(data="mnist160", epochs=epochs, imgsz=64, device="cpu")
    metrics = model.val()
    print(f"{epochs:2d} epochs  top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
print(model.names)  # {0: '0', 1: '1', ..., 9: '9'}

Результаты наших запусков: 1 эпоха → top-1 0.162, top-5 0.463; 10 эпох → top-1 0.550, top-5 0.950. При пяти классах правильный ответ всегда входит в топ-5, поэтому top-5 по определению равна 1.0 и ничего не говорит о качестве модели.

Для точности 75% на принятых изображениях route.py рекомендует порог 0.6 (точность 80% на 10 принятых изображениях); при этом 70 из 80 цифр отправляются на проверку. Вот честный ответ: порог соответствует требованию к точности, но модель пока бесполезна. Прежде чем выбирать порог для реальной задачи, обучи модель дольше и повтори проверку.

Что дальше

Дальше мы объединим задачи в цепочки: детектор будет передавать данные классификатору, а модель позы — трекеру.

Войди, чтобы сохранять прогресс