티스토리 뷰

728x90
반응형

목차

     

    전이 학습(Transfer Learning)

     

    전이 학습(Transfer Learning)은 딥러닝 모델이 이미 학습한 정보를 새로운 작업에 재사용하는 방법이다.

     

    예를 들어, ImageNet이라는 대규모 데이터셋에서 학습된 모델은

     

    일반적인 이미지 분류 작업에 필요한 유용한 특성을 이미 학습했기 때문에,

     

    이를 활용해 상대적으로 작은 데이터셋(CIFAR-10)에서 학습 시간을 단축하고 성능을 높이는 데 사용할 수 있다.

    전이 학습의 중요성은 다음과 같다:

    1. 데이터 효율성: 대규모 데이터가 부족한 상황에서 모델의 일반화 능력을 향상한다.
    2. 학습 시간 단축: 사전 학습된 모델을 기반으로 학습하기 때문에 초기 단계부터 학습할 필요가 없다.
    3. 성능 향상: 기존 모델의 강력한 특성 표현력을 활용하여 더 나은 성능을 얻을 수 있다.

    이번 글에서는 미리 학습된 모델을 가지고 CIFAR-10 데이터셋을 학습시키는 법에 대해 다룬다.

     

    내가 완전히 익숙해지기 전까지는 이전 글과 마찬가지로 생략 없이 코드를 뜯어볼 생각이다

     

    선 요약

     

    이번 글에서 다룰 코드는 다음과 같다:

    import torch
    import torchvision
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader
    
    import torch.nn as nn
    import torch.optim as optim
    from tqdm import trange
    
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print(device)
    
    transform = transforms.Compose(
        [
            transforms.RandomCrop(32, padding=4),
            transforms.ToTensor(),
            transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
        ]
    )
    
    test_transform = transforms.Compose(
        [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]
    )
    
    trainset = torchvision.datasets.CIFAR10(
        root="./data", train=True, download=True, transform=transform
    )
    
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=16, shuffle=True)
    
    testset = torchvision.datasets.CIFAR10(
        root="./data", train=False, download=True, transform=test_transform
    )
    testloader = torch.utils.data.DataLoader(testset, batch_size=16, shuffle=False)
    
    model = torchvision.models.resnet18(weights="DEFAULT")
    
    print(model)
    
    model.conv1 = nn.Conv2d(
        3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False
    )
    model.fc = nn.Linear(512, 10)
    model = model.to(device)
    
    print(model)
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-2)
    
    num_epochs = 20
    ls = 2
    pbar = trange(num_epochs)
    
    for epoch in pbar:
        correct = 0
        total = 0
        running_loss = 0.0
        for data in trainloader:
    
            inputs, labels = data[0].to(device), data[1].to(device)
    
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
    
            running_loss += loss.item()
            _, predicted = torch.max(outputs.detach(), 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
        cost = running_loss / len(trainloader)
        acc = 100 * correct / total
    
        if cost < ls:
            ls = cost
            torch.save(model.state_dict(), "./models/cifar10_resnet18.pth")
    
        pbar.set_postfix({"loss": cost, "train acc": acc})
    
    model = torchvision.models.resnet18(weights=None)
    model.conv1 = nn.Conv2d(
        3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False
    )
    model.fc = nn.Linear(512, 10)
    model = model.to(device)
    model.load_state_dict(torch.load("./models/cifar10_resnet18.pth"))
    
    correct = 0
    total = 0
    with torch.no_grad():
        model.eval()
        for data in testloader:
            images, labels = data[0].to(device), data[1].to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
    print(
        "Accuracy of the network on the 10000 test images: %d %%" % (100 * correct / total)
    )

    결과는 다음과 같으며,

    Accuracy of the network on the 10000 test images: 85 %

     

    핵심 워크플로우는 다음과 같다:

     

    1. 환경 설정

      • 필요한 라이브러리 임포트 및 GPU/CPU 디바이스 설정.
    2. 데이터 준비

      • CIFAR-10 데이터셋 다운로드.
      • 학습 데이터는 증강(RandomCrop) 및 정규화, 테스트 데이터는 정규화만 수행.
      • 데이터로더(DataLoader)로 배치 단위로 데이터 불러오기.
    3. 모델 수정

      • ResNet-18 사전 학습 모델 로드.
      • CIFAR-10에 맞게 입력(conv1)과 출력층(fc) 수정.
    4. 학습 설정

      • 손실 함수: CrossEntropyLoss
      • 최적화 함수: Adam (학습률 및 가중치 감소 설정).
    5. 모델 학습

      • 학습 반복(Epochs) 동안:

        • 데이터 배치 단위로 학습.
        • 손실 값 계산 → 그래디언트 계산 → 모델 가중치 업데이트.
        • 최소 손실 모델 저장(torch.save).
    6. 모델 평가

      • 저장된 모델 로드.
      • 테스트 데이터로 정확도 계산(torch.no_grad로 평가).
    7. 결과 출력

      • 테스트 데이터셋 정확도 출력.

    이제 코드를 살펴보자.

     

    라이브러리 및 디바이스 설정

    import torch
    import torchvision
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader
    
    import torch.nn as nn
    import torch.optim as optim
    from tqdm import trange
    
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print(device)

    라이브러리

     

    1. torch
      PyTorch는 딥러닝 모델을 구현하기 위한 오픈소스 라이브러리이다.
      동적 그래프 구조를 지원하여 데이터와 연산 그래프를 유연하게 조작할 수 있다.
      자동 미분(Autograd)을 제공해 기울기 계산을 자동화하며, GPU를 활용한 병렬 연산으로 성능을 최적화한다.
    2. torchvision
      컴퓨터 비전 작업을 위한 PyTorch 라이브러리이다.
      여러 유명한 이미지 데이터셋(CIFAR-10, ImageNet 등)과 사전 학습된 모델(ResNet, VGG 등)을 제공한다.
      데이터 증강과 이미지 전처리에 사용할 수 있는 도구(transform)도 포함되어 있다.
    3. torchvision.transforms
      이미지 데이터에 대한 변환(transform)을 정의하는 모듈이다.
      데이터 증강(예: 이미지 회전, 자르기)과 정규화(평균 및 표준편차를 이용한 값 조정)에 주로 사용된다.
      여러 변환을 결합하여 transforms.Compose로 하나의 파이프라인처럼 처리할 수 있다.
    4. torch.utils.data.DataLoader
      데이터를 배치(batch) 단위로 로드하고, 학습 데이터를 효율적으로 관리하는 도구이다.
      데이터셋을 쉽게 순회(iterate)할 수 있도록 지원하며, shuffle 옵션으로 데이터를 무작위로 섞을 수 있다.
      멀티스레딩을 이용해 데이터를 병렬로 불러올 수도 있다.
    5. torch.nn
      인공 신경망(Artificial Neural Network) 모델을 설계하기 위한 모듈이다.
      선형 계층(nn.Linear), 합성곱 계층(nn.Conv2d), 활성화 함수(ReLU, Sigmoid 등)와 같은 다양한 구성 요소를 포함한다.
      모델을 계층적으로 정의하고, 학습 가능한 파라미터를 자동으로 관리한다.
    6. torch.optim
      신경망 학습을 위한 최적화 알고리즘을 제공한다.
      SGD, Adam, RMSprop 등 다양한 최적화 방법이 포함되어 있으며, 학습률 및 가중치 감소 등 하이퍼파라미터를 설정할 수 있다.
    7. tqdm
      반복문 진행 상황을 시각적으로 표시하는 라이브러리이다.
      진행률 바(progress bar)를 제공하며, trange는 range와 동일하지만 진행률 바를 출력한다.
      학습 중 손실(loss)과 정확도 등의 정보를 실시간으로 확인할 수 있다.

     

    디바이스 설정

     

    1. torch.device
      PyTorch에서 연산에 사용될 디바이스를 지정하는 객체이다.
      cuda:0은 첫 번째 GPU를 의미하며, GPU가 없는 경우 CPU를 사용한다.
    2. cuda.is_available()
      CUDA 지원 여부를 확인하는 함수이다. GPU를 사용할 수 있으면 True, 그렇지 않으면 False를 반환한다.
    3. 결과 출력
      print(device)는 현재 설정된 디바이스(GPU 또는 CPU)를 출력한다.
      예를 들어, GPU가 사용 가능하면 cuda:0이 출력되고, 그렇지 않으면 cpu가 출력된다.

     

    데이터 전처리 및 로드

     

    데이터 전처리

    transform = transforms.Compose(
        [
            transforms.RandomCrop(32, padding=4),
            transforms.ToTensor(),
            transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
        ]
    )
    
    test_transform = transforms.Compose(
        [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]
    )
    1. transforms.Compose

      • 여러 전처리 작업을 연속적으로 수행할 수 있도록 묶는 역할을 한다.
      • 학습용 데이터는 다양한 변환 작업을 적용하여 일반화 성능을 높이고, 테스트 데이터는 필요한 최소한의 변환만 적용한다.
    2. transforms.RandomCrop(32, padding=4)

      • 이미지의 크기를 무작위로 32x32로 잘라내고, 여백(padding)을 4픽셀 추가하여 잘라낸다.
      • 데이터 증강(Data Augmentation)을 통해 모델이 다양한 변형 상황에 대해 학습할 수 있도록 돕는다.
      • 예를 들어, CIFAR-10 이미지의 객체가 이미지 중앙에만 위치하지 않는 상황도 학습에 반영한다.
    3. transforms.ToTensor()

      • 이미지를 텐서(Tensor)로 변환하며, 값의 범위를 [0, 255]에서 [0.0, 1.0]으로 정규화한다.
      • PyTorch 모델이 텐서를 입력으로 받기 때문에 이 변환이 필요하다.
    4. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))

      • RGB 채널별 평균(mean)과 표준편차(std)를 사용해 이미지를 정규화한다.
      • 평균과 표준편차를 각각 0.5로 설정했으므로, 정규화 후 각 픽셀 값은 [-1, 1] 범위로 변환된다.
      • 정규화를 통해 학습 과정에서 수렴 속도를 높이고 안정적인 학습이 가능해진다.

     

    데이터 로드

    trainset = torchvision.datasets.CIFAR10(
        root="./data", train=True, download=True, transform=transform
    )
    
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=16, shuffle=True)
    
    testset = torchvision.datasets.CIFAR10(
        root="./data", train=False, download=True, transform=test_transform
    )
    testloader = torch.utils.data.DataLoader(testset, batch_size=16, shuffle=False)
    1. CIFAR-10 데이터셋

      • CIFAR-10은 10개의 클래스(비행기, 자동차, 새, 고양이, 사슴, 개, 개구리, 말, 배, 트럭)로 구성된 32x32 크기의 컬러 이미지 데이터셋이다.
      • 학습용 데이터셋(train=True)과 테스트용 데이터셋(train=False)으로 나뉜다.
    2. torchvision.datasets.CIFAR10

      • PyTorch에서 제공하는 데이터셋 클래스로, CIFAR-10 데이터를 자동으로 다운로드하고 불러올 수 있다.
      • root="./data"는 데이터를 저장할 경로를 지정한다.
      • transform은 데이터를 불러올 때 적용할 전처리 방식을 정의한다.
    3. torch.utils.data.DataLoader

      • 데이터를 효율적으로 배치(batch) 단위로 묶어서 처리하는 클래스이다.
      • batch_size=16은 한 번에 처리할 이미지 개수를 설정한다. 이 경우 배치당 16개의 이미지를 모델에 입력한다.
      • shuffle=True는 데이터를 무작위로 섞어 학습에 사용하며, 과적합(overfitting)을 방지하고 학습의 일반화 성능을 높인다.
      • 테스트 데이터의 경우 shuffle=False를 설정해 데이터 순서를 유지한다.

     

    왜 데이터 전처리가 중요한가?

     

    1. 데이터 증강(Data Augmentation): RandomCrop과 같은 데이터 증강 기법은 모델이 다양한 데이터 분포를 학습하게 하여 일반화 성능을 높인다.
    2. 정규화(Normalization): Normalize는 학습 안정성을 향상하며, 모든 입력 데이터가 유사한 분포를 갖도록 보장한다.
    3. 효율적 처리: DataLoader는 GPU를 최대한 활용할 수 있도록 데이터를 배치 단위로 묶어 빠르고 효율적으로 처리한다.

     

    모델 준비

    model = torchvision.models.resnet18(weights="DEFAULT")
    • ResNet-18은 ImageNet 데이터셋으로 학습된 사전 학습 모델이다. 이 모델은 18개의 계층으로 구성된 Residual Network로, 딥러닝에서 발생할 수 있는 기울기 소실(Vanishing Gradient) 문제를 해결하기 위해 설계되었다.
    • Residual Network는 잔차(residual) 연결을 통해 깊은 신경망에서 정보를 효과적으로 전달한다. 이는 입력값을 일부 계층의 출력에 더해 주는 방식으로, 학습이 어려운 깊은 신경망에서 안정적인 학습을 가능하게 한다.
    • 이 코드에서는 사전 학습된 가중치(weight)를 활용하기 위해 "DEFAULT" 옵션을 사용하며, 이는 ImageNet 데이터셋으로 학습된 가중치를 불러온다.
    model.conv1 = nn.Conv2d(
        3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False
    )
    model.fc = nn.Linear(512, 10)
    model = model.to(device)
    model.conv1 = nn.Conv2d(
        3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False
    )

     

    • ResNet-18 모델의 첫 번째 합성곱 계층(conv1)은 원래 ImageNet 데이터셋(224x224 크기)을 처리하도록 설계되었다.
    • CIFAR-10 데이터셋의 이미지 크기는 32x32로 훨씬 작으므로, 기존의 conv1 필터를 다음과 같이 조정한다:

      • kernel_size: 필터 크기를 (7, 7)에서 (3, 3)으로 축소한다. 이는 작은 이미지에 더 적합한 필터 크기이다.
      • stride: 스트라이드를 (2, 2)에서 (1, 1)로 변경하여 입력 이미지의 크기를 지나치게 축소하지 않도록 한다.
      • padding: padding=(1, 1)을 설정하여 필터가 입력 이미지의 가장자리를 처리할 때 크기를 유지한다.
      • bias=False: 바이어스를 사용하지 않아 가중치 초기화를 간소화한다.
    • 이러한 변경은 CIFAR-10 데이터셋과 모델의 구조적 차이를 해결하고 효과적으로 학습할 수 있도록 한다.
    model.fc = nn.Linear(512, 10)
    • ResNet-18 모델의 마지막 출력층(fc)은 원래 1000개의 노드로 구성되어 있다. 이는 ImageNet 데이터셋이 1000개의 클래스(개, 고양이, 자동차 등)를 포함하기 때문이다.
    • CIFAR-10 데이터셋은 10개의 클래스만 포함하므로, 출력층을 다음과 같이 변경한다:
      • 입력 노드 수: ResNet-18의 마지막 Global Average Pooling(GAP) 계층의 출력 크기는 512이므로 입력 노드는 512로 설정한다.
      • 출력 노드 수: CIFAR-10의 클래스 수에 맞춰 출력 노드를 10개로 설정한다.
      • 변경된 출력층은 새로운 데이터셋에 맞춰 학습되며, 이전 계층의 사전 학습된 가중치와 함께 사용된다.
    model = model.to(device)
    • 모델을 GPU 또는 CPU로 전송한다. device는 사용 가능한 하드웨어에 따라 자동으로 선택된다.
    • GPU를 사용할 경우 학습 및 추론 속도가 크게 향상된다.

     

    구조 변경의 이유

     

    1. 데이터셋 차이:

      • ResNet-18은 원래 224x224 크기의 ImageNet 이미지를 처리하도록 설계되었다. CIFAR-10 데이터는 크기가 작기 때문에 모델의 초반 계층(특히 conv1)을 조정해야 한다.
    2. 출력 클래스 수 차이:

      • ImageNet은 1000개의 클래스를, CIFAR-10은 10개의 클래스를 갖는다. 따라서 CIFAR-10에 맞게 마지막 Fully Connected Layer를 재구성해야 한다.
    3. 사전 학습 가중치 활용:

      • 모델의 초기 계층(특히 컨볼루션 계층)들은 사전 학습된 가중치를 통해 이미 유용한 이미지 특징(에지, 패턴 등)을 학습한 상태이다. 이를 재사용함으로써 학습 속도를 높이고 더 좋은 초기 성능을 얻을 수 있다.

     

    결과적으로 이러한 구조 변경은 모델을 CIFAR-10 데이터셋에 맞게 재설계하는 과정이다. 

     

    이는 사전 학습된 ResNet-18의 강점을 활용하면서도 CIFAR-10의 특성에 적합한 새로운 학습을 가능하게 한다.

     

    손실 함수와 최적화 함수 설정

    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-2)

    CrossEntropyLoss

     

    • CrossEntropyLoss는 다중 클래스 분류 문제에서 사용하는 손실 함수이다.
    • 모델이 예측한 확률 분포와 실제 정답 레이블 간의 차이를 계산하여 손실 값을 구한다.
    • 이 함수는 내부적으로 소프트맥스(Softmax)와 음의 로그 우도(Negative Log-Likelihood, NLL)를 결합한 형태이다.

      • 소프트맥스: 모델의 출력 값을 확률 분포로 변환한다.
      • 음의 로그 우도(Negative Log-Likelihood, NLL): 정답 클래스의 확률 값을 음의 로그로 계산하여 손실 값으로 반환한다.
    • 예를 들어, CIFAR-10 데이터셋에서 입력 이미지가 고양이(정답 레이블: 3) 일 때, 모델이 고양이에 대한 확률을 높게 예측할수록 손실 값이 작아진다.
    • 이 함수는 모델이 올바르게 학습하도록 하는 핵심 요소로, 클래스 간의 확률 차이를 명확히 조정하는 데 유용하다.

     

    Adam Optimizer

     

    • Adam(Adaptive Moment Estimation)은 신경망 학습에서 가장 널리 사용되는 최적화 알고리즘 중 하나이다.
    • 확률적 경사 하강법(SGD)을 기반으로 하며, 학습 속도를 가속화하고 안정적으로 수렴하도록 설계되었다.
    • Adam은 다음 두 가지를 사용해 학습률을 자동으로 조정한다.

      • 모멘텀(Momentum): 이전 기울기 정보를 이용해 학습 방향을 조정하고, 진동을 줄인다.
      • 적응 학습률(Adaptive Learning Rate): 각 파라미터마다 학습률을 개별적으로 조정해 빠른 수렴을 유도한다.
    • Adam의 주요 장점:

      • 학습률을 수동으로 조정할 필요가 적다.
      • 희소한 기울기를 다루거나, 불안정한 손실 함수를 가진 모델에서도 잘 작동한다.

     

    학습률 (lr=1e-4)

     

    • 학습률은 모델이 한 번의 업데이트에서 얼마나 큰 폭으로 파라미터를 조정할지 결정한다.
    • lr=1e-4는 학습 속도를 설정하는 값으로, 너무 크면 수렴하지 않고, 너무 작으면 학습 속도가 느려진다.
    • 초기 학습 단계에서는 적당히 작은 학습률이 안정적인 수렴에 유리하다.

     

    가중치 감소 (weight_decay=1e-2)

     

    • weight_decay는 가중치 규제(Regularization)로, 과적합(Overfitting)을 방지하기 위한 기술이다.
    • 이는 L2 정규화와 유사하며, 모델의 가중치 값이 지나치게 커지지 않도록 제약을 준다.
    • 가중치 값이 너무 크면 모델이 훈련 데이터에 과도하게 적합되어 일반화 성능이 떨어지게 된다.
    • weight_decay=1e-2는 손실 함수에 가중치의 제곱합을 추가하여 모델의 가중치를 작게 유지하도록 유도한다.

     

    요약

     

    • CrossEntropyLoss는 모델의 예측 확률과 실제 레이블 간의 차이를 줄이는 역할을 한다.
    • Adam은 적응형 학습률과 모멘텀을 사용해 빠르고 안정적으로 학습을 진행한다.
    • 학습률과 가중치 감소는 모델이 올바르게 학습하고 과적합을 방지하는 데 중요한 하이퍼파라미터이다.

     

    모델 학습

    num_epochs = 20
    ls = 2
    pbar = trange(num_epochs)
    
    for epoch in pbar:
        correct = 0
        total = 0
        running_loss = 0.0
        for data in trainloader:
    
            inputs, labels = data[0].to(device), data[1].to(device)
    
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
    
            running_loss += loss.item()
            _, predicted = torch.max(outputs.detach(), 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
        cost = running_loss / len(trainloader)
        acc = 100 * correct / total
    
        if cost < ls:
            ls = cost
            torch.save(model.state_dict(), "./models/cifar10_resnet18.pth")
    
        pbar.set_postfix({"loss": cost, "train acc": acc})

    학습 준비

    num_epochs = 20
    ls = 2
    pbar = trange(num_epochs)

     

    • num_epochs: 모델을 학습할 전체 에포크 수를 지정한다. 여기서는 20번의 반복 학습을 진행한다.
    • ls: 손실 값의 최솟값을 추적하기 위한 변수로 초기값을 2로 설정한다. CIFAR-10의 손실 값이 일반적으로 2 미만이므로 이렇게 설정한다.
    • pbar: trange는 학습 과정을 시각적으로 보여주는 진행 표시줄을 생성한다.
    for epoch in pbar:
    • epoch: 현재 학습 중인 에포크를 나타낸다. num_epochs만큼 반복하여 전체 학습을 진행한다.
    correct = 0
    total = 0
    running_loss = 0.0
    • correct: 현재 에포크 동안 모델이 올바르게 예측한 샘플 수를 저장한다.
    • total: 현재 에포크 동안 처리한 전체 샘플 수를 저장한다.
    • running_loss: 현재 에포크 동안 누적된 손실 값을 저장한다.
    for data in trainloader:
    • trainloader: CIFAR-10 학습 데이터를 배치 단위로 반복해서 불러온다.
    • data: 한 번의 반복에서 입력 이미지(inputs)와 정답 라벨(labels)을 포함한다.

     

    입력 데이터를 디바이스로 이동

    inputs, labels = data[0].to(device), data[1].to(device)
    • data[0]: 입력 이미지 데이터를 나타낸다.
    • data[1]: 해당 이미지의 정답 라벨 데이터를 나타낸다.
    • to(device): 데이터를 GPU 또는 CPU로 이동시킨다.

     

    그래디언트 초기화

    optimizer.zero_grad()
    • 이전 배치에서 계산된 그래디언트를 초기화한다. PyTorch는 기본적으로 그래디언트를 누적하기 때문에, 이를 초기화하지 않으면 이전 배치의 그래디언트가 현재 배치에 영향을 미치게 된다.

     

    모델 출력 계산

    outputs = model(inputs)
    • 학습 데이터(inputs)를 모델에 통과시켜 출력값(outputs)을 계산한다.

     

    손실 계산

    loss = criterion(outputs, labels)
    • criterion: 손실 함수 객체로, 여기서는 CrossEntropyLoss를 사용한다.
    • outputs: 모델이 예측한 값.
    • labels: 실제 정답 값.
      CrossEntropyLoss는 출력값과 정답 값의 차이를 기반으로 손실을 계산한다.

     

    역전파 계산

    loss.backward()
    • 역전파(Backpropagation): 손실 값을 기준으로 각 파라미터에 대한 그래디언트를 계산한다.
    • 이 과정은 체인 룰(Chain Rule)을 사용해 모델의 모든 파라미터에 대해 미분을 수행한다.

     

    파라미터 업데이트

    optimizer.step()
    • optimizer: 파라미터를 업데이트하는 최적화 도구다. 여기서는 Adam을 사용한다.
    • 이 단계에서 계산된 그래디언트를 이용해 모델의 가중치를 업데이트한다.

     

    손실 및 정확도 계산

    running_loss += loss.item()
    _, predicted = torch.max(outputs.detach(), 1)
    total += labels.size(0)
    correct += (predicted == labels).sum().item()
    • running_loss: 현재 배치의 손실 값을 누적한다.
    • torch.max: 모델이 예측한 클래스에서 가장 높은 점수(확률)를 가진 클래스를 선택한다.
    • outputs.detach(): 그래디언트를 추적하지 않고 값을 가져온다.
    • total: 전체 샘플 수를 누적한다.
    • correct: 모델이 올바르게 예측한 샘플 수를 누적한다.

     

    에포크 손실 및 정확도 계산

    cost = running_loss / len(trainloader)
    acc = 100 * correct / total
    • cost: 현재 에포크 동안의 평균 손실 값을 계산한다.
    • acc: 현재 에포크 동안의 정확도를 백분율로 계산한다.

     

    모델 저장

    if cost < ls:
        ls = cost
        torch.save(model.state_dict(), "./models/cifar10_resnet18.pth")
    • 조건: 현재 에포크의 평균 손실 값(cost)이 최소 손실 값(ls) 보다 작으면 모델을 저장한다.
    • torch.save: 모델의 파라미터(state_dict)를 지정한 경로에 저장한다.

     

    진행 상태 업데이트

    pbar.set_postfix({"loss": cost, "train acc": acc})
    • pbar 진행 표시줄에 현재 에포크의 평균 손실 값(loss)과 정확도(train acc)를 표시한다.

     

    요약

     

    1. 학습 데이터를 GPU 또는 CPU로 이동시킨다.
    2. 이전 그래디언트를 초기화하고, 모델을 통해 출력을 계산한다.
    3. 출력값과 실제 정답 값 간의 손실을 계산하고 역전파를 수행한다.
    4. 최적화 알고리즘을 통해 모델의 파라미터를 업데이트한다.
    5. 손실 값이 최소일 때 모델의 가중치를 저장한다.
    6. 에포크 손실과 정확도를 출력하며 학습 상태를 시각적으로 표시한다.

     

    모델 평가

     

    모델 초기화 및 가중치 로드

    model = torchvision.models.resnet18(weights=None)
    model.conv1 = nn.Conv2d(
        3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False
    )
    model.fc = nn.Linear(512, 10)
    model = model.to(device)
    model.load_state_dict(torch.load("./models/cifar10_resnet18.pth"))
    • weights=None: ResNet-18 모델의 가중치를 초기화한다. 사전 학습된 가중치를 사용하지 않으며, 완전히 새로운 상태로 모델을 생성한다.
    • model.conv1: CIFAR-10 데이터는 입력 이미지 크기가 32x32이므로 conv1의 커널 크기를 3x3으로 조정하고 스트라이드와 패딩을 설정한다. 이는 작은 입력 이미지에서도 적절히 특성을 추출할 수 있도록 한다.
    • model.fc: CIFAR-10 데이터는 10개의 클래스를 가지고 있으므로 출력층의 노드를 10개로 변경한다. 이는 모델이 CIFAR-10에 맞는 출력값을 예측하도록 한다.
    • torch.load: 학습 중 손실 값이 최소일 때 저장된 가중치 파일(cifar10_resnet18.pth)을 불러온다.
    • load_state_dict: 저장된 가중치를 모델에 로드하여 동일한 네트워크 구조에서 사용한다.

     

    평가 및 정확도 출력

    correct = 0
    total = 0
    with torch.no_grad():
        model.eval()
        for data in testloader:
            images, labels = data[0].to(device), data[1].to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            
    print(
        "Accuracy of the network on the 10000 test images: %d %%" % (100 * correct / total)
    )
    • torch.no_grad: 평가 시에는 그래디언트 계산이 필요하지 않다. 이를 비활성화하여 메모리 사용량을 줄이고 계산 속도를 최적화한다.

      • 학습 중에는 그래디언트를 계산하고 저장하지만, 평가 단계에서는 이 과정이 불필요하다.
    • model.eval(): 모델을 평가 모드로 전환한다. 드롭아웃과 배치 정규화 같은 레이어를 학습 중과 다르게 동작하도록 설정한다.

      • 드롭아웃: 학습 중에는 일부 뉴런을 랜덤 하게 비활성화하지만, 평가 중에는 모두 활성화한다.
      • 배치 정규화: 학습 중에는 미니배치 단위의 통계를 사용하지만, 평가 중에는 전체 학습 데이터의 통계를 사용한다.
    • for data in testloader: 테스트 데이터를 미니배치 단위로 가져온다.
    • data[0].to(device)와 data[1].to(device): 입력 이미지(data[0])와 정답 레이블(data[1])을 GPU 또는 CPU로 전송한다.
    • model(images): 모델에 입력 이미지를 전달하여 출력값을 계산한다.
    • torch.max(outputs.data, 1): 출력값 중 가장 높은 확률을 가진 클래스(예측값)를 선택한다.

      • outputs.data: 모델 출력값을 가져온다.
      • 1: 두 번째 차원(클래스)에서 최댓값의 인덱스를 반환한다.
    • total += labels.size(0): 총 데이터 수를 누적한다.
    • correct += (predicted == labels).sum().item(): 예측값이 정답과 일치하는 경우를 누적한다.
    • correct / total: 정확도를 계산한다. 모델이 맞춘 샘플 수를 총 샘플 수로 나눈 비율이다.
    • 100 * correct / total: 정확도를 백분율로 변환한다.
    • CIFAR-10 테스트 데이터셋(10,000개)에 대해 모델이 얼마나 정확히 예측했는지를 출력한다.

     

    요약

     

    1. 모델 구조를 초기화하고 저장된 가중치를 불러온다.
    2. torch.no_grad를 사용해 평가 모드로 전환하고 그래디언트 계산을 비활성화한다.
    3. 테스트 데이터셋을 순회하며 모델의 예측 결과를 얻는다.
    4. 모델의 예측값과 정답을 비교하여 정확도를 계산한다.

     

    정리

     

    이렇게 해서 전이학습을 통한 모델 성능 개선에 대한 코드를 뜯어보았다.

     

    현실적으로 내가 바닥부터 모델을 만들어 갈 가능성은 매우 낮으니, 전이학습은 특히나 중요하게 느껴졌던 것 같다.

     

    커리큘럼을 따라 하나씩 구현하고, 논문도 읽을 수 있게 되면 좀 더 재미있어지지 않을까 싶다.

     

    끝!

    반응형
    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    링크
    «   2025/01   »
    1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29 30 31
    글 보관함