본문 바로가기
RCNN - Imagenet:Resnet (세브란스) #인공지능(Classic)

resnet과 imagenet의 차이는 무엇임?

ResNet과 ImageNet은 딥러닝과 컴퓨터 비전 분야에서 중요한 개념이지만, 서로 다른 목적을 가지고 있음.

ResNet (Residual Network)

ResNet은 마이크로소프트 연구팀이 개발한 딥러닝 모델 아키텍처임.
ResNet의 주요 목적은 네트워크의 깊이가 증가함에 따라 발생하는 성능 저하 문제를 해결하는 것입니다. Residual learning 개념을 도입하여 더 깊은 네트워크를 효과적으로 학습할 수 있게 했음.

ImageNet

ImageNet은 대규모 이미지 데이터셋임

  • 수백만 개의 레이블이 지정된 이미지를 포함
  • 1000개 이상의 카테고리로 분류
  • 컴퓨터 비전 연구 및 모델 평가에 널리 사용됨

ImageNet의 주요 목적은 컴퓨터 비전 알고리즘을 훈련하고 평가하기 위한 표준화된 대규모 데이터셋을 제공하는 것임.

결국,

  1. 성격: ResNet은 모델 아키텍처이고, ImageNet은 데이터셋입니다.
  2. 용도: ResNet은 이미지 분류 등의 작업을 수행하는 데 사용되는 모델이며, ImageNet은 이러한 모델들을 훈련하고 평가하는 데 사용되는 데이터입니다.
  3. 개발 목적: ResNet은 더 깊은 네트워크의 학습 문제를 해결하기 위해 개발되었고, ImageNet은 컴퓨터 비전 연구를 위한 표준 데이터셋을 제공하기 위해 만들어졌습니다.

그렇다면, tf.keras.application.resnet.ResNet50(weights='imagenet') 코드의 의미는 ResNet을 imagenet으로 학습시킨 모델을 말하는 것임?

그렇다고 볼수 있음. 해당 코드는 ImageNet 데이터셋으로 사전 학습된 ResNet50 모델을 불러오는 것을 의미함.

  1. tf.keras.applications.resnet.ResNet50: TensorFlow의 Keras API에서 제공하는 ResNet50 모델 아키텍처를 지정
  2. weights='imagenet': 이 매개변수는 모델의 가중치를 어떻게 초기화할지 지정. 'imagenet'으로 설정하면 ImageNet 데이터셋으로 사전 학습된 가중치를 사용한다는 의미임.

따라서 이 코드는 ImageNet으로 사전 학습된 ResNet50 모델을 불러와 사용할 준비를 하는 것임. 이 모델은 1000개의 클래스를 분류할 수 있으며, 다양한 컴퓨터 비전 작업에 활용될 수 있음.

 

그렇다면, ResNet50의 50은 버전인가?
ResNet50의 '50'은 버전 번호가 아니라 모델의 깊이, 즉 레이어의 수를 나타는 것임.

예를 들어 ResNet 아키텍처는 다양한 깊이로 구현될 수 있음.

  • ResNet18: 18개 레이어
  • ResNet34: 34개 레이어
  • ResNet50: 50개 레이어
  • ResNet101: 101개 레이어
  • ResNet152: 152개 레이어

ResNet은 CNN임?

당연함. ResNet은 CNN(Convolutional Neural Network, 합성곱 신경망)의 한 종류임. ResNet의 주요 특징은
잔차 학습: ResNet의 가장 큰 특징은 잔차 학습(Residual Learning) 개념을 도입한 것입니다. 이는 기존 CNN의 한계를 극복하고 더 깊은 네트워크를 효과적으로 학습할 수 있게 함
Skip Connections: ResNet은 Skip Connection(또는 Shortcut Connection)을 사용하여 기울기 소실 문제를 해결하고 더 깊은 네트워크 학습을 가능하게 함.

그러면, 50개 layer의 구성은 어떻게 됨? Convolution 몇개, 풀링 몇개 FC 몇개 이런식으로 설명해 보면,

  1. 컨볼루션 레이어 (Convolution layers): 총 49개
    • 1개의 7x7 컨볼루션 레이어 (첫 번째 레이어)
    • 48개의 1x1 및 3x3 컨볼루션 레이어 (16개의 병목 블록에 각 3개씩)
  2. 풀링 레이어 (Pooling layers): 총 2개
    • 1개의 max pooling 레이어 (초기 단계)
    • 1개의 average pooling 레이어 (마지막 단계)
  3. 완전 연결 레이어 (Fully Connected layer): 1개
    • 마지막에 1000개의 유닛을 가진 FC 레이어 (ImageNet 분류용)

