<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

#모델 불러오기 함수
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 |


미세조정 안한 분류기의 경우 마지막 출력 데이터 차원 크기(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")

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