Asosiy qismga o'tish

O'qitish: qurilish maydonida kaska va jilet

7-bob 18 kod bloki 4713 so'z

Bu bobning kodi: kod/07_oqitish.py — kitob papkasidan ishga tushiring: python kod/07_oqitish.py

Vazifa

Qurilish maydonida ishchi kaskasiz yoki signal jiletisiz yurmasligi kerak. Hozir buni prorab ko'zi bilan kuzatadi. Kamera va detector buni kechayu kunduz qila oladi: "3-kamera, 14:07, kaskasiz odam". Bu — IV qismda sotishni o'rganadigan mahsulotlardan biri. Bu bobda uning yuragini — detector ni o'zimiz o'qitamiz.

COCO da "kaska" degan sinf yo'q. Demak tayyor yolo11n bu vazifani bilmaydi va uni yangi dataset da o'qitish kerak. Bobning rejasi:

  1. Dataset ni yuklab olish va tekshirish.
  2. Ikki tajriba: COCO da o'qitilgan vaznlardan boshlash va noldan boshlash.
  3. Natijalarni o'qish: precision, recall, AP, mAP — formulalari va noldan yozilgan kodi bilan.

Dataset

Ultralytics ning Construction-PPE dataseti (PPE — shaxsiy himoya vositalari): 1 132 o'qitish, 143 validatsiya va 141 test rasmi, 11 sinf. Yuklab olish (bir marta, ~178 MB):

kod/yuklab_olish.pydavomi
python3 qator
# Construction-PPE (7-bob): 1 416 rasm, ~178 MB
if not Path("datasets/construction-ppe").exists():
    download("https://github.com/ultralytics/assets/releases/download/v0.0.0/construction-ppe.zip", dir=Path("datasets"), delete=True)

Datasetning data.yaml fayli path: construction-ppe deydi, ultralytics esa nisbiy yo'lni o'zining sozlamalaridagi papkaga bog'laydi. O'z nusxamizni yozamiz, yo'lni kitob papkasiga qaratib:

kod/07_oqitish.pyyangi fayl · 1-qator: from kitob_yordam import fig, show
python25 qator
import time
from collections import Counter
from pathlib import Path

import cv2
import numpy as np
import pandas as pd
import torch
import torchvision
import yaml
import matplotlib.pyplot as plt
from ultralytics import YOLO
from ultralytics.utils import LOGGER
from ultralytics.utils.metrics import ap_per_class

LOGGER.setLevel("ERROR")

ds = Path("datasets/construction-ppe")
tavsif = yaml.safe_load((ds / "data.yaml").read_text())
tavsif["path"] = str(ds)
tavsif.pop("download", None)
Path("datasets/ppe.yaml").write_text(yaml.safe_dump(tavsif, allow_unicode=True, sort_keys=False))
nomlar = tavsif["names"]
print("sinflar:", nomlar)
print("litsenziya:", (ds / "LICENSE").read_text().splitlines()[0].strip())
Chiqish
sinflar: {0: 'helmet', 1: 'gloves', 2: 'vest', 3: 'boots', 4: 'goggles', 5: 'none', 6: 'Person', 7: 'no_helmet', 8: 'no_goggle', 9: 'no_gloves', 10: 'no_boots'}
litsenziya: GNU AFFERO GENERAL PUBLIC LICENSE

Dataset ham, ultralytics kodi ham AGPL-3.0. Unda o'qitilgan modelni sotishdan oldin IV qismdagi litsenziya bobini o'qing.

Dataset ni tekshirish

O'qitishdan oldin har doim dataset ni o'z ko'zingiz bilan tekshiring. Birinchi savol: har bir label faylga rasm bormi va aksincha?

kod/07_oqitish.pydavomi
python5 qator
for qism in ("train", "val", "test"):
    rasmlar = {p.stem for p in (ds / "images" / qism).iterdir()}
    labellar = {p.stem for p in (ds / "labels" / qism).iterdir() if p.suffix == ".txt"}
    print(f"{qism:5s}: {len(rasmlar)} rasm, {len(labellar)} label | rasmsiz label: {sorted(labellar - rasmlar)[:4]}"
          f"{' ...' if len(labellar - rasmlar) > 4 else ''} ({len(labellar - rasmlar)} ta) | labelsiz rasm: {len(rasmlar - labellar)}")
