본문 바로가기
AI/컴퓨터 비전

[DL] 케글을 활용한 강아지 품종 분류

by 바다의 공간 2024. 9. 24.

지금까지는 1개 VS 2개 를 이용해서 문제를 풀었다면

지금은 강아지 품종 분류를 여러개를 해볼 수 있습니다.


!pip install timm
import os
import random
import shutil
import matplotlib.pyplot as plt
import glob
import torch
import torchvision
import torch.nn.functional as F
import timm
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, models, transforms
from PIL import Image
from tempfile import TemporaryDirectory
from time import time
from tqdm import tqdm
from torch.optim import lr_scheduler
#KAGGLE 내 아이디 연동
os.environ['KAGGLE_USERNAME'] = '00000'
os.environ['KAGGLE_KEY'] = '0000000000000'
!kaggle datasets download -d jessicali9530/stanford-dogs-dataset
!unzip -q /content/stanford-dogs-dataset.zip
data_root = './images'
file_root = f'{data_root}/Images'
train_root = f'{data_root}/train'
valid_root = f'{data_root}/valid'
test_root = f'{data_root}/test'

각 데이터들의 루트를 잡아줍니다.

cls_list = os.listdir(file_root)
cls_list
for folder in [train_root, valid_root, test_root]:
    if not os.path.exists(folder):
        os.makedirs(folder)
    for cls in cls_list:
        cls_folder = f'{folder}/{cls}'
        if not os.path.exists(cls_folder):
            os.makedirs(cls_folder)

for문을 돌면서 폴더를 만들어줍니다

random.seed(2024)

랜덤시드는 2024로 줍니다.

for cls in cls_list:
    file_list = os.listdir(f'{file_root}/{cls}')
    random.shuffle(file_list)
    test_ratio = 0.1
    num_file = len(file_list)

    test_list = file_list[:int(num_file*test_ratio)]
    valid_list = file_list[int(num_file*test_ratio):int(num_file*test_ratio)*2]
    train_list = file_list[int(num_file*test_ratio)*2:]

    for i in test_list:
        shutil.copyfile(f'{file_root}/{cls}/{i}', f'{test_root}/{cls}/{i}')

    for i in valid_list:
        shutil.copyfile(f'{file_root}/{cls}/{i}', f'{valid_root}/{cls}/{i}')

    for i in train_list:
        shutil.copyfile(f'{file_root}/{cls}/{i}', f'{train_root}/{cls}/{i}')

test_Ratio = 0.1 == 10%만 테스트에 쓰겠다는 말입니다.

test_file_list = glob.glob(f"{test_root}/*/*")
random.shuffle(test_file_list)

plt.figure(figsize = (20,10))
for i in range(10):
    test_img_path = os.path.join(test_file_list[i])
    ori_img = Image.open(test_img_path).convert('RGB')
    plt.subplot(2, 5, (i+1))
    plt.title(test_file_list[i].split('/')[-2])
    plt.imshow(ori_img)

plt.show()

IMG_SIZE = 224

data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(IMG_SIZE),
        transforms.RandomHorizontalFlip(0.5),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'valid': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(IMG_SIZE),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'test': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(IMG_SIZE),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])
}
image_datasets = {x: datasets.ImageFolder(os.path.join(data_root, x),
                                          data_transforms[x]) for x in ['train', 'valid', 'test']}

dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=16,
               shuffle=True, num_workers=4) for x in ['train', 'valid', 'test']}

dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid', 'test']}

class_names = image_datasets['train'].classes

