Yolo5 на примере задачи Help Protect the Great Barrier Reef
Введение
Yolov5 является одной из самых эффективных решений для детектирования объектов на настоящий момент. Собственно подтверждение этому можно найти на сайте Yolo:
Ну и конечно же в последнем конкурсе на Kaggle архитектура показала свое качество.
Конкурс: https://www.kaggle.com/c/tensorflow-great-barrier-reef/
Замечание: возможно именно этот конкурс не является показательным, т.к. private выборка значительно отличалась от public, в результате чего после завершения конкурса вся таблица перемешалась.
В данной статье я опишу саму задачу и как ее решать.
Задача
В конкурсе TensorFlow - Help Protect the Great Barrier Reef необходимо было детектировать морские звезды на последовательности изображений (можно сказать на видео). Примеры, приведены на картинках ниже:
Оценкой выступала метрика F2 в диапазоне от от 0.3 до 0.8 Про метрику можно почитать здесь: https://en.wikipedia.org/wiki/F-score
Формула, где бетта = 2.
Т.е. нужно обучить модель(-и), чтобы получить максимальный результат.
Подготовка машины для обучения
Рассмотрим стандартную машину (Linux) с установленными драйверами и Юпитером, которые обычно применяются на платных вычислительных ресурсах. Сначала в терминале нужно установить дополнительные модули:
apt-get update
apt install git
apt-get install ffmpeg libsm6 libxext6 -y
В Юпитере также надо ставить компоненты:
!pip install --upgrade pip
!pip install --upgrade --force-reinstall --no-deps kaggle
!pip install slugify
!pip install tqdm
Дальше загрузить kaggle.json с паролем-доступом (для вашего аккаунта). После чего скачиваем датасет.
! mkdir ~/.kaggle
! cp kaggle.json ~/.kaggle/
! chmod 600 ~/.kaggle/kaggle.json
!kaggle competitions download -c tensorflow-great-barrier-reef
Downloading tensorflow-great-barrier-reef.zip to /root
100%|███████████████████████████████████████| 14.2G/14.2G [02:40<00:00, 117MB/s]100%|██████████████████████████████████████| 14.2G/14.2G [02:40<00:00, 94.9MB/s]
Распаковываем данные:
! mkdir tensorflow-great-barrier-reef
! unzip tensorflow-great-barrier-reef.zip -d tensorflow-great-barrier-reef
И устанавливаем еще необходимые модули:
!pip install pandas
!pip install wandb
!pip install opencv-python
!pip install pillow
!pip install matplotlib
!pip install seaborn
!pip install torch==1.10.0+cu113 torchvision==0.11.1+cu113 torchaudio==0.10.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html
На этом установка закончена, Yolov5 поставим следующем разделе
Обучение Yolo
В соревновании выборка была разбита на 3 видео. И обучали либо: а) на двух видео, а на одном проверяли. Либо уменьшали проверочную выборку до минимума, чтобы было больше данных. Тут будет описано, как обучить на датасете при выделении 1 видео в проверочное.
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from shutil import copyfile
train = pd.read_csv('/root/tensorflow-great-barrier-reef/train.csv')
train['pos'] = train.annotations != '['
Создаем папки для хранения обучающих данных:
!mkdir -p ./yolo_data/fold1/images/val
!mkdir -p ./yolo_data/fold1/images/train
!mkdir -p ./yolo_data/fold1/labels/val
!mkdir -p ./yolo_data/fold1/labels/train
После чего заполняются тренировочные и валидационные папки:
fold = 1
annos = [
for i, x in train.iterrows():
if x.video_id == fold:
mode = 'val'
else:
# train
mode = 'train'
if not x.pos: continue
# val
copyfile(f'/root/tensorflow-great-barrier-reef/train_images/video_{x.video_id}/{x.video_frame}.jpg',
f'./yolo_data/fold{fold}/images/{mode}/{x.image_id}.jpg')
if not x.pos:
continue
r = ''
anno = eval(x.annotations)
for an in anno:
# annos.append(an)
r += '0 {} {} {} {}\n'.format((an['x'] + an['width'] / 2) / 1280,
(an['y'] + an['height'] / 2) / 720,
an['width'] / 1280, an['height'] / 720)
with open(f'./yolo_data/fold{fold}/labels/{mode}/{x.image_id}.txt', 'w') as fp:
fp.write(r)
Настройка гиперпараметров для обучения:
hyps = '''
# YOLOv5 by Ultralytics, GPL-3.0 license
# Hyperparameters for COCO training from scratch
# python train.py --batch 40 --cfg yolov5m.yaml --weights '' --data coco.yaml --img 640 --epochs 300
# See tutorials for hyperparameter evolution https://github.com/ultralytics/yolov5#tutorials
lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3)
lrf: 0.1 # final OneCycleLR learning rate (lr0 * lrf)
momentum: 0.937 # SGD momentum/Adam beta1
weight_decay: 0.0005 # optimizer weight decay 5e-4
warmup_epochs: 3.0 # warmup epochs (fractions ok)
warmup_momentum: 0.8 # warmup initial momentum
warmup_bias_lr: 0.1 # warmup initial bias lr
box: 0.05 # box loss gain
cls: 0.5 # cls loss gain
cls_pw: 1.0 # cls BCELoss positive_weight
obj: 1.0 # obj loss gain (scale with pixels)
obj_pw: 1.0 # obj BCELoss positive_weight
iou_t: 0.20 # IoU training threshold
anchor_t: 4.0 # anchor-multiple threshold
# anchors: 3 # anchors per output layer (0 to ignore)
fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5)
hsv_h: 0.015 # image HSV-Hue augmentation (fraction)
hsv_s: 0.7 # image HSV-Saturation augmentation (fraction)
hsv_v: 0.4 # image HSV-Value augmentation (fraction)
degrees: 0.0 # image rotation (+/- deg)
translate: 0.1 # image translation (+/- fraction)
scale: 0.5 # image scale (+/- gain)
shear: 0.0 # image shear (+/- deg)
perspective: 0.0 # image perspective (+/- fraction), range 0-0.001
flipud: 0.5 # image flip up-down (probability)
fliplr: 0.5 # image flip left-right (probability)
mosaic: 1.0 # image mosaic (probability)
mixup: 0.5 # image mixup (probability)
copy_paste: 0.0 # segment copy-paste (probability)
'''
data = '''
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: ../yolo_data/fold1/ # dataset root dir
train: images/train # train images (relative to 'path') 128 images
val: images/val # val images (relative to 'path') 128 images
test: # test images (optional)
# Classes
nc: 1 # number of classes
names: ['reef'] # class names
# Download script/URL (optional)
# download: https://ultralytics.com/assets/coco128.zip
'''
Скачиваем репозиторий Yolov5
!git clone https://github.com/ultralytics/yolov5.git
Записываем настройки:
with open('./yolov5/data/reef_f1_naive.yaml', 'w') as fp:
fp.write(data)
with open('./yolov5/data/hyps/hyp.heavy.2.yaml', 'w') as fp:
fp.write(hyps)
Инициализируем
import wandb
wandb.login(key="здесь ваше код для Weights & Biases")
И запускаем обучение:
!python train.py --img 3840 --batch 4 --epochs 15 --data reef_f1_naive.yaml --weights yolov5s6.pt --name s6_3840_b8_uflip_vm5_f1 --hyp data/hyps/hyp.heavy.2.yaml
Starting training for 15 epochs... Epoch gpu_mem box obj cls labels img_size 0/14 29.7G 0.07855 0.09465 0 24 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.0268 0.0323 0.00301 0.000935 Epoch gpu_mem box obj cls labels img_size 1/14 31.4G 0.05612 0.06586 0 19 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.487 0.385 0.323 0.0813 Epoch gpu_mem box obj cls labels img_size 2/14 31.4G 0.05011 0.05583 0 12 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.567 0.41 0.385 0.116 Epoch gpu_mem box obj cls labels img_size 3/14 31.4G 0.04531 0.05196 0 14 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.809 0.509 0.588 0.27 Epoch gpu_mem box obj cls labels img_size 4/14 31.4G 0.04059 0.04887 0 8 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.802 0.521 0.582 0.237 Epoch gpu_mem box obj cls labels img_size 5/14 31.4G 0.03859 0.04564 0 18 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.796 0.532 0.601 0.283 Epoch gpu_mem box obj cls labels img_size 6/14 31.4G 0.03656 0.04573 0 14 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.815 0.509 0.592 0.272 Epoch gpu_mem box obj cls labels img_size 7/14 31.4G 0.03527 0.04419 0 16 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.854 0.58 0.657 0.313 Epoch gpu_mem box obj cls labels img_size 8/14 31.4G 0.03361 0.04411 0 14 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.88 0.568 0.657 0.311 Epoch gpu_mem box obj cls labels img_size 9/14 31.4G 0.03272 0.04129 0 24 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.886 0.599 0.677 0.319 Epoch gpu_mem box obj cls labels img_size 10/14 31.4G 0.03199 0.0415 0 23 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.872 0.62 0.691 0.334 Epoch gpu_mem box obj cls labels img_size 11/14 31.4G 0.03092 0.04058 0 26 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.863 0.583 0.657 0.316 Epoch gpu_mem box obj cls labels img_size 12/14 31.4G 0.03023 0.03918 0 17 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.847 0.612 0.68 0.328 Epoch gpu_mem box obj cls labels img_size 13/14 31.4G 0.02963 0.03954 0 21 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.87 0.621 0.688 0.338 Epoch gpu_mem box obj cls labels img_size 14/14 31.4G 0.02902 0.03777 0 22 3840: 100%|███ Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.875 0.611 0.679 0.33215 epochs completed in 3.817 hours.Optimizer stripped from runs/train/l6_3840_b8_uflip_vm5_f13/weights/last.pt, 29.3MBValidating runs/train/l6_3840_b8_uflip_vm5_f13/weights/best.pt...Optimizer stripped from runs/train/l6_3840_b8_uflip_vm5_f13/weights/best.pt, 29.3MBFusing layers... Model Summary: 280 layers, 12308200 parameters, 0 gradients Class Images Labels P R mAP@.5 mAP@ all 8232 6384 0.849 0.632 0.688 0.338wandb: Waiting for W&B process to finish, PID 3077... (success).wandb: wandb: Run history:wandb: metrics/mAP_0.5 ▁▄▅▇▇▇▇█████████wandb: metrics/mAP_0.5:0.95 ▁▃▃▇▆▇▇▇▇███████wandb: metrics/precision ▁▅▅▇▇▇▇█████████wandb: metrics/recall ▁▅▅▇▇▇▇▇▇██▇████wandb: train/box_loss █▅▄▃▃▂▂▂▂▂▁▁▁▁▁wandb: train/cls_loss ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁wandb: train/obj_loss █▄▃▃▂▂▂▂▂▁▁▁▁▁▁wandb: val/box_loss █▅▄▂▃▂▂▁▁▂▁▁▁▁▁▁wandb: val/cls_loss ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁wandb: val/obj_loss █▄▃▂▂▂▂▁▁▁▁▁▁▁▁▁wandb: x/lr0 ▃▅███▇▆▆▅▄▃▃▂▁▁wandb: x/lr1 ▃▅███▇▆▆▅▄▃▃▂▁▁wandb: x/lr2 █▅▂▂▂▂▂▂▁▁▁▁▁▁▁wandb: wandb: Run summary:wandb: best/epoch 13wandb: best/mAP_0.5 0.6879wandb: best/mAP_0.5:0.95 0.33823wandb: best/precision 0.8697wandb: best/recall 0.6214wandb: metrics/mAP_0.5 0.6882wandb: metrics/mAP_0.5:0.95 0.33841wandb: metrics/precision 0.8494wandb: metrics/recall 0.63236wandb: train/box_loss 0.02902wandb: train/cls_loss 0.0wandb: train/obj_loss 0.03777wandb: val/box_loss 0.01187wandb: val/cls_loss 0.0wandb: val/obj_loss 0.01423wandb: x/lr0 0.00139wandb: x/lr1 0.00139wandb: x/lr2 0.00139wandb: wandb: Synced 5 W&B file(s), 497 media file(s), 1 artifact file(s) and 0 other file(s)wandb: Synced l6_3840_b8_uflip_vm5_f1: https://wandb.ai/vidikon/YOLOv5/runs/ils1wcwwwandb: Find logs at: ./wandb/run-20220117_104014-ils1wcww/logs/debug.logwandb: Results saved to runs/train/l6_3840_b8_uflip_vm5_f13
Weights & Biases
Weights & Biases является удобной штукой, где в реальном времени можно смотреть ход обучения https://wandb.ai/site
Метрики:
Потери:
Выбор моделей
Выбор оптимального inference всегда осуществляется по тестированию. Это могут быть и готовые функции по вычислению F2 score (однако это сложно сделать, если вы почти все данные записали в обучающие, а не валидационные).
Загрузка модели выглядит так:
model1 = torch.hub.load('../input/yolov5-lib-ds',
'custom',
path='../input/cots-3600s/best.pt',
source='local',
force_reload=True) # local repo
При различном обучении однако было выявлено два варианта, отличающиеся по форме Precission
Вариант 1 Обычный Precissiion
Вариант 2 Почти прямой Precissiion
Соответственно возникла идея использовать ансамбль моделей по принципу, базовый результат из наилучшей модели Вариант 1 + высокодостоверные результаты, которые на совпадают с Вариантом 1 из Варианта 2. Это было сделано следующим образом:
out_data=[
for idx, (img, pred_df) in enumerate(tqdm(iter_test)):
anno = ''
bboxes=[
bboxes2=[
out_step=[
r = model(img, size=6400, augment=True)
if r.pandas().xyxy[0].shape[0] == 0:
anno = ''
else:
for idx, row in r.pandas().xyxy[0].iterrows():
if row.confidence > 0.3:
#anno += '{} {} {} {} {} '.format(row.confidence, int(row.xmin), int(row.ymin), int(row.xmax-row.xmin), int(row.ymax-row.ymin))
bbox = {
'x1':row.xmin,
'x2':row.xmax,
'y1':row.ymin,
'y2':row.ymax
}
#print(bbox)
bboxes.append([bbox,row.confidence,0])
r = model1(img, size=3600, augment=True)
#print("a")
if r.pandas().xyxy[0].shape[0] == 0:
pass
else:
for idx, row in r.pandas().xyxy[0].iterrows():
if row.confidence > 0.1:
bbox = {
'x1':row.xmin,
'x2':row.xmax,
'y1':row.ymin,
'y2':row.ymax
}
#print(bbox)
k = 0
max_box = -1
box_iou = 0
if bboxes != [:
for i,b in enumerate(bboxes):
g = get_iou(b[0],bbox)
if g > 0.15:
bboxes[i][2]=1
if g > 0.5:
k = 1
if g > box_iou:
max_box = i
if k == 0:
if row.confidence > 0.45:
bboxes2.append([bbox,row.confidence,0])
#anno += '{} {} {} {} {} '.format(row.confidence, int(row.xmin), int(row.ymin), int(row.xmax-row.xmin), int(row.ymax-row.ymin))
for b in bboxes:
if b[2] == 1 or b[1] > 0.4:
anno += '{} {} {} {} {} '.format(b[1], int(b[0]['x1']), int(b[0]['y1']), int(b[0]['x2']-b[0]['x1']), int(b[0]['y2']-b[0]['y1']))
out_step.append([b[0],b[1],1])
for b in bboxes2:
anno += '{} {} {} {} {} '.format(b[1], int(b[0]['x1']), int(b[0]['y1']), int(b[0]['x2']-b[0]['x1']), int(b[0]['y2']-b[0]['y1']))
out_step.append([b[0],b[1],1])
out_data.append(out_step)
pred_df['annotations'] = anno.strip(' ')
env.predict(pred_df)
Get_iou здесь функция, вычисляющая IOU:
def get_iou(bb1, bb2):
"""
Calculate the Intersection over Union (IoU) of two bounding boxes.
Parameters
----------
bb1 : dict
Keys: {'x1', 'x2', 'y1', 'y2'}
The (x1, y1) position is at the top left corner,
the (x2, y2) position is at the bottom right corner
bb2 : dict
Keys: {'x1', 'x2', 'y1', 'y2'}
The (x, y) position is at the top left corner,
the (x2, y2) position is at the bottom right corner
Returns
-------
float
in [0, 1]
"""
assert bb1['x1'] < bb1['x2']
assert bb1['y1'] < bb1['y2']
assert bb2['x1'] < bb2['x2']
assert bb2['y1'] < bb2['y2']
# determine the coordinates of the intersection rectangle
x_left = max(bb1['x1'], bb2['x1'])
y_top = max(bb1['y1'], bb2['y1'])
x_right = min(bb1['x2'], bb2['x2'])
y_bottom = min(bb1['y2'], bb2['y2'])
if x_right < x_left or y_bottom < y_top:
return 0.0
# The intersection of two axis-aligned bounding boxes is always an
# axis-aligned bounding box
intersection_area = (x_right - x_left) * (y_bottom - y_top)
# compute the area of both AABBs
bb1_area = (bb1['x2'] - bb1['x1']) * (bb1['y2'] - bb1['y1'])
bb2_area = (bb2['x2'] - bb2['x1']) * (bb2['y2'] - bb2['y1'])
# compute the intersection over union by taking the intersection
# area and dividing it by the sum of prediction + ground-truth
# areas - the interesection area
iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
assert iou >= 0.0
assert iou <= 1.0
return iou
Далее были почищены ложные срабатывания простым алгоритмом:
from math import sqrt
df = pd.read_csv('submission.csv')
def len_(bbox1,bbox2):
x = ((bbox1['x1']+bbox1['x2'])/2)-((bbox2['x1']+bbox2['x2'])/2)
y = ((bbox1['y1']+bbox1['y2'])/2)-((bbox2['y1']+bbox2['y2'])/2)
return sqrt(x*x+y*y)
threshold = 80
a=[
for i, row in df.iterrows():
a.append(len(str(row['annotations'])) > 5)
for i, row in df.iterrows():
if i > 1 and i < len(a)-3:
if a[i] and a[i+1] and not a[i-1] and not a[i+2] and not a[i-2] and not a[i+3]:
df.loc[i,'annotations']=""
df.loc[i+1,'annotations']=""
if i > 0 and i < len(a)-1:
if a[i] and not a[i-1] and not a[i+1]:
df.loc[i,'annotations']=""
continue
if out_data[i] != [:
anno = ''
for j in range(len(out_data[i])):
f=0
if out_data[i-1] !=[:
for k in range(len(out_data[i-1])):
if len_(out_data[i-1][k][0],out_data[i][j][0]) < threshold:
f = 1
if out_data[i+1] !=[:
for k in range(len(out_data[i+1])):
if len_(out_data[i+1][k][0],out_data[i][j][0]) < threshold:
f = 1
if f == 1:
#out_data[i][j][2] = 0
b = out_data[i][j][0]
anno += '{} {} {} {} {} '.format(out_data[i][j][1], int(b['x1']), int(b['y1']), int(b['x2']-b['x1']), int(b['y2']-b['y1']))
df.loc[i,'annotations']=anno.strip(' ')
df.to_csv('submission.csv',index = False)
Такой результат дал 58 место в конкурсе, хотя это как оказалось был не самый оптимальный вариант. И невыбранный ноутбук давал 50 место.
Первые места
Два первых места были на yolo и были неожиданными в том числе и для самих авторов.
1 место. https://www.kaggle.com/c/tensorflow-great-barrier-reef/discussion/307878
Двухступенчатый конвейер - детектирование, а потом дополнительная классификация. 6 моделей yolo
2 место https://www.kaggle.com/c/tensorflow-great-barrier-reef/discussion/307760
Использование ансамблей из Yolo моделей.