Chiqish
train: 1132 rasm, 1142 label | rasmsiz label: ['image940(1)', 'image941(1)', 'image944(1)', 'image945(1)'] ... (10 ta) | labelsiz rasm: 0
val  : 143 rasm, 143 label | rasmsiz label: [] (0 ta) | labelsiz rasm: 0
test : 141 rasm, 141 label | rasmsiz label: [] (0 ta) | labelsiz rasm: 0

O'qitish qismida 10 ta "yetim" label bor: image949(1).txt kabi nomlar — kimdir faylni ikki marta nusxalagan. Ultralytics ularni jimgina e'tiborsiz qoldiradi (rasmi yo'q), shuning uchun zarar yo'q. Lekin bu datasetni kim va qanday tayyorlaganini eslatib turadi: tayyor dataset ham xatosiz emas.

Ikkinchi savol: sinflar qanchalik teng taqsimlangan?

kod/07_oqitish.pydavomi
python8 qator
def sanash(qism):
    c = Counter()
    for f in (ds / "labels" / qism).glob("*.txt"):
        c.update(int(q.split()[0]) for q in f.read_text().splitlines() if q.strip())
    return c

sanoq = {q: sanash(q) for q in ("train", "val", "test")}
show(pd.DataFrame([{"sinf": nomlar[k], **{q: sanoq[q][k] for q in sanoq}} for k in range(len(nomlar))]), 11)
sinftrainvaltest
helmet1 357201192
gloves1 162136163
vest1 283171178
boots1 251151211
goggles4274752
none6548165
Person1 790239236
no_helmet4004540
no_goggle3374133
no_gloves4425658
no_boots88423

no_boots (botinkasiz) o'qitishda 88 ta, validatsiyada atigi 4 ta. 4 ta misol bo'yicha o'lchangan aniqlikka ishonib bo'lmaydi: bitta to'g'ri yoki noto'g'ri topilgan box natijani 25% ga o'zgartiradi. Real loyihada bunday sinfga ko'proq rasm yig'iladi. none sinfi dataset hujjatida aniq izohlanmagan. Pastdagi namunalarga qaraganda, u jilet kiyilmagan tanaga qo'yilgan — ya'ni aslida "jiletsiz" degani. Sinf nomi noaniq bo'lsa, rasmlarni ochib ko'rishdan boshqa yo'l yo'q.

Uchinchisi: obyektlar qanchalik kichik? 1-bobda ko'rdik, kichik obyekt — qiyin obyekt.

kod/07_oqitish.pydavomi
python9 qator
olcham = []
for f in (ds / "labels" / "train").glob("*.txt"):
    for q in f.read_text().splitlines():
        if q.strip():
            _, _, _, w, h = map(float, q.split())
            olcham.append(max(w, h) * 640)                      # 640 li kirishdagi uzun tomon, px
olcham = np.array(olcham)
for chegara in (16, 32, 64, 128):
    print(f"uzun tomoni {chegara:3d} px dan kichik: {(olcham < chegara).mean() * 100:5.1f}%")
Chiqish
uzun tomoni  16 px dan kichik:   0.2%
uzun tomoni  32 px dan kichik:   3.2%
uzun tomoni  64 px dan kichik:  23.6%
uzun tomoni 128 px dan kichik:  57.4%

Bir nechta rasmni label lari bilan ko'raylik:

kod/07_oqitish.pydavomi
python14 qator
ranglar = plt.cm.tab20(np.linspace(0, 1, len(nomlar)))
_, axes = plt.subplots(1, 3, figsize=(12, 4.4))
for ax, yol in zip(axes, sorted((ds / "images" / "train").iterdir())[::377][:3]):
    rasm = cv2.imread(str(yol))[:, :, ::-1]
    H, W = rasm.shape[:2]
    ax.imshow(rasm)
    for q in (ds / "labels" / "train" / f"{yol.stem}.txt").read_text().splitlines():
        k, xc, yc, w, h = map(float, q.split())
        ax.add_patch(plt.Rectangle(((xc - w / 2) * W, (yc - h / 2) * H), w * W, h * H, fill=False, color=ranglar[int(k)], lw=1.5))
        ax.text((xc - w / 2) * W, (yc - h / 2) * H, nomlar[int(k)], color="white", fontsize=7,
                bbox=dict(fc=ranglar[int(k)], ec="none", pad=1))
    ax.set_title(yol.name, fontsize=9)
    ax.axis("off")
fig("ppe_namunalar.png", "O'qitish qismidan uchta rasm va ularning label lari.")

O'qitish qismidan uchta rasm va ularning label lari.

O'qitish qismidan uchta rasm va ularning label lari.

O'z datasetingizni belgilash

Bu dataset tayyor. Real mijozda esa ko'pincha o'zingiz belgilaysiz: uning kameralari, uning ob-havosi, uning kiyim rangi. Belgilash uchun keng tarqalgan ikki bepul dastur: CVAT (MIT litsenziyasi) va Label Studio (Apache-2.0). Ikkalasi ham o'z serveringizga o'rnatiladi va YOLO formatida (1-bobdagi .txt) eksport qiladi. Qoidalar oddiy, lekin buzilsa model yomon o'qiydi:

  • Har bir rasmda hamma obyektni belgilang. Belgilanmagan kaska model uchun "fon" bo'lib qoladi va u kaskani topmaslikka o'rgatiladi.
  • Box ni obyektga zich chizing. 1-bobdagi IoU formulasini eslang: kichik obyektda 3–4 px xato ham ko'p.
  • Sinflarni oldindan aniq yozib qo'ying ("yarim ko'ringan kaska — helmet mi?") va hamma belgilovchi shunga amal qilsin.
  • Qiyin rasmlarni ham qo'shing: tun, yomg'ir, uzoqdagi odam, kaskaga o'xshash narsalar.

Augmentatsiya: har epoxada boshqa rasm

1 132 ta rasm kam. Model ularni yodlab olmasligi uchun ultralytics har safar rasmni tasodifiy o'zgartiradi. Standart sozlamalar (cfg/default.yaml):

kod/07_oqitish.pydavomi
python6 qator
from ultralytics.utils import DEFAULT_CFG

izoh = {"mosaic": "4 ta rasmni bittaga yopishtirish ehtimoli", "close_mosaic": "oxirgi N epoxada mosaic o'chiriladi",
        "fliplr": "gorizontal aks ettirish ehtimoli", "scale": "masshtab: +/- ulushi", "translate": "siljitish: +/- ulushi",
        "hsv_h": "rang tusi o'zgarishi", "hsv_s": "to'yinganlik o'zgarishi", "hsv_v": "yorug'lik o'zgarishi"}
show(pd.DataFrame([{"parametr": k, "qiymat": getattr(DEFAULT_CFG, k), "ma'nosi": v} for k, v in izoh.items()]))
parametrqiymatma'nosi
mosaic14 ta rasmni bittaga yopishtirish ehtimoli
close_mosaic10oxirgi N epoxada mosaic o'chiriladi
fliplr0.5gorizontal aks ettirish ehtimoli
scale0.5masshtab: +/- ulushi
translate0.1siljitish: +/- ulushi
hsv_h0.015rang tusi o'zgarishi
hsv_s0.7to'yinganlik o'zgarishi
hsv_v0.4yorug'lik o'zgarishi

Eng kuchlisi — mosaic: to'rtta rasm bitta 640 × 640 ga yopishtiriladi. Model bir qadamda ko'proq obyekt, ko'proq fon va turli masshtablarni ko'radi. Oxirgi 10 epoxada mosaic o'chiriladi, chunki ishlatishda rasmlar "yopishtirilgan" bo'lmaydi va model oxirida haqiqiy ko'rinishga moslashishi kerak. O'qitish boshlanganda ultralytics birinchi batch ni rasm qilib saqlaydi — quyidagi rasm pastdagi o'qitishimizdan:

Birinchi o'qitish batch i: mosaic, aks ettirish, rang va masshtab o'zgarishlari

Birinchi o'qitish batch i: mosaic, aks ettirish, rang va masshtab o'zgarishlari

Ikki tajriba

Bir xil model (yolo11n), bir xil dataset, bir xil 50 epoxa. Yagona farq — boshlang'ich vaznlar:

  • A (ppe_coco) — COCO da o'qitilgan yolo11n.pt dan boshlaymiz. Bu transfer learning: tarmoq chegaralarni, shakllarni, odamni allaqachon taniydi, faqat yangi sinflarni o'rganadi. Oxirgi qatlam (80 sinf → 11 sinf) mos kelmagani uchun u yangidan boshlanadi.
  • B (ppe_noldan) — tasodifiy vaznlar (yolo11n.yaml, pretrained=False). Tarmoq hamma narsani noldan o'rganadi.
kod/07_oqitish.pydavomi
python11 qator
tajribalar = {"ppe_coco": ("weights/yolo11n.pt", True), "ppe_noldan": ("yolo11n.yaml", False)}
papka = Path("out/train").resolve()                       # nisbiy project runs/detect/ ichiga tushadi (2-bob)
for nom, (boshlanish, oldindan) in tajribalar.items():
    if (papka / nom / "results.csv").exists():             # kitobni qayta yig'ishda 20 daqiqalik o'qitish takrorlanmasin
        continue
    YOLO(boshlanish).train(data="datasets/ppe.yaml", epochs=50, imgsz=640, batch=16, device=0, seed=0,
                           pretrained=oldindan, cache="ram", workers=4, project=papka, name=nom, exist_ok=True)

for nom in tajribalar:
    tarix = pd.read_csv(papka / nom / "results.csv")
    print(f"{nom:10s}: {len(tarix)} epoxa, {tarix['time'].iloc[-1] / 60:.1f} daqiqa (RTX 5070 Ti)")
Chiqish
ppe_coco  : 50 epoxa, 4.8 daqiqa (RTX 5070 Ti)
ppe_noldan: 50 epoxa, 4.6 daqiqa (RTX 5070 Ti)

optimizer=auto qaysi optimizatorni tanladi? Qoida engine/trainer.py dagi build_optimizer da:

kod/07_oqitish.pydavomi
python7 qator
import inspect
from ultralytics.engine.trainer import BaseTrainer

kod = inspect.getsource(BaseTrainer.build_optimizer).splitlines()
print("\n".join(q for q in kod if "lr_fit" in q or '("MuSGD"' in q))
nc = len(nomlar)
print(f"\nbizda nc = {nc}: lr0 = round(0.002 * 5 / (4 + {nc}), 6) = {round(0.002 * 5 / (4 + nc), 6)}")
Chiqish
            lr_fit = round(0.002 * 5 / (4 + nc), 6)  # lr0 fit equation to 6 decimal places
            name, lr, momentum = ("MuSGD", 0.01, 0.9) if iterations > 10000 else ("AdamW", lr_fit, 0.9)

bizda nc = 11: lr0 = round(0.002 * 5 / (4 + 11), 6) = 0.000667

Qisqa o'qitishda (10 000 iteratsiyadan kam) — AdamW va sinflar soniga moslangan o'rganish tezligi. Uzoq o'qitishda — YOLO26 bilan birga kelgan MuSGD. Bizning 50 epoxamiz qisqa, demak AdamW.

Natijalar

Har epoxa oxirida ultralytics validatsiyada o'lchaydi va results.csv ga yozadi. Ikki tajriba yonma-yon:

kod/07_oqitish.pydavomi
python9 qator
_, axes = plt.subplots(1, 3, figsize=(12, 3.6))
for nom, rang in zip(tajribalar, ["#2563eb", "#dc2626"]):
    tarix = pd.read_csv(papka / nom / "results.csv")
    for ax, ustun in zip(axes, ["train/box_loss", "train/cls_loss", "metrics/mAP50-95(B)"]):
        ax.plot(tarix["epoch"], tarix[ustun], color=rang, label=nom, lw=2)
        ax.set_title(ustun, fontsize=10)
        ax.set_xlabel("epoxa")
axes[2].legend()
fig("oqitish_egri.png", "Ko'k — COCO dan boshlangan, qizil — noldan. Oxirgi 10 epoxada mosaic o'chadi.")

Ko'k — COCO dan boshlangan, qizil — noldan. Oxirgi 10 epoxada mosaic o'chadi.

Ko'k — COCO dan boshlangan, qizil — noldan. Oxirgi 10 epoxada mosaic o'chadi.

COCO dan boshlangan model birinchi epoxadayoq mAP50-95 ≈ 0.12 da, noldan boshlangani esa nolda. Eng muhim raqam: COCO dan boshlangan model 7-epoxadayoq noldan boshlangan modelning 50-epoxadagi yakuniy natijasiga (0.223) yetdi. Loss lar ham shuni ko'rsatadi: ko'k egri chiziq boshidanoq pastda. Noldan boshlangan model 50 epoxada ham sekin o'sishda davom etmoqda — uzoqroq o'qitilsa farq qisqaradi, lekin buning narxi vaqt va elektr.

Eng yaxshi vaznlar (best.pt) bilan validatsiya va test qismlarida yakuniy o'lchov. Validatsiya o'qitish davomida eng yaxshi epoxani tanlash uchun ishlatildi, shuning uchun halol yakuniy raqam — test qismida:

kod/07_oqitish.pydavomi
python10 qator
qatorlar, olchovlar = [], {}
for nom in tajribalar:
    for qism in ("val", "test"):
        m = YOLO(papka / nom / "weights" / "best.pt").val(data="datasets/ppe.yaml", split=qism, imgsz=640, batch=16,
                                                           device=0, plots=False, project=papka / "val",
                                                           name=f"{nom}_{qism}", exist_ok=True)
        olchovlar[nom, qism] = m
        qatorlar.append({"tajriba": nom, "qism": qism, "precision": m.box.mp, "recall": m.box.mr,
                         "mAP50": m.box.map50, "mAP50-95": m.box.map})
show(pd.DataFrame(qatorlar).round(3))
tajribaqismprecisionrecallmAP50mAP50-95
ppe_cocoval0.5040.5940.5960.297
ppe_cocotest0.7010.5160.5480.281
ppe_noldanval0.8610.3910.4590.224
ppe_noldantest0.8590.380.4420.22

Test da COCO dan boshlangan model mAP50-95 bo'yicha chorak qismdan ko'proq oldinda. Precision va recall ustunlariga ham qarang: noldan boshlangan modelning precision i yuqori, recall i esa past — u "ishonchli ko'rinsa" gapiradi, qolganini o'tkazib yuboradi. Bitta raqam (mAP) bu farqni yashiradi. Test raqamlari validatsiyadan biroz past — bu normal: validatsiya eng yaxshi epoxani tanlashda ishlatilgani uchun unga biroz "moslashib" qolgan.

Sinflar bo'yicha (A tajriba, test qismi):

kod/07_oqitish.pydavomi
python4 qator
m = olchovlar["ppe_coco", "test"]
show(pd.DataFrame([{"sinf": nomlar[int(k)], "testdagi soni": sanoq["test"][int(k)],
                    "AP50": m.box.ap50[i], "AP50-95": m.box.ap[i]} for i, k in enumerate(m.box.ap_class_index)])
     .sort_values("AP50-95", ascending=False).round(3), 11)
sinftestdagi soniAP50AP50-95
vest1780.9050.577
helmet1920.930.533
Person2360.8430.525
boots2110.7630.402
gloves1630.7820.399
goggles520.7770.312
none650.4680.159
no_helmet400.1940.081
no_goggle330.210.066
no_gloves580.0980.023
no_boots230.0550.014

Bu jadval — butun bobning eng muhim natijasi. Model kaska, jilet va odamni yaxshi topadi (AP50 0.84–0.93). Lekin "yo'q" sinflari — no_helmet, no_goggle, no_gloves, no_boots — juda yomon (AP50 0.21 va undan past). Mahsulot uchun esa aynan ular kerak: signal "kaskasiz odam" haqida bo'ladi.

Sabablari ikkita. Birinchisi — kam misol (testda 23–58 ta). Ikkinchisi — "yo'qlik" ni ko'rish qiyin: no_helmet ning box ida kaska emas, oddiy bosh yoki shapka bor, va model uni yuzlab boshqa "bosh" lardan ajratishi kerak. Muhandislik xulosasi: "kaskasiz odam" ni alohida sinf sifatida o'qitish o'rniga, yaxshi ishlaydigan ikki sinfdan mantiq bilan chiqarish mumkin — Person topildi, uning box ining yuqori qismida helmet yo'q → kaskasiz. Bu qoida III qismda, real vaqt pipeline ida, tracking bilan birga ishlatiladi: bitta kadrda emas, bir necha soniya davomida kaskasiz bo'lsagina signal beriladi.

Metrikalar: precision, recall, AP, mAP

Yuqoridagi raqamlar qayerdan keladi? Har bir bashorat box yoki TP (to'g'ri: shu sinfdagi GT bilan IoU ≥ 0.5, va bu GT hali boshqa box ga "berilmagan"), yoki FP (xato). Topilmay qolgan GT — FN.

Precision — "model 'kaska' degan joylarning qanchasi haqiqatan kaska". Recall — "haqiqiy kaskalarning qanchasini model topdi". Ikkalasi ishonch chegarasiga bog'liq (1-bob): chegara pasaysa recall oshadi, precision tushadi. Chegarani 0 dan 1 gacha surib, (R, P) nuqtalardan egri chiziq hosil bo'ladi. AP — shu egri chiziq ostidagi yuza:

Amalda: bashoratlar ishonch bo'yicha saralanadi, har qadamda P va R hisoblanadi, egri chiziq "konvert" qilinadi (har nuqtada o'ngdagi eng katta precision olinadi — zigzaglar tekislanadi) va 101 nuqtada integrallanadi. mAP50 — IoU chegarasi 0.5 da hamma sinflar AP sining o'rtachasi. mAP50-95 — 0.50, 0.55, ..., 0.95 dagi o'nta IoU chegarasi bo'yicha yana o'rtacha. U box ning aniq joylashuvini ham talab qiladi, shuning uchun doim pastroq.