이렇게 해서 컨볼루션 레이어 49개, 풀링 레이어 2개, FC 레이어 1개로 총 52개의 레이어가 있지만, 관례적으로 컨볼루션과 FC 레이어만 세어 "ResNet50"이라고 부름.

출력의 FC가 1000개면 1000개의 이미지를 구분함? ResNet50의 마지막 완전 연결 계층(Fully Connected layer, FC)에 1000개의 유닛이 있다면, 이는 1000개의 서로 다른 클래스(또는 카테고리)를 분류할 수 있음을 의미함.

  1. ImageNet 데이터셋: ResNet50이 1000개의 출력 유닛을 가진 이유는 주로 ImageNet 데이터셋에서 사전 훈련되었기 떄문임. ImageNet은 1000개의 서로 다른 객체 카테고리를 포함함.
  2. 소프트맥스 활성화: 마지막 FC 레이어는 일반적으로 소프트맥스 활성화 함수를 사용함. 이는 1000개의 출력을 확률 분포로 변환
  3. 각 유닛의 의미: 각 출력 유닛은 특정 클래스에 대한 확률을 나타냄. 예를 들어, 첫 번째 유닛은 '개'일 확률, 두 번째 유닛은 '고양이'일 확률 등을 나타낼 수 있음.
  4. 예측 과정: 이미지를 분류할 때, 모델은 1000개의 확률 값을 출력하고, 가장 높은 확률을 가진 클래스가 예측 결과로 선택함.
  5. 전이 학습: 다른 데이터셋이나 작업에 ResNet50을 사용할 때는, 마지막 FC 레이어를 새로운 작업에 맞게 조정할 수 있음. 예를 들어, 10개의 클래스만 있는 데이터셋에 사용한다면, FC 레이어를 10개의 유닛으로 변경할 수 있음. 이것이 전이학습의 가장 중요한 부분임.

그러면, ResNet을 RCNN으로 사용할 수 있음? 당연히 ResNet을 R-CNN (Region-based Convolutional Neural Network)의 백본(backbone) 네트워크로 사용할 수 있음. 실제로 ResNet은 많은 객체 탐지 모델에서 특징 추출기로 널리 사용됨. ResNet을 R-CNN 계열 모델에 적용하는 방법이 있음.

  1. 특징 추출기로 사용:
    ResNet은 이미지에서 강력한 특징을 추출할 수 있는 능력이 있어, R-CNN의 CNN 부분을 대체할 수 있음.
  2. 전이 학습:
    ImageNet 등의 대규모 데이터셋에서 사전 학습된 ResNet 모델을 사용하여 전이 학습을 수행할 수 있음.
  3. Fine-tuning:
    객체 탐지 작업에 맞게 ResNet의 상위 레이어를 미세 조정할 수 있음.
  4. 다양한 R-CNN 변형에 적용:
    Fast R-CNN, Faster R-CNN, Mask R-CNN 등 다양한 R-CNN 계열 모델에 ResNet을 백본으로 사용함.
  5. Feature Pyramid Network (FPN)와 결합:
    ResNet은 FPN과 결합하여 다중 스케일 특징 맵을 생성하는 데 효과적으로 사용될 수 있음.

RCNN을 ResNet으로 구현하고 추가 학습할 수 있는 방법은,

RCNN with ResNet: 구현과 추가 학습

ResNet 백본 네트워크 구현

  • ResNet-50 구조 상세 설명
  • PyTorch를 이용한 ResNet-50 구현

Region Proposal Network (RPN) 구현

  • RPN의 역할과 구조
  • ResNet 특징 맵을 이용한 RPN 구현

ROI Pooling 및 분류기 구현

  • ROI Pooling 레이어 구현
  • 객체 분류 및 바운딩 박스 회귀 헤드 구현

손실 함수 정의

  • 분류 손실과 바운딩 박스 회귀 손실 구현
  • 다중 작업 학습을 위한 손실 조합

모델 학습

학습 파이프라인 구축

  • 데이터 로더 설정
  • 옵티마이저 및 학습률 스케줄러 설정

학습 루프 구현

  • 배치 처리 및 그래디언트 계산
  • 검증 단계 구현

평가 지표

  • mAP (mean Average Precision) 계산
  • IoU (Intersection over Union) 기반 평가