print(dataset_sizes)
print(class_names)
def train_model(model, criterion, optimizer, scheduler, dataloaders, dataset_sizes, device, model_dir, model_name, num_epochs=25):
    print(device)
    since = time()
    with TemporaryDirectory() as tempdir:
        best_model_params_path = os.path.join(tempdir, 'best_model_params.pt')
        torch.save(model.state_dict(), best_model_params_path)
        best_acc = 0.0
        train_loss = []
        train_acc = []
        valid_loss = []
        valid_acc = []
        for epoch in range(num_epochs):
            print(f'Epoch {epoch+1}/{num_epochs}')
            print('-' * 10)
            for phase in ['train', 'valid']:
                if phase == 'train':
                    model.train()
                else:
                    model.eval()
                running_loss = 0.0
                running_corrects = 0
                for inputs, labels in tqdm(dataloaders[phase]):
                    inputs = inputs.to(device)
                    labels = labels.to(device)
                    optimizer.zero_grad()
                    with torch.set_grad_enabled(phase == 'train'):
                        outputs = model(inputs)
                        _, preds = torch.max(outputs, 1)
                        loss = criterion(outputs, labels)
                        if phase == 'train':
                            loss.backward()
                            optimizer.step()
                    running_loss += loss.item() * inputs.size(0)
                    running_corrects += torch.sum(preds == labels.data)
                if scheduler is not None and phase == 'train':
                    scheduler.step()
                epoch_loss = running_loss / dataset_sizes[phase]
                epoch_acc = running_corrects.double() / dataset_sizes[phase]
                print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
                if phase == 'train':
                    train_loss.append(epoch_loss)
                    train_acc.append(epoch_acc.item())
                else:
                    valid_loss.append(epoch_loss)
                    valid_acc.append(epoch_acc.item())
                    if epoch_acc > best_acc:
                        best_acc = epoch_acc
                        if not os.path.exists(model_dir):
                            os.makedirs(model_dir)
                        model_save_path = os.path.join(model_dir, f'{model_name}.pth')
                        torch.save(model.state_dict(), model_save_path)
            print()
        time_elapsed = time() - since
        print(f'Training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s')
        print(f'Best val Acc: {best_acc:.4f}')
    return [train_loss, valid_loss, train_acc, valid_acc]
data_root = './images'
model_dir = f'{data_root}/models'
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#강사님은 5로 함
NUM_EPOCHS = 5

데이터 루트는 이미지스로 상위로 하고

모델을 저장할 디렉토리를 넣고

gpu돌릴거리라서 보내줍니다.

그다음 에포크를 사용합니다.

num_class = len(os.listdir(train_root))
print(num_class)
#120종을 확인할 수 있음

클래스가 몇개인지 (폴더명이 강아지별로 있는것)

120이 나오는데 그러면 강아지들 품종이 120개가 있는것입니다.

resnet18_model = timm.create_model('resnet18', pretrained=True, num_classes=num_class).to(DEVICE)
loss_func = nn.CrossEntropyLoss()
optimizer = optim.Adam(resnet18_model.parameters(), lr=0.0002)
# 학습률 스케줄러 중 하나로, 학습률을 단계적으로 감소시키는 기능
# step_size: 학습률이 감소하는 주기 설정(예: 10 에폭마다 학습률이 조정)
# gamma: 학습률을 감소시킬 비율(예: 0.9 10 에폭마다 0.9배로 줄임)
scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.9)

레스넷중에서 레이어가 많이있지만 그 중 18을 사용하도록 합니다.

loss_func = 여러개중에 해당 확률을 리턴시켜주는것

옵티마이저는 아담을 사용하고 그 모델에 레스넷18모델을 넣어주고 러닝레이트는 0.0002를 사용했습니다.

0.0001을 사용하면 더 느려질것같아서입니다!

resnet18 = train_model(resnet18_model, loss_func, optimizer, scheduler, dataloaders, dataset_sizes,
                       DEVICE, model_dir, 'dog_resnet18', num_epochs=NUM_EPOCHS)

renet18을 학습시키기입니다

 

gpt발췌

각 파라미터들이 하는 역할

  • resnet18_model: ResNet18 모델 객체입니다. 학습할 네트워크 모델을 의미합니다.
  • loss_func: 손실 함수입니다. 예를 들어, nn.CrossEntropyLoss()는 모델의 출력값과 실제 레이블 사이의 차이를 계산해주는 함수입니다.

->차이를 계산하는 이유는 모델의 예측이 얼마나 정확한지를 평가하기 위해서 입니다 이것을 손실(loss)라고 부르고 얼마나 잘못예측했는지를 수치화한것입니다.

  • optimizer: 옵티마이저입니다. 모델의 가중치를 업데이트해주는 역할을 합니다. 여기서는 Adam 옵티마이저를 사용하고 있습니다.
  • scheduler: 학습률 스케줄러입니다. 학습이 진행되면서 학습률을 어떻게 조절할지 정해주는 역할을 합니다. StepLR은 일정한 간격으로 학습률을 감소시킵니다.
  • dataloaders: 학습 및 검증 데이터 로더입니다. 데이터를 미니배치로 나눠서 모델에 공급해줍니다.
  • dataset_sizes: 각 데이터셋(훈련, 검증, 테스트)의 크기를 나타냅니다. 정확도를 계산할 때 사용됩니다.
  • DEVICE: 학습을 실행할 디바이스(cuda 혹은 cpu)입니다. GPU가 가능할 경우 cuda에서 실행됩니다.
  • model_dir: 모델 가중치를 저장할 디렉토리 경로입니다.
  • dog_resnet18: 모델을 저장할 때 사용할 파일 이름입니다.
  • num_epochs: 학습을 몇 번 반복할지를 나타내는 파라미터입니다.