AP ni noldan yozamiz:

kod/07_oqitish.pydavomi
python14 qator
def ap_hisobla(ishonch, tp, n_gt):
    """Bitta sinf AP: saralash -> P/R egri chizig'i -> konvert -> 101 nuqtada integral."""
    if len(tp) == 0 or n_gt == 0:                                   # bashorat yoki GT yo'q: AP = 0
        return 0.0, np.array([]), np.array([])
    tartib = np.argsort(-ishonch)
    tpc = np.cumsum(tp[tartib])
    fpc = np.cumsum(1 - tp[tartib])
    recall = tpc / (n_gt + 1e-16)
    precision = tpc / (tpc + fpc)
    mrec = np.concatenate(([0.0], recall, [1.0]))
    mpre = np.concatenate(([1.0], precision, [0.0]))
    mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))            # konvert: o'ngdagi eng katta precision
    x = np.linspace(0, 1, 101)
    return np.trapezoid(np.interp(x, mrec, mpre), x), recall, precision

TP ni aniqlash uchun ultralytics ning moslash qoidasini takrorlaymiz (DetectionValidator.match_predictions): IoU chegaradan katta va sinfi to'g'ri juftliklar IoU bo'yicha saralanadi, keyin har bashorat va har GT faqat bir marta ishlatiladi:

