Download model.py from YMmim/object-detection-scratch: direct link, hf CLI and curl.
- Browser
- Download file 9.16 kB
-
https://huggingface.co/YMmim/object-detection-scratch/resolve/main/model.py
- Command line
-
hf download hf://YMmim/object-detection-scratch/model.py
-
curl -L -o model.py https://huggingface.co/YMmim/object-detection-scratch/resolve/main/model.py
9.16 kB
| """ | |
| Faster R-CNN ๋ฐ๋ฐ๋ฅ ๊ตฌํ โ [3/5] ๋ชจ๋ธ | |
| ========================================== | |
| ์ ์ฒด ๊ตฌ์กฐ: | |
| ์ด๋ฏธ์ง | |
| โ [๋ฐฑ๋ณธ: ResNet50 (ImageNet ์ฌ์ ํ์ต)] | |
| โผ | |
| ํน์ง๋งต (stride 16) | |
| โ | |
| โโโถ [RPN] โโ ์ต์ปค๋ง๋ค (๊ฐ์ฒด ์ฌ๋ถ, ๋ฐ์ค ๋ณด์ ) ์์ธก โโ ํ๋ณด์์ญ(proposal) ์์ฑ | |
| โ | |
| โผ | |
| [RoI Align] โโ ๊ฐ ํ๋ณด์์ญ์ ๊ณ ์ ํฌ๊ธฐ(7x7) ํน์ง์ผ๋ก ์ถ์ถ | |
| โ | |
| โผ | |
| [RoI Head] โโ (ํด๋์ค ๋ถ๋ฅ, ํด๋์ค๋ณ ๋ฐ์ค ๋ณด์ ) ์ต์ข ์์ธก | |
| 2-stage ํ์ง๊ธฐ์ ์ ์ ๊ตฌ์กฐ. RPN์ด "์ด๋์ ๋ญ๊ฐ ์๋ค"๋ฅผ, | |
| RoI Head๊ฐ "๊ทธ๊ฒ ๋ฌด์์ด๊ณ ์ ํํ ์์น๋ ์ด๋"๋ฅผ ๋ด๋นํ๋ค. | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import torchvision | |
| from torchvision.ops import roi_align | |
| from box_utils import (generate_anchors, shift_anchors, box_iou, | |
| encode_boxes, decode_boxes, clip_boxes, nms) | |
| # --------------------------------------------------------------- | |
| # ๋ฐฑ๋ณธ: ResNet50์ conv1~layer3๊น์ง (stride 16 ํน์ง๋งต) | |
| # --------------------------------------------------------------- | |
| class Backbone(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| resnet = torchvision.models.resnet50(weights="IMAGENET1K_V1") | |
| # layer4๋ RoI Head์์ ์ฐ๊ณ , ์ฌ๊ธฐ์ layer3๊น์ง โ stride 16 | |
| self.body = nn.Sequential( | |
| resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, | |
| resnet.layer1, resnet.layer2, resnet.layer3, | |
| ) | |
| self.out_channels = 1024 # layer3 ์ถ๋ ฅ ์ฑ๋ | |
| self.stride = 16 | |
| # ์ด๊ธฐ ๋ ์ด์ด๋ ๋๊ฒฐ(์์ ๋ฐ์ดํฐ์ ๊ณผ์ ํฉยท๋ถ์์ ๋ฐฉ์ง) | |
| for p in self.body[:5].parameters(): | |
| p.requires_grad = False | |
| def forward(self, x): | |
| return self.body(x) | |
| # --------------------------------------------------------------- | |
| # RPN (Region Proposal Network) | |
| # --------------------------------------------------------------- | |
| class RPN(nn.Module): | |
| def __init__(self, in_channels=1024, num_anchors=9): | |
| super().__init__() | |
| self.conv = nn.Conv2d(in_channels, 512, 3, padding=1) | |
| # ์ต์ปค๋ง๋ค: ๊ฐ์ฒด/๋ฐฐ๊ฒฝ 2์ ์ โ ์ฌ๊ธฐ์ objectness 1๊ฐ(logit)๋ก ๋จ์ํ | |
| self.cls = nn.Conv2d(512, num_anchors, 1) # objectness | |
| self.reg = nn.Conv2d(512, num_anchors * 4, 1) # ๋ฐ์ค ๋ณด์ | |
| self.num_anchors = num_anchors | |
| for layer in [self.conv, self.cls, self.reg]: | |
| nn.init.normal_(layer.weight, std=0.01) | |
| nn.init.constant_(layer.bias, 0) | |
| def forward(self, feat): | |
| t = F.relu(self.conv(feat)) | |
| logits = self.cls(t) # [B, A, H, W] | |
| deltas = self.reg(t) # [B, A*4, H, W] | |
| B, _, H, W = logits.shape | |
| # [B, H*W*A] ํํ๋ก ์ ๋ฆฌ | |
| logits = logits.permute(0, 2, 3, 1).reshape(B, -1) | |
| deltas = deltas.permute(0, 2, 3, 1).reshape(B, -1, 4) | |
| return logits, deltas, (H, W) | |
| # --------------------------------------------------------------- | |
| # RoI Head (๋ถ๋ฅ + ๋ฐ์ค ํ๊ท) | |
| # --------------------------------------------------------------- | |
| class RoIHead(nn.Module): | |
| def __init__(self, num_classes, in_channels=1024, roi_size=7): | |
| super().__init__() | |
| # layer4 ๋์ ๊ฐ๋จํ FC ํค๋๋ก ๊ตฌ์ฑ(ํ์ต ๊ฐ๋ฒผ์) | |
| self.roi_size = roi_size | |
| flat = in_channels * roi_size * roi_size | |
| self.fc = nn.Sequential( | |
| nn.Linear(flat, 1024), nn.ReLU(inplace=True), | |
| nn.Linear(1024, 1024), nn.ReLU(inplace=True), | |
| ) | |
| self.cls = nn.Linear(1024, num_classes) # ํด๋์ค ๋ถ๋ฅ | |
| self.reg = nn.Linear(1024, num_classes * 4) # ํด๋์ค๋ณ ๋ฐ์ค ๋ณด์ | |
| self.num_classes = num_classes | |
| nn.init.normal_(self.cls.weight, std=0.01) | |
| nn.init.normal_(self.reg.weight, std=0.001) | |
| nn.init.constant_(self.cls.bias, 0) | |
| nn.init.constant_(self.reg.bias, 0) | |
| def forward(self, feat, rois, stride): | |
| """ | |
| feat : ๋ฐฑ๋ณธ ํน์ง๋งต [B,C,H,W] | |
| rois : ํ๋ณด์์ญ [N,4] (์ด๋ฏธ์ง ์ขํ) | |
| """ | |
| # roi_align: ๊ฐ ํ๋ณด์์ญ์ 7x7 ๊ณ ์ ํฌ๊ธฐ ํน์ง์ผ๋ก ์ถ์ถ | |
| # batch index 0 (batch_size=1 ๊ฐ์ ) ๋ฅผ ์์ ๋ถ์ธ๋ค | |
| batch_idx = torch.zeros((rois.shape[0], 1), device=rois.device) | |
| rois_b = torch.cat([batch_idx, rois], dim=1) # [N,5] | |
| pooled = roi_align(feat, rois_b, | |
| output_size=(self.roi_size, self.roi_size), | |
| spatial_scale=1.0 / stride, | |
| sampling_ratio=2) | |
| x = pooled.flatten(1) | |
| x = self.fc(x) | |
| return self.cls(x), self.reg(x) | |
| # --------------------------------------------------------------- | |
| # ์ ์ฒด ๋ชจ๋ธ | |
| # --------------------------------------------------------------- | |
| class FasterRCNN(nn.Module): | |
| def __init__(self, num_classes): | |
| super().__init__() | |
| self.backbone = Backbone() | |
| self.rpn = RPN(self.backbone.out_channels) | |
| self.head = RoIHead(num_classes, self.backbone.out_channels) | |
| self.num_classes = num_classes | |
| self.base_anchors = generate_anchors() # 9๊ฐ | |
| self.stride = self.backbone.stride | |
| # ํ์ต/์ถ๋ก ํ์ดํผํ๋ผ๋ฏธํฐ | |
| self.rpn_pre_nms = 12000 | |
| self.rpn_post_nms_train = 2000 | |
| self.rpn_post_nms_test = 300 | |
| self.rpn_nms_thresh = 0.7 | |
| def _anchors_for(self, feat_h, feat_w, device): | |
| a = shift_anchors(self.base_anchors, feat_h, feat_w, self.stride) | |
| return a.to(device) | |
| def _proposals(self, rpn_logits, rpn_deltas, anchors, img_hw, training): | |
| """RPN ์ถ๋ ฅ โ NMS ๊ฑฐ์น ํ๋ณด์์ญ(proposal) ์์ฑ.""" | |
| scores = torch.sigmoid(rpn_logits[0]) # [K] | |
| deltas = rpn_deltas[0] # [K,4] | |
| proposals = decode_boxes(deltas, anchors) | |
| proposals = clip_boxes(proposals, img_hw[0], img_hw[1]) | |
| # ์ ์ ์์๋ง ์ถ๋ฆฐ ๋ค NMS | |
| n_pre = min(self.rpn_pre_nms, scores.numel()) | |
| top = scores.topk(n_pre).indices | |
| proposals, scores = proposals[top], scores[top] | |
| keep = nms(proposals, scores, self.rpn_nms_thresh) | |
| n_post = self.rpn_post_nms_train if training else self.rpn_post_nms_test | |
| keep = keep[:n_post] | |
| return proposals[keep], scores[keep] | |
| def forward(self, image, target=None): | |
| """ | |
| image : [1,3,H,W] ๋จ์ผ ์ด๋ฏธ์ง(batch_size=1) | |
| target : ํ์ต ์ {'boxes','labels'}, ์ถ๋ก ์ None | |
| """ | |
| device = image.device | |
| img_h, img_w = image.shape[-2:] | |
| feat = self.backbone(image) | |
| _, _, fh, fw = feat.shape | |
| anchors = self._anchors_for(fh, fw, device) | |
| rpn_logits, rpn_deltas, _ = self.rpn(feat) | |
| proposals, _ = self._proposals( | |
| rpn_logits, rpn_deltas, anchors, (img_h, img_w), | |
| training=self.training) | |
| if self.training: | |
| # ํ์ต ๊ฒฝ๋ก: ์์ค ๊ณ์ฐ (train.py ์ ํฌํผ๊ฐ ๋ด๋น) | |
| return { | |
| "feat": feat, "anchors": anchors, | |
| "rpn_logits": rpn_logits[0], "rpn_deltas": rpn_deltas[0], | |
| "proposals": proposals, "stride": self.stride, | |
| } | |
| else: | |
| # ์ถ๋ก ๊ฒฝ๋ก: ์ต์ข ํ์ง ๊ฒฐ๊ณผ | |
| cls_logits, reg = self.head(feat, proposals, self.stride) | |
| return self._postprocess(cls_logits, reg, proposals, (img_h, img_w)) | |
| def _postprocess(self, cls_logits, reg, proposals, img_hw, | |
| score_thresh=0.05, nms_thresh=0.3, max_det=100): | |
| """RoI Head ์ถ๋ ฅ โ ํด๋์ค๋ณ NMS โ ์ต์ข ๋ฐ์ค/๋ผ๋ฒจ/์ ์.""" | |
| probs = F.softmax(cls_logits, dim=1) # [N, C] | |
| N, C = probs.shape | |
| reg = reg.reshape(N, C, 4) | |
| all_boxes, all_scores, all_labels = [], [], [] | |
| for c in range(1, C): # 0=๋ฐฐ๊ฒฝ ์ ์ธ | |
| scores_c = probs[:, c] | |
| mask = scores_c > score_thresh | |
| if mask.sum() == 0: | |
| continue | |
| boxes_c = decode_boxes(reg[mask, c], proposals[mask]) | |
| boxes_c = clip_boxes(boxes_c, img_hw[0], img_hw[1]) | |
| scores_c = scores_c[mask] | |
| keep = nms(boxes_c, scores_c, nms_thresh) | |
| all_boxes.append(boxes_c[keep]) | |
| all_scores.append(scores_c[keep]) | |
| all_labels.append(torch.full((keep.numel(),), c, | |
| dtype=torch.int64, device=boxes_c.device)) | |
| if not all_boxes: | |
| return {"boxes": torch.empty((0, 4)), "labels": torch.empty((0,), dtype=torch.int64), | |
| "scores": torch.empty((0,))} | |
| boxes = torch.cat(all_boxes) | |
| scores = torch.cat(all_scores) | |
| labels = torch.cat(all_labels) | |
| # ์ ์ฒด์์ ์ ์ ์์ max_det๊ฐ๋ง | |
| if scores.numel() > max_det: | |
| top = scores.topk(max_det).indices | |
| boxes, scores, labels = boxes[top], scores[top], labels[top] | |
| return {"boxes": boxes, "labels": labels, "scores": scores} | |