모델 개선 전략

  • 앙상블 기법 적용
  • 후처리 기법 (NMS, Soft-NMS) 구현

실시간 객체 탐지 시스템 구축

  • 웹캠 또는 비디오 스트림에서의 객체 탐지
  • 성능 최적화 기법

커스텀 데이터셋에 대한 적용

  • 새로운 객체 클래스 추가 방법
  • 도메인 특화 데이터셋 구축 및 학습

구현 준비

개발 환경 설정

import torch
import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader

# GPU 사용 가능 여부 확인
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")

데이터셋 준비

# COCO 데이터셋 사용 예시
from torchvision.datasets import CocoDetection

transform = transforms.Compose([
    transforms.Resize((800, 800)),
    transforms.ToTensor(),
])

coco_train = CocoDetection(root="path/to/coco/train2017",
                           annFile="path/to/coco/annotations/instances_train2017.json",
                           transform=transform)

train_loader = DataLoader(coco_train, batch_size=4, shuffle=True, num_workers=4)

ResNet 기반 RCNN 구현

ResNet 백본 네트워크 구현

import torchvision.models as models

class ResNetBackbone(torch.nn.Module):
    def __init__(self):
        super(ResNetBackbone, self).__init__()
        resnet = models.resnet50(pretrained=True)
        self.features = torch.nn.Sequential(*list(resnet.children())[:-2])

    def forward(self, x):
        return self.features(x)

backbone = ResNetBackbone().to(device)

Region Proposal Network (RPN) 구현

class RPN(torch.nn.Module):
    def __init__(self, in_channels):
        super(RPN, self).__init__()
        self.conv = torch.nn.Conv2d(in_channels, 512, kernel_size=3, padding=1)
        self.cls_score = torch.nn.Conv2d(512, 9 * 2, kernel_size=1)
        self.bbox_pred = torch.nn.Conv2d(512, 9 * 4, kernel_size=1)

    def forward(self, x):
        x = torch.relu(self.conv(x))
        cls_score = self.cls_score(x)
        bbox_pred = self.bbox_pred(x)
        return cls_score, bbox_pred

rpn = RPN(2048).to(device)  # ResNet50의 마지막 feature map의 채널 수는 2048

ROI Pooling 및 분류기 구현

from torchvision.ops import RoIPool

class RCNNHead(torch.nn.Module):
    def __init__(self, num_classes):
        super(RCNNHead, self).__init__()
        self.roi_pool = RoIPool((7, 7), spatial_scale=1/32)
        self.fc = torch.nn.Linear(2048 * 7 * 7, 1024)
        self.cls_score = torch.nn.Linear(1024, num_classes)
        self.bbox_pred = torch.nn.Linear(1024, num_classes * 4)

    def forward(self, features, rois):
        x = self.roi_pool(features, rois)
        x = x.view(x.size(0), -1)
        x = torch.relu(self.fc(x))
        cls_score = self.cls_score(x)
        bbox_pred = self.bbox_pred(x)
        return cls_score, bbox_pred

rcnn_head = RCNNHead(num_classes=81).to(device)  # COCO 데이터셋은 80개 클래스 + 배경

학습 파이프라인 구축

optimizer = torch.optim.Adam([
    {'params': backbone.parameters()},
    {'params': rpn.parameters()},
    {'params': rcnn_head.parameters()}
], lr=0.001)

scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)

학습 루프 구현

def train_one_epoch(model, dataloader, optimizer):
    model.train()
    total_loss = 0
    for images, targets in dataloader:
        images = images.to(device)
        targets = [{k: v.to(device) for k, v in t.items()} for t in targets]

        loss_dict = model(images, targets)
        losses = sum(loss for loss in loss_dict.values())

        optimizer.zero_grad()
        losses.backward()
        optimizer.step()

        total_loss += losses.item()

    return total_loss / len(dataloader)

# 학습 루프
num_epochs = 10
for epoch in range(num_epochs):
    loss = train_one_epoch(model, train_loader, optimizer)
    scheduler.step()
    print(f"Epoch {epoch+1}/{num_epochs}, Loss: {loss:.4f}")

추가 학습 및 미세 조정
데이터 증강 기법

from torchvision.transforms import functional as F

class Compose:
    def __init__(self, transforms):
        self.transforms = transforms

   def __call__(self, image, target):
        for t in self.transforms:
            image, target = t(image, target)
        return image, target