kod/07_oqitish.pydavomi
python13 qator
def moslash(p_box, p_sinf, g_box, g_sinf, chegara=0.5):
    """Har bashorat uchun: TP (True) yoki FP (False)."""
    tp = np.zeros(len(p_box), bool)
    if len(p_box) == 0 or len(g_box) == 0:
        return tp
    iou = torchvision.ops.box_iou(torch.tensor(g_box), torch.tensor(p_box)).numpy() * (g_sinf[:, None] == p_sinf[None])
    juft = np.array(np.nonzero(iou >= chegara)).T                  # (GT, bashorat) juftliklari
    if len(juft):
        juft = juft[iou[juft[:, 0], juft[:, 1]].argsort()[::-1]]   # IoU bo'yicha kamayish
        juft = juft[np.unique(juft[:, 1], return_index=True)[1]]   # har bashorat bir marta
        juft = juft[np.unique(juft[:, 0], return_index=True)[1]]   # har GT bir marta
        tp[juft[:, 1]] = True
    return tp

Test qismining 141 rasmida predict() ishlatib (validatsiyadagi kabi conf=0.001, iou=0.7), hamma bashorat va GT ni yig'amiz:

kod/07_oqitish.pydavomi
python15 qator
eng_yaxshi = YOLO(papka / "ppe_coco" / "weights" / "best.pt")
hamma_ishonch, hamma_tp, hamma_sinf, gt_sinflar = [], [], [], []
for yol in sorted((ds / "images" / "test").iterdir()):
    r = eng_yaxshi.predict(yol, conf=0.001, iou=0.7, max_det=300, imgsz=640, device=0, verbose=False)[0]
    H, W = r.orig_shape
    lab = np.loadtxt(ds / "labels" / "test" / f"{yol.stem}.txt", ndmin=2).reshape(-1, 5)
    g_box = np.stack([(lab[:, 1] - lab[:, 3] / 2) * W, (lab[:, 2] - lab[:, 4] / 2) * H,
                      (lab[:, 1] + lab[:, 3] / 2) * W, (lab[:, 2] + lab[:, 4] / 2) * H], 1)
    p_sinf = r.boxes.cls.cpu().numpy()
    hamma_tp.append(moslash(r.boxes.xyxy.cpu().numpy(), p_sinf, g_box, lab[:, 0]))
    hamma_ishonch.append(r.boxes.conf.cpu().numpy())
    hamma_sinf.append(p_sinf)
    gt_sinflar.append(lab[:, 0])
