๐ PyTorch Tutorials
Task : Penn-Fudan Database for Pedestrian Detection and Segmentation ๋ฐ์ดํฐ์
์ผ๋ก ๋ฏธ๋ฆฌ ํ์ต๋ Mask R-CNN ๋ชจ๋ธ์ Fine-Tuning ํด๋ณด๊ธฐ
โ ์ฌ๋ ์ฌ๋ถ๋ฅผ ํ์
ํ๋ Instance Segmentation ๋ชจ๋ธ ํ์ต
Mask R-CNN
Faster R-CNN์ RPN์์ ์ป์ RoI(Region of Interest)์ ๋ํ์ฌ ๊ฐ์ฒด์ ํด๋์ค๋ฅผ ์์ธกํ๋ classification branch + bbox regression์ ์ํํ๋ bbox regression branch + ํํ์ผ๋ก segmentation mask๋ฅผ ์์ธกํ๋ mask branch
โ ๋ฌผ์ฒด๊ฐ ์์์ง๋ ๋ชจ๋ฅด๋ ์์น์ ํ๋ณด ์์ญ์ ์ ์ํ๋ ๋ถ๋ถ(RoI) :selective search
,RPN(Region Proposal Network)
๋ฑ์ ์ด์ฉํด ํน์ง์ ์ถ์ถํจ โ ์ฃผ์ด์ง RoI๋ค์ ๋ํด ํด๋์ค๋ฅผ ๋ถ๋ฅํ๊ณ bbox๋ฅผ ํ๊ทํจ
- mask branch : ๊ฐ๊ฐ์ RoI์ ์์ ํฌ๊ธฐ์ FC Network๊ฐ ์ถ๊ฐ๋ ํํ
- mask : ํด๋์ค์ ๋ฐ๋ผ ๋ถํ ๋ ์ด๋ฏธ์ง ์กฐ๊ฐ
import numpy as np
import torch
import torch.utils.data
from PIL import Image
class PennFudanDataset(torch.utils.data.Dataset):
def __init__(self, root, transforms=None):
self.root = root
self.transforms = transforms
# load all image files, sorting them to
# ensure that they are aligned
self.imgs = list(sorted(os.listdir(os.path.join(root, "PNGImages"))))
self.masks = list(sorted(os.listdir(os.path.join(root, "PedMasks"))))
def __getitem__(self, idx):
# load images ad masks
img_path = os.path.join(self.root, "PNGImages", self.imgs[idx])
mask_path = os.path.join(self.root, "PedMasks", self.masks[idx])
img = Image.open(img_path).convert("RGB")
# note that we haven't converted the mask to RGB,
# because each color corresponds to a different instance
# with 0 being background
mask = Image.open(mask_path)
mask = np.array(mask)
obj_ids = np.unique(mask) # ๊ฐ๊ฐ ๋ค๋ฅธ ์์ผ๋ก ์ธ์ฝ๋ฉ๋ ์ธ์คํด์ค๋ค
# first id is the background, so remove it
obj_ids = obj_ids[1:]
# split the color-encoded mask into a set
# of binary masks
masks = mask == obj_ids[:, None, None]
# get bounding box coordinates for each mask
num_objs = len(obj_ids)
boxes = []
for i in range(num_objs):
pos = np.where(masks[i])
xmin = np.min(pos[1])
xmax = np.max(pos[1])
ymin = np.min(pos[0])
ymax = np.max(pos[0])
boxes.append([xmin, ymin, xmax, ymax])
boxes = torch.as_tensor(boxes, dtype=torch.float32)
# only one class(=label) : ์ฌ๋์ ์ฐพ์๋ด๋ ๊ฒ์ด ๋ชฉํ๋ผ์
labels = torch.ones((num_objs,), dtype=torch.int64) # shape : (num_objs, )
masks = torch.as_tensor(masks, dtype=torch.uint8)
image_id = torch.tensor([idx])
area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
# suppose all instances are not crowd
iscrowd = torch.zeros((num_objs,), dtype=torch.int64)
target = {}
target["boxes"] = boxes
target["labels"] = labels
target["masks"] = masks
target["image_id"] = image_id
target["area"] = area
target["iscrowd"] = iscrowd # ๋ฌผ์ฒด๊ฐ ๋๋ฌด ์์๋ฐ ๋ง์์ ํ๋์ ๊ตฐ์ง์ผ๋ก ๋ฐ์ค๋ฅผ ์ฒ๋ฆฌํ์ฌ ๋ ์ด๋ธ๋งํ๋์ง์ ๊ดํ ์ฌ๋ถ
if self.transforms is not None:
img, target = self.transforms(img, target)
return img, target
def __len__(self):
return len(self.imgs)
__init__
๋ฉ์๋ : ์ด๋ฏธ์ง๋ค์ ๊ฒฝ๋ก๋ฅผ ๋ฐ์์ ์ ๋ ฌํ ํ imgs, masks๋ก ์ฌ์ฉ__getitem()
๋ฉ์๋ : ๊ฒฝ๋ก๋ค์ idx๋ก ์ ๊ทผํ์ฌ ์ด๋ฏธ์ง๋ฅผ open()
ํ๊ณ mask๊ฐ ์๋ img์ ๋ํด์๋ง RGB๋ก ๋ณํ (mask์ ๊ฐ ์๊น์ ๋ค๋ฅธ ์ธ์คํด์ค๋ฅผ ์๋ฏธํ๊ธฐ ๋๋ฌธ์ ์์๋ก RGB๋ก ๋ณํํ๋ฉด ์๋จ) & target ๋์
๋๋ฆฌ์ bbox, labels, masks, img_id, area, iscrowd ์ ๋ณด ๋ด์ ๋ฐํmaskrcnn_resnet50_fpn
: backbone = resnet50 & head = fpn FPN (Feature Pyramid Network)
Top-down ๋ฐฉ์์ผ๋ก ํน์ง์ ์ถ์ถํ๋ฉฐ, ๊ฐ ์ถ์ถ๋ ๊ฒฐ๊ณผ๋ค์ธ low-resolution ๋ฐ high-resolution๋ค์ ๋ฌถ๋ ๋ฐฉ์
๊ฐ ๋ ๋ฒจ์์ ๋ ๋ฆฝ์ ์ผ๋ก ํน์ง์ ์ถ์ถํ์ฌ ๊ฐ์ฒด๋ฅผ ํ์งํ๋๋ฐ ์์ ๋ ๋ฒจ์ ์ด๋ฏธ ๊ณ์ฐ๋ ํน์ง์ ์ฌ์ฌ์ฉํ๋ฏ๋ก ๋ฉํฐ ์ค์ผ์ผ ํน์ง๋ค์ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์
- forward์์ ์ถ์ถ๋ ์๋ฏธ ์ ๋ณด๋ค์ top-down ๊ณผ์ ์์ upsamplingํ์ฌ ํด์๋๋ฅผ ์ฌ๋ฆผ
- forward์์ ์์ค๋ ์ ๋ณด๋ค์ skip connection์ผ๋ก ๋ณด์ถฉํจ
- Bottom-up pathway
: Backbone ConvNet์ Feedforward ๊ณ์ฐ โ ๋งค ์ธต๋ง๋ค ์๋ฏธ ์ ๋ณด๋ฅผ ์์ถํ๋ ์ญํ
๊ฐ ๋จ๊ณ(๋ ์ด์ด๋ค)์ ๋ง์ง๋ง ๋ ์ด์ด์ ์ถ๋ ฅ = feature map์ Reference Set
- Top-down pathway and lateral connection
: feature map์ upsamplingํ์ฌ ๋ ๋์ ํด์๋์ ์ด๋ฏธ์ง๋ฅผ ๋ง๋๋ ์ญํ
skip-connection์ ํตํด ๊ฐ์ ์ฌ์ด์ฆ์ bottom-up ๋ ์ด์ด์ ํฉ์ณ์ ์์ค๋ ์ง์ญ์ ์ ๋ณด๋ฅผ ๋ณด์ถฉํจ
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=3,gamma=0.1)
์ฒ์๋ถํฐ ๋๊น์ง ๊ฐ์ learning rate๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค ์ฒ์์๋ ํฐ ๋ณดํญ์ผ๋ก ๋น ๋ฅด๊ฒ ์ต์ ํํ๊ณ ์ต์ ๊ฐ์ ๊ฐ๊น์์งใน์๋ก ๋ณดํญ์ ์ค์ฌ ๋ฏธ์ธ์กฐ์ ํ๋ ๊ฒ์ด ํ์ต์ด ๋ ์๋๋ค๊ณ ์๋ ค์ ธ ์์
<Learnging rate scheduler>
step 1. optimizer์ scheduler ์ ์ํ๊ธฐ
step 2. ํ์ต ์ batch๋ง๋คoptimizer.step()
, epoch๋ง๋คscheduler.step()
for epoch in range(epochs): for i, (data) in enumerate(data_loader): x_data, y_data = data optimizer.zero_grad() estimated_y = model(x_data) loss = loss(y_data, estimated_y) loss.backward() optimizer.step() scheduler.step()
step size๋ง๋ค gamma ๋น์จ๋ก lr์ ๊ฐ์์ํค๋
StepLR()
์ธ์๋
-LambdaLR()
: ์ด๊ธฐ lr์ lambdaํจ์์์ ๋์จ ๊ฐ์ ๊ณฑํด์ lr์ ์กฐ์ ํจ
-MultiplicativeLR()
: ์ด๊ธฐ lr์ lambdaํจ์์์ ๋์จ ๊ฐ์ ๋์ ๊ณฑํด์ lr์ ์กฐ์ ํจ
-MultiStepLR()
: lr์ ๊ฐ์์ํฌ epoch์ ์ง์ ์ง์ ํด์ค
๋ฑ PyTorch๊ฐ ์ ๊ณตํ๋ ๋ค์ํ Learning rate scheduler๊ฐ ์์
Task : ImageNet์ฒ๋ผ ๋งค์ฐ ํฐ ๋ฐ์ดํฐ์ ์ ํตํด ์ฌ์ ํ์ต๋ ํฉ์ฑ๊ณต ์ ๊ฒฝ๋ง์์ ๋ง์ง๋ง FC layer๋ง ์๋ก์ด ๋๋ค ๊ฐ์ค์น๋ก ๋์ฒด์์ผ ์ด ์ธต๋ง ํ์ต์ํค๊ธฐ
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']}
โ ๊ฐ ์ด๋ฏธ์ง๋ค์ด ์์ ์ ํด๋์ค ์ด๋ฆ์ผ๋ก ๋ ํด๋ ์์ ๋ค์ด๊ฐ ์๋ ๊ตฌ์กฐ๋ผ๋ฉด, ImageFolder ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ ๊ฐ์ฒด๋ก ๋ง๋ค ์ ์์
next()
, iter()
inputs, classes = next(iter(dataloaders['train']) # ๋ฐ์ดํฐ์
์์ ํ๋์ ๋ฐฐ์น ๋ฐ์์ค๊ธฐ
๋ฏธ๋ฆฌ ํ์ตํ ๋ชจ๋ธ์ ๋ถ๋ฌ์จ ํ ๋ง์ง๋ง ๊ณ์ธต์ ์ ์ธํ ๋ชจ๋ ๋ถ๋ถ์ ๊ณ ์ ์ํค๊ณ ๋ง์ง๋ง FC layer๋ง finetuningํจ
model_conv = torchvision.models.resnet18(weights='IMAGENET1K_V1')
for param in model_conv.parameters():
param.requires_grad = False # ๋ชจ๋ธ์ ๋ชจ๋ layer ๊ณ ์ ์ํค๊ธฐ
# ์๋ก ์์ฑ๋ layer(= nn.Linear())์ ๋งค๊ฐ๋ณ์๋ ๊ธฐ๋ณธ๊ฐ์ด ``requires_grad = True``์
num_ftrs = model_ft.fc.in_features # fc layer์ ์
๋ ฅ ์ฑ๋ ์ ์ป๊ธฐ
model_ft.fc = nn.Linear(num_ftrs, 2) # fc layer์ ์ถ๋ ฅ์ ์ฐ๋ฆฌ์ task(๊ฐ๋ฏธ/๋ฒ ๋ถ๋ฅ)์ ๋ง๊ฒ ์์ ํ๊ธฐ
model_ft = model_ft.to(device)
FGSM & PGD
: ์ ๋์ ๊ณต๊ฒฉ์ ์
- FGSM(Fast Gradient Sign Method)
์ผ ๋, (= perturbation)๊ฐ ์ถฉ๋ถํ ์์ ๊ฒฝ์ฐ ๋ถ๋ฅ๊ธฐ๋ ์ ๋ฅผ ๊ฐ์ ํด๋์ค๋ก ๋ถ๋ฅํจ
โ ๋ ์ฐจ์ ์ ๋น๋กํ๊ฒ ์ฆ๊ฐํ ์ ์์ผ๋ฉฐ, ๋์ ์ฐจ์์ ๋ฌธ์ ์์ input์ ์์ noise๊ฐ ouput์ ํฐ ์ฐจ์ด๋ฅผ ๋ง๋ค ์ ์์
์ผ๋ฐ์ ์ผ๋ก ๋ชจ๋ธ์ ํ์ต์ํฌ ๋๋ ์์ค์ด ๊ฐ์ฅ ๋ฎ์์ง๋ ์ง์ ์ ์ฐพ์
FGSM : ์ด๋ฏธ ๋ชจ๋ธ์ ํ์ต์ด ๋๋ ์ํ์ด๋ฏ๋ก ํ๋ผ๋ฏธํฐ๊ฐ ๊ณ ์ ๋๊ณ ๋ฐ์ดํฐ์ ์กฐ์์ ๊ฐํจ
โ ์์ค์ด ๊ฐ์ฅ ๋์์ง๋ ๋ฐฉํฅ(= ๋ชจ๋ธ์ด ์ต๋ํ ์ค๋ต์ ๋ด๋๋ก)์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๋ณํํจdef fgsm_attack(image, epsilon, data_grad): sign_data_grad = data_grad.sign() # data_grad ์ ์์๋ณ ๋ถํธ ๊ฐ perturbed_image = image + epsilon*sign_data_grad # ์ ๋ ฅ ์ด๋ฏธ์ง์ ๊ฐ ํฝ์ ์ sign_data_grad ๋ฅผ ์ ์ฉํด ์์ ๋ณํ๊ฐ ์ ์ฉ๋ ์ด๋ฏธ์ง๋ฅผ ์์ฑ perturbed_image = torch.clamp(perturbed_image, 0, 1) # clipping๊ฐ ๋ฒ์๋ฅผ [0,1]๋ก ์ ์ง return perturbed_image
- PGD
: FGSM ๋ฐฉ๋ฒ์ ์์ฉํ ๊ฒ์ผ๋ก, ๋ฒ์ step๋งํผ ๊ณต๊ฒฉ์ ๋ฐ๋ณตํ๋๋ฐ, ๊ฐ step๋ง๋ค ์ด ์๋ learning rate๋งํผ ๋ฐ์ดํฐ ์ ๋ณํ์ด ์ผ์ด๋๋๋ก ํจ
perturbed_data = fgsm_attack(data, epsilon, data_grad)
: ๋ณํ๋ ๊ฐ์ ์ด์ฉํด ๋
ธ์ด์ฆ ์ถ๊ฐ๋ ์ด๋ฏธ์ง ์์ฑoutput = model(perturbed_data)
: ๋
ธ์ด์ฆ ์ถ๊ฐ๋(= ๊ณต๊ฒฉ ๋ฐ์) ์ด๋ฏธ์ง๋ก ์ฌ๋ถ๋ฅโ ์ ํ๋์ ์ ๊ด๊ณ : trade-off
= ์ด ์ฆ๊ฐํจ(= ๋
ธ์ด์ฆ๊ฐ ๋ ์ปค์ง)์ ๋ฐ๋ผ ํ
์คํธ ์ ํ๋๊ฐ ๊ฐ์ํจ
[์ผ๋ผ์ค ์ฐฝ์์์๊ฒ ๋ฐฐ์ฐ๋ ๋ฅ๋ฌ๋] ์ฝ๋์๋
real image = 0 / generator๋ก๋ถํฐ ์์ฑ๋ fake image = 1๋ก ๋ผ๋ฒจ๋ง๋์ด ์์ด
ํ๊ธฐ ๋ด์ฉ์ด PyTorch ํํ ๋ฆฌ์ผ๊ณผ ๋ฐ๋์
โ "๋ณํ๋(gradient)๋ฅผ ์์น(ascending)์ํค๋ฉฐ ํ๋ จโ
= ์ต๋ํ์ํค๊ธฐ
= , ์ผ๋ก ์ ํ๋ณํ๋๋ก ํ์ต์ํค๊ธฐ
= ์ต์ํ์ํค๊ธฐ
= ์ด ๋์ค๋๋ก ํ์ต์ํค๊ธฐ (์์ฑ์๊ฐ ๋ง๋ ๊ฐ์ง ์ด๋ฏธ์ง๋ฅผ ํ๋ณ์๊ฐ ์ค์ ์ด๋ฏธ์ง(1)๋ผ๊ณ ์์ธกํ๋๋ก)
โ ์ต๋ํ์ํค๊ธฐ (์ ๋ฐฉ์์ด ํ์ต์ด ์ ์๋ผ์)
generator๋ก ์์ฑํ ์ด๋ฏธ์ง๋ฅผ real image๋ผ๊ณ ์์ฌ์ ๋ผ๋ฒจ๋งํ ํ ์ด๋ฏธ ํ์ต ์๋ฃ๋ Discriminator์๊ฒ ์ ๋ฌ
โ Discriminator๋ ์
๋ ฅ ๋ฐ์ดํฐ๋ฅผ fake image๋ผ ํ๋ณ
โ ์
๋ ฅ label(= ์์ฑ๋ ์ด๋ฏธ์ง๋ฅผ real์ด๋ผ ๊ฐ์ง๋ก ๋ผ๋ฒจ๋ง)๊ณผ ํ๋ณ๊ฐ ๊ฐ loss๋ฅผ ์ค์ด๋๋ก Generator ํ์ต
+) Discriminator๋ฅผ ํ์ต์ํฌ ๋๋ real image์ Generator๋ก๋ถํฐ ์์ฑ๋ fake image๋ฅผ ๋ ๋ค ์ฌ์ฉํ์ง๋ง,
Generator๋ฅผ ํ์ต์ํฌ ๋๋ real image๋ฅผ ์ฌ์ฉํ ์ ์๊ณ Discriminaotr๋ก๋ถํฐ ์ป์ด์ง๋ ์ ๋ณด๋ฅผ ํตํด real image๋ฅผ ๋ณด์ง ์๊ณ ๋ ์ต๋ํ ๋น์ทํ๊ฒ ๊ฐ์ง ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋๋ก ํ๋ จํจ
๊ตฌ๋ถ์์์๋ Conv2d()
๋ก ์ธต์ ์๋ ๋ฐ๋ฉด, ์์ฑ์์์๋ ConvTranspose2d()
๋ก ์ธต์ ์์
Convoluation Layer
: down-sampling์ ํจ๊ณผ๊ฐ ์์ (input ์ฐจ์ > output ์ฐจ์)
Transposed Convolutional Layer
: ์๋ณธ ์ธต๊ณผ ๊ฐ์ ๊ณต๊ฐ ์ฐจ์์ผ๋ก up-sampling (input ์ฐจ์ < output ์ฐจ์)
์๋ณธ size = 5 x 6
์ด๊ณ ์
๋ ฅ size = 2 x 3
์ผ ๋, kernel size = 4 x 4
๋ก ConvTranspose2d()
ํ๊ฒ ๋๋ฉด 5x6ํฌ๊ธฐ์ ๊ฐ์ tensor๊ฐ ๊ตฌํด์ง๊ณ ์ด๋ฅผ ๋ํด ์ต์ข
์ถ๋ ฅ์ ๋ง๋ค ์ ์์
๐ ์ฐธ๊ณ ์๋ฃ
์ด๋ฏธ์ง ๋ถ๋ฅ ๋ฌธ์ ์์๋ ์ด๋ฏธ์ง๊ฐ ๋ณํ๋๋๋ผ๋ ๊ทธ ์ด๋ฏธ์ง๋ก ์ธ์ํ๋ ๊ฒ(spatial invariance)๊ฐ ์ค์ํ๋ฐ
์ด๋ฅผ ์ํด CNN์์๋ max pooling layer๊ฐ ํ์ํ ๋ฐ๋ฉด,
Spatial Transformation์ affine transformation(์ด๋ฏธ์ง์ ํน์ ๋ถ๋ถ์ ์๋ฅด๊ณ ๋ณํํด์ ๊ทธ ๋ถ๋ถ๋ง ๋ผ์ด์ ํ๋ จ์ํด)์ ์ด์ฉํจ
โ spatial transform ๋ชจ๋์ ํตํด ์ฐ๊ทธ๋ฌ์ง์ด๋ ํ์ ๋ฑ์ ๋ ธ์ด์ฆ๊ฐ ์ฒจ๊ฐ๋ ์ด๋ฏธ์ง๋ฅผ ์ถ๋ก ํ์ฌ ์ ์ ํ ์์ํ์ ๋์ถํ ์ ์์
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
# Spatial transformer localization-network
self.localization = nn.Sequential(
nn.Conv2d(1, 8, kernel_size=7),
nn.MaxPool2d(2, stride=2),
nn.ReLU(True),
nn.Conv2d(8, 10, kernel_size=5),
nn.MaxPool2d(2, stride=2),
nn.ReLU(True)
)
# Regressor for the 3 * 2 affine matrix
self.fc_loc = nn.Sequential(
nn.Linear(10 * 3 * 3, 32),
nn.ReLU(True),
nn.Linear(32, 3 * 2)
)
# Initialize the weights/bias with identity transformation
self.fc_loc[2].weight.data.zero_()
self.fc_loc[2].bias.data.copy_(torch.tensor([1, 0, 0, 0, 1, 0], dtype=torch.float))
# Spatial transformer network forward function
def stn(self, x):
xs = self.localization(x)
xs = xs.view(-1, 10 * 3 * 3)
theta = self.fc_loc(xs)
theta = theta.view(-1, 2, 3)
grid = F.affine_grid(theta, x.size())
x = F.grid_sample(x, grid)
return x
def forward(self, x):
# transform the input
x = self.stn(x)
# Perform the usual forward pass
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
model = Net().to(device)
CNN ๋ชจ๋ธ ์ฝ๋ ์์ STN์ด ์ถ๊ฐ๋๋ ๊ฒ์ ํ์ธํ ์ ์์