수달이네 기술 블로그

13. Ailen vs Predator 데이터셋 2(전이학습 적용) 본문

AI공부/딥러닝

13. Ailen vs Predator 데이터셋 2(전이학습 적용)

슬픈 수달이 2026. 2. 13. 18:44

전이학습(Transfer Learning)

이미 학습된 모델을 새로운 문제에 적용하여 학습 시간을 단축시키고, 성능을 향상시키는 방법, 기존 모델이 학습한 특징으로 새로운 데이터셋의 모델의 일부만 다시 학습하거나 추가 학습(Fine-tuning)을 진행함.

  • 리소스가 제한된 상황에서 효과적인 방식
  • 이미지 분류, 자연어 처리 등의 다양한 분야에서 사용
  • 즉, 다른 문제를 해결하며 얻은 지식으로 원하는 문제를 해결함.

이미지넷

이전에 ImageNet을 보았는데, 대회에서만 사용한 데이터셋이 아닌, 사전학습에서 중요한 역할을 하며, 딥러닝 연구의 표준 벤치마크이다.

  • 1400만장의 이미지 + 22000개의 카테고리

Alexnet

https://docs.pytorch.org/vision/main/models/generated/torchvision.models.alexnet.html

파이토치에서 제공하는 Alexnet의 가중치를 확인할 수 있는데,

  • 이미지넷에서의 accuracy등을 확인 가능하다.
model = models.alexnet(weights = 'IMAGENET1K_V1').to(device)
print(model)
  • model에 pytorch의 모델 중 alexnet에서 IMAGENET1K_V1으로 사전학습을 한 버전을 다운 받는다. (pth파일을 적용)
AlexNet(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (4): ReLU(inplace=True)
    (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU(inplace=True)
    (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU(inplace=True)
    (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  )

Convolutional Layer계층

우리가 이전에 모델을 구현해 보았듯이 그 안의 내용들을 확인할 수 있다.

  • Conv2d연산: 3채널 > 64채널 (배치크기 11x11, stride 4, padding 2)
  • ReLU로 비선형화
  • Pooling으로 사이즈 감소(커널 크기 3)….
  • 여기서 dilation이 눈에 띄는데 커널 내부 간격을 조정해 넓은 영역을 커버한다. 그러나 1이므로 적용하지 않음.
  (classifier): Sequential(
    (0): Dropout(p=0.5, inplace=False)
    (1): Linear(in_features=9216, out_features=4096, bias=True)
    (2): ReLU(inplace=True)
    (3): Dropout(p=0.5, inplace=False)
    (4): Linear(in_features=4096, out_features=4096, bias=True)
    (5): ReLU(inplace=True)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )

fully connected layer 계층

학습시 라벨과 맞춰보거나 추론하는 부분이다.

  • 그러나 마지막 feature가 1000개로 나눠지는데 이걸 처리할때
  • softmax로 2개의 합이 1이 되도록 만들어 주는 방법과
  • sigmoid로 0~1사이에서 0.5보다 크면 1로 만들어주는 방법을 사용할 수 있다.

Model Freezing

for param in model.parameters():
    param.requires_glad = False

위 코드는 모델의 가중치/편향을 학습해도 업데이트 하지 않도록 함.

  • 그 이유는 우리는 위 Convolutional Layer 자체를 변화하지 않고, FCLayer(완전 연결 계층)부분만 학습시켜 변화할 것이기 때문.

Model Freezing은 전이학습에서 사전 학습된 모델의 일부, 전체 계층의 가중치를 고정해 학습하지 않도록 설정

  • 초기 계층(Convolutional Layer)은 일반적 특징(가장자리, 패턴등)을 학습하였기에 고정
  • 새로운 데이터셋의 특징을 학습하기 위해 최상위 계층(Fully connected layer, 분류헤드 등)만 학습

장점

  • 학습할 가중치 수의 감소로 계산비용 절감
  • 과적합 방지
  • 데이터가 부족할 때 유용하게 사용 가능
  • 필요에 따라 초기학습이 끝난 일부 계층을 고정해제 해 정교하게 조정 가능(Fine-Tuning)

Classifier 수정

이전에 말했듯 현재 classifier는 1000개의 feature를 내보내므로 우리가 원하는 2개의 분류와 맞지 않는다. 때문에 수정해주어야 한다.

원래 이미지의 크기 = 224 x 224인데,

AlexNet(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (4): ReLU(inplace=True)
    (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU(inplace=True)
    (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU(inplace=True)
    (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  )

위의 convolutional layer를 통과하면

Conv2d에서 이미지의 크기는 아래와 같이 감소된다.

$$ \mathrm{Output\ size} = \lfloor \frac{W+2P-K}{S}\rfloor +1 $$

  • W: 입력 크기
  • P: padding
  • K: 커널 크기
  • S: stride

maxpool에서 이미지의 크기는 아래와 같이 감소된다.

$$ \mathrm{Output\ size}=\left\lfloor \frac{W-K}{S}\right\rfloor +1 $$

  • W: 입력 크기
  • K: 커널 크기
  • S: stride

224 x 224 > (1) 55 x 55 > (2) 27 x 27 > (5) 13 x 13 > (12) 6 x 6 [conv2d는 1만 이미지 크기를 변환함.]

따라서 위 연산이후 6x6의 이미지가 256채널로 출력된다.

  • 6 x 6 x 256 = 9216

이걸 받아서 128로 그리고 다시 1로 줄여줄 것이다.

model.classifier = nn.Sequential(
    nn.Linear(256 * 6 * 6, 128),
    nn.ReLU(),
    nn.Linear(128, 1), # 여기선 1로 빼서 확률으로 분류해줌
    nn.Sigmoid()
).to(device)
  (classifier): Sequential(
    (0): Linear(in_features=9216, out_features=128, bias=True)
    (1): ReLU()
    (2): Linear(in_features=128, out_features=1, bias=True)
    (3): Sigmoid()

위와 같이 classifier가 수정된다.

  • 이진 분류 문제에선 classifier를 1로 출력하여 확률별로 나눠주는 방법과 2로 출력하여 분류하는 방법이 존재하지만 일단 위와 같은 방법으로 출력해주었다.

학습

epochs = 10

for epoch in range(epochs):
    for phase in ['train', 'validation']:
        if phase == 'train':
            model.train()
        else:
            model.eval()

        sum_losses = 0
        sum_accs = 0

        for x_batch, y_batch in dataloaders[phase]:
            x_batch = x_batch.to(device)
            y_batch = y_batch.to(device)
            
            y_pred = model(x_batch)
            loss = nn.BCELoss()(y_pred, y_batch)

            if phase == 'train':
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

            sum_losses = sum_losses + loss
            y_bool = (y_pred >= 0.5).float()
            acc = (y_batch == y_bool).float().sum()/len(y_batch) * 100
            sum_accs = sum_accs + acc

        avg_loss = sum_losses / len(dataloaders[phase])
        avg_acc = sum_accs / len(dataloaders[phase])
        print(f'{phase:10s}: Epoch {epoch+1:4d}/{epochs} Loss: {avg_loss: .4f} Accuracy: {avg_acc:.2f}%')
  • 학습코드를 위와 같이 짜주었다.
train     : Epoch   10/10 Loss:  0.0504 Accuracy: 98.10%
validation: Epoch   10/10 Loss:  0.2864 Accuracy: 91.07%
  • accuracy가 잘 나오는 것을 확인하였다.