ishonch, tp, p_sinf, gt_sinf = map(np.concatenate, (hamma_ishonch, hamma_tp, hamma_sinf, gt_sinflar))
print("bashoratlar:", len(ishonch), "| shundan TP (IoU 0.5):", int(tp.sum()), "| GT obyektlar:", len(gt_sinf))
Chiqish
bashoratlar: 20417 | shundan TP (IoU 0.5): 1091 | GT obyektlar: 1251

Endi har sinf uchun AP50 ni uch xil hisoblaymiz: o'z funksiyamiz, xuddi shu massivlar ustida ultralytics ning ap_per_class funksiyasi, va yuqoridagi val() natijasi:

kod/07_oqitish.pydavomi
python11 qator
_, _, _, _, _, ap_ultra, sinf_ultra, *_ = ap_per_class(tp[:, None], ishonch, p_sinf, gt_sinf)
qatorlar = []
for i, k in enumerate(sinf_ultra):
    ap_biz, _, _ = ap_hisobla(ishonch[p_sinf == k], tp[p_sinf == k].astype(float), (gt_sinf == k).sum())
    j = list(m.box.ap_class_index).index(k)
    qatorlar.append({"sinf": nomlar[int(k)], "AP50 (biz)": ap_biz, "AP50 (ap_per_class)": ap_ultra[i, 0],
                     "AP50 (val())": m.box.ap50[j]})
