در بیشتر پروژههای بینایی ماشین، صرفاً تشخیص اینکه در تصویر چه چیزی وجود دارد کافی نیست؛ بلکه باید محل دقیق تکتک اشیاء و تعداد آنها را با جعبههای محصورکننده مشخص کنیم. در نگاه اول، استفاده از کتابخانههای آماده مانند ultralytics بسیار ساده به نظر میرسد، اما وقتی مدل در دادههای واقعی دچار خطاهایی مثل همپوشانی جعبهها، ناتوانی در تشخیص اشیاء ریز یا گزارش اشیاء کاذب میشود، کار با یک جعبهی سیاه بدون فهم ریاضیات درونی آن متوقف خواهد شد.
در این درس، الگوریتم جریانساز YOLO را از صفر و روی مفاهیم شبکه (grid)، انکرها (anchors)، محاسبهی IoU و حذف غیرحداکثر (NMS) پیادهسازی میکنیم تا دقیقاً ببینید هر تنسور خروجی چه مفهومی دارد و پایپلاین استنتاج چگونه کار میکند. برای پیادهسازی و تست کدهای این درس نیازی به سرورهای ابری گرانقیمت یا پردازندههای گرافیکی قدرتمند خارجی ندارید؛ معماری روی ابعاد کوچک طراحی شده و بهراحتی روی CPU سیستم شخصی شما قابل اجراست.
اگر برای تست مدلهای آماده یا بستههای جانبی با دانلود از مخازنی مانند PyPI با افت سرعت یا ناپایداری ارتباط مواجه شدید، میتوانید از میرورهای داخلی بستههای پایتون استفاده کنید.
تشخیص اشیاء (Detection) یعنی دستهبندی بهعلاوهی رگرسیون؛ این فرایند روی تکتک نقاط نقشهی ویژگی اجرا و در نهایت با حذف غیرحداکثر (NMS) پالایش میشود.
اهداف یادگیری
- طراحی مبتنی بر شبکه و انکر (grid-and-anchor) را که تشخیص را به یک مسئلهی پیشبینی متراکم (dense prediction) تبدیل میکند توضیح دهید و مفهوم تکتک اعداد موجود در تنسور خروجی را بیان کنید.
- اشتراک روی اجتماع (IoU) را بین دو جعبه محاسبه کنید و الگوریتم حذف غیرحداکثر (NMS: Non-Maximum Suppression) را از صفر پیادهسازی کنید.
- یک سر (head) مینیمال به سبک YOLO روی یک ستونفقرات (backbone) از پیش آموزشدیده بسازید که شامل توابع زیان دستهبندی، شیءبودگی (objectness) و رگرسیون جعبه باشد.
- یک ردیف از معیارهای ارزیابی تشخیص اشیاء (precision@0.5، recall، mAP@0.5، mAP@0.5:0.95) را تحلیل کنید و تصمیم بگیرید در مرحلهی بعد کدام پیچ تنظیم را بچرخانید.
صورت مسئله
دستهبندی میگوید: «این تصویر یک سگ است.» تشخیص اشیاء میگوید: «یک سگ در پیکسلهای (112, 40, 280, 210) و یک گربه در (400, 180, 560, 310) قرار دارد و چیز دیگری در کادر نیست.» همین یک تغییر ساختاری — یعنی پیشبینی تعداد متغیری از جعبههای برچسبدار بهجای یک برچسب برای کل تصویر — پایهی تمام سیستمهای خودران، سامانههای نظارتی، ابزارهای تحلیل چیدمان اسناد و خطوط بینایی ماشین در کارخانههاست.
تشخیص اشیاء نقطهای است که تمام بدهبستانهای مهندسی بینایی ماشین همزمان ظاهر میشوند. جعبههایی با موقعیت دقیق میخواهید (سر رگرسیون)، کلاس درست را برای هر جعبه لازم دارید (سر دستهبندی)، میخواهید مدل بفهمد چه زمانی چیزی برای تشخیص وجود ندارد (امتیاز شیءبودگی)، و برای هر شیء واقعی دقیقاً یک پیشبینی میخواهید (حذف غیرحداکثر). اگر هر کدام از این بخشها بلنگد، پایپلاین یا اشیاء را جا میاندازد، جعبههای توهمی گزارش میدهد، یا یک شیء واحد را 15 بار در موقعیتهای کمی متفاوت پیشبینی میکند.
مدل YOLO (You Only Look Once، ردمون و همکاران، 2016) همان طرحی بود که با انجام کل این کارها در یک گذر رو به جلو (forward pass) درون یک شبکهی کانولوشنی، اجرای بیدرنگ (real-time) را ممکن کرد. این تصمیمهای ساختاری هنوز ستون فقرات دیتکتورهای مدرن (مانند YOLOv8، YOLOv9، YOLO-NAS و RT-DETR) هستند. هستهی اصلی را که یاد بگیرید، تمام نسخهها فقط چیدمان متفاوتی از همین قطعات خواهند بود.
مفاهیم پایه
تشخیص اشیاء بهمثابهی پیشبینی متراکم
یک دستهبند به ازای هر تصویر C عدد خروجی میدهد. یک دیتکتور سبکِ YOLO به ازای هر تصویر (S x S x (5 + C)) عدد تولید میکند که در آن S اندازهی شبکهی مکانی (spatial grid) است.
هر سلول از این شبکهی S * S، تعداد B جعبه را پیشبینی میکند. برای هر جعبه:
- 4 عدد هندسهی جعبه را توصیف میکنند:
tx, ty, tw, th. - 1 عدد امتیاز شیءبودگی است: «آیا مرکز یک شیء در این سلول قرار دارد؟»
- C عدد احتمالهای مربوط به هر کلاس هستند.
مجموع اعداد به ازای هر سلول: B * (5 + C). برای مجموعهدادهی VOC با مقادیر S=13, B=2, C=20، این مقدار برابر با 50 عدد در هر سلول است.
چرا شبکه و انکرها؟
در رگرسیون ساده، مختصات (x, y, w, h) هر شیء بهصورت مقادیر مطلق پیشبینی میشود. یادگیری چنین چیزی برای یک شبکهی کانولوشنی سخت است؛ زیرا جابهجایی تصویر نباید تمام پیشبینیها را به یک اندازه جابهجا کند — هر شیء به مکان خاصی متصل است. شبکه (grid) این مشکل را حل میکند: هر جعبهی واقعیت زمینی (ground-truth) را به سلولی واگذار میکند که مرکزش در آن افتاده است؛ فقط همان سلول مسئول آن شیء خواهد بود.
انکرها (Anchors) مشکل دوم را برطرف میکنند. یک لایهی کانولوشن 3x3 نمیتواند از یک سلول ویژگی با میدان دید 16 پیکسلی، یک جعبهی 500 پیکسلی را بهراحتی رگرسیون کند. بهجای آن، از پیش B شکل جعبهی پیشین (prior box) یا انکر به ازای هر سلول تعریف میکنیم و مدل فقط تغییرات کوچک (delta) را نسبت به هر انکر پیشبینی میکند. مدل یاد میگیرد انکر مناسب را بردارد و کمی تغییرش دهد، نه اینکه جعبه را از صفر حدس بزند.
Anchor box priors (example for 416x416 input):
small: (30, 60)
medium: (75, 170)
large: (200, 380)
At each grid cell, every anchor emits (tx, ty, tw, th, obj, c_1, ..., c_C).
دیتکتورهای امروزی معمولاً از FPN استفاده میکنند که در هر سطحِ تفکیکپذیری، انکرهای متفاوتی دارد — انکرهای کوچک روی نقشههای کمعمق با وضوح بالا، و انکرهای بزرگ روی نقشههای عمیق با وضوح پایین. ایده همان است، فقط مقیاسها بیشتر شدهاند.
رمزگشایی پیشبینیها
مقادیر خام tx, ty, tw, th مختصات نهایی جعبه نیستند؛ اینها اهداف رگرسیون هستند که قبل از رسم باید تبدیل شوند:
تابع sigmoid آفستهای مرکز را داخل سلول نگه میدارد. تابع exp به عرض و ارتفاع اجازه میدهد آزادانه نسبت به انکر بزرگ و کوچک شوند بدون اینکه علامتشان منفی شود. مقدار stride هم مختصات شبکه را دوباره به مقیاس پیکسل برمیگرداند. این مرحلهی رمزگشایی (decode) از زمان YOLOv2 در همهی نسخهها ثابت مانده است.
اشتراک روی اجتماع (IoU)
معیار استاندارد و جهانی شباهت بین دو جعبه در تشخیص اشیاء:
مقدار یعنی دو جعبه دقیقاً بر هم منطبقاند؛ و یعنی هیچ همپوشانی ندارند. مقدار IoU بین پیشبینی و جعبهی واقعی مشخص میکند که آیا پیشبینی مثبت واقعی (true positive) به حساب میآید یا نه (معمولاً با شرط ). همچنین IoU بین دو جعبهی پیشبینیشده همان ابزاری است که NMS برای حذف موارد تکراری به کار میبرد.
حذف غیرحداکثر (NMS)
شبکهی کانولوشنی که روی انکرهای مجاور آموزش دیده، معمولاً برای یک شیء واحد چندین جعبهی همپوشان پیشبینی میکند. الگوریتم NMS پیشبینی با بالاترین امتیاز اطمینان را نگه میدارد و جعبههای دیگری را که IoU آنها با این جعبه بیش از یک آستانه (threshold) مشخص باشد حذف میکند.
NMS(boxes, scores, iou_threshold):
sort boxes by score descending
keep = []
while boxes not empty:
pick the top-scoring box, add to keep
remove every box with IoU > iou_threshold to the picked box
return keep
آستانهی معمول: 0.45 برای تشخیص اشیاء. دیتکتورهای جدیدتر گاهی بهجای NMS استاندارد از soft-NMS یا DIoU-NMS استفاده میکنند، یا فرآیند حذف را بهصورت مستقیم آموزش میدهند (مثل RT-DETR)؛ با این حال هدف ساختاری تغییری نکرده است.
تابع زیان
تابع زیان YOLO حاصل جمع وزنیِ سه تابع زیان (loss) مختلف است:
فقط سلولهایی که حاوی شیء هستند در توابع زیان رگرسیون جعبه و دستهبندی سهم دارند. سلولهای خالی فقط روی زیان شیءبودگی اثر میگذارند (تا مدل یاد بگیرد وقتی چیزی نیست، ساکت بماند). ضریب lambda_noobj معمولاً کوچک انتخاب میشود (حدود 0.5)؛ چون بیشتر سلولها خالی هستند و اگر این ضریب کوچک نباشد، زیان آنها بر کل تابع زیان غالب میشود.
نسخههای جدیدتر، زیان خطای میانگین مربعات (MSE) جعبه را با CIoU یا DIoU (که مستقیماً IoU را بهینه میکنند) جایگزین کردهاند، برای مشکل عدمتوازن کلاسها (class imbalance) از focal loss بهره میبرند، و شیءبودگی را با quality focal loss تنظیم میکنند. اما ساختار سهبخشی کماکان دستنخورده باقی مانده است.
معیارهای ارزیابی تشخیص اشیاء
معیار دقت (accuracy) در تشخیص اشیاء کاربردی ندارد. چهار معیاری که باید بررسی کنید:
- Precision@IoU=0.5 — از میان جعبههایی که مثبت تشخیص داده شدهاند، چه کسری واقعاً درست هستند.
- Recall@IoU=0.5 — از میان اشیاء واقعی تصویر، چه کسری را توانستهایم پیدا کنیم.
- AP@0.5 — مساحت زیر منحنی دقت-بازخوانی در آستانهی IoU برابر 0.5؛ یک عدد برای هر کلاس.
- mAP@0.5:0.95 — میانگین AP در آستانههای IoU از 0.5 تا 0.95 با گامهای 0.05. این معیار COCO، سختگیرانهترین و آموزندهترین سنجه است.
هر چهار معیار را گزارش کنید. مدلی که mAP@0.5 بالایی دارد اما mAP@0.5:0.95 آن ضعیف است، اشیاء را بهطور تقریبی پیدا میکند ولی مرز جعبههایش دقیق نیست؛ این مشکل را با بهبود تابع زیان رگرسیون جعبه برطرف کنید. مدلی با دقت (precision) بالا و بازخوانی (recall) پایین بیش از حد محافظهکار است؛ آستانهی اطمینان را کم کنید یا وزن شیءبودگی را بالا ببرید.
پیادهسازی از صفر
مرحلهی 1: محاسبهی IoU
ابزار اصلی کل این درس. این تابع روی دو آرایه از جعبهها در قالب (x1, y1, x2, y2) کار میکند.
import numpy as np
def box_iou(boxes_a, boxes_b):
ax1, ay1, ax2, ay2 = boxes_a[:, 0], boxes_a[:, 1], boxes_a[:, 2], boxes_a[:, 3]
bx1, by1, bx2, by2 = boxes_b[:, 0], boxes_b[:, 1], boxes_b[:, 2], boxes_b[:, 3]
inter_x1 = np.maximum(ax1[:, None], bx1[None, :])
inter_y1 = np.maximum(ay1[:, None], by1[None, :])
inter_x2 = np.minimum(ax2[:, None], bx2[None, :])
inter_y2 = np.minimum(ay2[:, None], by2[None, :])
inter_w = np.clip(inter_x2 - inter_x1, 0, None)
inter_h = np.clip(inter_y2 - inter_y1, 0, None)
inter = inter_w * inter_h
area_a = (ax2 - ax1) * (ay2 - ay1)
area_b = (bx2 - bx1) * (by2 - by1)
union = area_a[:, None] + area_b[None, :] - inter
return inter / np.clip(union, 1e-8, None)
خروجی یک ماتریس با ابعاد (N_a, N_b) شامل IoUهای دوبهدو است. برای سنجش نسبت به یک جعبهی واقعیِ تنها، کافی است بعد یکی از آرایهها را (1, 4) بگذارید.
مرحلهی 2: حذف غیرحداکثر (NMS)
def nms(boxes, scores, iou_threshold=0.45):
order = np.argsort(-scores)
keep = []
while len(order) > 0:
i = order[0]
keep.append(i)
if len(order) == 1:
break
rest = order[1:]
ious = box_iou(boxes[[i]], boxes[rest])[0]
order = rest[ious <= iou_threshold]
return np.array(keep, dtype=np.int64)
این تابع قطعی (deterministic) است، به دلیل مرتبسازی پیچیدگی زمانی O(N log N) دارد، و خروجی آن با ورودیهای یکسان، دقیقاً مطابق torchvision.ops.nms است.
مرحلهی 3: کدگذاری و رمزگشایی جعبهها
تبدیل مختصات پیکسلی به اهداف رگرسیون (tx, ty, tw, th) که شبکه واقعاً پیشبینی میکند و برعکس.
def encode(box_xyxy, cell_x, cell_y, stride, anchor_wh):
x1, y1, x2, y2 = box_xyxy
cx = 0.5 * (x1 + x2)
cy = 0.5 * (y1 + y2)
w = x2 - x1
h = y2 - y1
off_x = np.clip(cx / stride - cell_x, 1e-6, 1 - 1e-6)
off_y = np.clip(cy / stride - cell_y, 1e-6, 1 - 1e-6)
tx = np.log(off_x / (1 - off_x))
ty = np.log(off_y / (1 - off_y))
tw = np.log(w / anchor_wh[0] + 1e-8)
th = np.log(h / anchor_wh[1] + 1e-8)
return np.array([tx, ty, tw, th])
def decode(tx_ty_tw_th, cell_x, cell_y, stride, anchor_wh):
tx, ty, tw, th = tx_ty_tw_th
cx = (sigmoid(tx) + cell_x) * stride
cy = (sigmoid(ty) + cell_y) * stride
w = anchor_wh[0] * np.exp(tw)
h = anchor_wh[1] * np.exp(th)
return np.array([cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2])
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
آزمون درستی: جعبهای را که مرکزش دقیقاً داخل سلول است encode کنید، سپس خروجی را decode کنید؛ در این حالت باید تا حد خطای اعشاری به همان جعبهی اول برسید. تابع encode آفست مرکز را به شکل لاجیت (معکوس sigmoid در decode) ذخیره میکند. استفاده از تابع clip لاجیتها را در بازهی متناهی نگه میدارد؛ مرکزی که دقیقاً روی لبهی سلول بیفتد حداکثر به اندازهی 1e-6 سلول جابهجا میشود. تابع assign_targets در ادامه نیز همین لاجیتها را ذخیره میکند تا سر مدل در همان فضایی آموزش ببیند که decode انتظارش را دارد.
مرحلهی 4: سر مینیمال YOLO
یک لایهی کانولوشن 1x1 روی نقشهی ویژگی که ابعاد خروجی را به (B, S, S, num_anchors, 5 + C) تغییر میدهد.
import torch
import torch.nn as nn
class YOLOHead(nn.Module):
def __init__(self, in_c, num_anchors, num_classes):
super().__init__()
self.num_anchors = num_anchors
self.num_classes = num_classes
self.conv = nn.Conv2d(in_c, num_anchors * (5 + num_classes), kernel_size=1)
def forward(self, x):
n, _, h, w = x.shape
y = self.conv(x)
y = y.view(n, self.num_anchors, 5 + self.num_classes, h, w)
y = y.permute(0, 3, 4, 1, 2).contiguous()
return y
شکل خروجی: (N, H, W, num_anchors, 5 + C). آخرین بعد شامل [tx, ty, tw, th, obj, cls_0, ..., cls_{C-1}] است.
مرحلهی 5: تخصیص اهداف به انکرها
برای هر جعبهی واقعی، تعیین کنید کدام (cell, anchor) مسئول پیشبینی آن است.
def assign_targets(boxes_xyxy, classes, anchors, stride, grid_size, num_classes):
num_anchors = len(anchors)
target = np.zeros((grid_size, grid_size, num_anchors, 5 + num_classes), dtype=np.float32)
has_obj = np.zeros((grid_size, grid_size, num_anchors), dtype=bool)
for box, cls in zip(boxes_xyxy, classes):
x1, y1, x2, y2 = box
cx, cy = 0.5 * (x1 + x2), 0.5 * (y1 + y2)
gx, gy = int(cx / stride), int(cy / stride)
bw, bh = x2 - x1, y2 - y1
ious = np.array([
(min(bw, aw) * min(bh, ah)) / (bw * bh + aw * ah - min(bw, aw) * min(bh, ah))
for aw, ah in anchors
])
best = int(np.argmax(ious))
aw, ah = anchors[best]
off_x = np.clip(cx / stride - gx, 1e-6, 1 - 1e-6)
off_y = np.clip(cy / stride - gy, 1e-6, 1 - 1e-6)
target[gy, gx, best, 0] = np.log(off_x / (1 - off_x))
target[gy, gx, best, 1] = np.log(off_y / (1 - off_y))
target[gy, gx, best, 2] = np.log(bw / aw + 1e-8)
target[gy, gx, best, 3] = np.log(bh / ah + 1e-8)
target[gy, gx, best, 4] = 1.0
target[gy, gx, best, 5 + cls] = 1.0
has_obj[gy, gx, best] = True
return target, has_obj
انتخاب انکر بر اساس «بهترین IoU شکلی با جعبهی واقعی» انجام میشود — رویکردی سریع و سبک که دقیقاً مشابه سازوکار YOLOv2/v3 است. در YOLOv5 و نسخههای بعد از روشهای پیچیدهتری استفاده میشود (مانند task-aligned matching و dynamic k) که نسخههای بهینهشدهی همین ایده هستند.
مرحلهی 6: سه تابع زیان
def yolo_loss(pred, target, has_obj, lambda_coord=5.0, lambda_obj=1.0, lambda_noobj=0.5, lambda_cls=1.0):
has_obj_t = torch.from_numpy(has_obj).bool()
target_t = torch.from_numpy(target).float()
# تابع زیان (loss) رگرسیون جعبه: فقط روی سلولهای دارای شیء
box_pred = pred[..., :4][has_obj_t]
box_true = target_t[..., :4][has_obj_t]
loss_box = torch.nn.functional.mse_loss(box_pred, box_true, reduction="sum")
# تابع زیان (loss) شیئیت (objectness)
obj_pred = pred[..., 4]
obj_true = target_t[..., 4]
loss_obj_pos = torch.nn.functional.binary_cross_entropy_with_logits(
obj_pred[has_obj_t], obj_true[has_obj_t], reduction="sum")
loss_obj_neg = torch.nn.functional.binary_cross_entropy_with_logits(
obj_pred[~has_obj_t], obj_true[~has_obj_t], reduction="sum")
# تابع زیان (loss) طبقهبندی روی سلولهای دارای شیء
cls_pred = pred[..., 5:][has_obj_t]
cls_true = target_t[..., 5:][has_obj_t]
loss_cls = torch.nn.functional.binary_cross_entropy_with_logits(
cls_pred, cls_true, reduction="sum")
total = (lambda_coord * loss_box
+ lambda_obj * loss_obj_pos
+ lambda_noobj * loss_obj_neg
+ lambda_cls * loss_cls)
return total, {"box": loss_box.item(), "obj_pos": loss_obj_pos.item(),
"obj_neg": loss_obj_neg.item(), "cls": loss_cls.item()}
چهار وزن مختلف برای تابع زیان وجود دارد که در آموزشهای YOLO یا مقدار ثابت میگیرند یا با جستجو تنظیم میشوند. نسبت این اعداد مهم است: مقادیر lambda_coord=5, lambda_noobj=0.5 دقیقاً از مقالهی اصلی YOLOv1 برداشته شدهاند و هنوز هم به عنوان مقادیر اولیهی پیشفرض عملکرد مناسبی دارند.
مرحلهی 7: پایپلاین استنتاج
رمزگشایی خروجی خام سر مدل، اعمال تابعهای sigmoid و exp، فیلتر کردن بر اساس آستانهی شیءبودگی، و اجرای NMS.
def postprocess(pred_tensor, anchors, stride, img_size, conf_threshold=0.25, iou_threshold=0.45):
pred = pred_tensor.detach().cpu().numpy()
grid_h, grid_w = pred.shape[1], pred.shape[2]
num_anchors = len(anchors)
boxes, scores, classes = [], [], []
for gy in range(grid_h):
for gx in range(grid_w):
for a in range(num_anchors):
tx, ty, tw, th, obj, *cls = pred[0, gy, gx, a]
score = sigmoid(obj) * sigmoid(np.array(cls)).max()
if score < conf_threshold:
continue
cls_idx = int(np.argmax(cls))
cx = (sigmoid(tx) + gx) * stride
cy = (sigmoid(ty) + gy) * stride
w = anchors[a][0] * np.exp(tw)
h = anchors[a][1] * np.exp(th)
boxes.append([cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2])
scores.append(float(score))
classes.append(cls_idx)
if not boxes:
return np.zeros((0, 4)), np.zeros((0,)), np.zeros((0,), dtype=int)
boxes = np.array(boxes)
scores = np.array(scores)
classes = np.array(classes)
keep = nms(boxes, scores, iou_threshold)
return boxes[keep], scores[keep], classes[keep]
این پایپلاینِ کامل ارزیابی است: سر مدل -> رمزگشایی -> اعمال آستانه -> حذف غیرحداکثر (NMS).
استفاده از ابزارهای واقعی
بستهی torchvision.models.detection دیتکتورهای آمادهی محیط عملیاتی را با همین ساختار مفهومی ارائه میدهد. بارگذاری یک مدل از پیش آموزشدیده تنها با سه خط کد انجام میشود.
import torch
from torchvision.models.detection import fasterrcnn_resnet50_fpn_v2
model = fasterrcnn_resnet50_fpn_v2(weights="DEFAULT")
model.eval()
with torch.no_grad():
predictions = model([torch.randn(3, 400, 600)])
print(predictions[0].keys())
print(f"boxes: {predictions[0]['boxes'].shape}")
print(f"scores: {predictions[0]['scores'].shape}")
print(f"labels: {predictions[0]['labels'].shape}")
برای پایپلاینهای استنتاج بیدرنگ، کتابخانهی ultralytics (شامل YOLOv8/v9) انتخاب استاندارد به حساب میآید: from ultralytics import YOLO; model = YOLO('yolov8n.pt'); model(img). این مدلها مراحل رمزگشایی و NMS را درونی انجام میدهند و دقیقاً همان سهگانهی boxes / scores / labels را که بالا ساختید بازمیگردانند.
خروجی این درس
از این درس خروجیهای زیر را به دست میآورید:
outputs/prompt-detection-metric-reader.md— یک پرامپت که یک سطر از مقادیرprecision, recall, AP, mAP@0.5:0.95را دریافت میکند و تحلیلی در یک خط به همراه مفیدترین آزمایش پیشنهادی بعدی تحویل میدهد.outputs/skill-anchor-designer.md— مهارتی که با دریافت مجموعهدادهای از جعبههای واقعی، الگوریتم k-means را روی(w, h)اجرا میکند، مجموعهی انکرها را برای هر سطح FPN به همراه آمارهای پوشش ارائه میدهد تا تعداد بهینهی انکرها را تعیین کنید.
تمرینها
- (ساده) تابع
box_iouرا بنویسید و روی 1000 جفتجعبهی تصادفی باtorchvision.ops.box_iouمقایسه کنید. مطمئن شوید حداکثر اختلاف مطلق کمتر از1e-6است. - (متوسط) کد
yolo_lossرا بهگونهای تغییر دهید که بهجای MSE برای جعبهها از تابع زیانCIoUاستفاده کند. روی یک مجموعهدادهی ساختگی 100 تصویری نشان دهید که در تعداد دورهای آموزشی (epoch) یکسان، تابع زیان CIoU در نهایت به mAP@0.5:0.95 بهتری نسبت به MSE میرسد. - (سخت) استنتاج چندمقیاسی (multi-scale inference) را پیاده کنید: یک تصویر یکسان را با سه مقیاس مختلف به مدل بدهید، جعبههای پیشبینیشده را با هم اجتماع بگیرید، و در پایان یک NMS واحد روی همهی آنها اجرا کنید. میزان افزایش mAP را در مقایسه با استنتاج تکمقیاسی روی مجموعهی اعتبارسنجی بسنجید.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوها | معنای فنی و دقیق |
|---|---|---|
| انکر (Anchor) | «جعبهی پیشین» | شکل از پیش تعریفشدهی جعبه در هر سلول شبکه که شبکه بهجای مختصات مطلق، تغییرات (delta) نسبت به آن را پیشبینی میکند |
| IoU | «همپوشانی» | اشتراک روی اجتماع دو جعبه؛ سنجهی جهانی سنجش شباهت در تشخیص اشیاء |
| NMS | «حذف موارد تکراری» | الگوریتمی حریصانه که پیشبینیها با بالاترین امتیاز را نگه میدارد و موارد همپوشان با تداخل بیش از آستانه را پاک میکند |
| شیءبودگی (Objectness) | «آیا اینجا چیزی هست؟» | یک عدد اسکالر به ازای هر انکر و هر سلول که احتمال قرار گرفتن مرکز یک شیء در آن سلول را بیان میکند |
| گام شبکه (Grid stride) | «ضریب کاهش ابعاد» | تعداد پیکسلها به ازای هر سلول شبکه؛ مثلاً ورودی 416 پیکسلی با سر شبکهی 13 تایی، گام 32 دارد |
| mAP | «میانگینِ میانگین دقت» | میانگین مساحت زیر منحنی دقت-بازخوانی، با میانگینگیری روی کلاسها و (در سنجهی COCO) روی آستانههای مختلف IoU |
| AP@0.5 | «معیار PASCAL VOC» | میانگین دقت با آستانهی همپوشانی IoU برابر 0.5؛ نسخهی آسانگیرانهتر این معیار |
| mAP@0.5:0.95 | «معیار COCO» | میانگین روی آستانههای IoU از 0.5 تا 0.95 با گام 0.05؛ نسخهی سختگیرانهتر و استاندارد فعلی جامعهی هوش مصنوعی |
برای مطالعهی بیشتر
- YOLOv1: You Only Look Once (Redmon et al., 2016) — مقالهی بنیادینی که مسیر را باز کرد؛ تمام نسخههای بعدی YOLO بهینهسازی همین ساختار هستند
- YOLOv3 (Redmon & Farhadi, 2018) — مقالهای که سرهای چندمقیاسی به سبک FPN را معرفی کرد؛ شفافترین نمودار ساختار را در این مقاله پیدا میکنید
- Ultralytics YOLOv8 docs — مرجع اصلی محیطهای عملیاتی فعلی؛ شامل توضیح فرمت دادهها، دادهافزاییها (augmentations) و دستورالعملهای آموزش
- The Illustrated Guide to Object Detection (Jonathan Hui) — بهترین راهنمای ساده و همهفهم برای مقایسهی خانوادههای مختلف دیتکتورها؛ برای درک ارتباط میان DETR، RetinaNet، FCOS و YOLO بسیار خواندنی است
