Обучение моделей на объёмах, облаках точек и видео
Обучите 3D U-Net на нарисованных масках КТ, запустите собственный детектор OpenPCDet на кубоидах лидара и обучите трекер видео, с оценкой каждой модели на отложенных объектах.
Новое в v2.14.0
Этот учебник обучает три модели на трёхмерных и движущихся данных по собственной разметке исследования в Potato. volume-unet обучает 3D U-Net, которая сегментирует объёмы КТ и МРТ, external-3d запускает детектор для лидара, который вы собираете и запускаете сами, а video-track обучает трекер объектов на видео. Страница предназначена для команд, которые уже размечают объёмы, облака точек или видео в Potato и хотят, чтобы модель разметила предварительными метками остальное. Для каждого тренера на странице описаны схема, которая даёт обучающие данные, конфигурация, проходящая potato validate, что делает запуск, оценка на отложенных объектах и то, как предсказания попадают к разметчикам. Учебник по первой модели проходит страницу Training на примере текстового классификатора, которому GPU не нужен, и этот учебник опирается на то прохождение.
Сводка по тренерам
Все три тренера обучают одну схему за запуск, оценивают модель на отложенных объектах val и test этого запуска и записывают предсказания обратно как предварительные метки, которые разметчики правят. volume-unet и детекторы за external-3d на практике требуют GPU, поэтому конфигурации для этих двух тренеров отправляют запуски на GPU-сервер по ssh, как настраивает учебник по удалённому обучению.
| Тренер | Схема | Объекты | Установка на машине, которая обучает | Главная оценка |
|---|---|---|---|---|
volume-unet | volume_annotation (нарисованные маски) | объёмы NIfTI, серии DICOM | pip install "potato-annotation[train-vision,volumes]"; на практике нужен GPU | mean_dice |
external-3d | spatial_annotation (кубоиды) | облака точек | ваш собственный детектор (OpenPCDet, MMDetection3D) | map_3d_50 |
video-track | video_annotation в режиме tracking или combined | видео | pip install "potato-annotation[train-vision]" opencv-python | idf1 |
Каждый тренер читает медиа из файлов на диске. Путь к медиа должен указывать внутрь папки задачи или media_directory, а объекты, у которых медиа задано URL с http(s), исключаются из обучения и предсказания.
Сегментация объёмов КТ и МРТ с volume-unet
volume-unet учится на масках, которые разметчики рисуют в схеме volume_annotation, и рисует маски на объёмах, которые никто ещё не сегментировал. Схема показывает файл NIfTI или серию DICOM в трёх связанных проекциях (аксиальной, корональной и сагиттальной), а разметчики рисуют каждую метку кистью или замыкая многоугольник. Поле объекта, которое называет source_field, содержит файл .nii или .nii.gz либо папку с одной серией DICOM относительно media_directory. У рамки, нарисованной на срезе, нет маски, поэтому ответ, состоящий только из рамок, не служит обучающей целью. Potato это исследовательское программное обеспечение без клинического или нормативного разрешения, а обученная здесь модель предлагает маски, которые разметчики исправляют.
Конфигурация ниже сегментирует селезёнку и печень, обучает на GPU-сервере и размечает предварительными метками каждый объём вне выборки train:
annotation_task_name: "Spleen segmentation"
task_dir: .
media_directory: media
data_files:
- data/scans.jsonl
item_properties:
id_key: id
text_key: volume
output_annotation_dir: annotation_output
user_config:
allow_all_users: true
annotation_schemes:
- annotation_type: volume_annotation
name: organs
description: "Paint the spleen and the liver on every slice where each organ appears."
source_field: volume
labels:
- {name: Spleen, color: "#e6553a", key_value: "1"}
- {name: Liver, color: "#3a8ee6", key_value: "2"}
tools: [brush, eraser, polygon]
default_window: Soft tissue
model_training:
enabled: true
backend: gpu-server
backends:
gpu-server:
type: ssh
host: gpu.example.edu
python: /opt/conda/bin/python3
gpus: auto
min_free_gpu_mb: 8000
trainer_params:
volume-unet:
patch: 96
writeback:
schemas: [organs]
min_confidence: 0.6
include_unlabeled: trueКаждая строка данных называет один объём:
{"id": "case_001", "volume": "case_001.nii.gz"}
{"id": "case_002", "volume": "case_002_ct"}Значение patch задаёт сторону кубического фрагмента, на котором обучается сеть, и при трёх уровнях по умолчанию должно быть кратно 4 (2 в степени levels минус 1). Фрагмент в 96 вокселей даёт сети больше контекста вокруг каждого вокселя, чем 64 по умолчанию, и требует больше памяти GPU. При min_free_gpu_mb: 8000 Potato начинает запуск только на GPU, у которого свободно не менее 8000 МБ. Potato берёт среднюю вероятность модели внутри маски каждой нарисованной метки и сообщает наименьшее из этих средних как уверенность для объёма. При min_confidence: 0.6 объём, в котором любой один орган нарисован со средней вероятностью ниже 0.6, не получает предварительной метки.
Шаги обучения в запуске volume-unet
Запуск читает каждый размеченный объём в том порядке вокселей, в котором рисует виджет, и превращает маски в одну метку на воксель. Там, где две метки перекрываются, побеждает та, что позже в порядке меток набора, как при экспорте NIfTI. Интенсивности обрезаются по 0.5 и 99.5 процентилям каждого объёма и стандартизируются. Сеть это 3D U-Net на чистом torch (три уровня, instance norm), обученная с кросс-энтропией и мягким Dice на 100 случайных фрагментах за эпоху. Половина фрагментов центрируется на нарисованном вокселе, поэтому малые структуры попадают в обучение. После каждой эпохи запуск оценивает средний Dice на объёмах val и сохраняет лучшую эпоху, а останавливается после 10 эпох без улучшения оценки. Предсказание скользит фрагментом по всему объёму с перекрытием в половину фрагмента и усредняет вероятности.
В выборке train должно быть не менее трёх размеченных объёмов (min_instances). Объём, у которого нарисованная форма отличается от формы файла, исключается с предупреждением в журнале запуска, как и объём, файл которого отсутствует. Если один объём размечают несколько читателей, запуск обучается на масках того одного читателя, которого выбирает aggregation, потому что агрегаторы сравнивают ответы целиком. Сегментация объёмов КТ несколькими читателями и STAPLE рассматривает согласованность читателей и консенсусную маску, построенную вне Potato.
Оценка на отложенных объектах для объёмов
Potato вычисляет коэффициент Дайса для каждой метки на каждом отложенном объёме (dice_<label>) и сообщает среднее по всем объёмам и меткам как mean_dice. Метка, которую модель не поставила на объёме, где она есть, получает 0. Тренер выбирает лучшую эпоху по val, поэтому таблица Runs берёт главную оценку из test (test_mean_dice). Полный список оценок по меткам есть на странице запуска.
Предварительные метки-маски на срезах
Предсказанные маски возвращаются в собственной форме виджета, по меткам и аксиальным срезам, с пометкой prefilled_from: model, и появляются на срезах, когда разметчик открывает объём. Разметчик исправляет маски кистью и ластиком, а исправленные маски сохраняются как ответ этого разметчика. Объекты из выборки train в запуске никогда не получают предварительной метки. Без include_unlabeled запуск предсказывает только на объёмах val и test этого запуска.
Обнаружение 3D-кубоидов с external-3d
external-3d передаёт размеченные сканы 3D-детектору, который вы устанавливаете на машине обучения, запускает ваши команды обучения и предсказания и читает предсказанные рамки обратно как кубоиды. Potato не включает детектор облаков точек. Два детектора, для которых создан тренер, OpenPCDet и MMDetection3D, требуют операторов CUDA, скомпилированных на GPU-машине. Со стороны Potato тренеру нужен только numpy.
Обучающие данные берутся из схемы spatial_annotation с инструментом cuboid_3d. Разметчики ставят ориентированные рамки на сканы PCD, PLY, LAS или KITTI .bin, которые хранятся в метрах в собственной системе координат скана (см. 3D-облака точек и кубоиды). Целями служат только кубоиды, поэтому точки, ломаные и сегменты точек в той же схеме игнорируются. Каждый объект это один скан, а последовательность кадров с идентификаторами треков не служит обучающей целью.
Папка набора данных, которую Potato пишет для детектора
Перед запуском команды обучения Potato записывает сканы в {dataset}, папку dataset внутри папки запуска, в формате пользовательского набора данных OpenPCDet:
points/<id>.npyхранит один скан как строки float32 видаx y z intensity, с интенсивностью 0, если в файле её нет.labels/<id>.txtхранит по одной строке на кубоид для скановtrainиvalв видеx y z dx dy dz heading label. Угол поворота это вращение вокруг +z в радианах, а тангаж и крен не сохраняются. Пробел в названии метки становится подчёркиванием.ImageSets/train.txt,val.txtиpredict.txtперечисляют идентификаторы сканов.predict.txtперечисляет сканыvalиtest, а также неразмеченные сканы, когда включёнwriteback.include_unlabeled.
Каждый <id> это первые 16 шестнадцатеричных символов SHA-1 от идентификатора объекта, потому что идентификаторы объектов могут содержать символы, недопустимые в имени файла. Метки сканов test никогда не попадают в папку. Potato записывает геореференцированный скан относительно начала скана, потому что геореференцированные координаты слишком велики для float32, и сдвигает предсказанные рамки обратно на то же смещение.
Файлы предсказаний, которые Potato читает у детектора
Команда предсказания пишет по одному файлу на скан в {predictions}/<id>.txt, по одной рамке на строку, в виде x y z dx dy dz heading label score. Оценка необязательна. Potato исключает строку, которая не разбирается, содержит nan или inf, называет метку, которой нет в схеме, или описывает рамку без размера, а журнал запуска считает исключённые строки. Остальные рамки возвращаются как объекты cuboid_3d с пометкой source: model, а оценка каждой рамки становится уверенностью этой рамки. Potato принимает за уверенность скана наименьшую оценку рамки на скане, и min_confidence применяется к этому значению.
Команды обучения и предсказания
Обе команды задаются как параметры тренера. Potato разбивает каждую команду на слова так, как это сделала бы оболочка, заменяет {dataset}, {workdir} (папка запуска) и {predictions} соответствующими путями и запускает команду в папке запуска без оболочки. Другие фигурные скобки в команде остаются как написаны. Конвейеры, && и $VARIABLES доходят до программы как буквальные слова, поэтому многошаговую работу следует вынести в скрипт. Добавьте этот блок в конфигурацию исследования с облаками точек:
model_training:
enabled: true
backend: gpu-server
backends:
gpu-server:
type: ssh
host: gpu.example.edu
python: /opt/conda/envs/pcdet/bin/python
gpus: auto
trainer_params:
external-3d:
train_command: "/opt/pcdet-jobs/train.sh {dataset} {workdir}"
predict_command: "/opt/conda/envs/pcdet/bin/python /opt/pcdet-jobs/predict.py {dataset} {workdir} {predictions}"
timeout_s: 43200
writeback:
schemas: [objects]
min_confidence: 0.3
include_unlabeled: trueПути указаны на машине обучения, где находится и папка запуска. timeout_s ограничивает каждую команду, и 12 часов здесь заменяют значение по умолчанию в 24. Команда обучения, завершившаяся с ненулевым кодом, прерывает запуск ошибкой, и в ошибке запуска показаны последние строки вывода команды.
train.sh это ваш собственный скрипт. Для OpenPCDet скрипт направляет конфигурацию пользовательского набора данных OpenPCDet на {dataset}, строит файлы info командой python -m pcdet.datasets.custom.custom_dataset create_custom_infos <dataset config>, запускает обучающий скрипт OpenPCDet и сохраняет контрольную точку в {workdir}. Названия классов в конфигурации детектора должны совпадать с названиями меток, как они записаны в labels/. Руководство по пользовательским наборам данных MMDetection3D читает облака точек только как файлы .bin, поэтому для MMDetection3D скрипт сначала записывает каждый points/<id>.npy как файл .bin. Оставьте train_command пустым, чтобы пропустить обучение, и тогда predict_command запустит модель, которую вы обучили в другом месте.
predict.py читает идентификаторы из predict.txt, запускает детектор на каждом скане и пишет рамки. Функции load_model и detect это ваш собственный код вокруг детектора, который вы обучили:
"""predict.py DATASET WORKDIR PREDICTIONS: one line per box, one file per scan."""
import os
import sys
import numpy as np
from my_detector import load_model, detect # your code around OpenPCDet or MMDetection3D
dataset, workdir, out = sys.argv[1:4]
model = load_model(os.path.join(workdir, "output")) # where train.sh saved the checkpoint
os.makedirs(out, exist_ok=True)
with open(os.path.join(dataset, "ImageSets", "predict.txt")) as fh:
scan_ids = fh.read().split()
for scan_id in scan_ids:
points = np.load(os.path.join(dataset, "points", scan_id + ".npy")) # float32, (N, 4)
with open(os.path.join(out, scan_id + ".txt"), "w") as fh:
for x, y, z, dx, dy, dz, heading, label, score in detect(model, points):
fh.write(f"{x} {y} {z} {dx} {dy} {dz} {heading} {label} {score}\n")Без train_command команда предсказания составляет весь запуск, и команда предсказания с ненулевым кодом завершения прерывает запуск ошибкой. Команда предсказания, которая завершается с кодом 0, но не пишет <id>.txt ни для одного скана или пишет только строки, которые Potato пропускает, тоже считается неудачной, потому что запуск, оценённый по такому выводу, выглядел бы как модель, не нашедшая ничего. Пустой файл означает скан без объектов. После команды обучения неудачная команда предсказания не прерывает запуск. Запуск завершается с предупреждением "Prediction pass failed after 0 prediction(s)" и последними строками вывода команды, а у запуска нет ни предсказаний, ни оценки на отложенных объектах. Когда завершённый запуск показывает "not scored", поищите это предупреждение в журнале запуска.
Оценка на отложенных объектах для кубоидов
Potato вычисляет 3D среднюю точность по каждой метке при объёмном IoU 0.5 (ap_3d_50_<label>). IoU точен для ориентированных кубоидов при любом повороте. Potato также сообщает среднее по меткам при IoU 0.25 и 0.5, а также точность, полноту и F1 при 0.5. Главная оценка это map_3d_50. При наличии train_command таблица Runs берёт главную оценку сначала из test, потому что команда обучения получает метки val в {dataset} и может выбирать контрольную точку по этим меткам. Без train_command модель никогда не видела объектов val исследования, и главная оценка берётся из val.
Предварительные метки-кубоиды в просмотрщике облаков точек
Предсказанные кубоиды появляются в просмотрщике облаков точек как редактируемые рамки, а изменённая рамка сохраняется как ответ разметчика. Potato проверяет каждый предсказанный объект до того, как просмотрщик его нарисует, и исключает объект, геометрия которого не разбирается, кубоид без размера и метку, которой нет в схеме. Как размечать 3D-облака точек рассматривает сторону разметки и согласованность по 3D-рамкам.
Отслеживание объектов на видео с video-track
video-track учится на треках, которые разметчики рисуют в схеме video_annotation в режиме tracking или combined, и предсказывает треки на видео, где их никто не отслеживал. Трек это метка и рамка на каждом ключевом кадре, а виджет интерполирует рамку между ключевыми кадрами. Схема video_annotation в другом режиме хранит сегменты, а не треки, и для такой схемы video-track не предлагается.
annotation_task_name: "Pedestrian tracking"
task_dir: .
media_directory: media
data_files:
- data/clips.jsonl
item_properties:
id_key: id
text_key: video
output_annotation_dir: annotation_output
user_config:
allow_all_users: true
annotation_schemes:
- annotation_type: video_annotation
name: tracks
description: "Track every pedestrian and cyclist with a box at each keyframe."
source_field: video
mode: tracking
video_fps: 25
labels:
- {name: pedestrian, color: "#FF6B6B"}
- {name: cyclist, color: "#45B7D1"}
tracking_options:
interpolation: linear
auto_advance_frames: 5
frame_stepping: true
model_training:
enabled: true
trainer_params:
video-track:
frame_fps: 2
predict_fps: 5
max_age: 4
writeback:
schemas: [tracks]
include_unlabeled: trueКаждая строка данных называет клип в media_directory:
{"id": "clip_001", "video": "/media/clip_001.mp4"}
{"id": "clip_002", "video": "/media/clip_002.mp4"}Задайте video_fps равным реальной частоте кадров клипов. Ключевые кадры нумеруются по кадрам, а тренер нумерует предсказанные ключевые кадры по video_fps схемы (30, если не задано). Тренер читает рамку на выбранном кадре по собственной настройке interpolation каждого трека (линейная, кубическая Катмулла-Рома или постоянная). Поэтому рамки, на которых учится модель, следуют траектории, которую видели разметчики. Как и в виджете, трек с одним ключевым кадром удерживается на каждом кадре, а трек, которому виджет задал startFrame и endFrame, идёт между этими двумя кадрами. Ключевой кадр вне этих двух кадров всё равно показывается на собственном кадре. predict_fps: 5 детектирует на пяти кадрах в секунду вместо двух. Пять кадров в секунду дают быстро движущемуся объекту больше ключевых кадров и стоят больше проходов детектора. Трек заканчивается после более чем max_age выбранных кадров без подходящего обнаружения, поэтому max_age: 4 при 5 кадрах в секунду позволяет треку пережить 0.8 секунды пропущенных обнаружений. Пешеход, который проходит за столбом, сохраняет один трек, если детектор находит пешехода снова в пределах 0.8 секунды. Конфигурация обучает на машине, где запущен Potato (бэкенд local), а бэкенд gpu-server из конфигурации для объёмов подходит и для video-track.
Обучение и связывание треков в запуске video-track
Запуск выбирает кадры из каждого размеченного видео с частотой frame_fps кадров в секунду, от начала до последнего ключевого кадра, и читает каждый выбранный кадр через OpenCV. Пиксели холста зависят от экрана разметчика, поэтому тренер читает рамку каждого трека на этом кадре как долю изображения. Выбранный кадр, на котором нет ни одного трека, остаётся в обучении как пример пустой сцены. Запуск дообучает torchvision Faster R-CNN на этих кадрах, с параметрами и значениями по умолчанию torchvision-detect. Детектор стартует с весов COCO из torchvision. После каждой эпохи запуск оценивает mAP рамок при IoU 0.5 на кадрах val (frame_map_50) и сохраняет лучшую эпоху.
Предсказание выбирает кадры из каждого видео с частотой predict_fps, детектирует на каждом выбранном кадре и связывает обнаружения в треки. Обнаружения каждого кадра сопоставляются один к одному с живыми треками той же метки по перекрытию с последней рамкой каждого трека, а для сопоставления нужен IoU не менее match_iou (0.3). Несопоставленное обнаружение с оценкой не менее new_track_score (0.5) начинает новый трек. Тренер отбрасывает предсказанный трек с числом обнаружений меньше min_track_keyframes (по умолчанию 2) до записи и оценки. Иначе виджет удерживал бы трек из одного обнаружения на каждом кадре. Каждое обнаружение становится ключевым кадром, поэтому виджет интерполирует между ключевыми кадрами модели так же, как между ключевыми кадрами разметчика.
В выборке train должно быть не менее двух размеченных видео (min_instances). Видео, которое OpenCV не может декодировать, исключается с предупреждением, называющим объект, и запуск продолжается с остальными.
Оценка на отложенных объектах для треков
Potato сравнивает предсказанные треки с размеченными треками на кадрах, выбранных по 2 в секунду на каждом отложенном видео, плюс на каждом размеченном ключевом кадре. Оценки это MOTA, IDF1, смены идентификаторов, пропуски и ложные срабатывания, причём рамка считается найденной при IoU 0.5. MOTA относит пропуски, ложные срабатывания и смены идентификаторов к числу размеченных рамок. IDF1 это F1 рамок, сопоставленных с правильным идентификатором на протяжении всего видео, и IDF1 служит главной оценкой (idf1). Тренер выбирает лучшую эпоху по val, поэтому таблица Runs берёт главную оценку из test.
Предварительные метки-треки в видеовиджете
Предсказанные треки заполняют видеовиджет как треки с ключевыми кадрами, помеченными как вывод ИИ и несущими оценку детектора. Виджет при рисовании треков переводит рамки в собственные пиксели холста, а разметчик исправляет треки как любые другие. Уверенность видео это средняя оценка ключевых кадров модели. В режиме combined предварительная метка содержит только треки, а сегменты добавляет разметчик. Как размечать отслеживание объектов на видео рассматривает сторону разметки, окклюзию и временную согласованность.
Начало запуска и проверка результата
Перезапустите сервер после добавления model_training, откройте /admin/training, выберите схему и тренер и нажмите Train now. Страница предлагает только тренеры, подходящие к каждой схеме, а тренер, который не установлен, показывает как "not installed" вместе с командой установки. Чтобы проверить GPU-сервер перед первым запуском, выполните:
potato training check gpu-server config.yamlПроверка сообщает версию Python на удалённой машине, каждый GPU с его свободной памятью, GPU, который получил бы запуск, и пакеты для обучения, которых нет в удалённом интерпретаторе. Запуск можно начать и через API, с параметрами для отдельного запуска, которые переопределяют trainer_params:
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
http://localhost:8000/admin/training/api/runs \
-d '{"trainer": "video-track", "schemas": ["tracks"], "params": {"predict_fps": 10}}'Когда запуск завершается, таблица Runs показывает оценку на отложенных объектах, а выборка и метрика указаны во всплывающей подсказке. Предварительные метки задают ориентир разметчикам, поэтому включайте include_unlabeled в новом исследовании только после того, как оценка на отложенных объектах оправдывает показ ответов модели. Учебник по модели в цикле рассматривает показ наименее уверенных предварительных меток первыми и переобучение на исправлениях, а Конфигурация обучения моделей и маршруты перечисляет все ключи, бэкенды и проверки обратной записи.
Дополнительные материалы
- Обучение моделей на вашей собственной разметке
- Тренеры моделей в Potato по задачам
- Учебник по моделям для изображений, для 2D-детекции и сегментации
- Отслеживание на видео и распространение масок
- Разметка видео
- Разметка медицинских изображений
- Консенсус масок со STAPLE
- Справочник в репозитории Potato по
volume-unet,external-3dиvideo-track
Литература
- Bernardin, K., and Stiefelhagen, R. (2008). Evaluating Multiple Object Tracking Performance: The CLEAR MOT Metrics. EURASIP Journal on Image and Video Processing. https://doi.org/10.1155/2008/246309
- Çiçek, Ö., Abdulkadir, A., Lienkamp, S. S., Brox, T., and Ronneberger, O. (2016). 3D U-Net: Learning Dense Volumetric Segmentation from Sparse Annotation. MICCAI 2016. https://arxiv.org/abs/1606.06650
- Ristani, E., Solera, F., Zou, R. S., Cucchiara, R., and Tomasi, C. (2016). Performance Measures and a Data Set for Multi-Target, Multi-Camera Tracking. ECCV 2016 Workshop on Benchmarking Multi-Target Tracking. https://arxiv.org/abs/1609.01775