jadval = pd.DataFrame(qatorlar)
show(jadval.round(4), 11)
print("biz vs ap_per_class, eng katta farq:", float((jadval["AP50 (biz)"] - jadval["AP50 (ap_per_class)"]).abs().max()))
print("biz vs val(), eng katta farq:       ", round(float((jadval["AP50 (biz)"] - jadval["AP50 (val())"]).abs().max()), 4))
Chiqish
biz vs ap_per_class, eng katta farq: 0.0
biz vs val(), eng katta farq:        0.0317
sinfAP50 (biz)AP50 (ap_per_class)AP50 (val())
helmet0.910.910.93
gloves0.7780.7780.781
vest0.8810.8810.905
boots0.7490.7490.763
goggles0.7450.7450.777
none0.4690.4690.468
Person0.830.830.843
no_helmet0.190.190.194
no_goggle0.1780.1780.21
no_gloves0.0670.0670.098
no_boots0.0690.0690.055

Birinchi ikki ustun bir xil massivlar ustida hisoblangan va aynan mos: formula to'g'ri. val() bilan esa kichik farq bor. Sababi — kirish rasmlari: val() rasmlarni batch larga guruhlab, har batch ga umumiy letterbox o'lchami tanlaydi (5-bobda ko'rgan "minimal to'ldirish" dan biroz farqli), predict() esa har rasmni alohida letterbox qiladi. Kirish bir necha piksel farq qilsa, ishonch qiymatlari ham biroz farq qiladi.

