<aside> 📢 소규모 개와 고양이 데이터세트를 활용해 VGG-16 모델을 미세 조정해 개와 고양이 이미지를 분류

</aside>

pet 데이터세트: 약 8,000개의 훈련용 데이터와 약 2,000개의 테스트 데이터로 이루어져 있음

#하이퍼파라미터 선언 및 이미지 변환
from torch.utils.data import DataLoader
from torchvision.datasets import ImageFolder
from torchvision import transforms

hyperparams = {
    "batch_size": 4, #배치 크기 4
    "learning_rate": 0.0001, #학습률 0.0001
    "epochs": 5, #에폭 5회
    "transform": transforms.Compose( #이미지 변환
        [
            transforms.Resize(256), #이미지 데이터 크기를 256X256으로 조절
#탐지하려는 객체가 중앙에 위치할 확률이 높으므로 불필요한 지역 특징을 제거하기 위해 224X224 크기보다 더 큰 이미지 크기로 조절하고 자르기 수행
#곧바로 크기 조절할 경우 검출하려는 객체의 크기가 더 작아지므로 객체 구별이 어려워짐
#입력 이미지보다 약간 더 큰 이미지 크기로 변경한 후, 이미지 테두리 부분을 잘라내면 객체의 특징을 최대한 유지
            transforms.CenterCrop(224), #224X224로 중앙 자르기 수행
            transforms.ToTensor(),
            transforms.Normalize( #각 채널에 평균과 표준편차로 정규화를 적용
                mean=[0.48235, 0.45882, 0.40784], #이미지넷 데이터세트에 대한 대푯값
                std=[1.0/255.0, 1.0/255.0, 1.0/255.0] #RGB값을 0.0 ~ 1.0으로 정규화한 값
            )
        ]
    )
}
#이미지 폴더 데이터세트: 이미지와 레이블을 자동으로 인식
train_dataset = ImageFolder("C:\\\\Users\\\\yuhyu\\\\Desktop\\\\CODE\\\\pytorch\\\\datasets\\\\pet/train", transform=hyperparams["transform"])
test_dataset = ImageFolder("C:\\\\Users\\\\yuhyu\\\\Desktop\\\\CODE\\\\pytorch\\\\datasets\\\\pet/test", transform=hyperparams["transform"])

train_dataloader = DataLoader(train_dataset, batch_size=hyperparams["batch_size"], shuffle=True, drop_last=True)
test_dataloader = DataLoader(test_dataset, batch_size=hyperparams["batch_size"], shuffle=True, drop_last=True)
#데이터 시각화
import numpy as np
from matplotlib import pyplot as plt

mean=[0.48235, 0.45882, 0.40784]
std=[1.0/255.0, 1.0/255.0, 1.0/255.0]

images, labels = next(iter(train_dataloader)) #첫 번째 배치의 이미지와 레이블을 추출(동일한 개수의 데이터)
for image, label in zip(images, labels):
		#ToTensor를 통해 PIL.Image(ndarray) >> Tensor 객체로 변환
		#Tensor 객체는 이미지 형태를 (HXWXC) >> (CXHXW)로 변환 : 다시 (HXWXC)로 변환해야 함
		#numpy 메서드를 통해 ndarray 배열로 변경하고 transpose 메서드로 축의 순서를 변경
		#C가 0, H가 1, W가 2의 순서로 할당되어 있으므로 [1,2,0]으로 설정
    image = image.numpy().transpose((1, 2, 0))
    #정규화 연산으로 인해 이미지 픽셀들이 재조정됐으므로 반대의 순서로 연산을 적용
    #이미지에 표준편차를 곱한 후, 평균값을 더함
    #텐서 변환 클래스에서 픽셀 범위가 [0, 255]에서 [0.0, 1.0]으로 재조정됐으므로 
    #픽셀 배율을 255배 곱한 후, uint8형식으로 변환
    image = ((std * image + mean) * 255).astype(np.uint8)

    plt.imshow(image)
    plt.title(train_dataset.classes[int(label)]) #레이블도 텐서로 변환되었기에 int로 변환한 후 classes 메서드로 매핑
    plt.show()
    break