이 파라미터들이 함께 작동하여 모델을 학습하고 최적화하며, 학습이 끝난 모델을 저장합니다.

import matplotlib.pyplot as plt

plt.title("loss")
plt.plot(resnet18[0], label='train')
plt.plot(resnet18[1], label='valid')
plt.legend()
plt.show()

plt.title("acc")
plt.plot(resnet18[2], label='train')
plt.plot(resnet18[3], label='valid')
plt.legend()
plt.show()

 

 

벨리드가 높은건 숫자가 높아서 그럴 수 있습니다.

 

resnet34모델
resnet34_model = timm.create_model('resnet34', pretrained=True, num_classes=num_class).to(DEVICE)
loss_func = nn.CrossEntropyLoss()
optimizer = optim.Adam(resnet34_model.parameters(), lr=0.0002)
scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.9)
resnet34 = train_model(resnet34_model, loss_func, optimizer, scheduler, dataloaders, dataset_sizes,
                       DEVICE, model_dir, 'dog_resnet34', num_epochs=NUM_EPOCHS)

 

restnet50모델
resnet50_model = timm.create_model('resnet50', pretrained=True, num_classes=num_class).to(DEVICE)
loss_func = nn.CrossEntropyLoss()
optimizer = optim.Adam(resnet50_model.parameters(), lr=0.0002)
scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.9)
resnet50 = train_model(resnet50_model, loss_func, optimizer, scheduler, dataloaders, dataset_sizes,
                       DEVICE, model_dir, 'dog_resnet50', num_epochs=NUM_EPOCHS)
예측을 잘 했는지 확인
test_img_pth = os.path.join(test_file_list[10])
test_img_pth

 

test_data_transform = transforms.Compose([
  transforms.Resize(256),
  transforms.CenterCrop(IMG_SIZE),
  transforms.ToTensor(),
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

 

ori_img = Image.open(test_img_path).convert('RGB')
image= test_data_transform(ori_img)
x_tensor = image.to(DEVICE).unsqueeze(0)

unsqueeze로 차원을 늘려주도록 합니다.

 

 

모델 예측 
resnet18_model.eval()
with torch.no_grad():
    outputs = resnet18_model(x_tensor)
    _, y_pred = torch.max(outputs, 1)

    plt.figure(figsize=(3,3))
    plt.title(class_names[y_pred])
    plt.imshow(ori_img)
    plt.show()

 

오답

 

가장 높은값을 예측하는걸 확인합니다.

못맞추는걸 확인할 수 있습니다.

 

다음모델 34로 보면

resnet34_model.eval()
with torch.no_grad():
    outputs = resnet34_model(x_tensor)
    _, y_pred = torch.max(outputs, 1)

    plt.figure(figsize=(3,3))
    plt.title(class_names[y_pred])
    plt.imshow(ori_img)
    plt.show()

정답

이건 잘 맞춘거같습니다. 

resnet50_model.eval()
with torch.no_grad():
    outputs = resnet50_model(x_tensor)
    _, y_pred = torch.max(outputs, 1)

    plt.figure(figsize=(3,3))
    plt.title(class_names[y_pred])
    plt.imshow(ori_img)
    plt.show()

정답

모델이 좀 커지니까 레이어(파라미터)가 많아지니까 우연치않게 성능이 좋다는걸 알 수 있습니다.

반대로 모델이 작아지면 에폭을 50정도는 돌려야할것같습니다.

 

 

 

scheduler 파이토치에서 학습률 스켸줄러 중 하나로 학습률을 단계적으로 감소시키는 기능
어떤 옵티마이저를쓸지 앞에 넣어주고, 스텝 : 학습률이 감소하는 주기 설정
그래서 10바퀴 아래는 성능이 나오지 않을것입니다.
학습률을 줄여야지 학습률이 올라가면 발산되게됩니다.