Bitta sinfning P–R egri chizig'i:

kod/07_oqitish.pydavomi
python10 qator
k = [i for i, n in nomlar.items() if n == "helmet"][0]
ap, rec, prec = ap_hisobla(ishonch[p_sinf == k], tp[p_sinf == k].astype(float), (gt_sinf == k).sum())
_, ax = plt.subplots(figsize=(5.5, 4))
ax.plot(rec, prec, lw=2, color="#2563eb")
ax.set_xlabel("recall")
ax.set_ylabel("precision")
ax.set_xlim(0, 1.02)
ax.set_ylim(0, 1.02)
ax.set_title(f"helmet, test: AP50 = {ap:.3f}")
fig("pr_helmet.png", "Ishonch chegarasi 1 dan 0 gacha tushganda: recall o'sadi, precision oxirida tushadi. AP — egri chiziq ostidagi yuza.")

Ishonch chegarasi 1 dan 0 gacha tushganda: recall o'sadi, precision oxirida tushadi. AP — egri chiziq ostidagi yuza.

Ishonch chegarasi 1 dan 0 gacha tushganda: recall o'sadi, precision oxirida tushadi. AP — egri chiziq ostidagi yuza.

Yangi rasmlarda

Test qismidan ikkita rasm, o'qitilgan model bilan (standart chegara 0.25):