class RandomHorizontalFlip:
    def __init__(self, prob):
        self.prob = prob

   def __call__(self, image, target):
        if random.random() < self.prob:
            height, width = image.shape[-2:]
            image = F.hflip(image)
            bbox = target["boxes"]
            bbox[:, [0, 2]] = width - bbox[:, [2, 0]]
            target["boxes"] = bbox
        return image, target

# 데이터 증강 적용
transforms = Compose([
    RandomHorizontalFlip(0.5),
])

dataset = CocoDetection(root="path/to/coco", annFile="path/to/annotations", transforms=transforms)

성능 평가 및 개선

from torchvision.ops import box_iou

def calculate_mAP(pred_boxes, pred_labels, pred_scores, true_boxes, true_labels):
    ap_per_class = []
    for c in range(num_classes):
        true_class = true_labels == c
        pred_class = pred_labels == c

        if true_class.sum() == 0 and pred_class.sum() == 0:
            continue
        elif true_class.sum() == 0 or pred_class.sum() == 0:
            ap_per_class.append(0)
            continue

        # 해당 클래스에 대한 예측과 실제 박스
        true_class_boxes = true_boxes[true_class]
        pred_class_boxes = pred_boxes[pred_class]
        pred_class_scores = pred_scores[pred_class]

        # IoU 계산
        iou = box_iou(pred_class_boxes, true_class_boxes)

        # AP 계산 (간단한 버전)
        correct = (iou > 0.5).any(dim=1)
        ap = correct.float().mean()
        ap_per_class.append(ap.item())

    mAP = sum(ap_per_class) / len(ap_per_class)
    return mAP

# 평가 함수
def evaluate(model, dataloader):
    model.eval()
    all_pred_boxes, all_pred_labels, all_pred_scores = [], [], []
    all_true_boxes, all_true_labels = [], []

    with torch.no_grad():
        for images, targets in dataloader:
            images = images.to(device)
            outputs = model(images)

            for output, target in zip(outputs, targets):
                all_pred_boxes.append(output['boxes'].cpu())
                all_pred_labels.append(output['labels'].cpu())
                all_pred_scores.append(output['scores'].cpu())
                all_true_boxes.append(target['boxes'].cpu())
                all_true_labels.append(target['labels'].cpu())

    mAP = calculate_mAP(torch.cat(all_pred_boxes), torch.cat(all_pred_labels),
                        torch.cat(all_pred_scores), torch.cat(all_true_boxes),
                        torch.cat(all_true_labels))
    return mAP

# 평가 실행
mAP = evaluate(model, val_loader)
print(f"Mean Average Precision: {mAP:.4f}")

모델에 classification이 1000개인데 추가로 1개의 label과 그 label안에서 4개의 레벨을 구별이 가능함?

기존 ResNet50 모델을 수정하여 새로운 분류 작업을 수행하도록 하는 것은 전이학습의 좋은 예시임.

  1. 기존 ResNet50 모델의 마지막 층 제거:
base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
  1. 새로운 분류 층 추가:
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)

# 1개의 label을 위한 출력
main_output = Dense(1, activation='sigmoid', name='main_output')(x)

# 4개의 레벨을 위한 출력
level_output = Dense(4, activation='softmax', name='level_output')(x)

model = Model(inputs=base_model.input, outputs=[main_output, level_output])
  1. 모델 컴파일:
model.compile(optimizer='adam',
              loss={'main_output': 'binary_crossentropy', 
                    'level_output': 'categorical_crossentropy'},
              loss_weights={'main_output': 1.0, 'level_output': 0.5},
              metrics={'main_output': 'accuracy', 
                       'level_output': 'accuracy'})
  1. 모델 학습:
history = model.fit(X_train, 
                    {'main_output': y_train_main, 'level_output': y_train_level},
                    validation_data=(X_val, 
                                     {'main_output': y_val_main, 'level_output': y_val_level}),
                    epochs=20, batch_size=32)

이 접근 방식의 주요 포인트:

  • 기존 ResNet50의 분류 층을 제거하고 새로운 층을 추가
  • 두 개의 출력 층을 만듭니다: 하나는 메인 라벨용(1개), 다른 하나는 4개 레벨 분류용.
  • 손실 함수를 각 출력에 맞게 설정
  • 두 작업의 중요도를 조절하기 위해 loss_weights를 사용

이 방법을 통해 하나의 모델로 두 가지 관련 작업을 동시에 수행할 수 있음. 필요에 따라 레이어 구성, 손실 함수, 가중치 등을 조정하여 성능을 최적화할 수 있음.

댓글