Untitled

#모델 불러오기 함수
model = models.vgg16(weights="VGG16_Weights.IMAGENET1K_V1")
#분류기의 여섯 번째 선형 계층에 접근. 출력 데이터 차원 크기에 train_dataset의 클래스 개수 입력
model.classifier[6] = nn.Linear(4096, len(train_dataset.classes))

VGG16_Weights.IMAGENET1K_V1

속성
acc@1 71.592
acc@5 90.382
입력 이미지 최소 크기 32X32
매개변수의 수 138,357,544
카테고리(클래스) 수 1,000
GFLOPS 15.47
파일 크기 527.8MB

Untitled

미세조정 안한 분류기의 경우 마지막 출력 데이터 차원 크기(out_features)는 총 1000개로 분류하는 카테고리의 갯수를 의미한다.

미세조정 안한 분류기의 경우 마지막 출력 데이터 차원 크기(out_features)는 총 1000개로 분류하는 카테고리의 갯수를 의미한다.

특징 추출(features), 평균 풀링(avgpool), 분류기(classifier)로 구성됨

device = "cuda" if torch.cuda.is_available() else "cpu"
model = model.to(device)
criterion = nn.CrossEntropyLoss().to(device)
optimizer = optim.SGD(model.parameters(), lr=hyperparams["learning_rate"])
# 지정된 epoch 수만큼 반복합니다.
for epoch in range(hyperparams["epochs"]):
    cost = 0.0

    # 각 epoch마다 훈련 데이터로더에서 이미지와 클래스를 가져와서 학습을 수행합니다.
    for images, classes in train_dataloader:
        # 데이터와 레이블을 지정된 장치로 이동시킵니다.
        images = images.to(device)
        classes = classes.to(device)

        # 모델에 이미지를 전달하여 출력을 얻습니다.
        output = model(images)
        # 출력과 실제 클래스 간의 손실을 계산합니다.
        loss = criterion(output, classes)

        # 이전의 그래디언트를 초기화합니다.
        optimizer.zero_grad()
        # 역전파를 통해 그래디언트를 계산합니다.
        loss.backward()
        # 최적화를 수행하여 모델의 파라미터를 업데이트합니다.
        optimizer.step()

        # 현재까지의 손실을 누적합니다.
        cost += loss

    # epoch당 평균 손실을 계산합니다.
    cost = cost / len(train_dataloader)
    # 현재 epoch의 정보를 출력합니다.
    print(f"Epoch : {epoch+1:4d}, Cost : {cost:.3f}")

# 모델을 평가 모드로 전환합니다.
with torch.no_grad():
    model.eval()

    accuracy = 0.0
    # 테스트 데이터로더를 사용하여 모델의 정확도를 계산합니다.
    for images, classes in test_dataloader:
        # 데이터와 레이블을 지정된 장치로 이동시킵니다.
        images = images.to(device)
        classes = classes.to(device)

        # 모델에 이미지를 전달하여 출력을 얻습니다.
        outputs = model(images)
        # 출력을 확률로 변환합니다.
        probs = F.softmax(outputs, dim=-1)
        # 가장 높은 확률을 가진 클래스를 예측합니다.
        outputs_classes = torch.argmax(probs, dim=-1)

        # 정확히 예측된 클래스의 개수를 계산합니다.
        accuracy += int(torch.eq(classes, outputs_classes).sum())

    # 전체 테스트 데이터셋에 대한 정확도를 출력합니다.
    print(f"acc@1 : {accuracy / (len(test_dataloader) * hyperparams['batch_size']) * 100:.2f}%")

# 훈련된 모델의 가중치를 저장합니다.
torch.save(model.state_dict(), "../models/VGG16.pt")
print("Saved the model weights")

Untitled

이미 개와 고양이 데이터로 사전 학습이 진행된 모델이므로 소규모 데이터세트와 작은 에폭으로도 오차가 안정적으로 감소한다.