kod/07_oqitish.pydavomi
python7 qator
_, axes = plt.subplots(1, 2, figsize=(12, 5.2))
for ax, yol in zip(axes, sorted((ds / "images" / "test").iterdir())[5::60][:2]):
    r = eng_yaxshi.predict(yol, imgsz=640, device=0, verbose=False)[0]
    ax.imshow(r.plot()[:, :, ::-1])
    ax.set_title(yol.name, fontsize=9)
    ax.axis("off")
fig("ppe_test.png", "Test qismidagi ikki rasm: o'qitishda hech qachon ko'rilmagan.")

Test qismidagi ikki rasm: o'qitishda hech qachon ko'rilmagan.

Test qismidagi ikki rasm: o'qitishda hech qachon ko'rilmagan.

Chapdagi rasmda odam, kaska, ko'zoynak, jilet, qo'lqop va botinka — hammasi topilgan, lekin ko'zoynakka ikkita box chizilgan. O'ngdagi olomonda yaqindagi ishchilar yaxshi, uzoqdagi va bir-birini to'sganlarda esa box lar ustma-ust va ishonch pastroq (0.3–0.6). 5 daqiqalik o'qitish uchun yomon emas, mahsulot uchun esa hali yetarli emas — ko'proq ma'lumot, mijozning o'z kameralaridan olingan rasmlar va uzoqroq o'qitish kerak.

Asosiy fikr

  • Dataset ni o'qitishdan oldin tekshiring: yetim label lar, sinflar muvozanati, obyektlar o'lchami. 4 ta misolli sinf bo'yicha o'lchangan aniqlikka ishonmang.
  • Transfer learning — kichik dataset uchun eng muhim qaror: COCO dan boshlangan model 7 epoxada noldan boshlangan modelning 50 epoxalik natijasiga yetdi. "Yo'qlik" sinflari (kaskasiz va h.k.) yomon o'rganiladi — ularni yaxshi sinflardan mantiq bilan chiqarish ishonchliroq.
  • AP — P–R egri chizig'i ostidagi yuza; mAP50 — sinflar bo'yicha o'rtacha, mAP50-95 — yana o'nta IoU chegarasi bo'yicha. Halol yakuniy raqam — o'qitishda ham, model tanlashda ham ishlatilmagan test qismida.

Mashqlar

  1. A tajribani epochs=100 bilan qayta o'qiting. Test dagi mAP50-95 qanchaga oshadi? Egri chiziqlarga qarab, 50 epoxa yetarli edimi?
  2. moslash() da chegarani 0.75 ga ko'taring va helmet uchun AP ni qayta hisoblang. Nega AP75 AP50 dan past?
  3. mosaic=0.0 bilan A tajribani takrorlang. Qaysi sinflar eng ko'p yutqazdi — kichiklarmi yoki kattalarmi?

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar